diff --git a/game-lib.js b/game-lib.js index 33bf4d6..2e61d5a 100644 --- a/game-lib.js +++ b/game-lib.js @@ -1,3 +1,7 @@ +if (typeof require != 'undefined') { + var Maths3D = require('./maths3d'); +} + function GameLib() {} /** @@ -25,6 +29,17 @@ GameLib.D3 = function( this.editorUrl = editorUrl || this.config.editor.url; }; +if (typeof require == 'undefined' && + typeof Maths3D == 'undefined') { + console.warn("You need a proper Maths3D library in order to use this library"); +} + +GameLib.D3.Vector2 = Maths3D.Vector2; +GameLib.D3.Vector3 = Maths3D.Vector3; +GameLib.D3.Vector4 = Maths3D.Vector4; +GameLib.D3.Matrix4 = Maths3D.Matrix4; +GameLib.D3.Color = Maths3D.Color; + /** * Texture Superset * @param id diff --git a/maths3d.js b/maths3d.js new file mode 100644 index 0000000..b21b831 --- /dev/null +++ b/maths3d.js @@ -0,0 +1,711 @@ +function Maths3D() {} + +Maths3D.Vector2 = function(x, y) { + + this.x = 0; + this.y = 0; + + if (x) { + this.x = x; + } + + if (y) { + this.y = y; + } +}; + +Maths3D.Vector3 = function(x, y, z) { + + this.x = 0; + this.y = 0; + this.z = 0; + + if (x) { + this.x = x; + } + + if (y) { + this.y = y; + } + + if (z) { + this.z = z; + } +}; + +Maths3D.Vector4 = function(x, y, z, w) { + + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 0; + + if (x) { + this.x = x; + } + + if (y) { + this.y = y; + } + + if (z) { + this.z = z; + } + + if (w) { + this.w = w; + } +}; + +Maths3D.Matrix3 = function( + row0, + row1, + row2 +) { + this.identity(); + + if (row0) { + this.rows[0] = row0; + } + + if (row1) { + this.rows[1] = row1; + } + + if (row2) { + this.rows[2] = row2; + } +}; + +Maths3D.Matrix4 = function( + row0, + row1, + row2, + row3 +) { + + this.identity(); + + if (row0) { + this.rows[0] = row0; + } + + if (row1) { + this.rows[1] = row1; + } + + if (row2) { + this.rows[2] = row2; + } + + if (row3) { + this.rows[3] = row3; + } +}; + +Maths3D.Color = function(r, g, b, a) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; +}; + +Maths3D.Vector2.prototype.copy = function() { + return new Maths3D.Vector2( + this.x, + this.y + ); +}; + +Maths3D.Vector2.prototype.equals = function(v) { + return !!(((this.x == v.x) && + (this.y == v.y)) || + ((this.y == v.x) && + (this.x == v.y))); +}; + + +Maths3D.Matrix4.prototype.rotationMatrixX = function (radians) { + this.identity(); + this.rows[1] = new Maths3D.Vector4(0, Math.cos(radians), -1 * Math.sin(radians), 0); + this.rows[2] = new Maths3D.Vector4(0, Math.sin(radians), Math.cos(radians), 0); + return this; +}; + +Maths3D.Matrix4.prototype.rotationMatrixY = function (radians) { + this.identity(); + this.rows[0] = new Maths3D.Vector4( + Math.cos(radians), + 0, + Math.sin(radians), + 0 + ); + this.rows[2] = new Maths3D.Vector4( + -1 * Math.sin(radians), + 0, + Math.cos(radians), + 0 + ); + return this; +}; + +Maths3D.Matrix4.prototype.rotationMatrixZ = function (radians) { + this.identity(); + this.rows[0] = new Maths3D.Vector4(Math.cos(radians), -1 * Math.sin(radians), 0, 0); + this.rows[1] = new Maths3D.Vector4(Math.sin(radians), Math.cos(radians), 0, 0); + return this; +}; + +Maths3D.Matrix4.prototype.rotateX = function (radians, point) { + this.identity(); + this.rotationMatrixX(radians); + return this.multiply(point); +}; + +Maths3D.Matrix4.prototype.rotateY = function (radians, point) { + this.identity(); + this.rotationMatrixY(radians); + return this.multiply(point); +}; + +Maths3D.Matrix4.prototype.rotateZ = function (radians, point) { + this.identity(); + this.rotationMatrixZ(radians); + return this.multiply(point); +}; + +Maths3D.Matrix4.prototype.multiply = function (mvp) { + if (mvp instanceof Maths3D.Vector4) { + return new Maths3D.Vector4( + this.rows[0].x * mvp.x + this.rows[0].y * mvp.y + this.rows[0].z * mvp.z + this.rows[0].w * mvp.w, + this.rows[1].x * mvp.x + this.rows[1].y * mvp.y + this.rows[1].z * mvp.z + this.rows[1].w * mvp.w, + this.rows[2].x * mvp.x + this.rows[2].y * mvp.y + this.rows[2].z * mvp.z + this.rows[2].w * mvp.w, + this.rows[3].x * mvp.x + this.rows[3].y * mvp.y + this.rows[3].z * mvp.z + this.rows[3].w * mvp.w + ); + } else if (mvp instanceof Maths3D.Vector3) { + return new Maths3D.Vector3( + this.rows[0].x * mvp.x + this.rows[0].y * mvp.y + this.rows[0].z * mvp.z, + this.rows[1].x * mvp.x + this.rows[1].y * mvp.y + this.rows[1].z * mvp.z, + this.rows[2].x * mvp.x + this.rows[2].y * mvp.y + this.rows[2].z * mvp.z + ); + } +}; + +Maths3D.Vector4.Points = function () { + this.vectors = []; +}; + +Maths3D.Vector4.Points.prototype.add = function (vector) { + + if (vector instanceof Maths3D.Vector3) { + vector = new Maths3D.Vector4( + vector.x, + vector.y, + vector.z, + 1 + ) + } + + if (!vector instanceof Maths3D.Vector4) { + console.warn("Vector needs to be of type Vector4"); + throw new Error("Vector needs to be of type Vector4"); + } + + this.vectors.push(vector); + + return this; +}; + +Maths3D.Vector4.Points.prototype.copy = function () { + + var vectors = []; + + for (var i = 0; i < this.vectors.length; i++) { + vectors.push(this.vectors[i].copy()); + } + + return vectors; +}; + +Maths3D.Vector4.Points.prototype.maximizeXDistance = function (grain) { + +// console.log("vectors (before): " + JSON.stringify(this.vectors, null, 2)); + + var multiplier = 0; + + var rotationMatrixY = new Maths3D.Matrix4().rotationMatrixY(grain); + + var totalRadians = 0; + + var backupVectors = this.copy(); + + var maxXDistance = 0; + + for (var i = 0; i < Math.PI * 2; i += grain) { + + multiplier++; + + for (var j = 0; j < this.vectors.length; j++) { + this.vectors[j] = rotationMatrixY.multiply(this.vectors[j]); + } + + var distances = this.distances(); + + if (distances.x > maxXDistance) { + + maxXDistance = distances.x; + totalRadians = multiplier * grain; + } + } + + this.vectors = backupVectors; + +// console.log("distance: " + maxXDistance + " radians : " + totalRadians); + + var maxRotationMatrix = new Maths3D.Matrix4().rotationMatrixY(totalRadians); + + for (var k = 0; k < this.vectors.length; k++) { + this.vectors[k] = maxRotationMatrix.multiply(this.vectors[k]); + } + +// console.log("vectors (after): " + JSON.stringify(this.vectors, null, 2)); + +}; + +Maths3D.Vector4.Points.prototype.maximizeYDistance = function (grain) { + +// console.log("vectors (before): " + JSON.stringify(this.vectors, null, 2)); + + var multiplier = 0; + + var rotationMatrixX = new Maths3D.Matrix4().rotationMatrixX(grain); + + var totalRadians = 0; + + var backupVectors = this.copy(); + + var maxYDistance = 0; + + for (var i = 0; i < Math.PI * 2; i += grain) { + + multiplier++; + + for (var j = 0; j < this.vectors.length; j++) { + this.vectors[j] = rotationMatrixX.multiply(this.vectors[j]); + } + + var distances = this.distances(); + + if (distances.y > maxYDistance) { + maxYDistance = distances.y; + totalRadians = multiplier * grain; + } + } + + this.vectors = backupVectors; + +// console.log("distance: " + maxYDistance + " radians : " + totalRadians); + + var maxRotationMatrix = new Maths3D.Matrix4().rotationMatrixX(totalRadians); + + for (var k = 0; k < this.vectors.length; k++) { + this.vectors[k] = maxRotationMatrix.multiply(this.vectors[k]); + } + +// console.log("vectors (after): " + JSON.stringify(this.vectors, null, 2)); + +}; + + +Maths3D.Vector4.Points.prototype.lookAt = function (at, up) { + + var polyCenter = this.average(); + + console.log("poly center : " + JSON.stringify(polyCenter)); + + var lookAtMatrix = new Maths3D.Matrix4().lookAt(polyCenter, at, up); + + lookAtMatrix.rows[0] = new Maths3D.Vector4(1, 0, 0, 0); + lookAtMatrix.rows[1] = new Maths3D.Vector4(0, 0, 1, 0); + lookAtMatrix.rows[2] = new Maths3D.Vector4(0, 1, 0, 0); + + console.log("look at matrix : " + JSON.stringify(lookAtMatrix, null, 2)); + + for (var i = 0; i < this.vectors.length; i++) { + console.log("vector " + i + " (before): " + JSON.stringify(this.vectors[i])); + this.vectors[i] = lookAtMatrix.multiply(this.vectors[i]); + console.log("vector " + i + " (after) : " + JSON.stringify(this.vectors[i])); + } +}; + +Maths3D.Vector4.Points.prototype.distances = function () { + + var minX = this.vectors[0].x; + var minY = this.vectors[0].y; + var minZ = this.vectors[0].z; + + var maxX = this.vectors[0].x; + var maxY = this.vectors[0].y; + var maxZ = this.vectors[0].z; + + for (var i = 0; i < this.vectors.length; i++) { + if (this.vectors[i].x < minX) { + minX = this.vectors[i].x; + } + if (this.vectors[i].y < minY) { + minY = this.vectors[i].y; + } + if (this.vectors[i].z < minZ) { + minZ = this.vectors[i].z; + } + + if (this.vectors[i].x > maxX) { + maxX = this.vectors[i].x; + } + if (this.vectors[i].y > maxY) { + maxY = this.vectors[i].y; + } + if (this.vectors[i].z > maxZ) { + maxZ = this.vectors[i].z; + } + } + + return new Maths3D.Vector3( + Math.abs(maxX - minX), + Math.abs(maxY - minY), + Math.abs(maxY - minZ) + ) +}; + +Maths3D.Vector4.Points.prototype.average = function () { + var averageX = 0; + var averageY = 0; + var averageZ = 0; + + for (var i = 0; i < this.vectors.length; i++) { + averageX += this.vectors[i].x; + averageY += this.vectors[i].y; + averageZ += this.vectors[i].z; + } + + return new Maths3D.Vector3( + averageX / this.vectors.length, + averageY / this.vectors.length, + averageZ / this.vectors.length + ); +}; + +Maths3D.Vector4.Points.prototype.negative = function () { + + for (var i = 0; i < this.vectors.length; i++) { + this.vectors[i].x *= -1; + this.vectors[i].y *= -1; + this.vectors[i].z *= -1; + } + + return this; +}; + + +Maths3D.Vector4.Points.prototype.toOrigin = function () { + + var distanceFromOrigin = this.average().negative(); + + for (var i = 0; i < this.vectors.length; i++) { + this.vectors[i].translate(distanceFromOrigin); + } +}; + +Maths3D.Vector3.clockwise = function (u, v, w, viewPoint) { + + var normal = Maths3D.Vector3.normal(u, v, w); + + var uv = u.copy(); + + var winding = normal.dot(uv.subtract(viewPoint)); + + return (winding > 0); +}; + +Maths3D.Vector3.normal = function (u, v, w) { + var vv = v.copy(); + var wv = w.copy(); + return vv.subtract(u).cross(wv.subtract(u)); +}; + +Maths3D.Vector3.prototype.lookAt = function (at, up) { + + var lookAtMatrix = Maths3D.Matrix4.lookAt(this, at, up); + + this.multiply(lookAtMatrix); +}; + +Maths3D.Vector3.prototype.translate = function (v) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + return this; +}; + +Maths3D.Vector4.prototype.translate = function (v) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + return this; +}; + +Maths3D.Vector3.prototype.squared = function () { + return this.x * this.x + this.y * this.y + this.z * this.z; +}; + +Maths3D.Vector3.prototype.copy = function () { + return new Maths3D.Vector3( + this.x, + this.y, + this.z + ); +}; + +Maths3D.Vector4.prototype.copy = function () { + return new Maths3D.Vector4( + this.x, + this.y, + this.z, + this.w + ); +}; + +Maths3D.Vector3.prototype.multiply = function (s) { + if (s instanceof Maths3D.Vector3) { + this.x *= s.x; + this.y *= s.y; + this.z *= s.z; + } else if (s instanceof Maths3D.Matrix4) { + var x = s.rows[0].x * this.x + s.rows[0].y * this.y + s.rows[0].z * this.z; + var y = s.rows[1].x * this.x + s.rows[1].y * this.y + s.rows[1].z * this.z; + var z = s.rows[2].x * this.x + s.rows[2].y * this.y + s.rows[2].z * this.z; + this.x = x; + this.y = y; + this.z = z; + } else { + console.log("functionality not implemented - please do this"); + throw new Error("not implemented"); + } + return this; +}; + +Maths3D.Vector4.prototype.multiply = function (s) { + if (s instanceof Maths3D.Vector3) { + this.x *= s.x; + this.y *= s.y; + this.z *= s.z; + } else if (s instanceof Maths3D.Matrix4) { + var x = s.rows[0].x * this.x + s.rows[0].y * this.y + s.rows[0].z * this.z + s.rows[0].w * this.w; + var y = s.rows[1].x * this.x + s.rows[1].y * this.y + s.rows[1].z * this.z + s.rows[1].w * this.w; + var z = s.rows[2].x * this.x + s.rows[2].y * this.y + s.rows[2].z * this.z + s.rows[2].w * this.w; + var w = s.rows[3].x * this.x + s.rows[3].y * this.y + s.rows[3].z * this.z + s.rows[3].w * this.w; + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } else { + console.log("functionality not implemented - please do this"); + throw new Error("not implemented"); + } +}; + +Maths3D.Vector3.prototype.dot = function (v) { + return (this.x * v.x) + (this.y * v.y) + (this.z * v.z); +}; + +Maths3D.Vector3.prototype.normalize = function () { + + var EPSILON = 0.000001; + + var v2 = this.squared(); + + if (v2 < EPSILON) { + return this; //do nothing for zero vector + } + + var invLength = 1 / Math.sqrt(v2); + + this.x *= invLength; + this.y *= invLength; + this.z *= invLength; + + return this; +}; + +Maths3D.Vector4.prototype.normalize = function () { + + // note - leave w untouched + var EPSILON = 0.000001; + + var v2 = this.x * this.x + this.y * this.y + this.z * this.z; + + if (v2 < EPSILON) { + return this; //do nothing for zero vector + } + + var invLength = 1 / Math.sqrt(v2); + + this.x *= invLength; + this.y *= invLength; + this.z *= invLength; + + return this; +}; + +Maths3D.Matrix3.prototype.identity = function () { + this.rows = [ + new Maths3D.Vector4(1, 0, 0), + new Maths3D.Vector4(0, 1, 0), + new Maths3D.Vector4(0, 0, 1) + ]; +}; + +Maths3D.Matrix4.prototype.identity = function () { + this.rows = [ + new Maths3D.Vector4(1, 0, 0, 0), + new Maths3D.Vector4(0, 1, 0, 0), + new Maths3D.Vector4(0, 0, 1, 0), + new Maths3D.Vector4(0, 0, 0, 1) + ]; +}; + +Maths3D.Matrix4.prototype.lookAt = function (position, target, up) { + + var pv = new Maths3D.Vector3(position.x, position.y, position.z); + + var z = pv.subtract(target).normalize(); + + if (z.squared() === 0) { + z.z = 1; + } + + var x = up.cross(z).normalize(); + + if (x.squared() === 0) { + z.x += 0.0001; + x = up.cross(z).normalize(); + } + + var y = z.cross(x); + + this.rows[0].x = x.x; + this.rows[0].y = x.y; + this.rows[0].z = x.z; + + this.rows[1].x = y.x; + this.rows[1].y = y.y; + this.rows[1].z = y.z; + + this.rows[2].x = z.x; + this.rows[2].y = z.y; + this.rows[2].z = z.z; + + return this; + + // te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x; + // te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y; + // te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z; + + + // var matrix4 = new Matrix4(); + // + // matrix4.rows[0] = side.negative(); + // matrix4.rows[1] = _up; + // matrix4.rows[2] = forward; + + // + // matrix4.setColumn(0, side.negative()); + // matrix4.setColumn(1, _up); + // matrix4.setColumn(2, forward); + + //return matrix4; + + // return new Matrix4( + // new Vector4( + // side.x, + // side.y, + // side.z, + // side.negative().dot(position) + // ), + // new Vector4( + // _up.x, + // _up.y, + // _up.z, + // _up.negative().dot(position) + // ), + // new Vector4( + // forward.negative().x, + // forward.negative().y, + // forward.negative().z, + // forward.dot(position) + // ) + // ) +}; + +Maths3D.Vector3.prototype.negative = function () { + this.x *= -1; + this.y *= -1; + this.z *= -1; + return this; +}; + +Maths3D.Vector3.prototype.magnitude = function () { + return this.x + this.y + this.z; +}; + +Maths3D.Vector4.prototype.subtract = function (v) { + + if (v instanceof Maths3D.Vector3) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + } + + if (v instanceof Maths3D.Vector4) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; + } + + return this; +}; + +Maths3D.Vector3.prototype.subtract = function (v) { + + if (v instanceof Maths3D.Vector3) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + } + + if (v instanceof Maths3D.Vector4) { + console.warn("trying to subtract vector of bigger length (4 vs 3))"); + } + + return this; +}; + +Maths3D.Vector3.prototype.cross = function (v) { + return new Maths3D.Vector3( + this.y * v.z - this.z * v.y, + this.z * v.x - this.x * v.z, + this.x * v.y - this.y * v.x + ); +}; + +if (typeof module !== 'undefined') { + module.exports = { + Vector2: Maths3D.Vector2, + Vector3: Maths3D.Vector3, + Vector4: Maths3D.Vector4, + Matrix3: Maths3D.Matrix3, + Matrix4: Maths3D.Matrix4, + Color: Maths3D.Color + }; +} \ No newline at end of file