Initial import.
[featherfall2.git] / src / yuu / core.js
1 /* Copyright 2014 Yukkuri Games
2 Licensed under the terms of the GNU GPL v2 or later
3 @license http://www.gnu.org/licenses/gpl-2.0.html
4 @source: http://yukkurigames.com/yuu/
5 */
6
7 (function (yuu) {
8 "use strict";
9
10
11 yuu.require = function (m) {
12 try { return require(m); }
13 catch (exc) { return null; }
14 };
15
16 if (!Math.sign)
17 require("./pre");
18
19 var yT = this.yT || require("./yT");
20 var yf = this.yf || require("./yf");
21 var gui = yuu.require("nw.gui");
22 var fs = yuu.require("fs");
23 var stringLerp = this.stringLerp || yuu.require("string-lerp");
24
25 var initHooks = [];
26 var initOptions = null;
27
28 if (typeof document !== "undefined") {
29 var scripts = document.getElementsByTagName('script');
30 var path = yf.last(scripts).src.split('?')[0];
31 yuu.PATH = path.split('/').slice(0, -1).join('/') + '/';
32 } else {
33 yuu.PATH = "file://" + escape(module.__dirname) + "/";
34 }
35
36 yuu.registerInitHook = initHooks.push.bind(initHooks);
37 /** Register a hook to be called during Yuu initialization
38
39 Hooks are called in registration order with the module and
40 the options dictionary passed to the init method. (This is
41 also set to the module.)
42 */
43
44 function showError (exc, kind) {
45 var prefix = "yuu-" + (kind || "") + "error";
46 yuu.logError(exc);
47 var dialog = document.getElementById(prefix);
48 var errorMessage = document.getElementById(prefix + "-message");
49 if (errorMessage)
50 errorMessage.textContent = exc.message;
51 var errorStack = document.getElementById(prefix + "-stack");
52 if (errorStack)
53 errorStack.textContent = exc.message + "\n\n" + exc.stack;
54 return dialog;
55 }
56 yuu.showError = showError;
57
58 function fatalError (exc) {
59 var dialog = showError(exc, "fatal-");
60 if (dialog)
61 dialog.style.display = "block";
62 if (gui) {
63 gui.Window.get().show();
64 gui.Window.get().focus();
65 }
66 throw exc;
67 }
68
69 yuu.init = function (options) {
70 /** Initialize Yuu and call all registered hooks
71 */
72
73 if (gui) {
74 var win = gui.Window.get();
75 var nativeMenuBar = new gui.Menu({ type: "menubar" });
76 if (nativeMenuBar.createMacBuiltin) {
77 nativeMenuBar.createMacBuiltin(
78 document.title, { hideEdit: true });
79 win.menu = nativeMenuBar;
80 }
81 var wkdoc = document;
82 win.on("minimize", function () {
83 var ev = new Event("visibilitychange");
84 wkdoc.hidden = true;
85 wkdoc.dispatchEvent(ev);
86 });
87 win.on("restore", function () {
88 var ev = new Event("visibilitychange");
89 wkdoc.hidden = false;
90 wkdoc.dispatchEvent(ev);
91 });
92 }
93
94 return new Promise(function (resolve) {
95 // TODO: Some kind of loading progress bar.
96 initOptions = options || {};
97 yuu.log("messages", "Initializing Yuu engine.");
98 var promises = [];
99 yf.each(function (hook) {
100 promises.push(hook.call(yuu, initOptions));
101 }, initHooks);
102 initHooks = null; // Bust future registerInitHook calls.
103 yuu.log("messages", "Initialization hooks complete.");
104 if (gui) {
105 gui.Window.get().show();
106 gui.Window.get().focus();
107 }
108 resolve(Promise.all(yf.filter(null, promises)));
109 }).then(function () {
110 yuu.log("messages", "Loading complete.");
111 }).catch(fatalError);
112 };
113
114 yuu.log = yf.argv(function (category, args) {
115 /** Log a message to the console.
116
117 This supports simple filtering by setting e.g.
118 `yuu.log.errors = true` to log anything with the
119 `"errors"` category.
120 */
121 if (!category || this.log[category]) {
122 switch (category) {
123 case "errors": return console.error.apply(console, args);
124 case "warnings": return console.warn.apply(console, args);
125 default: return console.log.apply(console, args);
126 }
127 }
128 });
129
130 yuu.log.errors = true;
131 yuu.log.warnings = true;
132 yuu.log.messages = true;
133
134 yuu.logError = function (e) {
135 yuu.log("errors", e.message || "unknown error", e);
136 };
137
138 yuu.GET = function (url, params) {
139 /** Promise the HTTP GET the contents of a URL. */
140 return new Promise(function (resolve, reject) {
141 var req = new XMLHttpRequest();
142 req.open("GET", url, true);
143 for (var k in params)
144 req[k] = params[k];
145 req.onload = function () {
146 var status = this.status;
147 // status === 0 is given by node-webkit for success.
148 if ((status >= 200 && status < 300) || status === 0)
149 resolve(this.response);
150 else
151 reject(new Error(
152 url + ": " + status + ": " + this.statusText));
153 };
154 req.onabort = function () { reject(new Error("aborted")); };
155 req.onerror = function () { reject(new Error("network error")); };
156 req.ontimeout = function () { reject(new Error("timed out")); };
157 req.send(null);
158 });
159 };
160
161 yuu.Image = function (src) {
162 /** Promises a DOM Image. */
163 return new Promise(function (resolve, reject) {
164 var img = new Image();
165 img.onload = function () {
166 resolve(img);
167 };
168 img.onerror = function () {
169 var msg = "Unable to load " + img.src;
170 yuu.log("errors", msg);
171 reject(new Error(msg));
172 };
173 img.src = src;
174 });
175 };
176
177 /** Command parsing and execution
178
179 The command API serves several roles. It is a way to enable or
180 disable different game logic within different scenes; capture
181 and replay or automate game events; loosely or late-bind game
182 modules; customize input mappings; and a debugging tool to
183 help inspect or modify the state of a running program.
184
185 A command is a string of a command name followed by arguments
186 separated by whitespace. It's similar to a fully bound
187 closure. It is less flexible but easier to inspect, store,
188 replay, and pass around.
189
190 Command names are mapped to functions, grouped into sets, and
191 the sets pushed onto a stack. They are executed by passing the
192 command string to the execute function which walks the stack
193 looking for the matching command.
194
195 If the command string is prefaced with + or -, true or false
196 are appended to the argument list. e.g. `+command` is
197 equivalent to `command true` and `-command 1 b` is equivalent
198 to `command 1 b false`. By convention, commands of those forms
199 return their internal state when called with neither true or
200 false. This is useful for another special prefix, ++. When
201 called as `++command`, it is executed with no arguments, the
202 result inverted (with !), and then called again passing that
203 inverted value as the last argument.
204 */
205
206 function isCommand (f) {
207 return yf.isFunction(f) && f._isCommandFunction;
208 }
209
210 function cmdbind () {
211 // Commands are practically a subtype of functions. Binding
212 // them (which happens often, e.g. when Scenes register
213 // commands) should also return a command.
214 var f = Function.prototype.bind.apply(this, arguments);
215 // usage is still valid iff no new arguments were given.
216 return cmd(f, arguments.length <= 1 && this.usage, this.description);
217 }
218
219 var cmd = yuu.cmd = yf.argcd(
220 /** Decorate a function for command execution
221
222 Command functions need some special attributes to work
223 correctly. This decorator makes sure they have them.
224 */
225 function (f) { return yuu.cmd(f, null, null); },
226 function (f, description) { return yuu.cmd(f, null, description); },
227 function (f, usage, description) {
228 f._isCommandFunction = true;
229 f.usage = usage || " <value>".repeat(f.length).substring(1);
230 f.description = description || "no description provided";
231 f.bind = cmdbind;
232 return f;
233 }
234 );
235
236 yuu.propcmd = function (o, prop, desc, valspec) {
237 /** Generate a command function that controls a property
238
239 A common pattern for command functions is to simply get or
240 set a single object property. This wrapper will generate a
241 correct function to do that.
242 */
243 valspec = valspec || typeof o[prop];
244 desc = desc || "Retrieve or modify the value of " + prop;
245 return cmd(function () {
246 if (arguments.length)
247 o[prop] = arguments[0];
248 return o[prop];
249 }, "<" + valspec + "?>", desc);
250 };
251
252 var QUOTED_SPLIT = /[^"\s]+|"(?:\\"|[^"])+"/g;
253 var COMMAND_SPLIT = /\s+(&&|\|\||;)\s+/g;
254
255 function parseParam (param) {
256 if (yf.head(param) === "{" && yf.last(param) === "}")
257 return resolvePropertyPath(
258 this, param.substr(1, param.length - 2));
259 try { return JSON.parse(param); }
260 catch (exc) { return param; }
261 }
262
263 function parseCommand (cmdstring, ctx) {
264 /** Parse a command string into an invocation object.
265
266 The command string has a form like `+quux 1 2 3` or
267 `foobar "hello world"`.
268
269 Multiple commands can be joined in one string with &&, ||,
270 or ;. To use these characters literally as a command
271 argument place them in quotes.
272
273 Arguments wrapped in {}s are interpreted as property paths
274 for the provided context object. `{x[0].y}` will resolve
275 `ctx.x[0].y` and put that into the arguments array. To
276 avoid this behavior and get a literal string bounded by
277 {}, JSON-encode the string beforehand (e.g. `"{x[0].y}"`).
278
279 The returned array contains objects with three properties:
280 `name` - the command name to execute
281 `args` - an array of objects to pass as arguments
282 `toggle` - if the command value should be toggled ('++')
283 and pushed into args
284 `cond` - "&&", "||", or ";", indicating what kind of
285 conditional should be applied.
286 */
287
288 var invs = [];
289 var conds = cmdstring.split(COMMAND_SPLIT);
290 for (var i = -1; i < conds.length; i += 2) {
291 var args = conds[i + 1].match(QUOTED_SPLIT).map(parseParam, ctx);
292 var name = args.shift();
293 var toggle = false;
294 if (name[0] === "+" && name[1] === "+") {
295 name = name.substring(2);
296 toggle = true;
297 } else if (name[0] === "+") {
298 name = name.substring(1);
299 args.push(true);
300 } else if (name[0] === "-") {
301 name = name.substring(1);
302 args.push(false);
303 }
304 invs.push({ name: name, args: args, toggle: toggle,
305 cond: conds[i] || ";"});
306 }
307 return invs;
308 }
309
310 yuu.CommandStack = yT({
311 constructor: function () {
312 /** A stack of command sets for command lookup and execution */
313 this._cmdsets = yf.slice(arguments);
314 },
315
316 push: function (cmdset) {
317 /** Add a command set to the lookup stack. */
318 this._cmdsets = this._cmdsets.concat(cmdset);
319 },
320
321 remove: function (cmdset) {
322 /** Remove a command set from the lookup stack. */
323 this._cmdsets = yf.without(this._cmdsets, cmdset);
324 },
325
326 insertBefore: function (cmdset, before) {
327 this._cmdsets = yf.insertBefore(
328 this._cmdsets.slice(), cmdset, before);
329 },
330
331 execute: function (cmdstring, ctx) {
332 /* Execute a command given a command string.
333
334 The command stack is searched top-down for the first
335 command with a matching name, and it is invoked. No
336 other commands are called.
337
338 A command set may also provide a special function named
339 `$`. If no matching command name is found, this
340 function is called with the raw invocation object (the
341 result of yuu.parseCommand) and may return true to stop
342 processing as if the command had been found.
343 */
344 var invs = parseCommand(cmdstring, ctx);
345 var cond;
346 var res;
347 yf.each.call(this, function (inv) {
348 if ((inv.cond === "&&" && !cond) || (inv.cond === "||" && cond))
349 return;
350 if (!yf.eachrUntil(function (cmdset) {
351 var cmd = cmdset[inv.name];
352 if (cmd) {
353 if (inv.toggle)
354 inv.args.push(!cmd.apply(null, inv.args));
355 yuu.log("commands", "Executing:", inv.name,
356 inv.args.map(JSON.stringify).join(" "));
357 res = cmd.apply(null, inv.args);
358 cond = res === undefined ? cond : !!res;
359 yuu.log("commands", "Result:", JSON.stringify(res));
360 return true;
361 }
362 return cmdset.$ && cmdset.$(inv);
363 }, this._cmdsets))
364 yuu.log("errors", "Unknown command", inv.name);
365 }, invs);
366 return res;
367 }
368 });
369
370 yuu.extractCommands = function (object) {
371 var commands = {};
372 yf.each(function (prop) {
373 // Check the descriptor before checking the value, because
374 // checking the value of accessors (which should never be
375 // stable commands) is generally a bad idea during
376 // constructors, and command sets are often filled in during
377 // constructors.
378 if (yT.isDataDescriptor(yT.getPropertyDescriptor(object, prop))
379 && isCommand(object[prop]))
380 commands[prop] = object[prop].bind(object);
381 }, yf.allKeys(object));
382 return commands;
383 };
384
385 yuu.commandStack = new yuu.CommandStack(yuu.defaultCommands = {
386 /** The default command stack and set. */
387 cmds: yuu.cmd(function (term) {
388 term = term || "";
389 var cmds = [];
390 yuu.commandStack._cmdsets.forEach(function (cmdset) {
391 for (var cmdname in cmdset) {
392 if (cmdname.indexOf(term) >= 0) {
393 var cmd = cmdset[cmdname];
394 var msg;
395 if (cmd.usage)
396 msg = [cmdname, cmd.usage, "--", cmd.description];
397 else
398 msg = [cmdname, "--", cmd.description];
399 cmds.push(msg.join(" "));
400 }
401 }
402 });
403 yuu.log("messages", cmds.join("\n"));
404 }, "<term?>", "display available commands (matching the term)"),
405
406 echo: yuu.cmd(function () {
407 yuu.log("messages", arguments);
408 }, "...", "echo arguments to the console"),
409
410 log: yuu.cmd(function (name, state) {
411 if (state !== undefined)
412 yuu.log[name] = !!state;
413 return yuu.log[name];
414 }, "<category> <boolean?>", "enable/disable a logging category")
415
416 });
417
418 yuu.defaultCommands.showDevTools = yuu.cmd(function () {
419 if (gui)
420 gui.Window.get().showDevTools();
421 }, "show developer tools");
422
423 yuu.anchorPoint = function (anchor, x0, y0, x1, y1) {
424 /** Calculate the anchor point for a box given extents and anchor mode
425
426 This function is the inverse of yuu.bottomLeft.
427 */
428 switch (anchor) {
429 case "center": return [(x0 + x1) / 2, (y0 + y1) / 2];
430 case "top": return [(x0 + x1) / 2, y1];
431 case "bottom": return [(x0 + x1) / 2, y0];
432 case "left": return [x0, (y0 + y1) / 2];
433 case "right": return [x1, (y0 + y1) / 2];
434
435 case "topleft": return [x0, y1];
436 case "topright": return [x1, y1];
437 case "bottomleft": return [x0, y0];
438 case "bottomright": return [x0, y0];
439 default: return [anchor[0], anchor[1]];
440 }
441 };
442
443 yuu.bottomLeft = function (anchor, x, y, w, h) {
444 /** Calculate the bottom-left for a box given size and anchor mode
445
446 This function is the inverse of yuu.anchorPoint.
447 */
448 switch (anchor) {
449 case "center": return [x - w / 2, y - h / 2];
450 case "top": return [x - w / 2, y - h];
451 case "bottom": return [x - w / 2, y];
452 case "left": return [x, y - h / 2];
453 case "right": return [x - w, y - h / 2];
454
455 case "topleft": return [x, y - h];
456 case "topright": return [x - w, y - h];
457 case "bottomleft": return [x, y];
458 case "bottomright": return [x - w, y];
459 default: return [anchor[0], anchor[1]];
460 }
461 };
462
463 yuu.lerp = function (a, b, p) {
464 return (a !== null && a !== undefined && a.lerp)
465 ? a.lerp(b, p) : (b !== null && b !== undefined && b.lerp)
466 ? b.lerp(a, 1 - p) : p < 0.5 ? a : b;
467 };
468
469 yuu.bilerp = function (x0y0, x1y0, x0y1, x1y1, px, py) {
470 /** Bilinearly interpolate between four values in two dimensions */
471 return yuu.lerp(yuu.lerp(x0y0, x1y0, px), yuu.lerp(x0y1, x1y1, px), py);
472 };
473
474 function resolvePropertyPath (object, path) {
475 /** Look up a full property path
476
477 If a null is encountered in the path, this function returns
478 null. If undefined is encountered or a property is missing, it
479 returns undefined.
480 */
481 var parts = path.replace(/\[(\w+)\]/g, '.$1').split('.');
482 for (var i = 0;
483 i < parts.length && object !== undefined && object !== null;
484 ++i) {
485 object = object[parts[i]];
486 }
487 return object;
488 }
489
490 yuu.Random = yT({
491 /** Somewhat like Python's random.Random.
492
493 Passed a function that returns a uniform random variable in
494 [0, 1) it can do other useful randomization algorithms.
495
496 Its methods are implemented straightforwardly rather than
497 rigorously - this means they may not behave correctly in
498 common edge cases like precision loss.
499 */
500 constructor: function (generator) {
501 this.random = generator || Math.random;
502 this._spareGauss = null;
503 },
504
505 choice: function (seq) {
506 /** Return a random element from the provided array. */
507 return seq[this.randrange(0, seq.length)];
508 },
509
510 randrange: yf.argcd(
511 function (a) {
512 /** Return a uniform random integer in [0, a). */
513 return (this.random() * a) | 0;
514 },
515
516 function (a, b) {
517 /** Return a uniform random integer in [a, b). */
518 a = a | 0;
519 b = b | 0;
520 return a + ((this.random() * (b - a)) | 0);
521 },
522
523 function (a, b, step) {
524 /** Return a uniform random number in [a, b).
525
526 The number is constrained to values of a + i * step
527 where i is a non-negative integer.
528 */
529 var i = Math.ceil((b - a) / step);
530 return a + this.randrange(i) * step;
531 }
532 ),
533
534 uniform: yf.argcd(
535 function (a) {
536 /** Return a uniform random variable in [0, a). */
537 return a * this.random();
538 },
539 function (a, b) {
540 /** Return a uniform random variable in [a, b). */
541 return a + (b - a) * this.random();
542 }
543 ),
544
545 gauss: function (mean, sigma) {
546 var u = this._spareGauss, v, s;
547 this._spareGauss = null;
548
549 if (u === null) {
550 do {
551 u = this.uniform(-1, 1);
552 v = this.uniform(-1, 1);
553 s = u * u + v * v;
554 } while (s >= 1.0 || s === 0.0);
555 var t = Math.sqrt(-2.0 * Math.log(s) / s);
556 this._spareGauss = v * t;
557 u *= t;
558 }
559 return mean + sigma * u;
560 },
561
562 randbool: yf.argcd(
563 /** Return true the given percent of the time (default 50%). */
564 function () { return this.random() < 0.5; },
565 function (a) { return this.random() < a; }
566 ),
567
568 randsign: function (v) {
569 return this.randbool() ? v : -v;
570 },
571
572 shuffle: function (seq) {
573 for (var i = seq.length - 1; i > 0; --i) {
574 var index = this.randrange(i + 1);
575 var temp = seq[i];
576 seq[i] = seq[index];
577 seq[index] = temp;
578 }
579 return seq;
580 },
581
582 discard: function (z) {
583 z = z | 0;
584 while (z-- > 0)
585 this.random();
586 }
587 });
588
589 yuu.createLCG = yf.argcd(
590 /** Linear congruential random generator
591
592 This returns a function that generates numbers [0, 1) as
593 with Math.random. You can also read or assign the `state`
594 attribute to set the internal state.
595 */
596 function () { return yuu.createLCG(Math.random() * 2147483647); },
597 function (seed) {
598 var state = seed | 0;
599 return function generator () {
600 state = (state * 1664525 + 1013904223) % 4294967296;
601 return state / 4294967296;
602 };
603 }
604 );
605
606 yuu.random = new yuu.Random();
607
608 function defaultKey (args) {
609 // Cache things that can be constructed with one string.
610 return args.length === 1 && yf.isString(args[0]) ? args[0] : null;
611 }
612
613 yuu.Caching = function (Type, cacheKey) {
614 function ctor () {
615 var k = ctor._cacheKey(arguments);
616 var o = k && ctor._cache[k];
617 if (!o)
618 o = ctor._cache[k] = yf.construct(ctor.Uncached, arguments);
619 return o;
620 }
621 ctor._cacheKey = cacheKey || defaultKey;
622 ctor._cache = {};
623 ctor.Uncached = Type;
624 return ctor;
625 };
626
627 yuu.transpose2d = function (a) {
628 for (var x = 0; x < a.length; ++x) {
629 for (var y = 0; y < x; ++y) {
630 var t = a[x][y];
631 a[x][y] = a[y][x];
632 a[y][x] = t;
633 }
634 }
635 };
636
637 yuu.normalizeRadians = function (theta) {
638 var PI = Math.PI;
639 return (theta + 3 * PI) % (2 * PI) - PI;
640 };
641
642 yuu.radians = function (v) {
643 return v * (Math.PI / 180.0);
644 };
645
646 yuu.degrees = function (v) {
647 return v * (180.0 / Math.PI);
648 };
649
650 var SHORT = /(\/|^)@(.+)$/;
651 yuu.resourcePath = function (path, category, ext) {
652 var match;
653 if ((match = path.match(SHORT))) {
654 path = path.replace(/^yuu\/@/, yuu.PATH + "@")
655 .replace(SHORT, "$1data/" + category + "/$2");
656 if (match[2].indexOf(".") === -1)
657 path += "." + ext;
658 }
659 return path;
660 };
661
662 yuu.ready = function (resources, result) {
663 return Promise.all(yf.filter(null, yf.pluck("ready", resources)))
664 .then(yf.K(result));
665 };
666
667 yuu.openURL = function (url) {
668 if (gui && gui.Shell)
669 gui.Shell.openExternal(url);
670 else
671 window.open(url);
672 };
673
674 function crossPlatformFilename (basename) {
675 return basename
676 // Replace D/M/Y with D-M-Y, and H:M:S with H.M.S.
677 .replace(/\//g, "-").replace(/:/g, ".")
678 // Replace all other problematic characters with _.
679 .replace(/["<>*?|\\]/g, "_");
680 }
681
682 yuu.downloadURL = function (url, suggestedName) {
683 var regex = /^data:[^;+]+;base64,(.*)$/;
684 var matches = url.match(regex);
685 suggestedName = crossPlatformFilename(suggestedName);
686 if (matches && fs) {
687 var data = matches[1];
688 var buffer = new Buffer(data, 'base64');
689 var HOME = process.env.HOME
690 || process.env.HOMEPATH
691 || process.env.USERPROFILE;
692 var filename = HOME + "/" + suggestedName;
693 console.log("Saving to", filename);
694 fs.writeFileSync(filename, buffer);
695 } else {
696 var link = document.createElement('a');
697 link.style.display = "none";
698 link.href = url;
699 link.download = suggestedName;
700 // Firefox (as of 28) won't download from a link not rooted in
701 // the document; so, root it and then remove it when done.
702 document.body.appendChild(link);
703 link.click();
704 document.body.removeChild(link);
705 }
706 };
707
708 yuu.AABB = yT({
709 constructor: yf.argcd(
710 function () { this.constructor(0, 0, 0, 0); },
711 function (w, h) { this.constructor(0, 0, w, h); },
712 function (x0, y0, x1, y1) {
713 this.x0 = x0;
714 this.y0 = y0;
715 this.x1 = x1;
716 this.y1 = y1;
717 }
718 ),
719
720 w: {
721 get: function () { return this.x1 - this.x0; },
722 set: function (w) { this.x1 = this.x0 + w; }
723 },
724
725 h: {
726 get: function () { return this.y1 - this.y0; },
727 set: function (h) { this.y1 = this.y0 + h; }
728 },
729
730 size: { swizzle: "wh" },
731
732 contains: yf.argcd(
733 function (p) { return this.contains(p.x, p.y); },
734 function (x, y) {
735 return x >= this.x0 && x < this.x1
736 && y >= this.y0 && y < this.y1;
737 }
738 ),
739
740 matchAspectRatio: function (outer) {
741 var matched = new this.constructor(
742 this.x0, this.y0, this.x1, this.y1);
743 var aRatio = matched.w / matched.h;
744 var bRatio = outer.w / outer.h;
745 if (aRatio > bRatio) {
746 // too wide, must be taller
747 var h = matched.w / bRatio;
748 var dh = h - matched.h;
749 matched.y0 -= dh / 2;
750 matched.y1 += dh / 2;
751 } else {
752 // too tall, must be wider
753 var w = matched.h * bRatio;
754 var dw = w - matched.w;
755 matched.x0 -= dw / 2;
756 matched.x1 += dw / 2;
757 }
758 return matched;
759 },
760
761 alignedInside: function (outer, alignment) {
762 var x0, y0;
763 switch (alignment) {
764 case "bottomleft":
765 x0 = outer.x0;
766 y0 = outer.y0;
767 break;
768 case "bottom":
769 x0 = outer.x0 + (outer.w - this.w) / 2;
770 y0 = outer.y0;
771 break;
772 case "bottomright":
773 x0 = outer.x0 - this.w;
774 y0 = outer.y0;
775 break;
776 case "left":
777 x0 = outer.x0;
778 y0 = outer.x0 + (outer.h - this.h) / 2;
779 break;
780 case "center":
781 x0 = outer.x0 + (outer.w - this.w) / 2;
782 y0 = outer.x0 + (outer.h - this.h) / 2;
783 break;
784 case "right":
785 x0 = outer.x1 - this.w;
786 y0 = outer.x0 + (outer.h - this.h) / 2;
787 break;
788 case "topleft":
789 x0 = outer.x0;
790 y0 = outer.y1 - this.h;
791 break;
792 case "top":
793 x0 = outer.x0 + (outer.w - this.w) / 2;
794 y0 = outer.y1 - this.h;
795 break;
796 case "topright":
797 x0 = outer.x1 - this.w;
798 y0 = outer.y1 - this.h;
799 break;
800 }
801 return new this.constructor(x0, y0, x0 + this.w, y0 + this.h);
802 }
803 });
804
805 function splitPathExtension (path) {
806 var dot = path.lastIndexOf(".");
807 if (dot <= 0) return [path, ""];
808
809 var dir = path.lastIndexOf("/");
810 if (dot < dir) return [path, ""];
811
812 return [path.substring(0, dot), path.substring(dot)];
813 }
814 yuu.splitPathExtension = splitPathExtension;
815
816 if (stringLerp) {
817 yT.defineProperty(String.prototype, "lerp", function (b, p) {
818 b = b.toString();
819 // Never numericLerp - if that's desired force Numbers.
820 // Be more conservative than stringLerp since this runs
821 // often and the diff can't be easily hoisted.
822 return this.length * b.length > 256
823 ? stringLerp.fastLerp(this, b, p)
824 : stringLerp.diffLerp(this, b, p);
825 });
826 }
827
828 yT.defineProperties(Number.prototype, {
829 lerp: function (b, p) { return this + (b - this) * p; }
830 });
831
832 yT.defineProperties(Array.prototype, {
833 lerp: function (b, p) {
834 var length = Math.round(this.length.lerp(b.length, p));
835 var c = new this.constructor(length);
836 for (var i = 0; i < length; ++i) {
837 if (i >= this.length)
838 c[i] = b[i];
839 else if (i >= b.length)
840 c[i] = this[i];
841 else
842 c[i] = this[i].lerp(b[i], p);
843 }
844 return c;
845 }
846 });
847
848 /** Typed array extensions
849
850 https://www.khronos.org/registry/typedarray/specs/1.0/
851 BUT: Read on for fun times in browser land~
852
853 Ideally we could just set these once on ArrayBufferView, but
854 the typed array specification doesn't require that such a
855 constructor actually exist. And in Firefox (18), it doesn't.
856
857 More infurating, in Safari (7.0.3) Int8Array etc. are not
858 functions so this needs to be added to the prototype
859 directly. This is a violation of the specification which
860 requires such constructors, and ECMA which requires
861 constructors be functions, and common decency.
862 */
863
864 [ Float32Array, Float64Array, Int8Array, Uint8Array,
865 Int16Array, Uint16Array, Int32Array, Uint32Array
866 ].forEach(function (A) {
867 yT.defineProperties(A.prototype, {
868 slice: yf.argcd(
869 /** Like Array's slice, but for typed arrays */
870 function () { return new this.constructor(this); },
871 function (begin) {
872 return new this.constructor(this.subarray(begin));
873 },
874 function (begin, end) {
875 return new this.constructor(this.subarray(begin, end));
876 }
877 ),
878
879 fill: Array.prototype.fill,
880 reverse: Array.prototype.reverse,
881
882 lerp: function (b, p) {
883 if (p === 0)
884 return this.slice();
885 else if (p === 1)
886 return b.slice();
887 var c = new this.constructor(this.length);
888 for (var i = 0; i < this.length; ++i)
889 c[i] = this[i] + (b[i] - this[i]) * p;
890 return c;
891 }
892 });
893 });
894
895 }).call(typeof exports === "undefined" ? this : exports,
896 typeof exports === "undefined" ? (this.yuu = {}) : exports);