1 /* Copyright 2014 Yukkuri Games
2 Licensed under the terms of the GNU GPL v2 or later
3 @license https://www.gnu.org/licenses/gpl-2.0.html
4 @source: https://yukkurigames.com/yuu/
10 yuu
.require = function (m
) {
11 try { return require(m
); }
12 catch (exc
) { return null; }
18 var yT
= this.yT
|| require("./yT");
19 var yf
= this.yf
|| require("./yf");
20 var gui
= yuu
.require("nw.gui");
21 var fs
= yuu
.require("fs");
22 var stringLerp
= this.stringLerp
|| yuu
.require("string-lerp");
25 var initOptions
= null;
27 if (typeof document
!== "undefined") {
28 var scripts
= document
.getElementsByTagName('script');
29 var path
= yf
.last(scripts
).src
.split('?')[0];
30 yuu
.PATH
= path
.split('/').slice(0, -1).join('/') + '/';
32 yuu
.PATH
= "file://" + escape(module
.__dirname
) + "/";
35 yuu
.registerInitHook
= initHooks
.push
.bind(initHooks
);
36 /** Register a hook to be called during Yuu initialization
38 Hooks are called in registration order with the module and
39 the options dictionary passed to the init method. (This is
40 also set to the module.)
43 function showError (exc
, kind
) {
44 var prefix
= "yuu-" + (kind
|| "") + "error";
46 var dialog
= document
.getElementById(prefix
);
47 var errorMessage
= document
.getElementById(prefix
+ "-message");
49 errorMessage
.textContent
= exc
.message
;
50 var errorStack
= document
.getElementById(prefix
+ "-stack");
52 errorStack
.textContent
= exc
.message
+ "\n\n" + exc
.stack
;
55 yuu
.showError
= showError
;
57 function fatalError (exc
) {
58 var dialog
= showError(exc
, "fatal-");
60 dialog
.style
.display
= "block";
62 gui
.Window
.get().show();
63 gui
.Window
.get().focus();
68 yuu
.init = function (options
) {
69 /** Initialize Yuu and call all registered hooks
72 document
.body
.className
+= (navigator
.standalone
|| gui
)
73 ? " standalone" : " browser";
76 var win
= gui
.Window
.get();
77 var nativeMenuBar
= new gui
.Menu({ type
: "menubar" });
78 if (nativeMenuBar
.createMacBuiltin
) {
79 nativeMenuBar
.createMacBuiltin(
80 document
.title
, { hideEdit
: true });
81 win
.menu
= nativeMenuBar
;
84 win
.on("minimize", function () {
85 var ev
= new Event("visibilitychange");
87 wkdoc
.dispatchEvent(ev
);
89 win
.on("restore", function () {
90 var ev
= new Event("visibilitychange");
92 wkdoc
.dispatchEvent(ev
);
94 win
.on('new-win-policy', function (frame
, url
, policy
) {
95 if (url
.startsWith('chrome')) {
96 policy
.forceNewPopup();
99 gui
.Shell
.openExternal(url
);
104 return new Promise(function (resolve
) {
105 // TODO: Some kind of loading progress bar.
106 initOptions
= options
|| {};
107 yuu
.log("messages", "Initializing Yuu engine.");
109 // initHooks can be pushed to while iterating, so iterate
110 // by index, not a foreach loop.
111 for (var i
= 0; i
< initHooks
.length
; ++i
)
112 promises
.push(initHooks
[i
].call(yuu
, initOptions
));
113 initHooks
= null; // Bust future registerInitHook calls.
114 yuu
.log("messages", "Initialization hooks complete.");
116 gui
.Window
.get().show();
117 gui
.Window
.get().focus();
119 resolve(Promise
.all(yf
.filter(null, promises
)));
120 }).then(function () {
121 yuu
.log("messages", "Loading complete.");
122 }).catch(fatalError
);
125 yuu
.log
= yf
.argv(function (category
, args
) {
126 /** Log a message to the console.
128 This supports simple filtering by setting e.g.
129 `yuu.log.errors = true` to log anything with the
132 if (!category
|| this.log
[category
]) {
134 case "errors": return console
.error
.apply(console
, args
);
135 case "warnings": return console
.warn
.apply(console
, args
);
136 default: return console
.log
.apply(console
, args
);
141 yuu
.log
.errors
= true;
142 yuu
.log
.warnings
= true;
143 yuu
.log
.messages
= true;
145 yuu
.logError = function (e
) {
146 yuu
.log("errors", e
.message
|| "unknown error", e
);
149 yuu
.GET = function (url
, params
) {
150 /** Promise the HTTP GET the contents of a URL. */
151 return new Promise(function (resolve
, reject
) {
152 var req
= new XMLHttpRequest();
153 req
.open("GET", url
, true);
154 for (var k
in params
)
156 req
.onload = function () {
157 var status
= this.status
;
158 // status === 0 is given by NW.js for success.
159 if ((status
>= 200 && status
< 300) || status
=== 0)
160 resolve(this.response
);
163 url
+ ": " + status
+ ": " + this.statusText
));
165 req
.onabort = function () { reject(new Error("aborted")); };
166 req
.onerror = function () { reject(new Error("network error")); };
167 req
.ontimeout = function () { reject(new Error("timed out")); };
172 yuu
.Image = function (src
) {
173 /** Promises a DOM Image. */
174 return new Promise(function (resolve
, reject
) {
175 var img
= new Image();
176 img
.onload = function () {
179 img
.onerror = function () {
180 var msg
= "Unable to load " + img
.src
;
181 yuu
.log("errors", msg
);
182 reject(new Error(msg
));
188 /** Command parsing and execution
190 The command API serves several roles. It is a way to enable or
191 disable different game logic within different scenes; capture
192 and replay or automate game events; loosely or late-bind game
193 modules; customize input mappings; and a debugging tool to
194 help inspect or modify the state of a running program.
196 A command is a string of a command name followed by arguments
197 separated by whitespace. It's similar to a fully bound
198 closure. It is less flexible but easier to inspect, store,
199 replay, and pass around.
201 Command names are mapped to functions, grouped into sets, and
202 the sets pushed onto a stack. They are executed by passing the
203 command string to the execute function which walks the stack
204 looking for the matching command.
206 If the command string is prefaced with + or -, true or false
207 are appended to the argument list. e.g. `+command` is
208 equivalent to `command true` and `-command 1 b` is equivalent
209 to `command 1 b false`. By convention, commands of those forms
210 return their internal state when called with neither true or
211 false. This is useful for another special prefix, ++. When
212 called as `++command`, it is executed with no arguments, the
213 result inverted (with !), and then called again passing that
214 inverted value as the last argument.
217 function isCommand (f
) {
218 return yf
.isFunction(f
) && f
._isCommandFunction
;
221 function cmdbind () {
222 // Commands are practically a subtype of functions. Binding
223 // them (which happens often, e.g. when Scenes register
224 // commands) should also return a command.
225 var f
= Function
.prototype.bind
.apply(this, arguments
);
226 // usage is still valid iff no new arguments were given.
227 return cmd(f
, arguments
.length
<= 1 && this.usage
, this.description
);
230 var cmd
= yuu
.cmd
= yf
.argcd(
231 /** Decorate a function for command execution
233 Command functions need some special attributes to work
234 correctly. This decorator makes sure they have them.
236 function (f
) { return yuu
.cmd(f
, null, null); },
237 function (f
, description
) { return yuu
.cmd(f
, null, description
); },
238 function (f
, usage
, description
) {
239 f
._isCommandFunction
= true;
240 f
.usage
= usage
|| " <value>".repeat(f
.length
).substring(1);
241 f
.description
= description
|| "no description provided";
247 yuu
.propcmd = function (o
, prop
, desc
, valspec
) {
248 /** Generate a command function that controls a property
250 A common pattern for command functions is to simply get or
251 set a single object property. This wrapper will generate a
252 correct function to do that.
254 valspec
= valspec
|| typeof o
[prop
];
255 desc
= desc
|| "Retrieve or modify the value of " + prop
;
256 return cmd(function () {
257 if (arguments
.length
)
258 o
[prop
] = arguments
[0];
260 }, "<" + valspec
+ "?>", desc
);
263 var QUOTED_SPLIT
= /[^"\s]+|"(?:\\"|[^"])+"/g;
264 var COMMAND_SPLIT
= /\s+(&&|\|\||;)\s+/g;
266 function parseParam (param
) {
267 if (yf
.head(param
) === "{" && yf
.last(param
) === "}")
268 return resolvePropertyPath(
269 this, param
.substr(1, param
.length
- 2));
270 try { return JSON
.parse(param
); }
271 catch (exc
) { return param
; }
274 function parseCommand (cmdstring
, ctx
) {
275 /** Parse a command string into an invocation object.
277 The command string has a form like `+quux 1 2 3` or
278 `foobar "hello world"`.
280 Multiple commands can be joined in one string with &&, ||,
281 or ;. To use these characters literally as a command
282 argument place them in quotes.
284 Arguments wrapped in {}s are interpreted as property paths
285 for the provided context object. `{x[0].y}` will resolve
286 `ctx.x[0].y` and put that into the arguments array. To
287 avoid this behavior and get a literal string bounded by
288 {}, JSON-encode the string beforehand (e.g. `"{x[0].y}"`).
290 The returned array contains objects with three properties:
291 `name` - the command name to execute
292 `args` - an array of objects to pass as arguments
293 `toggle` - if the command value should be toggled ('++')
295 `cond` - "&&", "||", or ";", indicating what kind of
296 conditional should be applied.
300 var conds
= cmdstring
.split(COMMAND_SPLIT
);
301 for (var i
= -1; i
< conds
.length
; i
+= 2) {
302 var args
= conds
[i
+ 1].match(QUOTED_SPLIT
).map(parseParam
, ctx
);
303 var name
= args
.shift();
305 if (name
[0] === "+" && name
[1] === "+") {
306 name
= name
.substring(2);
308 } else if (name
[0] === "+") {
309 name
= name
.substring(1);
311 } else if (name
[0] === "-") {
312 name
= name
.substring(1);
315 invs
.push({ name
: name
, args
: args
, toggle
: toggle
,
316 cond
: conds
[i
] || ";"});
321 yuu
.CommandStack
= yT({
322 constructor: function () {
323 /** A stack of command sets for command lookup and execution */
324 this._cmdsets
= yf
.slice(arguments
);
327 push: function (cmdset
) {
328 /** Add a command set to the lookup stack. */
329 this._cmdsets
= this._cmdsets
.concat(cmdset
);
332 remove: function (cmdset
) {
333 /** Remove a command set from the lookup stack. */
334 this._cmdsets
= yf
.without(this._cmdsets
, cmdset
);
337 insertBefore: function (cmdset
, before
) {
338 this._cmdsets
= yf
.insertBefore(
339 this._cmdsets
.slice(), cmdset
, before
);
342 execute: function (cmdstring
, ctx
) {
343 /* Execute a command given a command string.
345 The command stack is searched top-down for the first
346 command with a matching name, and it is invoked. No
347 other commands are called.
349 A command set may also provide a special function named
350 `$`. If no matching command name is found, this
351 function is called with the raw invocation object (the
352 result of yuu.parseCommand) and may return true to stop
353 processing as if the command had been found.
355 var invs
= parseCommand(cmdstring
, ctx
);
358 yf
.each
.call(this, function (inv
) {
359 if ((inv
.cond
=== "&&" && !cond
) || (inv
.cond
=== "||" && cond
))
361 if (!yf
.eachrUntil(function (cmdset
) {
362 var cmd
= cmdset
[inv
.name
];
365 inv
.args
.push(!cmd
.apply(null, inv
.args
));
366 yuu
.log("commands", "Executing:", inv
.name
,
367 inv
.args
.map(JSON
.stringify
).join(" "));
368 res
= cmd
.apply(null, inv
.args
);
369 cond
= res
=== undefined ? cond
: !!res
;
370 yuu
.log("commands", "Result:", JSON
.stringify(res
));
373 return cmdset
.$ && cmdset
.$(inv
);
375 yuu
.log("errors", "Unknown command", inv
.name
);
381 yuu
.extractCommands = function (object
) {
383 yf
.each(function (prop
) {
384 // Check the descriptor before checking the value, because
385 // checking the value of accessors (which should never be
386 // stable commands) is generally a bad idea during
387 // constructors, and command sets are often filled in during
389 if (yT
.isDataDescriptor(yT
.getPropertyDescriptor(object
, prop
))
390 && isCommand(object
[prop
]))
391 commands
[prop
] = object
[prop
].bind(object
);
392 }, yf
.allKeys(object
));
396 yuu
.commandStack
= new yuu
.CommandStack(yuu
.defaultCommands
= {
397 /** The default command stack and set. */
398 cmds
: yuu
.cmd(function (term
) {
401 yuu
.commandStack
._cmdsets
.forEach(function (cmdset
) {
402 for (var cmdname
in cmdset
) {
403 if (cmdname
.indexOf(term
) >= 0) {
404 var cmd
= cmdset
[cmdname
];
407 msg
= [cmdname
, cmd
.usage
, "--", cmd
.description
];
409 msg
= [cmdname
, "--", cmd
.description
];
410 cmds
.push(msg
.join(" "));
414 yuu
.log("messages", cmds
.join("\n"));
415 }, "<term?>", "display available commands (matching the term)"),
417 echo
: yuu
.cmd(function () {
418 yuu
.log("messages", arguments
);
419 }, "...", "echo arguments to the console"),
421 log
: yuu
.cmd(function (name
, state
) {
422 if (state
!== undefined)
423 yuu
.log
[name
] = !!state
;
424 return yuu
.log
[name
];
425 }, "<category> <boolean?>", "enable/disable a logging category")
429 yuu
.defaultCommands
.showDevTools
= yuu
.cmd(function () {
431 gui
.Window
.get().showDevTools();
432 }, "show developer tools");
434 yuu
.anchorPoint = function (anchor
, x0
, y0
, x1
, y1
) {
435 /** Calculate the anchor point for a box given extents and anchor mode
437 This function is the inverse of yuu.bottomLeft.
440 case "center": return [(x0
+ x1
) / 2, (y0
+ y1
) / 2];
441 case "top": return [(x0
+ x1
) / 2, y1
];
442 case "bottom": return [(x0
+ x1
) / 2, y0
];
443 case "left": return [x0
, (y0
+ y1
) / 2];
444 case "right": return [x1
, (y0
+ y1
) / 2];
446 case "topleft": return [x0
, y1
];
447 case "topright": return [x1
, y1
];
448 case "bottomleft": return [x0
, y0
];
449 case "bottomright": return [x0
, y0
];
450 default: return [anchor
[0], anchor
[1]];
454 yuu
.bottomLeft = function (anchor
, x
, y
, w
, h
) {
455 /** Calculate the bottom-left for a box given size and anchor mode
457 This function is the inverse of yuu.anchorPoint.
460 case "center": return [x
- w
/ 2, y
- h
/ 2];
461 case "top": return [x
- w
/ 2, y
- h
];
462 case "bottom": return [x
- w
/ 2, y
];
463 case "left": return [x
, y
- h
/ 2];
464 case "right": return [x
- w
, y
- h
/ 2];
466 case "topleft": return [x
, y
- h
];
467 case "topright": return [x
- w
, y
- h
];
468 case "bottomleft": return [x
, y
];
469 case "bottomright": return [x
- w
, y
];
470 default: return [anchor
[0], anchor
[1]];
474 yuu
.lerp = function (a
, b
, p
) {
475 return (a
!== null && a
!== undefined && a
.lerp
)
476 ? a
.lerp(b
, p
) : (b
!== null && b
!== undefined && b
.lerp
)
477 ? b
.lerp(a
, 1 - p
) : p
< 0.5 ? a
: b
;
480 yuu
.bilerp = function (x0y0
, x1y0
, x0y1
, x1y1
, px
, py
) {
481 /** Bilinearly interpolate between four values in two dimensions */
482 return yuu
.lerp(yuu
.lerp(x0y0
, x1y0
, px
), yuu
.lerp(x0y1
, x1y1
, px
), py
);
485 function resolvePropertyPath (object
, path
) {
486 /** Look up a full property path
488 If a null is encountered in the path, this function returns
489 null. If undefined is encountered or a property is missing, it
492 var parts
= path
.replace(/\[(\w+)\]/g, '.$1').split('.');
494 i
< parts
.length
&& object
!== undefined && object
!== null;
496 object
= object
[parts
[i
]];
502 /** Somewhat like Python's random.Random.
504 Passed a function that returns a uniform random variable in
505 [0, 1) it can do other useful randomization algorithms.
507 Its methods are implemented straightforwardly rather than
508 rigorously - this means they may not behave correctly in
509 common edge cases like precision loss.
511 constructor: function (generator
) {
512 this.random
= generator
|| Math
.random
;
513 this._spareGauss
= null;
516 choice: function (seq
) {
517 /** Return a random element from the provided array. */
518 return seq
[this.randrange(0, seq
.length
)];
523 /** Return a uniform random integer in [0, a). */
524 return (this.random() * a
) | 0;
528 /** Return a uniform random integer in [a, b). */
531 return a
+ ((this.random() * (b
- a
)) | 0);
534 function (a
, b
, step
) {
535 /** Return a uniform random number in [a, b).
537 The number is constrained to values of a + i * step
538 where i is a non-negative integer.
540 var i
= Math
.ceil((b
- a
) / step
);
541 return a
+ this.randrange(i
) * step
;
547 /** Return a uniform random variable in [0, a). */
548 return a
* this.random();
551 /** Return a uniform random variable in [a, b). */
552 return a
+ (b
- a
) * this.random();
556 gauss: function (mean
, sigma
) {
557 var u
= this._spareGauss
, v
, s
;
558 this._spareGauss
= null;
562 u
= this.uniform(-1, 1);
563 v
= this.uniform(-1, 1);
565 } while (s
>= 1.0 || s
=== 0.0);
566 var t
= Math
.sqrt(-2.0 * Math
.log(s
) / s
);
567 this._spareGauss
= v
* t
;
570 return mean
+ sigma
* u
;
574 /** Return true the given percent of the time (default 50%). */
575 function () { return this.random() < 0.5; },
576 function (a
) { return this.random() < a
; }
579 randsign: function (v
) {
580 return this.randbool() ? v
: -v
;
583 shuffle: function (seq
) {
584 for (var i
= seq
.length
- 1; i
> 0; --i
) {
585 var index
= this.randrange(i
+ 1);
593 discard: function (z
) {
600 yuu
.createLCG
= yf
.argcd(
601 /** Linear congruential random generator
603 This returns a function that generates numbers [0, 1) as
604 with Math.random. You can also read or assign the `state`
605 attribute to set the internal state.
607 function () { return yuu
.createLCG(Math
.random() * 2147483647); },
609 var state
= seed
| 0;
610 return function generator () {
611 state
= (state
* 1664525 + 1013904223) % 4294967296;
612 return state
/ 4294967296;
617 yuu
.random
= new yuu
.Random();
619 function defaultKey (args
) {
620 // Cache things that can be constructed with one string.
621 return args
.length
=== 1 && yf
.isString(args
[0]) ? args
[0] : null;
624 yuu
.Caching = function (Type
, cacheKey
) {
626 var k
= ctor
._cacheKey(arguments
);
627 var o
= k
&& ctor
._cache
[k
];
629 o
= ctor
._cache
[k
] = yf
.construct(ctor
.Uncached
, arguments
);
632 ctor
._cacheKey
= cacheKey
|| defaultKey
;
634 ctor
.Uncached
= Type
;
638 yuu
.transpose2d = function (a
) {
639 for (var x
= 0; x
< a
.length
; ++x
) {
640 for (var y
= 0; y
< x
; ++y
) {
648 yuu
.normalizeRadians = function (theta
) {
650 return (theta
+ 3 * PI
) % (2 * PI
) - PI
;
653 yuu
.radians = function (v
) {
654 return v
* (Math
.PI
/ 180.0);
657 yuu
.degrees = function (v
) {
658 return v
* (180.0 / Math
.PI
);
661 var SHORT
= /(\/|^)@(.+)$/;
662 yuu
.resourcePath = function (path
, category
, ext
) {
664 if ((match
= path
.match(SHORT
))) {
665 path
= path
.replace(/^yuu\/@/, yuu
.PATH
+ "@")
666 .replace(SHORT
, "$1data/" + category
+ "/$2");
667 if (match
[2].indexOf(".") === -1)
673 yuu
.ready = function (resources
, result
) {
674 return Promise
.all(yf
.filter(null, yf
.pluck("ready", resources
)))
678 yuu
.openURL = function (url
) {
679 if (gui
&& gui
.Shell
)
680 gui
.Shell
.openExternal(url
);
685 function crossPlatformFilename (basename
) {
687 // Replace D/M/Y with D-M-Y, and H:M:S with H.M.S.
688 .replace(/\//g, "-").replace(/:/g
, ".")
689 // Replace all other problematic characters with _.
690 .replace(/["<>*?|\\]/g, "_");
693 yuu
.downloadURL = function (url
, suggestedName
) {
694 var regex
= /^data:[^;+]+;base64,(.*)$/;
695 var matches
= url
.match(regex
);
696 suggestedName
= crossPlatformFilename(suggestedName
);
698 var data
= matches
[1];
699 var buffer
= new Buffer(data
, 'base64');
700 var HOME
= process
.env
.HOME
701 || process
.env
.HOMEPATH
702 || process
.env
.USERPROFILE
;
703 var filename
= HOME
+ "/" + suggestedName
;
704 console
.log("Saving to", filename
);
705 fs
.writeFileSync(filename
, buffer
);
707 var link
= document
.createElement('a');
708 link
.style
.display
= "none";
710 link
.download
= suggestedName
;
711 // Firefox (as of 28) won't download from a link not rooted in
712 // the document; so, root it and then remove it when done.
713 document
.body
.appendChild(link
);
715 document
.body
.removeChild(link
);
720 constructor: yf
.argcd(
721 function () { this.constructor(0, 0, 0, 0); },
722 function (w
, h
) { this.constructor(0, 0, w
, h
); },
723 function (x0
, y0
, x1
, y1
) {
732 get: function () { return this.x1
- this.x0
; },
733 set: function (w
) { this.x1
= this.x0
+ w
; }
737 get: function () { return this.y1
- this.y0
; },
738 set: function (h
) { this.y1
= this.y0
+ h
; }
741 size
: { swizzle
: "wh" },
744 function (p
) { return this.contains(p
.x
, p
.y
); },
746 return x
>= this.x0
&& x
< this.x1
747 && y
>= this.y0
&& y
< this.y1
;
751 matchAspectRatio: function (outer
) {
752 var matched
= new this.constructor(
753 this.x0
, this.y0
, this.x1
, this.y1
);
754 var aRatio
= matched
.w
/ matched
.h
;
755 var bRatio
= outer
.w
/ outer
.h
;
756 if (aRatio
> bRatio
) {
757 // too wide, must be taller
758 var h
= matched
.w
/ bRatio
;
759 var dh
= h
- matched
.h
;
760 matched
.y0
-= dh
/ 2;
761 matched
.y1
+= dh
/ 2;
763 // too tall, must be wider
764 var w
= matched
.h
* bRatio
;
765 var dw
= w
- matched
.w
;
766 matched
.x0
-= dw
/ 2;
767 matched
.x1
+= dw
/ 2;
772 alignedInside: function (outer
, alignment
) {
780 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
784 x0
= outer
.x0
- this.w
;
789 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
792 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
793 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
796 x0
= outer
.x1
- this.w
;
797 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
801 y0
= outer
.y1
- this.h
;
804 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
805 y0
= outer
.y1
- this.h
;
808 x0
= outer
.x1
- this.w
;
809 y0
= outer
.y1
- this.h
;
812 return new this.constructor(x0
, y0
, x0
+ this.w
, y0
+ this.h
);
816 function splitPathExtension (path
) {
817 var dot
= path
.lastIndexOf(".");
818 if (dot
<= 0) return [path
, ""];
820 var dir
= path
.lastIndexOf("/");
821 if (dot
< dir
) return [path
, ""];
823 return [path
.substring(0, dot
), path
.substring(dot
)];
825 yuu
.splitPathExtension
= splitPathExtension
;
828 yT
.defineProperty(String
.prototype, "lerp", function (b
, p
) {
830 // Never numericLerp - if that's desired force Numbers.
831 // Be more conservative than stringLerp since this runs
832 // often and the diff can't be easily hoisted.
833 return this.length
* b
.length
> 256
834 ? stringLerp
.fastLerp(this, b
, p
)
835 : stringLerp
.diffLerp(this, b
, p
);
839 yT
.defineProperties(Number
.prototype, {
840 lerp: function (b
, p
) { return this + (b
- this) * p
; }
843 yT
.defineProperties(Array
.prototype, {
844 lerp: function (b
, p
) {
845 var length
= Math
.round(this.length
.lerp(b
.length
, p
));
846 var c
= new this.constructor(length
);
847 for (var i
= 0; i
< length
; ++i
) {
848 if (i
>= this.length
)
850 else if (i
>= b
.length
)
853 c
[i
] = this[i
].lerp(b
[i
], p
);
859 /** Typed array extensions
861 https://www.khronos.org/registry/typedarray/specs/1.0/
862 BUT: Read on for fun times in browser land~
864 Ideally we could just set these once on ArrayBufferView, but
865 the typed array specification doesn't require that such a
866 constructor actually exist. And in Firefox (18), it doesn't.
868 More infurating, in Safari (7.0.3) Int8Array etc. are not
869 functions so this needs to be added to the prototype
870 directly. This is a violation of the specification which
871 requires such constructors, and ECMA which requires
872 constructors be functions, and common decency.
875 [ Float32Array
, Float64Array
, Int8Array
, Uint8Array
,
876 Int16Array
, Uint16Array
, Int32Array
, Uint32Array
877 ].forEach(function (A
) {
878 yT
.defineProperties(A
.prototype, {
880 /** Like Array's slice, but for typed arrays */
881 function () { return new this.constructor(this); },
883 return new this.constructor(this.subarray(begin
));
885 function (begin
, end
) {
886 return new this.constructor(this.subarray(begin
, end
));
890 fill
: Array
.prototype.fill
,
891 reverse
: Array
.prototype.reverse
,
893 lerp: function (b
, p
) {
898 var c
= new this.constructor(this.length
);
899 for (var i
= 0; i
< this.length
; ++i
)
900 c
[i
] = this[i
] + (b
[i
] - this[i
]) * p
;
906 }).call(typeof exports
=== "undefined" ? this : exports
,
907 typeof exports
=== "undefined" ? (this.yuu
= {}) : exports
);