How to add different camera using orbit controls? - javascript

What I have:
Notice that there are two cameras here (one is shown in the 3D space and the other one is recording that specific space). I use the code below to create the first/second camera and the orbit controls.
//Add First THREE Camera
camera_Main = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera_Main.position.set(-2000, 500, 500); //Set camera position
camera_Main_declared = true; //Checks if camera is declared for animate() function
orbitController_Main = new THREE.OrbitControls(camera_Main, renderer_Main.domElement);
orbitController.maxPolarAngle = Math.PI / 2;
orbitController.enablePan = false;
orbitController.maxDistance = 2800;
orbitController.minDistance = 400;
orbitController.saveState();
//Create Second Camera
camera_RT = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, -100, -500);
var helper = new THREE.CameraHelper( camera_RT );
scene_Main.add( helper );
The issue:
What I'm trying to do is to set a different camera for the orbit controller so that when I call:
orbitController.?????????? // Set camera_RT as default
orbitController.reset(); //Reset camera_RT orbit controls
it would reset the orbit controls of camera_RT and not camera_Main.

The simplest way to alternate control between two cameras is to just declare two separate OrbitControls(), then enable/disable them as needed:
var oControl_Main = new THREE.OrbitControls(camera_Main, renderer_Main.domElement);
var oControl_RT = new THREE.OrbitControls(camera_RT, renderer_Main.domElement);
// Only main camera is enabled
function enableMain() {
oControl_Main.enabled = true;
oControl_RT.enabled = false;
}
// Swap control to RT camera
function enableRT() {
oControl_Main.enabled = false;
oControl_RT.enabled = true;
}
A more involved way to swap control is to copy the source code of OrbitControls.js so you can edit it, and add a method that changes the camera that this.object = object refers to. However, you might also need to worry about swapping the two different states with all their rotations and positions, which could get complicated.

Related

How to properly utilize settimeout function to rotate a cube with three.js

The program I am writing will be used to rotate a cube. The html file will allow the user to input a csv file with quaternion data. Then, the javascript code rotates the cube using each quaternion in the data file at a constant rate. In this case, each new quaternion is applied to the cube every 16.7 milliseconds (60 fps). The current program I have written uses setTimeout to execute this goal.
Here is the javascript code I currently have:
<script>
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 );
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff70 } );
var cube = new THREE.Mesh( geometry, material );
scene.add( cube );
camera.position.z = 5;
renderer.render(scene,camera);
var animate = function() {
quat_data = [];
var rotate = function(quat) {
cube.applyQuaternion(quat);
}
for(i=0; i<quat_data.length; i++) {
time = 16.7;
var next_quat = quat_data[i];
setTimeout(rotate(next_quat), time);
}
}
</script>
The user clicks a button on the top of the browser display to execute the animate function. Also, note that quat_data is currently empty. To test the javascript code, I set quat_data equal to an array of sample quaternions. I have not yet written code to convert the inputted csv file to an array. Once I do, I will use that instead of the sample quaternions.
The problem is that when I run the program, the cube displays, but it does not rotate. I include enough sample quaternions to be able to observe the rotation, so lack of quaternions is not the issue. I get no console errors. I've tried including the line
renderer.render(scene,camera);
within the rotate function and at several other locations in the code, but that did not re-render the cube.
What code should I include in the animate function to re-render the cube after each quaternion is applied? Or, is there a way to execute the same task with requestAnimationFrame?
First thing is, you need to use the renderer.render(scene,camera); inside the rotate function because after changing the cube's orientation, you need to re-render the scene.
The second thing is, you are using the setTimeout function in a wrong way. You want to call the rotate function after each of 16.7, 33.4, 50.1 ... milliseconds. So you have to pass the time that way. Also you have to pass a callback function instead of directly calling the function. Try this following code -
let time = 0;
for(let i=0; i<quat_data.length; i++) {
time += 16.7;
setTimeout(function() {
rotate(quat_data[i])
}, time);
}
The last thing is, try to avoid setTimeout function for animation, instead use window.requestAnimationFrame. This function will be called by the browser before the next repaint and you can change the cube orientation in this function and render the scene. Here is the sample code -
let i= 0;
var animate = function () {
requestAnimationFrame( animate );
i++;
if (i >= quat_data.length)
i = quat_data.length - 1;
let quat = quat_data[i];
cube.applyQuaternion(quat);
renderer.render( scene, camera );
};
animate();

