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/
10 yuu
.isSafari = function (ua
) {
11 return /^((?!chrome).)*safari/i.test(ua
|| navigator
.userAgent
)
12 || navigator
.standalone
;
15 yuu
.require = function (m
) {
16 try { return require(m
); }
17 catch (exc
) { return null; }
23 var yT
= this.yT
|| require("./yT");
24 var yf
= this.yf
|| require("./yf");
25 var gui
= yuu
.require("nw.gui");
26 var fs
= yuu
.require("fs");
27 var stringLerp
= this.stringLerp
|| yuu
.require("string-lerp");
30 var initOptions
= null;
32 if (typeof document
!== "undefined") {
33 var scripts
= document
.getElementsByTagName('script');
34 var path
= yf
.last(scripts
).src
.split('?')[0];
35 yuu
.PATH
= path
.split('/').slice(0, -1).join('/') + '/';
37 yuu
.PATH
= "file://" + escape(module
.__dirname
) + "/";
40 yuu
.registerInitHook
= initHooks
.push
.bind(initHooks
);
41 /** Register a hook to be called during Yuu initialization
43 Hooks are called in registration order with the module and
44 the options dictionary passed to the init method. (This is
45 also set to the module.)
48 function showError (exc
, kind
) {
49 var prefix
= "yuu-" + (kind
|| "") + "error";
51 var dialog
= document
.getElementById(prefix
);
52 var errorMessage
= document
.getElementById(prefix
+ "-message");
54 errorMessage
.textContent
= exc
.message
;
55 var errorStack
= document
.getElementById(prefix
+ "-stack");
57 errorStack
.textContent
= exc
.message
+ "\n\n" + exc
.stack
;
60 yuu
.showError
= showError
;
62 function fatalError (exc
) {
63 var dialog
= showError(exc
, "fatal-");
65 dialog
.style
.display
= "block";
67 gui
.Window
.get().show();
68 gui
.Window
.get().focus();
73 yuu
.init = function (options
) {
74 /** Initialize Yuu and call all registered hooks
77 document
.body
.className
+= (navigator
.standalone
|| gui
)
78 ? " standalone" : " browser";
81 var win
= gui
.Window
.get();
82 var nativeMenuBar
= new gui
.Menu({ type
: "menubar" });
83 if (nativeMenuBar
.createMacBuiltin
) {
84 nativeMenuBar
.createMacBuiltin(
85 document
.title
, { hideEdit
: true });
86 win
.menu
= nativeMenuBar
;
89 win
.on("minimize", function () {
90 var ev
= new Event("visibilitychange");
92 wkdoc
.dispatchEvent(ev
);
94 win
.on("restore", function () {
95 var ev
= new Event("visibilitychange");
97 wkdoc
.dispatchEvent(ev
);
99 win
.on('new-win-policy', function (frame
, url
, policy
) {
100 if (url
.startsWith('chrome')) {
101 policy
.forceNewPopup();
104 gui
.Shell
.openExternal(url
);
109 return new Promise(function (resolve
) {
110 // TODO: Some kind of loading progress bar.
111 initOptions
= options
|| {};
112 yuu
.log("messages", "Initializing Yuu engine.");
114 // initHooks can be pushed to while iterating, so iterate
115 // by index, not a foreach loop.
116 for (var i
= 0; i
< initHooks
.length
; ++i
)
117 promises
.push(initHooks
[i
].call(yuu
, initOptions
));
118 initHooks
= null; // Bust future registerInitHook calls.
119 yuu
.log("messages", "Initialization hooks complete.");
121 gui
.Window
.get().show();
122 gui
.Window
.get().focus();
124 resolve(Promise
.all(yf
.filter(null, promises
)));
125 }).then(function () {
126 yuu
.log("messages", "Loading complete.");
127 }).catch(fatalError
);
130 yuu
.log
= yf
.argv(function (category
, args
) {
131 /** Log a message to the console.
133 This supports simple filtering by setting e.g.
134 `yuu.log.errors = true` to log anything with the
137 if (!category
|| this.log
[category
]) {
139 case "errors": return console
.error
.apply(console
, args
);
140 case "warnings": return console
.warn
.apply(console
, args
);
141 default: return console
.log
.apply(console
, args
);
146 yuu
.log
.errors
= true;
147 yuu
.log
.warnings
= true;
148 yuu
.log
.messages
= true;
150 yuu
.logError = function (e
) {
151 yuu
.log("errors", e
.message
|| "unknown error", e
);
154 yuu
.GET = function (url
, params
) {
155 /** Promise the HTTP GET the contents of a URL. */
156 return new Promise(function (resolve
, reject
) {
157 var req
= new XMLHttpRequest();
158 req
.open("GET", url
, true);
159 for (var k
in params
)
161 req
.onload = function () {
162 var status
= this.status
;
163 // status === 0 is given by node-webkit for success.
164 if ((status
>= 200 && status
< 300) || status
=== 0)
165 resolve(this.response
);
168 url
+ ": " + status
+ ": " + this.statusText
));
170 req
.onabort = function () { reject(new Error("aborted")); };
171 req
.onerror = function () { reject(new Error("network error")); };
172 req
.ontimeout = function () { reject(new Error("timed out")); };
177 yuu
.Image = function (src
) {
178 /** Promises a DOM Image. */
179 return new Promise(function (resolve
, reject
) {
180 var img
= new Image();
181 img
.onload = function () {
184 img
.onerror = function () {
185 var msg
= "Unable to load " + img
.src
;
186 yuu
.log("errors", msg
);
187 reject(new Error(msg
));
193 /** Command parsing and execution
195 The command API serves several roles. It is a way to enable or
196 disable different game logic within different scenes; capture
197 and replay or automate game events; loosely or late-bind game
198 modules; customize input mappings; and a debugging tool to
199 help inspect or modify the state of a running program.
201 A command is a string of a command name followed by arguments
202 separated by whitespace. It's similar to a fully bound
203 closure. It is less flexible but easier to inspect, store,
204 replay, and pass around.
206 Command names are mapped to functions, grouped into sets, and
207 the sets pushed onto a stack. They are executed by passing the
208 command string to the execute function which walks the stack
209 looking for the matching command.
211 If the command string is prefaced with + or -, true or false
212 are appended to the argument list. e.g. `+command` is
213 equivalent to `command true` and `-command 1 b` is equivalent
214 to `command 1 b false`. By convention, commands of those forms
215 return their internal state when called with neither true or
216 false. This is useful for another special prefix, ++. When
217 called as `++command`, it is executed with no arguments, the
218 result inverted (with !), and then called again passing that
219 inverted value as the last argument.
222 function isCommand (f
) {
223 return yf
.isFunction(f
) && f
._isCommandFunction
;
226 function cmdbind () {
227 // Commands are practically a subtype of functions. Binding
228 // them (which happens often, e.g. when Scenes register
229 // commands) should also return a command.
230 var f
= Function
.prototype.bind
.apply(this, arguments
);
231 // usage is still valid iff no new arguments were given.
232 return cmd(f
, arguments
.length
<= 1 && this.usage
, this.description
);
235 var cmd
= yuu
.cmd
= yf
.argcd(
236 /** Decorate a function for command execution
238 Command functions need some special attributes to work
239 correctly. This decorator makes sure they have them.
241 function (f
) { return yuu
.cmd(f
, null, null); },
242 function (f
, description
) { return yuu
.cmd(f
, null, description
); },
243 function (f
, usage
, description
) {
244 f
._isCommandFunction
= true;
245 f
.usage
= usage
|| " <value>".repeat(f
.length
).substring(1);
246 f
.description
= description
|| "no description provided";
252 yuu
.propcmd = function (o
, prop
, desc
, valspec
) {
253 /** Generate a command function that controls a property
255 A common pattern for command functions is to simply get or
256 set a single object property. This wrapper will generate a
257 correct function to do that.
259 valspec
= valspec
|| typeof o
[prop
];
260 desc
= desc
|| "Retrieve or modify the value of " + prop
;
261 return cmd(function () {
262 if (arguments
.length
)
263 o
[prop
] = arguments
[0];
265 }, "<" + valspec
+ "?>", desc
);
268 var QUOTED_SPLIT
= /[^"\s]+|"(?:\\"|[^"])+"/g;
269 var COMMAND_SPLIT
= /\s+(&&|\|\||;)\s+/g;
271 function parseParam (param
) {
272 if (yf
.head(param
) === "{" && yf
.last(param
) === "}")
273 return resolvePropertyPath(
274 this, param
.substr(1, param
.length
- 2));
275 try { return JSON
.parse(param
); }
276 catch (exc
) { return param
; }
279 function parseCommand (cmdstring
, ctx
) {
280 /** Parse a command string into an invocation object.
282 The command string has a form like `+quux 1 2 3` or
283 `foobar "hello world"`.
285 Multiple commands can be joined in one string with &&, ||,
286 or ;. To use these characters literally as a command
287 argument place them in quotes.
289 Arguments wrapped in {}s are interpreted as property paths
290 for the provided context object. `{x[0].y}` will resolve
291 `ctx.x[0].y` and put that into the arguments array. To
292 avoid this behavior and get a literal string bounded by
293 {}, JSON-encode the string beforehand (e.g. `"{x[0].y}"`).
295 The returned array contains objects with three properties:
296 `name` - the command name to execute
297 `args` - an array of objects to pass as arguments
298 `toggle` - if the command value should be toggled ('++')
300 `cond` - "&&", "||", or ";", indicating what kind of
301 conditional should be applied.
305 var conds
= cmdstring
.split(COMMAND_SPLIT
);
306 for (var i
= -1; i
< conds
.length
; i
+= 2) {
307 var args
= conds
[i
+ 1].match(QUOTED_SPLIT
).map(parseParam
, ctx
);
308 var name
= args
.shift();
310 if (name
[0] === "+" && name
[1] === "+") {
311 name
= name
.substring(2);
313 } else if (name
[0] === "+") {
314 name
= name
.substring(1);
316 } else if (name
[0] === "-") {
317 name
= name
.substring(1);
320 invs
.push({ name
: name
, args
: args
, toggle
: toggle
,
321 cond
: conds
[i
] || ";"});
326 yuu
.CommandStack
= yT({
327 constructor: function () {
328 /** A stack of command sets for command lookup and execution */
329 this._cmdsets
= yf
.slice(arguments
);
332 push: function (cmdset
) {
333 /** Add a command set to the lookup stack. */
334 this._cmdsets
= this._cmdsets
.concat(cmdset
);
337 remove: function (cmdset
) {
338 /** Remove a command set from the lookup stack. */
339 this._cmdsets
= yf
.without(this._cmdsets
, cmdset
);
342 insertBefore: function (cmdset
, before
) {
343 this._cmdsets
= yf
.insertBefore(
344 this._cmdsets
.slice(), cmdset
, before
);
347 execute: function (cmdstring
, ctx
) {
348 /* Execute a command given a command string.
350 The command stack is searched top-down for the first
351 command with a matching name, and it is invoked. No
352 other commands are called.
354 A command set may also provide a special function named
355 `$`. If no matching command name is found, this
356 function is called with the raw invocation object (the
357 result of yuu.parseCommand) and may return true to stop
358 processing as if the command had been found.
360 var invs
= parseCommand(cmdstring
, ctx
);
363 yf
.each
.call(this, function (inv
) {
364 if ((inv
.cond
=== "&&" && !cond
) || (inv
.cond
=== "||" && cond
))
366 if (!yf
.eachrUntil(function (cmdset
) {
367 var cmd
= cmdset
[inv
.name
];
370 inv
.args
.push(!cmd
.apply(null, inv
.args
));
371 yuu
.log("commands", "Executing:", inv
.name
,
372 inv
.args
.map(JSON
.stringify
).join(" "));
373 res
= cmd
.apply(null, inv
.args
);
374 cond
= res
=== undefined ? cond
: !!res
;
375 yuu
.log("commands", "Result:", JSON
.stringify(res
));
378 return cmdset
.$ && cmdset
.$(inv
);
380 yuu
.log("errors", "Unknown command", inv
.name
);
386 yuu
.extractCommands = function (object
) {
388 yf
.each(function (prop
) {
389 // Check the descriptor before checking the value, because
390 // checking the value of accessors (which should never be
391 // stable commands) is generally a bad idea during
392 // constructors, and command sets are often filled in during
394 if (yT
.isDataDescriptor(yT
.getPropertyDescriptor(object
, prop
))
395 && isCommand(object
[prop
]))
396 commands
[prop
] = object
[prop
].bind(object
);
397 }, yf
.allKeys(object
));
401 yuu
.commandStack
= new yuu
.CommandStack(yuu
.defaultCommands
= {
402 /** The default command stack and set. */
403 cmds
: yuu
.cmd(function (term
) {
406 yuu
.commandStack
._cmdsets
.forEach(function (cmdset
) {
407 for (var cmdname
in cmdset
) {
408 if (cmdname
.indexOf(term
) >= 0) {
409 var cmd
= cmdset
[cmdname
];
412 msg
= [cmdname
, cmd
.usage
, "--", cmd
.description
];
414 msg
= [cmdname
, "--", cmd
.description
];
415 cmds
.push(msg
.join(" "));
419 yuu
.log("messages", cmds
.join("\n"));
420 }, "<term?>", "display available commands (matching the term)"),
422 echo
: yuu
.cmd(function () {
423 yuu
.log("messages", arguments
);
424 }, "...", "echo arguments to the console"),
426 log
: yuu
.cmd(function (name
, state
) {
427 if (state
!== undefined)
428 yuu
.log
[name
] = !!state
;
429 return yuu
.log
[name
];
430 }, "<category> <boolean?>", "enable/disable a logging category")
434 yuu
.defaultCommands
.showDevTools
= yuu
.cmd(function () {
436 gui
.Window
.get().showDevTools();
437 }, "show developer tools");
439 yuu
.anchorPoint = function (anchor
, x0
, y0
, x1
, y1
) {
440 /** Calculate the anchor point for a box given extents and anchor mode
442 This function is the inverse of yuu.bottomLeft.
445 case "center": return [(x0
+ x1
) / 2, (y0
+ y1
) / 2];
446 case "top": return [(x0
+ x1
) / 2, y1
];
447 case "bottom": return [(x0
+ x1
) / 2, y0
];
448 case "left": return [x0
, (y0
+ y1
) / 2];
449 case "right": return [x1
, (y0
+ y1
) / 2];
451 case "topleft": return [x0
, y1
];
452 case "topright": return [x1
, y1
];
453 case "bottomleft": return [x0
, y0
];
454 case "bottomright": return [x0
, y0
];
455 default: return [anchor
[0], anchor
[1]];
459 yuu
.bottomLeft = function (anchor
, x
, y
, w
, h
) {
460 /** Calculate the bottom-left for a box given size and anchor mode
462 This function is the inverse of yuu.anchorPoint.
465 case "center": return [x
- w
/ 2, y
- h
/ 2];
466 case "top": return [x
- w
/ 2, y
- h
];
467 case "bottom": return [x
- w
/ 2, y
];
468 case "left": return [x
, y
- h
/ 2];
469 case "right": return [x
- w
, y
- h
/ 2];
471 case "topleft": return [x
, y
- h
];
472 case "topright": return [x
- w
, y
- h
];
473 case "bottomleft": return [x
, y
];
474 case "bottomright": return [x
- w
, y
];
475 default: return [anchor
[0], anchor
[1]];
479 yuu
.lerp = function (a
, b
, p
) {
480 return (a
!== null && a
!== undefined && a
.lerp
)
481 ? a
.lerp(b
, p
) : (b
!== null && b
!== undefined && b
.lerp
)
482 ? b
.lerp(a
, 1 - p
) : p
< 0.5 ? a
: b
;
485 yuu
.bilerp = function (x0y0
, x1y0
, x0y1
, x1y1
, px
, py
) {
486 /** Bilinearly interpolate between four values in two dimensions */
487 return yuu
.lerp(yuu
.lerp(x0y0
, x1y0
, px
), yuu
.lerp(x0y1
, x1y1
, px
), py
);
490 function resolvePropertyPath (object
, path
) {
491 /** Look up a full property path
493 If a null is encountered in the path, this function returns
494 null. If undefined is encountered or a property is missing, it
497 var parts
= path
.replace(/\[(\w+)\]/g, '.$1').split('.');
499 i
< parts
.length
&& object
!== undefined && object
!== null;
501 object
= object
[parts
[i
]];
507 /** Somewhat like Python's random.Random.
509 Passed a function that returns a uniform random variable in
510 [0, 1) it can do other useful randomization algorithms.
512 Its methods are implemented straightforwardly rather than
513 rigorously - this means they may not behave correctly in
514 common edge cases like precision loss.
516 constructor: function (generator
) {
517 this.random
= generator
|| Math
.random
;
518 this._spareGauss
= null;
521 choice: function (seq
) {
522 /** Return a random element from the provided array. */
523 return seq
[this.randrange(0, seq
.length
)];
528 /** Return a uniform random integer in [0, a). */
529 return (this.random() * a
) | 0;
533 /** Return a uniform random integer in [a, b). */
536 return a
+ ((this.random() * (b
- a
)) | 0);
539 function (a
, b
, step
) {
540 /** Return a uniform random number in [a, b).
542 The number is constrained to values of a + i * step
543 where i is a non-negative integer.
545 var i
= Math
.ceil((b
- a
) / step
);
546 return a
+ this.randrange(i
) * step
;
552 /** Return a uniform random variable in [0, a). */
553 return a
* this.random();
556 /** Return a uniform random variable in [a, b). */
557 return a
+ (b
- a
) * this.random();
561 gauss: function (mean
, sigma
) {
562 var u
= this._spareGauss
, v
, s
;
563 this._spareGauss
= null;
567 u
= this.uniform(-1, 1);
568 v
= this.uniform(-1, 1);
570 } while (s
>= 1.0 || s
=== 0.0);
571 var t
= Math
.sqrt(-2.0 * Math
.log(s
) / s
);
572 this._spareGauss
= v
* t
;
575 return mean
+ sigma
* u
;
579 /** Return true the given percent of the time (default 50%). */
580 function () { return this.random() < 0.5; },
581 function (a
) { return this.random() < a
; }
584 randsign: function (v
) {
585 return this.randbool() ? v
: -v
;
588 shuffle: function (seq
) {
589 for (var i
= seq
.length
- 1; i
> 0; --i
) {
590 var index
= this.randrange(i
+ 1);
598 discard: function (z
) {
605 yuu
.createLCG
= yf
.argcd(
606 /** Linear congruential random generator
608 This returns a function that generates numbers [0, 1) as
609 with Math.random. You can also read or assign the `state`
610 attribute to set the internal state.
612 function () { return yuu
.createLCG(Math
.random() * 2147483647); },
614 var state
= seed
| 0;
615 return function generator () {
616 state
= (state
* 1664525 + 1013904223) % 4294967296;
617 return state
/ 4294967296;
622 yuu
.random
= new yuu
.Random();
624 function defaultKey (args
) {
625 // Cache things that can be constructed with one string.
626 return args
.length
=== 1 && yf
.isString(args
[0]) ? args
[0] : null;
629 yuu
.Caching = function (Type
, cacheKey
) {
631 var k
= ctor
._cacheKey(arguments
);
632 var o
= k
&& ctor
._cache
[k
];
634 o
= ctor
._cache
[k
] = yf
.construct(ctor
.Uncached
, arguments
);
637 ctor
._cacheKey
= cacheKey
|| defaultKey
;
639 ctor
.Uncached
= Type
;
643 yuu
.transpose2d = function (a
) {
644 for (var x
= 0; x
< a
.length
; ++x
) {
645 for (var y
= 0; y
< x
; ++y
) {
653 yuu
.normalizeRadians = function (theta
) {
655 return (theta
+ 3 * PI
) % (2 * PI
) - PI
;
658 yuu
.radians = function (v
) {
659 return v
* (Math
.PI
/ 180.0);
662 yuu
.degrees = function (v
) {
663 return v
* (180.0 / Math
.PI
);
666 var SHORT
= /(\/|^)@(.+)$/;
667 yuu
.resourcePath = function (path
, category
, ext
) {
669 if ((match
= path
.match(SHORT
))) {
670 path
= path
.replace(/^yuu\/@/, yuu
.PATH
+ "@")
671 .replace(SHORT
, "$1data/" + category
+ "/$2");
672 if (match
[2].indexOf(".") === -1)
678 yuu
.ready = function (resources
, result
) {
679 return Promise
.all(yf
.filter(null, yf
.pluck("ready", resources
)))
683 yuu
.openURL = function (url
) {
684 if (gui
&& gui
.Shell
)
685 gui
.Shell
.openExternal(url
);
690 function crossPlatformFilename (basename
) {
692 // Replace D/M/Y with D-M-Y, and H:M:S with H.M.S.
693 .replace(/\//g, "-").replace(/:/g
, ".")
694 // Replace all other problematic characters with _.
695 .replace(/["<>*?|\\]/g, "_");
698 yuu
.downloadURL = function (url
, suggestedName
) {
699 var regex
= /^data:[^;+]+;base64,(.*)$/;
700 var matches
= url
.match(regex
);
701 suggestedName
= crossPlatformFilename(suggestedName
);
703 var data
= matches
[1];
704 var buffer
= new Buffer(data
, 'base64');
705 var HOME
= process
.env
.HOME
706 || process
.env
.HOMEPATH
707 || process
.env
.USERPROFILE
;
708 var filename
= HOME
+ "/" + suggestedName
;
709 console
.log("Saving to", filename
);
710 fs
.writeFileSync(filename
, buffer
);
712 var link
= document
.createElement('a');
713 link
.style
.display
= "none";
715 link
.download
= suggestedName
;
716 // Firefox (as of 28) won't download from a link not rooted in
717 // the document; so, root it and then remove it when done.
718 document
.body
.appendChild(link
);
720 document
.body
.removeChild(link
);
725 constructor: yf
.argcd(
726 function () { this.constructor(0, 0, 0, 0); },
727 function (w
, h
) { this.constructor(0, 0, w
, h
); },
728 function (x0
, y0
, x1
, y1
) {
737 get: function () { return this.x1
- this.x0
; },
738 set: function (w
) { this.x1
= this.x0
+ w
; }
742 get: function () { return this.y1
- this.y0
; },
743 set: function (h
) { this.y1
= this.y0
+ h
; }
746 size
: { swizzle
: "wh" },
749 function (p
) { return this.contains(p
.x
, p
.y
); },
751 return x
>= this.x0
&& x
< this.x1
752 && y
>= this.y0
&& y
< this.y1
;
756 matchAspectRatio: function (outer
) {
757 var matched
= new this.constructor(
758 this.x0
, this.y0
, this.x1
, this.y1
);
759 var aRatio
= matched
.w
/ matched
.h
;
760 var bRatio
= outer
.w
/ outer
.h
;
761 if (aRatio
> bRatio
) {
762 // too wide, must be taller
763 var h
= matched
.w
/ bRatio
;
764 var dh
= h
- matched
.h
;
765 matched
.y0
-= dh
/ 2;
766 matched
.y1
+= dh
/ 2;
768 // too tall, must be wider
769 var w
= matched
.h
* bRatio
;
770 var dw
= w
- matched
.w
;
771 matched
.x0
-= dw
/ 2;
772 matched
.x1
+= dw
/ 2;
777 alignedInside: function (outer
, alignment
) {
785 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
789 x0
= outer
.x0
- this.w
;
794 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
797 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
798 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
801 x0
= outer
.x1
- this.w
;
802 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
806 y0
= outer
.y1
- this.h
;
809 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
810 y0
= outer
.y1
- this.h
;
813 x0
= outer
.x1
- this.w
;
814 y0
= outer
.y1
- this.h
;
817 return new this.constructor(x0
, y0
, x0
+ this.w
, y0
+ this.h
);
821 function splitPathExtension (path
) {
822 var dot
= path
.lastIndexOf(".");
823 if (dot
<= 0) return [path
, ""];
825 var dir
= path
.lastIndexOf("/");
826 if (dot
< dir
) return [path
, ""];
828 return [path
.substring(0, dot
), path
.substring(dot
)];
830 yuu
.splitPathExtension
= splitPathExtension
;
833 yT
.defineProperty(String
.prototype, "lerp", function (b
, p
) {
835 // Never numericLerp - if that's desired force Numbers.
836 // Be more conservative than stringLerp since this runs
837 // often and the diff can't be easily hoisted.
838 return this.length
* b
.length
> 256
839 ? stringLerp
.fastLerp(this, b
, p
)
840 : stringLerp
.diffLerp(this, b
, p
);
844 yT
.defineProperties(Number
.prototype, {
845 lerp: function (b
, p
) { return this + (b
- this) * p
; }
848 yT
.defineProperties(Array
.prototype, {
849 lerp: function (b
, p
) {
850 var length
= Math
.round(this.length
.lerp(b
.length
, p
));
851 var c
= new this.constructor(length
);
852 for (var i
= 0; i
< length
; ++i
) {
853 if (i
>= this.length
)
855 else if (i
>= b
.length
)
858 c
[i
] = this[i
].lerp(b
[i
], p
);
864 /** Typed array extensions
866 https://www.khronos.org/registry/typedarray/specs/1.0/
867 BUT: Read on for fun times in browser land~
869 Ideally we could just set these once on ArrayBufferView, but
870 the typed array specification doesn't require that such a
871 constructor actually exist. And in Firefox (18), it doesn't.
873 More infurating, in Safari (7.0.3) Int8Array etc. are not
874 functions so this needs to be added to the prototype
875 directly. This is a violation of the specification which
876 requires such constructors, and ECMA which requires
877 constructors be functions, and common decency.
880 [ Float32Array
, Float64Array
, Int8Array
, Uint8Array
,
881 Int16Array
, Uint16Array
, Int32Array
, Uint32Array
882 ].forEach(function (A
) {
883 yT
.defineProperties(A
.prototype, {
885 /** Like Array's slice, but for typed arrays */
886 function () { return new this.constructor(this); },
888 return new this.constructor(this.subarray(begin
));
890 function (begin
, end
) {
891 return new this.constructor(this.subarray(begin
, end
));
895 fill
: Array
.prototype.fill
,
896 reverse
: Array
.prototype.reverse
,
898 lerp: function (b
, p
) {
903 var c
= new this.constructor(this.length
);
904 for (var i
= 0; i
< this.length
; ++i
)
905 c
[i
] = this[i
] + (b
[i
] - this[i
]) * p
;
911 }).call(typeof exports
=== "undefined" ? this : exports
,
912 typeof exports
=== "undefined" ? (this.yuu
= {}) : exports
);