Three.JS Rotate Camera around object by moving the device - javascript

I’ve asked in another forum but I thought I would like to be more clear on my problem.
Whats my intention?
Currently I am using three.js within WebView on my android device and created a scene which contains a simple box (which should be used as a bounding box) and a camera. My camera needs to be an interactive one with my android device, which means that I set the position by moving the device dynamically. These vectors are coming from a SLAM-algorithmn named Direct Sparse Odometry which recreates the camera position, I can also call these values with javascript by using the provided WebViewInterface from Android. My goal is to “walk around” the box dynamically without using the camera.lookAt()-Method every time I change the values, because if I move away from the box, the view should not be centered anymore (like an AR-Application), so the point of view should be created dynamically such as the position and rotation of the camera towards the object. My goal is to place an object over a real world object with three.js to scan it later with DSO by walking around the box to detect feature points. The whole visualisation should be created with three.js.
What is DSO?
DSO is a library to track the real environment by detecting points from a camera frames, which are provided by Android’s camera 2 API. This send me a 4x4 transformation Matrix with the current pose, which I try to apply on three.js’s camera position. Due to the complexity of this algorithm, lets pretend this gives me proper values (in meters, but I also tried to multiplicate the values by 10 or 100 to receive larger results than 0.XX).
Whats my Problem?
The box does not seem to have an absolute position, even if the values seems to be fixed. Every time when placing the Box, it seem to move in an opposite direction. After many adjustments on the dso values, I am crystal clear that this the problem is happening with three.js.
I’ve also tried to apply matrixes of the scene/camera and/or using the box as a child (because of the object-heredity), but the box seems not to have an absolute position inside the scene. Also I am not able to rotate the object that seem to be realistic.
Enclosed, you’ll find my code but p
lease note that I am using dynamically dummy values as a replacement for the dso values.
<body>
<canvas id="mCanvas">
</canvas>
</body>
<script>
// Var Init
var renderer, scene, camera, box, transformControl, orbitControl, geometry, material, poseMatrix;
var mPoints = [];
//Box coordinate
var xBCordinate, yBCordinate, zBCordinate, isScaled, posVec, startPosVec, lookPos, helper;
var process = false;
var scanActive = false;
var pointArr = [];
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({canvas: document.getElementById("mCanvas"),
alpha: true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.setClearColor(0xffffff, 0);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.up.set(0, 0, 1); // Definition of coordinationsystem
// set initial scale position of camera
camera.position.x = 0;
camera.position.y = -0.5;
camera.position.z = 0.15;
scene.add(camera);
// set position to look at
camera.lookAt(0,2.5,-0.2);
// apply values
camera.updateMatrix();
// light
var light = new THREE.HemisphereLight( 0xeeeeee, 0x888888, 1 );
light.position.set( 0, -0.75, 2.5 );
scene.add(light);
placeBox();
}
function placeBox()
{
geometry = new THREE.BoxGeometry(0.5, 1, 0.5); //3,5,3
material = new THREE.MeshLambertMaterial({color: 0xfece46});
box = new THREE.Mesh(geometry, material);
box.position.set(0, 2.5, -0.2);
box.updateMatrix();
scene.add(box);
}
function animate() {
requestAnimationFrame(animate);
if(process == false){
setCurrentPose();
}
renderer.render(scene, camera);
}
function setCurrentPose(){
process = true;
// this is where I receive the position data via Android
// but lets try using random numbers between 0.01 - 0.99 (which are the results interval of dso)
moveRotateCamera();
}
function moveRotateCamera(){
// Create Vector to work with
posVec = new THREE.Vector3();
posVec.x = getRandomFloat(0.01, 0.99);
posVec.y = pgetRandomFloat(0.01, 0.99);
posVec.z = getRandomFloat(0.01, 0.99);
camera.position.x = posVec.x;
camera.position.y = (posVec.y) - 0.50; // minus initial scale position
camera.position.z = (posVec.z) + 0.15;
// camera.updateMatrix(); <- seem to change nothing such as UpdateWorldMatrix() etc.
// camera rotation tried to calculate with quaternions (result NaN) and/or euler by using former and current point.
process = false;
}
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
// My attempts in trying to calculate the rotation
/*
function setQuaternionRotation(poseMatrix){
// TODO: delete if not needed!
// adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm, 2.12.2019, 2.34pm
mQuaternion = new THREE.Quaternion();
// Calculate Angle w
mQuaternion.w = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] + poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign x,y,z values of quaternion
mQuaternion.x = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] - poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] + poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] - poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign element values
mQuaternion.x = (Math.sign(mQuaternion.x * (poseMatrix.elements[6] - poseMatrix.elements[9])));
mQuaternion.y = (Math.sign(mQuaternion.y * (poseMatrix.elements[8] - poseMatrix.elements[2])));
mQuaternion.z = (Math.sign(mQuaternion.z * (poseMatrix.elements[1] - poseMatrix.elements[4])));
// debug
console.log("QuaternionVal: "+mQuaternion.x+ ", " +mQuaternion.y+", "+mQuaternion.z+", "+mQuaternion.w);
camera.applyQuaternion(mQuaternion);
camera.quaternion.normalize();
// debug
console.log("newCamRotation: "+camera.rotation.x +", "+camera.rotation.y+", "+ camera.rotation.z);
// camera.updateMatrix(true);
}
*/
</script>
Link to my Fiddle
Do you have any suggestions?
Thank you very much in advance!
Best regards,

FWIW. I think part of the issue is that the box is not centered about the camera rotation. I tweaked your fiddle by centering the box at the origin, in addition to using spherical coordinates to move the camera about. This keeps the camera at a uniform distance from the box, and with the box being centered about the rotation, it does not appear to be moving about the viewport...
<body>
<canvas id="mCanvas">
</canvas>
</body>
<script src="https://threejs.org/build/three.js"></script>
<script>
// Var Init
var renderer, scene, camera, box, transformControl, orbitControl, geometry, material, poseMatrix;
var mPoints = [];
//Box coordinate
var xBCordinate, yBCordinate, zBCordinate, isScaled, posVec, startPosVec, lookPos, helper;
var process = false;
var scanActive = false;
var pointArr = [];
var cameraSpherical;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({canvas: document.getElementById("mCanvas"),
alpha: true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.setClearColor(0xffffff, 0);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.up.set(0, 0, 1); // Definition of coordinationsystem
// set initial scale position of camera
camera.position.x = 0;
camera.position.y = -0.5;
camera.position.z = 0.15;
scene.add(camera);
cameraSpherical = new THREE.Spherical( camera.position );
// set position to look at
camera.lookAt(0,2.5,-0.2);
// apply values
camera.updateMatrix();
// light
var light = new THREE.HemisphereLight( 0xeeeeee, 0x888888, 1 );
light.position.set( 0, -0.75, 2.5 );
scene.add(light);
placeBox();
}
function placeBox()
{
geometry = new THREE.BoxGeometry(0.5, 1, 0.5); //3,5,3
material = new THREE.MeshLambertMaterial({color: 0xfece46});
box = new THREE.Mesh(geometry, material);
box.position.set(0, 0, 0);
box.updateMatrix();
scene.add(box);
}
function animate() {
requestAnimationFrame(animate);
if(process == false){
setCurrentPose();
}
renderer.render(scene, camera);
}
function setCurrentPose(){
process = true;
// this is where I receive the position data via Android
// but lets try using random numbers between 0.01 - 0.99 (which are the results interval of dso)
moveRotateCamera();
}
function moveRotateCamera(){
// Create Vector to work with
/* posVec = new THREE.Vector3();
posVec.x = getRandomFloat(0.01, 0.05);
posVec.y = getRandomFloat(0.01, 0.05);
posVec.z = getRandomFloat(0.01, 0.02);
camera.position.x += posVec.x;
camera.position.y += posVec.y; // minus initial scale position
camera.position.z += posVec.z;
*/
cameraSpherical.radius = 5;
cameraSpherical.phi += getRandomFloat(0.001, 0.015);
cameraSpherical.theta += getRandomFloat(0.001, 0.015);
let xyz = new THREE.Vector3().setFromSpherical( cameraSpherical );
camera.position.x = xyz.x;
camera.position.y = xyz.y;
camera.position.z = xyz.z;
camera.lookAt(0,0,0);
camera.updateMatrix();
// camera.updateMatrix(); <- seem to change nothing such as UpdateWorldMatrix() etc.
// camera rotation tried to calculate with quaternions (result NaN) and/or euler by using former and current point.
process = false;
}
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
// My attempts in trying to calculate the rotation
/*
function setQuaternionRotation(poseMatrix){
// TODO: delete if not needed!
// adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm, 2.12.2019, 2.34pm
mQuaternion = new THREE.Quaternion();
// Calculate Angle w
mQuaternion.w = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] + poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign x,y,z values of quaternion
mQuaternion.x = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] - poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] + poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] - poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign element values
mQuaternion.x = (Math.sign(mQuaternion.x * (poseMatrix.elements[6] - poseMatrix.elements[9])));
mQuaternion.y = (Math.sign(mQuaternion.y * (poseMatrix.elements[8] - poseMatrix.elements[2])));
mQuaternion.z = (Math.sign(mQuaternion.z * (poseMatrix.elements[1] - poseMatrix.elements[4])));
// debug
console.log("QuaternionVal: "+mQuaternion.x+ ", " +mQuaternion.y+", "+mQuaternion.z+", "+mQuaternion.w);
camera.applyQuaternion(mQuaternion);
camera.quaternion.normalize();
// debug
console.log("newCamRotation: "+camera.rotation.x +", "+camera.rotation.y+", "+ camera.rotation.z);
// camera.updateMatrix(true);
}
*/
</script>
Not sure whether this helps you in the direction you're going, but hope it sheds some light.

Related

Three.js - Extrude certain vertex/face from BufferGeometry

I made a new THREE.PlaneBufferGeometry(100, 100, 100, 100); and have been able to update position of vertices to change the mesh's shape like following:
I achieved this by following this discussion: Threejs drag points
What am I looking for?
I want to be able to extrude a face (grab 4 vertices), so I achieve something like this:
I want to keep it all part of the same mesh, to keep it clean, because I will be exporting it as a single mesh with the ColladaExporter.
Edit
In order to achieve this, I would need to clone vertex and extrude them upwards. This means, adding 4 new vertex and connecting them together.
I tried this:
var geo = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
geo.rotateX(-Math.PI * 0.5);
geo.translate(0,0.5,0);
//And the merge them together
var newplane = BufferGeometryUtils.mergeBufferGeometries([plane, geo]);
newplane = BufferGeometryUtils.mergeVertices(newplane,1);
And I got this:
I was hoping all vertices merged with the plane, leaving a flat plane. I did this for testing purposes, but it only merged one corner.
I started building a "cube" with multiple and placing them in the right spot, to then apply again BufferGeometryUtils.mergeVertices, but the vertices don't seem to merge correctly:
Edit 2 / Progress
I managed to create a PlaneBufferGeometry and extrude it by manually modifying the vertexes and normals, as told in: https://threejs.org/docs/#api/en/core/BufferGeometry
Extruded plane has all vertices connected, so whenever I drag one vertex it drags a whole piece, the problem now is that I need to connect these new vertices to the original grid to avoid this:
Goal is to merge all vertices, now I need to find a way to merge the base plane with the new extruded piece.
Edit 3 / Done
I made it, I will post answer when I have some time. I spent all day long on these today, and already very tired.
Not sure if that's what you need, but here's the modified example from the answer you referred to (please notice the difference in mouseMove implementation). I've extended that for two points only, but I believe you should get the idea:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1.25, 7, 7);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.PlaneBufferGeometry(10, 10, 10, 10);
geometry.rotateX(-Math.PI * 0.5);
var plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
wireframe: true,
color: "red"
}));
scene.add(plane);
var points = new THREE.Points(geometry, new THREE.PointsMaterial({
size: 0.25,
color: "yellow"
}));
scene.add(points);
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 0.25;
var mouse = new THREE.Vector2();
var intersects = null;
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var currentIndex = null;
var planePoint = new THREE.Vector3();
var dragging = false;
window.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mousemove", mouseMove, false);
window.addEventListener("mouseup", mouseUp, false);
function mouseDown(event) {
setRaycaster(event);
getIndex();
dragging = true;
}
function mouseMove(event) {
if (dragging && currentIndex !== null) {
setRaycaster(event);
raycaster.ray.intersectPlane(plane, planePoint);
var indicesToMoveUp = [currentIndex-1, currentIndex];
var delta_x = geometry.attributes.position.getX(currentIndex) - planePoint.x;
geometry.attributes.position.setXYZ(currentIndex, planePoint.x, planePoint.y, planePoint.z);
geometry.attributes.position.needsUpdate = true;
var old_x_neighbour = geometry.attributes.position.getX(currentIndex - 1);
geometry.attributes.position.setY(currentIndex-1, planePoint.y);
geometry.attributes.position.setZ(currentIndex-1, planePoint.z);
geometry.attributes.position.setX(currentIndex-1, old_x_neighbour - delta_x);
geometry.attributes.position.needsUpdate = true;
}
}
function mouseUp(event) {
dragging = false;
currentIndex = null;
}
function getIndex() {
intersects = raycaster.intersectObject(points);
if (intersects.length === 0) {
currentIndex = null;
return;
}
currentIndex = intersects[0].index;
setPlane(intersects[0].point);
}
function setPlane(point) {
planeNormal.subVectors(camera.position, point).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, point);
}
function setRaycaster(event) {
getMouse(event);
raycaster.setFromCamera(mouse, camera);
}
function getMouse(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>

Three.js - Rotate camera separately from it's parent

I have a scene in which an Object moves around a CatmullRomCurve3 path, going left when you press the left or down arrow keys and right when you press the right or up arrow keys.
I initially had a problem in which I wanted the Object to face 90 degrees from the path instead of facing along the path. You can find my code for this scene there. This problem was solved with this answer.
Basically, all I had to do was implement the following code to rotate the object:
object.lookAt((p2).sub(p1).applyAxisAngle(axis, -Math.PI * 0.5).add(p1));
Although the object now rotated correctly, I now had the problem that the camera wasn't facing the same direction as the object or moving with the object (I wanted an over the shoulder type perspective of the object) but someone suggested I fix this by just adding the camera as a child of the object, which worked!
The New Problem
I now want the object's default position to be facing 90 degrees away from the path (as it is), but when you press the left or down arrows, I want the object to face left (back along the path as it was originally), and face the other way when the right and up arrows are pressed.
I managed to get this to work by setting up some if statements in the render loop that change the value of -Math.PI * 0.5 to either -Math.PI * 2 or -Math.PI * 0.5 for left and right depending on which key is pressed.
However, this also causes the camera to take that new lookAt value as it is a child of the object, but I want the camera to always stay in the same position (i.e. facing 90 degrees from the path, as the object does when it is still).
So, how can I stop the camera from rotating/moving/changing it's lookAt (I'm not sure which one it is) when the Object changes?
It seems like nothing I do to the camera affects it, it is always locked to the same orientation of the object.
Thanks in advance.
I hope I've understood your question correctly:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 20, -20);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var grid = new THREE.GridHelper(300, 30);
grid.position.setZ(100);
scene.add(grid);
var points = [
new THREE.Vector3(-80, 5, 35),
new THREE.Vector3(-80, 5, 192.5),
new THREE.Vector3(80, 5, 192.5),
new THREE.Vector3(80, 5, 35)
];
var curve = new THREE.CatmullRomCurve3(points);
curve.closed = true;
var curvePoints = curve.getPoints(200);
var geomCurvePath = new THREE.BufferGeometry().setFromPoints(curvePoints);
var matCurvePath = new THREE.LineBasicMaterial({
color: 0xff0000
});
var CurvePath = new THREE.Line(geomCurvePath, matCurvePath);
scene.add(CurvePath);
var group = new THREE.Group();
scene.add(group);
group.add(camera);
var pointerGeom = new THREE.ConeGeometry(4, 20, 4);
pointerGeom.translate(0, 10, 0);
pointerGeom.rotateX(Math.PI * 0.5);
var pointer = new THREE.Mesh(pointerGeom, new THREE.MeshNormalMaterial());
group.add(pointer);
btnLeft.addEventListener("click", function() {
turn(Math.PI * 0.5)
});
btnRight.addEventListener("click", function() {
turn(-Math.PI * 0.5)
});
function turn(angle) {
pointer.rotation.y += angle;
}
var p1 = new THREE.Vector3();
var p2 = new THREE.Vector3();
var lookAt = new THREE.Vector3();
var axis = new THREE.Vector3(0, 1, 0);
var percentage = 0;
render();
function render() {
requestAnimationFrame(render);
percentage += 0.0005;
curve.getPointAt(percentage % 1, p1);
curve.getPointAt((percentage + 0.001) % 1, p2);
lookAt.copy(p2).sub(p1).applyAxisAngle(axis, -Math.PI * 0.5).add(p1); // look at the point 90 deg from the path
group.position.copy(p1);
group.lookAt(lookAt);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
#buttons {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div id="buttons">
<button id="btnLeft">left</button>
<button id="btnRight">right</button>
</div>

Three.js: Strategies to progressively increase resolution of texture

I'm working on a Three.js chart that basically represents a bunch of images in a 2D plane.
Right now the individual images are each 32px by 32px segments of larger 2048px by 2048px image atlas files. I want to increase the size of those individual images when users zoom in to particular regions of the scene. For example, if users start to zoom in on the images in the far right region of the space, I plan to update the 32px by 32px individual images in that region with 64px by 64px images with the same content (to show more detail).
My question is: what's the Three.js way to accomplish this goal?
My flat-footed plan is to load the higher-resolution assets, map them to the proper geometry coordinates, then simply delete the old mesh with 32px subimages and add the new mesh with 64px subimages. I originally thought I could just update the texture/material for an extant geometry, but I've read that one shouldn't use textures larger than 2048px by 2048px, and a geometry with n points won't allow me to continually increase the fidelity of the images in that geometry without surpassing that maximum texture size.
I would be very grateful for any insight Three.js veterans can offer on how they would approach this task!
Full code:
/**
* Globals
**/
// Identify data endpoint
var dataUrl = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/data/';
// Create global stores for image and atlas sizes
var image, atlas;
// Create a store for image position information
var imagePositions = null;
// Create a store for the load progress. Data structure:
// {atlas0: percentLoaded, atlas1: percentLoaded}
var loadProgress = {};
// Create a store for the image atlas materials. Data structure:
// {subImageSize: {atlas0: material, atlas1: material}}
var materials = {32: {}, 64: {}};
// Create a store for meshes
var meshes = [];
/**
* Create Scene
**/
// Create the scene and a camera to view it
var scene = new THREE.Scene();
/**
* Camera
**/
// Specify the portion of the scene visiable at any time (in degrees)
var fieldOfView = 75;
// Specify the camera's aspect ratio
var aspectRatio = window.innerWidth / window.innerHeight;
/*
Specify the near and far clipping planes. Only objects
between those planes will be rendered in the scene
(these values help control the number of items rendered
at any given time)
*/
var nearPlane = 100;
var farPlane = 50000;
// Use the values specified above to create a camera
var camera = new THREE.PerspectiveCamera(
fieldOfView, aspectRatio, nearPlane, farPlane
);
// Finally, set the camera's position
camera.position.z = 12000;
camera.position.y = -2000;
/**
* Lights
**/
// Add a point light with #fff color, .7 intensity, and 0 distance
var light = new THREE.PointLight( 0xffffff, 1, 0 );
// Specify the light's position
light.position.set( 1, 1, 100 );
// Add the light to the scene
scene.add(light)
/**
* Renderer
**/
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({ antialias: true });
// Add support for retina displays
renderer.setPixelRatio( window.devicePixelRatio );
// Specify the size of the canvas
renderer.setSize( window.innerWidth, window.innerHeight );
// Add the canvas to the DOM
document.body.appendChild( renderer.domElement );
/**
* Load External Data
**/
// Load the image position JSON file
var fileLoader = new THREE.FileLoader();
var url = dataUrl + 'image_tsne_projections.json';
fileLoader.load(url, function(data) {
imagePositions = JSON.parse(data);
conditionallyBuildGeometries(32)
})
/**
* Load Atlas Textures
**/
// List of all textures to be loaded, the size of subimages
// in each, and the total count of atlas files for each size
var textureSets = {
32: { size: 32, count: 5 },
64: { size: 64, count: 20 }
}
// Create a texture loader so we can load our image files
var textureLoader = new AjaxTextureLoader();
function loadTextures(size, onProgress) {
setImageAndAtlasSize(size)
for (var i=0; i<textureSets[size].count; i++) {
var url = dataUrl + 'atlas_files/' + size + 'px/atlas-' + i + '.jpg';
if (onProgress) {
textureLoader.load(url,
handleTexture.bind(null, size, i),
onProgress.bind(null, size, i));
} else {
textureLoader.load(url, handleTexture.bind(null, size, i));
}
}
}
function handleProgress(size, idx, xhr) {
loadProgress[idx] = xhr.loaded / xhr.total;
var sum = 0;
Object.keys(loadProgress).forEach(function(k) { sum += loadProgress[k]; })
var progress = sum/textureSets[size].count;
var loader = document.querySelector('#loader');
progress < 1
? loader.innerHTML = parseInt(progress * 100) + '%'
: loader.style.display = 'none';
}
// Create a material from the new texture and call
// the geometry builder if all textures have loaded
function handleTexture(size, idx, texture) {
var material = new THREE.MeshBasicMaterial({ map: texture });
materials[size][idx] = material;
conditionallyBuildGeometries(size, idx)
}
// If the textures and the mapping from image idx to positional information
// are all loaded, create the geometries
function conditionallyBuildGeometries(size, idx) {
if (size === 32) {
var nLoaded = Object.keys(materials[size]).length;
var nRequired = textureSets[size].count;
if (nLoaded === nRequired && imagePositions) {
// Add the low-res textures and load the high-res textures
buildGeometry(size);
loadTextures(64)
}
} else {
// Add the new high-res texture to the scene
updateMesh(size, idx)
}
}
loadTextures(32, handleProgress)
/**
* Build Image Geometry
**/
// Iterate over the textures in the current texture set
// and for each, add a new mesh to the scene
function buildGeometry(size) {
for (var i=0; i<textureSets[size].count; i++) {
// Create one new geometry per set of 1024 images
var geometry = new THREE.Geometry();
geometry.faceVertexUvs[0] = [];
for (var j=0; j<atlas.cols*atlas.rows; j++) {
var coords = getCoords(i, j);
geometry = updateVertices(geometry, coords);
geometry = updateFaces(geometry);
geometry = updateFaceVertexUvs(geometry, j);
if ((j+1)%1024 === 0) {
var idx = (i*textureSets[size].count) + j;
buildMesh(geometry, materials[size][i], idx);
var geometry = new THREE.Geometry();
}
}
}
}
// Get the x, y, z coords for the subimage at index position j
// of atlas in index position i
function getCoords(i, j) {
var idx = (i * atlas.rows * atlas.cols) + j;
var coords = imagePositions[idx];
coords.x *= 2200;
coords.y *= 1200;
coords.z = (-200 + j/10);
return coords;
}
// Add one vertex for each corner of the image, using the
// following order: lower left, lower right, upper right, upper left
function updateVertices(geometry, coords) {
// Retrieve the x, y, z coords for this subimage
geometry.vertices.push(
new THREE.Vector3(
coords.x,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y + image.shownHeight,
coords.z
),
new THREE.Vector3(
coords.x,
coords.y + image.shownHeight,
coords.z
)
);
return geometry;
}
// Create two new faces for a given subimage, then add those
// faces to the geometry
function updateFaces(geometry) {
// Add the first face (the lower-right triangle)
var faceOne = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-3,
geometry.vertices.length-2
)
// Add the second face (the upper-left triangle)
var faceTwo = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-2,
geometry.vertices.length-1
)
// Add those faces to the geometry
geometry.faces.push(faceOne, faceTwo);
return geometry;
}
function updateFaceVertexUvs(geometry, j) {
// Identify the relative width and height of the subimages
// within the image atlas
var relativeW = image.width / atlas.width;
var relativeH = image.height / atlas.height;
// Identify this subimage's offset in the x dimension
// An xOffset of 0 means the subimage starts flush with
// the left-hand edge of the atlas
var xOffset = (j % atlas.cols) * relativeW;
// Identify this subimage's offset in the y dimension
// A yOffset of 0 means the subimage starts flush with
// the bottom edge of the atlas
var yOffset = 1 - (Math.floor(j/atlas.cols) * relativeH) - relativeH;
// Determine the faceVertexUvs index position
var faceIdx = 2 * (j%1024);
// Use the xOffset and yOffset (and the knowledge that
// each row and column contains only 32 images) to specify
// the regions of the current image. Use .set() if the given
// faceVertex is already defined, due to a bug in updateVertexUvs:
// https://github.com/mrdoob/three.js/issues/7179
if (geometry.faceVertexUvs[0][faceIdx]) {
geometry.faceVertexUvs[0][faceIdx][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx][1].set(xOffset + relativeW, yOffset)
geometry.faceVertexUvs[0][faceIdx][2].set(xOffset + relativeW, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH)
]
}
// Map the region of the image described by the lower-left,
// upper-right, and upper-left vertices to `faceTwo`
if (geometry.faceVertexUvs[0][faceIdx+1]) {
geometry.faceVertexUvs[0][faceIdx+1][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx+1][1].set(xOffset + relativeW, yOffset + relativeH)
geometry.faceVertexUvs[0][faceIdx+1][2].set(xOffset, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx+1] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH),
new THREE.Vector2(xOffset, yOffset + relativeH)
]
}
return geometry;
}
function buildMesh(geometry, material, idx) {
// Convert the geometry to a BuferGeometry for additional performance
//var geometry = new THREE.BufferGeometry().fromGeometry(geometry);
// Combine the image geometry and material into a mesh
var mesh = new THREE.Mesh(geometry, material);
// Store this image's index position in the mesh
mesh.userData.idx = idx;
// Set the position of the image mesh in the x,y,z dimensions
mesh.position.set(0,0,0)
// Add the image to the scene
scene.add(mesh);
// Save this mesh
meshes.push(mesh);
return mesh;
}
/**
* Update Geometries with new VertexUvs and materials
**/
function updateMesh(size, idx) {
// Update the appropriate material
meshes[idx].material = materials[size][idx];
meshes[idx].material.needsUpdate = true;
// Update the facevertexuvs
for (var j=0; j<atlas.cols*atlas.rows; j++) {
meshes[idx].geometry = updateFaceVertexUvs(meshes[idx].geometry, j);
}
meshes[idx].geometry.uvsNeedUpdate = true;
meshes[idx].geometry.verticesNeedUpdate = true;
}
/**
* Helpers
**/
function setImageAndAtlasSize(size) {
// Identify the subimage size in px (width/height) and the
// size of the image as it will be displayed in the map
image = { width: size, height: size, shownWidth: 64, shownHeight: 64 };
// Identify the total number of cols & rows in the image atlas
atlas = { width: 2048, height: 2048, cols: 2048/size, rows: 2048/size };
}
/**
* Add Controls
**/
var controls = new THREE.TrackballControls(camera, renderer.domElement);
/**
* Add Raycaster
**/
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseMove( event ) {
// Calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function onClick( event ) {
// Determine which image is selected (if any)
var selected = raycaster.intersectObjects( scene.children );
// Intersecting elements are ordered by their distance (increasing)
if (!selected) return;
if (selected.length) {
selected = selected[0];
console.log('clicked', selected.object.userData.idx)
}
}
window.addEventListener('mousemove', onMouseMove)
window.addEventListener('click', onClick)
/**
* Handle window resizes
**/
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
});
/**
* Render!
**/
// The main animation function that re-renders the scene each animation frame
function animate() {
requestAnimationFrame( animate );
raycaster.setFromCamera( mouse, camera );
renderer.render( scene, camera );
controls.update();
}
animate();
* {
margin: 0;
padding: 0;
background: #000;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
<script src="https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/js/texture-loader.js"></script>
<script src="https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/js/trackball-controls.js"></script>
<div id='loader'>0%</div>
You can potentially use mutli-materials and geometry groups (or in your case, material indices).
This depends on your texture dimensions scaling 1::1. In other words, if your first resolution as dimensions 32x64, then double that resolution should have dimensions of 64x128. UVs are percentage-based, so moving from an image at one resolution to the same image at another resolution "just works".
At this point, you really only need to change the texture image source. But it sounds like you don't want to do that. So instead, we need to assign ALL of your textures to the same Mesh at once. Three.js makes this really easy...
var myMesh = new THREE.Mesh(myGeometry, [ material1, material2, material3 ]);
Notice that the material parameters is defined as an array. Each material has a different texture, which in your case are the different resolution images.
Now, debug into your Mesh. Under the goemetry property, you'll see an a property called faces, which is an array of Face3 objects. Each face has a property named materialIndex. This is the face's reference to the array of materials.
When you reach a point where you want to trigger a change (such as your camera being a certain distance from a mesh), you can change the material index, then trigger the mesh to change its material:
var distance = camera.position.distanceTo(myMesh.position);
if(distance < 50){
myMesh.faces.forEach(function(face){
face.materialIndex = 2;
});
}
else if(distance => 50 && distance < 100){
myMesh.faces.forEach(function(face){
face.materialIndex = 1;
});
}
else{
myMesh.faces.forEach(function(face){
face.materialIndex = 0;
});
}
myMesh.groupsNeedUpdate = true;
The last line (myMesh.groupsNeedUpdate = true;) tells the renderer that the material indices changed, so it will need to update the materials for the render.
Perhaps you could use THREE.LOD. It basically allows you to define different meshes for a range of distances. The meshes would be the same Quads, but you could change their materials to use different textures...
Here is the LOD example in the THREE.js web.
Hope it helps!!

Three JS Keep Label Size On Zoom

I'm working on a solar system in three.js and am curious if there is an easy way to make the labels for the planets I have below all show up the same size regardless of how far they are from the camera? I can't seem to find a solution to this. I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that. Seems like there would be an easier way to accomplish this?
Thanks!
Updated with answer from prisoner849. Works excellent!
I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that.
And it's very simple. Let's say, a THREE.Sprite() object (label) is a child of a THREE.Mesh() object (planet), then in your animation loop you need to do
var scaleVector = new THREE.Vector3();
var scaleFactor = 4;
var sprite = planet.children[0];
var scale = scaleVector.subVectors(planet.position, camera.position).length() / scaleFactor;
sprite.scale.set(scale, scale, 1);
I've made a very simple example of the Solar System, using this technique.
For the benefit of future visitors, the transform controls example does exactly this:
https://threejs.org/examples/misc_controls_transform.html
Here's how its done in the example code:
var factor;
if ( this.camera.isOrthographicCamera ) {
factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
} else {
factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
}
handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 );
Finally I found the answer to your question:
First, create a DOM Element:
<div class="element">Not Earth</div>
Then set CSS styles for it:
.element {position: absolute; top:0; left:0; color: white}
// |-------------------------------| |-----------|
// make the element on top of canvas is
// the canvas black, so text
// must be white
After that, create moveDom() function and run it every time you render the scene requestAnimationFrame()
geometry is the geometry of the mesh
cube is the mesh you want to create label
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * innerWidth/2) + innerWidth/2;
vector.y = -(vector.y * innerHeight/2) + innerHeight/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
You can create a for loop to set label for all the mesh in your scene.
Because this trick only set 2D position of DOM Element, the size of label is the same even if you zoom (the label is not part of three.js scene).
Full test case: https://jsfiddle.net/0L1rpayz/1/
var renderer, scene, camera, cube, vector, geometry;
var ww = window.innerWidth,
wh = window.innerHeight;
function init(){
renderer = new THREE.WebGLRenderer({canvas : document.getElementById('scene')});
renderer.setSize(ww,wh);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50,ww/wh, 0.1, 10000 );
camera.position.set(0,0,500);
scene.add(camera);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set( 0, 0, 500 );
scene.add(light);
//Vector use to get position of vertice
vector = new THREE.Vector3();
//Generate Not Earth
geometry = new THREE.BoxGeometry(50,50,50);
var material = new THREE.MeshLambertMaterial({color: 0x00ff00});
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
//Render my scene
render();
}
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * ww/2) + ww/2;
vector.y = -(vector.y * wh/2) + wh/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
var counter = 0;
var render = function (a) {
requestAnimationFrame(render);
counter++;
//Move my cubes
cube.position.x = Math.cos((counter+1*150)/200)*(ww/6+1*80);
cube.position.y = Math.sin((counter+1*150)/200)*(70+1*80);
cube.rotation.x += .001*1+.002;
cube.rotation.y += .001*1+.02;
//Move my dom elements
moveDom();
renderer.render(scene, camera);
};
init();
body,html, canvas{width:100%;height:100%;padding:0;margin:0;overflow: hidden;}
.element{color:white;position:absolute;top:0;left:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<!-- My scene -->
<canvas id="scene"></canvas>
<div class="element">
<h1>Not Earth</h1>
</div>
If you downvote this, please tell me why. I will try my best to improve my posts.
If you are using spriteMaterial to present your text, you could try to set the sizeAttenuation attribute to false.
var spriteMaterial = new THREE.SpriteMaterial( { map: spriteMap, color: 0xffffff, sizeAttenuation:false } );
See more information from here:
https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation

Manipulate objects in the browser with three.js using the mouse

I have this piece of code (see below) that I used to draw a cube with three.js:
// revolutions per second
var angularSpeed = 0.0;
var lastTime = 0;
function animate(){
// update
var time = (new Date()).getTime();
var timeDiff = time - lastTime;
var angleChange = angularSpeed * timeDiff * 2 * Math.PI / 1000;
cube.rotation.y += angleChange;
lastTime = time;
// render
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(animate);
}
// renderer
var container = document.getElementById("container");
var renderer = new THREE.WebGLRenderer();
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 700;
// scene
var scene = new THREE.Scene();
// cube Length, Height, Width
var cube = new THREE.Mesh(new THREE.CubeGeometry(400, 200, 200), new THREE.MeshBasicMaterial({
wireframe: true,
color: '#ff0000'
}));
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
// start animation
animate();
Does anyone know whether is it possible to allow the user to change the size of the cube by dragging the edges using the mouse?
Check this jsfiddle. I reused the structure of draggableCubes, plus little changes :
to drag the vertices i created vertexHelpers (little spheres);
to avoid maths the trick is to use an invisible plane to drag your objects/vertices on, perpendicular to the camera. To see it in action, just set plane.visible=true
now we can correctly drag a vertexHelper, its distance to the center of the cube changes. We just have to scale the cube at the same ratio :
Within the mouseMove listener's function it becomes:
if(SELECTED){
var intersects=raycaster.intersectObject(plane);
//so we get the mouse 3D coordinates in intersects[0].point
var previousDistance=SELECTED.position.sub(cube.position).length();
var increaseRatio=intersects[0].point.sub(cube.position).length()/previousDistance;
cube.scale.set(
cube.scale.x*increaseRatio,
cube.scale.y*increaseRatio,
cube.scale.z*increaseRatio
);
//then update the vertexHelpers position (copy the new vertices positions)
}
EDIT :
In your question you precisely ask to resize a cube by dragging its edges. I did not remember it in the example and did not think about it intuitively, but it can be done the same way.
However, given lineWidth is not implemented in ANGLE (the program used on windows to translate WebGL), it is not easy to pick lines with a 1px-width. I remember a threejs example I could not find, where a geometry is associated to the line so it looks outlined. Basically you could do it by creating a cylinder as custom 'edgesHelpers' (i'm precisely not talking about the THREE.EdgesHelper) and they have to be resized each time the cube is too.
In your setup code add an eventlistener for mousemove:
// Event Handlers
renderer.domElement.addEventListener('mousemove', onDocumentMouseMove, false);
Then in the event handler code, you can check which object is selected and adjust the scale parameter.
function onDocumentMouseMove(event) {
...
var selected = raycaster.intersectObjects(objects);
if (selected.length > 0) {
// Do things to the scale parameter(s)... Just for demo purposes
selected.scale.x = selected.scale.x + selected[0].point.sub(offset).x / 1000;
selected.scale.y = selected.scale.y + selected[0].point.sub(offset).y / 1000;
return;
}
...
}
Since I am typing pseudo code here, all too easy to make an error, so I have left a test version here for you to try: http://www.numpty.co.uk/cubedrag.html
As you can see, size of selected object changes in horrible ways with the dragging of the mouse. You have me interested, so will look into making it proportional to movement if I get more time.

Categories