Switch between two scenes using the same renderer in three.js

I have been trying to find a way to be able to toggle between two scenes in three.js. I am aware that one can load a scene by using sceneLoader / exportScene combo.
Code taken from josdirksen/learning-threejs - loading a scene
var controls = new function () {
this.exportScene = function () {
var exporter = new THREE.SceneExporter();
var sceneJson = JSON.stringify(exporter.parse(scene));
localStorage.setItem('scene', sceneJson);
};
this.clearScene = function () {
scene = new THREE.Scene();
};
this.importScene = function () {
var json = (localStorage.getItem('scene'));
var sceneLoader = new THREE.SceneLoader();
sceneLoader.parse(JSON.parse(json), function (e) {
scene = e.scene;
}, '.');
}
};
From my understanding of the above code you need to have the scene loaded first before you can extract it and save to local storage before you can put it back into the scene. I am also aware that SceneLoader is now deprecated.
For my senario I want to have an initial scene load and by clicking the 'scene2' button I then want to display scene2 only and if I click the 'scene1' button go back to seeing scene1 only (see fiddle below).
A Basic Example setup
I'm not sure where to begin with this, so any pointers suggestions or advice would be helpful.
If you need to just switch to new scene, then why not have two scene object and one main scene. Try following code
/* Buttons to handle scene switch */
$("#scene2").click(function() {
scene = scene2
})
$("#scene1").click(function() {
scene = scene1
})
function init() {
....
/* I dont think you need to add camera to scene for viewing perpose. By doing this, essentially you are adding camera object to scene, and you won't be able to see it because scene is rendered using this camera and camera eye is at same location
*/
scene1 = new THREE.Scene();
// Build scene1
// scene1.add(camera);
scene2 = new THREE.Scene();
// Build scene2
// Choosing default scene as scene1
scene = scene1;
}
function render() {
// Try some checking to update what is necessary
renderer.render(scene, camera);
}
Updated jsfiddle
You can redraw the canvas by removing current scene scene.remove(mesh); and add create new mesh add into scene
Demo http://jsfiddle.net/sumitridhal/x8t801f5/4/
You can add custom controls using dat.GUI library.
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.4/dat.gui.js"></script>
//
var controls = new function() {
// we need the first child, since it's a multimaterial
this.radius = 10;
this.detail = 0;
this.type = 'Icosahedron';
this.redraw = function() {
// remove the old plane
scene.remove(mesh);
// create a new one
// add it to the scene.
scene.add(mesh);
}
});
var gui = new dat.GUI();
gui.add(controls, 'radius', 0, 40).step(1).onChange(controls.redraw);
gui.add(controls, 'detail', 0, 3).step(1).onChange(controls.redraw);
gui.add(controls, 'type', ['Icosahedron', 'Tetrahedron', 'Octahedron', 'Custom']).onChange(controls.redraw);
Demo http://codepen.io/sumitridhal/pen/NjbGpB

Three.js: Can I update the 3D object in WebGL only in few cases like texture add or mouse move?

