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();
Related
I'm trying to make a little "engine" of sorts that allows me to easily create and position objects using three.js. So far I am able to set up a scene, renderer, and camera and everything seems to be working properly, until I try to render and attach a cube to my scene.
I am able to create the cube and attach it to my scene just fine, but my code seems to fail when I try to use the render() function in three.js and I get the error:
THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.
But I'm not really sure why. I can't find much about this error online so I was hoping someone here could point me in the right direction.
While troubleshooting, I tried to output my cube to the console to see if I could get any useful info from it. It seems like it attaches to my scene just fine and both my scene and camera show up as a parent object to the cube.
Here's the code:
function spnScene(alias, fov, x, y, z) {
window.spnCreateScene = new THREE.Scene();
var WIDTH = window.innerWidth, HEIGHT = window.innerHeight;
if (alias == false) {
console.warn('spnRenderer scene antialias set to ' + alias + ". Please check your code to make sure it's correct.");
}
spnRenderer = new THREE.WebGLRenderer({
antialias: alias
});
spnRenderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(spnRenderer.domElement);
// create camera
spnCamera = new THREE.PerspectiveCamera(fov, WIDTH / HEIGHT, 2000);
spnCamera.position.set(x, y, z);
spnCreateScene.add(spnCamera);
}
function spnBasicCube(material, clr, l, w, depth, x, y, z) { // draw a basic cube and render it
if (material == "basic") {
var spnBasicCube = new THREE.Mesh(new THREE.CubeGeometry(l, w, depth), new THREE.MeshBasicMaterial({color: clr}));
spnBasicCube.position.x = x;
spnBasicCube.position.y = y;
spnBasicCube.position.z = z;
spnCreateScene.add(spnBasicCube);
spnRenderer.render(spnBasicCube);
console.log(spnBasicCube);
// this should end up making a cube but throws an error rn
}
}
I hope someone can help me out! Thanks! :)
Don't render the cube
spnRenderer.render(spnBasicCube);
but render the scene with the camera:
spnRenderer.render(spnCreateScene, spnCamera);
See the documentation THREE.WebGLRenderer.render:
.render ( scene : Scene, camera : Camera ) : null
Render a scene using a camera.
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();
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.
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
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.