Created
April 2, 2012 13:32
-
-
Save noboko/2283442 to your computer and use it in GitHub Desktop.
Learning WebGL for Plask Lesson 8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifdef GL_ES | |
precision highp float; | |
#endif | |
varying vec2 vTextureCoord; | |
varying vec3 vLightWeighting; | |
uniform float uAlpha; | |
uniform sampler2D uSampler; | |
void main(void) { | |
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); | |
gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a * uAlpha); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Learning WebGL for Plask Lesson8 | |
//Learing WebGL : http://learningwebgl.com/blog/?page_id=1217 | |
//Github : https://github.com/gpjt/webgl-lessons (this script use 'glass.gif'.) | |
//元ネタは十字カーソルとページアップダウンにボタン割り当てですが、手持ちの環境にページアップ/ダウンボタンがないので | |
//rotateをwsad, zoomをqe,lightingをf,付け加えてresetにrを割り当ててます。 | |
//plaskで入力エリアを作る方法が無さげなのでtgyhujに平行光の色だけ割り当ててます。 | |
//blenging on/off :z, -alpha : x, +alpha : c | |
// | |
//Plask : http://www.plask.org/ | |
// | |
//元ネタの英語版と日本語版解説では微妙にコードが違っていて、日本語版のほうが若干古いです。 | |
//で、何が違ってるかというと、normal matrixをMat3で処理してます。 | |
//英語版に準拠して処理をしたかったのですが、plask.jsにはMat4の処理しか書いてないで、 | |
//俺俺でMat3とMat4.toInverseMat3()あたりを追記したものを使ってやりました。 | |
//元のplask.jsと区別つけるために同一フォルダから読み込むようにしてます。 | |
//尚、バイナリ版とgithubの最新版で微妙にplask.jsの変更があるみたいなので、 | |
//plask_.js バイナリ版用、plask.js github用としてます。 | |
// | |
var path = require('path'); | |
var plask = require('./plask_'); | |
function clone(obj){ | |
var F = function(){}; | |
F.prototype = obj; | |
return new F; | |
} | |
var init ={ | |
xRot : 0, | |
xSpeed : 0, | |
yRot : 0, | |
ySpeed : 0, | |
z : -5.0, | |
lighiting : 1, | |
d_lite : {x:-0.25, y:-0.25,z:-1.0, | |
r:0.8, g:0.8, b:0.8 | |
}, | |
a_lite : {r:0.2, g:0.2, b:0.2}, | |
blending : 1, | |
alfa : 0.5, | |
}; | |
plask.simpleWindow({ | |
settings: { | |
width: 1280, | |
height: 720, | |
type: '3d', // Create an OpenGL window. | |
vsync: true, // Prevent tearing. | |
multisample: true // Anti-alias. | |
}, | |
init: function() { | |
var gl = this.gl; | |
this.framerate(60); | |
this.param = clone(init); | |
this.on('keyDown', function(e) { | |
// The |str| property on the key event is a string of the unicode | |
// translation of the key. Take bigger steps while holding shift. | |
switch (e.str) { | |
case 'w': this.param.ySpeed -= 0.01; break; | |
case 's': this.param.ySpeed += 0.01; break; | |
case 'a': this.param.xSpeed -= 0.01; break; | |
case 'd': this.param.xSpeed += 0.01; break; | |
case 'q': this.param.z += 0.05; break; | |
case 'e': this.param.z -= 0.05; break; | |
case 'f': this.param.lighiting += 1; | |
this.param.lighiting = this.param.lighiting % 2; | |
// console.log(this.param.lighiting); | |
break; | |
case 'r': this.param = clone(init); break;//reset | |
case 't': this.param.d_lite.r += 0.05; break; | |
case 'g': this.param.d_lite.r -= 0.05; break; | |
case 'y': this.param.d_lite.g += 0.05; break; | |
case 'h': this.param.d_lite.g -= 0.05; break; | |
case 'u': this.param.d_lite.b += 0.05; break; | |
case 'z': this.param.blending += 1; | |
this.param.blending = this.param.blending % 2; | |
break; | |
case 'x': this.param.alfa -= 0.05; break; | |
case 'c': this.param.alfa += 0.05; break; | |
} | |
this.redraw(); | |
}); | |
// Read/compile/link the shaders into a MagicProgram. The |mprogram| | |
// object created has methods to set the shader uniform variables. | |
this.mprogram = new plask.gl.MagicProgram.createFromBasename( | |
gl, __dirname, 'app'); | |
// Create the three 3d (xyz) vertices for the triangle. This creates the | |
// geometry and loads it into an Vertex Buffer Object (VBO). | |
function makeBuffer(ar,itemSize) { | |
var buffer = gl.createBuffer(); | |
var data = new Float32Array(ar); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); | |
return {buffer: buffer, num: data.length / itemSize, data: data, itemSize: itemSize}; | |
} | |
function makeEABuffer(ar,itemSize) { | |
var buffer = gl.createBuffer(); | |
var data = new Uint16Array(ar); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW); | |
return {buffer: buffer, num: data.length / itemSize, data: data, itemSize: itemSize}; | |
} | |
this.cubeVertexPositionBuffer = makeBuffer([ | |
-1.0, -1.0, 1.0, | |
1.0, -1.0, 1.0, | |
1.0, 1.0, 1.0, | |
-1.0, 1.0, 1.0, | |
// Back face | |
-1.0, -1.0, -1.0, | |
-1.0, 1.0, -1.0, | |
1.0, 1.0, -1.0, | |
1.0, -1.0, -1.0, | |
// Top face | |
-1.0, 1.0, -1.0, | |
-1.0, 1.0, 1.0, | |
1.0, 1.0, 1.0, | |
1.0, 1.0, -1.0, | |
// Bottom face | |
-1.0, -1.0, -1.0, | |
1.0, -1.0, -1.0, | |
1.0, -1.0, 1.0, | |
-1.0, -1.0, 1.0, | |
// Right face | |
1.0, -1.0, -1.0, | |
1.0, 1.0, -1.0, | |
1.0, 1.0, 1.0, | |
1.0, -1.0, 1.0, | |
// Left face | |
-1.0, -1.0, -1.0, | |
-1.0, -1.0, 1.0, | |
-1.0, 1.0, 1.0, | |
-1.0, 1.0, -1.0, | |
],3); | |
this.cubeVertexNormalBuffer= makeBuffer([ | |
// Front face | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
0.0, 0.0, 1.0, | |
// Back face | |
0.0, 0.0, -1.0, | |
0.0, 0.0, -1.0, | |
0.0, 0.0, -1.0, | |
0.0, 0.0, -1.0, | |
// Top face | |
0.0, 1.0, 0.0, | |
0.0, 1.0, 0.0, | |
0.0, 1.0, 0.0, | |
0.0, 1.0, 0.0, | |
// Bottom face | |
0.0, -1.0, 0.0, | |
0.0, -1.0, 0.0, | |
0.0, -1.0, 0.0, | |
0.0, -1.0, 0.0, | |
// Right face | |
1.0, 0.0, 0.0, | |
1.0, 0.0, 0.0, | |
1.0, 0.0, 0.0, | |
1.0, 0.0, 0.0, | |
// Left face | |
-1.0, 0.0, 0.0, | |
-1.0, 0.0, 0.0, | |
-1.0, 0.0, 0.0, | |
-1.0, 0.0, 0.0, | |
],3); | |
this.cubeVertexIndexBuffer = makeEABuffer([ | |
0, 1, 2, 0, 2, 3, // Front face | |
4, 5, 6, 4, 6, 7, // Back face | |
8, 9, 10, 8, 10, 11, // Top face | |
12, 13, 14, 12, 14, 15, // Bottom face | |
16, 17, 18, 16, 18, 19, // Right face | |
20, 21, 22, 20, 22, 23 // Left face | |
],1); | |
this.cubeVertexTextureCoordBuffer = makeBuffer([ | |
// Front face | |
0.0, 0.0, | |
1.0, 0.0, | |
1.0, 1.0, | |
0.0, 1.0, | |
// Back face | |
1.0, 0.0, | |
1.0, 1.0, | |
0.0, 1.0, | |
0.0, 0.0, | |
// Top face | |
0.0, 1.0, | |
0.0, 0.0, | |
1.0, 0.0, | |
1.0, 1.0, | |
// Bottom face | |
1.0, 1.0, | |
0.0, 1.0, | |
0.0, 0.0, | |
1.0, 0.0, | |
// Right face | |
1.0, 0.0, | |
1.0, 1.0, | |
0.0, 1.0, | |
0.0, 0.0, | |
// Left face | |
0.0, 0.0, | |
1.0, 0.0, | |
1.0, 1.0, | |
0.0, 1.0, | |
],2); | |
// console.log(this.cubeVertexNormalBuffer); | |
// console.log(this.cubeVertexPositionBuffer); | |
function careateTextureWithFile(fileName) { | |
var image = new plask.SkCanvas.createFromImage(path.join(__dirname, fileName)); | |
var texture = gl.createTexture(); | |
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.texImage2DSkCanvas(gl.TEXTURE_2D, 0, image); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); | |
gl.generateMipmap(gl.TEXTURE_2D); | |
//textureの切り替えをしないのでnullをbindせずにそのままdrawへもってく。 | |
//gl.bindTexture(gl.TEXTURE_2D, null); | |
//return{texture : texture, image : image}; | |
} | |
//this.texture = | |
careateTextureWithFile('glass.gif'); | |
/* | |
function makeTexture(fileName){ | |
var tex_image = new plask.SkCanvas.createFromImage(path.join(__dirname, fileName)); | |
var texture = gl.createTexture(); | |
gl.activeTexture(gl.TEXTURE0); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | |
gl.texImage2DSkCanvas(gl.TEXTURE_2D, 0, tex_image); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
}; | |
makeTexture('crate.gif'); | |
*/ | |
gl.viewport(0, 0, this.width, this.height); | |
gl.clearColor(0.0, 0.0, 0.0, 1.0); | |
gl.clearDepth(1.0) | |
gl.enable(gl.DEPTH_TEST); | |
//gl.enable(gl.TEXTURING); | |
//gl.enable(gl.TEXTURE_2D); | |
gl.depthFunc(gl.LEQUAL); | |
}, | |
draw: function() { | |
var gl = this.gl; | |
var cubeVertexPositionBuffer = this.cubeVertexPositionBuffer; | |
var cubeVertexTextureCoordBuffer = this.cubeVertexTextureCoordBuffer; | |
var cubeVertexIndexBuffer = this.cubeVertexIndexBuffer; | |
var cubeVertexNormalBuffer = this.cubeVertexNormalBuffer; | |
var mprogram = this.mprogram; | |
var param = this.param; | |
//param.d_lite.x = Math.random()-0.5; | |
//param.d_lite.y = Math.random()-0.5; | |
//param.d_lite.z = Math.random()*5-2.5; | |
// Clear the background to gray. The depth buffer isn't really needed for | |
// a single triangle, but generally the depth buffer should also be cleared. | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
// Build the "model-view" matrix, which will rotate the triangle based on | |
// the mouse controls. | |
var mv = new plask.Mat4(); | |
mv.translate(0.0, 0.0, param.z); | |
param.xRot += param.xSpeed * this.frametime ; | |
param.yRot += param.ySpeed * this.frametime ; | |
//console.log(param.xRot); | |
mv.rotate(param.xRot* plask.kPI /180 + this.frametime*0.5, 0, 1, 0); | |
mv.rotate(param.yRot* plask.kPI /180 + this.frametime*0.6, 1, 0, 0); | |
// Build the "perspective" matrix, which will apply the aspect ratio, | |
// perspective divide, and clipping planes. | |
var persp = new plask.Mat4(); | |
persp.perspective(45, this.width / this.height, 0.1, 100.0); | |
var norm = mv.toInverseMat3(); | |
norm.transpose(); | |
//console.log(norm); | |
var a_color = new plask.Vec3(param.a_lite.r, param.a_lite.g, param.a_lite.b); | |
var l_dir = new plask.Vec3(param.d_lite.x, param.d_lite.y, param.d_lite.z); | |
l_dir.normalize(); | |
l_dir.scale(-1); | |
//console.log(l_dir); | |
var d_color = new plask.Vec3(param.d_lite.r, param.d_lite.g, param.d_lite.b); | |
// Set the shaders to be used during drawing, and pass the current | |
// transformation matrices to them. | |
mprogram.use(); | |
mprogram.set_uMVMatrix(mv); | |
mprogram.set_uPMatrix(persp); | |
mprogram.set_uNMatrix(norm); | |
mprogram.set_uAmbientColor(a_color); | |
mprogram.set_uLightingDirection(l_dir); | |
mprogram.set_uDirectionalColor(d_color); | |
mprogram.set_uUseLighting(param.lighiting); | |
mprogram.set_uSampler(0); | |
if (param.blending) { | |
gl.blendFunc(gl.SRC_ALPHA, gl.ONE); | |
gl.enable(gl.BLEND); | |
gl.disable(gl.DEPTH_TEST); | |
mprogram.set_uAlpha(param.alfa); | |
} else { | |
gl.disable(gl.BLEND); | |
gl.enable(gl.DEPTH_TEST); | |
} | |
// Draw the geometry from the VBO, passing the vertices to the vertex | |
// shader as a vec3 named |a_xyz|. | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer.buffer); | |
gl.vertexAttribPointer(mprogram.location_aVertexPosition, | |
cubeVertexPositionBuffer.itemSize, | |
gl.FLOAT, | |
false, 0, 0); | |
gl.enableVertexAttribArray(mprogram.location_aVertexPosition); | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexNormalBuffer.buffer); | |
gl.vertexAttribPointer(mprogram.location_aVertexNormal, | |
cubeVertexNormalBuffer.itemSize, | |
gl.FLOAT, | |
false, 0, 0); | |
gl.enableVertexAttribArray(mprogram.location_aVertexNormal); | |
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer.buffer); | |
gl.vertexAttribPointer(mprogram.location_aTextureCoord, | |
cubeVertexTextureCoordBuffer.itemSize, | |
gl.FLOAT, | |
false, 0, 0); | |
gl.enableVertexAttribArray(mprogram.location_aTextureCoord); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer.buffer); | |
gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.num, gl.UNSIGNED_SHORT, 0); | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
attribute vec3 aVertexPosition; | |
attribute vec3 aVertexNormal; | |
attribute vec2 aTextureCoord; | |
uniform mat4 uMVMatrix; | |
uniform mat4 uPMatrix; | |
uniform mat3 uNMatrix; | |
uniform vec3 uAmbientColor; | |
uniform vec3 uLightingDirection; | |
uniform vec3 uDirectionalColor; | |
uniform bool uUseLighting; | |
varying vec2 vTextureCoord; | |
varying vec3 vLightWeighting; | |
void main(void) { | |
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); | |
vTextureCoord = aTextureCoord; | |
if (!uUseLighting) { | |
vLightWeighting = vec3(1.0, 1.0, 1.0); | |
} else { | |
vec3 transformedNormal = uNMatrix * aVertexNormal; | |
float directionalLightWeighting = max(dot(transformedNormal, uLightingDirection), 0.0); | |
vLightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Plask. | |
// (c) Dean McNamee <[email protected]>, 2010. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to | |
// deal in the Software without restriction, including without limitation the | |
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
// sell copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
// IN THE SOFTWARE. | |
var sys = require('sys'); | |
var fs = require('fs'); | |
var path = require('path'); | |
var events = require('events'); | |
var dgram = require('dgram'); | |
var inherits = sys.inherits; | |
exports.SkPath = PlaskRawMac.SkPath; | |
exports.SkPaint = PlaskRawMac.SkPaint; | |
exports.SkCanvas = PlaskRawMac.SkCanvas; | |
// NOTE(deanm): The SkCanvas constructor has become too complicated in | |
// supporting different types of canvases and ways to create them. Use one of | |
// the following factory functions instead of calling the constructor directly. | |
exports.SkCanvas.createFromImage = function(path) { | |
return new exports.SkCanvas('^IMG', path); | |
}; | |
exports.SkCanvas.createFromImageData = function(data) { | |
return new exports.SkCanvas('^IMG', data); | |
}; | |
exports.SkCanvas.create = function(width, height) { | |
return new exports.SkCanvas(width, height); | |
}; | |
exports.SkCanvas.createNSWindowBacked = function(nswindow) { | |
return new exports.SkCanvas(nswindow); | |
}; | |
// Sizes are in points, at 72 points per inch, letter would be 612x792. | |
// That makes A4 about 595x842. | |
// TODO(deanm): The sizes are integer, check the right size to use for A4. | |
exports.SkCanvas.createForPDF = function(page_width, page_height, | |
content_width, content_height) { | |
return new exports.SkCanvas( | |
'%PDF', | |
page_width, page_height, | |
content_width === undefined ? page_width : content_width, | |
content_height === undefined ? page_height : content_height); | |
}; | |
var kPI = 3.14159265358979323846264338327950288; | |
var kPI2 = 1.57079632679489661923132169163975144; | |
var kPI4 = 0.785398163397448309615660845819875721; | |
var k2PI = 6.28318530717958647692528676655900576; | |
function min(a, b) { | |
if (a < b) return a; | |
return b; | |
} | |
function max(a, b) { | |
if (a > b) return a; | |
return b; | |
} | |
// Keep the value |v| in the range vmin .. vmax. This matches GLSL clamp(). | |
function clamp(v, vmin, vmax) { | |
return min(vmax, max(vmin, v)); | |
} | |
// Linear interpolation on the line along points (0, |a|) and (1, |b|). The | |
// position |t| is the x coordinate, where 0 is |a| and 1 is |b|. | |
function lerp(a, b, t) { | |
return a + (b-a)*t; | |
} | |
// GLSL smoothstep(). NOTE: Undefined if edge0 == edge1. | |
function smoothstep(edge0, edge1, x) { | |
var t = clamp((x - edge0) / (edge1 - edge0), 0, 1); | |
return t * t * (3 - t - t); | |
} | |
// Test if |num| is a floating point -0. | |
function isNegZero(num) { | |
return 1/num === -Infinity; | |
} | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib1fv = function(idx, seq) { | |
this.vertexAttrib1f(idx, seq[0]); | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib2fv = function(idx, seq) { | |
this.vertexAttrib2f(idx, seq[0], seq[1]); | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib3fv = function(idx, seq) { | |
this.vertexAttrib3f(idx, seq[0], seq[1], seq[2]); | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib4fv = function(idx, seq) { | |
this.vertexAttrib4f(idx, seq[0], seq[1], seq[2], seq[3]); | |
}; | |
var flipper_paint = new exports.SkPaint; | |
flipper_paint.setXfermodeMode(flipper_paint.kSrcMode); | |
PlaskRawMac.NSOpenGLContext.prototype.texImage2DSkCanvas = function(a, b, c) { | |
var width = c.width, height = c.height; | |
var flipped = exports.SkCanvas.create(width, height); | |
flipped.translate(0, height); | |
flipped.scale(1, -1); | |
flipped.drawCanvas(flipper_paint, c, 0, 0, width, height); | |
var result = this.texImage2DSkCanvasB(a, b, flipped); | |
return result; | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.texImage2DSkCanvasNoFlip = function() { | |
return this.texImage2DSkCanvasB.apply(this, arguments); | |
}; | |
PlaskRawMac.CAMIDISource.prototype.noteOn = function(chan, note, vel) { | |
return this.sendData([0x90 | (chan & 0xf), note & 0x7f, vel & 0x7f]); | |
}; | |
PlaskRawMac.CAMIDISource.prototype.noteOff = function(chan, note, vel) { | |
return this.sendData([0x80 | (chan & 0xf), note & 0x7f, vel & 0x7f]); | |
}; | |
// Pitch wheel takes a value between -1 .. 1, and will be mapped to 14-bit midi. | |
PlaskRawMac.CAMIDISource.prototype.pitchWheel = function(chan, val) { | |
var bits = clamp((val * 0.5 + 0.5) * 16384, 0, 16383); // Not perfect at +1. | |
return this.sendData([0xe0 | (chan & 0xf), bits & 0x7f, (bits >> 7) & 0x7f]); | |
}; | |
PlaskRawMac.CAMIDISource.prototype.controller = function(chan, con, val) { | |
return this.sendData([0xb0 | (chan & 0xf), con & 0x7f, val & 0x7f]); | |
}; | |
PlaskRawMac.CAMIDISource.prototype.programChange = function(chan, val) { | |
return this.sendData([0xc0 | (chan & 0xf), val & 0x7f]); | |
}; | |
inherits(PlaskRawMac.CAMIDIDestination, events.EventEmitter); | |
PlaskRawMac.CAMIDIDestination.prototype.on = function(evname, callback) { | |
if (this._dgram_initialized !== true) { | |
var path = '/tmp/plask_internal_midi_socket_' + | |
process.pid + '_' + Date.now(); | |
var sock = dgram.createSocket('unix_dgram'); | |
sock.bind(path); | |
var this_ = this; | |
sock.on('message', function(msg, rinfo) { | |
if (msg.length < 1) { | |
console.log('Received zero length midi message.'); | |
return; | |
} | |
// NOTE(deanm): I would have assumed that every MIDI message should come | |
// in as its own 'packet', but for example sending a snapshot from a | |
// UC-33e sends some of the controller messages back to back in the same | |
// packet. I'm not sure if this is the expected behavior, but we'll | |
// try to handle it... | |
var j = 0; | |
while (j < msg.length) { | |
if ((msg[j] & 0x80) !== 0x80) { | |
console.log('First MIDI byte not a status byte.'); | |
return; | |
} | |
// NOTE(deanm): We expect MIDI packets are the correct length, for | |
// example 3 bytes for note on and off. Instead of error checking, | |
// we'll get undefined from msg[] if the message is shorter, maybe | |
// should handle this better, but loads of length checking is annoying. | |
switch (msg[j] & 0xf0) { | |
case 0x90: // Note on. | |
this_.emit('noteOn', {type:'noteOn', | |
chan: msg[j+0] & 0x0f, | |
note: msg[j+1], | |
vel: msg[j+2]}); | |
break; | |
case 0x80: // Note off. | |
this_.emit('noteOff', {type:'noteOff', | |
chan: msg[j+0] & 0x0f, | |
note: msg[j+1], | |
vel: msg[j+2]}); | |
break; | |
case 0xb0: // Controller message. | |
this_.emit('controller', {type:'controller', | |
chan: msg[j+0] & 0x0f, | |
num: msg[j+1], | |
val: msg[j+2]}); | |
break; | |
default: | |
console.log('Unhandled MIDI status byte: 0x' + msg[0].toString(16)); | |
return; | |
} | |
j += 3; // NOTE: Careful, some day we'll handle longer messages? | |
} | |
}); | |
this.setDgramPath(path); | |
this._dgram_initialized = true; | |
} | |
events.EventEmitter.prototype.on.call(this, evname, callback); | |
}; | |
exports.MidiIn = PlaskRawMac.CAMIDIDestination; | |
exports.MidiOut = PlaskRawMac.CAMIDISource; | |
exports.SBApplication = function(bundleid) { | |
var sbapp = new PlaskRawMac.SBApplication(bundleid); | |
var methods = sbapp.objcMethods(); | |
for (var i = 0, il = methods.length; i < il; ++i) { | |
var sig = methods[i]; | |
if (sig.length === 4) { | |
this[sig[0].replace(/:/g, '_')] = (function(name) { | |
return function() { | |
return sbapp.invokeVoid0(name); | |
}; | |
})(sig[0]); | |
} | |
if (sig.length === 5 && sig[4] === '@') { // Assume arg is a string. | |
this[sig[0].replace(/:/g, '_')] = (function(name) { | |
return function(arg) { | |
return sbapp.invokeVoid1s(name, arg); | |
}; | |
})(sig[0]); | |
} | |
} | |
console.log(methods); | |
}; | |
exports.AppleScript = PlaskRawMac.NSAppleScript; | |
exports.Window = function(width, height, opts) { | |
var nswindow_ = new PlaskRawMac.NSWindow( | |
opts.type === '3d' ? 1 : 0, | |
width, height, | |
opts.multisample === true, | |
opts.display === undefined ? -1 : opts.display, | |
opts.borderless === true, | |
opts.fullscreen === true); | |
var this_ = this; | |
this.context = nswindow_.context; // Export the 3d context (if it exists). | |
this.width = width; this.height = height; | |
// About mouse buttons. One day it will be important to have consistent | |
// numbering across platforms. We name from base 1: | |
// 1 left | |
// 2 right | |
// 3 middle | |
// ... Others (figure out wheel, etc). | |
function buttonNumberToName(numBaseOne) { | |
switch (numBaseOne) { | |
case 1: return 'left'; | |
case 2: return 'right'; | |
case 3: return 'middle'; | |
default: return 'button' + numBaseOne; | |
} | |
} | |
this.setTitle = function(title) { | |
nswindow_.setTitle(title); | |
} | |
// This is quite noisy on the event loop if you don't need it. | |
//nswindow_.setAcceptsMouseMovedEvents(true); | |
function nsEventNameToEmitName(nsname) { | |
switch (nsname) { | |
case PlaskRawMac.NSEvent.NSLeftMouseUp: return 'leftMouseUp'; | |
case PlaskRawMac.NSEvent.NSLeftMouseDown: return 'leftMouseDown'; | |
case PlaskRawMac.NSEvent.NSRightMouseUp: return 'rightMouseUp'; | |
case PlaskRawMac.NSEvent.NSRightMouseDown: return 'rightMouseDown'; | |
case PlaskRawMac.NSEvent.NSOtherMouseUp: return 'otherMouseUp'; | |
case PlaskRawMac.NSEvent.NSOtherMouseDown: return 'otherMouseDown'; | |
case PlaskRawMac.NSEvent.NSLeftMouseDragged: return 'leftMouseDragged'; | |
case PlaskRawMac.NSEvent.NSRightMouseDragged: return 'rightMouseDragged'; | |
case PlaskRawMac.NSEvent.NSOtherMouseDragged: return 'otherMouseDragged'; | |
case PlaskRawMac.NSEvent.NSKeyUp: return 'keyUp'; | |
case PlaskRawMac.NSEvent.NSKeyDown: return 'keyDown'; | |
case PlaskRawMac.NSEvent.NSScrollWheel: return 'scrollWheel'; | |
case PlaskRawMac.NSEvent.NSTabletPoint: return 'tabletPoint'; | |
case PlaskRawMac.NSEvent.NSTabletProximity: return 'tabletProximity'; | |
default: return ''; | |
} | |
} | |
this.setMouseMovedEnabled = function(enabled) { | |
return nswindow_.setAcceptsMouseMovedEvents(enabled); | |
}; | |
this.setFileDragEnabled = function(enabled) { | |
return nswindow_.setAcceptsFileDrag(enabled); | |
}; | |
this.setFrameTopLeftPoint = function(x, y) { | |
return nswindow_.setFrameTopLeftPoint(x, y); | |
}; | |
this.screenSize = function() { | |
return nswindow_.screenSize(); | |
}; | |
this.hideCursor = function() { | |
return nswindow_.hideCursor(); | |
}; | |
this.showCursor = function() { | |
return nswindow_.showCursor(); | |
}; | |
this.center = function() { | |
nswindow_.center(); | |
}; | |
function handleRawNSEvent(e) { | |
var type = e.type(); | |
if (0) { | |
sys.puts("event: " + type); | |
for (var key in e) { | |
if (e[key] === type) sys.puts(key); | |
} | |
} | |
switch (type) { | |
case PlaskRawMac.NSEvent.NSLeftMouseDown: | |
case PlaskRawMac.NSEvent.NSLeftMouseUp: | |
case PlaskRawMac.NSEvent.NSRightMouseDown: | |
case PlaskRawMac.NSEvent.NSRightMouseUp: | |
case PlaskRawMac.NSEvent.NSOtherMouseDown: | |
case PlaskRawMac.NSEvent.NSOtherMouseUp: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var button = e.buttonNumber() + 1; // We work starting from 1. | |
var type_name = nsEventNameToEmitName(type); | |
// We want to also emit middleMouseUp, etc, but NS buckets it as other. | |
if (button === 3) type_name = type_name.replace('other', 'middle'); | |
var te = { | |
type: type_name, | |
x: loc.x, | |
y: height - loc.y, // Map from button left to top left. | |
buttonNumber: button, | |
buttonName: buttonNumberToName(button), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
// Filter out clicks on the title bar. | |
if (te.y < 0) break; | |
// Emit the specific per-button event. | |
this_.emit(te.type, te); | |
// Emit a generic up / down event for all buttons. | |
this_.emit(te.type.substr(-2) === 'Up' ? 'mouseUp' : 'mouseDown', te); | |
break; | |
case PlaskRawMac.NSEvent.NSLeftMouseDragged: | |
case PlaskRawMac.NSEvent.NSRightMouseDragged: | |
case PlaskRawMac.NSEvent.NSOtherMouseDragged: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var button = e.buttonNumber() + 1; // We work starting from 1. | |
var type_name = nsEventNameToEmitName(type); | |
// We want to also emit middleMouseUp, etc, but NS buckets it as other. | |
if (button === 3) type_name = type_name.replace('other', 'middle'); | |
var te = { | |
type: type_name, | |
x: loc.x, | |
y: height - loc.y, | |
dx: e.deltaX(), | |
dy: e.deltaY(), // Doesn't need flipping since it's in device space. | |
dz: e.deltaZ(), | |
pressure: e.pressure(), | |
buttonNumber: button, | |
buttonName: buttonNumberToName(button), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
// TODO(deanm): This is wrong if the drag started in the content view. | |
if (te.y < 0) break; | |
// Emit the specific per-button event. | |
this_.emit(te.type, te); | |
// Emit a generic up / down event for all buttons. | |
this_.emit('mouseDragged', te); | |
break; | |
case PlaskRawMac.NSEvent.NSTabletPoint: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var te = { | |
type: nsEventNameToEmitName(type), | |
x: loc.x, | |
y: height - loc.y, | |
pressure: e.pressure(), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSTabletProximity: | |
var te = { | |
type: nsEventNameToEmitName(type), | |
entering: e.isEnteringProximity() | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSKeyUp: | |
case PlaskRawMac.NSEvent.NSKeyDown: | |
var mods = e.modifierFlags(); | |
var te = { | |
type: nsEventNameToEmitName(type), | |
str: e.characters(), | |
keyCode: e.keyCode(), // I'll probably regret this. | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSMouseMoved: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var te = { | |
type: 'mouseMoved', | |
x: loc.x, | |
y: height - loc.y, | |
dx: e.deltaX(), | |
dy: e.deltaY(), // Doesn't need flipping since it's in device space. | |
dz: e.deltaZ(), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSScrollWheel: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var te = { | |
type: 'scrollWheel', | |
x: loc.x, | |
y: height - loc.y, | |
dx: e.deltaX(), | |
dy: e.deltaY(), // Doesn't need flipping since it's in device space. | |
dz: e.deltaZ(), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
default: | |
break; | |
} | |
} | |
// Handle events coming from the native layer. | |
nswindow_.setEventCallback(function(msgtype, msgdata) { | |
// Since emit is synchronous, we need to catch any exceptions that might | |
// happen during event handlers. | |
try { | |
if (msgtype === 0) { // Cocoa NSEvent. | |
handleRawNSEvent(msgdata); | |
} else if (msgtype === 1) { // File drag. | |
this_.emit('filesDropped', {type: 'filesDropped', | |
paths: msgdata.paths, | |
x: msgdata.x, | |
y: height - msgdata.y}); | |
} | |
} catch(ex) { | |
sys.puts(ex.stack); | |
} | |
}); | |
this.getRelativeMouseState = function() { | |
var res = nswindow_.mouseLocationOutsideOfEventStream(); | |
res.y = height - res.y; // Map from Desktop OSX bottom left to top left. | |
var buttons = PlaskRawMac.NSEvent.pressedMouseButtons(); | |
for (var i = 0; i < 6; ++i) { | |
res[buttonNumberToName(i + 1)] = ((buttons >> i) & 1) === 1; | |
} | |
return res; | |
}; | |
this.makeWindowBackedCanvas = function() { | |
return exports.SkCanvas.createNSWindowBacked(nswindow_); | |
}; | |
this.blit = function() { | |
nswindow_.blit(); | |
}; | |
}; | |
inherits(exports.Window, events.EventEmitter); | |
exports.simpleWindow = function(obj) { | |
// NOTE(deanm): Moving to a settings object to reduce the pollution of the | |
// main simpleWindow object. For now fall back for compat. | |
var settings = obj.settings; | |
if (settings === undefined) { | |
settings = obj; | |
if (obj.width !== undefined) | |
console.log('Warning, using legacy settings, use the settings object.'); | |
} | |
var wintype = (settings.type === '3d' || settings.type === '3d2d') ? '3d' : | |
'2d'; | |
var width = settings.width === undefined ? 400 : settings.width; | |
var height = settings.height === undefined ? 300 : settings.height; | |
var syphon_server = null; | |
// TODO(deanm): Fullscreen. | |
var window_ = new exports.Window( | |
width, height, {type: wintype, | |
multisample: settings.multisample === true, | |
display: settings.display, | |
borderless: settings.borderless === undefined ? | |
settings.fullscreen : settings.borderless, | |
fullscreen: settings.fullscreen}); | |
if (settings.position !== undefined) { | |
var position_x = settings.position.x; | |
var position_y = settings.position.y; | |
if (position_y < 0 || isNegZero(position_y)) | |
position_y = window_.screenSize().height + position_y; | |
if (position_x < 0 || isNegZero(position_x)) | |
position_x = window_.screenSize().width + position_x; | |
window_.setFrameTopLeftPoint(position_x, position_y); | |
} else if (settings.center === true || | |
(settings.fullscreen !== true && settings.center !== false)) { | |
window_.center(); | |
} | |
var gl_ = window_.context; | |
// obj.window = window_; | |
obj.width = width; | |
obj.height = height; | |
if (settings.title !== undefined) | |
window_.setTitle(settings.title); | |
obj.setTitle = function(title) { window_.setTitle(title); }; | |
if (settings.cursor === false) | |
window_.hideCursor(); | |
obj.getRelativeMouseState = function() { | |
return window_.getRelativeMouseState(); | |
}; | |
var canvas = null; // Protected from getting clobbered on obj. | |
if (wintype === '3d') { | |
if (settings.vsync === true) | |
gl_.setSwapInterval(1); | |
if (settings.type === '3d') { // Don't expose gl for 3d2d windows. | |
obj.gl = gl_; | |
} else { // Create a canvas and paint for 3d2d windows. | |
obj.paint = new exports.SkPaint; | |
canvas = exports.SkCanvas.create(width, height); // Offscreen. | |
obj.canvas = canvas; | |
} | |
if (settings.syphon_server !== undefined) { | |
syphon_server = gl_.createSyphonServer(settings.syphon_server); | |
} | |
} else { | |
obj.paint = new exports.SkPaint; | |
canvas = window_.makeWindowBackedCanvas(); | |
obj.canvas = canvas; | |
} | |
var framerate_handle = null; | |
obj.framerate = function(fps) { | |
if (framerate_handle !== null) | |
clearInterval(framerate_handle); | |
if (fps === 0) return; | |
framerate_handle = setInterval(function() { | |
obj.redraw(); | |
}, 1000 / fps); | |
} | |
// Export listener API so you can "this.on" instead of "this.window.on". | |
obj.on = function(e, listener) { | |
// TODO(deanm): Do this properly | |
if (e === 'mouseMoved') | |
window_.setMouseMovedEnabled(true); | |
if (e === 'filesDropped') | |
window_.setFileDragEnabled(true); | |
var this_ = this; | |
return window_.on(e, function() { | |
return listener.apply(this_, arguments); | |
}); | |
}; | |
obj.removeListener = function(e, listener) { | |
return window_.removeListener(e, listener); | |
}; | |
this.getRelativeMouseState = function() { | |
return window_.getRelativeMouseState(); | |
}; | |
// Call init as early as possible, to give the init routine a chance to | |
// setup anything on the object it might want to do at runtime. | |
if ('init' in obj) { | |
try { | |
obj.init(); | |
} catch (ex) { | |
sys.error('Exception caught in simpleWindow init:\n' + | |
ex + '\n' + ex.stack); | |
} | |
} | |
var draw = null; | |
var framenum = 0; | |
var frame_start_time = Date.now(); | |
if ('draw' in obj) | |
draw = obj.draw; | |
obj.redraw = function() { | |
if (gl_ !== undefined) | |
gl_.makeCurrentContext(); | |
if (draw !== null) { | |
obj.framenum = framenum; | |
obj.frametime = (Date.now() - frame_start_time) / 1000; // Secs. | |
try { | |
obj.draw(); | |
} catch (ex) { | |
sys.error('Exception caught in simpleWindow draw:\n' + | |
ex + '\n' + ex.stack); | |
} | |
framenum++; | |
} | |
if (gl_ !== undefined && canvas !== null) { // 3d2d | |
// Blit to Syphon. | |
if (syphon_server !== null && syphon_server.hasClients() === true) { | |
if (syphon_server.bindToDrawFrameOfSize(width, height) === true) { | |
gl_.drawSkCanvas(canvas); | |
syphon_server.unbindAndPublish(); | |
} else { | |
console.log('Error blitting for Syphon.'); | |
} | |
} | |
// Blit to the screen OpenGL context. | |
gl_.drawSkCanvas(canvas); | |
} | |
window_.blit(); // Update the screen automatically. | |
}; | |
obj.redraw(); // Draw the first frame. | |
return obj; | |
}; | |
// A class representing a 3 dimensional point and/or vector. There isn't a | |
// good reason to differentiate between the two, and you often want to change | |
// how you think about the same set of values. So there is only "vector". | |
// | |
// The class is designed without accessors or individual mutators, you should | |
// access the x, y, and z values directly on the object. | |
// | |
// Almost all of the core operations happen in place, writing to the current | |
// object. If you want a copy, you can call dup(). For convenience, many | |
// operations have a passed-tense version that returns a new object. Most | |
// methods return this to support chaining. | |
function Vec3(x, y, z) { | |
this.x = x; this.y = y; this.z = z; | |
} | |
Vec3.prototype.set = function(x, y, z) { | |
this.x = x; this.y = y; this.z = z; | |
return this; | |
}; | |
Vec3.prototype.setVec3 = function(v) { | |
this.x = v.x; this.y = v.y; this.z = v.z; | |
return this; | |
}; | |
// Cross product, this = a x b. | |
Vec3.prototype.cross2 = function(a, b) { | |
var ax = a.x, ay = a.y, az = a.z, | |
bx = b.x, by = b.y, bz = b.z; | |
this.x = ay * bz - az * by; | |
this.y = az * bx - ax * bz; | |
this.z = ax * by - ay * bx; | |
return this; | |
}; | |
// Cross product, this = this x b. | |
Vec3.prototype.cross = function(b) { | |
return this.cross2(this, b); | |
}; | |
// Returns the dot product, this . b. | |
Vec3.prototype.dot = function(b) { | |
return this.x * b.x + this.y * b.y + this.z * b.z; | |
}; | |
// Add two Vec3s, this = a + b. | |
Vec3.prototype.add2 = function(a, b) { | |
this.x = a.x + b.x; | |
this.y = a.y + b.y; | |
this.z = a.z + b.z; | |
return this; | |
}; | |
// Add a Vec3, this = this + b. | |
Vec3.prototype.add = function(b) { | |
return this.add2(this, b); | |
}; | |
Vec3.prototype.added = function(b) { | |
return new Vec3(this.x + b.x, | |
this.y + b.y, | |
this.z + b.z); | |
}; | |
// Subtract two Vec3s, this = a - b. | |
Vec3.prototype.sub2 = function(a, b) { | |
this.x = a.x - b.x; | |
this.y = a.y - b.y; | |
this.z = a.z - b.z; | |
return this; | |
}; | |
// Subtract another Vec3, this = this - b. | |
Vec3.prototype.sub = function(b) { | |
return this.sub2(this, b); | |
}; | |
Vec3.prototype.subbed = function(b) { | |
return new Vec3(this.x - b.x, | |
this.y - b.y, | |
this.z - b.z); | |
}; | |
// Multiply two Vec3s, this = a * b. | |
Vec3.prototype.mul2 = function(a, b) { | |
this.x = a.x * b.x; | |
this.y = a.y * b.y; | |
this.z = a.z * b.z; | |
return this; | |
}; | |
// Multiply by another Vec3, this = this * b. | |
Vec3.prototype.mul = function(b) { | |
return this.mul2(this, b); | |
}; | |
Vec3.prototype.mulled = function(b) { | |
return new Vec3(this.x * b.x, | |
this.y * b.y, | |
this.z * b.z); | |
}; | |
// Multiply by a scalar. | |
Vec3.prototype.scale = function(s) { | |
this.x *= s; this.y *= s; this.z *= s; | |
return this; | |
}; | |
Vec3.prototype.scaled = function(s) { | |
return new Vec3(this.x * s, this.y * s, this.z * s); | |
}; | |
// Interpolate between this and another Vec3 |b|, based on |t|. | |
Vec3.prototype.lerp = function(b, t) { | |
this.x = this.x + (b.x-this.x)*t; | |
this.y = this.y + (b.y-this.y)*t; | |
this.z = this.z + (b.z-this.z)*t; | |
return this; | |
}; | |
Vec3.prototype.lerped = function(b, t) { | |
return new Vec3(this.x + (b.x-this.x)*t, | |
this.y + (b.y-this.y)*t, | |
this.z + (b.z-this.z)*t); | |
}; | |
// Magnitude (length). | |
Vec3.prototype.length = function() { | |
var x = this.x, y = this.y, z = this.z; | |
return Math.sqrt(x*x + y*y + z*z); | |
}; | |
// Magnitude squared. | |
Vec3.prototype.lengthSquared = function() { | |
var x = this.x, y = this.y, z = this.z; | |
return x*x + y*y + z*z; | |
}; | |
// Distance to Vec3 |b|. | |
Vec3.prototype.dist = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
var z = b.z - this.z; | |
return Math.sqrt(x*x + y*y + z*z); | |
}; | |
// Squared Distance to Vec3 |b|. | |
Vec3.prototype.distSquared = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
var z = b.z - this.z; | |
return x*x + y*y + z*z; | |
}; | |
// Normalize, scaling so the magnitude is 1. Invalid for a zero vector. | |
Vec3.prototype.normalize = function() { | |
return this.scale(1/this.length()); | |
}; | |
Vec3.prototype.normalized = function() { | |
return this.dup().normalize(); | |
}; | |
Vec3.prototype.dup = function() { | |
return new Vec3(this.x, this.y, this.z); | |
}; | |
Vec3.prototype.debugString = function() { | |
return 'x: ' + this.x + ' y: ' + this.y + ' z: ' + this.z; | |
}; | |
// Like a z-less Vec3, Vec2. | |
function Vec2(x, y) { | |
this.x = x; this.y = y; | |
} | |
Vec2.prototype.set = function(x, y) { | |
this.x = x; this.y = y | |
return this; | |
}; | |
Vec2.prototype.setVec2 = function(v) { | |
this.x = v.x; this.y = v.y; | |
return this; | |
}; | |
// Returns the dot product, this . b. | |
Vec2.prototype.dot = function(b) { | |
return this.x * b.x + this.y * b.y; | |
}; | |
// Add two Vec2s, this = a + b. | |
Vec2.prototype.add2 = function(a, b) { | |
this.x = a.x + b.x; | |
this.y = a.y + b.y; | |
return this; | |
}; | |
// Add a Vec2, this = this + b. | |
Vec2.prototype.add = function(b) { | |
return this.add2(this, b); | |
}; | |
Vec2.prototype.added = function(b) { | |
return new Vec2(this.x + b.x, | |
this.y + b.y); | |
}; | |
// Subtract two Vec2s, this = a - b. | |
Vec2.prototype.sub2 = function(a, b) { | |
this.x = a.x - b.x; | |
this.y = a.y - b.y; | |
return this; | |
}; | |
// Subtract another Vec2, this = this - b. | |
Vec2.prototype.sub = function(b) { | |
return this.sub2(this, b); | |
}; | |
Vec2.prototype.subbed = function(b) { | |
return new Vec2(this.x - b.x, | |
this.y - b.y); | |
}; | |
// Multiply two Vec2s, this = a * b. | |
Vec2.prototype.mul2 = function(a, b) { | |
this.x = a.x * b.x; | |
this.y = a.y * b.y; | |
return this; | |
}; | |
// Multiply by another Vec2, this = this * b. | |
Vec2.prototype.mul = function(b) { | |
return this.mul2(this, b); | |
}; | |
Vec2.prototype.mulled = function(b) { | |
return new Vec2(this.x * b.x, | |
this.y * b.y); | |
}; | |
// Multiply by a scalar. | |
Vec2.prototype.scale = function(s) { | |
this.x *= s; this.y *= s; | |
return this; | |
}; | |
Vec2.prototype.scaled = function(s) { | |
return new Vec2(this.x * s, this.y * s); | |
}; | |
// Interpolate between this and another Vec2 |b|, based on |t|. | |
Vec2.prototype.lerp = function(b, t) { | |
this.x = this.x + (b.x-this.x)*t; | |
this.y = this.y + (b.y-this.y)*t; | |
return this; | |
}; | |
Vec2.prototype.lerped = function(b, t) { | |
return new Vec2(this.x + (b.x-this.x)*t, | |
this.y + (b.y-this.y)*t); | |
}; | |
// Magnitude (length). | |
Vec2.prototype.length = function() { | |
var x = this.x, y = this.y; | |
return Math.sqrt(x*x + y*y); | |
}; | |
// Magnitude squared. | |
Vec2.prototype.lengthSquared = function() { | |
var x = this.x, y = this.y; | |
return x*x + y*y; | |
}; | |
// Distance to Vec2 |b|. | |
Vec2.prototype.dist = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
return Math.sqrt(x*x + y*y); | |
}; | |
// Squared Distance to Vec2 |b|. | |
Vec2.prototype.distSquared = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
return x*x + y*y; | |
}; | |
// Normalize, scaling so the magnitude is 1. Invalid for a zero vector. | |
Vec2.prototype.normalize = function() { | |
return this.scale(1/this.length()); | |
}; | |
Vec2.prototype.normalized = function() { | |
return this.dup().normalize(); | |
}; | |
// Rotate around the origin by |theta| radians (counter-clockwise). | |
Vec2.prototype.rotate = function(theta) { | |
var st = Math.sin(theta); | |
var ct = Math.cos(theta); | |
var x = this.x, y = this.y; | |
this.x = x * ct - y * st; | |
this.y = x * st + y * ct; | |
return this; | |
}; | |
Vec2.prototype.rotated = function(theta) { | |
return this.dup().rotate(theta); | |
}; | |
// Reflect a vector about the normal |n|. The vectors should both be unit. | |
Vec2.prototype.reflect = function(n) { | |
// r = u - 2(u.n)n | |
// This could could basically be: | |
// this.sub(n.scaled(this.dot(n) * 2)); | |
// But we avoid some extra object allocated / etc and just flatten it. | |
var s = this.dot(n) * 2; | |
this.x -= n.x * s; | |
this.y -= n.y * s; | |
return this; | |
}; | |
Vec2.prototype.reflected = function(n) { | |
var s = this.dot(n) * 2; | |
return Vec2(this.x - n.x * s, | |
this.y - n.y * s); | |
}; | |
Vec2.prototype.dup = function() { | |
return new Vec2(this.x, this.y); | |
}; | |
Vec2.prototype.debugString = function() { | |
return 'x: ' + this.x + ' y: ' + this.y; | |
}; | |
// TODO(deanm): Vec4 is currently a skeleton container, it should match the | |
// features of Vec3. | |
function Vec4(x, y, z, w) { | |
this.x = x; this.y = y; this.z = z; this.w = w; | |
} | |
Vec4.prototype.set = function(x, y, z, w) { | |
this.x = x; this.y = y; this.z = z; this.w = w; | |
return this; | |
}; | |
Vec4.prototype.setVec4 = function(v) { | |
this.x = v.x; this.y = v.y; this.z = v.z; this.w = v.w; | |
return this; | |
}; | |
// Multiply by a scalar. | |
Vec4.prototype.scale = function(s) { | |
this.x *= s; this.y *= s; this.z *= s; this.w *= s; | |
return this; | |
}; | |
Vec4.prototype.scaled = function(s) { | |
return new Vec3(this.x * s, this.y * s, this.z * s, this.w * s); | |
}; | |
Vec4.prototype.dup = function() { | |
return new Vec4(this.x, this.y, this.z, this.w); | |
}; | |
Vec4.prototype.toVec3 = function() { | |
return new Vec3(this.x, this.y, this.z); | |
}; | |
// noboko ////////////////////////////////////////////// | |
// 3x3 matrix | |
// a11 a12 a13 | |
// a21 a22 a23 | |
// a31 a32 a33 | |
// | |
// | |
//////////////////////////////////////////////////////// | |
function Mat3() { | |
this.reset(); | |
} | |
// Set the full 9 elements of the 3x3 matrix, arguments in row major order. | |
// The elements are specified in row major order. | |
Mat3.prototype.set3x3r = function(a11, a12, a13, a21, a22, a23,a31, a32, a33) { | |
this.a11 = a11; this.a12 = a12; this.a13 = a13; | |
this.a21 = a21; this.a22 = a22; this.a23 = a23; | |
this.a31 = a31; this.a32 = a32; this.a33 = a33; | |
return this; | |
}; | |
// Reset the transform to the identity matrix. | |
Mat3.prototype.reset = function() { | |
this.set3x3r(1, 0, 0, | |
0, 1, 0, | |
0, 0, 1 ); | |
return this; | |
}; | |
// Transpose the matrix, rows become columns and columns become rows. | |
Mat3.prototype.transpose = function() { | |
var a11 = this.a11, a12 = this.a12, a13 = this.a13, | |
a21 = this.a21, a22 = this.a22, a23 = this.a23, | |
a31 = this.a31, a32 = this.a32, a33 = this.a33; | |
this.a11 = a11; this.a12 = a21; this.a13 = a31; | |
this.a21 = a12; this.a22 = a22; this.a23 = a32; | |
this.a31 = a13; this.a32 = a23; this.a33 = a33; | |
return this; | |
}; | |
Mat3.prototype.dup = function() { | |
var m = new Mat3(); // TODO(deanm): This could be better. | |
m.set3x3r(this.a11, this.a12, this.a13, | |
this.a21, this.a22, this.a23, | |
this.a31, this.a32, this.a33); | |
return m; | |
}; | |
Mat3.prototype.toFloat32Array = function() { | |
return new Float32Array([this.a11, this.a21, this.a31, | |
this.a12, this.a22, this.a32, | |
this.a13, this.a23, this.a33]); | |
}; | |
Mat3.prototype.toMat4 = function(){ | |
var m = new Mat4; | |
m.set4x4r(this.a11, this.a12, this.a13, 0, | |
this.a21, this.a22, this.a23, 0, | |
this.a31, this.a32, this.a33, 0, | |
0, 0, 0, 1); | |
return m; | |
}; | |
Mat3.prototype.debugString = function() { | |
var s = [this.a11, this.a12, this.a13, | |
this.a21, this.a22, this.a23, | |
this.a31, this.a32, this.a33,]; | |
var row_lengths = [0, 0, 0]; | |
for (var i = 0; i < 9; ++i) { | |
s[i] += ''; // Stringify. | |
var len = s[i].length; | |
var row = i & 2; | |
if (row_lengths[row] < len) | |
row_lengths[row] = len; | |
} | |
var out = ''; | |
for (var i = 0; i < 9; ++i) { | |
var len = s[i].length; | |
var row_len = row_lengths[i & 2]; | |
var num_spaces = row_len - len; | |
while (num_spaces--) out += ' '; | |
out += s[i] + ((i & 2) === 2 ? '\n' : ' '); | |
} | |
return out; | |
}; | |
////////////////////////////////////// | |
// This represents an affine 4x4 matrix, using mathematical notation, | |
// numbered (starting from 1) as aij, where i is the row and j is the column. | |
// a11 a12 a13 a14 | |
// a21 a22 a23 a24 | |
// a31 a32 a33 a34 | |
// a41 a42 a43 a44 | |
// | |
// Almost all operations are multiplies to the current matrix, and happen | |
// in place. You can use dup() to return a copy. | |
// | |
// Most operations return this to support chaining. | |
// | |
// It's common to use toFloat32Array to get a Float32Array in OpenGL (column | |
// major) memory ordering. NOTE: The code tries to be explicit about whether | |
// things are row major or column major, but remember that GLSL works in | |
// column major ordering, and PreGL generally uses row major ordering. | |
function Mat4() { | |
this.reset(); | |
} | |
// Set the full 16 elements of the 4x4 matrix, arguments in row major order. | |
// The elements are specified in row major order. TODO(deanm): set4x4c. | |
Mat4.prototype.set4x4r = function(a11, a12, a13, a14, a21, a22, a23, a24, | |
a31, a32, a33, a34, a41, a42, a43, a44) { | |
this.a11 = a11; this.a12 = a12; this.a13 = a13; this.a14 = a14; | |
this.a21 = a21; this.a22 = a22; this.a23 = a23; this.a24 = a24; | |
this.a31 = a31; this.a32 = a32; this.a33 = a33; this.a34 = a34; | |
this.a41 = a41; this.a42 = a42; this.a43 = a43; this.a44 = a44; | |
return this; | |
}; | |
// Reset the transform to the identity matrix. | |
Mat4.prototype.reset = function() { | |
this.set4x4r(1, 0, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Matrix multiply this = a * b | |
Mat4.prototype.mul2 = function(a, b) { | |
var a11 = a.a11, a12 = a.a12, a13 = a.a13, a14 = a.a14, | |
a21 = a.a21, a22 = a.a22, a23 = a.a23, a24 = a.a24, | |
a31 = a.a31, a32 = a.a32, a33 = a.a33, a34 = a.a34, | |
a41 = a.a41, a42 = a.a42, a43 = a.a43, a44 = a.a44; | |
var b11 = b.a11, b12 = b.a12, b13 = b.a13, b14 = b.a14, | |
b21 = b.a21, b22 = b.a22, b23 = b.a23, b24 = b.a24, | |
b31 = b.a31, b32 = b.a32, b33 = b.a33, b34 = b.a34, | |
b41 = b.a41, b42 = b.a42, b43 = b.a43, b44 = b.a44; | |
this.a11 = a11*b11 + a12*b21 + a13*b31 + a14*b41; | |
this.a12 = a11*b12 + a12*b22 + a13*b32 + a14*b42; | |
this.a13 = a11*b13 + a12*b23 + a13*b33 + a14*b43; | |
this.a14 = a11*b14 + a12*b24 + a13*b34 + a14*b44; | |
this.a21 = a21*b11 + a22*b21 + a23*b31 + a24*b41; | |
this.a22 = a21*b12 + a22*b22 + a23*b32 + a24*b42; | |
this.a23 = a21*b13 + a22*b23 + a23*b33 + a24*b43; | |
this.a24 = a21*b14 + a22*b24 + a23*b34 + a24*b44; | |
this.a31 = a31*b11 + a32*b21 + a33*b31 + a34*b41; | |
this.a32 = a31*b12 + a32*b22 + a33*b32 + a34*b42; | |
this.a33 = a31*b13 + a32*b23 + a33*b33 + a34*b43; | |
this.a34 = a31*b14 + a32*b24 + a33*b34 + a34*b44; | |
this.a41 = a41*b11 + a42*b21 + a43*b31 + a44*b41; | |
this.a42 = a41*b12 + a42*b22 + a43*b32 + a44*b42; | |
this.a43 = a41*b13 + a42*b23 + a43*b33 + a44*b43; | |
this.a44 = a41*b14 + a42*b24 + a43*b34 + a44*b44; | |
return this; | |
}; | |
// Matrix multiply this = this * b | |
Mat4.prototype.mul = function(b) { | |
return this.mul2(this, b); | |
}; | |
// Multiply the current matrix by 16 elements that would compose a Mat4 | |
// object, but saving on creating the object. this = this * b. | |
// The elements are specific in row major order. TODO(deanm): mul4x4c. | |
// TODO(deanm): It's a shame to duplicate the multiplication code. | |
Mat4.prototype.mul4x4r = function(b11, b12, b13, b14, b21, b22, b23, b24, | |
b31, b32, b33, b34, b41, b42, b43, b44) { | |
var a11 = this.a11, a12 = this.a12, a13 = this.a13, a14 = this.a14, | |
a21 = this.a21, a22 = this.a22, a23 = this.a23, a24 = this.a24, | |
a31 = this.a31, a32 = this.a32, a33 = this.a33, a34 = this.a34, | |
a41 = this.a41, a42 = this.a42, a43 = this.a43, a44 = this.a44; | |
this.a11 = a11*b11 + a12*b21 + a13*b31 + a14*b41; | |
this.a12 = a11*b12 + a12*b22 + a13*b32 + a14*b42; | |
this.a13 = a11*b13 + a12*b23 + a13*b33 + a14*b43; | |
this.a14 = a11*b14 + a12*b24 + a13*b34 + a14*b44; | |
this.a21 = a21*b11 + a22*b21 + a23*b31 + a24*b41; | |
this.a22 = a21*b12 + a22*b22 + a23*b32 + a24*b42; | |
this.a23 = a21*b13 + a22*b23 + a23*b33 + a24*b43; | |
this.a24 = a21*b14 + a22*b24 + a23*b34 + a24*b44; | |
this.a31 = a31*b11 + a32*b21 + a33*b31 + a34*b41; | |
this.a32 = a31*b12 + a32*b22 + a33*b32 + a34*b42; | |
this.a33 = a31*b13 + a32*b23 + a33*b33 + a34*b43; | |
this.a34 = a31*b14 + a32*b24 + a33*b34 + a34*b44; | |
this.a41 = a41*b11 + a42*b21 + a43*b31 + a44*b41; | |
this.a42 = a41*b12 + a42*b22 + a43*b32 + a44*b42; | |
this.a43 = a41*b13 + a42*b23 + a43*b33 + a44*b43; | |
this.a44 = a41*b14 + a42*b24 + a43*b34 + a44*b44; | |
return this; | |
}; | |
// TODO(deanm): Some sort of mat3x3. There are two ways you could do it | |
// though, just multiplying the 3x3 portions of the 4x4 matrix, or doing a | |
// 4x4 multiply with the last row/column implied to be 0, 0, 0, 1. This | |
// keeps true to the original matrix even if it's last row is not 0, 0, 0, 1. | |
// IN RADIANS, not in degrees like OpenGL. Rotate about x, y, z. | |
// The caller must supply a x, y, z as a unit vector. | |
Mat4.prototype.rotate = function(theta, x, y, z) { | |
// http://www.cs.rutgers.edu/~decarlo/428/gl_man/rotate.html | |
var s = Math.sin(theta); | |
var c = Math.cos(theta); | |
this.mul4x4r( | |
x*x*(1-c)+c, x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0, | |
y*x*(1-c)+z*s, y*y*(1-c)+c, y*z*(1-c)-x*s, 0, | |
x*z*(1-c)-y*s, y*z*(1-c)+x*s, z*z*(1-c)+c, 0, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Multiply by a translation of x, y, and z. | |
Mat4.prototype.translate = function(dx, dy, dz) { | |
// TODO(deanm): Special case the multiply since most goes unchanged. | |
this.mul4x4r(1, 0, 0, dx, | |
0, 1, 0, dy, | |
0, 0, 1, dz, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Multiply by a scale of x, y, and z. | |
Mat4.prototype.scale = function(sx, sy, sz) { | |
// TODO(deanm): Special case the multiply since most goes unchanged. | |
this.mul4x4r(sx, 0, 0, 0, | |
0, sy, 0, 0, | |
0, 0, sz, 0, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Multiply by a look at matrix, computed from the eye, center, and up points. | |
Mat4.prototype.lookAt = function(ex, ey, ez, cx, cy, cz, ux, uy, uz) { | |
var z = (new Vec3(ex - cx, ey - cy, ez - cz)).normalize(); | |
var x = (new Vec3(ux, uy, uz)).cross(z).normalize(); | |
var y = z.dup().cross(x).normalize(); | |
// The new axis basis is formed as row vectors since we are transforming | |
// the coordinate system (alias not alibi). | |
this.mul4x4r(x.x, x.y, x.z, 0, | |
y.x, y.y, y.z, 0, | |
z.x, z.y, z.z, 0, | |
0, 0, 0, 1); | |
this.translate(-ex, -ey, -ez); | |
return this; | |
}; | |
// Multiply by a frustum matrix computed from left, right, bottom, top, | |
// near, and far. | |
Mat4.prototype.frustum = function(l, r, b, t, n, f) { | |
this.mul4x4r( | |
(n+n)/(r-l), 0, (r+l)/(r-l), 0, | |
0, (n+n)/(t-b), (t+b)/(t-b), 0, | |
0, 0, (f+n)/(n-f), (2*f*n)/(n-f), | |
0, 0, -1, 0); | |
return this; | |
}; | |
// Multiply by a perspective matrix, computed from the field of view, aspect | |
// ratio, and the z near and far planes. | |
Mat4.prototype.perspective = function(fovy, aspect, znear, zfar) { | |
// This could also be done reusing the frustum calculation: | |
// var ymax = znear * Math.tan(fovy * kPI / 360.0); | |
// var ymin = -ymax; | |
// | |
// var xmin = ymin * aspect; | |
// var xmax = ymax * aspect; | |
// | |
// return makeFrustumAffine(xmin, xmax, ymin, ymax, znear, zfar); | |
var f = 1.0 / Math.tan(fovy * kPI / 360.0); | |
this.mul4x4r( | |
f/aspect, 0, 0, 0, | |
0, f, 0, 0, | |
0, 0, (zfar+znear)/(znear-zfar), 2*znear*zfar/(znear-zfar), | |
0, 0, -1, 0); | |
return this; | |
}; | |
// Multiply by a orthographic matrix, computed from the clipping planes. | |
Mat4.prototype.ortho = function(l, r, b, t, n, f) { | |
this.mul4x4r(2/(r-l), 0, 0, (r+l)/(l-r), | |
0, 2/(t-b), 0, (t+b)/(b-t), | |
0, 0, 2/(n-f), (f+n)/(n-f), | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Invert the matrix. The matrix must be invertible. | |
Mat4.prototype.invert = function() { | |
// Based on the math at: | |
// http://www.geometrictools.com/LibMathematics/Algebra/Wm5Matrix4.inl | |
var x0 = this.a11, x1 = this.a12, x2 = this.a13, x3 = this.a14, | |
x4 = this.a21, x5 = this.a22, x6 = this.a23, x7 = this.a24, | |
x8 = this.a31, x9 = this.a32, x10 = this.a33, x11 = this.a34, | |
x12 = this.a41, x13 = this.a42, x14 = this.a43, x15 = this.a44; | |
var a0 = x0*x5 - x1*x4, | |
a1 = x0*x6 - x2*x4, | |
a2 = x0*x7 - x3*x4, | |
a3 = x1*x6 - x2*x5, | |
a4 = x1*x7 - x3*x5, | |
a5 = x2*x7 - x3*x6, | |
b0 = x8*x13 - x9*x12, | |
b1 = x8*x14 - x10*x12, | |
b2 = x8*x15 - x11*x12, | |
b3 = x9*x14 - x10*x13, | |
b4 = x9*x15 - x11*x13, | |
b5 = x10*x15 - x11*x14; | |
// TODO(deanm): These terms aren't reused, so get rid of the temporaries. | |
var invdet = 1 / (a0*b5 - a1*b4 + a2*b3 + a3*b2 - a4*b1 + a5*b0); | |
this.a11 = (+ x5*b5 - x6*b4 + x7*b3) * invdet; | |
this.a12 = (- x1*b5 + x2*b4 - x3*b3) * invdet; | |
this.a13 = (+ x13*a5 - x14*a4 + x15*a3) * invdet; | |
this.a14 = (- x9*a5 + x10*a4 - x11*a3) * invdet; | |
this.a21 = (- x4*b5 + x6*b2 - x7*b1) * invdet; | |
this.a22 = (+ x0*b5 - x2*b2 + x3*b1) * invdet; | |
this.a23 = (- x12*a5 + x14*a2 - x15*a1) * invdet; | |
this.a24 = (+ x8*a5 - x10*a2 + x11*a1) * invdet; | |
this.a31 = (+ x4*b4 - x5*b2 + x7*b0) * invdet; | |
this.a32 = (- x0*b4 + x1*b2 - x3*b0) * invdet; | |
this.a33 = (+ x12*a4 - x13*a2 + x15*a0) * invdet; | |
this.a34 = (- x8*a4 + x9*a2 - x11*a0) * invdet; | |
this.a41 = (- x4*b3 + x5*b1 - x6*b0) * invdet; | |
this.a42 = (+ x0*b3 - x1*b1 + x2*b0) * invdet; | |
this.a43 = (- x12*a3 + x13*a1 - x14*a0) * invdet; | |
this.a44 = (+ x8*a3 - x9*a1 + x10*a0) * invdet; | |
return this; | |
}; | |
// Transpose the matrix, rows become columns and columns become rows. | |
Mat4.prototype.transpose = function() { | |
var a11 = this.a11, a12 = this.a12, a13 = this.a13, a14 = this.a14, | |
a21 = this.a21, a22 = this.a22, a23 = this.a23, a24 = this.a24, | |
a31 = this.a31, a32 = this.a32, a33 = this.a33, a34 = this.a34, | |
a41 = this.a41, a42 = this.a42, a43 = this.a43, a44 = this.a44; | |
this.a11 = a11; this.a12 = a21; this.a13 = a31; this.a14 = a41; | |
this.a21 = a12; this.a22 = a22; this.a23 = a32; this.a24 = a42; | |
this.a31 = a13; this.a32 = a23; this.a33 = a33; this.a34 = a43; | |
this.a41 = a14; this.a42 = a24; this.a43 = a34; this.a44 = a44; | |
return this; | |
}; | |
// Multiply Vec3 |v| by the current matrix, returning a Vec3 of this * v. | |
Mat4.prototype.mulVec3 = function(v) { | |
var x = v.x, y = v.y, z = v.z; | |
return new Vec3(this.a14 + this.a11*x + this.a12*y + this.a13*z, | |
this.a24 + this.a21*x + this.a22*y + this.a23*z, | |
this.a34 + this.a31*x + this.a32*y + this.a33*z); | |
}; | |
// Multiply Vec4 |v| by the current matrix, returning a Vec4 of this * v. | |
Mat4.prototype.mulVec4 = function(v) { | |
var x = v.x, y = v.y, z = v.z, w = v.w; | |
return new Vec4(this.a14*w + this.a11*x + this.a12*y + this.a13*z, | |
this.a24*w + this.a21*x + this.a22*y + this.a23*z, | |
this.a34*w + this.a31*x + this.a32*y + this.a33*z, | |
this.a44*w + this.a41*x + this.a42*y + this.a43*z); | |
}; | |
////////////////////noboko///////////////////////////////////////// | |
////toMat3 | |
Mat4.prototype.toMat3 = function(){ | |
var m = new Mat3; | |
m.set3x3r(this.a11, this.a12, this.a13, | |
this.a21, this.a22, this.a23, | |
this.a31, this.a32, this.a33); | |
return m; | |
}; | |
////toInverseMat3 | |
Mat4.prototype.toInverseMat3 = function () { | |
var m = new Mat3; | |
var x11 = this.a11, x12 = this.a12, x13 = this.a13, | |
x21 = this.a21, x22 = this.a22, x23 = this.a23, | |
x31 = this.a31, x32 = this.a32, x33 = this.a33; | |
var b11 = x22 * x33 - x23 * x32, | |
b12 = x32 * x13 - x33 * x12, | |
b13 = x12 * x23 - x13 * x22, | |
b21 = x23 * x31 - x21 * x33, | |
b22 = x33 * x11 - x31 * x13, | |
b23 = x13 * x21 - x11 * x23, | |
b31 = x21 * x32 - x22 * x31, | |
b32 = x31 * x12 - x32 * x11, | |
b33 = x11 * x22 - x12 * x21; | |
var det = x11 * b11 + x21 * b12 + x31 * b13; | |
var invdet; | |
if (!det){ | |
return console.log('det is zero'); | |
} | |
else{ | |
invdet = 1 / det; | |
m.set3x3r(b11 * invdet, | |
b12 * invdet, | |
b13 * invdet, | |
b21 * invdet, | |
b22 * invdet, | |
b23 * invdet, | |
b31 * invdet, | |
b32 * invdet, | |
b33 * invdet); | |
return m; | |
} | |
}; | |
////////////////////////////////////////////////////////////////////// | |
Mat4.prototype.dup = function() { | |
var m = new Mat4(); // TODO(deanm): This could be better. | |
m.set4x4r(this.a11, this.a12, this.a13, this.a14, | |
this.a21, this.a22, this.a23, this.a24, | |
this.a31, this.a32, this.a33, this.a34, | |
this.a41, this.a42, this.a43, this.a44); | |
return m; | |
}; | |
Mat4.prototype.toFloat32Array = function() { | |
return new Float32Array([this.a11, this.a21, this.a31, this.a41, | |
this.a12, this.a22, this.a32, this.a42, | |
this.a13, this.a23, this.a33, this.a43, | |
this.a14, this.a24, this.a34, this.a44]); | |
}; | |
Mat4.prototype.debugString = function() { | |
var s = [this.a11, this.a12, this.a13, this.a14, | |
this.a21, this.a22, this.a23, this.a24, | |
this.a31, this.a32, this.a33, this.a34, | |
this.a41, this.a42, this.a43, this.a44]; | |
var row_lengths = [0, 0, 0, 0]; | |
for (var i = 0; i < 16; ++i) { | |
s[i] += ''; // Stringify. | |
var len = s[i].length; | |
var row = i & 3; | |
if (row_lengths[row] < len) | |
row_lengths[row] = len; | |
} | |
var out = ''; | |
for (var i = 0; i < 16; ++i) { | |
var len = s[i].length; | |
var row_len = row_lengths[i & 3]; | |
var num_spaces = row_len - len; | |
while (num_spaces--) out += ' '; | |
out += s[i] + ((i & 3) === 3 ? '\n' : ' '); | |
} | |
return out; | |
}; | |
var kFragmentShaderPrefix = "#ifdef GL_ES\n" + | |
"#ifdef GL_FRAGMENT_PRECISION_HIGH\n" + | |
" precision highp float;\n" + | |
"#else\n" + | |
" precision mediump float;\n" + | |
"#endif\n" + | |
"#endif\n"; | |
// Given a string of GLSL source |source| of type |type|, create the shader | |
// and compile |source| to the shader. Throws on error. Returns the newly | |
// created WebGLShader. Automatically compiles GL_ES default precision | |
// qualifiers before a fragment source. | |
function webGLcreateAndCompilerShader(gl, source, type) { | |
var shader = gl.createShader(type); | |
// NOTE(deanm): We're not currently running on ES, so we don't need this. | |
// if (type === gl.FRAGMENT_SHADER) source = kFragmentShaderPrefix + source; | |
gl.shaderSource(shader, source); | |
gl.compileShader(shader); | |
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) !== true) | |
throw gl.getShaderInfoLog(shader); | |
return shader; | |
} | |
// Given the source text of the vertex shader |vsource| and fragment shader | |
// |fsource|, create a new program with the shaders together. Throws on | |
// error. Returns the newly created WebGLProgram. Does not call useProgram. | |
// Automatically compiles GL_ES default precision qualifiers before a | |
// fragment source. | |
function webGLcreateProgramFromShaderSources(gl, vsource, fsource) { | |
var vshader = webGLcreateAndCompilerShader(gl, vsource, gl.VERTEX_SHADER); | |
var fshader = webGLcreateAndCompilerShader(gl, fsource, gl.FRAGMENT_SHADER); | |
var program = gl.createProgram(); | |
gl.attachShader(program, vshader); | |
gl.attachShader(program, fshader); | |
gl.linkProgram(program); | |
if (gl.getProgramParameter(program, gl.LINK_STATUS) !== true) | |
throw gl.getProgramInfoLog(program); | |
return program; | |
} | |
function MagicProgram(gl, program) { | |
this.gl = gl; | |
this.program = program; | |
this.use = function() { | |
gl.useProgram(program); | |
}; | |
function makeSetter(type, loc) { | |
switch (type) { | |
case gl.BOOL: // NOTE: bool could be set with 1i or 1f. | |
case gl.INT: | |
case gl.SAMPLER_2D: | |
case gl.SAMPLER_CUBE: | |
return function(value) { | |
gl.uniform1i(loc, value); | |
return this; | |
}; | |
case gl.FLOAT: | |
return function(value) { | |
gl.uniform1f(loc, value); | |
return this; | |
}; | |
case gl.FLOAT_VEC2: | |
return function(v) { | |
gl.uniform2f(loc, v.x, v.y); | |
}; | |
case gl.FLOAT_VEC3: | |
return function(v) { | |
gl.uniform3f(loc, v.x, v.y, v.z); | |
}; | |
case gl.FLOAT_VEC4: | |
return function(v) { | |
gl.uniform4f(loc, v.x, v.y, v.z, v.w); | |
}; | |
case gl.FLOAT_MAT3:// noboko | |
return function(mat3) { | |
gl.uniformMatrix3fv(loc, false, mat3.toFloat32Array()); | |
}; | |
case gl.FLOAT_MAT4: | |
return function(mat4) { | |
gl.uniformMatrix4fv(loc, false, mat4.toFloat32Array()); | |
}; | |
default: | |
break; | |
} | |
return function() { | |
throw "MagicProgram doesn't know how to set type: " + type; | |
}; | |
} | |
var num_uniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); | |
for (var i = 0; i < num_uniforms; ++i) { | |
var info = gl.getActiveUniform(program, i); | |
var name = info.name; | |
var loc = gl.getUniformLocation(program, name); | |
this['set_' + name] = makeSetter(info.type, loc); | |
this['location_' + name] = loc; | |
} | |
var num_attribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); | |
for (var i = 0; i < num_attribs; ++i) { | |
var info = gl.getActiveAttrib(program, i); | |
var name = info.name; | |
var loc = gl.getAttribLocation(program, name); | |
this['location_' + name] = loc; | |
} | |
} | |
MagicProgram.createFromStrings = function(gl, vstr, fstr) { | |
return new MagicProgram(gl, webGLcreateProgramFromShaderSources( | |
gl, vstr, fstr)); | |
}; | |
MagicProgram.createFromFiles = function(gl, vfn, ffn) { | |
return MagicProgram.createFromStrings( | |
gl, fs.readFileSync(vfn, 'utf8'), fs.readFileSync(ffn, 'utf8')); | |
}; | |
MagicProgram.createFromBasename = function(gl, directory, base) { | |
return MagicProgram.createFromFiles( | |
gl, | |
path.join(directory, base + '.vshader'), | |
path.join(directory, base + '.fshader')); | |
}; | |
exports.kPI = kPI; | |
exports.kPI2 = kPI2; | |
exports.kPI4 = kPI4; | |
exports.k2PI = k2PI; | |
exports.kRadToDeg = 180/kPI; | |
exports.kDegToRad = kPI/180; | |
exports.min = min; | |
exports.max = max; | |
exports.clamp = clamp; | |
exports.lerp = lerp; | |
exports.smoothstep = smoothstep; | |
exports.Vec3 = Vec3; | |
exports.Vec2 = Vec2; | |
exports.Vec4 = Vec4; | |
exports.Mat3 = Mat3; | |
exports.Mat4 = Mat4; | |
exports.gl = {MagicProgram: MagicProgram}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Plask. | |
// (c) Dean McNamee <[email protected]>, 2010. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to | |
// deal in the Software without restriction, including without limitation the | |
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
// sell copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
// IN THE SOFTWARE. | |
var sys = require('sys'); | |
var fs = require('fs'); | |
var path = require('path'); | |
var events = require('events'); | |
var dgram = require('dgram'); | |
//var NodObjC = require('NodObjC'); | |
var inherits = sys.inherits; | |
exports.SkPath = PlaskRawMac.SkPath; | |
exports.SkPaint = PlaskRawMac.SkPaint; | |
exports.SkCanvas = PlaskRawMac.SkCanvas; | |
// NOTE(deanm): The SkCanvas constructor has become too complicated in | |
// supporting different types of canvases and ways to create them. Use one of | |
// the following factory functions instead of calling the constructor directly. | |
exports.SkCanvas.createFromImage = function(path) { | |
return new exports.SkCanvas('^IMG', path); | |
}; | |
exports.SkCanvas.createFromImageData = function(data) { | |
return new exports.SkCanvas('^IMG', data); | |
}; | |
exports.SkCanvas.create = function(width, height) { | |
return new exports.SkCanvas(width, height); | |
}; | |
exports.SkCanvas.createNSWindowBacked = function(nswindow) { | |
return new exports.SkCanvas(nswindow); | |
}; | |
// Sizes are in points, at 72 points per inch, letter would be 612x792. | |
// That makes A4 about 595x842. | |
// TODO(deanm): The sizes are integer, check the right size to use for A4. | |
exports.SkCanvas.createForPDF = function(page_width, page_height, | |
content_width, content_height) { | |
return new exports.SkCanvas( | |
'%PDF', | |
page_width, page_height, | |
content_width === undefined ? page_width : content_width, | |
content_height === undefined ? page_height : content_height); | |
}; | |
var kPI = 3.14159265358979323846264338327950288; | |
var kPI2 = 1.57079632679489661923132169163975144; | |
var kPI4 = 0.785398163397448309615660845819875721; | |
var k2PI = 6.28318530717958647692528676655900576; | |
function min(a, b) { | |
if (a < b) return a; | |
return b; | |
} | |
function max(a, b) { | |
if (a > b) return a; | |
return b; | |
} | |
// Keep the value |v| in the range vmin .. vmax. This matches GLSL clamp(). | |
function clamp(v, vmin, vmax) { | |
return min(vmax, max(vmin, v)); | |
} | |
// Linear interpolation on the line along points (0, |a|) and (1, |b|). The | |
// position |t| is the x coordinate, where 0 is |a| and 1 is |b|. | |
function lerp(a, b, t) { | |
return a + (b-a)*t; | |
} | |
// Test if |num| is a floating point -0. | |
function isNegZero(num) { | |
return 1/num === -Infinity; | |
} | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib1fv = function(idx, seq) { | |
this.vertexAttrib1f(idx, seq[0]); | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib2fv = function(idx, seq) { | |
this.vertexAttrib2f(idx, seq[0], seq[1]); | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib3fv = function(idx, seq) { | |
this.vertexAttrib3f(idx, seq[0], seq[1], seq[2]); | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.vertexAttrib4fv = function(idx, seq) { | |
this.vertexAttrib4f(idx, seq[0], seq[1], seq[2], seq[3]); | |
}; | |
var flipper_paint = new exports.SkPaint; | |
flipper_paint.setXfermodeMode(flipper_paint.kSrcMode); | |
PlaskRawMac.NSOpenGLContext.prototype.texImage2DSkCanvas = function(a, b, c) { | |
var width = c.width, height = c.height; | |
var flipped = exports.SkCanvas.create(width, height); | |
flipped.translate(0, height); | |
flipped.scale(1, -1); | |
flipped.drawCanvas(flipper_paint, c, 0, 0, width, height); | |
var result = this.texImage2DSkCanvasB(a, b, flipped); | |
return result; | |
}; | |
PlaskRawMac.NSOpenGLContext.prototype.texImage2DSkCanvasNoFlip = function() { | |
return this.texImage2DSkCanvasB.apply(this, arguments); | |
}; | |
PlaskRawMac.CAMIDISource.prototype.noteOn = function(chan, note, vel) { | |
return this.sendData([0x90 | (chan & 0xf), note & 0x7f, vel & 0x7f]); | |
}; | |
PlaskRawMac.CAMIDISource.prototype.noteOff = function(chan, note, vel) { | |
return this.sendData([0x80 | (chan & 0xf), note & 0x7f, vel & 0x7f]); | |
}; | |
// Pitch wheel takes a value between -1 .. 1, and will be mapped to 14-bit midi. | |
PlaskRawMac.CAMIDISource.prototype.pitchWheel = function(chan, val) { | |
var bits = clamp((val * 0.5 + 0.5) * 16384, 0, 16383); // Not perfect at +1. | |
return this.sendData([0xe0 | (chan & 0xf), bits & 0x7f, (bits >> 7) & 0x7f]); | |
}; | |
PlaskRawMac.CAMIDISource.prototype.controller = function(chan, con, val) { | |
return this.sendData([0xb0 | (chan & 0xf), con & 0x7f, val & 0x7f]); | |
}; | |
inherits(PlaskRawMac.CAMIDIDestination, events.EventEmitter); | |
PlaskRawMac.CAMIDIDestination.prototype.on = function(evname, callback) { | |
if (this._dgram_initialized !== true) { | |
var path = '/tmp/plask_internal_midi_socket_' + | |
process.pid + '_' + Date.now(); | |
var sock = dgram.createSocket('unix_dgram'); | |
sock.bind(path); | |
var this_ = this; | |
sock.on('message', function(msg, rinfo) { | |
if (msg.length < 1) { | |
console.log('Received zero length midi message.'); | |
return; | |
} | |
if ((msg[0] & 0x80) !== 0x80) { | |
console.log('First MIDI byte not a status byte.'); | |
return; | |
} | |
// NOTE(deanm): We expect MIDI packets are the correct length, for | |
// example 3 bytes for note on and off. Instead of error checking, we'll | |
// get undefined from msg[] if the message is shorter, maybe should | |
// handle this better, but loads of length checking is annoying. | |
switch (msg[0] & 0xf0) { | |
case 0x90: // Note on. | |
this_.emit('noteOn', {type:'noteOn', | |
chan: msg[0] & 0x0f, | |
note: msg[1], | |
vel: msg[2]}); | |
break; | |
case 0x80: // Note off. | |
this_.emit('noteOff', {type:'noteOff', | |
chan: msg[0] & 0x0f, | |
note: msg[1], | |
vel: msg[2]}); | |
break; | |
case 0xb0: // Controller message. | |
this_.emit('controller', {type:'controller', | |
chan: msg[0] & 0x0f, | |
num: msg[1], | |
val: msg[2]}); | |
break; | |
default: | |
console.log('Unhandled MIDI status byte: 0x' + msg[0].toString(16)); | |
break; | |
} | |
}); | |
this.setDgramPath(path); | |
this._dgram_initialized = true; | |
} | |
events.EventEmitter.prototype.on.call(this, evname, callback); | |
}; | |
exports.MidiIn = PlaskRawMac.CAMIDIDestination; | |
exports.MidiOut = PlaskRawMac.CAMIDISource; | |
exports.Window = function(width, height, opts) { | |
var nswindow_ = new PlaskRawMac.NSWindow( | |
opts.type === '3d' ? 1 : 0, | |
width, height, | |
opts.multisample === true, | |
opts.display === undefined ? -1 : opts.display, | |
opts.fullscreen === true); | |
var this_ = this; | |
this.context = nswindow_.context; // Export the 3d context (if it exists). | |
this.width = width; this.height = height; | |
// About mouse buttons. One day it will be important to have consistent | |
// numbering across platforms. We name from base 1: | |
// 1 left | |
// 2 right | |
// 3 middle | |
// ... Others (figure out wheel, etc). | |
function buttonNumberToName(numBaseOne) { | |
switch (numBaseOne) { | |
case 1: return 'left'; | |
case 2: return 'right'; | |
case 3: return 'middle'; | |
default: return 'button' + numBaseOne; | |
} | |
} | |
this.setTitle = function(title) { | |
nswindow_.setTitle(title); | |
} | |
// This is quite noisy on the event loop if you don't need it. | |
//nswindow_.setAcceptsMouseMovedEvents(true); | |
function nsEventNameToEmitName(nsname) { | |
switch (nsname) { | |
case PlaskRawMac.NSEvent.NSLeftMouseUp: return 'leftMouseUp'; | |
case PlaskRawMac.NSEvent.NSLeftMouseDown: return 'leftMouseDown'; | |
case PlaskRawMac.NSEvent.NSRightMouseUp: return 'rightMouseUp'; | |
case PlaskRawMac.NSEvent.NSRightMouseDown: return 'rightMouseDown'; | |
case PlaskRawMac.NSEvent.NSOtherMouseUp: return 'otherMouseUp'; | |
case PlaskRawMac.NSEvent.NSOtherMouseDown: return 'otherMouseDown'; | |
case PlaskRawMac.NSEvent.NSLeftMouseDragged: return 'leftMouseDragged'; | |
case PlaskRawMac.NSEvent.NSRightMouseDragged: return 'rightMouseDragged'; | |
case PlaskRawMac.NSEvent.NSOtherMouseDragged: return 'otherMouseDragged'; | |
case PlaskRawMac.NSEvent.NSKeyUp: return 'keyUp'; | |
case PlaskRawMac.NSEvent.NSKeyDown: return 'keyDown'; | |
case PlaskRawMac.NSEvent.NSScrollWheel: return 'scrollWheel'; | |
case PlaskRawMac.NSEvent.NSTabletPoint: return 'tabletPoint'; | |
case PlaskRawMac.NSEvent.NSTabletProximity: return 'tabletProximity'; | |
default: return ''; | |
} | |
} | |
this.setMouseMovedEnabled = function(enabled) { | |
return nswindow_.setAcceptsMouseMovedEvents(enabled); | |
}; | |
this.setFileDragEnabled = function(enabled) { | |
return nswindow_.setAcceptsFileDrag(enabled); | |
}; | |
this.setFrameTopLeftPoint = function(x, y) { | |
return nswindow_.setFrameTopLeftPoint(x, y); | |
}; | |
this.screenSize = function() { | |
return nswindow_.screenSize(); | |
}; | |
this.hideCursor = function() { | |
return nswindow_.hideCursor(); | |
}; | |
this.showCursor = function() { | |
return nswindow_.showCursor(); | |
}; | |
this.center = function() { | |
nswindow_.center(); | |
}; | |
function handleRawNSEvent(e) { | |
var type = e.type(); | |
if (0) { | |
sys.puts("event: " + type); | |
for (var key in e) { | |
if (e[key] === type) sys.puts(key); | |
} | |
} | |
switch (type) { | |
case PlaskRawMac.NSEvent.NSLeftMouseDown: | |
case PlaskRawMac.NSEvent.NSLeftMouseUp: | |
case PlaskRawMac.NSEvent.NSRightMouseDown: | |
case PlaskRawMac.NSEvent.NSRightMouseUp: | |
case PlaskRawMac.NSEvent.NSOtherMouseDown: | |
case PlaskRawMac.NSEvent.NSOtherMouseUp: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var button = e.buttonNumber() + 1; // We work starting from 1. | |
var type_name = nsEventNameToEmitName(type); | |
// We want to also emit middleMouseUp, etc, but NS buckets it as other. | |
if (button === 3) type_name = type_name.replace('other', 'middle'); | |
var te = { | |
type: type_name, | |
x: loc.x, | |
y: height - loc.y, // Map from button left to top left. | |
buttonNumber: button, | |
buttonName: buttonNumberToName(button), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
// Filter out clicks on the title bar. | |
if (te.y < 0) break; | |
// Emit the specific per-button event. | |
this_.emit(te.type, te); | |
// Emit a generic up / down event for all buttons. | |
this_.emit(te.type.substr(-2) === 'Up' ? 'mouseUp' : 'mouseDown', te); | |
break; | |
case PlaskRawMac.NSEvent.NSLeftMouseDragged: | |
case PlaskRawMac.NSEvent.NSRightMouseDragged: | |
case PlaskRawMac.NSEvent.NSOtherMouseDragged: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var button = e.buttonNumber() + 1; // We work starting from 1. | |
var type_name = nsEventNameToEmitName(type); | |
// We want to also emit middleMouseUp, etc, but NS buckets it as other. | |
if (button === 3) type_name = type_name.replace('other', 'middle'); | |
var te = { | |
type: type_name, | |
x: loc.x, | |
y: height - loc.y, | |
dx: e.deltaX(), | |
dy: e.deltaY(), // Doesn't need flipping since it's in device space. | |
dz: e.deltaZ(), | |
pressure: e.pressure(), | |
buttonNumber: button, | |
buttonName: buttonNumberToName(button), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
// TODO(deanm): This is wrong if the drag started in the content view. | |
if (te.y < 0) break; | |
// Emit the specific per-button event. | |
this_.emit(te.type, te); | |
// Emit a generic up / down event for all buttons. | |
this_.emit('mouseDragged', te); | |
break; | |
case PlaskRawMac.NSEvent.NSTabletPoint: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var te = { | |
type: nsEventNameToEmitName(type), | |
x: loc.x, | |
y: height - loc.y, | |
pressure: e.pressure(), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSTabletProximity: | |
var te = { | |
type: nsEventNameToEmitName(type), | |
entering: e.isEnteringProximity() | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSKeyUp: | |
case PlaskRawMac.NSEvent.NSKeyDown: | |
var mods = e.modifierFlags(); | |
var te = { | |
type: nsEventNameToEmitName(type), | |
str: e.characters(), | |
keyCode: e.keyCode(), // I'll probably regret this. | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSMouseMoved: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var te = { | |
type: 'mouseMoved', | |
x: loc.x, | |
y: height - loc.y, | |
dx: e.deltaX(), | |
dy: e.deltaY(), // Doesn't need flipping since it's in device space. | |
dz: e.deltaZ(), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
case PlaskRawMac.NSEvent.NSScrollWheel: | |
var mods = e.modifierFlags(); | |
var loc = e.locationInWindow(); | |
var te = { | |
type: 'scrollWheel', | |
x: loc.x, | |
y: height - loc.y, | |
dx: e.deltaX(), | |
dy: e.deltaY(), // Doesn't need flipping since it's in device space. | |
dz: e.deltaZ(), | |
capslock: (mods & e.NSAlphaShiftKeyMask) !== 0, | |
shift: (mods & e.NSShiftKeyMask) !== 0, | |
ctrl: (mods & e.NSControlKeyMask) !== 0, | |
option: (mods & e.NSAlternateKeyMask) !== 0, | |
cmd: (mods & e.NSCommandKeyMask) !== 0 | |
}; | |
this_.emit(te.type, te); | |
break; | |
default: | |
break; | |
} | |
} | |
// Handle events coming from the native layer. | |
nswindow_.setEventCallback(function(msgtype, msgdata) { | |
// Since emit is synchronous, we need to catch any exceptions that might | |
// happen during event handlers. | |
try { | |
if (msgtype === 0) { // Cocoa NSEvent. | |
handleRawNSEvent(msgdata); | |
} else if (msgtype === 1) { // File drag. | |
this_.emit('filesDropped', {type: 'filesDropped', | |
paths: msgdata.paths, | |
x: msgdata.x, | |
y: height - msgdata.y}); | |
} | |
} catch(ex) { | |
sys.puts(ex.stack); | |
} | |
}); | |
this.getRelativeMouseState = function() { | |
var res = nswindow_.mouseLocationOutsideOfEventStream(); | |
res.y = height - res.y; // Map from Desktop OSX bottom left to top left. | |
var buttons = PlaskRawMac.NSEvent.pressedMouseButtons(); | |
for (var i = 0; i < 6; ++i) { | |
res[buttonNumberToName(i + 1)] = ((buttons >> i) & 1) === 1; | |
} | |
return res; | |
}; | |
this.makeWindowBackedCanvas = function() { | |
return exports.SkCanvas.createNSWindowBacked(nswindow_); | |
}; | |
this.blit = function() { | |
nswindow_.blit(); | |
}; | |
}; | |
inherits(exports.Window, events.EventEmitter); | |
exports.simpleWindow = function(obj) { | |
// NOTE(deanm): Moving to a settings object to reduce the pollution of the | |
// main simpleWindow object. For now fall back for compat. | |
var settings = obj.settings; | |
if (settings === undefined) { | |
settings = obj; | |
if (obj.width !== undefined) | |
console.log('Warning, using legacy settings, use the settings object.'); | |
} | |
var wintype = (settings.type === '3d' || settings.type === '3d2d') ? '3d' : | |
'2d'; | |
var width = settings.width === undefined ? 400 : settings.width; | |
var height = settings.height === undefined ? 300 : settings.height; | |
var syphon_server = null; | |
// TODO(deanm): Fullscreen. | |
var window_ = new exports.Window( | |
width, height, {type: wintype, | |
multisample: settings.multisample === true, | |
display: settings.display, | |
fullscreen: settings.fullscreen}); | |
if (settings.position !== undefined) { | |
var position_x = settings.position.x; | |
var position_y = settings.position.y; | |
if (position_y < 0 || isNegZero(position_y)) | |
position_y = window_.screenSize().height + position_y; | |
if (position_x < 0 || isNegZero(position_x)) | |
position_x = window_.screenSize().width + position_x; | |
window_.setFrameTopLeftPoint(position_x, position_y); | |
} else if (settings.center === true || | |
(settings.fullscreen !== true && settings.center !== false)) { | |
window_.center(); | |
} | |
var gl_ = window_.context; | |
// obj.window = window_; | |
obj.width = width; | |
obj.height = height; | |
if (settings.title !== undefined) | |
window_.setTitle(settings.title); | |
obj.setTitle = function(title) { window_.setTitle(title); }; | |
if (settings.cursor === false) | |
window_.hideCursor(); | |
obj.getRelativeMouseState = function() { | |
return window_.getRelativeMouseState(); | |
}; | |
var canvas = null; // Protected from getting clobbered on obj. | |
if (wintype === '3d') { | |
if (settings.vsync === true) | |
gl_.setSwapInterval(1); | |
if (settings.type === '3d') { // Don't expose gl for 3d2d windows. | |
obj.gl = gl_; | |
} else { // Create a canvas and paint for 3d2d windows. | |
obj.paint = new exports.SkPaint; | |
canvas = exports.SkCanvas.create(width, height); // Offscreen. | |
obj.canvas = canvas; | |
} | |
if (obj.syphon_server !== undefined) { | |
syphon_server = gl_.createSyphonServer(obj.syphon_server); | |
} | |
} else { | |
obj.paint = new exports.SkPaint; | |
canvas = window_.makeWindowBackedCanvas(); | |
obj.canvas = canvas; | |
} | |
var framerate_handle = null; | |
obj.framerate = function(fps) { | |
if (framerate_handle !== null) | |
clearInterval(framerate_handle); | |
if (fps === 0) return; | |
framerate_handle = setInterval(function() { | |
obj.redraw(); | |
}, 1000 / fps); | |
} | |
// Export listener API so you can "this.on" instead of "this.window.on". | |
obj.on = function(e, listener) { | |
// TODO(deanm): Do this properly | |
if (e === 'mouseMoved') | |
window_.setMouseMovedEnabled(true); | |
if (e === 'filesDropped') | |
window_.setFileDragEnabled(true); | |
var this_ = this; | |
return window_.on(e, function() { | |
return listener.apply(this_, arguments); | |
}); | |
}; | |
obj.removeListener = function(e, listener) { | |
return window_.removeListener(e, listener); | |
}; | |
this.getRelativeMouseState = function() { | |
return window_.getRelativeMouseState(); | |
}; | |
// Call init as early as possible, to give the init routine a chance to | |
// setup anything on the object it might want to do at runtime. | |
if ('init' in obj) { | |
try { | |
obj.init(); | |
} catch (ex) { | |
sys.error('Exception caught in simpleWindow init:\n' + | |
ex + '\n' + ex.stack); | |
} | |
} | |
var draw = null; | |
var framenum = 0; | |
var frame_start_time = Date.now(); | |
if ('draw' in obj) | |
draw = obj.draw; | |
obj.redraw = function() { | |
if (gl_ !== undefined) | |
gl_.makeCurrentContext(); | |
if (draw !== null) { | |
obj.framenum = framenum; | |
obj.frametime = (Date.now() - frame_start_time) / 1000; // Secs. | |
try { | |
obj.draw(); | |
} catch (ex) { | |
sys.error('Exception caught in simpleWindow draw:\n' + | |
ex + '\n' + ex.stack); | |
} | |
framenum++; | |
} | |
if (gl_ !== undefined && canvas !== null) { // 3d2d | |
// Blit to Syphon. | |
if (syphon_server !== null && syphon_server.hasClients() === true) { | |
if (syphon_server.bindToDrawFrameOfSize(width, height) === true) { | |
gl_.drawSkCanvas(canvas); | |
syphon_server.unbindAndPublish(); | |
} else { | |
console.log('Error blitting for Syphon.'); | |
} | |
} | |
// Blit to the screen OpenGL context. | |
gl_.drawSkCanvas(canvas); | |
} | |
window_.blit(); // Update the screen automatically. | |
}; | |
obj.redraw(); // Draw the first frame. | |
return obj; | |
}; | |
// A class representing a 3 dimensional point and/or vector. There isn't a | |
// good reason to differentiate between the two, and you often want to change | |
// how you think about the same set of values. So there is only "vector". | |
// | |
// The class is designed without accessors or individual mutators, you should | |
// access the x, y, and z values directly on the object. | |
// | |
// Almost all of the core operations happen in place, writing to the current | |
// object. If you want a copy, you can call dup(). For convenience, many | |
// operations have a passed-tense version that returns a new object. Most | |
// methods return this to support chaining. | |
function Vec3(x, y, z) { | |
this.x = x; this.y = y; this.z = z; | |
} | |
Vec3.prototype.set = function(x, y, z) { | |
this.x = x; this.y = y; this.z = z; | |
return this; | |
}; | |
Vec3.prototype.setVec3 = function(v) { | |
this.x = v.x; this.y = v.y; this.z = v.z; | |
return this; | |
}; | |
// Cross product, this = a x b. | |
Vec3.prototype.cross2 = function(a, b) { | |
var ax = a.x, ay = a.y, az = a.z, | |
bx = b.x, by = b.y, bz = b.z; | |
this.x = ay * bz - az * by; | |
this.y = az * bx - ax * bz; | |
this.z = ax * by - ay * bx; | |
return this; | |
}; | |
// Cross product, this = this x b. | |
Vec3.prototype.cross = function(b) { | |
return this.cross2(this, b); | |
}; | |
// Returns the dot product, this . b. | |
Vec3.prototype.dot = function(b) { | |
return this.x * b.x + this.y * b.y + this.z * b.z; | |
}; | |
// Add two Vec3s, this = a + b. | |
Vec3.prototype.add2 = function(a, b) { | |
this.x = a.x + b.x; | |
this.y = a.y + b.y; | |
this.z = a.z + b.z; | |
return this; | |
}; | |
// Add a Vec3, this = this + b. | |
Vec3.prototype.add = function(b) { | |
return this.add2(this, b); | |
}; | |
Vec3.prototype.added = function(b) { | |
return new Vec3(this.x + b.x, | |
this.y + b.y, | |
this.z + b.z); | |
}; | |
// Subtract two Vec3s, this = a - b. | |
Vec3.prototype.sub2 = function(a, b) { | |
this.x = a.x - b.x; | |
this.y = a.y - b.y; | |
this.z = a.z - b.z; | |
return this; | |
}; | |
// Subtract another Vec3, this = this - b. | |
Vec3.prototype.sub = function(b) { | |
return this.sub2(this, b); | |
}; | |
Vec3.prototype.subbed = function(b) { | |
return new Vec3(this.x - b.x, | |
this.y - b.y, | |
this.z - b.z); | |
}; | |
// Multiply two Vec3s, this = a * b. | |
Vec3.prototype.mul2 = function(a, b) { | |
this.x = a.x * b.x; | |
this.y = a.y * b.y; | |
this.z = a.z * b.z; | |
return this; | |
}; | |
// Multiply by another Vec3, this = this * b. | |
Vec3.prototype.mul = function(b) { | |
return this.mul2(this, b); | |
}; | |
Vec3.prototype.mulled = function(b) { | |
return new Vec3(this.x * b.x, | |
this.y * b.y, | |
this.z * b.z); | |
}; | |
// Multiply by a scalar. | |
Vec3.prototype.scale = function(s) { | |
this.x *= s; this.y *= s; this.z *= s; | |
return this; | |
}; | |
Vec3.prototype.scaled = function(s) { | |
return new Vec3(this.x * s, this.y * s, this.z * s); | |
}; | |
// Interpolate between this and another Vec3 |b|, based on |t|. | |
Vec3.prototype.lerp = function(b, t) { | |
this.x = this.x + (b.x-this.x)*t; | |
this.y = this.y + (b.y-this.y)*t; | |
this.z = this.z + (b.z-this.z)*t; | |
return this; | |
}; | |
Vec3.prototype.lerped = function(b, t) { | |
return new Vec3(this.x + (b.x-this.x)*t, | |
this.y + (b.y-this.y)*t, | |
this.z + (b.z-this.z)*t); | |
}; | |
// Magnitude (length). | |
Vec3.prototype.length = function() { | |
var x = this.x, y = this.y, z = this.z; | |
return Math.sqrt(x*x + y*y + z*z); | |
}; | |
// Magnitude squared. | |
Vec3.prototype.lengthSquared = function() { | |
var x = this.x, y = this.y, z = this.z; | |
return x*x + y*y + z*z; | |
}; | |
// Distance to Vec3 |b|. | |
Vec3.prototype.dist = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
var z = b.z - this.z; | |
return Math.sqrt(x*x + y*y + z*z); | |
}; | |
// Squared Distance to Vec3 |b|. | |
Vec3.prototype.distSquared = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
var z = b.z - this.z; | |
return x*x + y*y + z*z; | |
}; | |
// Normalize, scaling so the magnitude is 1. Invalid for a zero vector. | |
Vec3.prototype.normalize = function() { | |
return this.scale(1/this.length()); | |
}; | |
Vec3.prototype.normalized = function() { | |
return this.dup().normalize(); | |
}; | |
Vec3.prototype.dup = function() { | |
return new Vec3(this.x, this.y, this.z); | |
}; | |
Vec3.prototype.debugString = function() { | |
return 'x: ' + this.x + ' y: ' + this.y + ' z: ' + this.z; | |
}; | |
// Like a z-less Vec3, Vec2. | |
function Vec2(x, y) { | |
this.x = x; this.y = y; | |
} | |
Vec2.prototype.set = function(x, y) { | |
this.x = x; this.y = y | |
return this; | |
}; | |
Vec2.prototype.setVec2 = function(v) { | |
this.x = v.x; this.y = v.y; | |
return this; | |
}; | |
// Returns the dot product, this . b. | |
Vec2.prototype.dot = function(b) { | |
return this.x * b.x + this.y * b.y; | |
}; | |
// Add two Vec2s, this = a + b. | |
Vec2.prototype.add2 = function(a, b) { | |
this.x = a.x + b.x; | |
this.y = a.y + b.y; | |
return this; | |
}; | |
// Add a Vec2, this = this + b. | |
Vec2.prototype.add = function(b) { | |
return this.add2(this, b); | |
}; | |
Vec2.prototype.added = function(b) { | |
return new Vec2(this.x + b.x, | |
this.y + b.y); | |
}; | |
// Subtract two Vec2s, this = a - b. | |
Vec2.prototype.sub2 = function(a, b) { | |
this.x = a.x - b.x; | |
this.y = a.y - b.y; | |
return this; | |
}; | |
// Subtract another Vec2, this = this - b. | |
Vec2.prototype.sub = function(b) { | |
return this.sub2(this, b); | |
}; | |
Vec2.prototype.subbed = function(b) { | |
return new Vec2(this.x - b.x, | |
this.y - b.y); | |
}; | |
// Multiply two Vec2s, this = a * b. | |
Vec2.prototype.mul2 = function(a, b) { | |
this.x = a.x * b.x; | |
this.y = a.y * b.y; | |
return this; | |
}; | |
// Multiply by another Vec2, this = this * b. | |
Vec2.prototype.mul = function(b) { | |
return this.mul2(this, b); | |
}; | |
Vec2.prototype.mulled = function(b) { | |
return new Vec2(this.x * b.x, | |
this.y * b.y); | |
}; | |
// Multiply by a scalar. | |
Vec2.prototype.scale = function(s) { | |
this.x *= s; this.y *= s; | |
return this; | |
}; | |
Vec2.prototype.scaled = function(s) { | |
return new Vec2(this.x * s, this.y * s); | |
}; | |
// Interpolate between this and another Vec2 |b|, based on |t|. | |
Vec2.prototype.lerp = function(b, t) { | |
this.x = this.x + (b.x-this.x)*t; | |
this.y = this.y + (b.y-this.y)*t; | |
return this; | |
}; | |
Vec2.prototype.lerped = function(b, t) { | |
return new Vec2(this.x + (b.x-this.x)*t, | |
this.y + (b.y-this.y)*t); | |
}; | |
// Magnitude (length). | |
Vec2.prototype.length = function() { | |
var x = this.x, y = this.y; | |
return Math.sqrt(x*x + y*y); | |
}; | |
// Magnitude squared. | |
Vec2.prototype.lengthSquared = function() { | |
var x = this.x, y = this.y; | |
return x*x + y*y; | |
}; | |
// Distance to Vec2 |b|. | |
Vec2.prototype.dist = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
return Math.sqrt(x*x + y*y); | |
}; | |
// Squared Distance to Vec2 |b|. | |
Vec2.prototype.distSquared = function(b) { | |
var x = b.x - this.x; | |
var y = b.y - this.y; | |
return x*x + y*y; | |
}; | |
// Normalize, scaling so the magnitude is 1. Invalid for a zero vector. | |
Vec2.prototype.normalize = function() { | |
return this.scale(1/this.length()); | |
}; | |
Vec2.prototype.normalized = function() { | |
return this.dup().normalize(); | |
}; | |
// Rotate around the origin by |theta| radians. | |
Vec2.prototype.rotate = function(theta) { | |
var st = Math.sin(theta); | |
var ct = Math.cos(theta); | |
var x = this.x, y = this.y; | |
this.x = x * ct - y * st; | |
this.y = x * st + y * ct; | |
return this; | |
}; | |
Vec2.prototype.rotated = function(theta) { | |
return this.dup().rotate(theta); | |
}; | |
// Reflect a vector about the normal |n|. The vectors should both be unit. | |
Vec2.prototype.reflect = function(n) { | |
// r = u - 2(u.n)n | |
// This could could basically be: | |
// this.sub(n.scaled(this.dot(n) * 2)); | |
// But we avoid some extra object allocated / etc and just flatten it. | |
var s = this.dot(n) * 2; | |
this.x -= n.x * s; | |
this.y -= n.y * s; | |
return this; | |
}; | |
Vec2.prototype.reflected = function(n) { | |
var s = this.dot(n) * 2; | |
return Vec2(this.x - n.x * s, | |
this.y - n.y * s); | |
}; | |
Vec2.prototype.dup = function() { | |
return new Vec2(this.x, this.y); | |
}; | |
Vec2.prototype.debugString = function() { | |
return 'x: ' + this.x + ' y: ' + this.y; | |
}; | |
// TODO(deanm): Vec4 is currently a skeleton container, it should match the | |
// features of Vec3. | |
function Vec4(x, y, z, w) { | |
this.x = x; this.y = y; this.z = z; this.w = w; | |
} | |
Vec4.prototype.set = function(x, y, z, w) { | |
this.x = x; this.y = y; this.z = z; this.w = w; | |
return this; | |
}; | |
Vec4.prototype.setVec4 = function(v) { | |
this.x = v.x; this.y = v.y; this.z = v.z; this.w = v.w; | |
return this; | |
}; | |
Vec4.prototype.dup = function() { | |
return new Vec4(this.x, this.y, this.z, this.w); | |
}; | |
Vec4.prototype.toVec3 = function() { | |
return new Vec3(this.x, this.y, this.z); | |
}; | |
// noboko ////////////////////////////////////////////// | |
// 4x4 matrix | |
// a11 a12 a13 | |
// a21 a22 a23 | |
// a31 a32 a33 | |
// | |
// | |
//////////////////////////////////////////////////////// | |
function Mat3() { | |
this.reset(); | |
} | |
// Set the full 9 elements of the 3x3 matrix, arguments in row major order. | |
// The elements are specified in row major order. | |
Mat3.prototype.set3x3r = function(a11, a12, a13, a21, a22, a23,a31, a32, a33) { | |
this.a11 = a11; this.a12 = a12; this.a13 = a13; | |
this.a21 = a21; this.a22 = a22; this.a23 = a23; | |
this.a31 = a31; this.a32 = a32; this.a33 = a33; | |
return this; | |
}; | |
// Reset the transform to the identity matrix. | |
Mat3.prototype.reset = function() { | |
this.set3x3r(1, 0, 0, | |
0, 1, 0, | |
0, 0, 1 ); | |
return this; | |
}; | |
// Transpose the matrix, rows become columns and columns become rows. | |
Mat3.prototype.transpose = function() { | |
var a11 = this.a11, a12 = this.a12, a13 = this.a13, | |
a21 = this.a21, a22 = this.a22, a23 = this.a23, | |
a31 = this.a31, a32 = this.a32, a33 = this.a33; | |
this.a11 = a11; this.a12 = a21; this.a13 = a31; | |
this.a21 = a12; this.a22 = a22; this.a23 = a32; | |
this.a31 = a13; this.a32 = a23; this.a33 = a33; | |
return this; | |
}; | |
Mat3.prototype.dup = function() { | |
var m = new Mat3(); // TODO(deanm): This could be better. | |
m.set3x3r(this.a11, this.a12, this.a13, | |
this.a21, this.a22, this.a23, | |
this.a31, this.a32, this.a33); | |
return m; | |
}; | |
Mat3.prototype.toFloat32Array = function() { | |
return new Float32Array([this.a11, this.a21, this.a31, | |
this.a12, this.a22, this.a32, | |
this.a13, this.a23, this.a33]); | |
}; | |
Mat3.prototype.toMat4 = function(){ | |
var m = new Mat4; | |
m.set4x4r(this.a11, this.a12, this.a13, 0, | |
this.a21, this.a22, this.a23, 0, | |
this.a31, this.a32, this.a33, 0, | |
0, 0, 0, 1); | |
return m; | |
}; | |
Mat3.prototype.debugString = function() { | |
var s = [this.a11, this.a12, this.a13, | |
this.a21, this.a22, this.a23, | |
this.a31, this.a32, this.a33,]; | |
var row_lengths = [0, 0, 0]; | |
for (var i = 0; i < 9; ++i) { | |
s[i] += ''; // Stringify. | |
var len = s[i].length; | |
var row = i & 2; | |
if (row_lengths[row] < len) | |
row_lengths[row] = len; | |
} | |
var out = ''; | |
for (var i = 0; i < 9; ++i) { | |
var len = s[i].length; | |
var row_len = row_lengths[i & 2]; | |
var num_spaces = row_len - len; | |
while (num_spaces--) out += ' '; | |
out += s[i] + ((i & 2) === 2 ? '\n' : ' '); | |
} | |
return out; | |
}; | |
////////////////////////////////////// | |
// This represents an affine 4x4 matrix, using mathematical notation, | |
// numbered (starting from 1) as aij, where i is the row and j is the column. | |
// a11 a12 a13 a14 | |
// a21 a22 a23 a24 | |
// a31 a32 a33 a34 | |
// a41 a42 a43 a44 | |
// | |
// Almost all operations are multiplies to the current matrix, and happen | |
// in place. You can use dup() to return a copy. | |
// | |
// Most operations return this to support chaining. | |
// | |
// It's common to use toFloat32Array to get a Float32Array in OpenGL (column | |
// major) memory ordering. NOTE: The code tries to be explicit about whether | |
// things are row major or column major, but remember that GLSL works in | |
// column major ordering, and PreGL generally uses row major ordering. | |
function Mat4() { | |
this.reset(); | |
} | |
// Set the full 16 elements of the 4x4 matrix, arguments in row major order. | |
// The elements are specified in row major order. TODO(deanm): set4x4c. | |
Mat4.prototype.set4x4r = function(a11, a12, a13, a14, a21, a22, a23, a24, | |
a31, a32, a33, a34, a41, a42, a43, a44) { | |
this.a11 = a11; this.a12 = a12; this.a13 = a13; this.a14 = a14; | |
this.a21 = a21; this.a22 = a22; this.a23 = a23; this.a24 = a24; | |
this.a31 = a31; this.a32 = a32; this.a33 = a33; this.a34 = a34; | |
this.a41 = a41; this.a42 = a42; this.a43 = a43; this.a44 = a44; | |
return this; | |
}; | |
// Reset the transform to the identity matrix. | |
Mat4.prototype.reset = function() { | |
this.set4x4r(1, 0, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Matrix multiply this = a * b | |
Mat4.prototype.mul2 = function(a, b) { | |
var a11 = a.a11, a12 = a.a12, a13 = a.a13, a14 = a.a14, | |
a21 = a.a21, a22 = a.a22, a23 = a.a23, a24 = a.a24, | |
a31 = a.a31, a32 = a.a32, a33 = a.a33, a34 = a.a34, | |
a41 = a.a41, a42 = a.a42, a43 = a.a43, a44 = a.a44; | |
var b11 = b.a11, b12 = b.a12, b13 = b.a13, b14 = b.a14, | |
b21 = b.a21, b22 = b.a22, b23 = b.a23, b24 = b.a24, | |
b31 = b.a31, b32 = b.a32, b33 = b.a33, b34 = b.a34, | |
b41 = b.a41, b42 = b.a42, b43 = b.a43, b44 = b.a44; | |
this.a11 = a11*b11 + a12*b21 + a13*b31 + a14*b41; | |
this.a12 = a11*b12 + a12*b22 + a13*b32 + a14*b42; | |
this.a13 = a11*b13 + a12*b23 + a13*b33 + a14*b43; | |
this.a14 = a11*b14 + a12*b24 + a13*b34 + a14*b44; | |
this.a21 = a21*b11 + a22*b21 + a23*b31 + a24*b41; | |
this.a22 = a21*b12 + a22*b22 + a23*b32 + a24*b42; | |
this.a23 = a21*b13 + a22*b23 + a23*b33 + a24*b43; | |
this.a24 = a21*b14 + a22*b24 + a23*b34 + a24*b44; | |
this.a31 = a31*b11 + a32*b21 + a33*b31 + a34*b41; | |
this.a32 = a31*b12 + a32*b22 + a33*b32 + a34*b42; | |
this.a33 = a31*b13 + a32*b23 + a33*b33 + a34*b43; | |
this.a34 = a31*b14 + a32*b24 + a33*b34 + a34*b44; | |
this.a41 = a41*b11 + a42*b21 + a43*b31 + a44*b41; | |
this.a42 = a41*b12 + a42*b22 + a43*b32 + a44*b42; | |
this.a43 = a41*b13 + a42*b23 + a43*b33 + a44*b43; | |
this.a44 = a41*b14 + a42*b24 + a43*b34 + a44*b44; | |
return this; | |
}; | |
// Matrix multiply this = this * b | |
Mat4.prototype.mul = function(b) { | |
return this.mul2(this, b); | |
}; | |
// Multiply the current matrix by 16 elements that would compose a Mat4 | |
// object, but saving on creating the object. this = this * b. | |
// The elements are specific in row major order. TODO(deanm): mul4x4c. | |
// TODO(deanm): It's a shame to duplicate the multiplication code. | |
Mat4.prototype.mul4x4r = function(b11, b12, b13, b14, b21, b22, b23, b24, | |
b31, b32, b33, b34, b41, b42, b43, b44) { | |
var a11 = this.a11, a12 = this.a12, a13 = this.a13, a14 = this.a14, | |
a21 = this.a21, a22 = this.a22, a23 = this.a23, a24 = this.a24, | |
a31 = this.a31, a32 = this.a32, a33 = this.a33, a34 = this.a34, | |
a41 = this.a41, a42 = this.a42, a43 = this.a43, a44 = this.a44; | |
this.a11 = a11*b11 + a12*b21 + a13*b31 + a14*b41; | |
this.a12 = a11*b12 + a12*b22 + a13*b32 + a14*b42; | |
this.a13 = a11*b13 + a12*b23 + a13*b33 + a14*b43; | |
this.a14 = a11*b14 + a12*b24 + a13*b34 + a14*b44; | |
this.a21 = a21*b11 + a22*b21 + a23*b31 + a24*b41; | |
this.a22 = a21*b12 + a22*b22 + a23*b32 + a24*b42; | |
this.a23 = a21*b13 + a22*b23 + a23*b33 + a24*b43; | |
this.a24 = a21*b14 + a22*b24 + a23*b34 + a24*b44; | |
this.a31 = a31*b11 + a32*b21 + a33*b31 + a34*b41; | |
this.a32 = a31*b12 + a32*b22 + a33*b32 + a34*b42; | |
this.a33 = a31*b13 + a32*b23 + a33*b33 + a34*b43; | |
this.a34 = a31*b14 + a32*b24 + a33*b34 + a34*b44; | |
this.a41 = a41*b11 + a42*b21 + a43*b31 + a44*b41; | |
this.a42 = a41*b12 + a42*b22 + a43*b32 + a44*b42; | |
this.a43 = a41*b13 + a42*b23 + a43*b33 + a44*b43; | |
this.a44 = a41*b14 + a42*b24 + a43*b34 + a44*b44; | |
return this; | |
}; | |
// TODO(deanm): Some sort of mat3x3. There are two ways you could do it | |
// though, just multiplying the 3x3 portions of the 4x4 matrix, or doing a | |
// 4x4 multiply with the last row/column implied to be 0, 0, 0, 1. This | |
// keeps true to the original matrix even if it's last row is not 0, 0, 0, 1. | |
// IN RADIANS, not in degrees like OpenGL. Rotate about x, y, z. | |
// The caller must supply a x, y, z as a unit vector. | |
Mat4.prototype.rotate = function(theta, x, y, z) { | |
// http://www.cs.rutgers.edu/~decarlo/428/gl_man/rotate.html | |
var s = Math.sin(theta); | |
var c = Math.cos(theta); | |
this.mul4x4r( | |
x*x*(1-c)+c, x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0, | |
y*x*(1-c)+z*s, y*y*(1-c)+c, y*z*(1-c)-x*s, 0, | |
x*z*(1-c)-y*s, y*z*(1-c)+x*s, z*z*(1-c)+c, 0, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Multiply by a translation of x, y, and z. | |
Mat4.prototype.translate = function(dx, dy, dz) { | |
// TODO(deanm): Special case the multiply since most goes unchanged. | |
this.mul4x4r(1, 0, 0, dx, | |
0, 1, 0, dy, | |
0, 0, 1, dz, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Multiply by a scale of x, y, and z. | |
Mat4.prototype.scale = function(sx, sy, sz) { | |
// TODO(deanm): Special case the multiply since most goes unchanged. | |
this.mul4x4r(sx, 0, 0, 0, | |
0, sy, 0, 0, | |
0, 0, sz, 0, | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Multiply by a look at matrix, computed from the eye, center, and up points. | |
Mat4.prototype.lookAt = function(ex, ey, ez, cx, cy, cz, ux, uy, uz) { | |
var z = (new Vec3(ex - cx, ey - cy, ez - cz)).normalize(); | |
var x = (new Vec3(ux, uy, uz)).cross(z).normalize(); | |
var y = z.dup().cross(x).normalize(); | |
// The new axis basis is formed as row vectors since we are transforming | |
// the coordinate system (alias not alibi). | |
this.mul4x4r(x.x, x.y, x.z, 0, | |
y.x, y.y, y.z, 0, | |
z.x, z.y, z.z, 0, | |
0, 0, 0, 1); | |
this.translate(-ex, -ey, -ez); | |
return this; | |
}; | |
// Multiply by a frustum matrix computed from left, right, bottom, top, | |
// near, and far. | |
Mat4.prototype.frustum = function(l, r, b, t, n, f) { | |
this.mul4x4r( | |
(n+n)/(r-l), 0, (r+l)/(r-l), 0, | |
0, (n+n)/(t-b), (t+b)/(t-b), 0, | |
0, 0, (f+n)/(n-f), (2*f*n)/(n-f), | |
0, 0, -1, 0); | |
return this; | |
}; | |
// Multiply by a perspective matrix, computed from the field of view, aspect | |
// ratio, and the z near and far planes. | |
Mat4.prototype.perspective = function(fovy, aspect, znear, zfar) { | |
// This could also be done reusing the frustum calculation: | |
// var ymax = znear * Math.tan(fovy * kPI / 360.0); | |
// var ymin = -ymax; | |
// | |
// var xmin = ymin * aspect; | |
// var xmax = ymax * aspect; | |
// | |
// return makeFrustumAffine(xmin, xmax, ymin, ymax, znear, zfar); | |
var f = 1.0 / Math.tan(fovy * kPI / 360.0); | |
this.mul4x4r( | |
f/aspect, 0, 0, 0, | |
0, f, 0, 0, | |
0, 0, (zfar+znear)/(znear-zfar), 2*znear*zfar/(znear-zfar), | |
0, 0, -1, 0); | |
return this; | |
}; | |
// Multiply by a orthographic matrix, computed from the clipping planes. | |
Mat4.prototype.ortho = function(l, r, b, t, n, f) { | |
this.mul4x4r(2/(r-l), 0, 0, (r+l)/(l-r), | |
0, 2/(t-b), 0, (t+b)/(b-t), | |
0, 0, 2/(n-f), (f+n)/(n-f), | |
0, 0, 0, 1); | |
return this; | |
}; | |
// Invert the matrix. The matrix must be invertible. | |
Mat4.prototype.invert = function() { | |
// Based on the math at: | |
// http://www.geometrictools.com/LibMathematics/Algebra/Wm5Matrix4.inl | |
var x0 = this.a11, x1 = this.a12, x2 = this.a13, x3 = this.a14, | |
x4 = this.a21, x5 = this.a22, x6 = this.a23, x7 = this.a24, | |
x8 = this.a31, x9 = this.a32, x10 = this.a33, x11 = this.a34, | |
x12 = this.a41, x13 = this.a42, x14 = this.a43, x15 = this.a44; | |
var a0 = x0*x5 - x1*x4, | |
a1 = x0*x6 - x2*x4, | |
a2 = x0*x7 - x3*x4, | |
a3 = x1*x6 - x2*x5, | |
a4 = x1*x7 - x3*x5, | |
a5 = x2*x7 - x3*x6, | |
b0 = x8*x13 - x9*x12, | |
b1 = x8*x14 - x10*x12, | |
b2 = x8*x15 - x11*x12, | |
b3 = x9*x14 - x10*x13, | |
b4 = x9*x15 - x11*x13, | |
b5 = x10*x15 - x11*x14; | |
// TODO(deanm): These terms aren't reused, so get rid of the temporaries. | |
var invdet = 1 / (a0*b5 - a1*b4 + a2*b3 + a3*b2 - a4*b1 + a5*b0); | |
this.a11 = (+ x5*b5 - x6*b4 + x7*b3) * invdet; | |
this.a12 = (- x1*b5 + x2*b4 - x3*b3) * invdet; | |
this.a13 = (+ x13*a5 - x14*a4 + x15*a3) * invdet; | |
this.a14 = (- x9*a5 + x10*a4 - x11*a3) * invdet; | |
this.a21 = (- x4*b5 + x6*b2 - x7*b1) * invdet; | |
this.a22 = (+ x0*b5 - x2*b2 + x3*b1) * invdet; | |
this.a23 = (- x12*a5 + x14*a2 - x15*a1) * invdet; | |
this.a24 = (+ x8*a5 - x10*a2 + x11*a1) * invdet; | |
this.a31 = (+ x4*b4 - x5*b2 + x7*b0) * invdet; | |
this.a32 = (- x0*b4 + x1*b2 - x3*b0) * invdet; | |
this.a33 = (+ x12*a4 - x13*a2 + x15*a0) * invdet; | |
this.a34 = (- x8*a4 + x9*a2 - x11*a0) * invdet; | |
this.a41 = (- x4*b3 + x5*b1 - x6*b0) * invdet; | |
this.a42 = (+ x0*b3 - x1*b1 + x2*b0) * invdet; | |
this.a43 = (- x12*a3 + x13*a1 - x14*a0) * invdet; | |
this.a44 = (+ x8*a3 - x9*a1 + x10*a0) * invdet; | |
return this; | |
}; | |
////////////////////noboko///////////////////////////////////////// | |
////toMat3 | |
Mat4.prototype.toMat3 = function(){ | |
var m = new Mat3; | |
m.set3x3r(this.a11, this.a12, this.a13, | |
this.a21, this.a22, this.a23, | |
this.a31, this.a32, this.a33); | |
return m; | |
}; | |
////toInverseMat3 | |
Mat4.prototype.toInverseMat3 = function () { | |
var m = new Mat3; | |
var x11 = this.a11, x12 = this.a12, x13 = this.a13, | |
x21 = this.a21, x22 = this.a22, x23 = this.a23, | |
x31 = this.a31, x32 = this.a32, x33 = this.a33; | |
var b11 = x22 * x33 - x23 * x32, | |
b12 = x32 * x13 - x33 * x12, | |
b13 = x12 * x23 - x13 * x22, | |
b21 = x23 * x31 - x21 * x33, | |
b22 = x33 * x11 - x31 * x13, | |
b23 = x13 * x21 - x11 * x23, | |
b31 = x21 * x32 - x22 * x31, | |
b32 = x31 * x12 - x32 * x11, | |
b33 = x11 * x22 - x12 * x21; | |
var det = x11 * b11 + x21 * b12 + x31 * b13; | |
var invdet; | |
if (!det){ | |
return console.log('det is zero'); | |
} | |
else{ | |
invdet = 1 / det; | |
m.set3x3r(b11 * invdet, | |
b12 * invdet, | |
b13 * invdet, | |
b21 * invdet, | |
b22 * invdet, | |
b23 * invdet, | |
b31 * invdet, | |
b32 * invdet, | |
b33 * invdet); | |
return m; | |
} | |
}; | |
////////////////////////////////////////////////////////////////////// | |
// Transpose the matrix, rows become columns and columns become rows. | |
Mat4.prototype.transpose = function() { | |
var a11 = this.a11, a12 = this.a12, a13 = this.a13, a14 = this.a14, | |
a21 = this.a21, a22 = this.a22, a23 = this.a23, a24 = this.a24, | |
a31 = this.a31, a32 = this.a32, a33 = this.a33, a34 = this.a34, | |
a41 = this.a41, a42 = this.a42, a43 = this.a43, a44 = this.a44; | |
this.a11 = a11; this.a12 = a21; this.a13 = a31; this.a14 = a41; | |
this.a21 = a12; this.a22 = a22; this.a23 = a32; this.a24 = a42; | |
this.a31 = a13; this.a32 = a23; this.a33 = a33; this.a34 = a43; | |
this.a41 = a14; this.a42 = a24; this.a43 = a34; this.a44 = a44; | |
return this; | |
}; | |
// Multiply Vec3 |v| by the current matrix, returning a Vec3 of this * v. | |
Mat4.prototype.mulVec3 = function(v) { | |
var x = v.x, y = v.y, z = v.z; | |
return new Vec3(this.a14 + this.a11*x + this.a12*y + this.a13*z, | |
this.a24 + this.a21*x + this.a22*y + this.a23*z, | |
this.a34 + this.a31*x + this.a32*y + this.a33*z); | |
}; | |
// Multiply Vec4 |v| by the current matrix, returning a Vec4 of this * v. | |
Mat4.prototype.mulVec4 = function(v) { | |
var x = v.x, y = v.y, z = v.z, w = v.w; | |
return new Vec4(this.a14*w + this.a11*x + this.a12*y + this.a13*z, | |
this.a24*w + this.a21*x + this.a22*y + this.a23*z, | |
this.a34*w + this.a31*x + this.a32*y + this.a33*z, | |
this.a44*w + this.a41*x + this.a42*y + this.a43*z); | |
}; | |
Mat4.prototype.dup = function() { | |
var m = new Mat4(); // TODO(deanm): This could be better. | |
m.set4x4r(this.a11, this.a12, this.a13, this.a14, | |
this.a21, this.a22, this.a23, this.a24, | |
this.a31, this.a32, this.a33, this.a34, | |
this.a41, this.a42, this.a43, this.a44); | |
return m; | |
}; | |
Mat4.prototype.toFloat32Array = function() { | |
return new Float32Array([this.a11, this.a21, this.a31, this.a41, | |
this.a12, this.a22, this.a32, this.a42, | |
this.a13, this.a23, this.a33, this.a43, | |
this.a14, this.a24, this.a34, this.a44]); | |
}; | |
Mat4.prototype.debugString = function() { | |
var s = [this.a11, this.a12, this.a13, this.a14, | |
this.a21, this.a22, this.a23, this.a24, | |
this.a31, this.a32, this.a33, this.a34, | |
this.a41, this.a42, this.a43, this.a44]; | |
var row_lengths = [0, 0, 0, 0]; | |
for (var i = 0; i < 16; ++i) { | |
s[i] += ''; // Stringify. | |
var len = s[i].length; | |
var row = i & 3; | |
if (row_lengths[row] < len) | |
row_lengths[row] = len; | |
} | |
var out = ''; | |
for (var i = 0; i < 16; ++i) { | |
var len = s[i].length; | |
var row_len = row_lengths[i & 3]; | |
var num_spaces = row_len - len; | |
while (num_spaces--) out += ' '; | |
out += s[i] + ((i & 3) === 3 ? '\n' : ' '); | |
} | |
return out; | |
}; | |
var kFragmentShaderPrefix = "#ifdef GL_ES\n" + | |
"#ifdef GL_FRAGMENT_PRECISION_HIGH\n" + | |
" precision highp float;\n" + | |
"#else\n" + | |
" precision mediump float;\n" + | |
"#endif\n" + | |
"#endif\n"; | |
// Given a string of GLSL source |source| of type |type|, create the shader | |
// and compile |source| to the shader. Throws on error. Returns the newly | |
// created WebGLShader. Automatically compiles GL_ES default precision | |
// qualifiers before a fragment source. | |
function webGLcreateAndCompilerShader(gl, source, type) { | |
var shader = gl.createShader(type); | |
// NOTE(deanm): We're not currently running on ES, so we don't need this. | |
// if (type === gl.FRAGMENT_SHADER) source = kFragmentShaderPrefix + source; | |
gl.shaderSource(shader, source); | |
gl.compileShader(shader); | |
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) !== true) | |
throw gl.getShaderInfoLog(shader); | |
return shader; | |
} | |
// Given the source text of the vertex shader |vsource| and fragment shader | |
// |fsource|, create a new program with the shaders together. Throws on | |
// error. Returns the newly created WebGLProgram. Does not call useProgram. | |
// Automatically compiles GL_ES default precision qualifiers before a | |
// fragment source. | |
function webGLcreateProgramFromShaderSources(gl, vsource, fsource) { | |
var vshader = webGLcreateAndCompilerShader(gl, vsource, gl.VERTEX_SHADER); | |
var fshader = webGLcreateAndCompilerShader(gl, fsource, gl.FRAGMENT_SHADER); | |
var program = gl.createProgram(); | |
gl.attachShader(program, vshader); | |
gl.attachShader(program, fshader); | |
gl.linkProgram(program); | |
if (gl.getProgramParameter(program, gl.LINK_STATUS) !== true) | |
throw gl.getProgramInfoLog(program); | |
return program; | |
} | |
function MagicProgram(gl, program) { | |
this.gl = gl; | |
this.program = program; | |
this.use = function() { | |
gl.useProgram(program); | |
}; | |
function makeSetter(type, loc) { | |
switch (type) { | |
case gl.BOOL: // NOTE: bool could be set with 1i or 1f. | |
case gl.INT: | |
case gl.SAMPLER_2D: | |
case gl.SAMPLER_CUBE: | |
return function(value) { | |
gl.uniform1i(loc, value); | |
return this; | |
}; | |
case gl.FLOAT: | |
return function(value) { | |
gl.uniform1f(loc, value); | |
return this; | |
}; | |
case gl.FLOAT_VEC2: | |
return function(v) { | |
gl.uniform2f(loc, v.x, v.y); | |
}; | |
case gl.FLOAT_VEC3: | |
return function(v) { | |
gl.uniform3f(loc, v.x, v.y, v.z); | |
}; | |
case gl.FLOAT_VEC4: | |
return function(v) { | |
gl.uniform4f(loc, v.x, v.y, v.z, v.w); | |
}; | |
case gl.FLOAT_MAT3:// noboko | |
return function(mat3) { | |
gl.uniformMatrix3fv(loc, false, mat3.toFloat32Array()); | |
}; | |
case gl.FLOAT_MAT4: | |
return function(mat4) { | |
gl.uniformMatrix4fv(loc, false, mat4.toFloat32Array()); | |
}; | |
default: | |
break; | |
} | |
return function() { | |
throw "MagicProgram doesn't know how to set type: " + type; | |
}; | |
} | |
var num_uniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); | |
for (var i = 0; i < num_uniforms; ++i) { | |
var info = gl.getActiveUniform(program, i); | |
var name = info.name; | |
var loc = gl.getUniformLocation(program, name); | |
this['set_' + name] = makeSetter(info.type, loc); | |
this['location_' + name] = loc; | |
} | |
var num_attribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); | |
for (var i = 0; i < num_attribs; ++i) { | |
var info = gl.getActiveAttrib(program, i); | |
var name = info.name; | |
var loc = gl.getAttribLocation(program, name); | |
this['location_' + name] = loc; | |
} | |
} | |
MagicProgram.createFromFiles = function(gl, vfn, ffn) { | |
return new MagicProgram(gl, webGLcreateProgramFromShaderSources( | |
gl, fs.readFileSync(vfn, 'utf8'), fs.readFileSync(ffn, 'utf8'))); | |
} | |
MagicProgram.createFromBasename = function(gl, directory, base) { | |
return MagicProgram.createFromFiles( | |
gl, | |
path.join(directory, base + '.vshader'), | |
path.join(directory, base + '.fshader')); | |
} | |
exports.kPI = kPI; | |
exports.kPI2 = kPI2; | |
exports.kPI4 = kPI4; | |
exports.k2PI = k2PI; | |
exports.min = min; | |
exports.max = max; | |
exports.clamp = clamp; | |
exports.lerp = lerp; | |
exports.Vec3 = Vec3; | |
exports.Vec2 = Vec2; | |
exports.Vec4 = Vec4; | |
exports.Mat3 = Mat3;//noboko | |
exports.Mat4 = Mat4; | |
exports.gl = {MagicProgram: MagicProgram}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment