Skip to content

Instantly share code, notes, and snippets.

@noboko
Created April 24, 2012 17:07
Show Gist options
  • Save noboko/2481541 to your computer and use it in GitHub Desktop.
Save noboko/2481541 to your computer and use it in GitHub Desktop.
Learning WebGL for Plask Lesson 16
//Learning WebGL for Plask Lesson16
//Learing WebGL : http://learningwebgl.com/blog/?page_id=1217
//Github : https://github.com/gpjt/webgl-lessons (this script use 'earth.jpg' , 'crate.gif' and 'macbook.json')
//
//Plask : http://www.plask.org/
//
//俺俺でMat3とMat4.toInverseMat3()あたりを追記したものを使ってやりました。
//元のplask.jsと区別つけるために同一フォルダから読み込むようにしてます。
//尚、binary版とgithubの最新版で微妙にplask.jsの変更があるみたいなので、
//plask_.js binary版用、plask.js github用としてます。
//
var path = require('path');
var plask = require('./plask_');
var fs = require('fs');
function clone(obj){
var F = function(){};
F.prototype = obj;
return new F;
}
function degToRad(degrees) {
return degrees * Math.PI / 180;
}
var init ={
lastTime : 0,
moonAngle : 180,
cubeAngle : 0,
laptopAngle : 0,
laptopScreenAspectRatio : 1.66
};
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);
// 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, 'perFragmentProgram');
// 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};
}
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);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
var latitudeBands = 30;
var longitudeBands = 30;
var radius = 1;
var vertexPositionData = [];
var normalData = [];
var textureCoordData = [];
for (var latNumber=0; latNumber <= latitudeBands; latNumber++) {
var theta = latNumber * Math.PI / latitudeBands;
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
for (var longNumber=0; longNumber <= longitudeBands; longNumber++) {
var phi = longNumber * 2 * Math.PI / longitudeBands;
var sinPhi = Math.sin(phi);
var cosPhi = Math.cos(phi);
var x = cosPhi * sinTheta;
var y = cosTheta;
var z = sinPhi * sinTheta;
var u = 1 - (longNumber / longitudeBands);
var v = 1 - (latNumber / latitudeBands);
normalData.push(x);
normalData.push(y);
normalData.push(z);
textureCoordData.push(u);
textureCoordData.push(v);
vertexPositionData.push(radius * x);
vertexPositionData.push(radius * y);
vertexPositionData.push(radius * z);
}
}
var indexData = [];
for (var latNumber=0; latNumber < latitudeBands; latNumber++) {
for (var longNumber=0; longNumber < longitudeBands; longNumber++) {
var first = (latNumber * (longitudeBands + 1)) + longNumber;
var second = first + longitudeBands + 1;
indexData.push(first);
indexData.push(second);
indexData.push(first + 1);
indexData.push(second);
indexData.push(second + 1);
indexData.push(first + 1);
}
}
this.moonBuffer = {
position : makeBuffer(vertexPositionData,3),
normal : makeBuffer(normalData,3),
index : makeEABuffer(indexData,1),
coord : makeBuffer(textureCoordData,2),
texture : careateTextureWithFile('moon.gif')
}
this.cubeBuffer = {
position : makeBuffer([
// Front 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,
// 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),
normal : 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),
index : 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),
coord : 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),
texture : careateTextureWithFile('crate.gif')
}
var laptopData = JSON.parse(fs.readFileSync('./macbook.json','utf8'));
this.laptopBuffer = {
position : makeBuffer(laptopData.vertexPositions,3),
normal : makeBuffer(laptopData.vertexNormals,3),
index : makeEABuffer(laptopData.indices,1),
coord : makeBuffer(laptopData.vertexTextureCoords,2),
}
this.laptopScreenBuffer = {
position : makeBuffer([
0.580687, 0.659, 0.813106,
-0.580687, 0.659, 0.813107,
0.580687, 0.472, 0.113121,
-0.580687, 0.472, 0.113121,
],3),
normal : makeBuffer([
0.000000, -0.965926, 0.258819,
0.000000, -0.965926, 0.258819,
0.000000, -0.965926, 0.258819,
0.000000, -0.965926, 0.258819,
],3),
coord : makeBuffer([
1.0, 1.0,
0.0, 1.0,
1.0, 0.0,
0.0, 0.0,
],2),
}
var rttFramebuffer = this.rttFramebuffer = gl.createFramebuffer();
var rttTexture = this.rttTexture = gl.createTexture();
var renderbuffer = this.renderbuffer = gl.createRenderbuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 512, 512, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 512, 512);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
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.depthFunc(gl.LEQUAL);
},
draw: function() {
var gl = this.gl;
var mprogram = this.mprogram;
var param = this.param;
var moonBuffer = this.moonBuffer;
var cubeBuffer = this.cubeBuffer;
var laptopBuffer = this.laptopBuffer;
var laptopScreenBuffer = this.laptopScreenBuffer;
var rttFramebuffer = this.rttFramebuffer;
var rttTexture = this.rttTexture;
var mv = new plask.Mat4();
var mvStack = [];
var persp = new plask.Mat4();
function setMatrixUniforms() {
mprogram.set_uMVMatrix(mv);
mprogram.set_uPMatrix(persp);
var norm = mv.toInverseMat3();
norm.transpose();
mprogram.set_uNMatrix(norm);
}
function drawObj (obj){
if(obj.texture){
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, obj.texture);
mprogram.set_uSampler(0);
}
gl.bindBuffer(gl.ARRAY_BUFFER, obj.position.buffer);
gl.vertexAttribPointer(mprogram.location_aVertexPosition,
obj.position.itemSize,
gl.FLOAT,
false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.normal.buffer);
gl.vertexAttribPointer(mprogram.location_aVertexNormal,
obj.normal.itemSize,
gl.FLOAT,
false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.coord.buffer);
gl.vertexAttribPointer(mprogram.location_aTextureCoord,
obj.coord.itemSize,
gl.FLOAT,
false, 0, 0);
if(obj.index){
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.index.buffer);
gl.drawElements(gl.TRIANGLES, obj.index.num, gl.UNSIGNED_SHORT, 0);
}
}
function animate() {
var timeNow = new Date().getTime();
if (param.lastTime != 0) {
var elapsed = timeNow - param.lastTime;
param.moonAngle += 0.05 * elapsed;
param.cubeAngle += 0.05 * elapsed;
param.laptopAngle -= 0.005 * elapsed;
}
param.lastTime = timeNow;
}
persp.perspective(45, param.laptopScreenAspectRatio, 0.1, 100.0);
mprogram.use();
gl.enableVertexAttribArray(mprogram.location_aVertexPosition);
gl.enableVertexAttribArray(mprogram.location_aVertexNormal);
gl.enableVertexAttribArray(mprogram.location_aTextureCoord);
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
gl.viewport(0, 0, 512, 512);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mprogram.set_uShowSpecularHighlights(true);
mprogram.set_uAmbientLightingColor(new plask.Vec3(0.2,0.2,0.2));
mprogram.set_uPointLightingLocation(new plask.Vec3(0, 0, -5));
mprogram.set_uPointLightingDiffuseColor(new plask.Vec3(0.8, 0.8, 0.8));
mprogram.set_uShowSpecularHighlights(0);
mprogram.set_uUseTextures(true);
mprogram.set_uMaterialAmbientColor(new plask.Vec3(1.0, 1.0, 1.0));
mprogram.set_uMaterialDiffuseColor(new plask.Vec3(1.0, 1.0, 1.0));
mprogram.set_uMaterialSpecularColor(new plask.Vec3(0.0, 0.0, 0.0));
mprogram.set_uMaterialShininess(false);
mprogram.set_uMaterialEmissiveColor(new plask.Vec3(0.0, 0.0, 0.0));
mv.translate(0, 0, -5);
mv.rotate(degToRad(30), 1.0, 0.0, 0.0);
mvStack.push(mv.dup());
mv.rotate(degToRad(param.moonAngle), 0.0, 1.0, 0.0);
mv.translate(2, 0, 0);
setMatrixUniforms();
drawObj(moonBuffer);
mv = mvStack.pop();
mvStack.push(mv.dup());
mv.rotate(degToRad(param.cubeAngle), 0.0, 1.0, 0.0);
mv.translate(1.25, 0, 0);
setMatrixUniforms();
drawObj(cubeBuffer);
mv = mvStack.pop();
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, this.width, this.height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
//persp.perspective(45, this.width / this.height, 0.1, 100.0);
mv.reset();
mv.translate(0, -0.4, -2.2);
mv.rotate(degToRad(param.laptopAngle), 0.0, 1.0, 0.0);
mv.rotate(degToRad(-90), 1.0, 0.0, 0.0);
mprogram.set_uShowSpecularHighlights(true);
mprogram.set_uAmbientLightingColor(new plask.Vec3(0.2,0.2,0.2));
mprogram.set_uPointLightingLocation(new plask.Vec3(-1, 2, -1));
mprogram.set_uPointLightingDiffuseColor(new plask.Vec3(0.8, 0.8, 0.8));
mprogram.set_uPointLightingSpecularColor(new plask.Vec3(0.8, 0.8, 0.8));
mprogram.set_uMaterialAmbientColor(new plask.Vec3(1.0, 1.0, 1.0));
mprogram.set_uMaterialDiffuseColor(new plask.Vec3(1.0, 1.0, 1.0));
mprogram.set_uMaterialSpecularColor(new plask.Vec3(1.5, 1.5, 1.5));
mprogram.set_uMaterialShininess(5);
mprogram.set_uMaterialEmissiveColor(new plask.Vec3(0.0, 0.0, 0.0));
mprogram.set_uUseTextures(false);
setMatrixUniforms();
drawObj(laptopBuffer);
mprogram.set_uMaterialAmbientColor(new plask.Vec3(0.0, 0.0, 0.0));
mprogram.set_uMaterialDiffuseColor(new plask.Vec3(0.0, 0.0, 0.0));
mprogram.set_uMaterialSpecularColor(new plask.Vec3(0.5, 0.5, 0.5));
mprogram.set_uMaterialShininess(20);
mprogram.set_uMaterialEmissiveColor(new plask.Vec3(1.5, 1.5, 1.5));
mprogram.set_uUseTextures(true);
setMatrixUniforms();
drawObj(laptopScreenBuffer);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
mprogram.set_uSampler(0);
gl.drawArrays(gl.TRIANGLE_STRIP,0,laptopScreenBuffer.position.num);
animate();
}
});
#ifdef GL_ES
precision highp float;
#endif
varying vec2 vTextureCoord;
varying vec3 vTransformedNormal;
varying vec4 vPosition;
uniform vec3 uMaterialAmbientColor;
uniform vec3 uMaterialDiffuseColor;
uniform vec3 uMaterialSpecularColor;
uniform float uMaterialShininess;
uniform vec3 uMaterialEmissiveColor;
uniform bool uShowSpecularHighlights;
uniform bool uUseTextures;
uniform vec3 uAmbientLightingColor;
uniform vec3 uPointLightingLocation;
uniform vec3 uPointLightingDiffuseColor;
uniform vec3 uPointLightingSpecularColor;
uniform sampler2D uSampler;
void main(void) {
vec3 ambientLightWeighting = uAmbientLightingColor;
vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
vec3 normal = normalize(vTransformedNormal);
vec3 specularLightWeighting = vec3(0.0, 0.0, 0.0);
if (uShowSpecularHighlights) {
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, normal);
float specularLightBrightness = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
specularLightWeighting = uPointLightingSpecularColor * specularLightBrightness;
}
float diffuseLightBrightness = max(dot(normal, lightDirection), 0.0);
vec3 diffuseLightWeighting = uPointLightingDiffuseColor * diffuseLightBrightness;
vec3 materialAmbientColor = uMaterialAmbientColor;
vec3 materialDiffuseColor = uMaterialDiffuseColor;
vec3 materialSpecularColor = uMaterialSpecularColor;
vec3 materialEmissiveColor = uMaterialEmissiveColor;
float alpha = 1.0;
if (uUseTextures) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
materialAmbientColor = materialAmbientColor * textureColor.rgb;
materialDiffuseColor = materialDiffuseColor * textureColor.rgb;
materialEmissiveColor = materialEmissiveColor * textureColor.rgb;
alpha = textureColor.a;
}
gl_FragColor = vec4(
materialAmbientColor * ambientLightWeighting
+ materialDiffuseColor * diffuseLightWeighting
+ materialSpecularColor * specularLightWeighting
+ materialEmissiveColor,
alpha
);
}
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat3 uNMatrix;
varying vec2 vTextureCoord;
varying vec3 vTransformedNormal;
varying vec4 vPosition;
void main(void) {
vPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
gl_Position = uPMatrix * vPosition;
vTextureCoord = aTextureCoord;
vTransformedNormal = uNMatrix * aVertexNormal;
}
// 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};
// 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 //////////////////////////////////////////////
// 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;
};
////////////////////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