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/
11 yuu
.require = function (m
) {
12 try { return require(m
); }
13 catch (exc
) { return null; }
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");
26 var initOptions
= null;
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('/') + '/';
33 yuu
.PATH
= "file://" + escape(module
.__dirname
) + "/";
36 yuu
.registerInitHook
= initHooks
.push
.bind(initHooks
);
37 /** Register a hook to be called during Yuu initialization
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.)
44 function showError (exc
, kind
) {
45 var prefix
= "yuu-" + (kind
|| "") + "error";
47 var dialog
= document
.getElementById(prefix
);
48 var errorMessage
= document
.getElementById(prefix
+ "-message");
50 errorMessage
.textContent
= exc
.message
;
51 var errorStack
= document
.getElementById(prefix
+ "-stack");
53 errorStack
.textContent
= exc
.message
+ "\n\n" + exc
.stack
;
56 yuu
.showError
= showError
;
58 function fatalError (exc
) {
59 var dialog
= showError(exc
, "fatal-");
61 dialog
.style
.display
= "block";
63 gui
.Window
.get().show();
64 gui
.Window
.get().focus();
69 yuu
.init = function (options
) {
70 /** Initialize Yuu and call all registered hooks
73 document
.body
.className
+= (navigator
.standalone
|| gui
)
74 ? " standalone" : " browser";
77 var win
= gui
.Window
.get();
78 var nativeMenuBar
= new gui
.Menu({ type
: "menubar" });
79 if (nativeMenuBar
.createMacBuiltin
) {
80 nativeMenuBar
.createMacBuiltin(
81 document
.title
, { hideEdit
: true });
82 win
.menu
= nativeMenuBar
;
85 win
.on("minimize", function () {
86 var ev
= new Event("visibilitychange");
88 wkdoc
.dispatchEvent(ev
);
90 win
.on("restore", function () {
91 var ev
= new Event("visibilitychange");
93 wkdoc
.dispatchEvent(ev
);
95 win
.on('new-win-policy', function (frame
, url
, policy
) {
96 if (url
.startsWith('chrome')) {
97 policy
.forceNewPopup();
100 gui
.Shell
.openExternal(url
);
105 return new Promise(function (resolve
) {
106 // TODO: Some kind of loading progress bar.
107 initOptions
= options
|| {};
108 yuu
.log("messages", "Initializing Yuu engine.");
110 // initHooks can be pushed to while iterating, so iterate
111 // by index, not a foreach loop.
112 for (var i
= 0; i
< initHooks
.length
; ++i
)
113 promises
.push(initHooks
[i
].call(yuu
, initOptions
));
114 initHooks
= null; // Bust future registerInitHook calls.
115 yuu
.log("messages", "Initialization hooks complete.");
117 gui
.Window
.get().show();
118 gui
.Window
.get().focus();
120 resolve(Promise
.all(yf
.filter(null, promises
)));
121 }).then(function () {
122 yuu
.log("messages", "Loading complete.");
123 }).catch(fatalError
);
126 yuu
.log
= yf
.argv(function (category
, args
) {
127 /** Log a message to the console.
129 This supports simple filtering by setting e.g.
130 `yuu.log.errors = true` to log anything with the
133 if (!category
|| this.log
[category
]) {
135 case "errors": return console
.error
.apply(console
, args
);
136 case "warnings": return console
.warn
.apply(console
, args
);
137 default: return console
.log
.apply(console
, args
);
142 yuu
.log
.errors
= true;
143 yuu
.log
.warnings
= true;
144 yuu
.log
.messages
= true;
146 yuu
.logError = function (e
) {
147 yuu
.log("errors", e
.message
|| "unknown error", e
);
150 yuu
.GET = function (url
, params
) {
151 /** Promise the HTTP GET the contents of a URL. */
152 return new Promise(function (resolve
, reject
) {
153 var req
= new XMLHttpRequest();
154 req
.open("GET", url
, true);
155 for (var k
in params
)
157 req
.onload = function () {
158 var status
= this.status
;
159 // status === 0 is given by node-webkit for success.
160 if ((status
>= 200 && status
< 300) || status
=== 0)
161 resolve(this.response
);
164 url
+ ": " + status
+ ": " + this.statusText
));
166 req
.onabort = function () { reject(new Error("aborted")); };
167 req
.onerror = function () { reject(new Error("network error")); };
168 req
.ontimeout = function () { reject(new Error("timed out")); };
173 yuu
.Image = function (src
) {
174 /** Promises a DOM Image. */
175 return new Promise(function (resolve
, reject
) {
176 var img
= new Image();
177 img
.onload = function () {
180 img
.onerror = function () {
181 var msg
= "Unable to load " + img
.src
;
182 yuu
.log("errors", msg
);
183 reject(new Error(msg
));
189 /** Command parsing and execution
191 The command API serves several roles. It is a way to enable or
192 disable different game logic within different scenes; capture
193 and replay or automate game events; loosely or late-bind game
194 modules; customize input mappings; and a debugging tool to
195 help inspect or modify the state of a running program.
197 A command is a string of a command name followed by arguments
198 separated by whitespace. It's similar to a fully bound
199 closure. It is less flexible but easier to inspect, store,
200 replay, and pass around.
202 Command names are mapped to functions, grouped into sets, and
203 the sets pushed onto a stack. They are executed by passing the
204 command string to the execute function which walks the stack
205 looking for the matching command.
207 If the command string is prefaced with + or -, true or false
208 are appended to the argument list. e.g. `+command` is
209 equivalent to `command true` and `-command 1 b` is equivalent
210 to `command 1 b false`. By convention, commands of those forms
211 return their internal state when called with neither true or
212 false. This is useful for another special prefix, ++. When
213 called as `++command`, it is executed with no arguments, the
214 result inverted (with !), and then called again passing that
215 inverted value as the last argument.
218 function isCommand (f
) {
219 return yf
.isFunction(f
) && f
._isCommandFunction
;
222 function cmdbind () {
223 // Commands are practically a subtype of functions. Binding
224 // them (which happens often, e.g. when Scenes register
225 // commands) should also return a command.
226 var f
= Function
.prototype.bind
.apply(this, arguments
);
227 // usage is still valid iff no new arguments were given.
228 return cmd(f
, arguments
.length
<= 1 && this.usage
, this.description
);
231 var cmd
= yuu
.cmd
= yf
.argcd(
232 /** Decorate a function for command execution
234 Command functions need some special attributes to work
235 correctly. This decorator makes sure they have them.
237 function (f
) { return yuu
.cmd(f
, null, null); },
238 function (f
, description
) { return yuu
.cmd(f
, null, description
); },
239 function (f
, usage
, description
) {
240 f
._isCommandFunction
= true;
241 f
.usage
= usage
|| " <value>".repeat(f
.length
).substring(1);
242 f
.description
= description
|| "no description provided";
248 yuu
.propcmd = function (o
, prop
, desc
, valspec
) {
249 /** Generate a command function that controls a property
251 A common pattern for command functions is to simply get or
252 set a single object property. This wrapper will generate a
253 correct function to do that.
255 valspec
= valspec
|| typeof o
[prop
];
256 desc
= desc
|| "Retrieve or modify the value of " + prop
;
257 return cmd(function () {
258 if (arguments
.length
)
259 o
[prop
] = arguments
[0];
261 }, "<" + valspec
+ "?>", desc
);
264 var QUOTED_SPLIT
= /[^"\s]+|"(?:\\"|[^"])+"/g;
265 var COMMAND_SPLIT
= /\s+(&&|\|\||;)\s+/g;
267 function parseParam (param
) {
268 if (yf
.head(param
) === "{" && yf
.last(param
) === "}")
269 return resolvePropertyPath(
270 this, param
.substr(1, param
.length
- 2));
271 try { return JSON
.parse(param
); }
272 catch (exc
) { return param
; }
275 function parseCommand (cmdstring
, ctx
) {
276 /** Parse a command string into an invocation object.
278 The command string has a form like `+quux 1 2 3` or
279 `foobar "hello world"`.
281 Multiple commands can be joined in one string with &&, ||,
282 or ;. To use these characters literally as a command
283 argument place them in quotes.
285 Arguments wrapped in {}s are interpreted as property paths
286 for the provided context object. `{x[0].y}` will resolve
287 `ctx.x[0].y` and put that into the arguments array. To
288 avoid this behavior and get a literal string bounded by
289 {}, JSON-encode the string beforehand (e.g. `"{x[0].y}"`).
291 The returned array contains objects with three properties:
292 `name` - the command name to execute
293 `args` - an array of objects to pass as arguments
294 `toggle` - if the command value should be toggled ('++')
296 `cond` - "&&", "||", or ";", indicating what kind of
297 conditional should be applied.
301 var conds
= cmdstring
.split(COMMAND_SPLIT
);
302 for (var i
= -1; i
< conds
.length
; i
+= 2) {
303 var args
= conds
[i
+ 1].match(QUOTED_SPLIT
).map(parseParam
, ctx
);
304 var name
= args
.shift();
306 if (name
[0] === "+" && name
[1] === "+") {
307 name
= name
.substring(2);
309 } else if (name
[0] === "+") {
310 name
= name
.substring(1);
312 } else if (name
[0] === "-") {
313 name
= name
.substring(1);
316 invs
.push({ name
: name
, args
: args
, toggle
: toggle
,
317 cond
: conds
[i
] || ";"});
322 yuu
.CommandStack
= yT({
323 constructor: function () {
324 /** A stack of command sets for command lookup and execution */
325 this._cmdsets
= yf
.slice(arguments
);
328 push: function (cmdset
) {
329 /** Add a command set to the lookup stack. */
330 this._cmdsets
= this._cmdsets
.concat(cmdset
);
333 remove: function (cmdset
) {
334 /** Remove a command set from the lookup stack. */
335 this._cmdsets
= yf
.without(this._cmdsets
, cmdset
);
338 insertBefore: function (cmdset
, before
) {
339 this._cmdsets
= yf
.insertBefore(
340 this._cmdsets
.slice(), cmdset
, before
);
343 execute: function (cmdstring
, ctx
) {
344 /* Execute a command given a command string.
346 The command stack is searched top-down for the first
347 command with a matching name, and it is invoked. No
348 other commands are called.
350 A command set may also provide a special function named
351 `$`. If no matching command name is found, this
352 function is called with the raw invocation object (the
353 result of yuu.parseCommand) and may return true to stop
354 processing as if the command had been found.
356 var invs
= parseCommand(cmdstring
, ctx
);
359 yf
.each
.call(this, function (inv
) {
360 if ((inv
.cond
=== "&&" && !cond
) || (inv
.cond
=== "||" && cond
))
362 if (!yf
.eachrUntil(function (cmdset
) {
363 var cmd
= cmdset
[inv
.name
];
366 inv
.args
.push(!cmd
.apply(null, inv
.args
));
367 yuu
.log("commands", "Executing:", inv
.name
,
368 inv
.args
.map(JSON
.stringify
).join(" "));
369 res
= cmd
.apply(null, inv
.args
);
370 cond
= res
=== undefined ? cond
: !!res
;
371 yuu
.log("commands", "Result:", JSON
.stringify(res
));
374 return cmdset
.$ && cmdset
.$(inv
);
376 yuu
.log("errors", "Unknown command", inv
.name
);
382 yuu
.extractCommands = function (object
) {
384 yf
.each(function (prop
) {
385 // Check the descriptor before checking the value, because
386 // checking the value of accessors (which should never be
387 // stable commands) is generally a bad idea during
388 // constructors, and command sets are often filled in during
390 if (yT
.isDataDescriptor(yT
.getPropertyDescriptor(object
, prop
))
391 && isCommand(object
[prop
]))
392 commands
[prop
] = object
[prop
].bind(object
);
393 }, yf
.allKeys(object
));
397 yuu
.commandStack
= new yuu
.CommandStack(yuu
.defaultCommands
= {
398 /** The default command stack and set. */
399 cmds
: yuu
.cmd(function (term
) {
402 yuu
.commandStack
._cmdsets
.forEach(function (cmdset
) {
403 for (var cmdname
in cmdset
) {
404 if (cmdname
.indexOf(term
) >= 0) {
405 var cmd
= cmdset
[cmdname
];
408 msg
= [cmdname
, cmd
.usage
, "--", cmd
.description
];
410 msg
= [cmdname
, "--", cmd
.description
];
411 cmds
.push(msg
.join(" "));
415 yuu
.log("messages", cmds
.join("\n"));
416 }, "<term?>", "display available commands (matching the term)"),
418 echo
: yuu
.cmd(function () {
419 yuu
.log("messages", arguments
);
420 }, "...", "echo arguments to the console"),
422 log
: yuu
.cmd(function (name
, state
) {
423 if (state
!== undefined)
424 yuu
.log
[name
] = !!state
;
425 return yuu
.log
[name
];
426 }, "<category> <boolean?>", "enable/disable a logging category")
430 yuu
.defaultCommands
.showDevTools
= yuu
.cmd(function () {
432 gui
.Window
.get().showDevTools();
433 }, "show developer tools");
435 yuu
.anchorPoint = function (anchor
, x0
, y0
, x1
, y1
) {
436 /** Calculate the anchor point for a box given extents and anchor mode
438 This function is the inverse of yuu.bottomLeft.
441 case "center": return [(x0
+ x1
) / 2, (y0
+ y1
) / 2];
442 case "top": return [(x0
+ x1
) / 2, y1
];
443 case "bottom": return [(x0
+ x1
) / 2, y0
];
444 case "left": return [x0
, (y0
+ y1
) / 2];
445 case "right": return [x1
, (y0
+ y1
) / 2];
447 case "topleft": return [x0
, y1
];
448 case "topright": return [x1
, y1
];
449 case "bottomleft": return [x0
, y0
];
450 case "bottomright": return [x0
, y0
];
451 default: return [anchor
[0], anchor
[1]];
455 yuu
.bottomLeft = function (anchor
, x
, y
, w
, h
) {
456 /** Calculate the bottom-left for a box given size and anchor mode
458 This function is the inverse of yuu.anchorPoint.
461 case "center": return [x
- w
/ 2, y
- h
/ 2];
462 case "top": return [x
- w
/ 2, y
- h
];
463 case "bottom": return [x
- w
/ 2, y
];
464 case "left": return [x
, y
- h
/ 2];
465 case "right": return [x
- w
, y
- h
/ 2];
467 case "topleft": return [x
, y
- h
];
468 case "topright": return [x
- w
, y
- h
];
469 case "bottomleft": return [x
, y
];
470 case "bottomright": return [x
- w
, y
];
471 default: return [anchor
[0], anchor
[1]];
475 yuu
.lerp = function (a
, b
, p
) {
476 return (a
!== null && a
!== undefined && a
.lerp
)
477 ? a
.lerp(b
, p
) : (b
!== null && b
!== undefined && b
.lerp
)
478 ? b
.lerp(a
, 1 - p
) : p
< 0.5 ? a
: b
;
481 yuu
.bilerp = function (x0y0
, x1y0
, x0y1
, x1y1
, px
, py
) {
482 /** Bilinearly interpolate between four values in two dimensions */
483 return yuu
.lerp(yuu
.lerp(x0y0
, x1y0
, px
), yuu
.lerp(x0y1
, x1y1
, px
), py
);
486 function resolvePropertyPath (object
, path
) {
487 /** Look up a full property path
489 If a null is encountered in the path, this function returns
490 null. If undefined is encountered or a property is missing, it
493 var parts
= path
.replace(/\[(\w+)\]/g, '.$1').split('.');
495 i
< parts
.length
&& object
!== undefined && object
!== null;
497 object
= object
[parts
[i
]];
503 /** Somewhat like Python's random.Random.
505 Passed a function that returns a uniform random variable in
506 [0, 1) it can do other useful randomization algorithms.
508 Its methods are implemented straightforwardly rather than
509 rigorously - this means they may not behave correctly in
510 common edge cases like precision loss.
512 constructor: function (generator
) {
513 this.random
= generator
|| Math
.random
;
514 this._spareGauss
= null;
517 choice: function (seq
) {
518 /** Return a random element from the provided array. */
519 return seq
[this.randrange(0, seq
.length
)];
524 /** Return a uniform random integer in [0, a). */
525 return (this.random() * a
) | 0;
529 /** Return a uniform random integer in [a, b). */
532 return a
+ ((this.random() * (b
- a
)) | 0);
535 function (a
, b
, step
) {
536 /** Return a uniform random number in [a, b).
538 The number is constrained to values of a + i * step
539 where i is a non-negative integer.
541 var i
= Math
.ceil((b
- a
) / step
);
542 return a
+ this.randrange(i
) * step
;
548 /** Return a uniform random variable in [0, a). */
549 return a
* this.random();
552 /** Return a uniform random variable in [a, b). */
553 return a
+ (b
- a
) * this.random();
557 gauss: function (mean
, sigma
) {
558 var u
= this._spareGauss
, v
, s
;
559 this._spareGauss
= null;
563 u
= this.uniform(-1, 1);
564 v
= this.uniform(-1, 1);
566 } while (s
>= 1.0 || s
=== 0.0);
567 var t
= Math
.sqrt(-2.0 * Math
.log(s
) / s
);
568 this._spareGauss
= v
* t
;
571 return mean
+ sigma
* u
;
575 /** Return true the given percent of the time (default 50%). */
576 function () { return this.random() < 0.5; },
577 function (a
) { return this.random() < a
; }
580 randsign: function (v
) {
581 return this.randbool() ? v
: -v
;
584 shuffle: function (seq
) {
585 for (var i
= seq
.length
- 1; i
> 0; --i
) {
586 var index
= this.randrange(i
+ 1);
594 discard: function (z
) {
601 yuu
.createLCG
= yf
.argcd(
602 /** Linear congruential random generator
604 This returns a function that generates numbers [0, 1) as
605 with Math.random. You can also read or assign the `state`
606 attribute to set the internal state.
608 function () { return yuu
.createLCG(Math
.random() * 2147483647); },
610 var state
= seed
| 0;
611 return function generator () {
612 state
= (state
* 1664525 + 1013904223) % 4294967296;
613 return state
/ 4294967296;
618 yuu
.random
= new yuu
.Random();
620 function defaultKey (args
) {
621 // Cache things that can be constructed with one string.
622 return args
.length
=== 1 && yf
.isString(args
[0]) ? args
[0] : null;
625 yuu
.Caching = function (Type
, cacheKey
) {
627 var k
= ctor
._cacheKey(arguments
);
628 var o
= k
&& ctor
._cache
[k
];
630 o
= ctor
._cache
[k
] = yf
.construct(ctor
.Uncached
, arguments
);
633 ctor
._cacheKey
= cacheKey
|| defaultKey
;
635 ctor
.Uncached
= Type
;
639 yuu
.transpose2d = function (a
) {
640 for (var x
= 0; x
< a
.length
; ++x
) {
641 for (var y
= 0; y
< x
; ++y
) {
649 yuu
.normalizeRadians = function (theta
) {
651 return (theta
+ 3 * PI
) % (2 * PI
) - PI
;
654 yuu
.radians = function (v
) {
655 return v
* (Math
.PI
/ 180.0);
658 yuu
.degrees = function (v
) {
659 return v
* (180.0 / Math
.PI
);
662 var SHORT
= /(\/|^)@(.+)$/;
663 yuu
.resourcePath = function (path
, category
, ext
) {
665 if ((match
= path
.match(SHORT
))) {
666 path
= path
.replace(/^yuu\/@/, yuu
.PATH
+ "@")
667 .replace(SHORT
, "$1data/" + category
+ "/$2");
668 if (match
[2].indexOf(".") === -1)
674 yuu
.ready = function (resources
, result
) {
675 return Promise
.all(yf
.filter(null, yf
.pluck("ready", resources
)))
679 yuu
.openURL = function (url
) {
680 if (gui
&& gui
.Shell
)
681 gui
.Shell
.openExternal(url
);
686 function crossPlatformFilename (basename
) {
688 // Replace D/M/Y with D-M-Y, and H:M:S with H.M.S.
689 .replace(/\//g, "-").replace(/:/g
, ".")
690 // Replace all other problematic characters with _.
691 .replace(/["<>*?|\\]/g, "_");
694 yuu
.downloadURL = function (url
, suggestedName
) {
695 var regex
= /^data:[^;+]+;base64,(.*)$/;
696 var matches
= url
.match(regex
);
697 suggestedName
= crossPlatformFilename(suggestedName
);
699 var data
= matches
[1];
700 var buffer
= new Buffer(data
, 'base64');
701 var HOME
= process
.env
.HOME
702 || process
.env
.HOMEPATH
703 || process
.env
.USERPROFILE
;
704 var filename
= HOME
+ "/" + suggestedName
;
705 console
.log("Saving to", filename
);
706 fs
.writeFileSync(filename
, buffer
);
708 var link
= document
.createElement('a');
709 link
.style
.display
= "none";
711 link
.download
= suggestedName
;
712 // Firefox (as of 28) won't download from a link not rooted in
713 // the document; so, root it and then remove it when done.
714 document
.body
.appendChild(link
);
716 document
.body
.removeChild(link
);
721 constructor: yf
.argcd(
722 function () { this.constructor(0, 0, 0, 0); },
723 function (w
, h
) { this.constructor(0, 0, w
, h
); },
724 function (x0
, y0
, x1
, y1
) {
733 get: function () { return this.x1
- this.x0
; },
734 set: function (w
) { this.x1
= this.x0
+ w
; }
738 get: function () { return this.y1
- this.y0
; },
739 set: function (h
) { this.y1
= this.y0
+ h
; }
742 size
: { swizzle
: "wh" },
745 function (p
) { return this.contains(p
.x
, p
.y
); },
747 return x
>= this.x0
&& x
< this.x1
748 && y
>= this.y0
&& y
< this.y1
;
752 matchAspectRatio: function (outer
) {
753 var matched
= new this.constructor(
754 this.x0
, this.y0
, this.x1
, this.y1
);
755 var aRatio
= matched
.w
/ matched
.h
;
756 var bRatio
= outer
.w
/ outer
.h
;
757 if (aRatio
> bRatio
) {
758 // too wide, must be taller
759 var h
= matched
.w
/ bRatio
;
760 var dh
= h
- matched
.h
;
761 matched
.y0
-= dh
/ 2;
762 matched
.y1
+= dh
/ 2;
764 // too tall, must be wider
765 var w
= matched
.h
* bRatio
;
766 var dw
= w
- matched
.w
;
767 matched
.x0
-= dw
/ 2;
768 matched
.x1
+= dw
/ 2;
773 alignedInside: function (outer
, alignment
) {
781 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
785 x0
= outer
.x0
- this.w
;
790 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
793 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
794 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
797 x0
= outer
.x1
- this.w
;
798 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
802 y0
= outer
.y1
- this.h
;
805 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
806 y0
= outer
.y1
- this.h
;
809 x0
= outer
.x1
- this.w
;
810 y0
= outer
.y1
- this.h
;
813 return new this.constructor(x0
, y0
, x0
+ this.w
, y0
+ this.h
);
817 function splitPathExtension (path
) {
818 var dot
= path
.lastIndexOf(".");
819 if (dot
<= 0) return [path
, ""];
821 var dir
= path
.lastIndexOf("/");
822 if (dot
< dir
) return [path
, ""];
824 return [path
.substring(0, dot
), path
.substring(dot
)];
826 yuu
.splitPathExtension
= splitPathExtension
;
829 yT
.defineProperty(String
.prototype, "lerp", function (b
, p
) {
831 // Never numericLerp - if that's desired force Numbers.
832 // Be more conservative than stringLerp since this runs
833 // often and the diff can't be easily hoisted.
834 return this.length
* b
.length
> 256
835 ? stringLerp
.fastLerp(this, b
, p
)
836 : stringLerp
.diffLerp(this, b
, p
);
840 yT
.defineProperties(Number
.prototype, {
841 lerp: function (b
, p
) { return this + (b
- this) * p
; }
844 yT
.defineProperties(Array
.prototype, {
845 lerp: function (b
, p
) {
846 var length
= Math
.round(this.length
.lerp(b
.length
, p
));
847 var c
= new this.constructor(length
);
848 for (var i
= 0; i
< length
; ++i
) {
849 if (i
>= this.length
)
851 else if (i
>= b
.length
)
854 c
[i
] = this[i
].lerp(b
[i
], p
);
860 /** Typed array extensions
862 https://www.khronos.org/registry/typedarray/specs/1.0/
863 BUT: Read on for fun times in browser land~
865 Ideally we could just set these once on ArrayBufferView, but
866 the typed array specification doesn't require that such a
867 constructor actually exist. And in Firefox (18), it doesn't.
869 More infurating, in Safari (7.0.3) Int8Array etc. are not
870 functions so this needs to be added to the prototype
871 directly. This is a violation of the specification which
872 requires such constructors, and ECMA which requires
873 constructors be functions, and common decency.
876 [ Float32Array
, Float64Array
, Int8Array
, Uint8Array
,
877 Int16Array
, Uint16Array
, Int32Array
, Uint32Array
878 ].forEach(function (A
) {
879 yT
.defineProperties(A
.prototype, {
881 /** Like Array's slice, but for typed arrays */
882 function () { return new this.constructor(this); },
884 return new this.constructor(this.subarray(begin
));
886 function (begin
, end
) {
887 return new this.constructor(this.subarray(begin
, end
));
891 fill
: Array
.prototype.fill
,
892 reverse
: Array
.prototype.reverse
,
894 lerp: function (b
, p
) {
899 var c
= new this.constructor(this.length
);
900 for (var i
= 0; i
< this.length
; ++i
)
901 c
[i
] = this[i
] + (b
[i
] - this[i
]) * p
;
907 }).call(typeof exports
=== "undefined" ? this : exports
,
908 typeof exports
=== "undefined" ? (this.yuu
= {}) : exports
);