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 var yT
= this.yT
|| require("./yT");
11 var yf
= this.yf
|| require("./yf");
15 var dpr
= yuu
.DPR
= this.devicePixelRatio
|| 1;
17 yT
.defineProperty(Int8Array
.prototype, "GL_TYPE", 0x1400);
18 yT
.defineProperty(Uint8Array
.prototype, "GL_TYPE", 0x1401);
19 yT
.defineProperty(Int16Array
.prototype, "GL_TYPE", 0x1402);
20 yT
.defineProperty(Uint16Array
.prototype, "GL_TYPE", 0x1403);
21 yT
.defineProperty(Int32Array
.prototype, "GL_TYPE", 0x1404);
22 yT
.defineProperty(Uint32Array
.prototype, "GL_TYPE", 0x1405);
23 yT
.defineProperty(Float32Array
.prototype, "GL_TYPE", 0x1406);
24 /** Patch the WebGL type onto arrays for data-driven access later
26 Values from https://www.khronos.org/registry/webgl/specs/1.0/.
28 See also notes in pre on Safari's typed array problems.
31 yuu
.uniform = function (location
, value
) {
32 /** Set a uniform in the active program
34 The type of the uniform is automatically determined from
37 * Typed integer arrays of length 1-4 call uniform[1-4]iv
38 * Other sequences of length 1-4 call uniform[1-4]fv
39 * Sequences of length 9 or 16 call uniformMatrix[3-4]fv
40 * Non-sequences call uniform1fv (even if the parameter
42 * Sequences of other lengths throw a TypeError
44 It is not possible to call uniformMatrix2fv via this
47 switch (value
.constructor) {
54 switch (value
.length
) {
55 case 1: return gl
.uniform1iv(location
, value
);
56 case 2: return gl
.uniform2iv(location
, value
);
57 case 3: return gl
.uniform3iv(location
, value
);
58 case 4: return gl
.uniform4iv(location
, value
);
59 default: throw new TypeError("unexpected array length");
63 switch (value
.length
) {
64 case 1: return gl
.uniform1fv(location
, value
);
65 case 2: return gl
.uniform2fv(location
, value
);
66 case 3: return gl
.uniform3fv(location
, value
);
67 case 4: return gl
.uniform4fv(location
, value
);
68 case 9: return gl
.uniformMatrix3fv(location
, false, value
);
69 case 16: return gl
.uniformMatrix4fv(location
, false, value
);
70 case undefined: return gl
.uniform1f(location
, value
);
71 default: throw new TypeError("unexpected array length");
76 function isShaderSource (src
) {
77 return src
.indexOf("\n") >= 0 || yf
.last(src
.trim()) === ";";
80 var FRAGMENT_SHADER
= 0x8B30;
81 var VERTEX_SHADER
= 0x8B31;
83 EXTS
[FRAGMENT_SHADER
] = "frag";
84 EXTS
[VERTEX_SHADER
] = "vert";
86 function compile (type
, srcs
) {
87 function getSource (src
) {
88 return isShaderSource(src
)
89 ? Promise
.resolve(src
)
90 : yuu
.GET(yuu
.resourcePath(src
, "shaders", EXTS
[type
]));
92 return Promise
.all(yf
.map(getSource
, srcs
))
93 .then(function (srcs
) {
94 var src
= srcs
.join("\n");
95 var shader
= gl
.createShader(type
);
96 gl
.shaderSource(shader
, src
);
97 gl
.compileShader(shader
);
98 if (!gl
.getShaderParameter(shader
, gl
.COMPILE_STATUS
)) {
99 var log
= gl
.getShaderInfoLog(shader
);
101 "Shader compile error:\n\n" + src
+ "\n\n" + log
);
107 yuu
.ShaderProgram
= yT({
108 constructor: function (vs
, fs
) {
109 /** A linked program of vertex and fragment shaders
111 vs and fs are arrays of vertex and fragment shader source
114 fs
= fs
|| ["yuu/@default"];
115 vs
= vs
|| ["yuu/@default"];
116 var id
= this.id
= gl
.createProgram();
117 var attribs
= this.attribs
= {};
118 var uniforms
= this.uniforms
= {};
119 this.ready
= Promise
.all([compile(VERTEX_SHADER
, vs
),
120 compile(FRAGMENT_SHADER
, fs
)])
121 .then(function (shaders
) {
122 yf
.each(gl
.attachShader
.bind(gl
, id
), shaders
);
124 if (!gl
.getProgramParameter(id
, gl
.LINK_STATUS
))
125 throw new Error("Shader link error: "
126 + gl
.getProgramInfoLog(id
));
128 }).catch(function (exc
) {
130 this.id
= yuu
.ShaderProgram
.DEFAULT
.id
;
131 this.attribs
= yuu
.ShaderProgram
.DEFAULT
.attribs
;
132 this.uniforms
= yuu
.ShaderProgram
.DEFAULT
.uniforms
;
134 }.bind(this)).then(function (id
) {
136 yf
.irange(function (i
) {
137 var name
= gl
.getActiveAttrib(id
, i
).name
;
138 attribs
[name
] = gl
.getAttribLocation(id
, name
);
139 }, gl
.getProgramParameter(id
, gl
.ACTIVE_ATTRIBUTES
));
140 yf
.irange(function (i
) {
141 var name
= gl
.getActiveUniform(id
, i
).name
;
142 uniforms
[name
] = gl
.getUniformLocation(id
, name
);
143 }, gl
.getProgramParameter(id
, gl
.ACTIVE_UNIFORMS
));
148 setUniforms: function () {
149 /** Set the values of program uniforms
151 The arguments are any number of objects mapping
152 uniform names to values (floats, vec3s, etc.).
154 for (var i
= 0; i
< arguments
.length
; ++i
)
155 for (var name
in arguments
[i
])
156 yuu
.uniform(this.uniforms
[name
], arguments
[i
][name
]);
159 setAttribPointers: function (buffer
) {
160 /** Bind the contents of a vertex buffer to attributes
162 `buffer` is (or is like) a yuu.VertexBuffer instance.
164 for (var name
in this.attribs
)
165 gl
.vertexAttribPointer(
167 buffer
.spec
.attribs
[name
].elements
,
168 buffer
.spec
.attribs
[name
].View
.prototype.GL_TYPE
,
169 false, 0, buffer
.arrays
[name
].byteOffset
);
173 // This function is easier to read than a giant lookup table
174 // ({ textureWrapS: "TEXTURE_WRAP_S", ... x100 }) but slower.
175 function glEnum (gl
, name
) {
176 return gl
[name
.replace(/([A-Z]+)/g, "_$1").toUpperCase()];
179 function glScopedEnum (scope
, gl
, name
) {
180 var value
= glEnum(gl
, scope
+ "_" + name
);
181 if (value
=== undefined)
182 value
= glEnum(gl
, name
);
186 var glTextureEnum
= glScopedEnum
.bind(null, "texture");
188 yuu
.Texture
= yuu
.Caching(yT({
189 constructor: function (path
, overrideOptions
) {
192 The texture is set to a 1x1 white texture until it is
193 loaded (or if loading fails).
196 yf
.ipairs(function (k
, v
) {
197 options
[glTextureEnum(gl
, k
)] = glEnum(gl
, v
);
198 }, TEXTURE_DEFAULTS
);
199 yf
.ipairs(function (k
, v
) {
200 options
[glTextureEnum(gl
, k
)] = glEnum(gl
, v
);
201 }, overrideOptions
|| {});
204 var data
= new Uint8Array([255, 255, 255, 255]);
205 this.id
= gl
.createTexture();
206 this.width
= this.height
= 1;
207 this.src
= "default / fallback 1x1 white texture";
208 gl
.bindTexture(gl
.TEXTURE_2D
, this.id
);
210 gl
.TEXTURE_2D
, 0, gl
.RGBA
, 1, 1, 0,
211 gl
.RGBA
, gl
.UNSIGNED_BYTE
, data
);
213 gl
.TEXTURE_2D
, gl
.TEXTURE_MAG_FILTER
, gl
.NEAREST
);
215 gl
.TEXTURE_2D
, gl
.TEXTURE_MIN_FILTER
, gl
.NEAREST
);
216 gl
.bindTexture(gl
.TEXTURE_2D
, null);
217 this.ready
= Promise
.resolve(this);
221 path
= yuu
.resourcePath(path
, "images", "png");
222 this.id
= yuu
.Texture
.DEFAULT
.id
;
224 this.width
= yuu
.Texture
.DEFAULT
.width
;
225 this.height
= yuu
.Texture
.DEFAULT
.height
;
227 this.ready
= yuu
.Image(path
).then(function (img
) {
228 var id
= gl
.createTexture();
229 gl
.bindTexture(gl
.TEXTURE_2D
, id
);
230 gl
.pixelStorei(gl
.UNPACK_FLIP_Y_WEBGL
, true);
231 gl
.pixelStorei(gl
.UNPACK_PREMULTIPLY_ALPHA_WEBGL
, true);
232 for (var opt
in options
)
233 gl
.texParameteri(gl
.TEXTURE_2D
, opt
, options
[opt
]);
235 gl
.TEXTURE_2D
, 0, gl
.RGBA
,
236 gl
.RGBA
, gl
.UNSIGNED_BYTE
, img
);
237 gl
.bindTexture(gl
.TEXTURE_2D
, null);
239 this.width
= img
.width
;
240 this.height
= img
.height
;
243 }.bind(this)).catch(function (e
) {
244 this.src
= "Error loading " + path
+ ": " + e
;
245 yuu
.log("errors", this.src
);
246 gl
.bindTexture(gl
.TEXTURE_2D
, null);
252 var TEXTURE_DEFAULTS
= yuu
.Texture
.DEFAULTS
= {
255 wrapS
: "clampToEdge",
259 yuu
.Material
= yuu
.Caching(yT({
260 constructor: function (texture
, program
, uniforms
) {
261 /** A material is a combination of a texture and shader program */
262 if (yf
.isString(texture
))
263 texture
= new yuu
.Texture(texture
);
264 this.texture
= texture
|| yuu
.Texture
.DEFAULT
;
265 this.program
= program
|| yuu
.ShaderProgram
.DEFAULT
;
266 this.ready
= yuu
.ready([this.texture
, this.program
], this);
267 this.uniforms
= uniforms
|| {};
270 enable: function (uniforms
) {
271 /** Enable this material and its default parameters */
272 gl
.bindTexture(gl
.TEXTURE_2D
, this.texture
.id
);
273 gl
.useProgram(this.program
.id
);
274 for (var attrib
in this.program
.attribs
)
275 gl
.enableVertexAttribArray(this.program
.attribs
[attrib
]);
276 this.program
.setUniforms(this.uniforms
, uniforms
);
279 disable: function () {
280 /** Disable this material */
281 gl
.bindTexture(gl
.TEXTURE_2D
, null);
283 for (var attrib
in this.program
.attribs
)
284 gl
.disableVertexAttribArray(this.program
.attribs
[attrib
]);
288 yuu
.VertexAttribSpec = function (spec
) {
289 /** Ordering and types for vertex buffer layout
291 Interleaved vertices (e.g. VTCVTCVTC) are not currently
292 supported, as ArrayBufferViews are not able to manage a buffer
293 with this kind of layout.
297 spec
.forEach(function (a
) {
299 var elements
= a
.elements
;
300 var View
= a
.View
|| Float32Array
;
301 this.attribs
[name
] = { elements
: elements
,
302 byteOffset
: byteOffset
,
304 byteOffset
+= elements
* View
.BYTES_PER_ELEMENT
;
306 this.bytesPerVertex
= byteOffset
;
309 yuu
.V3T2C4_F
= new yuu
.VertexAttribSpec([
310 /** vec3 position; vec2 texCoord; vec4 color; */
311 { name
: "position", elements
: 3 },
312 { name
: "texCoord", elements
: 2 },
313 { name
: "color", elements
: 4 }
316 yuu
.IndexBuffer
= yT({
317 constructor: function (maxIndex
, length
) {
319 this._maxIndex
= maxIndex
;
322 this.length
= length
;
323 this._glBuffer
= gl
.createBuffer();
327 bindBuffer: function () {
328 gl
.bindBuffer(gl
.ELEMENT_ARRAY_BUFFER
, this._glBuffer
);
332 gl
.ELEMENT_ARRAY_BUFFER
, this.buffer
, gl
.DYNAMIC_DRAW
);
336 GL_TYPE
: { alias
: "buffer.GL_TYPE" },
339 get: function () { return this._maxIndex
; },
340 set: function (maxIndex
) {
341 var Array
= yuu
.IndexBuffer
.Array(maxIndex
);
342 if (maxIndex
> this._maxIndex
343 && Array
!== this.buffer
.constructor) {
344 var buffer
= new Array(this._capacity
);
346 buffer
.set(this.buffer
);
347 this.buffer
= buffer
;
350 this._maxIndex
= maxIndex
;
355 get: function () { return this._length
; },
356 set: function (count
) {
357 if (count
> this._capacity
) {
358 var Array
= yuu
.IndexBuffer
.Array(this._maxIndex
);
359 var buffer
= new Array(count
);
361 buffer
.set(this.buffer
);
362 this.buffer
= buffer
;
363 this._capacity
= count
;
366 this._length
= count
;
371 yuu
.IndexBuffer
.Array = function (maxIndex
) {
372 if (maxIndex
< 0 || maxIndex
>= (256 * 256 * 256 * 256))
373 throw new Error("invalid maxIndex index: " + maxIndex
);
374 else if (maxIndex
< (1 << 8))
376 else if (maxIndex
< (1 << 16))
382 yuu
.VertexBuffer
= yT({
383 constructor: function (spec
, vertexCount
) {
384 /** A buffer with a specified vertex format and vertex count
386 The individual vertex attribute array views from the
387 attribute specification are available via the .arrays
388 property, e.g. v.arrays.position. The underlying
389 buffer is available as v.buffer.
391 The vertex count may be changed after creation and the
392 buffer size and views will be adjusted. If you've
393 grown the buffer, you will need to refill all its
394 data. Shrinking it will truncate it.
397 this._vertexCapacity
= -1;
400 this.vertexCount
= vertexCount
;
401 this._glBuffer
= gl
.createBuffer();
405 bindBuffer: function () {
406 gl
.bindBuffer(gl
.ARRAY_BUFFER
, this._glBuffer
);
409 gl
.bufferData(gl
.ARRAY_BUFFER
, this.buffer
, gl
.DYNAMIC_DRAW
);
413 subdata: function (begin
, length
) {
414 return new yuu
.VertexBuffer
.SubData(this, begin
, begin
+ length
);
418 get: function () { return this._vertexCount
; },
419 set: function (count
) {
420 if (count
> this._vertexCapacity
) {
421 var buffer
= new ArrayBuffer(
422 this.spec
.bytesPerVertex
* count
);
424 yf
.ipairs
.call(this, function (name
, attrib
) {
425 arrays
[name
] = new attrib
.View(
426 buffer
, attrib
.byteOffset
* count
,
427 attrib
.elements
* count
);
428 if (this.arrays
[name
])
429 arrays
[name
].set(this.arrays
[name
]);
430 }, this.spec
.attribs
);
431 this.buffer
= buffer
;
432 this.arrays
= arrays
;
433 this._vertexCapacity
= count
;
436 this._vertexCount
= count
;
441 yuu
.VertexBuffer
.SubData
= yT({
442 constructor: function (parent
, begin
, end
) {
443 var arrays
= this.arrays
= {};
444 this._parent
= parent
;
445 this.spec
= parent
.spec
;
446 this.buffer
= parent
.buffer
;
447 yT
.defineProperty(this, "vertexCount", end
- begin
);
448 for (var attrib
in parent
.arrays
) {
449 var s
= parent
.spec
.attribs
[attrib
].elements
;
450 arrays
[attrib
] = parent
.arrays
[attrib
].subarray(
455 dirty
: { alias
: "_parent.dirty" }
458 var rgbToHsl
= yuu
.rgbToHsl
= yf
.argcd(
459 /** Convert RBG [0, 1] to HSL [0, 1]. */
460 function (rgb
) { return rgbToHsl
.apply(null, rgb
); },
461 function (r
, g
, b
, a
) {
462 var hsl
= rgbToHsl(r
, g
, b
);
467 var max
= Math
.max(r
, g
, b
);
468 var min
= Math
.min(r
, g
, b
);
469 var h
, s
, l
= (max
+ min
) / 2;
475 s
= l
> 0.5 ? d
/ (2 - max
- min
) : d
/ (max
+ min
);
478 h
= (g
- b
) / d
+ (g
< b
? 6 : 0);
494 var hslToRgb
= yuu
.hslToRgb
= yf
.argcd(
495 /** Convert HSL [0, 1] to RGB [0, 1]. */
496 function (hsl
) { return hslToRgb
.apply(null, hsl
); },
497 function (h
, s
, l
, a
) {
498 var rgb
= hslToRgb(h
, s
, l
);
505 function hToC (p
, q
, t
) {
511 return p
+ (q
- p
) * 6 * t
;
515 return p
+ (q
- p
) * (2/3 - t
) * 6;
523 var q
= l
< 0.5 ? l
* (1 + s
) : l
+ s
- l
* s
;
525 r
= hToC(p
, q
, h
+ 1 / 3);
527 b
= hToC(p
, q
, h
- 1 / 3);
534 var deviceFromCanvas
= yuu
.deviceFromCanvas
= yf
.argcd(
535 /** Convert a point from client to normalized device space
537 Normalized device space ranges from [-1, -1] at the
538 bottom-left of the viewport to [1, 1] at the top-right.
539 (This is the definition of the space, _not_ bounds on the
540 return value, as events can happen outside the viewport or
541 even outside the canvas.)
544 return deviceFromCanvas(p
.x
|| p
.pageX
|| p
[0] || 0,
545 p
.y
|| p
.pageY
|| p
[1] || 0);
548 x
-= canvas
.offsetLeft
;
549 y
-= canvas
.offsetTop
;
550 x
/= canvas
.clientWidth
;
551 y
/= canvas
.clientHeight
;
552 // xy is now in [0, 1] page space.
556 // xy now in canvas buffer space.
558 var vp
= gl
.getParameter(gl
.VIEWPORT
);
559 var hvpw
= vp
[2] / 2;
560 var hvph
= vp
[3] / 2;
561 x
= (x
- vp
[0] - hvpw
) / hvpw
;
562 y
= (y
- vp
[1] - hvph
) / -hvph
;
563 // xy now in normalized device space.
568 inside
: Math
.abs(x
) <= 1 && Math
.abs(y
) <= 1
573 yuu
.viewport
= new yuu
.AABB();
575 function onresize () {
576 var resize
= canvas
.getAttribute("data-yuu-resize") !== null;
577 var width
= +canvas
.getAttribute("data-yuu-width");
578 var height
= +canvas
.getAttribute("data-yuu-height");
581 canvas
.width
= canvas
.clientWidth
* dpr
;
582 canvas
.height
= canvas
.clientHeight
* dpr
;
585 var vw
= canvas
.width
;
586 var vh
= canvas
.height
;
587 if (width
&& height
) {
588 var aspectRatio
= width
/ height
;
589 if (vw
/ vh
> aspectRatio
)
590 vw
= vh
* aspectRatio
;
592 vh
= vw
/ aspectRatio
;
594 var vx
= (canvas
.width
- vw
) / 2;
595 var vy
= (canvas
.height
- vh
) / 2;
596 gl
.viewport(vx
, vy
, vw
, vh
);
597 yuu
.viewport
= new yuu
.AABB(vx
, vy
, vx
+ vw
/ dpr
, vy
+ vh
/ dpr
);
600 yuu
.afterAnimationFrame = function (f
) {
601 /* DOM class modifications intended to trigger transitions
602 must be delayed for at least one frame after the element is
603 created, i.e. after it has gone through at least one full
606 window
.requestAnimationFrame(function () {
611 yuu
.registerInitHook(function (options
) {
612 var bgColor
= options
.backgroundColor
|| [0.0, 0.0, 0.0, 0.0];
614 canvas
= this.canvas
= document
.getElementById("yuu-canvas");
616 alpha
: options
.hasOwnProperty("alpha")
617 ? options
.alpha
: bgColor
[3] !== 1.0,
618 antialias
: options
.hasOwnProperty("antialias")
619 ? options
.antialias
: true
621 if (!window
.HTMLCanvasElement
)
622 throw new Error("<canvas> isn't supported.");
623 gl
= this.gl
= canvas
.getContext("webgl", glOptions
)
624 || canvas
.getContext("experimental-webgl", glOptions
);
626 throw new Error("WebGL isn't supported.");
630 window
.addEventListener('resize', onresize
);
633 this.ShaderProgram
.DEFAULT
= new this.ShaderProgram();
634 this.Texture
.DEFAULT
= new this.Texture();
635 this.Material
.DEFAULT
= new this.Material();
637 gl
.clearColor
.apply(gl
, bgColor
);
638 gl
.disable(gl
.DEPTH_TEST
);
639 gl
.clear(gl
.COLOR_BUFFER_BIT
);
641 gl
.blendEquationSeparate(gl
.FUNC_ADD
, gl
.FUNC_ADD
);
642 gl
.blendFuncSeparate(gl
.ONE
, gl
.ONE_MINUS_SRC_ALPHA
, gl
.ONE
, gl
.ONE
);
645 var gui
= yuu
.require("nw.gui");
646 yT
.defineProperty(yuu
, "fullscreen", {
649 ? gui
.Window
.get().isFullscreen
650 : !!(document
.fullscreenElement
651 || document
.mozFullScreenElement
);
655 gui
.Window
.get().isFullscreen
= !!v
;
657 document
.body
.requestFullscreen();
659 document
.exitFullscreen();
663 }).call(typeof exports
=== "undefined" ? this : exports
,
664 typeof exports
=== "undefined"
665 ? this.yuu
: (module
.exports
= require('./core')));