three.js: rotate tetrahedron on correct axis - javascript

I have to display a rotating tetrahedron in an animated HTML5 graphic, using three.js.
When i create the object, it's upside down, but it should be on the ground with one surface, like on this screenshot: http://www10.pic-upload.de/08.10.12/v3nnz25zo65q.png
This is the code for rotating the object, at the moment. Stored in render() function. But the axis is incorrect and the object is rotating wrong.
object.useQuaternion = true;
var rotateQuaternion_ = new THREE.Quaternion();
rotateQuaternion_.setFromAxisAngle(new THREE.Vector3(0, 1, 1), 0.2 * (Math.PI / 180));
object.quaternion.multiplySelf(rotateQuaternion_);
object.quaternion.normalize();
(copied from Three.JS - how to set rotation axis)
This is the result at the moment: http://jsfiddle.net/DkhT3/
How can i access the correct axis to move/rotate the tetrahedron on the ground?
Thanks for suggestions!

Don't do what you copied. It is not correct.
I am assuming that what you want to do is to start off with a tetrahedron that is right-side-up to begin with. One thing you can do is to modify THREE.TetrahedronGeometry() to use four vertices that produce the tetrahedron that you want.
If you want to use the existing code, then what you have to do is to apply a rotation to the geometry right after it is created, like so:
const geometry = new THREE.TetrahedronGeometry( 10, 0 );
geometry.applyMatrix4( new THREE.Matrix4().makeRotationAxis( new THREE.Vector3( 1, 0, - 1 ).normalize(), Math.atan( Math.sqrt( 2 ) ) ) );
geometry.translate( 0, 10 / 3, 0 ); // optional, so it sits on the x-z plane
(Determining the proper rotation angle requires a bit of math, but it turns out to be the arctangent of the sqrt( 2 ) in this case, in radians.)
three.js r.146

