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