How to reset the OrbitControls and when to use the update method? - three.js

I want to reset my camera which has an active damping. I've tried different ways but I don't know if they are correct.
The goal is to stop the damping and set the initial position of the camera and then activate the damping again. I want to avoid that the model/camera is rotating a bit after I reset the controls with a button. I would do it like this:
controls.enableDamping = false;
controls.update();
camera.position.set( 10, 13, 10 );
camera.lookAt( 0, 0, 0 );
controls.enableDamping = true;
controls.update();
My rendering function is called by a EventListener:
controls.addEventListener( "change", requestRenderer );
And the render function:
const renderer = new THREE.WebGLRenderer( { canvas: canvas, antialias: true, alpha: true } );
let renderRequested = false;
function render( time ) {
time *= 0.001;
renderRequested = false;
resizeRenderer( renderer, camera );
controls.update();
renderer.render( scene, camera );
}
function requestRenderer() {
if( !renderRequested ) {
renderRequested = true;
requestAnimationFrame( render );
}
}
This works pretty well. The question is if this is the correct way and when do I have to update the controls? I think the first update is necessary to tell the controls that the damping isn't active anymore (but what does the update do?) and I think, that I don't need the second update.

From your code, it seems like you don't need the second update. You aren't changing a key property about the controls, in your render() loop:
controls.update();
Which covers every other case.

Related

load and dispose textures during runtime

