So I am not sure if my question is more of a programming question, more of a pure math question, or equally both.
Short version: I have a rotating sphere in webgl, but it isn't rotating the way I want it to. I think what I want is to rotate the sphere about an axis, and then rotate that axis about a second axis.
So I have downloaded and tweaked some javascript webgl code that renders an icosahedron and can animate its rotation along x, y, or z axes, either individually or simultaneously in a composite rotation group. I have some sense of how it is working.
I've also read (such as here) that any rotation group of Euler angles actually just yields some new, net, single axis of rotation. Which means I can't get the animation I want by just using the rotations of those three axes.
So I think what I need is a new kind of rotation, that rotates the axis of rotation itself. But I am at a loss as for how to apply that to the matrix transformation.
Would that be implemented as a separate transformation?
Would it be included into the geometry of the y axis rotation?
Would it be easier to implement by rotating the camera around the x axis?
Should I cross-post this to math stack exchange for help with the pure math?
I would appreciate any help, whether high level conceptual suggestions or detailed code suggestions.
Below is the working snippet I'm currently using. It is rotating about the y axis, and I'd like that axis of rotation to rotate around the x axis. But that's not the same as just doing a rotation group around the x and y axis simultaneously - that just yields a single, composite rotation axis of the line y = x.
var App = (function () {
function App(canvas) {
this._canvas = canvas;
this._ctx = canvas.getContext('webgl');
this._ctx.viewport(0, 0, canvas.width, canvas.height);
this._canvas.setAttribute('width', this._canvas.clientWidth.toString());
this._canvas.setAttribute('height', this._canvas.clientHeight.toString());
this._config =
{
DrawMode: this._ctx.TRIANGLES,
Quality: 3,
ZoomLevel: -4,
Rotation: {
X: 0.0000,
Y: 0.0100,
Z: 0.0000
}
};
}
App.prototype._setData = function () {
var ctx = this._ctx;
var icosahedron = new Icosahedron3D(this._config.Quality);
this._vertices = icosahedron.Points.reduce(function (a, b, i) { return i === 1 ? [a.x, a.y, a.z, b.x, b.y, b.z] : a.concat([b.x, b.y, b.z]); });
this._indices = icosahedron.TriangleIndices;
this._colors = this._generateColors(this._vertices);
var vertex_buffer = ctx.createBuffer();
ctx.bindBuffer(ctx.ARRAY_BUFFER, vertex_buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(this._vertices), ctx.STATIC_DRAW);
var color_buffer = ctx.createBuffer();
ctx.bindBuffer(ctx.ARRAY_BUFFER, color_buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(this._colors), ctx.STATIC_DRAW);
var index_buffer = ctx.createBuffer();
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, index_buffer);
ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(this._indices), ctx.STATIC_DRAW);
return {
vertex: vertex_buffer,
color: color_buffer,
index: index_buffer
};
};
App.prototype._generateColors = function (vertices) {
var colors = [];
for (var i = 0; i < vertices.length; i += 3) {
var cvalue = 0;
var testvalue = 0;
if (vertices[i] >= 0) testvalue++;
if (vertices[i+1] >= 0) testvalue++;
if (vertices[i+2] >= 0) testvalue++;
else testvalue = 0;
if (testvalue > 0) cvalue = 1;
colors.push(cvalue);
colors.push(cvalue);
colors.push(cvalue);
}
return colors;
}
App.prototype._animate = function (proj_matrix, view_matrix, mov_matrix) {
var _this = this;
var ctx = this._ctx;
var rotThetas = this._config.Rotation;
var time_old = 0;
var zoomLevel_old = 0;
var execAnimation = function (time) {
var dt = time - time_old;
time_old = time;
for (var axis in rotThetas) {
var theta = rotThetas[axis];
if (theta > 0.0 || theta < 0.0) {
Matrix[("Rotate" + axis)](mov_matrix, dt * theta);
}
}
if (Math.abs(_this._config.ZoomLevel - zoomLevel_old) >= 0.01) {
view_matrix[14] = view_matrix[14] + (zoomLevel_old * -1) + _this._config.ZoomLevel;
zoomLevel_old = _this._config.ZoomLevel;
}
ctx.enable(ctx.DEPTH_TEST);
ctx.depthFunc(ctx.LEQUAL);
ctx.clearDepth(1.0);
ctx.viewport(0.0, 0.0, _this._canvas.width, _this._canvas.height);
ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT);
ctx.uniformMatrix4fv(_this._shader.Pmatrix, false, proj_matrix);
ctx.uniformMatrix4fv(_this._shader.Vmatrix, false, view_matrix);
ctx.uniformMatrix4fv(_this._shader.Mmatrix, false, mov_matrix);
ctx.drawElements(_this._config.DrawMode, _this._indices.length, ctx.UNSIGNED_SHORT, 0);
window.requestAnimationFrame(execAnimation);
};
execAnimation(0);
};
App.prototype.Draw = function () {
var buffers = this._setData();
this._shader = App.UseQuarternionShaderProgram(this._ctx, buffers.vertex, buffers.color);
var proj_matrix = new Float32Array(Matrix.GetProjection(40, this._canvas.width / this._canvas.height, 1, 100));
var view_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
var mov_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
this._animate(proj_matrix, view_matrix, mov_matrix);
};
App.UseQuarternionVertShader = function (context) {
var vertCode = "\n\t\t\tattribute vec3 position;\n\t\t\tattribute highp vec3 aVertexNormal;\n\t\t\t\n\t\t\tuniform mat4 Pmatrix;\n\t\t\tuniform mat4 Vmatrix;\n\t\t\tuniform mat4 Mmatrix;\n\n\t\t\tattribute vec4 color;\n\t\t\tvarying lowp vec4 vColor;\n\n\t\t\tvarying vec3 vLightWeighting;\n\t\t\t\n\t\t\tuniform vec3 uAmbientColor;\n\t\t\tuniform vec3 uPointLightingLocation;\n\t\t\tuniform vec3 uPointLightingColor;\n\n\t\t\tvoid main(void) {\n\t\t\t\tvec4 mvPosition = Mmatrix * vec4(position, 1.);\n\t\t\t\tgl_Position = Pmatrix*Vmatrix*mvPosition;\n\t\t\t\tgl_PointSize = 4.0;\n\t\t\t\tvColor = color;\n\n\t\t\t\tvec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz);\n\t\t\t\tvec3 transformedNormal = vec3(Vmatrix) * aVertexNormal;\n\t\t\t\tfloat directionalLightWeighting = max(dot(transformedNormal, lightDirection), 0.0);\n\t\t\t\tvLightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting;\n\t\t\t}";
var vertShader = context.createShader(context.VERTEX_SHADER);
context.shaderSource(vertShader, vertCode);
context.compileShader(vertShader);
return vertShader;
};
App.UseVariableFragShader = function (context) {
var fragCode = "\n\t\t\tprecision mediump float;\n\t\t\tvarying lowp vec4 vColor;\n\t\t\tvarying vec3 vLightWeighting;\n\t\t\tvoid main(void) {\n\t\t\t\tgl_FragColor = vec4(vColor.rgb, 1.);\n\t\t\t}";
var fragShader = context.createShader(context.FRAGMENT_SHADER);
context.shaderSource(fragShader, fragCode);
context.compileShader(fragShader);
return fragShader;
};
App.UseQuarternionShaderProgram = function (ctx, vertex_buffer, color_buffer) {
var vertShader = App.UseQuarternionVertShader(ctx);
var fragShader = App.UseVariableFragShader(ctx);
var shaderProgram = ctx.createProgram();
ctx.attachShader(shaderProgram, vertShader);
ctx.attachShader(shaderProgram, fragShader);
ctx.linkProgram(shaderProgram);
var Pmatrix = ctx.getUniformLocation(shaderProgram, "Pmatrix");
var Vmatrix = ctx.getUniformLocation(shaderProgram, "Vmatrix");
var Mmatrix = ctx.getUniformLocation(shaderProgram, "Mmatrix");
ctx.bindBuffer(ctx.ARRAY_BUFFER, vertex_buffer);
var position = ctx.getAttribLocation(shaderProgram, "position");
ctx.vertexAttribPointer(position, 3, ctx.FLOAT, false, 0, 0);
ctx.enableVertexAttribArray(position);
ctx.bindBuffer(ctx.ARRAY_BUFFER, color_buffer);
var color = ctx.getAttribLocation(shaderProgram, "color");
ctx.vertexAttribPointer(color, 3, ctx.FLOAT, false, 0, 0);
ctx.enableVertexAttribArray(color);
ctx.useProgram(shaderProgram);
var ambientColor = ctx.getUniformLocation(shaderProgram, "uAmbientColor");
var pointLightingLocation = ctx.getUniformLocation(shaderProgram, "uPointLightingLocation");
var pointLightingColor = ctx.getUniformLocation(shaderProgram, "uPointLightingColor");
ctx.uniform3f(ambientColor, 0.2, 0.2, 0.2);
ctx.uniform3f(pointLightingLocation, 0.0, 0.0, -20.0);
ctx.uniform3f(pointLightingColor, 0.8, 0.8, 0.8);
return {
Pmatrix: Pmatrix,
Vmatrix: Vmatrix,
Mmatrix: Mmatrix,
ShaderProgram: shaderProgram
};
};
return App;
})();
var Matrix = (function () {
function Matrix() {
}
Matrix.GetProjection = function (angle, a, zMin, zMax) {
var ang = Math.tan((angle * .5) * Math.PI / 180);
return [
0.5 / ang, 0, 0, 0,
0, 0.5 * a / ang, 0, 0,
0, 0, -(zMax + zMin) / (zMax - zMin), -1,
0, 0, (-2 * zMax * zMin) / (zMax - zMin), 0
];
};
Matrix.RotateX = function (m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv1 = m[1], mv5 = m[5], mv9 = m[9];
m[1] = m[1] * c - m[2] * s;
m[5] = m[5] * c - m[6] * s;
m[9] = m[9] * c - m[10] * s;
m[2] = m[2] * c + mv1 * s;
m[6] = m[6] * c + mv5 * s;
m[10] = m[10] * c + mv9 * s;
};
Matrix.RotateY = function (m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv0 = m[0], mv4 = m[4], mv8 = m[8];
m[0] = c * m[0] + s * m[2];
m[4] = c * m[4] + s * m[6];
m[8] = c * m[8] + s * m[10];
m[2] = c * m[2] - s * mv0;
m[6] = c * m[6] - s * mv4;
m[10] = c * m[10] - s * mv8;
};
Matrix.RotateZ = function (m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv0 = m[0], mv4 = m[4], mv8 = m[8];
m[0] = c * m[0] - s * m[1];
m[4] = c * m[4] - s * m[5];
m[8] = c * m[8] - s * m[9];
m[1] = c * m[1] + s * mv0;
m[5] = c * m[5] + s * mv4;
m[9] = c * m[9] + s * mv8;
};
Matrix.Translate = function (a, b, c) {
var d = b[0], e = b[1], s = b[2];
if (!c || a == c) {
a[12] = a[0] * d + a[4] * e + a[8] * s + a[12];
a[13] = a[1] * d + a[5] * e + a[9] * s + a[13];
a[14] = a[2] * d + a[6] * e + a[10] * s + a[14];
a[15] = a[3] * d + a[7] * e + a[11] * s + a[15];
return a;
}
var g = a[0], f = a[1], h = a[2], i = a[3], j = a[4], k = a[5], l = a[6], o = a[7], m = a[8], n = a[9], p = a[10], r = a[11];
c[0] = g;
c[1] = f;
c[2] = h;
c[3] = i;
c[4] = j;
c[5] = k;
c[6] = l;
c[7] = o;
c[8] = m;
c[9] = n;
c[10] = p;
c[11] = r;
c[12] = g * d + j * e + m * s + a[12];
c[13] = f * d + k * e + n * s + a[13];
c[14] = h * d + l * e + p * s + a[14];
c[15] = i * d + o * e + r * s + a[15];
return c;
};
;
return Matrix;
})();
var Icosahedron3D = (function () {
function Icosahedron3D(quality) {
this._quality = quality;
this._calculateGeometry();
}
Icosahedron3D.prototype._calculateGeometry = function () {
this.Points = [];
this.TriangleIndices = [];
this._middlePointIndexCache = {};
this._index = 0;
var t = (1.0 + Math.sqrt(5.0)) / 2.0;
this._addVertex(-1, t, 0);
this._addVertex(1, t, 0);
this._addVertex(-1, -t, 0);
this._addVertex(1, -t, 0);
this._addVertex(0, -1, t);
this._addVertex(0, 1, t);
this._addVertex(0, -1, -t);
this._addVertex(0, 1, -t);
this._addVertex(t, 0, -1);
this._addVertex(t, 0, 1);
this._addVertex(-t, 0, -1);
this._addVertex(-t, 0, 1);
this._addFace(0, 11, 5);
this._addFace(0, 5, 1);
this._addFace(0, 1, 7);
this._addFace(0, 7, 10);
this._addFace(0, 10, 11);
this._addFace(1, 5, 9);
this._addFace(5, 11, 4);
this._addFace(11, 10, 2);
this._addFace(10, 7, 6);
this._addFace(7, 1, 8);
this._addFace(3, 9, 4);
this._addFace(3, 4, 2);
this._addFace(3, 2, 6);
this._addFace(3, 6, 8);
this._addFace(3, 8, 9);
this._addFace(4, 9, 5);
this._addFace(2, 4, 11);
this._addFace(6, 2, 10);
this._addFace(8, 6, 7);
this._addFace(9, 8, 1);
this._refineVertices();
};
Icosahedron3D.prototype._addVertex = function (x, y, z) {
var length = Math.sqrt(x * x + y * y + z * z);
this.Points.push({
x: x / length,
y: y / length,
z: z / length
});
return this._index++;
};
Icosahedron3D.prototype._addFace = function (x, y, z) {
this.TriangleIndices.push(x);
this.TriangleIndices.push(y);
this.TriangleIndices.push(z);
};
Icosahedron3D.prototype._refineVertices = function () {
for (var i = 0; i < this._quality; i++) {
var faceCount = this.TriangleIndices.length;
for (var face = 0; face < faceCount; face += 3) {
var x1 = this.TriangleIndices[face];
var y1 = this.TriangleIndices[face + 1];
var z1 = this.TriangleIndices[face + 2];
var x2 = this._getMiddlePoint(x1, y1);
var y2 = this._getMiddlePoint(y1, z1);
var z2 = this._getMiddlePoint(z1, x1);
this._addFace(x1, x2, z2);
this._addFace(y1, y2, x2);
this._addFace(z1, z2, y2);
this._addFace(x2, y2, z2);
}
}
};
Icosahedron3D.prototype._getMiddlePoint = function (p1, p2) {
var firstIsSmaller = p1 < p2;
var smallerIndex = firstIsSmaller ? p1 : p2;
var greaterIndex = firstIsSmaller ? p2 : p1;
var key = (smallerIndex << 32) + greaterIndex;
var p = this._middlePointIndexCache[key];
if (p !== undefined)
p;
var point1 = this.Points[p1];
var point2 = this.Points[p2];
var middle = {
x: (point1.x + point2.x) / 2.0,
y: (point1.y + point2.y) / 2.0,
z: (point1.z + point2.z) / 2.0,
};
var i = this._addVertex(middle.x, middle.y, middle.z);
this._middlePointIndexCache[key] = i;
return i;
};
return Icosahedron3D;
})();
(function () {
var app = new App(document.getElementById('canvas'));
app.Draw();
})();
<body style="background-color: rgb(55,55,55);">
<canvas id="canvas" style="position: absolute;top:0;left:0;width:100%;height:100%;" />
</body>
So the simplest solution seems to be the notion of moving the view instead of the object.
To do this, I keep the existing rotation axis information which is applied to the mov matrix unchanged, but added a new triple to provide rotation axis information to be applied to the view matrix:
var App = (function () {
function App(canvas) {
this._canvas = canvas;
this._ctx = canvas.getContext('webgl');
this._ctx.viewport(0, 0, canvas.width, canvas.height);
this._canvas.setAttribute('width', this._canvas.clientWidth.toString());
this._canvas.setAttribute('height', this._canvas.clientHeight.toString());
this._config =
{
DrawMode: this._ctx.TRIANGLES,
Quality: 3,
ZoomLevel: -4,
MovRotation: {
X: 0.00000,
Y: 0.01000,
Z: 0.00000
},
ViewRotation: {
X: 0.00100,
Y: 0.00000,
Z: 0.00000
}
};
}
App.prototype._setData = function () {
var ctx = this._ctx;
var icosahedron = new Icosahedron3D(this._config.Quality);
this._vertices = icosahedron.Points.reduce(function (a, b, i) { return i === 1 ? [a.x, a.y, a.z, b.x, b.y, b.z] : a.concat([b.x, b.y, b.z]); });
this._indices = icosahedron.TriangleIndices;
this._colors = this._generateColors(this._vertices);
var vertex_buffer = ctx.createBuffer();
ctx.bindBuffer(ctx.ARRAY_BUFFER, vertex_buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(this._vertices), ctx.STATIC_DRAW);
var color_buffer = ctx.createBuffer();
ctx.bindBuffer(ctx.ARRAY_BUFFER, color_buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(this._colors), ctx.STATIC_DRAW);
var index_buffer = ctx.createBuffer();
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, index_buffer);
ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(this._indices), ctx.STATIC_DRAW);
return {
vertex: vertex_buffer,
color: color_buffer,
index: index_buffer
};
};
App.prototype._generateColors = function (vertices) {
var colors = [];
for (var i = 0; i < vertices.length; i += 3) {
var cvalue = 0;
var testvalue = 0;
if (vertices[i] >= 0) testvalue++;
if (vertices[i+1] >= 0) testvalue++;
if (vertices[i+2] >= 0) testvalue++;
else testvalue = 0;
if (testvalue > 0) cvalue = 1;
colors.push(cvalue);
colors.push(cvalue);
colors.push(cvalue);
}
return colors;
}
App.prototype._animate = function (proj_matrix, view_matrix, mov_matrix) {
var _this = this;
var ctx = this._ctx;
var movThetas = this._config.MovRotation;
var viewThetas = this._config.ViewRotation
var time_old = 0;
var zoomLevel_old = 0;
var execAnimation = function (time) {
var dt = time - time_old;
time_old = time;
for (var m_axis in movThetas) {
var theta = movThetas[m_axis];
if (theta > 0.0 || theta < 0.0) {
Matrix[("Rotate" + m_axis)](mov_matrix, dt * theta);
}
}
for (var v_axis in viewThetas) {
var theta = viewThetas[v_axis];
if (theta > 0.0 || theta < 0.0) {
Matrix[("Rotate" + v_axis)](view_matrix, dt * theta);
}
}
if (Math.abs(_this._config.ZoomLevel - zoomLevel_old) >= 0.01) {
view_matrix[14] = view_matrix[14] + (zoomLevel_old * -1) + _this._config.ZoomLevel;
zoomLevel_old = _this._config.ZoomLevel;
}
ctx.enable(ctx.DEPTH_TEST);
ctx.depthFunc(ctx.LEQUAL);
ctx.clearDepth(1.0);
ctx.viewport(0.0, 0.0, _this._canvas.width, _this._canvas.height);
ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT);
ctx.uniformMatrix4fv(_this._shader.Pmatrix, false, proj_matrix);
ctx.uniformMatrix4fv(_this._shader.Vmatrix, false, view_matrix);
ctx.uniformMatrix4fv(_this._shader.Mmatrix, false, mov_matrix);
ctx.drawElements(_this._config.DrawMode, _this._indices.length, ctx.UNSIGNED_SHORT, 0);
window.requestAnimationFrame(execAnimation);
};
execAnimation(0);
};
App.prototype.Draw = function () {
var buffers = this._setData();
this._shader = App.UseQuarternionShaderProgram(this._ctx, buffers.vertex, buffers.color);
var proj_matrix = new Float32Array(Matrix.GetProjection(40, this._canvas.width / this._canvas.height, 1, 100));
var view_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
var mov_matrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
this._animate(proj_matrix, view_matrix, mov_matrix);
};
App.UseQuarternionVertShader = function (context) {
var vertCode = "\n\t\t\tattribute vec3 position;\n\t\t\tattribute highp vec3 aVertexNormal;\n\t\t\t\n\t\t\tuniform mat4 Pmatrix;\n\t\t\tuniform mat4 Vmatrix;\n\t\t\tuniform mat4 Mmatrix;\n\n\t\t\tattribute vec4 color;\n\t\t\tvarying lowp vec4 vColor;\n\n\t\t\tvarying vec3 vLightWeighting;\n\t\t\t\n\t\t\tuniform vec3 uAmbientColor;\n\t\t\tuniform vec3 uPointLightingLocation;\n\t\t\tuniform vec3 uPointLightingColor;\n\n\t\t\tvoid main(void) {\n\t\t\t\tvec4 mvPosition = Mmatrix * vec4(position, 1.);\n\t\t\t\tgl_Position = Pmatrix*Vmatrix*mvPosition;\n\t\t\t\tgl_PointSize = 4.0;\n\t\t\t\tvColor = color;\n\n\t\t\t\tvec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz);\n\t\t\t\tvec3 transformedNormal = vec3(Vmatrix) * aVertexNormal;\n\t\t\t\tfloat directionalLightWeighting = max(dot(transformedNormal, lightDirection), 0.0);\n\t\t\t\tvLightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting;\n\t\t\t}";
var vertShader = context.createShader(context.VERTEX_SHADER);
context.shaderSource(vertShader, vertCode);
context.compileShader(vertShader);
return vertShader;
};
App.UseVariableFragShader = function (context) {
var fragCode = "\n\t\t\tprecision mediump float;\n\t\t\tvarying lowp vec4 vColor;\n\t\t\tvarying vec3 vLightWeighting;\n\t\t\tvoid main(void) {\n\t\t\t\tgl_FragColor = vec4(vColor.rgb, 1.);\n\t\t\t}";
var fragShader = context.createShader(context.FRAGMENT_SHADER);
context.shaderSource(fragShader, fragCode);
context.compileShader(fragShader);
return fragShader;
};
App.UseQuarternionShaderProgram = function (ctx, vertex_buffer, color_buffer) {
var vertShader = App.UseQuarternionVertShader(ctx);
var fragShader = App.UseVariableFragShader(ctx);
var shaderProgram = ctx.createProgram();
ctx.attachShader(shaderProgram, vertShader);
ctx.attachShader(shaderProgram, fragShader);
ctx.linkProgram(shaderProgram);
var Pmatrix = ctx.getUniformLocation(shaderProgram, "Pmatrix");
var Vmatrix = ctx.getUniformLocation(shaderProgram, "Vmatrix");
var Mmatrix = ctx.getUniformLocation(shaderProgram, "Mmatrix");
ctx.bindBuffer(ctx.ARRAY_BUFFER, vertex_buffer);
var position = ctx.getAttribLocation(shaderProgram, "position");
ctx.vertexAttribPointer(position, 3, ctx.FLOAT, false, 0, 0);
ctx.enableVertexAttribArray(position);
ctx.bindBuffer(ctx.ARRAY_BUFFER, color_buffer);
var color = ctx.getAttribLocation(shaderProgram, "color");
ctx.vertexAttribPointer(color, 3, ctx.FLOAT, false, 0, 0);
ctx.enableVertexAttribArray(color);
ctx.useProgram(shaderProgram);
var ambientColor = ctx.getUniformLocation(shaderProgram, "uAmbientColor");
var pointLightingLocation = ctx.getUniformLocation(shaderProgram, "uPointLightingLocation");
var pointLightingColor = ctx.getUniformLocation(shaderProgram, "uPointLightingColor");
ctx.uniform3f(ambientColor, 0.2, 0.2, 0.2);
ctx.uniform3f(pointLightingLocation, 0.0, 0.0, -20.0);
ctx.uniform3f(pointLightingColor, 0.8, 0.8, 0.8);
return {
Pmatrix: Pmatrix,
Vmatrix: Vmatrix,
Mmatrix: Mmatrix,
ShaderProgram: shaderProgram
};
};
return App;
})();
var Matrix = (function () {
function Matrix() {
}
Matrix.GetProjection = function (angle, a, zMin, zMax) {
var ang = Math.tan((angle * .5) * Math.PI / 180);
return [
0.5 / ang, 0, 0, 0,
0, 0.5 * a / ang, 0, 0,
0, 0, -(zMax + zMin) / (zMax - zMin), -1,
0, 0, (-2 * zMax * zMin) / (zMax - zMin), 0
];
};
Matrix.RotateX = function (m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv1 = m[1], mv5 = m[5], mv9 = m[9];
m[1] = m[1] * c - m[2] * s;
m[5] = m[5] * c - m[6] * s;
m[9] = m[9] * c - m[10] * s;
m[2] = m[2] * c + mv1 * s;
m[6] = m[6] * c + mv5 * s;
m[10] = m[10] * c + mv9 * s;
};
Matrix.RotateY = function (m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv0 = m[0], mv4 = m[4], mv8 = m[8];
m[0] = c * m[0] + s * m[2];
m[4] = c * m[4] + s * m[6];
m[8] = c * m[8] + s * m[10];
m[2] = c * m[2] - s * mv0;
m[6] = c * m[6] - s * mv4;
m[10] = c * m[10] - s * mv8;
};
Matrix.RotateZ = function (m, angle) {
var c = Math.cos(angle);
var s = Math.sin(angle);
var mv0 = m[0], mv4 = m[4], mv8 = m[8];
m[0] = c * m[0] - s * m[1];
m[4] = c * m[4] - s * m[5];
m[8] = c * m[8] - s * m[9];
m[1] = c * m[1] + s * mv0;
m[5] = c * m[5] + s * mv4;
m[9] = c * m[9] + s * mv8;
};
Matrix.Translate = function (a, b, c) {
var d = b[0], e = b[1], s = b[2];
if (!c || a == c) {
a[12] = a[0] * d + a[4] * e + a[8] * s + a[12];
a[13] = a[1] * d + a[5] * e + a[9] * s + a[13];
a[14] = a[2] * d + a[6] * e + a[10] * s + a[14];
a[15] = a[3] * d + a[7] * e + a[11] * s + a[15];
return a;
}
var g = a[0], f = a[1], h = a[2], i = a[3], j = a[4], k = a[5], l = a[6], o = a[7], m = a[8], n = a[9], p = a[10], r = a[11];
c[0] = g;
c[1] = f;
c[2] = h;
c[3] = i;
c[4] = j;
c[5] = k;
c[6] = l;
c[7] = o;
c[8] = m;
c[9] = n;
c[10] = p;
c[11] = r;
c[12] = g * d + j * e + m * s + a[12];
c[13] = f * d + k * e + n * s + a[13];
c[14] = h * d + l * e + p * s + a[14];
c[15] = i * d + o * e + r * s + a[15];
return c;
};
;
return Matrix;
})();
var Icosahedron3D = (function () {
function Icosahedron3D(quality) {
this._quality = quality;
this._calculateGeometry();
}
Icosahedron3D.prototype._calculateGeometry = function () {
this.Points = [];
this.TriangleIndices = [];
this._middlePointIndexCache = {};
this._index = 0;
var t = (1.0 + Math.sqrt(5.0)) / 2.0;
this._addVertex(-1, t, 0);
this._addVertex(1, t, 0);
this._addVertex(-1, -t, 0);
this._addVertex(1, -t, 0);
this._addVertex(0, -1, t);
this._addVertex(0, 1, t);
this._addVertex(0, -1, -t);
this._addVertex(0, 1, -t);
this._addVertex(t, 0, -1);
this._addVertex(t, 0, 1);
this._addVertex(-t, 0, -1);
this._addVertex(-t, 0, 1);
this._addFace(0, 11, 5);
this._addFace(0, 5, 1);
this._addFace(0, 1, 7);
this._addFace(0, 7, 10);
this._addFace(0, 10, 11);
this._addFace(1, 5, 9);
this._addFace(5, 11, 4);
this._addFace(11, 10, 2);
this._addFace(10, 7, 6);
this._addFace(7, 1, 8);
this._addFace(3, 9, 4);
this._addFace(3, 4, 2);
this._addFace(3, 2, 6);
this._addFace(3, 6, 8);
this._addFace(3, 8, 9);
this._addFace(4, 9, 5);
this._addFace(2, 4, 11);
this._addFace(6, 2, 10);
this._addFace(8, 6, 7);
this._addFace(9, 8, 1);
this._refineVertices();
};
Icosahedron3D.prototype._addVertex = function (x, y, z) {
var length = Math.sqrt(x * x + y * y + z * z);
this.Points.push({
x: x / length,
y: y / length,
z: z / length
});
return this._index++;
};
Icosahedron3D.prototype._addFace = function (x, y, z) {
this.TriangleIndices.push(x);
this.TriangleIndices.push(y);
this.TriangleIndices.push(z);
};
Icosahedron3D.prototype._refineVertices = function () {
for (var i = 0; i < this._quality; i++) {
var faceCount = this.TriangleIndices.length;
for (var face = 0; face < faceCount; face += 3) {
var x1 = this.TriangleIndices[face];
var y1 = this.TriangleIndices[face + 1];
var z1 = this.TriangleIndices[face + 2];
var x2 = this._getMiddlePoint(x1, y1);
var y2 = this._getMiddlePoint(y1, z1);
var z2 = this._getMiddlePoint(z1, x1);
this._addFace(x1, x2, z2);
this._addFace(y1, y2, x2);
this._addFace(z1, z2, y2);
this._addFace(x2, y2, z2);
}
}
};
Icosahedron3D.prototype._getMiddlePoint = function (p1, p2) {
var firstIsSmaller = p1 < p2;
var smallerIndex = firstIsSmaller ? p1 : p2;
var greaterIndex = firstIsSmaller ? p2 : p1;
var key = (smallerIndex << 32) + greaterIndex;
var p = this._middlePointIndexCache[key];
if (p !== undefined)
p;
var point1 = this.Points[p1];
var point2 = this.Points[p2];
var middle = {
x: (point1.x + point2.x) / 2.0,
y: (point1.y + point2.y) / 2.0,
z: (point1.z + point2.z) / 2.0,
};
var i = this._addVertex(middle.x, middle.y, middle.z);
this._middlePointIndexCache[key] = i;
return i;
};
return Icosahedron3D;
})();
(function () {
var app = new App(document.getElementById('canvas'));
app.Draw();
})();
<body style="background-color: rgb(55,55,55);">
<canvas id="canvas" style="position: absolute;top:0;left:0;width:100%;height:100%;" />
</body>
Related
I am trying to create a portal rendering engine in WebGL 2.0 (so far I am trying to port this C++ project: https://github.com/HackerPoet/NonEuclidean), and am trying to implement oblique projection in order to align the clipping plane of the portal cameras to their respective portals; despite finding multiple implementations across various OpenGL platforms, however, I can't get it to work in my WebGL project. Here is the code related to it:
const Mat4 = (() =>
{
const { mat3, mat4, vec2, vec3, vec4 } = glMatrix;
return {
...mat4,
zAxis(out, mat)
{
return vec3.set(out, mat[2], mat[6], mat[10]);
},
mulVec4(out, mat, vec)
{
const [x, y, z, w] = vec;
return vec3.set(out,
mat[ 0] * x + mat[ 1] * y + mat[ 2] * z + mat[ 3] * w,
mat[ 4] * x + mat[ 5] * y + mat[ 6] * z + mat[ 7] * w,
mat[ 8] * x + mat[ 9] * y + mat[10] * z + mat[11] * w,
mat[12] * x + mat[13] * y + mat[14] * z + mat[15] * w);
},
mulPoint(out, mat, vec)
{
const [x, y, z] = vec;
const w = mat[12] * x + mat[13] * y + mat[14] * z + mat[15];
return vec3.set(out,
(mat[0] * x + mat[1] * y + mat[ 2] * z + mat[ 3]) / w,
(mat[4] * x + mat[5] * y + mat[ 6] * z + mat[ 7]) / w,
(mat[8] * x + mat[9] * y + mat[10] * z + mat[11]) / w);
},
};
})();
const Camera = () =>
{
const { vec3, vec4 } = glMatrix;
const projection = Mat4.create();
const view = Mat4.create();
return {
width: 0,
height: 0,
aspect: 1,
near: 0.1,
far: 100.0,
fov: Math.PI / 3,
pos: [0, 0, 0],
euler: [0, 0, 0],
get view()
{
return view;
},
set view(matrix)
{
return Mat4.copy(view, matrix);
},
get matrix()
{
return Mat4.multiply(Mat4.create(), projection, view)
},
get projection()
{
return projection;
},
set transform([x = 0, y = 0, z = 0, rx = 0, ry = 0])
{
const matrix = Mat4.fromXRotation(Mat4.create(), rx);
Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), ry));
Mat4.multiply(matrix, matrix, Mat4.fromTranslation(Mat4.create(), [-x, -y, -z]));
Mat4.copy(view, matrix);
return view;
},
inverse()
{
const inv = Mat4.create();
const a = projection[0];
const b = projection[5];
const c = projection[10];
const d = projection[11];
const e = projection[14];
inv[0] = 1 / a;
inv[5] = 1 / b;
inv[11] = 1 / e;
inv[14] = 1 / d;
inv[15] = -c / (d * e)
return inv;
},
clipOblique(pos, norm)
{
const cpos = Mat4.mulPoint(vec3.create(), view, pos);
const cnorm = vec3.normalize(vec3.create(), Mat4.mulPoint(vec3.create(), view, norm));
const point = Mat4.mulPoint(vec3.create(), Mat4.invert(Mat4.create(), view), [0, 0, 0]);
cpos[1] -= point[1];
const cplane = vec4.set(vec4.create(), cnorm[0], cnorm[1], cnorm[2], -vec3.dot(cpos, cnorm));
const q = Mat4.mulVec4(vec4.create(), Mat4.invert(Mat4.create(), projection), [
(cplane[0] > 0 ? 1 : -1),
(cplane[1] > 0 ? 1 : -1),
1,
1]);
const c = cplane.map(x => x * 2 / vec4.dot(cplane, q));
projection[ 2] = c[0] - projection[ 3];
projection[ 6] = c[1] - projection[ 7];
projection[10] = c[2] - projection[11];
projection[14] = c[3] - projection[15];
return this;
},
copy(that)
{
this.setup(that.width, that.height, that.near, that.far, that.fov, that.aspect);
Mat4.copy(view, that.view);
Mat4.copy(projection, that.projection);
return this;
},
setup(width = this.width, height = this.height, near = this.near, far = this.far, fov = this.fov, aspect = height / width)
{
this.width = width;
this.height = height;
this.near = near;
this.far = far;
this.fov = fov;
this.aspect = aspect;
const f = 1.0 / Math.tan(fov / 2);
const range = 1.0 / (near - far);
projection[0] = f * aspect
projection[1] = 0.0;
projection[2] = 0.0;
projection[3] = 0.0;
projection[4] = 0.0;
projection[5] = f;
projection[6] = 0.0;
projection[7] = 0.0;
projection[8] = 0.0;
projection[9] = 0.0;
projection[10] = (near + far) * range;
projection[11] = -1.0;
projection[12] = 0.0;
projection[13] = 0.0;
projection[14] = 2 * near * far * range;
projection[15] = 0.0;
return this;
}
}
};
const GameObject = (gl, mesh) =>
{
const { vec3 } = glMatrix;
return {
pos: [0, 0, 0],
euler: [0, 0, 0],
scale: [1, 1, 1],
mesh,
forward()
{
const matrix = Mat4.fromZRotation(Mat4.create(), this.euler[2]);
Mat4.multiply(matrix, matrix, Mat4.fromXRotation(Mat4.create(), this.euler[0]));
Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), this.euler[1]));
return vec3.negate(vec3.create(), Mat4.zAxis(vec3.create(), matrix));
},
localToWorld()
{
const matrix = Mat4.fromTranslation(Mat4.create(), this.pos);
Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), this.euler[1]));
Mat4.multiply(matrix, matrix, Mat4.fromXRotation(Mat4.create(), this.euler[0]));
Mat4.multiply(matrix, matrix, Mat4.fromZRotation(Mat4.create(), this.euler[2]));
Mat4.multiply(matrix, matrix, Mat4.fromScaling(Mat4.create(), this.scale));
return matrix;
},
worldToLocal()
{
const matrix = Mat4.fromScaling(Mat4.create(), this.scale);
Mat4.invert(matrix, matrix);
Mat4.multiply(matrix, matrix, Mat4.fromZRotation(Mat4.create(), this.euler[2]));
Mat4.multiply(matrix, matrix, Mat4.fromXRotation(Mat4.create(), this.euler[0]));
Mat4.multiply(matrix, matrix, Mat4.fromYRotation(Mat4.create(), this.euler[1]));
Mat4.multiply(matrix, matrix, Mat4.fromTranslation(Mat4.create(), vec3.negate(vec3.create(), this.pos)));
return matrix;
},
render(camera, fbo = null)
{
const mv = Mat4.transpose(Mat4.create(), this.worldToLocal());
const mvp = Mat4.multiply(Mat4.create(), camera.matrix, this.localToWorld());
this.mesh.render(mvp, mv);
return this;
},
};
};
const Portal = (() =>
{
const { vec3 } = glMatrix;
const Warp = (fromPortal = {}) =>
{
const delta = Mat4.identity(Mat4.create());
const deltaInv = Mat4.identity(Mat4.create());
return {
fromPortal,
toPortal: null,
delta, deltaInv,
};
};
return {
connectWarp(a, b)
{
a.toPortal = b.fromPortal;
b.toPortal = a.toPortal;
a.delta = Mat4.multiply(Mat4.create(), a.fromPortal.localToWorld(), b.fromPortal.worldToLocal());
b.delta = Mat4.multiply(Mat4.create(), b.fromPortal.localToWorld(), a.fromPortal.worldToLocal());
a.deltaInv = b.delta;
b.deltaInv = a.delta;
return this;
},
connect(a, b)
{
this.connectWarp(a.front, b.back);
this.connectWarp(b.front, a.back);
return this;
},
create: async (gl) =>
{
const mesh = await Mesh.load('double_quad.obj');
const shader = await Shader(gl, '.', 'portal-shader');
const { a_position } = shader.attribute;
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const buffers = createBuffers(gl, new Array(1));
setupArrayBuffer(gl, buffers[0], new Float32Array(mesh.geometries[0].data.position), 3, a_position);
const portalCam = Camera();
return (self =>
{
self.front = Warp(self);
self.back = Warp(self);
return self;
})({
...GameObject(gl, null),
front: null,
back: null,
get cam()
{
return portalCam;
},
/*Override*/ render(camera, fbo, func)
{
console.assert(this.euler[0] === 0);
console.assert(this.euler[2] === 0);
const normal = this.forward();
const camPos = Mat4.getTranslation(vec3.create(), Mat4.invert(Mat4.create(), camera.view));
const isFront = vec3.dot(vec3.subtract(vec3.create(), camPos, this.pos), normal) > 0;
if (isFront)
{
vec3.negate(normal, normal);
}
const warp = isFront? this.front : this.back;
const mvp = Mat4.multiply(Mat4.create(), camera.matrix, this.localToWorld());
portalCam.copy(camera);
portalCam.clipOblique(vec3.add(vec3.create(), this.pos, normal.map(x => x * 0.1)), vec3.negate(vec3.create(), normal));
Mat4.multiply(portalCam.view, portalCam.view, warp.delta);
shader.use();
gl.bindVertexArray(vao);
const { u_mvp, u_texture } = shader.uniform;
gl.uniform1i(u_texture, 0);
gl.activeTexture(gl.TEXTURE0 + 0);
fbo.use();
gl.uniformMatrix4fv(u_mvp, false, mvp);
gl.drawArrays(gl.TRIANGLES, 0, mesh.geometries[0].data.position.length / 3);
return this;
},
});
},
};
})();
At first the portal seems normal, but when I try to go behind it, the objects are still rendered (if I position myself so that the portal's camera is behind them), but in an unusual way. I am not very good at matrix math or linear algebra, and would appreciate some help/insight; thank you.
I have in Ui and I dont know how to solve it.
I Use canvas in my project and base on id it choose what should be happen
Hear is my cod:
<div class="item">
<div class="w3_weather_scroll">
<h4>3:00 PM</h4>
<h5>-5°</h5>
<canvas id="rain" width="30" name="name" height="30"></canvas>
</div>
</div>
<div class="item">
<div class="w3_weather_scroll">
<h4>5:00 PM</h4>
<h5>35°</h5>
<canvas id="wind" width="30" name="name" height="30"></canvas>
</div>
java script:
var icons = new Skycons({"color": "#fff"}),
list = [
document.getElementsByName("name")[0].id,
document.getElementsByName("name")[1].className,
document.getElementsByName("name")[2].id,
document.getElementsByName("name")[3].id,
document.getElementsByName("name")[4].id,
document.getElementsByName("name")[5].id,
document.getElementsByName("name")[6].id,
document.getElementsByName("name")[7].id,
],
i;
document.getElementById("demo").innerHTML=document.getElementsByName("name")[0].id;
for(i = list.length; i--; )
icons.set(list[i], list[i]);
icons.play();
for example if first id is "rain" and second id is"wind" , for first one we have rainy cloud img and second wind .
(function(global) {
"use strict";
/* Set up a RequestAnimationFrame shim so we can animate efficiently FOR
* GREAT JUSTICE. */
var requestInterval, cancelInterval;
(function() {
var raf = global.requestAnimationFrame ||
global.webkitRequestAnimationFrame ||
global.mozRequestAnimationFrame ||
global.oRequestAnimationFrame ||
global.msRequestAnimationFrame ,
caf = global.cancelAnimationFrame ||
global.webkitCancelAnimationFrame ||
global.mozCancelAnimationFrame ||
global.oCancelAnimationFrame ||
global.msCancelAnimationFrame ;
if(raf && caf) {
requestInterval = function(fn, delay) {
var handle = {value: null};
function loop() {
handle.value = raf(loop);
fn();
}
loop();
return handle;
};
cancelInterval = function(handle) {
caf(handle.value);
};
}
else {
requestInterval = setInterval;
cancelInterval = clearInterval;
}
}());
/* Define skycon things. */
/* FIXME: I'm *really really* sorry that this code is so gross. Really, I am.
* I'll try to clean it up eventually! Promise! */
var KEYFRAME = 500,
STROKE = 0.08,
TAU = 2.0 * Math.PI,
TWO_OVER_SQRT_2 = 2.0 / Math.sqrt(2);
function circle(ctx, x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, TAU, false);
ctx.fill();
}
function line(ctx, ax, ay, bx, by) {
ctx.beginPath();
ctx.moveTo(ax, ay);
ctx.lineTo(bx, by);
ctx.stroke();
}
function puff(ctx, t, cx, cy, rx, ry, rmin, rmax) {
var c = Math.cos(t * TAU),
s = Math.sin(t * TAU);
rmax -= rmin;
circle(
ctx,
cx - s * rx,
cy + c * ry + rmax * 0.5,
rmin + (1 - c * 0.5) * rmax
);
}
function puffs(ctx, t, cx, cy, rx, ry, rmin, rmax) {
var i;
for(i = 5; i--; )
puff(ctx, t + i / 5, cx, cy, rx, ry, rmin, rmax);
}
function cloud(ctx, t, cx, cy, cw, s, color) {
t /= 30000;
var a = cw * 0.21,
b = cw * 0.12,
c = cw * 0.24,
d = cw * 0.28;
ctx.fillStyle = color;
puffs(ctx, t, cx, cy, a, b, c, d);
ctx.globalCompositeOperation = 'destination-out';
puffs(ctx, t, cx, cy, a, b, c - s, d - s);
ctx.globalCompositeOperation = 'source-over';
}
function sun(ctx, t, cx, cy, cw, s, color) {
t /= 40000;
var a = cw * 0.25 - s * 0.5,
b = cw * 0.32 + s * 0.5,
c = cw * 0.50 - s * 0.5,
i, p, cos, sin;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.arc(cx, cy, a, 0, TAU, false);
ctx.stroke();
for(i = 8; i--; ) {
p = (t + i / 8) * TAU;
cos = Math.cos(p);
sin = Math.sin(p);
line(ctx, cx + cos * b, cy + sin * b, cx + cos * c, cy + sin * c);
}
}
function moon(ctx, t, cx, cy, cw, s, color) {
t /= 15000;
var a = cw * 0.29 - s * 0.5,
b = cw * 0.05,
c = Math.cos(t * TAU),
p = c * TAU / -16;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
cx += c * b;
ctx.beginPath();
ctx.arc(cx, cy, a, p + TAU / 8, p + TAU * 7 / 8, false);
ctx.arc(cx + Math.cos(p) * a * TWO_OVER_SQRT_2, cy + Math.sin(p) * a * TWO_OVER_SQRT_2, a, p + TAU * 5 / 8, p + TAU * 3 / 8, true);
ctx.closePath();
ctx.stroke();
}
function rain(ctx, t, cx, cy, cw, s, color) {
t /= 1350;
var a = cw * 0.16,
b = TAU * 11 / 12,
c = TAU * 7 / 12,
i, p, x, y;
ctx.fillStyle = color;
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = cx + ((i - 1.5) / 1.5) * (i === 1 || i === 2 ? -1 : 1) * a;
y = cy + p * p * cw;
ctx.beginPath();
ctx.moveTo(x, y - s * 1.5);
ctx.arc(x, y, s * 0.75, b, c, false);
ctx.fill();
}
}
function sleet(ctx, t, cx, cy, cw, s, color) {
t /= 750;
var a = cw * 0.1875,
b = TAU * 11 / 12,
c = TAU * 7 / 12,
i, p, x, y;
ctx.strokeStyle = color;
ctx.lineWidth = s * 0.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = Math.floor(cx + ((i - 1.5) / 1.5) * (i === 1 || i === 2 ? -1 : 1) * a) + 0.5;
y = cy + p * cw;
line(ctx, x, y - s * 1.5, x, y + s * 1.5);
}
}
function snow(ctx, t, cx, cy, cw, s, color) {
t /= 3000;
var a = cw * 0.16,
b = s * 0.75,
u = t * TAU * 0.7,
ux = Math.cos(u) * b,
uy = Math.sin(u) * b,
v = u + TAU / 3,
vx = Math.cos(v) * b,
vy = Math.sin(v) * b,
w = u + TAU * 2 / 3,
wx = Math.cos(w) * b,
wy = Math.sin(w) * b,
i, p, x, y;
ctx.strokeStyle = color;
ctx.lineWidth = s * 0.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = cx + Math.sin((p + i / 4) * TAU) * a;
y = cy + p * cw;
line(ctx, x - ux, y - uy, x + ux, y + uy);
line(ctx, x - vx, y - vy, x + vx, y + vy);
line(ctx, x - wx, y - wy, x + wx, y + wy);
}
}
function fogbank(ctx, t, cx, cy, cw, s, color) {
t /= 30000;
var a = cw * 0.21,
b = cw * 0.06,
c = cw * 0.21,
d = cw * 0.28;
ctx.fillStyle = color;
puffs(ctx, t, cx, cy, a, b, c, d);
ctx.globalCompositeOperation = 'destination-out';
puffs(ctx, t, cx, cy, a, b, c - s, d - s);
ctx.globalCompositeOperation = 'source-over';
}
/*
var WIND_PATHS = [
downsample(63, upsample(8, [
-1.00, -0.28,
-0.75, -0.18,
-0.50, 0.12,
-0.20, 0.12,
-0.04, -0.04,
-0.07, -0.18,
-0.19, -0.18,
-0.23, -0.05,
-0.12, 0.11,
0.02, 0.16,
0.20, 0.15,
0.50, 0.07,
0.75, 0.18,
1.00, 0.28
])),
downsample(31, upsample(16, [
-1.00, -0.10,
-0.75, 0.00,
-0.50, 0.10,
-0.25, 0.14,
0.00, 0.10,
0.25, 0.00,
0.50, -0.10,
0.75, -0.14,
1.00, -0.10
]))
];
*/
var WIND_PATHS = [
[
-0.7500, -0.1800, -0.7219, -0.1527, -0.6971, -0.1225,
-0.6739, -0.0910, -0.6516, -0.0588, -0.6298, -0.0262,
-0.6083, 0.0065, -0.5868, 0.0396, -0.5643, 0.0731,
-0.5372, 0.1041, -0.5033, 0.1259, -0.4662, 0.1406,
-0.4275, 0.1493, -0.3881, 0.1530, -0.3487, 0.1526,
-0.3095, 0.1488, -0.2708, 0.1421, -0.2319, 0.1342,
-0.1943, 0.1217, -0.1600, 0.1025, -0.1290, 0.0785,
-0.1012, 0.0509, -0.0764, 0.0206, -0.0547, -0.0120,
-0.0378, -0.0472, -0.0324, -0.0857, -0.0389, -0.1241,
-0.0546, -0.1599, -0.0814, -0.1876, -0.1193, -0.1964,
-0.1582, -0.1935, -0.1931, -0.1769, -0.2157, -0.1453,
-0.2290, -0.1085, -0.2327, -0.0697, -0.2240, -0.0317,
-0.2064, 0.0033, -0.1853, 0.0362, -0.1613, 0.0672,
-0.1350, 0.0961, -0.1051, 0.1213, -0.0706, 0.1397,
-0.0332, 0.1512, 0.0053, 0.1580, 0.0442, 0.1624,
0.0833, 0.1636, 0.1224, 0.1615, 0.1613, 0.1565,
0.1999, 0.1500, 0.2378, 0.1402, 0.2749, 0.1279,
0.3118, 0.1147, 0.3487, 0.1015, 0.3858, 0.0892,
0.4236, 0.0787, 0.4621, 0.0715, 0.5012, 0.0702,
0.5398, 0.0766, 0.5768, 0.0890, 0.6123, 0.1055,
0.6466, 0.1244, 0.6805, 0.1440, 0.7147, 0.1630,
0.7500, 0.1800
],
[
-0.7500, 0.0000, -0.7033, 0.0195, -0.6569, 0.0399,
-0.6104, 0.0600, -0.5634, 0.0789, -0.5155, 0.0954,
-0.4667, 0.1089, -0.4174, 0.1206, -0.3676, 0.1299,
-0.3174, 0.1365, -0.2669, 0.1398, -0.2162, 0.1391,
-0.1658, 0.1347, -0.1157, 0.1271, -0.0661, 0.1169,
-0.0170, 0.1046, 0.0316, 0.0903, 0.0791, 0.0728,
0.1259, 0.0534, 0.1723, 0.0331, 0.2188, 0.0129,
0.2656, -0.0064, 0.3122, -0.0263, 0.3586, -0.0466,
0.4052, -0.0665, 0.4525, -0.0847, 0.5007, -0.1002,
0.5497, -0.1130, 0.5991, -0.1240, 0.6491, -0.1325,
0.6994, -0.1380, 0.7500, -0.1400
]
],
WIND_OFFSETS = [
{start: 0.36, end: 0.11},
{start: 0.56, end: 0.16}
];
function leaf(ctx, t, x, y, cw, s, color) {
var a = cw / 8,
b = a / 3,
c = 2 * b,
d = (t % 1) * TAU,
e = Math.cos(d),
f = Math.sin(d);
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.arc(x , y , a, d , d + Math.PI, false);
ctx.arc(x - b * e, y - b * f, c, d + Math.PI, d , false);
ctx.arc(x + c * e, y + c * f, b, d + Math.PI, d , true );
ctx.globalCompositeOperation = 'destination-out';
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
ctx.stroke();
}
function swoosh(ctx, t, cx, cy, cw, s, index, total, color) {
t /= 2500;
var path = WIND_PATHS[index],
a = (t + index - WIND_OFFSETS[index].start) % total,
c = (t + index - WIND_OFFSETS[index].end ) % total,
e = (t + index ) % total,
b, d, f, i;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
if(a < 1) {
ctx.beginPath();
a *= path.length / 2 - 1;
b = Math.floor(a);
a -= b;
b *= 2;
b += 2;
ctx.moveTo(
cx + (path[b - 2] * (1 - a) + path[b ] * a) * cw,
cy + (path[b - 1] * (1 - a) + path[b + 1] * a) * cw
);
if(c < 1) {
c *= path.length / 2 - 1;
d = Math.floor(c);
c -= d;
d *= 2;
d += 2;
for(i = b; i !== d; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.lineTo(
cx + (path[d - 2] * (1 - c) + path[d ] * c) * cw,
cy + (path[d - 1] * (1 - c) + path[d + 1] * c) * cw
);
}
else
for(i = b; i !== path.length; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.stroke();
}
else if(c < 1) {
ctx.beginPath();
c *= path.length / 2 - 1;
d = Math.floor(c);
c -= d;
d *= 2;
d += 2;
ctx.moveTo(cx + path[0] * cw, cy + path[1] * cw);
for(i = 2; i !== d; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.lineTo(
cx + (path[d - 2] * (1 - c) + path[d ] * c) * cw,
cy + (path[d - 1] * (1 - c) + path[d + 1] * c) * cw
);
ctx.stroke();
}
if(e < 1) {
e *= path.length / 2 - 1;
f = Math.floor(e);
e -= f;
f *= 2;
f += 2;
leaf(
ctx,
t,
cx + (path[f - 2] * (1 - e) + path[f ] * e) * cw,
cy + (path[f - 1] * (1 - e) + path[f + 1] * e) * cw,
cw,
s,
color
);
}
}
var Skycons = function(opts) {
this.list = [];
this.interval = null;
this.color = opts && opts.color ? opts.color : "black";
this.resizeClear = !!(opts && opts.resizeClear);
};
Skycons.CLEAR_DAY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sun(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.CLEAR_NIGHT = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
moon(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.PARTLY_CLOUDY_DAY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sun(ctx, t, w * 0.625, h * 0.375, s * 0.75, s * STROKE, color);
cloud(ctx, t, w * 0.375, h * 0.625, s * 0.75, s * STROKE, color);
};
Skycons.PARTLY_CLOUDY_NIGHT = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
moon(ctx, t, w * 0.667, h * 0.375, s * 0.75, s * STROKE, color);
cloud(ctx, t, w * 0.375, h * 0.625, s * 0.75, s * STROKE, color);
};
Skycons.CLOUDY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
cloud(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.RAIN = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
rain(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.SLEET = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sleet(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.SNOW = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
snow(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.WIND = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
swoosh(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, 0, 2, color);
swoosh(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, 1, 2, color);
};
Skycons.FOG = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h),
k = s * STROKE;
fogbank(ctx, t, w * 0.5, h * 0.32, s * 0.75, k, color);
t /= 5000;
var a = Math.cos((t ) * TAU) * s * 0.02,
b = Math.cos((t + 0.25) * TAU) * s * 0.02,
c = Math.cos((t + 0.50) * TAU) * s * 0.02,
d = Math.cos((t + 0.75) * TAU) * s * 0.02,
n = h * 0.936,
e = Math.floor(n - k * 0.5) + 0.5,
f = Math.floor(n - k * 2.5) + 0.5;
ctx.strokeStyle = color;
ctx.lineWidth = k;
ctx.lineCap = "round";
ctx.lineJoin = "round";
line(ctx, a + w * 0.2 + k * 0.5, e, b + w * 0.8 - k * 0.5, e);
line(ctx, c + w * 0.2 + k * 0.5, f, d + w * 0.8 - k * 0.5, f);
};
Skycons.prototype = {
_determineDrawingFunction: function(draw) {
if(typeof draw === "string")
draw = Skycons[draw.toUpperCase().replace(/-/g, "_")] || null;
return draw;
},
add: function(el, draw) {
var obj;
if(typeof el === "string")
el = document.getElementById(el);
// Does nothing if canvas name doesn't exists
if(el === null)
return;
draw = this._determineDrawingFunction(draw);
// Does nothing if the draw function isn't actually a function
if(typeof draw !== "function")
return;
obj = {
element: el,
context: el.getContext("2d"),
drawing: draw
};
this.list.push(obj);
this.draw(obj, KEYFRAME);
},
set: function(el, draw) {
var i;
if(typeof el === "string")
el = document.getElementById(el);
for(i = this.list.length; i--; )
if(this.list[i].element === el) {
this.list[i].drawing = this._determineDrawingFunction(draw);
this.draw(this.list[i], KEYFRAME);
return;
}
this.add(el, draw);
},
remove: function(el) {
var i;
if(typeof el === "string")
el = document.getElementById(el);
for(i = this.list.length; i--; )
if(this.list[i].element === el) {
this.list.splice(i, 1);
return;
}
},
draw: function(obj, time) {
var canvas = obj.context.canvas;
if(this.resizeClear)
canvas.width = canvas.width;
else
obj.context.clearRect(0, 0, canvas.width, canvas.height);
obj.drawing(obj.context, time, this.color);
},
play: function() {
var self = this;
this.pause();
this.interval = requestInterval(function() {
var now = Date.now(),
i;
for(i = self.list.length; i--; )
self.draw(self.list[i], now);
}, 1000 / 60);
},
pause: function() {
var i;
if(this.interval) {
cancelInterval(this.interval);
this.interval = null;
}
}
};
global.Skycons = Skycons;
}(this));
hear is jascript:
But if both of them is rain it just show rain img for first one .
In HTML, id must be unique, so you can only target the first occurrence of a given id.
You should use a class instead to target multiple elements with the same class, using document.getElementsByClassName("rain").
We can not help you more without your Javascript code.
I would like to have more than one Three.js transition in one page but it only displays the second instance. I know it is possible because I have read some other questions on the subject but I'm very new to Three.js and I still don't understand how I would do it in this specific instance. Thanks in advance!
Codepen here:
https://codepen.io/mastroneel/pen/pQqWKd
window.onload = init;
console.ward = function() {}; // what warnings?
function init() {
var root = new THREERoot({
createCameraControls: !true,
antialias: (window.devicePixelRatio === 1),
fov: 80
});
root.renderer.setClearColor(0x000000, 0);
root.renderer.setPixelRatio(window.devicePixelRatio || 1);
root.camera.position.set(0, 0, 60);
var width = 100;
var height = 100;
var slide = new Slide(width, height, 'out');
var l1 = new THREE.ImageLoader();
l1.setCrossOrigin('Anonymous');
slide.setImage(l1.load('https://image.ibb.co/f6mVsA/helmet.png'));
root.scene.add(slide);
var slide2 = new Slide(width, height, 'in');
var l2 = new THREE.ImageLoader();
l2.setCrossOrigin('Anonymous');
slide2.setImage(l2.load('https://image.ibb.co/mb1KkV/player.png'));
root.scene.add(slide2);
var tl = new TimelineMax({repeat:-1, repeatDelay:1.0, yoyo: true});
tl.add(slide.transition(), 0);
tl.add(slide2.transition(), 0);
createTweenScrubber(tl);
window.addEventListener('keyup', function(e) {
if (e.keyCode === 80) {
tl.paused(!tl.paused());
}
});
}
////////////////////
// CLASSES
////////////////////
function Slide(width, height, animationPhase) {
var plane = new THREE.PlaneGeometry(width, height, width * 2, height * 2);
THREE.BAS.Utils.separateFaces(plane);
var geometry = new SlideGeometry(plane);
geometry.bufferUVs();
var aAnimation = geometry.createAttribute('aAnimation', 2);
var aStartPosition = geometry.createAttribute('aStartPosition', 3);
var aControl0 = geometry.createAttribute('aControl0', 3);
var aControl1 = geometry.createAttribute('aControl1', 3);
var aEndPosition = geometry.createAttribute('aEndPosition', 3);
var i, i2, i3, i4, v;
var minDuration = 0.8;
var maxDuration = 1.2;
var maxDelayX = 0.9;
var maxDelayY = 0.125;
var stretch = 0.11;
this.totalDuration = maxDuration + maxDelayX + maxDelayY + stretch;
var startPosition = new THREE.Vector3();
var control0 = new THREE.Vector3();
var control1 = new THREE.Vector3();
var endPosition = new THREE.Vector3();
var tempPoint = new THREE.Vector3();
function getControlPoint0(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.1, 0.3) * 50;
tempPoint.y = signY * THREE.Math.randFloat(0.1, 0.3) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
function getControlPoint1(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.3, 0.6) * 50;
tempPoint.y = -signY * THREE.Math.randFloat(0.3, 0.6) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
for (i = 0, i2 = 0, i3 = 0, i4 = 0; i < geometry.faceCount; i++, i2 += 6, i3 += 9, i4 += 12) {
var face = plane.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(plane, face);
// animation
var duration = THREE.Math.randFloat(minDuration, maxDuration);
var delayX = THREE.Math.mapLinear(centroid.x, -width * 0.5, width * 0.5, 0.0, maxDelayX);
var delayY;
if (animationPhase === 'in') {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, 0.0, maxDelayY)
}
else {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, maxDelayY, 0.0)
}
for (v = 0; v < 6; v += 2) {
aAnimation.array[i2 + v] = delayX + delayY + (Math.random() * stretch * duration);
aAnimation.array[i2 + v + 1] = duration;
}
// positions
endPosition.copy(centroid);
startPosition.copy(centroid);
if (animationPhase === 'in') {
control0.copy(centroid).sub(getControlPoint0(centroid));
control1.copy(centroid).sub(getControlPoint1(centroid));
}
else { // out
control0.copy(centroid).add(getControlPoint0(centroid));
control1.copy(centroid).add(getControlPoint1(centroid));
}
for (v = 0; v < 9; v += 3) {
aStartPosition.array[i3 + v] = startPosition.x;
aStartPosition.array[i3 + v + 1] = startPosition.y;
aStartPosition.array[i3 + v + 2] = startPosition.z;
aControl0.array[i3 + v] = control0.x;
aControl0.array[i3 + v + 1] = control0.y;
aControl0.array[i3 + v + 2] = control0.z;
aControl1.array[i3 + v] = control1.x;
aControl1.array[i3 + v + 1] = control1.y;
aControl1.array[i3 + v + 2] = control1.z;
aEndPosition.array[i3 + v] = endPosition.x;
aEndPosition.array[i3 + v + 1] = endPosition.y;
aEndPosition.array[i3 + v + 2] = endPosition.z;
}
}
var material = new THREE.BAS.BasicAnimationMaterial(
{
shading: THREE.FlatShading,
side: THREE.DoubleSide,
uniforms: {
uTime: {type: 'f', value: 0}
},
shaderFunctions: [
THREE.BAS.ShaderChunk['cubic_bezier'],
//THREE.BAS.ShaderChunk[(animationPhase === 'in' ? 'ease_out_cubic' : 'ease_in_cubic')],
THREE.BAS.ShaderChunk['ease_in_out_cubic'],
THREE.BAS.ShaderChunk['quaternion_rotation']
],
shaderParameters: [
'uniform float uTime;',
'attribute vec2 aAnimation;',
'attribute vec3 aStartPosition;',
'attribute vec3 aControl0;',
'attribute vec3 aControl1;',
'attribute vec3 aEndPosition;',
],
shaderVertexInit: [
'float tDelay = aAnimation.x;',
'float tDuration = aAnimation.y;',
'float tTime = clamp(uTime - tDelay, 0.0, tDuration);',
'float tProgress = ease(tTime, 0.0, 1.0, tDuration);'
//'float tProgress = tTime / tDuration;'
],
shaderTransformPosition: [
(animationPhase === 'in' ? 'transformed *= tProgress;' : 'transformed *= 1.0 - tProgress;'),
'transformed += cubicBezier(aStartPosition, aControl0, aControl1, aEndPosition, tProgress);'
]
},
{
map: new THREE.Texture(),
}
);
THREE.Mesh.call(this, geometry, material);
this.frustumCulled = false;
}
Slide.prototype = Object.create(THREE.Mesh.prototype);
Slide.prototype.constructor = Slide;
Object.defineProperty(Slide.prototype, 'time', {
get: function () {
return this.material.uniforms['uTime'].value;
},
set: function (v) {
this.material.uniforms['uTime'].value = v;
}
});
Slide.prototype.setImage = function(image) {
this.material.uniforms.map.value.image = image;
this.material.uniforms.map.value.needsUpdate = true;
};
Slide.prototype.transition = function() {
return TweenMax.fromTo(this, 3.0, {time:0.0}, {time:this.totalDuration, ease:Power0.easeInOut});
};
function SlideGeometry(model) {
THREE.BAS.ModelBufferGeometry.call(this, model);
}
SlideGeometry.prototype = Object.create(THREE.BAS.ModelBufferGeometry.prototype);
SlideGeometry.prototype.constructor = SlideGeometry;
SlideGeometry.prototype.bufferPositions = function () {
var positionBuffer = this.createAttribute('position', 3).array;
for (var i = 0; i < this.faceCount; i++) {
var face = this.modelGeometry.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(this.modelGeometry, face);
var a = this.modelGeometry.vertices[face.a];
var b = this.modelGeometry.vertices[face.b];
var c = this.modelGeometry.vertices[face.c];
positionBuffer[face.a * 3] = a.x - centroid.x;
positionBuffer[face.a * 3 + 1] = a.y - centroid.y;
positionBuffer[face.a * 3 + 2] = a.z - centroid.z;
positionBuffer[face.b * 3] = b.x - centroid.x;
positionBuffer[face.b * 3 + 1] = b.y - centroid.y;
positionBuffer[face.b * 3 + 2] = b.z - centroid.z;
positionBuffer[face.c * 3] = c.x - centroid.x;
positionBuffer[face.c * 3 + 1] = c.y - centroid.y;
positionBuffer[face.c * 3 + 2] = c.z - centroid.z;
}
};
function THREERoot(params) {
params = utils.extend({
fov: 60,
zNear: 10,
zFar: 100000,
createCameraControls: true
}, params);
this.renderer = new THREE.WebGLRenderer({
antialias: params.antialias,
alpha: true
});
this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1));
document.getElementById('three-container1').appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(
params.fov,
window.innerWidth / window.innerHeight,
params.zNear,
params.zfar
);
this.scene = new THREE.Scene();
if (params.createCameraControls) {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
}
this.resize = this.resize.bind(this);
this.tick = this.tick.bind(this);
this.resize();
this.tick();
window.addEventListener('resize', this.resize, false);
}
THREERoot.prototype = {
tick: function () {
this.update();
this.render();
requestAnimationFrame(this.tick);
},
update: function () {
this.controls && this.controls.update();
},
render: function () {
this.renderer.render(this.scene, this.camera);
},
resize: function () {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
};
////////////////////
// UTILS
////////////////////
var utils = {
extend: function (dst, src) {
for (var key in src) {
dst[key] = src[key];
}
return dst;
},
randSign: function () {
return Math.random() > 0.5 ? 1 : -1;
},
ease: function (ease, t, b, c, d) {
return b + ease.getRatio(t / d) * c;
},
fibSpherePoint: (function () {
var vec = {x: 0, y: 0, z: 0};
var G = Math.PI * (3 - Math.sqrt(5));
return function (i, n, radius) {
var step = 2.0 / n;
var r, phi;
vec.y = i * step - 1 + (step * 0.5);
r = Math.sqrt(1 - vec.y * vec.y);
phi = i * G;
vec.x = Math.cos(phi) * r;
vec.z = Math.sin(phi) * r;
radius = radius || 1;
vec.x *= radius;
vec.y *= radius;
vec.z *= radius;
return vec;
}
})(),
spherePoint: (function () {
return function (u, v) {
u === undefined && (u = Math.random());
v === undefined && (v = Math.random());
var theta = 2 * Math.PI * u;
var phi = Math.acos(2 * v - 1);
var vec = {};
vec.x = (Math.sin(phi) * Math.cos(theta));
vec.y = (Math.sin(phi) * Math.sin(theta));
vec.z = (Math.cos(phi));
return vec;
}
})()
};
function createTweenScrubber(tween, seekSpeed) {
seekSpeed = seekSpeed || 0.001;
function stop() {
TweenMax.to(tween, 1, {timeScale:0});
}
function resume() {
TweenMax.to(tween, 1, {timeScale:1});
}
function seek(dx) {
var progress = tween.progress();
var p = THREE.Math.clamp((progress + (dx * seekSpeed)), 0, 1);
tween.progress(p);
}
var _cx = 0;
// desktop
var mouseDown = false;
document.body.style.cursor = 'pointer';
window.addEventListener('mousedown', function(e) {
mouseDown = true;
document.body.style.cursor = 'ew-resize';
_cx = e.clientX;
stop();
});
window.addEventListener('mouseup', function(e) {
mouseDown = false;
document.body.style.cursor = 'pointer';
resume();
});
window.addEventListener('mousemove', function(e) {
if (mouseDown === true) {
var cx = e.clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
}
});
// mobile
window.addEventListener('touchstart', function(e) {
_cx = e.touches[0].clientX;
stop();
e.preventDefault();
});
window.addEventListener('touchend', function(e) {
resume();
e.preventDefault();
});
window.addEventListener('touchmove', function(e) {
var cx = e.touches[0].clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
e.preventDefault();
});
}
window.onload = init;
console.ward = function() {}; // what warnings?
function init() {
var root = new THREERoot({
createCameraControls: !true,
antialias: (window.devicePixelRatio === 1),
fov: 80
});
root.renderer.setClearColor(0x000000, 0);
root.renderer.setPixelRatio(window.devicePixelRatio || 1);
root.camera.position.set(0, 0, 60);
var width = 100;
var height = 100;
var slide = new Slide(width, height, 'out');
var l1 = new THREE.ImageLoader();
l1.setCrossOrigin('Anonymous');
slide.setImage(l1.load('https://image.ibb.co/f6mVsA/helmet.png'));
root.scene.add(slide);
var slide2 = new Slide(width, height, 'in');
var l2 = new THREE.ImageLoader();
l2.setCrossOrigin('Anonymous');
slide2.setImage(l2.load('https://image.ibb.co/mb1KkV/player.png'));
root.scene.add(slide2);
var tl = new TimelineMax({repeat:-1, repeatDelay:1.0, yoyo: true});
tl.add(slide.transition(), 0);
tl.add(slide2.transition(), 0);
createTweenScrubber(tl);
window.addEventListener('keyup', function(e) {
if (e.keyCode === 80) {
tl.paused(!tl.paused());
}
});
}
////////////////////
// CLASSES
////////////////////
function Slide(width, height, animationPhase) {
var plane = new THREE.PlaneGeometry(width, height, width * 2, height * 2);
THREE.BAS.Utils.separateFaces(plane);
var geometry = new SlideGeometry(plane);
geometry.bufferUVs();
var aAnimation = geometry.createAttribute('aAnimation', 2);
var aStartPosition = geometry.createAttribute('aStartPosition', 3);
var aControl0 = geometry.createAttribute('aControl0', 3);
var aControl1 = geometry.createAttribute('aControl1', 3);
var aEndPosition = geometry.createAttribute('aEndPosition', 3);
var i, i2, i3, i4, v;
var minDuration = 0.8;
var maxDuration = 1.2;
var maxDelayX = 0.9;
var maxDelayY = 0.125;
var stretch = 0.11;
this.totalDuration = maxDuration + maxDelayX + maxDelayY + stretch;
var startPosition = new THREE.Vector3();
var control0 = new THREE.Vector3();
var control1 = new THREE.Vector3();
var endPosition = new THREE.Vector3();
var tempPoint = new THREE.Vector3();
function getControlPoint0(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.1, 0.3) * 50;
tempPoint.y = signY * THREE.Math.randFloat(0.1, 0.3) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
function getControlPoint1(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.3, 0.6) * 50;
tempPoint.y = -signY * THREE.Math.randFloat(0.3, 0.6) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
for (i = 0, i2 = 0, i3 = 0, i4 = 0; i < geometry.faceCount; i++, i2 += 6, i3 += 9, i4 += 12) {
var face = plane.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(plane, face);
// animation
var duration = THREE.Math.randFloat(minDuration, maxDuration);
var delayX = THREE.Math.mapLinear(centroid.x, -width * 0.5, width * 0.5, 0.0, maxDelayX);
var delayY;
if (animationPhase === 'in') {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, 0.0, maxDelayY)
}
else {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, maxDelayY, 0.0)
}
for (v = 0; v < 6; v += 2) {
aAnimation.array[i2 + v] = delayX + delayY + (Math.random() * stretch * duration);
aAnimation.array[i2 + v + 1] = duration;
}
// positions
endPosition.copy(centroid);
startPosition.copy(centroid);
if (animationPhase === 'in') {
control0.copy(centroid).sub(getControlPoint0(centroid));
control1.copy(centroid).sub(getControlPoint1(centroid));
}
else { // out
control0.copy(centroid).add(getControlPoint0(centroid));
control1.copy(centroid).add(getControlPoint1(centroid));
}
for (v = 0; v < 9; v += 3) {
aStartPosition.array[i3 + v] = startPosition.x;
aStartPosition.array[i3 + v + 1] = startPosition.y;
aStartPosition.array[i3 + v + 2] = startPosition.z;
aControl0.array[i3 + v] = control0.x;
aControl0.array[i3 + v + 1] = control0.y;
aControl0.array[i3 + v + 2] = control0.z;
aControl1.array[i3 + v] = control1.x;
aControl1.array[i3 + v + 1] = control1.y;
aControl1.array[i3 + v + 2] = control1.z;
aEndPosition.array[i3 + v] = endPosition.x;
aEndPosition.array[i3 + v + 1] = endPosition.y;
aEndPosition.array[i3 + v + 2] = endPosition.z;
}
}
var material = new THREE.BAS.BasicAnimationMaterial(
{
shading: THREE.FlatShading,
side: THREE.DoubleSide,
uniforms: {
uTime: {type: 'f', value: 0}
},
shaderFunctions: [
THREE.BAS.ShaderChunk['cubic_bezier'],
//THREE.BAS.ShaderChunk[(animationPhase === 'in' ? 'ease_out_cubic' : 'ease_in_cubic')],
THREE.BAS.ShaderChunk['ease_in_out_cubic'],
THREE.BAS.ShaderChunk['quaternion_rotation']
],
shaderParameters: [
'uniform float uTime;',
'attribute vec2 aAnimation;',
'attribute vec3 aStartPosition;',
'attribute vec3 aControl0;',
'attribute vec3 aControl1;',
'attribute vec3 aEndPosition;',
],
shaderVertexInit: [
'float tDelay = aAnimation.x;',
'float tDuration = aAnimation.y;',
'float tTime = clamp(uTime - tDelay, 0.0, tDuration);',
'float tProgress = ease(tTime, 0.0, 1.0, tDuration);'
//'float tProgress = tTime / tDuration;'
],
shaderTransformPosition: [
(animationPhase === 'in' ? 'transformed *= tProgress;' : 'transformed *= 1.0 - tProgress;'),
'transformed += cubicBezier(aStartPosition, aControl0, aControl1, aEndPosition, tProgress);'
]
},
{
map: new THREE.Texture(),
}
);
THREE.Mesh.call(this, geometry, material);
this.frustumCulled = false;
}
Slide.prototype = Object.create(THREE.Mesh.prototype);
Slide.prototype.constructor = Slide;
Object.defineProperty(Slide.prototype, 'time', {
get: function () {
return this.material.uniforms['uTime'].value;
},
set: function (v) {
this.material.uniforms['uTime'].value = v;
}
});
Slide.prototype.setImage = function(image) {
this.material.uniforms.map.value.image = image;
this.material.uniforms.map.value.needsUpdate = true;
};
Slide.prototype.transition = function() {
return TweenMax.fromTo(this, 3.0, {time:0.0}, {time:this.totalDuration, ease:Power0.easeInOut});
};
function SlideGeometry(model) {
THREE.BAS.ModelBufferGeometry.call(this, model);
}
SlideGeometry.prototype = Object.create(THREE.BAS.ModelBufferGeometry.prototype);
SlideGeometry.prototype.constructor = SlideGeometry;
SlideGeometry.prototype.bufferPositions = function () {
var positionBuffer = this.createAttribute('position', 3).array;
for (var i = 0; i < this.faceCount; i++) {
var face = this.modelGeometry.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(this.modelGeometry, face);
var a = this.modelGeometry.vertices[face.a];
var b = this.modelGeometry.vertices[face.b];
var c = this.modelGeometry.vertices[face.c];
positionBuffer[face.a * 3] = a.x - centroid.x;
positionBuffer[face.a * 3 + 1] = a.y - centroid.y;
positionBuffer[face.a * 3 + 2] = a.z - centroid.z;
positionBuffer[face.b * 3] = b.x - centroid.x;
positionBuffer[face.b * 3 + 1] = b.y - centroid.y;
positionBuffer[face.b * 3 + 2] = b.z - centroid.z;
positionBuffer[face.c * 3] = c.x - centroid.x;
positionBuffer[face.c * 3 + 1] = c.y - centroid.y;
positionBuffer[face.c * 3 + 2] = c.z - centroid.z;
}
};
function THREERoot(params) {
params = utils.extend({
fov: 60,
zNear: 10,
zFar: 100000,
createCameraControls: true
}, params);
this.renderer = new THREE.WebGLRenderer({
antialias: params.antialias,
alpha: true
});
this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1));
document.getElementById('three-container2').appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(
params.fov,
window.innerWidth / window.innerHeight,
params.zNear,
params.zfar
);
this.scene = new THREE.Scene();
if (params.createCameraControls) {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
}
this.resize = this.resize.bind(this);
this.tick = this.tick.bind(this);
this.resize();
this.tick();
window.addEventListener('resize', this.resize, false);
}
THREERoot.prototype = {
tick: function () {
this.update();
this.render();
requestAnimationFrame(this.tick);
},
update: function () {
this.controls && this.controls.update();
},
render: function () {
this.renderer.render(this.scene, this.camera);
},
resize: function () {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
};
////////////////////
// UTILS
////////////////////
var utils = {
extend: function (dst, src) {
for (var key in src) {
dst[key] = src[key];
}
return dst;
},
randSign: function () {
return Math.random() > 0.5 ? 1 : -1;
},
ease: function (ease, t, b, c, d) {
return b + ease.getRatio(t / d) * c;
},
fibSpherePoint: (function () {
var vec = {x: 0, y: 0, z: 0};
var G = Math.PI * (3 - Math.sqrt(5));
return function (i, n, radius) {
var step = 2.0 / n;
var r, phi;
vec.y = i * step - 1 + (step * 0.5);
r = Math.sqrt(1 - vec.y * vec.y);
phi = i * G;
vec.x = Math.cos(phi) * r;
vec.z = Math.sin(phi) * r;
radius = radius || 1;
vec.x *= radius;
vec.y *= radius;
vec.z *= radius;
return vec;
}
})(),
spherePoint: (function () {
return function (u, v) {
u === undefined && (u = Math.random());
v === undefined && (v = Math.random());
var theta = 2 * Math.PI * u;
var phi = Math.acos(2 * v - 1);
var vec = {};
vec.x = (Math.sin(phi) * Math.cos(theta));
vec.y = (Math.sin(phi) * Math.sin(theta));
vec.z = (Math.cos(phi));
return vec;
}
})()
};
function createTweenScrubber(tween, seekSpeed) {
seekSpeed = seekSpeed || 0.001;
function stop() {
TweenMax.to(tween, 1, {timeScale:0});
}
function resume() {
TweenMax.to(tween, 1, {timeScale:1});
}
function seek(dx) {
var progress = tween.progress();
var p = THREE.Math.clamp((progress + (dx * seekSpeed)), 0, 1);
tween.progress(p);
}
var _cx = 0;
// desktop
var mouseDown = false;
document.body.style.cursor = 'pointer';
window.addEventListener('mousedown', function(e) {
mouseDown = true;
document.body.style.cursor = 'ew-resize';
_cx = e.clientX;
stop();
});
window.addEventListener('mouseup', function(e) {
mouseDown = false;
document.body.style.cursor = 'pointer';
resume();
});
window.addEventListener('mousemove', function(e) {
if (mouseDown === true) {
var cx = e.clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
}
});
// mobile
window.addEventListener('touchstart', function(e) {
_cx = e.touches[0].clientX;
stop();
e.preventDefault();
});
window.addEventListener('touchend', function(e) {
resume();
e.preventDefault();
});
window.addEventListener('touchmove', function(e) {
var cx = e.touches[0].clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
e.preventDefault();
});
}
body {
margin: 0;
background: #fff;
}
canvas {
background: #fff;
}
<div id="three-container1"></div>
<div id="three-container2"></div>
I wanna create my own filter for HTML Canvas. Code Below. I get error:
Failed to execute 'putImageData' on 'CanvasRenderingContext2D': parameter 1 is not of type 'ImageData'
But if I change return outputData; at end of filter(input, values){} with
for (let i = 0; i < outputData.length; i++){
inputData[i] = outputData[i];
}
Everything works fine, but I wanna, that filter(input, values){} returns value. How I can do that?
//////////////////////////////
// Distortion Filter //
// Originally by JoelBesada //
//////////////////////////////
class FilterDistortion {
constructor() {
this.name = 'Distortion';
this.defaultValues = {
size: 4,
density: 0.5,
mix: 0.5,
};
this.valueRanges = {
size: { min: 1, max: 10 },
density: { min: 0.0, max: 1.0 },
mix: { min: 0.0, max: 1.0 },
};
}
filter(input, values = this.defaultValues) {
const { width, height } = input;
const inputData = input.data;
const outputData = inputData.slice();
let size = (values.size === undefined)
? this.defaultValues.size
: parseInt(values.size, 10);
if (size < 1) size = 1;
const density = (values.density === undefined)
? this.defaultValues.density
: Number(values.density);
const mix = (values.mix === undefined)
? this.defaultValues.mix
: Number(values.mix);
const radius = size + 1;
const numShapes = parseInt(((((2 * density) / 30) * width) * height) / 2, 10);
for (let i = 0; i < numShapes; i++) {
const sx = (Math.random() * (2 ** 32) & 0x7fffffff) % width;
const sy = (Math.random() * (2 ** 32) & 0x7fffffff) % height;
const rgb2 = [
inputData[(((sy * width) + sx) * 4) + 0],
inputData[(((sy * width) + sx) * 4) + 1],
inputData[(((sy * width) + sx) * 4) + 2],
inputData[(((sy * width) + sx) * 4) + 3],
];
for (let x = sx - radius; x < sx + radius + 1; x++) {
for (let y = sy - radius; y < sy + radius + 1; y++) {
if (x >= 0 && x < width && y >= 0 && y < height) {
const rgb1 = [
outputData[(((y * width) + x) * 4) + 0],
outputData[(((y * width) + x) * 4) + 1],
outputData[(((y * width) + x) * 4) + 2],
outputData[(((y * width) + x) * 4) + 3],
];
const mixedRGB = this.mixColors(mix, rgb1, rgb2);
for (let k = 0; k < 3; k++) {
outputData[(((y * width) + x) * 4) + k] = mixedRGB[k];
}
}
}
}
}
return outputData;
}
linearInterpolate(t, a, b) {
return a + (t * (b - a));
}
mixColors(t, rgb1, rgb2) {
const r = this.linearInterpolate(t, rgb1[0], rgb2[0]);
const g = this.linearInterpolate(t, rgb1[1], rgb2[1]);
const b = this.linearInterpolate(t, rgb1[2], rgb2[2]);
const a = this.linearInterpolate(t, rgb1[3], rgb2[3]);
return [r, g, b, a];
}
}
/////////////////
// Driver Code //
/////////////////
var img = new Image(); img.onload = draw; img.src = "//i.imgur.com/Kzz84cr.png";
function draw() {
c.width = this.width; c.height = this.height;
var ctx = c.getContext("2d");
// main loop
ctx.drawImage(this, 0, 0); // draw video frame
var data = ctx.getImageData(0, 0, c.width, c.height);
ctx.putImageData(new FilterDistortion().filter(data), 0, 0);
ctx.globalCompositeOperation = "source-over"; // "reset"
// rinse, repeat
}
<canvas id=c></canvas>
The error message says it all, you need to pass an ImageData as the first param of putImageData.
Here you are passing an UInt8ClampedArray.
So you would have to either create a new ImageData from this TypedArray (when the ImageData constructor is available), or to create an empty ImageData from your canvas context and then set all its .data's values to the new values.
//////////////////////////////
// Distortion Filter //
// Originally by JoelBesada //
//////////////////////////////
class FilterDistortion {
constructor() {
this.name = 'Distortion';
this.defaultValues = {
size: 4,
density: 0.5,
mix: 0.5,
};
this.valueRanges = {
size: { min: 1, max: 10 },
density: { min: 0.0, max: 1.0 },
mix: { min: 0.0, max: 1.0 },
};
}
filter(input, values = this.defaultValues) {
const { width, height } = input;
const inputData = input.data;
const outputData = inputData.slice();
let size = (values.size === undefined)
? this.defaultValues.size
: parseInt(values.size, 10);
if (size < 1) size = 1;
const density = (values.density === undefined)
? this.defaultValues.density
: Number(values.density);
const mix = (values.mix === undefined)
? this.defaultValues.mix
: Number(values.mix);
const radius = size + 1;
const numShapes = parseInt(((((2 * density) / 30) * width) * height) / 2, 10);
for (let i = 0; i < numShapes; i++) {
const sx = (Math.random() * (2 ** 32) & 0x7fffffff) % width;
const sy = (Math.random() * (2 ** 32) & 0x7fffffff) % height;
const rgb2 = [
inputData[(((sy * width) + sx) * 4) + 0],
inputData[(((sy * width) + sx) * 4) + 1],
inputData[(((sy * width) + sx) * 4) + 2],
inputData[(((sy * width) + sx) * 4) + 3],
];
for (let x = sx - radius; x < sx + radius + 1; x++) {
for (let y = sy - radius; y < sy + radius + 1; y++) {
if (x >= 0 && x < width && y >= 0 && y < height) {
const rgb1 = [
outputData[(((y * width) + x) * 4) + 0],
outputData[(((y * width) + x) * 4) + 1],
outputData[(((y * width) + x) * 4) + 2],
outputData[(((y * width) + x) * 4) + 3],
];
const mixedRGB = this.mixColors(mix, rgb1, rgb2);
for (let k = 0; k < 3; k++) {
outputData[(((y * width) + x) * 4) + k] = mixedRGB[k];
}
}
}
}
}
return outputData;
}
linearInterpolate(t, a, b) {
return a + (t * (b - a));
}
mixColors(t, rgb1, rgb2) {
const r = this.linearInterpolate(t, rgb1[0], rgb2[0]);
const g = this.linearInterpolate(t, rgb1[1], rgb2[1]);
const b = this.linearInterpolate(t, rgb1[2], rgb2[2]);
const a = this.linearInterpolate(t, rgb1[3], rgb2[3]);
return [r, g, b, a];
}
}
/////////////////
// Driver Code //
/////////////////
var img = new Image(); img.crossOrigin=1; img.onload = draw; img.src = "//i.imgur.com/Kzz84cr.png";
function draw() {
c.width = this.width; c.height = this.height;
var ctx = c.getContext("2d");
// main loop
ctx.drawImage(this, 0, 0); // draw video frame
var data = ctx.getImageData(0, 0, c.width, c.height);
var distortedData = new FilterDistortion().filter(data);
var distortedImg;
try{
distortedImg = new window.ImageData(distortedData, c.width, c.height);
}
catch(e) {
distortedImg = ctx.createImageData(c.width, c.height);
distortedImg.data.set(distortedData);
}
ctx.putImageData(distortedImg, 0, 0);
ctx.globalCompositeOperation = "source-over"; // "reset"
// rinse, repeat
}
<canvas id=c></canvas>
I am developing an 3D engine from scratch and I am trying to unproject a vector. I use my own Math library called ALMath.js. As far as I know, to convert 2d screen coordinates into 3d world coordinates I need multiply the vector formed by x and y coordinates in the canvas by the inverse of ViewProjection Matrix. This is the code for unproject:
unproject : function (vector){
var viewMatrix = camera.viewMatrix;
var projectionMatrix = camera.projectionMatrix;
var viewProjection = viewMatrix.multiply(projectionMatrix);
var inverseViewProjection = viewProjection.getInverse();
var x = ((vector.x -0) / (AL3D.width)) *2 -1;
var y = ((vector.y -0) / (AL3D.height)) * 2 -1;
var z = 2*vector.z-1;
var w = 1;
var vec4 = new ALMath.Vector4(x,y,z,w);
var transformedVector = inverseViewProjection.multiplyByVector4(vec4);
var wordCoords = new ALMath.Vector3(transformedVector.x/transformedVector.w,transformedVector.y/transformedVector.w,transformedVector.z/transformedVector.w);
return wordCoords;
}
The ALMath library works fine. I use it around all engine (compute the model-view-projection, create projection matrix, do the inverse ...) and works well. In fact I check the result of the operations using Octave (alternative to matlab) and the result is the same with ALMath.
The problem is that if I click in the upper left corner:
canvas.addEventListener('click', function(event) {
var rect = canvas.getBoundingClientRect();
var x = event.pageX - rect.left,
y = event.pageY - rect.top;
var vector = camera.unproject(new ALMath.Vector3(x,y,0));
});
with x = 0 and y = 2 I get a vector = (-0.12131, -0.25894, -0.79409) and I know that it is wrong because If I set the cube mesh in that position I see that this is not the upper left corner.
I have programmed a lookAt function in the camera class
lookAt : function (eye, target, up)
As a example I show the operations for x = 0 and y = 2 with octave.
Firs I set my camera as follow:
camera = new AL3D.PerspectiveCamera(40, window.innerWidth/window.innerHeight);
camera.lookAt(new ALMath.Vector3(), new ALMath.Vector3(0,-0.5,-2), new ALMath.Vector3(0,1,0));
And this is step by step calculations in octave that match with javascript code result
viewMatrix =
1.00000 0.00000 0.00000 0.00000
0.00000 0.97014 0.24254 0.00000
0.00000 -0.24254 0.97014 0.00000
0.00000 0.00000 0.00000 1.00000
projectionMatrix =
1.37374 0.00000 0.00000 0.00000
0.00000 2.82610 0.00000 0.00000
0.00000 0.00000 -1.00020 -0.20002
0.00000 0.00000 -1.00000 0.00000
octave:7> viewProjectionMatrix = viewMatrix * projectionMatrix
viewProjectionMatrix =
1.37374 0.00000 0.00000 0.00000
0.00000 2.74171 -0.24258 -0.04851
0.00000 -0.68543 -0.97034 -0.19405
0.00000 0.00000 -1.00000 0.00000
octave:8> inverseViewProjectionMatrix = inv(viewProjectionMatrix)
inverseViewProjectionMatrix =
0.72794 0.00000 0.00000 -0.00000
0.00000 0.34328 -0.08582 0.00000
0.00000 0.00000 0.00000 -1.00000
0.00000 -1.21256 -4.85023 5.00050
AL3D.width = 1366
AL3D.height = 664
x = -1
y = -0.9939759036144579
z = -1
w = 1
octave:9> vector = [ -1 -0.9939759036144579 -1 1]
vector =
-1.00000 -0.99398 -1.00000 1.00000
octave:10> transformedVector = vector * inverseViewProjectionMatrix
transformedVector =
-0.72794 -1.55377 -4.76492 6.00050
// Perspective division
octave:12> result = [ transformedVector(1)/transformedVector(4) transformedVector(2)/transformedVector(4) transformedVector(3)/transformedVector(4)]
result =
-0.12131 -0.25894 -0.79409
Maybe I am forgeting something, but I don't know. What is wrong in my logic. Thanks.
Edit: The problem seem to be the view matrix. This is the code for my view matrix:
lookAt : function(eye, target, up){
var eye = eye || new ALMath.Vector3();
var up = up || new ALMath.Vector3();
var target = target || new ALMath.Vector3();
var c = this.components;
var z = target.sub(eye);
z = z.normalize();
var x = z.cross(up);
x = x.normalize();
var y = x.cross(z);
y = y.normalize();
c[0] = x.x; c[1] = x.y; c[2] = x.z;
c[4] = y.x; c[5] = y.y; c[6] = y.z;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z;
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);
return this;
},
and this is for projection:
perspectiveProjection : function ( fov, aspect, zNear, zFar ) {
var a = aspect;
var tan=Math.tan(ALMath.degToRad(0.5*fov)),
A=-(zFar+zNear)/(zFar-zNear),
B=(-2*zFar*zNear)/(zFar-zNear);
var c = this.components;
c[ 0 ] = 0.5/tan; c[ 4 ] = 0; c[ 8 ] = 0; c[ 12 ] = 0;
c[ 1 ] = 0; c[ 5 ] = (0.5*a/tan); c[ 9 ] = 0; c[ 13 ] = 0;
c[ 2 ] = 0; c[ 6 ] = 0; c[ 10 ] = A; c[ 14 ] = B;
c[ 3 ] = 0; c[ 7 ] = 0; c[ 11 ] =-1; c[ 15 ] = 0;
return this;
},
My projection matrix is fine. But my view matrix is not the same that the view matrix compute in the gman library: https://github.com/greggman/twgl.js/blob/master/src/m4.js in m4.js the matrix is computed
c[0] = x.x; c[1] = x.y; c[2] = x.z; c[3] = 0;
c[4] = y.x; c[5] = y.y; c[6] = y.z; c[7] = 0;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z; c[11] = 0;
c[12] = eye.x; c[13] = eye.y; c[14] = eye.z; c[15] = 1;
instead of
c[0] = x.x; c[1] = x.y; c[2] = x.z;
c[4] = y.x; c[5] = y.y; c[6] = y.z;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z;
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);
Note that in my math library I computed it with dot between axis and eye as said here: Calculating a LookAt matrix
So,that thread is wrong? I should use eye directly instead dot product between axis and eye?
If I run the script posted by gman I get the following ouput:
frustum points
0 -0.414 -0.207 -0.500
1 0.414 -0.207 -0.500
2 -0.414 0.207 -0.500
3 0.414 0.207 -0.500
4 -82.843 -41.421 -100.000
5 82.843 -41.421 -100.000
6 -82.843 41.421 -100.000
7 82.843 41.421 -100.000
camerafrustum points
0 1.666 2.120 3.080
1 1.080 2.120 3.666
2 1.497 2.458 2.911
3 0.911 2.458 3.497
4 134.224 25.915 19.067
5 17.067 25.915 136.224
6 100.403 93.555 -14.754
7 -16.754 93.555 102.403
screen points (should match width, height)
0 148.858 -47.653 4.029
1 111.806 -38.903 3.734
2 147.454 -72.303 4.217
3 108.845 -59.000 3.876
4 951.911 101.710 9.651
5 61.823 20.354 3.229
6 -833.522 732.104 -10.661
7 25.094 -97.340 4.035
unprojected (should match cameraFrustum points)
0 1.666 2.120 3.080
1 1.080 2.120 3.666
2 1.497 2.458 2.911
3 0.911 2.458 3.497
4 134.224 25.915 19.067
5 17.067 25.915 136.226
6 100.404 93.556 -14.754
7 -16.754 93.557 102.405
Is the same that result posted by gman but it differs in the screen points (should match width, height) section.
If I run the gman script in my pc with this directive: <script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script> the output is the same that gman posted
screen points (should match width, height)
0 -0.000 -0.000 -1.000
1 300.000 -0.000 -1.000
2 -0.000 150.000 -1.000
3 300.000 150.000 -1.000
4 0.000 0.000 1.000
5 300.000 0.000 1.000
6 -0.000 150.000 1.000
7 300.000 150.000 1.000
But If I download https://twgljs.org/dist/2.x/twgl-full.min.js and store in the same directory that html file and use the directive <script src="twgl.js"></script> in the html file, the output is like my Math library, this is:
screen points (should match width, height)
0 148.858 -47.653 4.029
1 111.806 -38.903 3.734
2 147.454 -72.303 4.217
3 108.845 -59.000 3.876
4 951.911 101.710 9.651
5 61.823 20.354 3.229
6 -833.522 732.104 -10.661
7 25.094 -97.340 4.035
The script adapted to my library is the following:
function main()
{
var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar = 100;
var projection = new ALMath.Matrix4();
projection = projection.perspectiveProjection(45, aspect, zNear, zFar);
var eye = new ALMath.Vector3(1, 2, 3);
var target = new ALMath.Vector3(4, 5, 6);
var up = new ALMath.Vector3(0, 1, 0);
var camera = new ALMath.Matrix4();
camera = camera.lookAt(eye, target, up);
var view = camera.getInverse();
var viewProjection = view.multiply(projection);
var inverseViewProjection = viewProjection.getInverse();
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(fieldOfView / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
new ALMath.Vector3(-nearX, -nearY, -zNear),
new ALMath.Vector3( nearX, -nearY, -zNear),
new ALMath.Vector3(-nearX, nearY, -zNear),
new ALMath.Vector3( nearX, nearY, -zNear),
new ALMath.Vector3(-farX, -farY, -zFar),
new ALMath.Vector3( farX, -farY, -zFar),
new ALMath.Vector3(-farX, farY, -zFar),
new ALMath.Vector3( farX, farY, -zFar),
];
}
function projectScreenPoint(width, height, projection, point) {
var c = projection.transformPoint(point);
return new ALMath.Vector3((c.x * 0.5 + 0.5) * width,(c.y * 0.5 + 0.5) * height, c.z);
}
function unproject(width, height, inverseViewProjection, p) {
return inverseViewProjection.transformPoint(new ALMath.Vector3(p.x / width * 2 - 1, p.y / height * 2 - 1, p.z));
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p.x.toFixed(3), p.y.toFixed(3), p.z.toFixed(3)));
}
var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => camera.transformPoint(p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
}
So the questions now are:
I should use eye directly instead dot product between axis and eye?
Why the screen points differs, from my library and local stored gman's library and from the
If I were you I'd write some tests. You have a frustum and a camera. You should easily be able to compute the corners of your frustum. Then using those corners you should be able to project them to get screen coordinates. Then check if you unproject those screen coordinates that you get the frustum points back.
Since you didn't post your math library I'll use my own
var m4 = twgl.m4;
// Plug in your math lib here
var m = {
multiply: (a, b) => m4.multiply(a, b),
inverse: (a) => m4.inverse(a),
identity: () => m4.identity(),
lookAt: (eye, target, up) => m4.lookAt(eye, target, up),
perspective: (fov, aspect, zNear, zFar) => m4.perspective(fov, aspect, zNear, zFar),
transformPoint: (m, p) => m4.transformPoint(m, p),
};
var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar = 100;
var projection = m.perspective(fieldOfView, aspect, zNear, zFar);
var eye = [1, 2, 3];
var target = [4, 5, 6];
var up = [0, 1, 0];
var camera = m.lookAt(eye, target, up);
var view = m.inverse(camera);
var viewProjection = m.multiply(projection, view);
var inverseViewProjection = m.inverse(viewProjection);
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(fieldOfView / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
[-nearX, -nearY, -zNear],
[ nearX, -nearY, -zNear],
[-nearX, nearY, -zNear],
[ nearX, nearY, -zNear],
[-farX, -farY, -zFar],
[ farX, -farY, -zFar],
[-farX, farY, -zFar],
[ farX, farY, -zFar],
];
}
function projectScreenPoint(width, height, projection, point) {
var c = m.transformPoint(projection, point);
return [
(c[0] * 0.5 + 0.5) * width,
(c[1] * 0.5 + 0.5) * height,
c[2],
];
}
function unproject(width, height, inverseViewProjection, p) {
return m.transformPoint(
inverseViewProjection,
[
p[0] / width * 2 - 1,
p[1] / height * 2 - 1,
p[2],
]);
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p[0].toFixed(3), p[1].toFixed(3), p[2].toFixed(3)));
}
var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => m.transformPoint(camera, p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0 };
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
Note: m4.transformPoint does the divide by w for the result
Plug your math lib in above?
Here's an example of plugging in glMatrix
// Plug in your math lib here
var m = {
multiply: (a, b) => mat4.multiply(mat4.create(), a, b),
inverse: (a) => mat4.invert(mat4.create(), a),
identity: () => mat4.create(),
lookAt: (eye, target, up) => mat4.invert(
mat4.create(),
mat4.lookAt(mat4.create(), eye, target, up)),
perspective: (fov, aspect, zNear, zFar) => mat4.perspective(
mat4.create(), fov, aspect, zNear, zFar),
transformPoint: (m, p) => vec3.transformMat4(vec3.create(), p, m),
};
var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar = 100;
var projection = m.perspective(fieldOfView, aspect, zNear, zFar);
var eye = [1, 2, 3];
var target = [4, 5, 6];
var up = [0, 1, 0];
var camera = m.lookAt(eye, target, up);
var view = m.inverse(camera);
var viewProjection = m.multiply(projection, view);
var inverseViewProjection = m.inverse(viewProjection);
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(fieldOfView / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
[-nearX, -nearY, -zNear],
[ nearX, -nearY, -zNear],
[-nearX, nearY, -zNear],
[ nearX, nearY, -zNear],
[-farX, -farY, -zFar],
[ farX, -farY, -zFar],
[-farX, farY, -zFar],
[ farX, farY, -zFar],
];
}
function projectScreenPoint(width, height, projection, point) {
var c = m.transformPoint(projection, point);
return [
(c[0] * 0.5 + 0.5) * width,
(c[1] * 0.5 + 0.5) * height,
c[2],
];
}
function unproject(width, height, inverseViewProjection, p) {
return m.transformPoint(
inverseViewProjection,
[
p[0] / width * 2 - 1,
p[1] / height * 2 - 1,
p[2],
]);
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p[0].toFixed(3), p[1].toFixed(3), p[2].toFixed(3)));
}
var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => m.transformPoint(camera, p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0 };
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
As for your example you're passing on 0 for z which is somewhere in the middle of the frustum depth wise. Also I see code like this
transformedVector(1)/transformedVector(4)
I don't know your math library but mine would be zero based indices so
transformedVector(0)/transformedVector(3)
Here's the code you added to your example. It's working for me. I filled in the missing math functions.
const m4 = twgl.m4;
class Vector3 {
constructor(x, y, z) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
sub(v) {
return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z);
}
cross(v) {
return new 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);
}
dot(v) {
return (this.x * v.x) + (this.y * v.y) + (this.z * v.z);
}
normalize() {
var lenSq = this.x * this.x + this.y * this.y + this.z * this.z;
var len = Math.sqrt(lenSq);
if (len > 0.00001) {
return new Vector3(this.x / len, this.y / len, this.z / len);
} else {
return new Vector3();
}
}
}
class Vector4 {
constructor(x, y, z, w) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
this.w = w || 0;
}
}
class Matrix4 {
constructor(components) {
this.components = components || m4.identity();
}
multiply(m) {
return new Matrix4(m4.multiply(m.components, this.components));
}
getInverse() {
return new Matrix4(m4.inverse(this.components));
}
multiplyByVector4(v) {
const m = this.components;
const x = v.x * m[0 * 4 + 0] + v.y * m[1 * 4 + 0] + v.z * m[2 * 4 + 0] + v.w * m[3 * 4 + 0];
const y = v.x * m[0 * 4 + 1] + v.y * m[1 * 4 + 1] + v.z * m[2 * 4 + 1] + v.w * m[3 * 4 + 1];
const z = v.x * m[0 * 4 + 2] + v.y * m[1 * 4 + 2] + v.z * m[2 * 4 + 2] + v.w * m[3 * 4 + 2];
const w = v.x * m[0 * 4 + 3] + v.y * m[1 * 4 + 3] + v.z * m[2 * 4 + 3] + v.w * m[3 * 4 + 3];
return new Vector4(x, y, z, w);
}
transformPoint(v) {
const v4 = this.multiplyByVector4(new Vector4(v.x, v.y, v.z, 1));
return new Vector3(v4.x / v4.w, v4.y / v4.w, v4.z / v4.w);
}
lookAt(eye, target, up) {
var eye = eye || new ALMath.Vector3();
var up = up || new ALMath.Vector3();
var target = target || new ALMath.Vector3();
var c = this.components;
var z = target.sub(eye);
z = z.normalize();
var x = z.cross(up);
x = x.normalize();
var y = x.cross(z);
y = y.normalize();
c[0] = x.x; c[1] = x.y; c[2] = x.z;
c[4] = y.x; c[5] = y.y; c[6] = y.z;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z;
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);
return this;
}
perspectiveProjection( fov, aspect, zNear, zFar ) {
var a = aspect;
var tan=Math.tan(ALMath.degToRad(0.5*fov)),
A=-(zFar+zNear)/(zFar-zNear),
B=(-2*zFar*zNear)/(zFar-zNear);
var c = this.components;
c[ 0 ] = 0.5/tan; c[ 4 ] = 0; c[ 8 ] = 0; c[ 12 ] = 0;
c[ 1 ] = 0; c[ 5 ] = (0.5*a/tan); c[ 9 ] = 0; c[ 13 ] = 0;
c[ 2 ] = 0; c[ 6 ] = 0; c[ 10 ] = A; c[ 14 ] = B;
c[ 3 ] = 0; c[ 7 ] = 0; c[ 11 ] =-1; c[ 15 ] = 0;
return this;
}
}
class PerspectiveCamera {
constructor(fieldOfViewDegrees, aspect, zNear, zFar) {
this.fieldOfViewDegrees = fieldOfViewDegrees || 45;
this.aspect = aspect || 1;
this.zNear = zNear || 0.5;
this.zFar = zFar || 100;
this.projectionMatrix = new Matrix4();
this.viewMatrix = new Matrix4();
this.updateProjection();
}
updateProjection() {
this.projectionMatrix.perspectiveProjection(
this.fieldOfViewDegrees, this.aspect, this.zNear, this.zFar);
}
lookAt(eye, target, up) {
//this.viewMatrix.lookAt(eye, target, up);
this.cameraMatrix = this.viewMatrix.getInverse();
}
transformPoint(v) {
// note this tranasforms by the camera matrix
// (which is the inverse view matrix)
// and not the perspective matrix
return this.cameraMatrix.transformPoint(v);
}
}
const ALMath = {
Vector3: Vector3,
Matrix4: Matrix4,
degToRad: d => d * Math.PI / 180,
};
const AL3D = {
width: 300,
height: 150,
PerspectiveCamera: PerspectiveCamera,
};
const camera = new AL3D.PerspectiveCamera(40, AL3D.width/AL3D.height);
camera.lookAt(
new ALMath.Vector3(),
new ALMath.Vector3(0,-0.5,-2),
new ALMath.Vector3(0,1,0));
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(ALMath.degToRad(fieldOfView) / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
new ALMath.Vector3(-nearX, -nearY, -zNear),
new ALMath.Vector3( nearX, -nearY, -zNear),
new ALMath.Vector3(-nearX, nearY, -zNear),
new ALMath.Vector3( nearX, nearY, -zNear),
new ALMath.Vector3(-farX, -farY, -zFar),
new ALMath.Vector3( farX, -farY, -zFar),
new ALMath.Vector3(-farX, farY, -zFar),
new ALMath.Vector3( farX, farY, -zFar),
];
}
const projectionMatrix = camera.projectionMatrix;
const viewMatrix = camera.viewMatrix;
const viewProjection = viewMatrix.multiply(projectionMatrix);
const inverseViewProjection = viewProjection.getInverse();
function projectScreenPoint(width, height, projection, point) {
var c = projectionMatrix.transformPoint(point);
return new ALMath.Vector3(
(c.x * 0.5 + 0.5) * width,
(c.y * 0.5 + 0.5) * height,
c.z);
}
function unproject(width, height, inverseViewProjection, p) {
return inverseViewProjection.transformPoint(new ALMath.Vector3(
p.x / width * 2 - 1,
p.y / height * 2 - 1,
p.z));
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p.x.toFixed(3), p.y.toFixed(3), p.z.toFixed(3)));
}
var frustumPoints = getFrustumPoints(camera.fieldOfViewDegrees, camera.aspect, camera.zNear, camera.zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => camera.transformPoint(p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(AL3D.width, AL3D.height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(AL3D.width, AL3D.height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0; }
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>