Download three.js from [http://www.html5canvastutorials.com/libraries/Three.js] library and put in the below HTML code.
You will get good output.
<!Doctype html>
<html lang="eng">
<head>
<title>I like shapes</title>
<meta charset="UTF-8">
<style>
canvas{
width: 100%;
height: 100%
}
body{
background: url(mathematics.jpg);
width: 800px;
height: 500px;
}
</style>
</head>
<body>
<script src="three.js"></script>
<script>
// creating a Scene
var scene = new THREE.Scene();
// Adding Perspective Camera
// NOTE: There are 3 cameras: Orthographic, Perspective and Combined.
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
//create WebGL renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
//Drawing a Tetrahedron.
var pyramidgeometry = new THREE.CylinderGeometry(0, 0.8, 4, 4, false);
//Change colour of Pyramid using Wireframe
var pyramidmaterial = new THREE.MeshBasicMaterial({wireframe: true, color: 0x0ff000});
var pyramid = new THREE.Mesh(pyramidgeometry, pyramidmaterial); //This creates the cone
pyramid.position.set(3.1,0,0);
scene.add(pyramid);
//moving camera outward of centre to Z-axis, because Cube is being created at the centre
camera.position.z = 5;
//we have a scene, a camera, a cube and a renderer.
/*A render loop essentially makes the renderer draw the scene 60 times per second.
I have used requestAnimationFrame() function.*/
var render = function() {
requestAnimationFrame(render);
/*cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
cube.rotation.z += 0.01;*/
pyramid.rotation.y += 0.01;
pyramid.rotation.x += 0.01;
pyramid.rotation.z += 0.01;
renderer.render(scene, camera);
};
// calling the function
render();
</script>
</body>
</html>

Related

my three.js scene is not rendering. not sure why

my main.js file contains :-
import './style.css';
import * as THREE from 'three';
//create scene
const scene = new THREE.Scene();
//arguments - field of view, aspect ratio, last 2 are view frustrum(controls which objects are visible relative to the camera)
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth/window.innerHeight,
0.1,
1000,
);
const renderer = new THREE.WebGLRenderer({
//which DOM element to use
canvas: document.querySelector('.canvas'),
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth / window.innerHeight);
camera.position.setZ(100);
//arguments - radius, widthsegments, heightsegements
const geometry = new THREE.SphereGeometry(15, 32, 16);
//wireframe true to get a better look at its geometry
const material = new THREE.MeshBasicMaterial({color: 0xffff00, wireframe: true});
//torus is creating the mesh with geometry and material //mesh = geometry + material
const globe = new THREE.Mesh(geometry, material);
scene.add(globe);
function animate(){
requestAnimationFrame(animate); //optimized rendering
//rotation
globe.rotateOnAxis += 0.01;
renderer.render( scene, camera );
}
animate();
renderer.render(scene, camera);
and my index.html :-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Gautam</title>
</head>
<body>
<canvas id="globe">This is the canvas
</canvas>
<script type="module" src="./main.js"></script>
</body>
</html>
All that appears on my screen is :-
[what displays on my browser][1]
[1]: https://i.stack.imgur.com/PQmJu.png
I checked and my main.js file is definitely executing, but nothing is rendering on the screen
You have a few issues.
document.querySelector('.canvas')
This would select elements with the class canvas, but your DOM has no such element. It does have an element with the type canvas and the ID globe, so that line should be one of the following:
document.querySelector('canvas')
document.querySelector('#globe')
document.getElementById('globe')
Next,
renderer.setSize(window.innerWidth / window.innerHeight);
As #Mugen87 points out, this should be
renderer.setSize(window.innerWidth, window.innerHeight);
As you've written it, the renderer's width is being set to the quotient, and its height (the missing second parameter) is being set to undefined, a situation that three.js has no handling for.
And,
globe.rotateOnAxis += 0.01;
rotateOnAxis is a function that takes a vector (the axis of rotation) and a scalar (the rotation angle) as its parameters. By assigning +0.01 to it, you're simply replacing the function itself, not calling the function. If, for example, you want to rotate around the globe's y-axis, you could use
globe.rotateOnAxis(new THREE.Vector3(0, 1, 0), 0.01);
Finally, the second call to renderer.render(...) (the one outside the animate() function) is unnecessary.
renderer.setSize(window.innerWidth / window.innerHeight);
Call the method like so instead:
renderer.setSize(window.innerWidth, window.innerHeight);
//create scene
const scene = new THREE.Scene();
//arguments - field of view, aspect ratio, last 2 are view frustrum(controls which objects are visible relative to the camera)
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
const renderer = new THREE.WebGLRenderer({
//which DOM element to use
canvas: document.querySelector('#canvas')
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
camera.position.setZ(100);
//arguments - radius, widthsegments, heightsegements
const geometry = new THREE.SphereGeometry(15, 32, 16);
//wireframe true to get a better look at its geometry
const material = new THREE.MeshBasicMaterial({
color: 0xffff00,
wireframe: true
});
//torus is creating the mesh with geometry and material //mesh = geometry + material
const globe = new THREE.Mesh(geometry, material);
scene.add(globe);
function animate() {
requestAnimationFrame(animate); //optimized rendering
//rotation
globe.rotateOnAxis += 0.01;
renderer.render(scene, camera);
}
animate();
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.140.2/build/three.min.js"></script>
<canvas id="canvas"></canvas>

three.js - how to render scene.background before a mesh?

I am a novice to Javascript and ThreeJS. I have a 3D rotating cube that appears on top of a static background, but one frustrating property is that the cube typically appears first and then the background image appears. How do I ensure the background is rendered first? Specifically, I always want the background image to appear before the cube.
My code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body { margin: 0; }
</style>
</head>
<body>
<script src="js/three.js"></script>
<script>
function resize() {
var aspect = window.innerWidth / window.innerHeight;
let texAspect = bgWidth / bgHeight;
let relAspect = aspect / texAspect;
bgTexture.repeat = new THREE.Vector2( Math.max(relAspect, 1), Math.max(1/relAspect,1) );
bgTexture.offset = new THREE.Vector2( -Math.max(relAspect-1, 0)/2, -Math.max(1/relAspect-1, 0)/2 );
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
const scene = new THREE.Scene();
// Arguments:
// 1) Field of Value (degrees)
// 2) Aspect ratio
// 3) Near clipping plane
// 4) Far clipping plane
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
// Need to set size of renderer. For performance, may want to reduce size.
// Can also reduce resolution by passing false as third arg to .setSize
renderer.setSize( window.innerWidth, window.innerHeight );
// Add the rendered to the HTML
document.body.appendChild( renderer.domElement );
// A BoxGeometry is an object that contains all points (vertices) and fill (faces)
// of the cube
const geometry = new THREE.BoxGeometry();
// Determines surface color (maybe texture?)
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
// Mesh takes a geometry and applies the material to it
const cube = new THREE.Mesh( geometry, material );
// Add background image
const loader = new THREE.TextureLoader();
bgTexture = loader.load('https://images.pexels.com/photos/1205301/pexels-photo-1205301.jpeg' ,
function(texture) {
// Resize image to fit in window
// Code from https://stackoverflow.com/a/48126806/4570472
var img = texture.image;
var bgWidth = img.width;
var bgHeight = img.height;
resize();
});
scene.background = bgTexture;
// By default, whatever we add to the scene will be at coordinates (0, 0, 0)
scene.add( cube );
camera.position.z = 5;
// This somehow creates a loop that causes the rendered to draw the scene
// every time the screen is refreshed (typically 60fps)
function animate() {
requestAnimationFrame( animate );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
}
animate();
</script>
</body>
</html>
This is happening because the texture is taking longer to load than it takes for Three.js to set up the rest of the scene. You already have a handler for the onLoad callback of TextureLoader.load(), so we can use that to adjust the behavior.
Before scene.add( cube );, add a new line:
cube.visible = false;
Now the cube will still be added to the scene, but it won't be visible. Now after the resize() call at the end of function(texture), add
cube.visible = true;
While testing the problem and solution locally, I ran into a few other, less significant issues with your code. You can see all of the changes I had to make to get it running properly at this Gist.

Selecting and caluclating area of 'planes' in three.js

I am trying to calulate the area of a 'plane' of 3d object (.stl mesh object) n in three.js. By selecting it with a mouse click.
For example this area:
We know that all 'triangles' that make up the surface of the 'plane' (since it is an .stl) are attached at the same angle, if the angle changes in comparison to the last triangle, we know that this triangle is not part of the 'plane' anymore.
So knowing this is a property of the surface we are trying to calulate, we can use this in combination with some form of three.js Raycaster to select and calculate an area (by adding all the area's of the triangles with the correct properties together).
For now, we don't need to worry about the size of the selected surface (for example trying to find a 'plane' on a curved surface wouldn't be valid, but we do not need to worry about that now)
How would I go about trying to find the area and 'plane' in three.js. Or more specifically, how do I start reading the .stl format's 'triangles'.
Minimal working example of my current code (with working .stl file included), it can be run using a simple live server, for example in VS Code using Ritwick Dey's plugin, no extra plugins/libraries needed, since it's 'normal' HTML/JS.
The only requirement is that we select the point we are trying to calculate in three.js and give the area (selected triangles) a different colour, in three.js, when they are selected. Any other calculations needed could be done in some kind of backend (Python, Node.js or anything else really), but I see no obvious way of transporting such complicated data between programs.
My minimal reproduction code, see my abovementioned github link for the .stl file used in my picture:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stackoverflow Example</title>
<link rel="stylesheet" type="text/css" href="../style/render.css">
</head>
<body>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r117/build/three.min.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r117/examples/js/loaders/STLLoader.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r117/examples/js/controls/OrbitControls.js"></script>
<script>
function init() {
// Setup some basic stuff
scene = new THREE.Scene();
scene.background = new THREE.Color(0xdddddd);
// Setup Camera
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 5000);
// Setup renerer and add to page
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Setup Camera Position
camera.rotation.y = 45 / 180 * Math.PI;
camera.position.x = 800;
camera.position.y = 100;
camera.position.z = 1000;
// Add Camera Control through orbit.js
let controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', renderer);
// Add some basic ambient lighting (Does light all parts equally and does not cast shadows)
hlight = new THREE.AmbientLight(0x404040, 20);
scene.add(hlight);
//Add some point lights to simulate real lights
light = new THREE.PointLight(0xc4c4c4, 1, 10000);
light.position.set(0, 300, 500);
scene.add(light);
light2 = new THREE.PointLight(0xc4c4c4, 1, 10000);
light2.position.set(500, 100, 0);
scene.add(light2);
light3 = new THREE.PointLight(0xc4c4c4, 1, 10000);
light3.position.set(0, 100, -500);
scene.add(light3);
light4 = new THREE.PointLight(0xc4c4c4, 1, 10000);
light4.position.set(-500, 300, 0);
scene.add(light4);
controls.update();
// Animation Script
function animate() {
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
// Setup GLTF Loader and load in obj
let loader = new THREE.STLLoader();
loader.load('example.stl', function (geometry) {
// console.log(gltf);
var material = new THREE.MeshPhongMaterial({
color: 0x171717,
shininess: 200
});
var mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.position.set(0, 0, 0);
scene.add(mesh);
renderer.render(scene, camera)
animate();
});
}
// Call method for starting init
init();
</script>
</body>
</html>
EDIT:
Example of my intended output, but in Fusion 360 instead of three.js (Note, the original file is a .stl but since three.js doesn't allow for part viewing, it is converted to .stl in the backend)
Example of the 'triangles' in a .stl file

Rendering Complicated Models in Three.js

if some rockstar could look through my code real quick and tell me why I cant render/see my three.js model I'd be forever in your debt!
I'll post the whole script, but I think some preliminary info is important. Basically, I have thousands of points that look like this:
472232.14363148 2943288.56768013 200.129142"
472237.03086105 2943289.62356560 200.119496"
472241.91809061 2943290.67945108 200.109851"
472246.80532018 2943291.73533656 200.100205"
...and so on...
and a bunch of faces that look like this:
["1021 1020 1061", "640 754 641", "1534 1633 1535", "4701 27 26", "654 753 655", ...and so on...
When I extracted all the data and configured it correctly I then push it to the geometry and try to add it to the scene but with no success. Here's the whole script:
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 50, 50 );
camera.lookAt( 0, 0, 0 );
var scene = new THREE.Scene();
var material = new THREE.MeshStandardMaterial( { color : 0x00cc00 } );
var geometry = new THREE.Geometry();
var points = bigData['LandXML']['Surfaces']['Surface']['Definition']['Pnts']['P']
var faces = bigData['LandXML']['Surfaces']['Surface']['Definition']['Faces']['F']
for(i=0;i<points.length;i++){
var [point1,point2,point3] = points[i]['__text'].split(' ').map(Number)
//point1 *= .00001
//point2 *= .00001
//point3 *= .00001
geometry.vertices.push(new THREE.Vector3(point1,point2,point3))
}
for(i=0;i<faces.length;i++){
console.log(faces[i]);
var [face1,face2,face3] = faces[i].split(' ').map(Number)
var face = new THREE.Face3(face1 - 1,face2 - 1, face3 - 1)
geometry.faces.push(face)
}
scene.add( new THREE.Mesh( geometry, material ) );
You can see that in the for loop for the points, I have multiplied them by .00001 to scale the model because otherwise the numbers are so huge, if that makes sense. And I subtract 1 from each face because the data was not zero indexed. Anyways, if any coding superhero took the time to read this and has some insight, please help me out! Thanks!
So, I found your model on scene, the code missed those parts:
Your model has big coordinates, but small dimension. After dividing
all coordinates by 100.000 it has boundingSphere center at (4.72;
29,432; 0,00192) and boundingSphere radius 0,00480, So you need
to either translate model so it has center at (0;0;0) or move camera
target to it's center;
geometry.computeFaceNormals() must be called after building faces
in order to make faces resposive to lighting and shading;
Scene needs some light in it. In curent state it will not show
anything
Render loop or at least single call after adding mesh is required.
Here is fixed code, additionally I played with camera distance and added simple rotation animation. Also I used geometry translation approach to have an adequate rotation:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>DXF Test</title>
</head>
<body>
<script src="three.min.js"></script>
<script type="text/javascript" src="JFM.js"></script>
<script>
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.set(0, 650, 650);
camera.lookAt(0, 0, 0);
var scene = new THREE.Scene();
var material = new THREE.MeshStandardMaterial({color: 0x00cc00});
var geometry = new THREE.Geometry();
//geometry.vertices.push(new THREE.Vector3(4.72227256402, 29.4328751179, 0.00200138787));
//geometry.vertices.push(new THREE.Vector3(3, 3, 0));
//geometry.vertices.push(new THREE.Vector3(5, 6, 0));
//var line = new THREE.Line( geometry, material );
//scene.add( line );
//renderer.render( scene, camera );
var points = bigData['LandXML']['Surfaces']['Surface']['Definition']['Pnts']['P'];
var faces = bigData['LandXML']['Surfaces']['Surface']['Definition']['Faces']['F'];
console.log('Complete XML file');
console.log(bigData);
console.log('POINTS');
console.log(points);
console.log('FACES');
console.log(faces);
for (i = 0; i < points.length; i++) {
var [point1, point2, point3] = points[i]['__text'].split(' ').map(Number);
geometry.vertices.push(new THREE.Vector3(point1, point2, point3));
}
for (i = 0; i < faces.length; i++) {
var [face1, face2, face3] = faces[i].split(' ').map(Number);
var face = new THREE.Face3(face1 - 1, face3 - 1, face2 - 1);
geometry.faces.push(face)
}
geometry.computeFaceNormals();
geometry.computeBoundingSphere();
// translate model so it's real center will be the same as mesh pivot
geometry.translate(
-geometry.boundingSphere.center.x,
-geometry.boundingSphere.center.y,
-geometry.boundingSphere.center.z
);
// compute bounding sphere again, because it was broken during translation
geometry.computeBoundingSphere();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var sunLight = new THREE.DirectionalLight(0xffffff);
sunLight.position.set(200, 600, 1000);
scene.add(sunLight);
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
mesh.rotation.y += 0.01;
mesh.rotation.x += 0.01;
}
animate();
</script>
</body>
</html>
If you don't like translation, you can remove it, remove rotation and after computeBoundingSphere do like this:
camera.position.set(
geometry.boundingSphere.center.x + geometry.boundingSphere.radius,
geometry.boundingSphere.center.y + geometry.boundingSphere.radius,
geometry.boundingSphere.center.z + geometry.boundingSphere.radius,
);
camera.lookAt(
geometry.boundingSphere.center.x,
geometry.boundingSphere.center.y,
geometry.boundingSphere.center.z,
);
Hope it will help.

Three.js mouse action

i asked yesterday about how to draw 3D objects in the browser, be able to manipulate them with the mouse and be sure that it works in every browser. Now i am using three.js and im quite happy because it actually works.
I was able to understand the basic tutorial, draw my first scene and rotate the first cube etc.
Now i want to be able to rotate the complete scene with the mouse, googled and found this link. Works fine in the example and i tried to copy the example. Current code looks like this:
<html>
<head>
<title>My first Three.js app</title>
<style>canvas { width: 100%; height: 100% }</style>
</head>
<body>
<script src="three.min.js"></script>
<script src="TrackballControls.js"></script>
<script type="text/javascript">
// Variables
var scene, camera, controls, renderer;
var geometry, material, cube;
init();
animate();
render();
function init() {
// Scene, camera
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 500, window.innerWidth / window.innerHeight, 0.1, 1000 );
// Mouse controls
controls = new THREE.TrackballControls(camera);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [ 65, 83, 68 ];
controls.addEventListener('change',test,false);
// Renderer
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// Basic cube to show
geometry = new THREE.CubeGeometry(10,10,10);
material = new THREE.MeshBasicMaterial({color:0x00ff00});
cube = new THREE.Mesh(geometry);
scene.add(cube);
camera.position.z = 10;
}
function test() {
alert("hi");
render();
}
function animate() {
requestAnimationFrame(render);
controls.update();
}
function render() {
renderer.render(scene, camera);
//cube.rotation.x += 0.1;
//cube.rotation.y += 0.1;
}
</script>
</body>
But it doesn't work. The testoutput "hi" only appears once. It should appear quite often when using the mouse. What i tried to accomplish is that controls.addEventListener('change',test,false); looks if anything with the mouse changes (position, keydown etc.) and then calls a new renderframe. In the example (Example) it somehow does what i want it to do.
What am i doing wrong here? I just want to be able to rotate the camera with mousedragging. And yes i am totally new to this. Just read about it for 2 hours now.
Thanks in advance.

Categories

Resources