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
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
;
82 win
.on("minimize", function () {
83 var ev
= new Event("visibilitychange");
85 wkdoc
.dispatchEvent(ev
);
87 win
.on("restore", function () {
88 var ev
= new Event("visibilitychange");
90 wkdoc
.dispatchEvent(ev
);
94 return new Promise(function (resolve
) {
95 // TODO: Some kind of loading progress bar.
96 initOptions
= options
|| {};
97 yuu
.log("messages", "Initializing Yuu engine.");
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.");
106 gui
.Window
.get().show();
107 gui
.Window
.get().focus();
109 resolve(Promise
.all(yf
.filter(null, promises
)));
110 }).then(function () {
111 yuu
.log("messages", "Loading complete.");
112 }).catch(fatalError
);
115 yuu
.log
= yf
.argv(function (category
, args
) {
116 /** Log a message to the console.
118 This supports simple filtering by setting e.g.
119 `yuu.log.errors = true` to log anything with the
122 if (!category
|| this.log
[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
);
131 yuu
.log
.errors
= true;
132 yuu
.log
.warnings
= true;
133 yuu
.log
.messages
= true;
135 yuu
.logError = function (e
) {
136 yuu
.log("errors", e
.message
|| "unknown error", e
);
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
)
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
);
153 url
+ ": " + status
+ ": " + this.statusText
));
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")); };
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 () {
169 img
.onerror = function () {
170 var msg
= "Unable to load " + img
.src
;
171 yuu
.log("errors", msg
);
172 reject(new Error(msg
));
178 /** Command parsing and execution
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.
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.
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.
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.
207 function isCommand (f
) {
208 return yf
.isFunction(f
) && f
._isCommandFunction
;
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
);
220 var cmd
= yuu
.cmd
= yf
.argcd(
221 /** Decorate a function for command execution
223 Command functions need some special attributes to work
224 correctly. This decorator makes sure they have them.
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";
237 yuu
.propcmd = function (o
, prop
, desc
, valspec
) {
238 /** Generate a command function that controls a property
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.
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];
250 }, "<" + valspec
+ "?>", desc
);
253 var QUOTED_SPLIT
= /[^"\s]+|"(?:\\"|[^"])+"/g;
254 var COMMAND_SPLIT
= /\s+(&&|\|\||;)\s+/g;
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
; }
264 function parseCommand (cmdstring
, ctx
) {
265 /** Parse a command string into an invocation object.
267 The command string has a form like `+quux 1 2 3` or
268 `foobar "hello world"`.
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.
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}"`).
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 ('++')
285 `cond` - "&&", "||", or ";", indicating what kind of
286 conditional should be applied.
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();
295 if (name
[0] === "+" && name
[1] === "+") {
296 name
= name
.substring(2);
298 } else if (name
[0] === "+") {
299 name
= name
.substring(1);
301 } else if (name
[0] === "-") {
302 name
= name
.substring(1);
305 invs
.push({ name
: name
, args
: args
, toggle
: toggle
,
306 cond
: conds
[i
] || ";"});
311 yuu
.CommandStack
= yT({
312 constructor: function () {
313 /** A stack of command sets for command lookup and execution */
314 this._cmdsets
= yf
.slice(arguments
);
317 push: function (cmdset
) {
318 /** Add a command set to the lookup stack. */
319 this._cmdsets
= this._cmdsets
.concat(cmdset
);
322 remove: function (cmdset
) {
323 /** Remove a command set from the lookup stack. */
324 this._cmdsets
= yf
.without(this._cmdsets
, cmdset
);
327 insertBefore: function (cmdset
, before
) {
328 this._cmdsets
= yf
.insertBefore(
329 this._cmdsets
.slice(), cmdset
, before
);
332 execute: function (cmdstring
, ctx
) {
333 /* Execute a command given a command string.
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.
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.
345 var invs
= parseCommand(cmdstring
, ctx
);
348 yf
.each
.call(this, function (inv
) {
349 if ((inv
.cond
=== "&&" && !cond
) || (inv
.cond
=== "||" && cond
))
351 if (!yf
.eachrUntil(function (cmdset
) {
352 var cmd
= cmdset
[inv
.name
];
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
));
363 return cmdset
.$ && cmdset
.$(inv
);
365 yuu
.log("errors", "Unknown command", inv
.name
);
371 yuu
.extractCommands = function (object
) {
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
379 if (yT
.isDataDescriptor(yT
.getPropertyDescriptor(object
, prop
))
380 && isCommand(object
[prop
]))
381 commands
[prop
] = object
[prop
].bind(object
);
382 }, yf
.allKeys(object
));
386 yuu
.commandStack
= new yuu
.CommandStack(yuu
.defaultCommands
= {
387 /** The default command stack and set. */
388 cmds
: yuu
.cmd(function (term
) {
391 yuu
.commandStack
._cmdsets
.forEach(function (cmdset
) {
392 for (var cmdname
in cmdset
) {
393 if (cmdname
.indexOf(term
) >= 0) {
394 var cmd
= cmdset
[cmdname
];
397 msg
= [cmdname
, cmd
.usage
, "--", cmd
.description
];
399 msg
= [cmdname
, "--", cmd
.description
];
400 cmds
.push(msg
.join(" "));
404 yuu
.log("messages", cmds
.join("\n"));
405 }, "<term?>", "display available commands (matching the term)"),
407 echo
: yuu
.cmd(function () {
408 yuu
.log("messages", arguments
);
409 }, "...", "echo arguments to the console"),
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")
419 yuu
.defaultCommands
.showDevTools
= yuu
.cmd(function () {
421 gui
.Window
.get().showDevTools();
422 }, "show developer tools");
424 yuu
.anchorPoint = function (anchor
, x0
, y0
, x1
, y1
) {
425 /** Calculate the anchor point for a box given extents and anchor mode
427 This function is the inverse of yuu.bottomLeft.
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];
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]];
444 yuu
.bottomLeft = function (anchor
, x
, y
, w
, h
) {
445 /** Calculate the bottom-left for a box given size and anchor mode
447 This function is the inverse of yuu.anchorPoint.
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];
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]];
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
;
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
);
475 function resolvePropertyPath (object
, path
) {
476 /** Look up a full property path
478 If a null is encountered in the path, this function returns
479 null. If undefined is encountered or a property is missing, it
482 var parts
= path
.replace(/\[(\w+)\]/g, '.$1').split('.');
484 i
< parts
.length
&& object
!== undefined && object
!== null;
486 object
= object
[parts
[i
]];
492 /** Somewhat like Python's random.Random.
494 Passed a function that returns a uniform random variable in
495 [0, 1) it can do other useful randomization algorithms.
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.
501 constructor: function (generator
) {
502 this.random
= generator
|| Math
.random
;
503 this._spareGauss
= null;
506 choice: function (seq
) {
507 /** Return a random element from the provided array. */
508 return seq
[this.randrange(0, seq
.length
)];
513 /** Return a uniform random integer in [0, a). */
514 return (this.random() * a
) | 0;
518 /** Return a uniform random integer in [a, b). */
521 return a
+ ((this.random() * (b
- a
)) | 0);
524 function (a
, b
, step
) {
525 /** Return a uniform random number in [a, b).
527 The number is constrained to values of a + i * step
528 where i is a non-negative integer.
530 var i
= Math
.ceil((b
- a
) / step
);
531 return a
+ this.randrange(i
) * step
;
537 /** Return a uniform random variable in [0, a). */
538 return a
* this.random();
541 /** Return a uniform random variable in [a, b). */
542 return a
+ (b
- a
) * this.random();
546 gauss: function (mean
, sigma
) {
547 var u
= this._spareGauss
, v
, s
;
548 this._spareGauss
= null;
552 u
= this.uniform(-1, 1);
553 v
= this.uniform(-1, 1);
555 } while (s
>= 1.0 || s
=== 0.0);
556 var t
= Math
.sqrt(-2.0 * Math
.log(s
) / s
);
557 this._spareGauss
= v
* t
;
560 return mean
+ sigma
* u
;
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
; }
569 randsign: function (v
) {
570 return this.randbool() ? v
: -v
;
573 shuffle: function (seq
) {
574 for (var i
= seq
.length
- 1; i
> 0; --i
) {
575 var index
= this.randrange(i
+ 1);
583 discard: function (z
) {
590 yuu
.createLCG
= yf
.argcd(
591 /** Linear congruential random generator
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.
597 function () { return yuu
.createLCG(Math
.random() * 2147483647); },
599 var state
= seed
| 0;
600 return function generator () {
601 state
= (state
* 1664525 + 1013904223) % 4294967296;
602 return state
/ 4294967296;
607 yuu
.random
= new yuu
.Random();
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;
614 yuu
.Caching = function (Type
, cacheKey
) {
616 var k
= ctor
._cacheKey(arguments
);
617 var o
= k
&& ctor
._cache
[k
];
619 o
= ctor
._cache
[k
] = yf
.construct(ctor
.Uncached
, arguments
);
622 ctor
._cacheKey
= cacheKey
|| defaultKey
;
624 ctor
.Uncached
= Type
;
628 yuu
.transpose2d = function (a
) {
629 for (var x
= 0; x
< a
.length
; ++x
) {
630 for (var y
= 0; y
< x
; ++y
) {
638 yuu
.normalizeRadians = function (theta
) {
640 return (theta
+ 3 * PI
) % (2 * PI
) - PI
;
643 yuu
.radians = function (v
) {
644 return v
* (Math
.PI
/ 180.0);
647 yuu
.degrees = function (v
) {
648 return v
* (180.0 / Math
.PI
);
651 var SHORT
= /(\/|^)@(.+)$/;
652 yuu
.resourcePath = function (path
, category
, ext
) {
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)
663 yuu
.ready = function (resources
, result
) {
664 return Promise
.all(yf
.filter(null, yf
.pluck("ready", resources
)))
668 yuu
.openURL = function (url
) {
669 if (gui
&& gui
.Shell
)
670 gui
.Shell
.openExternal(url
);
675 function crossPlatformFilename (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, "_");
683 yuu
.downloadURL = function (url
, suggestedName
) {
684 var regex
= /^data:[^;+]+;base64,(.*)$/;
685 var matches
= url
.match(regex
);
686 suggestedName
= crossPlatformFilename(suggestedName
);
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
);
697 var link
= document
.createElement('a');
698 link
.style
.display
= "none";
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
);
705 document
.body
.removeChild(link
);
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
) {
722 get: function () { return this.x1
- this.x0
; },
723 set: function (w
) { this.x1
= this.x0
+ w
; }
727 get: function () { return this.y1
- this.y0
; },
728 set: function (h
) { this.y1
= this.y0
+ h
; }
731 size
: { swizzle
: "wh" },
734 function (p
) { return this.contains(p
.x
, p
.y
); },
736 return x
>= this.x0
&& x
< this.x1
737 && y
>= this.y0
&& y
< this.y1
;
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;
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;
762 alignedInside: function (outer
, alignment
) {
770 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
774 x0
= outer
.x0
- this.w
;
779 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
782 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
783 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
786 x0
= outer
.x1
- this.w
;
787 y0
= outer
.x0
+ (outer
.h
- this.h
) / 2;
791 y0
= outer
.y1
- this.h
;
794 x0
= outer
.x0
+ (outer
.w
- this.w
) / 2;
795 y0
= outer
.y1
- this.h
;
798 x0
= outer
.x1
- this.w
;
799 y0
= outer
.y1
- this.h
;
802 return new this.constructor(x0
, y0
, x0
+ this.w
, y0
+ this.h
);
806 function splitPathExtension (path
) {
807 var dot
= path
.lastIndexOf(".");
808 if (dot
<= 0) return [path
, ""];
810 var dir
= path
.lastIndexOf("/");
811 if (dot
< dir
) return [path
, ""];
813 return [path
.substring(0, dot
), path
.substring(dot
)];
815 yuu
.splitPathExtension
= splitPathExtension
;
818 yT
.defineProperty(String
.prototype, "lerp", function (b
, p
) {
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
);
829 yT
.defineProperties(Number
.prototype, {
830 lerp: function (b
, p
) { return this + (b
- this) * p
; }
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
)
840 else if (i
>= b
.length
)
843 c
[i
] = this[i
].lerp(b
[i
], p
);
849 /** Typed array extensions
851 https://www.khronos.org/registry/typedarray/specs/1.0/
852 BUT: Read on for fun times in browser land~
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.
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.
865 [ Float32Array
, Float64Array
, Int8Array
, Uint8Array
,
866 Int16Array
, Uint16Array
, Int32Array
, Uint32Array
867 ].forEach(function (A
) {
868 yT
.defineProperties(A
.prototype, {
870 /** Like Array's slice, but for typed arrays */
871 function () { return new this.constructor(this); },
873 return new this.constructor(this.subarray(begin
));
875 function (begin
, end
) {
876 return new this.constructor(this.subarray(begin
, end
));
880 fill
: Array
.prototype.fill
,
881 reverse
: Array
.prototype.reverse
,
883 lerp: function (b
, p
) {
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
;
896 }).call(typeof exports
=== "undefined" ? this : exports
,
897 typeof exports
=== "undefined" ? (this.yuu
= {}) : exports
);