With a couple of research and examples online I'm able to tween the camera to and fro different position. However, there seems to have a problem when animating to the top view. where the lookat is not able to maintain in the middle and it kind of moves "up" then "down"
how am I able to navigate to the destination smoothly while maintaining center.
https://codepen.io/edwinlimlx/full/QzWBrY
var camera;
var controls;
var scene;
var torus;
var light;
var renderer;
var w = window.innerWidth;
var h = window.innerHeight;
var is3D = true;
var frustumSize = 200;
init();
animate();
function init() {
//WebGL Renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
//renderer.setClearColor(0xffffff, 1)
renderer.setSize(w, h);
$(".webgl").append(renderer.domElement);
//camera
var aspect = w / h;
camera = new THREE.OrthographicCamera(
frustumSize * aspect / -2,
frustumSize * aspect / 2,
frustumSize / 2,
frustumSize / -2,
1,
1000
);
camera.aspect = aspect;
camera.position.set(frustumSize, frustumSize, frustumSize);
//controls
controls = new THREE.MapControls(camera, renderer.domElement);
controls.minZoom = 1;
controls.maxZoom = 6;
//Scene
scene = new THREE.Scene();
// Helpers
axes = new THREE.AxisHelper(50);
helper = new THREE.GridHelper(200, 10);
scene.add(axes);
scene.add(helper);
// Torus Geometry
torus = new THREE.Mesh(
new THREE.TorusGeometry(60, 30, 20, 20),
new THREE.MeshNormalMaterial()
);
torus.position.set(0, 0, 0);
scene.add(torus);
//Hemisphere Light
// light = new THREE.HemisphereLight(0xffbf67, 0x15c6ff);
// scene.add(light);
$(".click").click(function() {
is3D = !is3D;
var endPos = is3D
? {
x: 200,
y: 200,
z: 200
}
: {
x: 0,
y: 500,
z: 0
};
var lookAt = new THREE.Vector3();
// save original postion and rotation
var startPos = new THREE.Vector3().copy(camera.position);
var startQua = new THREE.Quaternion().copy(camera.quaternion);
// extract the end quaternion
camera.position.set(endPos.x, endPos.y, endPos.z);
camera.lookAt(lookAt);
var endQua = new THREE.Quaternion().copy(camera.quaternion);
// reset original position and rotation
camera.position.set(startPos.x, startPos.y, startPos.z);
camera.quaternion.copy(startQua);
// var tween = new TWEEN.Tween(from)
// .to(to, 600)
// .easing(TWEEN.Easing.Linear.None)
// .onUpdate(function () {
// camera.position.set(this.x, this.y, this.z);
// camera.lookAt(new THREE.Vector3(0, 0, 0));
// })
// .onComplete(function () {
// camera.lookAt(new THREE.Vector3(0, 0, 0));
// })
// .start();
var o = { t: 0 };
new TWEEN.Tween(o)
.to(
{
t: 1
},
1000
)
.onUpdate(function() {
console.log(startPos, endPos, o.t);
var currentPos = new THREE.Vector3().copy(startPos);
currentPos.lerp(endPos, o.t);
camera.position.copy(currentPos);
var currentQua = new THREE.Quaternion().copy(startQua);
currentQua.slerp(endQua, o.t);
camera.quaternion.copy(currentQua);
})
.onComplete(function() {
controls.target = lookAt;
controls.update();
})
.easing(TWEEN.Easing.Linear.None)
.start();
});
}
function animate() {
requestAnimationFrame(animate);
controls.update();
TWEEN.update();
renderer.render(scene, camera);
}
Related
I have a piece of code from 3 years ago that appears to not be working anymore from Three.js.
I have updated all of the other issues of the migration to ES6 for Three.js, but when I try to add as I did in the past more than 1 place marker on the planet surface, only 1 place marker is displayed, with only the last added place marker being successfully displayed in the desired position... with all other place markers being erased.
Problem example -> https://jsfiddle.net/jhL6s579/6/
Complete code:
HTML
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://s3-eu-west-2.amazonaws.com/bckld/lab/loading.js"></script>
JS
// ------ Marker object ------------------------------------------------
function Marker() {
var radius = 0.005;
var sphereRadius = 0.02;
var height = 0.05;
var material = new THREE.MeshPhongMaterial({ color: 0xbab68f });
var cone = new THREE.Mesh(new THREE.ConeBufferGeometry(radius, height, 8, 1, true), material);
cone.position.y = height * 0.5;
cone.rotation.x = Math.PI;
var sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(sphereRadius, 16, 8), material);
sphere.position.y = height * 0.95 + sphereRadius;
this.add(cone, sphere);
}
Marker.prototype = Object.create(new THREE.Object3D());
// ------ Earth object -------------------------------------------------
function Earth(radius, texture) {
this.userData.radius = radius;
var earth = new THREE.Mesh(
new THREE.SphereBufferGeometry(radius, 64.0, 48.0),
new THREE.MeshPhongMaterial({
map: texture
})
);
this.add(earth);
}
Earth.prototype = Object.create(new THREE.Object3D());
Earth.prototype.createMarker = function (lat,lon ) {
var marker = new Marker();
var lonRad = -lon * (Math.PI / 180);
var latRad = lat * (Math.PI / 180);
var r = this.userData.radius;
marker.position.set(Math.cos(latRad) * Math.cos(lonRad) * r, Math.sin(latRad) * r, Math.cos(latRad) * Math.sin(lonRad) * r);
marker.rotation.set(0.0, -lonRad, latRad - Math.PI * 0.5);
this.attach(marker);
};
// ------ Three.js code ------------------------------------------------
var scene, camera, renderer;
var controls;
init();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, 4 / 3, 0.1, 100);
camera.position.set(0.0, 1.5, 3.0);
renderer = new THREE.WebGLRenderer({ antialias: true });
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;
controls.autoRotateSpeed = -1.0;
controls.enablePan = false;
var ambient = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambient);
var direcitonal = new THREE.DirectionalLight(0xffffff, 0.5);
direcitonal.position.set(5.0, 2.0, 5.0).normalize();
scene.add(direcitonal);
// just some code for the loading
var manager = createLoader(renderer.domElement, animate);
var texLoader = new THREE.TextureLoader(manager).setCrossOrigin(true);
var texture = texLoader.load('https://s3-eu-west-2.amazonaws.com/bckld/lab/textures/earth_latlon.jpg');
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
var earth = new Earth(1.0, texture);
earth.createMarker(48.856700, 2.350800); // Paris
earth.createMarker(28.524167, -80.650833); // Paris
scene.add(earth);
window.addEventListener('resize', onResize);
onResize();
document.body.appendChild(renderer.domElement);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
CSS
body {
background: #000;
margin: 0;
overflow: hidden;
}
I have also tried doing this.attach(marker) but still not working...
You have to derive your custom classes Marker and Earth in ES6 now. Meaning you have to use the class syntax like below since Object3D is now a class, too.
// ------ Marker object ------------------------------------------------
class Marker extends THREE.Object3D {
constructor() {
super();
var radius = 0.005;
var sphereRadius = 0.02;
var height = 0.05;
var material = new THREE.MeshPhongMaterial({
color: 0xbab68f
});
var cone = new THREE.Mesh(new THREE.ConeBufferGeometry(radius, height, 8, 1, true), material);
cone.position.y = height * 0.5;
cone.rotation.x = Math.PI;
var sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(sphereRadius, 16, 8), material);
sphere.position.y = height * 0.95 + sphereRadius;
this.add(cone, sphere);
}
}
// ------ Earth object -------------------------------------------------
class Earth extends THREE.Object3D {
constructor(radius, texture) {
super();
this.userData.radius = radius;
var earth = new THREE.Mesh(
new THREE.SphereBufferGeometry(radius, 64.0, 48.0),
new THREE.MeshPhongMaterial({
map: texture
})
);
this.add(earth);
}
createMarker(lat, lon) {
var marker = new Marker();
var lonRad = -lon * (Math.PI / 180);
var latRad = lat * (Math.PI / 180);
var r = this.userData.radius;
marker.position.set(Math.cos(latRad) * Math.cos(lonRad) * r, Math.sin(latRad) * r, Math.cos(latRad) * Math.sin(lonRad) * r);
marker.rotation.set(0.0, -lonRad, latRad - Math.PI * 0.5);
this.attach(marker);
}
}
// ------ Three.js code ------------------------------------------------
var scene, camera, renderer;
var controls;
init();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, 4 / 3, 0.1, 100);
camera.position.set(0.0, 1.5, 3.0);
renderer = new THREE.WebGLRenderer({
antialias: true
});
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;
controls.autoRotateSpeed = -1.0;
controls.enablePan = false;
var ambient = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambient);
var direcitonal = new THREE.DirectionalLight(0xffffff, 0.5);
direcitonal.position.set(5.0, 2.0, 5.0).normalize();
scene.add(direcitonal);
// just some code for the loading
var manager = createLoader(renderer.domElement, animate);
var texLoader = new THREE.TextureLoader(manager).setCrossOrigin(true);
var texture = texLoader.load('https://s3-eu-west-2.amazonaws.com/bckld/lab/textures/earth_latlon.jpg');
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
var earth = new Earth(1.0, texture);
earth.createMarker(48.856700, 2.350800); // Paris
earth.createMarker(28.524167, -80.650833); // Paris
scene.add(earth);
window.addEventListener('resize', onResize);
onResize();
document.body.appendChild(renderer.domElement);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.137.5/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.137.5/examples/js/controls/OrbitControls.js"></script>
<script src="https://s3-eu-west-2.amazonaws.com/bckld/lab/loading.js"></script>
I want to make a smoke effect like this demo (Smoke) by using points. Now I have two problems.
At the beginning of the animation, all points get together and go up together like cloud.
the shape of the effect looks like a rectangular prism. How to make it looks like a cone (like the following picture)?
Can somebody please tell me how to fix these problems? Thanks!
let renderer, scene, camera;
let controls, stats;
// points
const particleCount = 500;
let points;
function createPoints() {
const geometry = new THREE.Geometry();
const texture = new THREE.TextureLoader().load('http://stemkoski.github.io/Three.js/images/smokeparticle.png');
let material = new THREE.PointsMaterial({
size: 15,
map: texture,
blending: THREE.AdditiveBlending,
depthWrite: false,
transparent: true,
color: 'rgb(30,30,30)'
});
const range = 10;
for (let i = 0; i < particleCount; i++) {
const x = THREE.Math.randInt(-range, range);
const y = THREE.Math.randInt(-range, range);
const z = THREE.Math.randInt(-range, range);
const point = new THREE.Vector3(x, y, z);
point.velocityX = THREE.Math.randFloat(-0.01, 0.01);
point.velocityY = THREE.Math.randFloat(0.1, 0.3);
geometry.vertices.push(point);
}
points = new THREE.Points(geometry, material);
scene.add(points);
}
function init() {
// scene
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.0008);
// camera
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set(0, 10, 170);
camera.lookAt(scene.position);
let axes = new THREE.AxesHelper(20);
scene.add(axes);
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// OrbitControls
controls = new THREE.OrbitControls( camera, renderer.domElement );
createPoints();
document.body.appendChild(renderer.domElement);
}
function pointsAnimation() {
points.geometry.vertices.forEach(function(v) {
v.y = v.y + v.velocityY;
v.x = v.x + v.velocityX;
if (v.y >= 100) v.y = 0;
});
points.geometry.verticesNeedUpdate = true;
}
function render() {
pointsAnimation();
requestAnimationFrame(render);
controls.update();
renderer.render(scene, camera);
}
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
init();
render();
body {
margin: 0;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/104/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
You could simply set velocityX depending on point x, e.g. :
point.velocityX = THREE.Math.randFloat(0.0, 0.1) * Math.sign(x);
Don't forget to reset x position :
if (v.y >= 100) {
v.x = THREE.Math.randInt(-10, 10);
v.y = 0;
}
Here I'm trying to disable the trackball control rotate event and to call my custom rotate event. When it is tried in OrbitControl (controls.enableRotate = false;) is doing the job but when comes to trackball control it executes both the trackball control and custom event rotate functionalities. Here's the fiddle
https://jsfiddle.net/azso936f/1/
In the fiddle the rotate operation works on mousemove and when we mousedown the rotate functionality of trackballcontrol works too
var scene, renderer, camera;
var cube;
var controls;
var containerWidth = window.innerWidth,
containerHeight = window.innerHeight;
var isDragging = false;
var previousMousePosition = {
x: 0,
y: 0
};
init();
animate();
function init() {
configureRenderer();
scene = new THREE.Scene();
configureCube();
configureCamera();
configureLight();
configureControls();
}
function configureRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(containerWidth, containerHeight);
document.body.appendChild(renderer.domElement);
window.onresize = function () {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
if (controls)
controls.handleResize();
}
renderer.domElement.addEventListener("wheel", function (e) {
const delta = event.deltaY;
let x = 2 * event.clientX / window.innerWidth - 1;
let y = 1 - 2 * event.clientY / window.innerHeight;
let cursorpos = new THREE.Vector3(x, y, 1);
cursorpos.unproject(camera);
let dir = new THREE.Vector3().copy(cursorpos).sub(camera.position).normalize();
let shift = new THREE.Vector3().copy(dir).multiplyScalar(delta * 0.1);
camera.position.add(shift);
controls.position0.add(shift);
controls.target.add(shift);
});
renderer.domElement.addEventListener('mousemove', event => {
isDragging = true;
});
renderer.domElement.addEventListener('mousemove', function (e) {
isDragging = true;
//console.log(e);
var deltaMove = {
x: e.offsetX - previousMousePosition.x,
y: e.offsetY - previousMousePosition.y
};
if (isDragging) {
var deltaRotationQuaternion = new THREE.Quaternion()
.setFromEuler(new THREE.Euler(
toRadians(deltaMove.y * 1),
toRadians(deltaMove.x * 1),
0,
'XYZ'
));
cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion);
}
previousMousePosition = {
x: e.offsetX,
y: e.offsetY
};
});
function toRadians(angle) {
return angle * (Math.PI / 180);
}
renderer.domElement.addEventListener('mouseup', event => {
isDragging = false;
})
}
function configureCube() {
var cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(50, 0, 0);
scene.add(cube);
}
function configureCamera() {
camera = new THREE.PerspectiveCamera(45, containerWidth / containerHeight, 1, 1000);
camera.position.set(0, 160, 400);
camera.lookAt(scene);
}
function configureLight() {
pointLight = new THREE.PointLight(0xffffff, 1.0, 100000);
pointLight.position.set(0, 300, 200);
scene.add(pointLight);
}
function configureControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
// configuration of controls
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 0.0;
controls.panSpeed = 2.0;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0;
}
function animate() {
controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
In the fiddle the rotate operation works on mousemove and when we mousedown the rotate functionality of trackballcontrol works too
It is sufficient to set the rotation speed (.rotateSpeed) of THREE.TrackballControls to 0:
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 0.0;
The rotation speed can be changed by scaling the THREE.Euler angles:
renderer.domElement.addEventListener('mousemove', function (e) {
const that = this;
var rotateSpeed = 0.1;
var deltaMove = {
x: e.offsetX - previousMousePosition.x,
y: e.offsetY - previousMousePosition.y
};
if (isDragging) {
var deltaRotationQuaternion = new THREE.Quaternion()
.setFromEuler(new THREE.Euler(
toRadians(deltaMove.y * rotateSpeed),
toRadians(deltaMove.x * rotateSpeed),
0,
'XYZ'
));
cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion);
}
previousMousePosition = {
x: e.offsetX,
y: e.offsetY
};
});
Here I'm trying to zoom in the object(say cube) based on the on mouse pointer position. Here I googled and got a idea that it can be achieved in D3 JS and also it can achieved by capturing the mouse pointer 3D point and passing it in the mouse wheel function.
Here's the fiddle https://jsfiddle.net/fpt9hswo/
var scene, renderer, camera;
var cube;
var controls;
var containerWidth = window.innerWidth,
containerHeight = window.innerHeight;
init();
animate();
function init() {
configureRenderer();
scene = new THREE.Scene();
configureCube();
configureCamera();
configureLight();
configureControls();
fitAll();
}
function configureRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(containerWidth, containerHeight);
document.body.appendChild(renderer.domElement);
}
function configureCube() {
var cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(50, 0, 0);
scene.add(cube);
}
function configureCamera() {
camera = new THREE.PerspectiveCamera(45, containerWidth / containerHeight, 1, 1000);
camera.position.set(0, 160, 400);
camera.lookAt(scene);
}
function configureLight() {
pointLight = new THREE.PointLight(0xffffff, 1.0, 100000);
pointLight.position.set(0, 300, 200);
scene.add(pointLight);
}
function configureControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
// configuration of controls
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5.0;
controls.panSpeed = 2.0;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0;
}
function fitAll() {
// Calculate bounding box of the whole scene
var boundingBoxOfNode = new THREE.Box3().setFromObject(scene),
centerOfGravity = boundingBoxOfNode.getCenter();
/************* CAMERA *************************/
camera.position.addVectors(camera.position, centerOfGravity);
camera.lookAt(centerOfGravity);
//new camera positions will be set here
//Eg: camera.position.set(newCamera.x,newCamera.y,newCamera.z);
//Similarly new camera rotation and quaternion coordinates will be set
//Eg: camera.rotation.set(newCamera.rotatex,newCamera.rotatey,newCamera.rotatez);
//Eg: camera.quaternion.set(newCamera.qw,newCamera.qx,newCamera.qy,newCamera.qz);
/************* CONTROLS *************************/
controls.target.set(centerOfGravity.x, centerOfGravity.y, centerOfGravity.z);
//new controls.target values will be set here
//Eg: controls.target.set(newCamera.targetx,newCamera.targety,newCamera.targetz);
}
function animate() {
controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Thanks in advance
To do what you want you've to implement your own mouse wheel event and you've to disable the default zoom of THREE.TrackballControls:
controls = new THREE.TrackballControls(camera, renderer.domElement);
//...
controls.zoomSpeed = 0.0;
Add a wheel event:
e.g.
renderer.domElement.addEventListener("wheel", event => {
const delta = event.deltaY;
// [...]
}
Zooming in perspective projection can be achieved, by shifting the camera position in the depth of the world.
If you want to zoom to a point, this can be achieved by moving the camera along a ray, from the camera position through the cursor (mouse) position and to move the target position in parallel.
A point on the ray from the camera to the cursor in normalized device space can be found with ease. In normalized device space all coordinates are in range [-1, 1] and all points with the same x and y coordinate are at the same ray. If the z coordinate is -1, the the point is on the near plane and if z is 1 then the point is on the far plane.
e.g. NDC point on the far plane and on a ray through the cursor:
let x = 2 * event.clientX / window.innerWidth - 1;
let y = 1 - 2 * event.clientY / window.innerHeight;
let cursorpos = new THREE.Vector3(x, y, 1);
This point in normalized device space can be transformed to a point in world space, by THREE.Vector3.unproject(). The parameter to the function has to be the THREE.Camera which defines the view and projection:
cursorpos.unproject(camera);
The direction for the movement of the camera is the normalized direction from the camera position to the cursor position in world space:
let dir = new THREE.Vector3().copy(cursorpos).sub(camera.position).normalize();
Calculate the movement dependent on the direction and the mouse wheel delta and update the camera and THREE.TrackballControls:
let shift = new THREE.Vector3().copy(dir).multiplyScalar(delta * 0.1);
camera.position.add(shift);
controls.position0.add(shift);
controls.target.add(shift);
See the example, where I applied the suggestions to the code of the question:
var scene, renderer, camera;
var cube;
var controls;
var containerWidth = window.innerWidth,
containerHeight = window.innerHeight;
init();
animate();
function init() {
configureRenderer();
scene = new THREE.Scene();
configureCube();
configureCamera();
configureLight();
configureControls();
}
function configureRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(containerWidth, containerHeight);
document.body.appendChild(renderer.domElement);
window.onresize = function() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
if (controls)
controls.handleResize();
}
renderer.domElement.addEventListener("wheel", event => {
const delta = event.deltaY;
let x = 2 * event.clientX / window.innerWidth - 1;
let y = 1 - 2 * event.clientY / window.innerHeight;
let cursorpos = new THREE.Vector3(x, y, 1);
cursorpos.unproject(camera);
let dir = new THREE.Vector3().copy(cursorpos).sub(camera.position).normalize();
let shift = new THREE.Vector3().copy(dir).multiplyScalar(delta * 0.1);
camera.position.add(shift);
controls.position0.add(shift);
controls.target.add(shift);
});
}
function configureCube() {
var cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
var cubeMaterial = new THREE.MeshLambertMaterial({
color: 0xff0000
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(50, 0, 0);
scene.add(cube);
}
function configureCamera() {
camera = new THREE.PerspectiveCamera(45, containerWidth / containerHeight, 1, 1000);
camera.position.set(0, 160, 400);
camera.lookAt(scene);
}
function configureLight() {
pointLight = new THREE.PointLight(0xffffff, 1.0, 100000);
pointLight.position.set(0, 300, 200);
scene.add(pointLight);
}
function configureControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
// configuration of controls
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 0.0;
controls.panSpeed = 2.0;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0;
}
function animate() {
controls.update();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
How can I preserve constant size of meshes regardless of perspective using Three.js?
Lets assume I have multiple meshes of the same size. I want them to always have the same on screen size. I also have to use perspective camera.
I have found some related answers, but seems that I miss something:
First approach is to use Euclidean distance to objects from
camera and scale them respectively every animation frame.
Three JS Keep Label Size On Zoom
I have tried to modify jsfiddle given in the answer to apply scale to meshes instead of sprites, but that doesn't work for me. The result looks as follows https://jsfiddle.net/a2ogz9vx/258/
var camera, scene, renderer, controls;
var planets = [];
var timestamp = 0;
var scaleVector = new THREE.Vector3();
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 100, 100);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
var createPlanet = function(name, radius, orbit, speed) {
var geom = new THREE.SphereGeometry(radius, 32, 16);
var mat = new THREE.MeshBasicMaterial({
color: Math.random() * 0xFFFFFF,
});
var planet = new THREE.Mesh(geom, mat);
planet.userData.orbit = orbit;
planet.userData.speed = speed;
var canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
var tex = new THREE.Texture(canvas);
tex.needsUpdate = true;
var spriteMat = new THREE.SpriteMaterial({
map: tex
});
var sprite = new THREE.Sprite(spriteMat);
planet.add(sprite);
planets.push(planet);
scene.add(planet);
};
createPlanet("One", 11, -10, 5);
createPlanet("Two", 11, 5, 5);
createPlanet("Three", 11, 10, 5);
}
function animate() {
requestAnimationFrame(animate);
planets.forEach(function(planet) {
var scaleFactor = 100;
var scale = scaleVector.subVectors(planet.position, camera.position).length() / scaleFactor;
planet.scale.set(scale, scale, scale);
var orbit = planet.userData.orbit;
var speed = planet.userData.speed;
planet.position.x = speed * orbit;
planet.position.z = speed * orbit;
});
render();
}
function render() {
renderer.render(scene, camera);
}
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
Second approach is to use on screen width calculation. https://stackoverflow.com/a/13351534/8465699
Result also doesn't look right https://jsfiddle.net/a2ogz9vx/260/
var camera, scene, renderer, controls;
var planets = [];
var timestamp = 0;
var scaleVector = new THREE.Vector3();
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 100, 100);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
var createPlanet = function(name, radius, orbit, speed) {
var geom = new THREE.SphereGeometry(radius, 32, 16);
var mat = new THREE.MeshBasicMaterial({
color: Math.random() * 0xFFFFFF,
});
var planet = new THREE.Mesh(geom, mat);
planet.userData.orbit = orbit;
planet.userData.speed = speed;
var canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
var tex = new THREE.Texture(canvas);
tex.needsUpdate = true;
var spriteMat = new THREE.SpriteMaterial({
map: tex
});
var sprite = new THREE.Sprite(spriteMat);
planet.add(sprite);
planets.push(planet);
scene.add(planet);
};
createPlanet("One", 11, -10, 5);
createPlanet("Two", 11, 0, 5);
createPlanet("Three", 11, 10, 5);
}
function animate() {
requestAnimationFrame(animate);
planets.forEach(function(planet) {
const dist = planet.position.distanceTo(camera.position);
const vFOV = THREE.Math.degToRad(camera.fov);
const size = 2 * Math.tan(vFOV / 2) * dist;
const scaleFactor = 130;
const scale = size / scaleFactor;
planet.scale.set(scale, scale, scale);
var orbit = planet.userData.orbit;
var speed = planet.userData.speed;
planet.position.x = speed * orbit;
planet.position.z = speed * orbit;
});
render();
}
function render() {
renderer.render(scene, camera);
}
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
Any suggestions how to fix the code or other approaches are welcome!