Related
I'm trying to do some behind the scenes rendering work with WebGL and then copy the canvas data to an image.
I'm able to accomplish this successfully with a physical canvas element on the page, however, once I try to replace my
const canvas = document.getElementById("glcanvas") with either
const cavnas = document.createElement("canvas") or
const canvas = new OffscreenCanvas(640, 480)
My render doesn't seem to work anymore.
Interestingly I still see my WebGL clear color so I know it's rendering something. If I go back to the getElemeentById method with a physical canvas in the DOM, everything works fine, any ideas?
code (I pretty much just followed the Mozilla tutorial for WebGL, some code is omitted because it is not important):
function main() {
// Initialize the GL context
const canvas = new OffscreenCanvas(640, 480)
const gl = canvas.getContext("webgl2")
// Only continue if WebGL is available and working
if (gl === null) {
alert("Unable to initialize WebGL. Your browser or machine may not support it.")
return
}
// Set clear color to black, fully opaque
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear the color buffer with specified clear color
gl.clear(gl.COLOR_BUFFER_BIT);
// Initialize a shader program; this is where all the lighting
// for the vertices and so forth is established.
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
// Collect all the info needed to use the shader program.
// Look up which attributes our shader program is using
// for aVertexPosition, aVertexColor and also
// look up uniform locations.
const programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
vertexColor: gl.getAttribLocation(shaderProgram, "aVertexColor"),
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(
shaderProgram,
"uProjectionMatrix"
),
modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
},
};
const objects = [
initCuboid(
gl,
{x: 0, y: 0, z: 0},
{x: 1, y: 1, z: 1},
{pitch: 0, yaw: 0, roll: 0}
),
initCuboid(
gl,
{x: 0, y: 2, z: 0},
{x: .5, y: .5, z: .5},
{pitch: 0, yaw: 0, roll: 0}
),
]
function render() {
drawScene(gl, programInfo, objects);
canvas.convertToBlob().then((res) => {
document.getElementById("glimg").src = URL.createObjectURL(res)
})
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}
I have a problem with bounding box points calculation. I'm using three.js to render polygons, it's basically 2D with orthographic camera. Unfortunately, simple bounding box calculation - iterate over points and get extreme values doesn't work correctly after camera is rotated. It stays aligned to axes. I'd like bounding box to be aligned to a viewport (just like in the picture below). It can be rotated by any angle, has to be always aligned to a viewport.
I added an example below - how to calculate points of the bounding box on the right?
Image description: left - trivial bounding box without rotation, middle - axis aligned bounding box, right - desired result - viewport aligned bounding box
Fiddle producing middle case: https://jsfiddle.net/tqrc2ue6/5/
var camera, scene, renderer, geometry, material, line, axesHelper, boundingBoxGeometry, boundingBoxLine;
const polygonPoints = [{
x: 10,
y: 10,
z: 0
},
{
x: 15,
y: 15,
z: 0
},
{
x: 20,
y: 10,
z: 0
},
{
x: 25,
y: 20,
z: 0
},
{
x: 15,
y: 20,
z: 0
},
{
x: 10,
y: 10,
z: 0
},
]
function getBoundingBoxGeometry(geometry) {
geometry.computeBoundingBox();
const boundingBox = geometry.boundingBox;
const boundingBoxPoints = [{
x: boundingBox.min.x,
y: boundingBox.min.y,
z: 0
},
{
x: boundingBox.max.x,
y: boundingBox.min.y,
z: 0
},
{
x: boundingBox.max.x,
y: boundingBox.max.y,
z: 0
},
{
x: boundingBox.min.x,
y: boundingBox.max.y,
z: 0
},
{
x: boundingBox.min.x,
y: boundingBox.min.y,
z: 0
},
];
return new THREE.BufferGeometry().setFromPoints(boundingBoxPoints);
}
init();
animate();
function init() {
scene = new THREE.Scene();
axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper);
//camera = new THREE.OrthographicCamera(-25, 25, -25, 25, -1, 1);
//camera.position.set(15, 15)
//camera.rotation.z = -Math.PI / 4
var frustumSize = 50
var aspect = window.innerWidth / window.innerHeight;
camera = new THREE.OrthographicCamera(frustumSize * aspect / -2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / -2, -1, 1);
//camera.rotation.z = 2 * Math.PI /3
camera.rotation.z = 3 * Math.PI / 4
camera.position.set(15, 15)
scene.add(camera);
geometry = new THREE.BufferGeometry().setFromPoints(polygonPoints);
boundingBoxGeometry = getBoundingBoxGeometry(geometry);
material = new THREE.LineBasicMaterial({
color: 0xffffff
});
line = new THREE.Line(geometry, material);
scene.add(line);
boundingBoxLine = new THREE.Line(boundingBoxGeometry, material)
scene.add(boundingBoxLine);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
The left figure is obtained by computing the bounding box of the original coordinates.
The right figure is obtained by computing the bounding box of the rotated coordinates.
The central figure is obtained by computing the bounding box in the original coordinates and applying the rotation to the corners. It is no more axis aligned wrt the original coordinates.
Yves explained the concept. You need to convert points from one coordinate system to another to solve this. But since it's an orthographic view, you can also use camera projection for conversions.
In this way, we project all the points to the screen coordinate system, we calculate the position of the box in this coordinate, and then we unproject the points of the box to the world coordinate system.
I updated your sample to demonstrate this. Just keep in mind that width and height of this rectangle are valid only if view direction be parallel to one of the X, Y or Z axes.
I'm trying to animate a three.js block in such a way that it returns to its original position when the animation ends, using tween.js.
Is there a way to achieve this with tween.js only using one tween?
I have got this working as shown below:
var position = {x: -200, y: 150, width: 1, height: 1, depth: 1, rotx: -0.5, roty: 0.7, rotz: 0.9};
var target = {x: 200, y: -100, width: 0.4, height: 3, depth: 8, rotx: 0.3, roty: -0.4, rotz: -0.6};
var position2 = {x: -200, y: 150, width: 1, height: 1, depth: 1, rotx: -0.5, roty: 0.7, rotz: 0.9};
var mesh = new THREE.Mesh(
new THREE.CubeGeometry(190, 45, 30),
new THREE.MeshBasicMaterial({color: 0x444444}),
0
);
mesh.position.set(position.x, position.y, 0);
mesh.rotation.set(position.rotx, position.roty, position.rotz);
scene.add(mesh);
var t1 = new TWEEN.Tween(position).to(target, 2000);
t1.onUpdate(function() {
mesh.position.set(position.x, position.y, 0);
mesh.scale.set(position.width, position.height, position.depth);
mesh.rotation.set(position.rotx, position.roty, position.rotz);
});
t1.easing(TWEEN.Easing.Quadratic.Out);
t1.onComplete(function() {t2.start();});
var t2 = new TWEEN.Tween(target).to(position2, 2000);
t2.onUpdate(function() {
mesh.position.set(target.x, target.y, 0);
mesh.scale.set(target.width, target.height, target.depth);
mesh.rotation.set(target.rotx, target.roty, target.rotz);
});
t2.easing(TWEEN.Easing.Quadratic.In);
t1.start();
And I have the tweens updating in my animation function:
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
mesh.__dirtyPosition = true;
mesh.__dirtyRotation = true;
TWEEN.update();
}
animate();
This is working as I expect it to, but it is clearly very inefficient, and difficult to work around.
Any and all help will be appreciated.
You're overcomplicating things a bit by re-naming the x, y, z properties to width, height, depth or rotx, roty, rotz. This only means you have to manually translate these properties onUpdate when you do scale.x = position.width and rotation.x = position.rotx. I recommend you keep x, y, z, to avoid these repetitive assignments.
// We set our start and target pos using the THREE.js "x, y, z" nomenclature
var startPos = {x: -200, y: 150, z: 0};
var targetPos = {x: 200, y: -100, z: 0};
// Scale also is defined in "x, y, z"
var startScale = {x: 1, y: 1, z: 1};
var targetScale = {x: 0.4, y: 3, z: 8};
// Rotation also has "x, y, z" degrees in Euler angles
var startRot = {x: -0.5, y: 0.7, z: 0.9};
var targetRot = {x: 0.3, y: -0.4, z: -0.6};
// Standard mesh setup
var mesh = new THREE.Mesh(
new THREE.CubeGeometry(190, 45, 30),
new THREE.MeshBasicMaterial({color: 0x444444})
);
mesh.position.copy(startPos);
mesh.rotation.copy(startRot);
scene.add(mesh);
// Create shortcuts for shorter easing names
var QuadOut = TWEEN.Easing.Quadratic.Out;
var QuadIn = TWEEN.Easing.Quadratic.In;
// Create one tween for position
// Notice that you can chain the animation
// back to startPos by doing double ".to().to()""
var t1 = new TWEEN.Tween(mesh.position)
.to(targetPos, 2000, QuadOut)
.to(startPos, 2000, QuadIn);
// Second, we tween the mesh's rotation
var t2 = new TWEEN.Tween(mesh.rotation)
.to(targetRot, 2000, QuadOut)
.to(startRot, 2000, QuadIn);
// Third, we tween the mesh's scale
var t3 = new TWEEN.Tween(mesh.scale)
.to(targetScale, 2000, QuadOut)
.to(startScale, 2000, QuadIn);
t1.start();
t2.start();
t3.start();
And finally, during animate(), you no longer have to change __dirtyPosition or anything, because the tween is updating the mesh's properties directly.
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
renderer.render(scene, camera);
}
animate();
three.js r89
var A = new THREE.Object3D();
var B = new THREE.Object3D();
scene.add(A);
A.add(B);
A.position.set(0, 0, -2);
var bPos = B.getWorldPosition();
bPos is still Vector3 {x: 0, y: 0, z: 0}. Why? I thought it should be Vector3 {x: 0, y: 0, z: -2} (B moved with A).
Proof:
Hi I've found some code that animates 3d shapes and even gives an example of making and animating an icosahedron I'm trying to turn it in to a decahedron though and my geometry is pretty bad. The code I have for the icosahedron is:
// draw a icosahedron
var tau = 1.6180,
phi = 20.90515745, // (180-138.1896851)/2
rt3 = Math.sqrt(3),
d = sideLen/2,
foldTbl = [ 60, -60, 60, -60,
-60, -60, 60, 60,
60, -60, 60, -60,
-60, -60, 60, 60,
60, -60, 60, -60],
moveTbl = [ 0, 2*d, 0, 2*d,
2*d, 2*d, 0, 0,
0, 2*d, 0, 2*d,
2*d, 2*d, 0, 0,
0, 2*d, 0, 2*d],
triangle = ['M',0,0,0, 'L', d*rt3,d,0, 0,2*d,0, 'z'],
tri,
faces = g.createGroup3D(),
bend = -2*phi,
i;
for (i=0; i<20; i++)
{
// create the next face
tri = g.compileShape3D(triangle, "red", null, 1); // backColor irrelevant
faces.addObj(tri);
faces.translate(0, -moveTbl[i], 0);
faces.rotate(0, 0, 1, foldTbl[i]);
faces.rotate(0, 1, 0, bend);
faces.translate(0, moveTbl[i], 0);
}
return faces;
i'm sure there must be an easy way to make this a decahedron but if anyone has any advice that'd be amazing - thanks!
If you have coordinates for an icosahedron but want to draw a dodecahedron, you can make use of the duality between those two. Take the icosahedron, and put a new vertex in the middle of every one of its triangular faces. Connect two new vertices with an edge if the corresponding faces of the icosahedron had an edge in common. You will obtain a dodecahedron, with one vertex for every face of the icosahedron, and one face for every vertex.