OrbiterControls not working properly in three.js app - javascript

I'm trying to create a basic scene with an orbitercontrols that can rotate around the center, i.e. always looking at (0,0,0). I want it to behave like this https://threejs.org/examples/misc_controls_orbit.html exactly. But in my version if I click and drag to the left/right, the camera just spins in a circle always looking at (0,0,0). How can I fix this?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js';
import { OrbitControls } from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// ADD LIGHT
const light = new THREE.PointLight(0xffffff, 2)
light.position.set(0, 5, 10)
scene.add(light)
// ADD ORBITER
const controls = new OrbitControls( camera, renderer.domElement );
controls.listenToKeyEvents( window );
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.05;
//controls.screenSpacePanning = false;
//controls.minDistance = 100;
//controls.maxDistance = 500;
//controls.maxPolarAngle = Math.PI / 2;
//controls.minPolarAngle = Math.PI / 2;
//controls.maxPolarAngle = Math.PI / 2;
camera.position.set(0, -5, 5);
camera.lookAt( 0, 0, 0 );
camera.up.set( 0, 0, 1 );
//controls.target = new THREE.Vector3(0, 0, 0);
controls.update();
// ADD CUBE
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
// ADD MAP
const loader = new THREE.TextureLoader();
const height = loader.load('/data/images/height.png');
const texture = loader.load('/data/images/height.png');
const map_geometry = new THREE.PlaneBufferGeometry(10,10,64,64);
const map_material = new THREE.MeshStandardMaterial({
color:'orange',
//side: THREE.DoubleSide,
map:texture,
displacementMap: height,
displacementScale: 0.5,
//alphaMap: alpha,
//transparent:true
});
const map_plane = new THREE.Mesh(map_geometry, map_material);
//map_plane.position.set(0, 0, 0);
scene.add( map_plane );
// RENDER
function animate() {
requestAnimationFrame( animate );
//cube.rotation.x += 0.01;
//cube.rotation.y += 0.01;
renderer.render( scene, camera );
}
animate();
</script>
</body>
</html>