I'm trying to understand how to be memory efficient in three.js. In my main application i try to load and dispose textures depending on the distance to the camera to save memory in the gpu. Since i work with a lot of textures and large textures in my main application i can't possibly work with a texture atlas into which i initially load all the textures. Here I have a code example of how i imagine it. Loading the texture works but not deleting it. But i don't see why that is. According to the three.js docu this should work with dispose() but it doesn't work here.Wwhere is my mistake?
In general: If someone thinks there is something in my concept that could be improved, i'd be happy to hear your advice. I only do what i do within the framework of what i can best imagine based on what i know so far. I like to learn about it. For example, i would be interested in how i not only load and dispose textures. Also the object that i have created here should be efficiently added and deleted as a whole, added again and deleted, ... after all, that's the purpose of class objects, being able to be created and deleted again at will.
var camera, controls, scene, renderer, container, aspect;
var cube;
async function main() {
await init();
animate();
}
async function init() {
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setClearColor( 0x000000 );
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, aspect, 1, 1000000 );
camera.position.set(0, 0, 300);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//------End three.js setup-------
cube = new Cube();
scene.add(cube.box);
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
function render() {
var distance = camera.position.distanceTo( cube.box.position );
cube.update(distance);
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
class Cube{
constructor(){
this.texture;
this.texstatus = false;
const geometry = new THREE.BoxGeometry( 10, 10, 10 );
const material = new THREE.MeshBasicMaterial( {
color: 0x00ff00,
map: null
} );
this.box = new THREE.Mesh( geometry, material );
}//end constructor
update(distToCam){
if(distToCam <= 100 && this.texstatus == false){
var loader = new THREE.TextureLoader();
this.texture = loader.load("grass.jpg");
this.box.material.map = this.texture;
this.texstatus = true;
}
if(distToCam > 100 && this.texstatus == true){
//prvious solution deactivated
//this.texture.dispose();
//this.texstatus = false;
//new solution
this.box.material.map = null;
this.texture.dispose();
this.texstatus = false;
this.texture = undefined;
}
this.box.material.needsUpdate = true;
}//end update
}//end Cube class

threejs mixer not updating with `setTime`

I have a threejs animation mixer set up as follows:
this.mixer = new THREE.AnimationMixer(this.object);
this.mixer.timeScale = 2; //play twice as fast
this.mixer.addEventListener('finished', this.handlePullAnimFinished);
this.pullAnim = this.mixer.clipAction(this.animations[0]);
this.pullAnim.clampWhenFinished = true;
this.pullAnim.setLoop(THREE.LoopOnce);
this.pullAnim.enable = true;
but if I try to do something like this.mixer.setTime(0.5), followed, optionally, by this.mixer.update() nothing happens
How do I programmatically set the mixer to a specific point in an animation (and not have it autoplay)?
I've seen the documentation here on setting up animations to autoplay (and successfully gotten that to work)
At first glance it looks like your mistake is that .update() does not have any arguments in it.
According to the docs, the mixer expects the update method to receive a change in seconds on each frame.
According to this demo you can do something like this:
var clock = new THREE.Clock();
function animate() {
var dt = clock.getDelta();
mixer.update( dt );
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
you could try this:
// r3f version
useEffect(()=>{
action.play()
}, [])
useFrame(()=>{
mixer.setTime(.3)
})
// vanilla version
actions['EmptyAction.001'].play()
let time = .3
animate(){
mixer.setTime(time)
requestAnimationFrame(animate)
}
animate()

touch controls: repeat action until touchend

I am trying to add touch controls to a three.js scene. I want to move the camera in whatever direction the user touches. It works great using the keyboard because you can press and hold the button and the camera moves continuously. But when I try the same thing using touchstart, you have to keep tapping the screen over and over to move, you can't just hold your finger down like on a keyboard or mouse.
I looked at touchmove, but if you just tap and hold without moving, there are no new touches.
Is there something similar to holding down the keyboard or mousekey using touch events?
There is no builtin callback for a touch event which fires repeatedly like the keyboard. You can, however, simply track the start and end of the touch and then call the move method at a set interval.
First, subscribe to the correct events and set a bool to track the state:
var isTouching = false;
window.addEventListener("touchstart", () => isTouching = true);
window.addEventListener("touchend", () => isTouching = false);
In Three.js you will most likely already have a render loop (e.g. a function called "animate"). Check the state variable at every iteration and apply the movement each time. You may need to also factor in deltaTime (the duration of the last frame), to make movement framerate independent.
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
if (isTouching) {
console.log("move camera");
}
renderer.render(scene, camera);
}
Here is a snippet which shows the basic approach. Click and hold in the left or right half of the output window to move the camera.
var camera, scene, renderer, mesh, material, clock;
init();
animate();
var isTouching = false;
var mousePositionX;
window.addEventListener("mousedown", (e) => {
isTouching = true;
mousePositionX = e.clientX;
});
window.addEventListener("mouseup", (e) => isTouching = false);
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
clock = new THREE.Clock();
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
scene = new THREE.Scene();
material = new THREE.MeshPhongMaterial();
var geometry = new THREE.BoxGeometry(200, 200, 200);
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var light = new THREE.AmbientLight(0x404040);
scene.add(light);
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
window.addEventListener('resize', onWindowResize, false);
}
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
let deltaTime = clock.getDelta();
if (isTouching) {
let speed = 200; // px per second
let movement = speed * deltaTime;
if (mousePositionX > window.innerWidth / 2) {
camera.translateX(-movement);
} else {
camera.translateX(movement);
}
}
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
body {
padding: 0;
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.min.js"></script>

Function LookAt to turn around an object ?

as part of a project, I have to turn the camera around an object (position 0, 0,0) which remains to him still. For this, I want to know if the LookAt function is the one that is best suited , And also how does it work?
Integrating OrbitControls should be done with a few lines of code. So, the basic lines of code should be:
// init
var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = false; // optional
controls.enablePan = false; // optional
controls.center.set(0,0,0); // should be initially 0,0,0
controls.addEventListener( 'change', render ); // if you are not using requestAnimationFrame()
camera.position.z = 500; // should be bigger than the radius of your sphere
// render
function render() {
renderer.render(scene, camera);
}
<script src="js/controls/OrbitControls.js"></script>
Now, you should be able to rotate the camera around your sphere using your mouse.
All the other essential stuff (camera, renderer) can be found at the example: https://threejs.org/examples/#misc_controls_orbit

Update three.js Group after it was added to the scene on runtime

I have a THREE.Group(); in my scene and at runtime i want to add more to this group but it doesn't get updatet or better i cannot see the sprite that i added on runtime. The sprite is in the group but not rendered.
How can i update a THREE.Group();?
Example Code to clarify my problem. Not really running i know.
var systemGroup = new THREE.Group();
init();
animate();
function init() {
//add something to systemGroup like multiple meshes
systemGroup.add(mesh);
scene.add(systemGroup);
}
function render() {
scene.updateMatrixWorld();
renderer.render( scene, camera );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function addMore(x,y,z){
var map = THREE.ImageUtils.loadTexture( "sprite.png" );
var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
var sprite = new THREE.Sprite( material );
sprite.position.set(x,y,z);
systemGroup.add(sprite);
//Here how do i update the group so that the mesh is in the scene?
}
Thank you.
The code you are showing is adding the same mesh over and over again.
As the mesh is already in the group, it doesn't change anything.
What you seem to want is to clone() the mesh and add it at a different position.
Like this:
var group = new THREE.Group()
scene.add(group);
document.body.addEventListener('click', function(evt) {
var clone = mesh.clone()
clone.position.set(-100 + Math.random()*200, 0, 0)
group.add(mesh)
})
No need to update the group with anything else.

Resources