On a scene, I've got a camera and an obj.
I want my obj always looks at the camera.
I tried with:
function render() {
camera.updateProjectionMatrix();
var zCamVec = new THREE.Vector3(0,0,1);
camera.localToWorld(zCamVec);
obj.lookAt(zCamVec);
renderer.render(scene, camera);
}
but without luck: my obj stays static...
if i understand you right, the object needs to reposition in front of the camera, and look at the camera.
try this:
function render() {
camera.updateProjectionMatrix();
var zCamVec = new THREE.Vector3(0,0,1);
var position = camera.localToWorld(zCamVec);
obj.position.set(position.x, position.y, position.z);
obj.lookAt(camera.position);
renderer.render(scene, camera);
}
Related
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>
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
I'm new to three.js and am having trouble loading a collada object to it. I can't get home.dae to render in the browser.
I updated the code under SECOND UPDATE based on the answers.
// INITIAL
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 loader = new THREE.ColladaLoader();
loader.load('home.dae', function(collada){
scene.add(collada);
});
function render() {
renderer.render(scene, camera);
}
render();
// SECOND UPDATE
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.set(0,1,4);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var loader = new THREE.ColladaLoader();
loader.load('home.dae', function(collada){
scene.add(collada.scene);
});
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
Your render function is called only once. Try with this -
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
Also check if you file path is valid.
Few things that come to mind:
as #Rasheduzzaman already noted, the render-function is called too early (when you call render(), the scene.add(collada); call can not have happened. Use his Answer instead.
the collada-loader works a bit differently: the documentation isn't clear about this, but the returned object is a collection of all the stuff that could've been in the collada-file, see here for a list: https://github.com/mrdoob/three.js/blob/dev/examples/js/loaders/ColladaLoader.js#L181-L204 (also: use the debugger from the browser-devtools to inspect the data you find there). You will probably want to do scene.add(collada.scene) or similar.
you are not setting a position for the camera, so it is located at (0,0,0), which might not be a great idea, try camera.position.set(0,1,4) or something like that.
you need to know what to expect: what is the size of the model you are loading? Where is it placed? Make sure to point your camera there (could use camera.lookAt(object.position)) and adjust nearPlane and farPlane accordingly.
I'm making use of the 3.js library and am at the stage of actually rendering the object/shape to the screen, which renders like this:
In the tutorial that I'm making use of it has some code for rotating the cube on it's x and y axis:
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
This works if I add it to the render function like so:
var render = () => {
requestAnimationFrame(render);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
this.renderer.render(this.scene, this.camera);
};
But I want to be flexible in the animation approach and pass any rotations or whatever as a function to be executed inside the render function. I've tried to do this:
var render = (animation) => {
requestAnimationFrame(render);
animation();
this.renderer.render(this.scene, this.camera);
};
render(this.animation(cube));
What I can see this that the code is executed once correctly as a function but each subsequent time it then say's that it isn't a function.
What am I missing here and how can I ensure that this is executed as a function each and every time?
Thanks
EDIT
From Engineer and sdgluck's answer this is what I think they mean but it still didn't work:
var render = (animation) => {
requestAnimationFrame(render);
animation();
this.renderer.render(this.scene, this.camera);
};
render(() => this.animation(cube));
EDIT 2
This is all the code in the service that creates the cube and attempts to animate it:
export class ThreeService {
constructor() {
this.scene;
this.aspect;
this.camera;
this.renderer;
}
/*
Creates the scene, the camera and the renderer
*/
setup() {
this.createScene();
this.createCamera();
this.createRenderer();
}
createScene() {
this.scene = new THREE.Scene();
}
createCamera() {
// This is the viewpoint that the users are looking from
this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight, 1,10000);
}
createRenderer() {
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById('model').appendChild( this.renderer.domElement );
}
createCube() {
// Creates the basic structure of the cube
var geometry = new THREE.BoxGeometry(700, 700, 700, 10, 10, 10);
// Adds colour to the cube using materials
var material = new THREE.MeshBasicMaterial({color: 0xfffff, wireframe: true});
// Cubes needs a geometry and a material to be rendered
var cube = new THREE.Mesh(geometry, material);
this.addObjToScene(cube);
this.positionCamera(1000);
var render = (animation) => {
debugger
requestAnimationFrame(render);
animation();
this.renderer.render(this.scene, this.camera);
};
render(() => {this.animation(cube)});
}
animation(obj) {
obj.rotation.x += 0.01;
obj.rotation.y += 0.01;
}
addObjToScene(obj) {
// They then needed added to the scene
if(!this.scene)
this.setup();
// By default the add function adds the obj to the coordinates 0,0,0
this.scene.add(obj);
}
positionCamera(zPos) {
if(!this.camera)
this.setup();
this.camera.position.z = zPos;
}
rotateObj(obj,x,y) {
obj.rotation.x += x;
obj.rotation.y += y;
}
}
And this is the error code that is appearing:
EXCEPTION: TypeError: animation is not a function
browser_adapter.js:84EXCEPTION: TypeError: animation is not a functionBrowserDomAdapter.logError # browser_adapter.js:84
browser_adapter.js:84STACKTRACE:BrowserDomAdapter.logError # browser_adapter.js:84
browser_adapter.js:84TypeError: animation is not a function
at render (three.service.js:49)
at ZoneDelegate.invokeTask (zone.js:356)
at Object.onInvokeTask (ng_zone_impl.js:44)
at ZoneDelegate.invokeTask (zone.js:355)
at Zone.runTask (zone.js:256)
at ZoneTask.invoke (zone.js:423)
BrowserDomAdapter.logError # browser_adapter.js:84
Subscriber.js:229
Uncaught TypeError: animation is not a function
I guess this.animation(cube) does not return a function which you expect to call inside of render (by animation();). Possible workaround could be:
render(() => {
this.animation(cube);
});
#Engineer provides a solution, but doesn't explain that the reason your shared example does not work as you expect is that you are calling this.animation in place, not passing reference to the function or a function that wraps it (as in #Engineer's answer).
I have a post processing bloom shader running and a VR (virtual reality) conditional stereo effect. They don't seem to want to work together. I am likely just implementing this wrong. Here is the code but where is the mistake?
In my Init()
effectPass = new THREE.ShaderPass(THREE.CopyShader);
bloomPass = new THREE.BloomPass(2);
renderPass = new THREE.RenderPass( scene, camera );
composer = new THREE.EffectComposer(renderer);
composer.addPass(renderPass);
composer.addPass(bloomPass);
composer.addPass(effectPass);
effectPass.renderToScreen = true;
renderer.autoClear = false;
if (vr) {
stereo = new THREE.StereoEffect(renderer);
stereo.setSize( window.innerWidth, window.innerHeight );
}
In my render()
renderer.clear();
composer.render();
renderFrame = requestAnimationFrame(render);
if (vr) { stereo.render(scene, camera); }
else { renderer.render(scene, camera); }