I have a 3D object from Blender: cylinder.obj which I am able to render on the screen using Three.js. I also have the code in place to rotate the object using mouse. All this scene is inside a big div, say 600x600 pixel div.
I have the following code for render logic:
// Renders the scene and updates the render as needed.
function animate() {
// Render the scene.
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
This in my understanding is creating a loop and rendering the object again and again on the page.
Is there a way I can render the object once in the scene and then only redraw when there is any change to the object like texture change or when rotating the object using mouse. Once this texture application is done or the mouse rotate is complete, I want the scene to say intact and not consume any CPU for 3D rendering.
So, in brief, is there a way to render the scene only when needed and then stay idle and not consume a lot of CPU when the scene is behaving like an image.
I am new to three.js. I am testing this application in Chrome and IE11. Let me know if any more information is needed for clarification.
EDIT: Adding full JS code:
// global variables
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
var WIDTH = $("#myDiv").width(),
HEIGHT = $("#myDiv").height();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
// Create a renderer and add it to the DOM.
if (Detector.webgl)
renderer = new THREE.WebGLRenderer({ antialias: true });
else
renderer = new THREE.CanvasRenderer(); //IE10 and below, and may be mobile devices
renderer.setSize(WIDTH, HEIGHT);
renderer.domElement.id = 'mycanvas'; //setting id for canvas element
console.log(renderer);
$("#myDiv").append(renderer.domElement);
// Create a camera, zoom it out from the model a bit, and add it to the scene.
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 100);
camera.position.set(-3, 4, 12);
scene.add(camera);
// Create an event listener that resizes the renderer with the browser window.
window.addEventListener('resize', function () {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// Set the background color of the scene.
renderer.setClearColor(0x333F47, 1);
var light2 = new THREE.PointLight(0xffffff);
light2.position.set(0, 10, -10);
scene.add(light2);
var light3 = new THREE.PointLight(0xffffff);
light3.position.set(1, 5, 10);
scene.add(light3);
// Load in the mesh and add it to the scene.
var objLoader = new THREE.OBJLoader();
objLoader.load('objects/male.obj', function (object) {
object.position.y = 0;
object.scale.x = object.scale.y = object.scale.z = 1;
scene.add(object);
});
}
// Renders the scene and updates the render as needed.
function draw() {
//requestAnimationFrame(draw);
// Render the scene.
renderer.render(scene, camera);
}
init();
draw();
To do something only after something happens is called event-driven programming.
For example:
function OnMouseMove(evt) {
// do some transform update on object
// after done updating - draw!
draw();
}
function textureLoaded() {
// hurray, my texture's now loaded, I'm ready to draw now
draw();
}
function draw() {
renderer.render(scene, camera);
}
So instead drawing with timer, you draw thing only when you need them to, and in your case, after mouse move or after the texture's finished loading.
You could then attach OnMouseMove to an event listener, and textureLoaded as some Three.js's callback function for texture loading.
P. S. GPU is actually doing the drawing, not the CPU.
Figured out the logic using start - stop animation function on my events from link: Prevent requestAnimationFrame from running all the time

Rotate camera around object with Three.js

I'm displaying an OBJ element with Three.js using WebGlRenderer, now I'd like to allow users to rotate the camera around the object in any direction, I've found this answer:
Rotate camera in Three.js with mouse
But both examples return me errors, the first says that projector is not defined, and I don't know what it means with "projector". I've just a simple camera, the object and some light.
The second code says that undefined is not a function.
Does someone know how to get the result I need?
This is what you want: http://threejs.org/examples/misc_controls_orbit.html
Include the orbit controls (after you have downloaded them):
<script src="js/controls/OrbitControls.js"></script>
Setup the variable:
var controls;
Attach the controls to the camera and add a listener:
controls = new THREE.OrbitControls( camera );
controls.addEventListener( 'change', render );
and in your animate function update the controls:
controls.update();
[Update] controls.autoRotate = true; (tested in v73. Recent versions of OrbitControls.js has added this control.)
If you don't want to mess with OrbitControls, simply add the camera to a boom Group
const boom = new THREE.Group();
boom.add(camera);
scene.add(boom);
camera.position.set( 0, 0, 100 ); // this sets the boom's length
camera.lookAt( 0, 0, 0 ); // camera looks at the boom's zero
then you can rotate this boom instead of the camera itself.
boom.rotation.x += 0.01;
You may want to add some extra objects, like lights etc. to this boom, btw
Here is a quick hack, in case you don't want to use the OrbitControls for some reason.
camera.position.copy( target );
camera.position.x+=Math.sin(camera.rotationy)*3;
camera.position.z+=Math.cos(camera.rotationy)*3;
camera.position.y+=cameraHeight; // optional
tempVector.copy(target).y+=cameraHeight; // the += is optional
camera.lookAt( tempVector );
camera.rotationy is a copy of the mouse rotation value since we are changing it with the call to lookAt.
If you are using ES6, following could be used for OrbitControls
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// this would create the orbit controls
// it would allow camera control using mouse
const orbitControls = new OrbitControls(camera, renderer.domElement);
If you need autorotate,
function init() {
...
// following would enable autorotate
const orbitControls.autoRotate = true;
..
}
function animate() {
// need to update the orbitcontrols for autorotate camera to take effect
orbitControls.update();
...
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
For ref: https://threejs.org/docs/#examples/en/controls/OrbitControls
Indeed, if you substitute 'camera' with the object of your choice, the object will rotate. But if there are other objects surrounding it (for example a grid on the floor), they will still stand still. That might be what you want, or it might look weird. (Imagine a chair rotating floating above the floor...?)
I choose to override the center object from OrbitControls.JS from my code after initializing the Orbit Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
…
controls.center = new THREE.Vector3(
chair.position.x,
chair.position.y,
chair.position.z
);
(disclaimer: I have the impression there are different versions of OrbitControls.js around, but I assume they all use this center-object)
Add a listener to trigger render method on change of OrbitControl:
const controls = new OrbitControls(camera, this.renderer.domElement);
controls.enableDamping = true; //damping
controls.dampingFactor = 0.25; //damping inertia
controls.enableZoom = true; //Zooming
controls.autoRotate = true; // enable rotation
controls.maxPolarAngle = Math.PI / 2; // Limit angle of visibility
controls.addEventListener("change", () => {
if (this.renderer) this.renderer.render(this.scene, camera);
});
and in animate update controls:
start = () => {
if (!this.frameId) {
this.frameId = requestAnimationFrame(this.animate);
}
};
stop = () => {
cancelAnimationFrame(this.frameId);
};
renderScene = () => {
if (this.renderer) this.renderer.render(this.scene, camera);
};
animate = () => {
// update controls
controls.update();
}
Extra info for who looking auto rotate direction change on a limit:
if (
controls.getAzimuthalAngle() >= Math.PI / 2 ||
controls.getAzimuthalAngle() <= -Math.PI / 2
) {
controls.autoRotateSpeed *= -1;
}
controls.update();

Three.js and colladaloader

I am new to Three.JS and am trying to load a load a very simple (single cube) Sketchup model into Three.JS via the ColladaLoader, I get no errors but nothing is displayed:
var renderer = new THREE.WebGLRenderer();
var loader = new THREE.ColladaLoader();
var scene = new THREE.Scene();
var camera = new THREE.Camera();
loader.load('Test.dae', function (result) {
scene.add(result.scene);
});
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(0, 0, 5);
scene.add(camera);
renderer.render(scene,camera);
Can anyone spot any immediate errors? Thanks
Fixed. Whilst I had declared the renderer, I hadn't attached it to the document. The following code works:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 0, 4);
var loader = new THREE.ColladaLoader();
loader.load("test.dae", function (result) {
scene.add(result.scene);
});
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
You need to rerender the scene after the collada is loaded. Something like
loader.load('Test.dae', function (result) {
scene.add(result.scene);
renderer.render(scene,camera);
});
Additionally:
The camera might be inside your geometry. Faces are one-sided by default, they are invisible when looking from back/inside. You could try changing the camera or object location and/or set material.side = THREE.DoubleSide so the faces are visible from both front and back.
The scale of the model can be way off, so it can be too small or large to show. Try different camera positions (eg. z= -1000, -100, -10, -1, 1, 10, 100, 1000) and use lookAt() to point it to 0,0,0 or, I think colladaloader has a scale setting nowadays, not sure.
Loader defaults to position 0,0,0, so center of the world/scene. That doesn't necessarily mean center of screen, depends on camera. In some cases the Collada model itself can be such that the center is away from visible objects, so when it's placed on the scene it can be effectively "off-center". That's quite unlikely though.
You dont have any lights in the scene.
loader.load('test.dae', function colladaReady(collada) {
localObject = collada.scene;
localObject.scale.x = localObject.scale.y = localObject.scale.z = 2;
localObject.updateMatrix();
scene.add(localObject);
I think you need to add the object in the collada scene, or there might be problem with the scaling of the object that you are adding, scale it and than update the matrix of the object.
If your still having trouble it may be that some versions of IE stop you downloading local files (your Test.dae) so it may be worth trying it using firefox or putting your code up on a server.

Categories