The code needed several modifications. Use the code below instead and compare it with yours to find out which parts did I change.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body {
padding: 0;
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js';
import {OrbitControls} from 'https://threejs.org/examples/jsm/controls/OrbitControls.js';
// BUILDING THE SCENE
const scene = new THREE.Scene();
// ADDING CAMERA
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, -5, 5);
scene.add(camera);
// CREATING THE RENDERER
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// ADD LIGHT
const light = new THREE.PointLight(0xffffff, 2);
light.position.set(0, 5, 10);
scene.add(light);
// ADD ORBITER
const controls = new OrbitControls(camera, renderer.domElement);
controls.maxPolarAngle = Math.PI/2.2;
controls.minDistance = 5;
controls.maxDistance = 10;
controls.target.set(0, 0, 0);
// ADD CUBE
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
const cube = new THREE.Mesh(geometry, material);
cube.position.set(0, .5, 0);
scene.add(cube);
// ADD MAP
const loader = new THREE.TextureLoader();
const height = loader.load('/data/images/height.png');
const texture = loader.load('/data/images/height.png');
const map_geometry = new THREE.PlaneBufferGeometry(10,10,64,64);
const map_material = new THREE.MeshStandardMaterial({
color:'orange',
side: THREE.DoubleSide,
map:texture,
displacementMap: height,
displacementScale: 0.5,
});
const map_plane = new THREE.Mesh(map_geometry, map_material);
map_plane.rotation.x = Math.PI/2;
scene.add(map_plane);
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// ANIMATING THE SCENE
function animate() {
controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
Write controls.update(); in the animate function.
controls.maxPolarAngle = Math.PI/2.2; doesn't let the camera to go under the ground.
controls.target.set(0, 0, 0); makes the control stair at the center.
I added map_plane.rotation.x = Math.PI/2; so the plane will be placed horizontally as the floor.
And side: THREE.DoubleSide, helps the floor to be visible from all angels.
I also added an onWindowResize function to the script to make the applicaion responsive. Now, it'll response to the browser's width and height when its resized.
In case of CSS, the page has default padding as well. So, adding a padding: 0; to the CSS code is not a bad idea as well.

Related

How to create a Sphere using three.js?

I was making a sphere using three.js but the output is just a black screen as shown.
The code I used is:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body { margin: 0; }
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<script>
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const geometry = new THREE.SphereGeometry( 15, 32, 16 );
const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
const sphere = new THREE.Mesh( geometry, material );
scene.add( sphere );
camera.position.z = 5;
function animate() {
requestAnimationFrame( animate );
sphere.rotation.x += 0.01;
sphere.rotation.y += 0.01;
renderer.render( scene, camera );
};
animate();
</script>
</body>
</html>
I couldn't explain why the code was unable to render the sphere. What went wrong?
Your camera is inside the sphere. Move it a bit further away from the origin like in the following live example:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 50;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.SphereGeometry(15, 32, 16);
const material = new THREE.MeshBasicMaterial({
color: 0xffff00
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
function animate() {
requestAnimationFrame(animate);
sphere.rotation.x += 0.01;
sphere.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.143/build/three.min.js"></script>

How to make TextGeometry in THREE JS follow mouse?

This is my source code. I am trying to make the text rotate according to mouse position.
// Initialization
const scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
let body = document.getElementsByTagName("body");
let pageX = 0.5;
let pageY = 0.5;
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById("board").appendChild(renderer.domElement);
// Handle resize event
window.addEventListener('resize', () => {
renderer.setSize( window.innerWidth, window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
camera.position.z = 20;
// Create light
let directLight = new THREE.DirectionalLight('#fff', 4);
directLight.position.set(0, 7, 5);
scene.add(directLight);
var light = new THREE.AmbientLight( 0x404040 ); // soft white light
scene.add( light );
function animate (){
requestAnimationFrame( animate );
var loader = new THREE.FontLoader();
loader.load( 'https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', function ( font ) {
var geometry = new THREE.TextGeometry( 'Hello three.js!', {
font: font,
size: 3,
height: 0.5,
curveSegments: 4,
bevelEnabled: true,
bevelThickness: 0.02,
bevelSize: 0.05,
bevelSegments: 3
} );
geometry.center();
var material = new THREE.MeshPhongMaterial(
{ color: '#dbe4eb', specular: '#dbe4eb' }
);
var mesh = new THREE.Mesh( geometry, material );
mesh.rotation.x = (pageY - 0.5) * 2;
mesh.rotation.y = (pageX - 0.5) * 2;
scene.add( mesh );
} );
renderer.render(scene, camera);
}
animate();
// Get mouse coordinates inside the browser
document.body.addEventListener('mousemove', (event) => {
pageX = event.pageX / window.innerWidth;
pageY = event.pageY / window.innerHeight;
});
renderer.render(scene, camera);
</script>
This is the best I could get. The problem is that each time I move the mouse, it instantiates a new mesh and rotates it accordingly, and I only need one mesh to follow the mouse. Can anyone help?
Thanks in advance!
As you already figured out, each frame you're reloading the font and ultimately recreating the mesh each time.
To get around this you need to move the font loading and object creation inside some initialization function, so it just happens once.
The only part of code you want to keep inside the render loop is the updating of the text's rotation according to the mouse movement:
mesh.rotation.x = (pageY - 0.5) * 2;
mesh.rotation.y = (pageX - 0.5) * 2;
This will bring up another problem though. Since mesh is a local object defined inside the callback function of the font loader, it won't be accessible outside. Luckily three.js offers a property called .name which you can use to give your object a name.
e.g.
var mesh = new THREE.Mesh(geometry, material);
mesh.name = "myText";
scene.add(mesh);
Later on you can get a reference to this object using:
scene.getObjectByName("myText")
Here's an example:
var container, scene, camera, renderer, pageX, pageY;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
pageX = 0.5;
pageY = 0.5;
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("container").appendChild(renderer.domElement);
window.addEventListener('resize', () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
camera.position.z = 20;
let directLight = new THREE.DirectionalLight('#fff', 4);
directLight.position.set(0, 7, 5);
scene.add(directLight);
var light = new THREE.AmbientLight(0x404040); // soft white light
scene.add(light);
var loader = new THREE.FontLoader();
loader.load('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', function(font) {
var geometry = new THREE.TextGeometry('Hello three.js!', {
font: font,
size: 3,
height: 0.5,
curveSegments: 4,
bevelEnabled: true,
bevelThickness: 0.02,
bevelSize: 0.05,
bevelSegments: 3
});
geometry.center();
var material = new THREE.MeshPhongMaterial({
color: '#dbe4eb',
specular: '#dbe4eb'
});
var mesh = new THREE.Mesh(geometry, material);
mesh.name = "myText";
scene.add(mesh);
animate();
});
document.body.addEventListener('mousemove', (event) => {
pageX = event.pageX / window.innerWidth;
pageY = event.pageY / window.innerHeight;
});
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
scene.getObjectByName("myText").rotation.x = (pageY - 0.5) * 2;
scene.getObjectByName("myText").rotation.y = (pageX - 0.5) * 2;
renderer.render(scene, camera);
}
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r120/three.min.js"></script>
<div id="container"></div>

How do I avoid circular movements when placing a cube on the camera position, but a bit behind?

So this is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
overflow: hidden;
margin: 0;
}
</style>
</head>
<body>
<script src="three.js"></script>
<script src="pointerlockcontrols.js"></script>
<script>
var camera, scene, renderer, controls;
var cube;
var previous_time = performance.now();
var color = new THREE.Color();
camera = new THREE.PerspectiveCamera(99, window.innerWidth / window.innerHeight, 1, 100);
camera.position.y = 10;
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0000ff);
scene.fog = new THREE.Fog(0xffffff, 0, 750);
var light = new THREE.HemisphereLight(0xeeeeff, 0x777788, 0.75);
light.position.set(0.5, 1, 0.75);
scene.add(light);
controls = new THREE.PointerLockControls(camera);
document.body.addEventListener("click", function() { controls.lock(); });
scene.add(controls.getObject());
var geometry = new THREE.BoxBufferGeometry(0.8*5, 1.5*5, 0.4*5);
var material = new THREE.MeshPhongMaterial({ color: 0xffffff });
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, - 1, 0), 0, 10);
var floorGeometry = new THREE.PlaneBufferGeometry(2000, 2000, 100, 100);
floorGeometry.rotateX(- Math.PI / 2);
floorGeometry = floorGeometry.toNonIndexed(); // ensure each face has unique vertices
position = floorGeometry.attributes.position;
var colors = [];
for (var i = 0, l = position.count; i < l; i ++) {
color.setHSL(Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75);
colors.push(color.r, color.g, color.b);
}
floorGeometry.addAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
var floorMaterial = new THREE.MeshBasicMaterial({ vertexColors: THREE.VertexColors });
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
scene.add(floor);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', on_window_resize, false);
function on_window_resize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function loop() {
requestAnimationFrame(loop);
if (controls.isLocked === true) {
raycaster.ray.origin.copy(controls.getObject().position);
var vector = new THREE.Vector3();
controls.getObject().getWorldDirection(vector);
cube.rotation.y = Math.atan2(vector.x, vector.z);
cube.position.x = controls.getObject().position.x;
cube.position.z = controls.getObject().position.z+1;
}
renderer.render(scene, camera);
}
loop();
</script>
</body>
</html>
The important part here is:
// This makes the cube always look in the direction of the camera.
var vector = new THREE.Vector3();
controls.getObject().getWorldDirection(vector);
cube.rotation.y = Math.atan2(vector.x, vector.z);
// This makes the cube always be on the position of the camera (for later when there's movement)
cube.position.x = controls.getObject().position.x;
cube.position.z = controls.getObject().position.z+1;
And that works all pretty well, however because of the +1 (I want the cube to be a bit behind the camera) at the end, the cube is doing circular movements when rotating the camera.
I tried
cube.rotation.y = Math.atan2(vector.x, vector.z+1);
However that makes the cube not rotate at all.
How do I avoid the circular movements? I just want to place the cube on my position, a bit behind me, while it also faces the direction I look at.

STLLoader.js code example for starting with three.js? Is there any good clean sample code?

I have to include the STLLoader.js of three.js into my web application but can't find some good clean code examples on how to start.
On the https://threejs.org website there is only the example demonstration for the STLLoader (https://threejs.org/examples/#webgl_loader_stl) but I can't find some clean sample code for the STLLoader (except for this one: https://github.com/mrdoob/three.js/blob/master/examples/webgl_loader_stl.html which is still quite difficult to understand for beginners), like there is for some other Loaders e.g. the OBJLoader (https://threejs.org/docs/index.html#examples/en/loaders/OBJLoader)...
Please help me out with this:)
That is what i came up with at the end...
For all those who had problems figuring it out too:)
HTML:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>STLLoader Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<script src="three.js-master/build/three.js"></script>
<script src="three.js-master/examples/jsm/controls/OrbitControls.js"></script>
<script src="three.js-master/examples/jsm/loaders/STLLoader.js"></script>
<script src="js/custom.js"></script>
</body>
</html>
JS (custom.js):
// Necessary for camera/plane rotation
var degree = Math.PI/180;
// Setup
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Resize after viewport-size-change
window.addEventListener("resize", function () {
var height = window.innerHeight;
var width = window.innerWidth;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
// Adding controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
// Ground (comment out line: "scene.add( plane );" if Ground is not needed...)
var plane = new THREE.Mesh(
new THREE.PlaneBufferGeometry(500, 500 ),
new THREE.MeshPhongMaterial( { color: 0x999999, specular: 0x101010 } )
);
plane.rotation.x = -90 * degree;
plane.position.y = 0;
scene.add( plane );
plane.receiveShadow = true;
// ASCII file - STL Import
var loader = new THREE.STLLoader();
loader.load( './stl/1.stl', function ( geometry ) {
var material = new THREE.MeshLambertMaterial( { color: 0xFFFFFF, specular: 0x111111, shininess: 200 } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set( 0, 0, 0);
scene.add( mesh );
} );
// Binary files - STL Import
loader.load( './stl/2.stl', function ( geometry ) {
var material = new THREE.MeshLambertMaterial( { color: 0xFFFFFF, specular: 0x111111, shininess: 200 } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set( 0, 20, 0);
scene.add( mesh );
} );
// Camera positioning
camera.position.z = 100;
camera.position.y = 100;
camera.rotation.x = -45 * degree;
// Ambient light (necessary for Phong/Lambert-materials, not for Basic)
var ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
// Draw scene
var render = function () {
renderer.render(scene, camera);
};
// Run game loop (render,repeat)
var GameLoop = function () {
requestAnimationFrame(GameLoop);
render();
};
GameLoop();
And of course you need to download the three.js project and include it into your project root.
Leon

How to rotate object with offset by orbit control in three.js?

I want to control object like this:
Now camera is move around Y coordinate, but need to be move around NEW Y. Code is here:
var material = new THREE.MeshLambertMaterial({color: 0x00ff00});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
var controls = new THREE.OrbitControls(camera);
cube.position.x = 300;
// controls.target = THREE.Vector3(300, 0, 0);
I found that target option can set the focus point of the controls, but if I add this code: controls.target = THREE.Vector3(300, 0, 0); the object back to the center of the screen, but it should be at the right edge.
More over, when I set new THREE.OrbitControls(camera); to new THREE.OrbitControls(cube); and add controls.target = THREE.Vector3(300, 0, 0); control doesn't work, but when I set controls.target = THREE.Vector3(301, 0, 0); it is managed but not like a camera
OrbitControls.target set the focus point of the controls and the .object orbits around this. But it is a manually manipulation. As mentioned in the documentation OrbitControls.update() must be called after any manual changes to the camera's transform.
e.g.
controls.target = new THREE.Vector3(1, 0, 0)
controls.update();
See the example:
(function onLoad() {
var container, camera, scene, renderer, controls;
init();
animate();
function init() {
container = document.getElementById('container');
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 100);
camera.position.set(0, 1.0, -4);
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
scene.add(camera);
window.onresize = resize;
var ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set(1,2,1.5);
scene.add( directionalLight );
controls = new THREE.OrbitControls(camera, container);
controls.target = new THREE.Vector3(3, 0, 0)
controls.update();
controls.autoRotate = true;
controls.autoRotateSpeed = 5.0
var axis = new THREE.AxesHelper(1000);
scene.add(axis);
var material = new THREE.MeshPhongMaterial({color:'#b090b0'});
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
function resize() {
var aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
render();
}
function render() {
renderer.render(scene, camera);
}
})();
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.115/examples/js/controls/OrbitControls.js"></script>
<div id="container"></div>

Categories

Resources