three.js change object dynamically related to distance - three.js

I made a graphical website with three.js.
It's concept is universe that has so many texts.
When distance of between camera and mesh is close, mesh is text.
But if distance is far, it change to square.
I wonder that change mesh related to distance is possible?
I searched in google few hours, there is no information about this.
code here:
// Define Variables
var myElement = document.getElementById("threejs");
let camera, scene, renderer;
const mouse = new THREE.Vector2();
clicked = new THREE.Vector2();
const target = new THREE.Vector2();
const windowHalf = new THREE.Vector2( window.innerWidth / 2, window.innerHeight / 2 );
const moveState = {forward: 0, back: 0};
var isMobile = false;
var hold = -1;
/****** Define Function ******/
/*****************************/
checkMobile = () => {
var UserAgent = navigator.userAgent;
if (UserAgent.match(/iPhone|iPod|Android|Windows CE|BlackBerry|Symbian|Windows Phone|webOS|Opera Mini|Opera Mobi|POLARIS|IEMobile|lgtelecom|nokia|SonyEricsson/i) != null || UserAgent.match(/LG|SAMSUNG|Samsung/) != null) {
isMobile = true;
} else {
isMobile = false;
}
}
checkMobile();
onMouseMove = (event) => {
mouse.x = ( (event.clientX/2) - (windowHalf.x/2) );
mouse.y = ( (event.clientY/2) - (windowHalf.y/2) );
clicked.x = ( event.clientX / window.innerWidth ) * 2 - 1;
clicked.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
onResize = (event) => {
const width = window.innerWidth;
const height = window.innerHeight;
windowHalf.set( width / 2, height / 2 );
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
}
onContextMenu = (event) => {
event.preventDefault();
}
onMouseDown = (event) => {
hold = event.which;
}
onMouseUp = () => {
hold = -1;
};
// TEST
//
// Start Script
init = () => {
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 1000;
scene = new THREE.Scene();
const geometry = new THREE.BoxBufferGeometry();
const material = new THREE.MeshNormalMaterial({ transparent: true });
if(isMobile) {
var controls = new THREE.DeviceOrientationControls(camera);
} else {
console.log('isMobile false');
}
group = new THREE.Group();
for ( let i = 0; i < 800; i ++ ) {
let sprite = new THREE.TextSprite({
textSize: 2,
redrawInterval: 1,
texture: {
text: 'TEST',
fontFamily: 'Arial, Helvetica, sans-serif',
},
material: {
color: 'white',
},
});
sprite.position.x = Math.random() * 180-100;
sprite.position.y = Math.random() * 180-100;
sprite.position.z = Math.random() * 1000-40;
group.add(sprite);
}
scene.add(group);
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// Event handler
document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('mousedown', onMouseDown, false);
document.addEventListener('mouseup', onMouseUp, false);
document.addEventListener('contextmenu', onContextMenu, false);
window.addEventListener('resize', onResize, false);
// Helper
var axesHelper = new THREE.AxesHelper( 15 );
scene.add( axesHelper );
animate = () => {
// For camera follow mouse cursor
target.x = ( 1 - mouse.x ) * 0.002;
target.y = ( 1 - mouse.y ) * 0.002;
camera.rotation.x += 0.05 * ( target.y - camera.rotation.x );
camera.rotation.y += 0.05 * ( target.x - camera.rotation.y );
if(isMobile) {
controls.update();
}
switch(hold) {
case 1:
if(camera.position.z > 0) {
camera.position.z -= 4;
}
break;
case 3:
camera.position.z += 4;
break;
}
// Object opacity related to distance between camera and object
for (i = 0; i < 800; i++) {
var distance = camera.position.distanceTo(group.children[i].position);
var opacity = -1 / 400 * distance + 1;
if (opacity < 0) {
opacity = 0;
}
group.children[i].material.opacity = opacity;
}
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
// Run
animate();
}
// Run
init();
canvas {
width: 100%;
height: 100%;
/* background: #11e8bb; Old browsers */
/* background: -moz-linear-gradient(top, #11e8bb 0%, #8200c9 100%); FF3.6-15 */
/* background: -webkit-linear-gradient(top, #11e8bb 0%,#8200c9 100%); Chrome10-25,Safari5.1-6 */
/* background: linear-gradient(to bottom, #11e8bb 0%,#8200c9 100%); W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
background: radial-gradient(circle, #ed1654, #f61e6c, #f76098);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#11e8bb', endColorstr='#8200c9',GradientType=0 ); /* IE6-9 */
}
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
#threejs {
position: absolute;
overflow: hidden;
width: 100%;
height: 100%;
}
header {
position: fixed;
z-index: 9999;
background-color: white;
width: 100%;
top: 0;
display: flex;
align-items: center;
height: 50px;
}
/* Header Left */
.header-left {
display: flex;
justify-content: center;
flex: 1;
}
.header-left img {
width: 80px;
height: 20px;
}
/* Header Right */
.header-right {
flex: 1;
padding-left: 200px;
}
.header-right a {
text-decoration: none;
color: black;
font-weight: 600;
}
.header-right a:nth-child(2) {
padding-left: 50px;
padding-right: 50px;
}
/* Main Company */
.down-btn {
display: flex;
position: absolute;
justify-content: center;
align-items: center;
bottom: 0;
color: white;
left: 50%;
font-size: 2rem;
cursor: pointer;
}
.down-btn a {
text-decoration: none;
color: white;
padding-bottom: 20px;
}
/* Section */
section {
background-color: aliceblue;
height: 100%;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<!-- <script src="three.js"></script>-->
<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>
<script src="https://unpkg.com/three.texttexture"></script>
<script src="https://unpkg.com/three.textsprite"></script>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
</head>
<body>
<header>
<div class="header-left">
MAIN
</div>
<div class="header-right">
ABOUT
PRODUCT
CONTACT
</div>
</header>
<div id="threejs"></div>
<script src="src.js"></script>
<div class="down-btn">
↓
</div>
<section id="section">
SECTION
</section>
</body>
</html>
(Mouse left click : move forward / right click : move backward)
I implemented almost done except change mesh.
Is it possible or any solution about this issue?
Thanks.

You can use the THREE.LOD() (Level of Detail) to replace the a mesh by another mesh at a certain distance.
The example (https://threejs.org/examples/#webgl_lod) uses only same type of geometry for the different distances. But if you look into the code, each distance has its own geometry and mesh instance.
And so can you, to change to 100% different mesh.
// create meshes and LOD objects
for ( let i = 0; i < 800; i ++ ) {
let lod = new THREE.LOD();
let sprite = new THREE.TextSprite(...);
let squareGeo = new THREE.PlaneBufferGeometry(2,2),
squareMat = new THREE.MeshBasicMaterial(),
square = new THREE.Mesh(squareGeo, squareMat);
lod.addLevel(sprite, 1);
lod.addLevel(square, 100); // will be visible from 100 and beyond
lod.position.x = Math.random() * 180-100;
lod.position.y = Math.random() * 180-100;
lod.position.z = Math.random() * 1000-40;
group.add(lod);
}
// animation loop
function animate() {
// ...
group.children.forEach(function (child) {
// LOD update
child.update(camera);
// opacity
var distance = camera.position.distanceTo(child.position);
var opacity = -1 / 400 * distance + 1;
if (opacity < 0) {
opacity = 0;
}
child.getObjectForDistance(distance).material.opacity = opacity;
});
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
EDIT: LOD class modification for smooth transition
addLevel: function ( object, distance, fadeDistance ) {
...
levels.splice( l, 0, { distance: distance, fadeDistance: fadeDistance || distance, object: object } );
...
}
update: function () {
...
levels[ 0 ].object.visible = true;
levels[ 0 ].object.material.opacity = 1.0;
for ( var i = 1, l = levels.length; i < l; i ++ ) {
if ( distance >= levels[ i ].distance && distance < levels[ i ].fadeDistance ) {
levels[ i ].object.visible = true;
var t = (distance - levels[i].distance) / (levels[i].fadeDistance - levels[i].distance);
levels[ i - 1 ].object.material.opacity = 1.0 - t;
levels[ i ].object.material.opacity = t;
} else if ( distance >= levels[ i ].fadeDistance ) {
levels[ i - 1 ].object.visible = false;
levels[ i ].object.visible = true;
levels[ i ].object.material.opacity = 1.0;
} else {
break;
}
}
...
}
Of course, material.transparent property of objects should be set true, so opacity will work.
Adding square to LOD object
lod.addLevel(sprite, 1);
lod.addLevel(star, 100, 140); // will fade in at distance of 100 to 140, fully visible beyond 140
lod.addLevel(dummy, 200, 400); // if dummy is an object with empty geometry, star will fade out between 200 and 400
Remove opacity modification in animation loop.

Related

Is there a way to get the depth values using WebGL and Three.js?

I am trying to get the depth values of each pixel in the canvas element. Is there a way to find these depth values using WebGL and Three.js?
What I majorly want is that for eg. in the image below, the red background should have 0 as the depth value whereas the 3D model should have the depth values based on the distance from the camera.
Using the X,Y coordinates of the canvas, is there a method to access the depth values?
[Edit 1]: Adding more information
I pick three random points as shown below, then I ask the user to input the depth values for each of these points. Once the input is received from the user, I will compute the difference between the depth values in three.js and the values inputted from the user.
Basically, I would require a 2D array of the canvas size where each pixel corresponds to an array value. This 2D array must contain the value 0 if the pixel is a red background, or contain the depth value if the pixel contains the 3D model.
Two ways come to mind.
One you can just use RayCaster
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
.info {
position: absolute;
left: 1em;
top: 1em;
padding: 1em;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: xx-small;
}
.info::after{
content: '';
position: absolute;
border: 10px solid transparent;
border-top: 10px solid rgba(0, 0, 0, 0.7);
top: 0;
left: -10px;
}
<canvas id="c"></canvas>
<script type="module">
// Three.js - Picking - RayCaster
// from https://threejsfundamentals.org/threejs/threejs-picking-raycaster.html
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 60;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 30;
const points = [
[170, 20],
[400, 50],
[225, 120],
].map((point) => {
const infoElem = document.createElement('pre');
document.body.appendChild(infoElem);
infoElem.className = "info";
infoElem.style.left = `${point[0] + 10}px`;
infoElem.style.top = `${point[1]}px`;
return {
point,
infoElem,
};
});
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
// put the camera on a pole (parent it to an object)
// so we can spin the pole to move the camera around the scene
const cameraPole = new THREE.Object3D();
scene.add(cameraPole);
cameraPole.add(camera);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
camera.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
function randomColor() {
return `hsl(${rand(360) | 0}, ${rand(50, 100) | 0}%, 50%)`;
}
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
const material = new THREE.MeshPhongMaterial({
color: randomColor(),
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.set(rand(-20, 20), rand(-20, 20), rand(-20, 20));
cube.rotation.set(rand(Math.PI), rand(Math.PI), 0);
cube.scale.set(rand(3, 6), rand(3, 6), rand(3, 6));
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
const raycaster = new THREE.Raycaster();
function render(time) {
time *= 0.001; // convert to seconds;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
cameraPole.rotation.y = time * .1;
for (const {point, infoElem} of points) {
const pickPosition = {
x: (point[0] / canvas.clientWidth ) * 2 - 1,
y: (point[1] / canvas.clientHeight) * -2 + 1, // note we flip Y
};
raycaster.setFromCamera(pickPosition, camera);
const intersectedObjects = raycaster.intersectObjects(scene.children);
if (intersectedObjects.length) {
// pick the first object. It's the closest one
const intersection = intersectedObjects[0];
infoElem.textContent = `position : ${point[0]}, ${point[1]}
distance : ${intersection.distance.toFixed(2)}
z depth : ${((intersection.distance - near) / (far - near)).toFixed(3)}
local pos: ${intersection.point.x.toFixed(2)}, ${intersection.point.y.toFixed(2)}, ${intersection.point.z.toFixed(2)}
local uv : ${intersection.uv.x.toFixed(2)}, ${intersection.uv.y.toFixed(2)}`;
} else {
infoElem.textContent = `position : ${point[0]}, ${point[1]}`;
}
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
The other way is to do as you mentioned and read the depth buffer. Unfortunately there is no direct way to read the depth buffer.
To read the depth values you need 2 render targets. You'd render to the first target. That gives you both a color texture with the rendered image and a depth texture with the depth values. You can't read a depth texture directly but you can draw it to another color texture and then read the color texture. Finally you can draw the first color texture to the cavnas.
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
.info {
position: absolute;
left: 1em;
top: 1em;
padding: 1em;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: xx-small;
}
.info::after{
content: '';
position: absolute;
border: 10px solid transparent;
border-top: 10px solid rgba(0, 0, 0, 0.7);
top: 0;
left: -10px;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const points = [
[170, 20],
[400, 50],
[225, 120],
].map((point) => {
const infoElem = document.createElement('pre');
document.body.appendChild(infoElem);
infoElem.className = "info";
infoElem.style.left = `${point[0] + 10}px`;
infoElem.style.top = `${point[1]}px`;
return {
point,
infoElem,
};
});
const renderTarget = new THREE.WebGLRenderTarget(1, 1);
renderTarget.depthTexture = new THREE.DepthTexture();
const depthRenderTarget = new THREE.WebGLRenderTarget(1, 1, {
depthBuffer: false,
stenciBuffer: false,
});
const rtFov = 60;
const rtAspect = 1;
const rtNear = 0.1;
const rtFar = 200;
const rtCamera = new THREE.PerspectiveCamera(rtFov, rtAspect, rtNear, rtFar);
rtCamera.position.z = 30;
const rtScene = new THREE.Scene();
rtScene.background = new THREE.Color('white');
// put the camera on a pole (parent it to an object)
// so we can spin the pole to move the camera around the scene
const cameraPole = new THREE.Object3D();
rtScene.add(cameraPole);
cameraPole.add(rtCamera);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
rtCamera.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
function randomColor() {
return `hsl(${rand(360) | 0}, ${rand(50, 100) | 0}%, 50%)`;
}
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
const material = new THREE.MeshPhongMaterial({
color: randomColor(),
});
const cube = new THREE.Mesh(geometry, material);
rtScene.add(cube);
cube.position.set(rand(-20, 20), rand(-20, 20), rand(-20, 20));
cube.rotation.set(rand(Math.PI), rand(Math.PI), 0);
cube.scale.set(rand(3, 6), rand(3, 6), rand(3, 6));
}
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);
const scene = new THREE.Scene();
camera.position.z = 1;
const sceneMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.texture,
});
const planeGeo = new THREE.PlaneBufferGeometry(2, 2);
const plane = new THREE.Mesh(planeGeo, sceneMaterial);
scene.add(plane);
const depthScene = new THREE.Scene();
const depthMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.depthTexture,
});
const depthPlane = new THREE.Mesh(planeGeo, depthMaterial);
depthScene.add(depthPlane);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
let depthValues = new Uint8Array(0);
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
renderTarget.setSize(canvas.width, canvas.height);
depthRenderTarget.setSize(canvas.width, canvas.height);
rtCamera.aspect = canvas.clientWidth / canvas.clientHeight;
rtCamera.updateProjectionMatrix();
}
cameraPole.rotation.y = time * .1;
// draw render target scene to render target
renderer.setRenderTarget(renderTarget);
renderer.render(rtScene, rtCamera);
renderer.setRenderTarget(null);
// render the depth texture to another render target
renderer.setRenderTarget(depthRenderTarget);
renderer.render(depthScene, camera);
renderer.setRenderTarget(null);
{
const {width, height} = depthRenderTarget;
const spaceNeeded = width * height * 4;
if (depthValues.length !== spaceNeeded) {
depthValues = new Uint8Array(spaceNeeded);
}
renderer.readRenderTargetPixels(
depthRenderTarget,
0,
0,
depthRenderTarget.width,
depthRenderTarget.height,
depthValues);
for (const {point, infoElem} of points) {
const offset = ((height - point[1] - 1) * width + point[0]) * 4;
infoElem.textContent = `position : ${point[0]}, ${point[1]}
z depth : ${(depthValues[offset] / 255).toFixed(3)}`;
}
}
// render the color texture to the canvas
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
The problem is you can only read UNSIGNED_BYTE values from the texture so your depth values only go from 0 to 255 which is not really enough resolution to do much.
To solve that issue you have to encode the depth values across channels when drawing the depth texture to the 2nd render target which means you need to make your own shader. three.js has some shader snippets for packing the values so hacking a shader using ideas from this article we can get better depth values.
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
.info {
position: absolute;
left: 1em;
top: 1em;
padding: 1em;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: xx-small;
}
.info::after{
content: '';
position: absolute;
border: 10px solid transparent;
border-top: 10px solid rgba(0, 0, 0, 0.7);
top: 0;
left: -10px;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const points = [
[170, 20],
[400, 50],
[225, 120],
].map((point) => {
const infoElem = document.createElement('pre');
document.body.appendChild(infoElem);
infoElem.className = "info";
infoElem.style.left = `${point[0] + 10}px`;
infoElem.style.top = `${point[1]}px`;
return {
point,
infoElem,
};
});
const renderTarget = new THREE.WebGLRenderTarget(1, 1);
renderTarget.depthTexture = new THREE.DepthTexture();
const depthRenderTarget = new THREE.WebGLRenderTarget(1, 1, {
depthBuffer: false,
stenciBuffer: false,
});
const rtFov = 60;
const rtAspect = 1;
const rtNear = 0.1;
const rtFar = 200;
const rtCamera = new THREE.PerspectiveCamera(rtFov, rtAspect, rtNear, rtFar);
rtCamera.position.z = 30;
const rtScene = new THREE.Scene();
rtScene.background = new THREE.Color('white');
// put the camera on a pole (parent it to an object)
// so we can spin the pole to move the camera around the scene
const cameraPole = new THREE.Object3D();
rtScene.add(cameraPole);
cameraPole.add(rtCamera);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
rtCamera.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
function randomColor() {
return `hsl(${rand(360) | 0}, ${rand(50, 100) | 0}%, 50%)`;
}
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
const material = new THREE.MeshPhongMaterial({
color: randomColor(),
});
const cube = new THREE.Mesh(geometry, material);
rtScene.add(cube);
cube.position.set(rand(-20, 20), rand(-20, 20), rand(-20, 20));
cube.rotation.set(rand(Math.PI), rand(Math.PI), 0);
cube.scale.set(rand(3, 6), rand(3, 6), rand(3, 6));
}
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);
const scene = new THREE.Scene();
camera.position.z = 1;
const sceneMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.texture,
});
const planeGeo = new THREE.PlaneBufferGeometry(2, 2);
const plane = new THREE.Mesh(planeGeo, sceneMaterial);
scene.add(plane);
const depthScene = new THREE.Scene();
const depthMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.depthTexture,
});
depthMaterial.onBeforeCompile = function(shader) {
// the <packing> GLSL chunk from three.js has the packDeathToRGBA function.
// then at the end of the shader the default MaterialBasicShader has
// already read from the material's `map` texture (the depthTexture)
// which has depth in 'r' and assigned it to gl_FragColor
shader.fragmentShader = shader.fragmentShader.replace(
'#include <common>',
'#include <common>\n#include <packing>',
).replace(
'#include <fog_fragment>',
'gl_FragColor = packDepthToRGBA( gl_FragColor.r );',
);
};
const depthPlane = new THREE.Mesh(planeGeo, depthMaterial);
depthScene.add(depthPlane);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
let depthValues = new Uint8Array(0);
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
renderTarget.setSize(canvas.width, canvas.height);
depthRenderTarget.setSize(canvas.width, canvas.height);
rtCamera.aspect = canvas.clientWidth / canvas.clientHeight;
rtCamera.updateProjectionMatrix();
}
cameraPole.rotation.y = time * .1;
// draw render target scene to render target
renderer.setRenderTarget(renderTarget);
renderer.render(rtScene, rtCamera);
renderer.setRenderTarget(null);
// render the depth texture to another render target
renderer.setRenderTarget(depthRenderTarget);
renderer.render(depthScene, camera);
renderer.setRenderTarget(null);
{
const {width, height} = depthRenderTarget;
const spaceNeeded = width * height * 4;
if (depthValues.length !== spaceNeeded) {
depthValues = new Uint8Array(spaceNeeded);
}
renderer.readRenderTargetPixels(
depthRenderTarget,
0,
0,
depthRenderTarget.width,
depthRenderTarget.height,
depthValues);
for (const {point, infoElem} of points) {
const offset = ((height - point[1] - 1) * width + point[0]) * 4;
const depth = depthValues[offset ] * ((255 / 256) / (256 * 256 * 256)) +
depthValues[offset + 1] * ((255 / 256) / (256 * 256)) +
depthValues[offset + 2] * ((255 / 256) / 256);
infoElem.textContent = `position : ${point[0]}, ${point[1]}
z depth : ${depth.toFixed(3)}`;
}
}
// render the color texture to the canvas
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
Note depthTexture uses a webgl extension which is an optional feature not found on all devices
To work around that would require drawing the scene twice. Once with your normal materials and then again to a color render target using the MeshDepthMaterial.

Three.js: show index number of each vertex in .obj file

is there some helper method in THREE.js that allows one to see the number assigned to each vertex in a mesh loaded from an obj file? [minimal obj file]
I'm trying to rig up some bones inside a mesh, and want to position those bones between particular vertices, but don't know the vertex numbers yet. A little tool like this could be super helpful for this purpose.
If there's no such method in THREE.js, I'll likely build a tool to this effect, but I wanted to save the time if I can. Any suggestions on where to look in THREE.js for this functionality would be greatly appreciated!
The solution is:
Place small boxes on each vertex,
Add tooltip to each box,
Tooltip text set to vertex index.
How to add tooltip in three.js find in my answer here: Threejs Tooltip
Working jsfiddle for your convenience find here: http://jsfiddle.net/mmalex/fux6srgv/
Javascript code:
var scene = new THREE.Scene();
var raycaster = new THREE.Raycaster();
//create some camera
camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 3;
camera.position.y = 3;
camera.position.z = 3;
camera.lookAt(0, 0, 0);
var controls = new THREE.OrbitControls(camera);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x595959));
document.body.appendChild(renderer.domElement);
// white spotlight shining from the side, casting a shadow
var spotLight = new THREE.SpotLight(0xffffff, 2.5, 25, Math.PI / 6);
spotLight.position.set(4, 10, 7);
scene.add(spotLight);
// collect objects for raycasting,
// for better performance don't raytrace all scene
var tooltipEnabledObjects = [];
var colors = new RayysWebColors();
var dodecahedronGeometry = new THREE.DodecahedronBufferGeometry(1, 0);
var dodecahedronMaterial = new THREE.MeshPhongMaterial({
color: colors.pickRandom().hex,
transparent: true,
opacity: 0.75
});
var dodecahedron = new THREE.Mesh(dodecahedronGeometry, dodecahedronMaterial);
scene.add(dodecahedron);
var size = 0.1;
var vertGeometry = new THREE.BoxGeometry(size, size, size);
var vertMaterial = new THREE.MeshBasicMaterial({
color: 0x0000ff,
transparent: false
});
var verts = dodecahedronGeometry.attributes.position.array;
for (let k=0; k<verts.length; k+=3) {
var vertMarker = new THREE.Mesh(vertGeometry, vertMaterial);
// this is how tooltip text is defined for each box
let tooltipText = `idx: ${k}, pos: [${verts[k].toFixed(3)},${verts[k+1].toFixed(3)},${verts[k+2].toFixed(3)}]`;
vertMarker.userData.tooltipText = tooltipText;
vertMarker.applyMatrix(new THREE.Matrix4().makeTranslation(verts[k],verts[k+1],verts[k+2]));
scene.add(vertMarker);
tooltipEnabledObjects.push(vertMarker);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
// this will be 2D coordinates of the current mouse position, [0,0] is middle of the screen.
var mouse = new THREE.Vector2();
var latestMouseProjection; // this is the latest projection of the mouse on object (i.e. intersection with ray)
var hoveredObj; // this objects is hovered at the moment
// tooltip will not appear immediately. If object was hovered shortly,
// - the timer will be canceled and tooltip will not appear at all.
var tooltipDisplayTimeout;
// This will move tooltip to the current mouse position and show it by timer.
function showTooltip() {
var divElement = $("#tooltip");
if (divElement && latestMouseProjection) {
divElement.css({
display: "block",
opacity: 0.0
});
var canvasHalfWidth = renderer.domElement.offsetWidth / 2;
var canvasHalfHeight = renderer.domElement.offsetHeight / 2;
var tooltipPosition = latestMouseProjection.clone().project(camera);
tooltipPosition.x = (tooltipPosition.x * canvasHalfWidth) + canvasHalfWidth + renderer.domElement.offsetLeft;
tooltipPosition.y = -(tooltipPosition.y * canvasHalfHeight) + canvasHalfHeight + renderer.domElement.offsetTop;
var tootipWidth = divElement[0].offsetWidth;
var tootipHeight = divElement[0].offsetHeight;
divElement.css({
left: `${tooltipPosition.x - tootipWidth/2}px`,
top: `${tooltipPosition.y - tootipHeight - 5}px`
});
// var position = new THREE.Vector3();
// var quaternion = new THREE.Quaternion();
// var scale = new THREE.Vector3();
// hoveredObj.matrix.decompose(position, quaternion, scale);
divElement.text(hoveredObj.userData.tooltipText);
setTimeout(function() {
divElement.css({
opacity: 1.0
});
}, 25);
}
}
// This will immediately hide tooltip.
function hideTooltip() {
var divElement = $("#tooltip");
if (divElement) {
divElement.css({
display: "none"
});
}
}
// Following two functions will convert mouse coordinates
// from screen to three.js system (where [0,0] is in the middle of the screen)
function updateMouseCoords(event, coordsObj) {
coordsObj.x = ((event.clientX - renderer.domElement.offsetLeft + 0.5) / window.innerWidth) * 2 - 1;
coordsObj.y = -((event.clientY - renderer.domElement.offsetTop + 0.5) / window.innerHeight) * 2 + 1;
}
function handleManipulationUpdate() {
raycaster.setFromCamera(mouse, camera);
{
var intersects = raycaster.intersectObjects(tooltipEnabledObjects);
if (intersects.length > 0) {
latestMouseProjection = intersects[0].point;
hoveredObj = intersects[0].object;
}
}
if (tooltipDisplayTimeout || !latestMouseProjection) {
clearTimeout(tooltipDisplayTimeout);
tooltipDisplayTimeout = undefined;
hideTooltip();
}
if (!tooltipDisplayTimeout && latestMouseProjection) {
tooltipDisplayTimeout = setTimeout(function() {
tooltipDisplayTimeout = undefined;
showTooltip();
}, 330);
}
}
function onMouseMove(event) {
updateMouseCoords(event, mouse);
latestMouseProjection = undefined;
hoveredObj = undefined;
handleManipulationUpdate();
}
window.addEventListener('mousemove', onMouseMove, false);
animate();
HTML code:
<p style="margin-left: 12px;">Mouse hover verts to see vert index and coordinates</p>
<div id="tooltip"></div>
CSS of tooltip element:
#tooltip {
position: fixed;
left: 0;
top: 0;
min-width: 10px;
text-align: center;
padding: 2px 2px;
font-family: monospace;
background: #a0c020;
display: none;
opacity: 0;
border: 0px solid transparent;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
transition: opacity 0.25s linear;
border-radius: 0px;
}
Given approach allows to attach and display any text info per vertex.

three.js object dynamic change with LOD animation

I made a website with three.js for 3d work.
It's concept is universe that has so many texts.
There is 2 things I have to implement.
First. Object is star when distance between camera and object is far.
Second. Object is Text when distance between camera and object is close.
So, I implement it with LOD.
Everything works fine.
But, star <-> text change trainsition is so rough.
I want to insert smooth change transition(animation) into it.
So I read a docs, sadly, there is any properties about it.
Is it possible to insert animation into LOD?
or any possible soulution about this?
[Source code]
// VARIABLES
let clock, camera, scene, renderer, mixer;
var myElement = document.getElementById("threejs");
const mouse = new THREE.Vector2();
const clicked = new THREE.Vector2();
const target = new THREE.Vector2();
const windowHalf = new THREE.Vector2( window.innerWidth / 2, window.innerHeight / 2 );
const moveState = {forward: 0, back: 0};
var isMobile = false;
var textCount = 200;
checkMobile()
init();
function init() {
// CAMERA
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2100 );
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 1200;
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0xf05fa6, 300, 400);
clock = new THREE.Clock();
// HELPER
const gridHelper = new THREE.PolarGridHelper( 8, 16 );
scene.add( gridHelper );
// LIGHT
const ambientLight = new THREE.AmbientLight( 0xffffff, 0.2 );
scene.add( ambientLight );
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
directionalLight.position.set( 1, 1, - 1 );
scene.add( directionalLight );
// CONTROLS
if(isMobile) {
var controls = new THREE.DeviceOrientationControls(camera);
console.log('isMobile true');
} else {
console.log('isMobile false');
}
// ADD MESH
var group = new THREE.Group();
var size = ( isMobile ? 2 : 4 );
var starsLights = new THREE.Group();
for ( let i = 0; i < textCount; i ++ ) {
var lod = new THREE.LOD();
// Star
var geometry = new THREE.SphereGeometry(0.3, 16, 16);
var material = new THREE.MeshBasicMaterial({color: 0xffffff});
var star = new THREE.Mesh(geometry, material);
// Text
let sprite = new THREE.TextSprite({
textSize: size,
redrawInterval: 250,
texture: {
text: 'TEST TEST TEST',
fontFamily: 'Arial, Helvetica, sans-serif',
},
material: {
color: 'white',
},
});
// Star Light
var velX = (Math.random() + 0.1) * 0.1 * (Math.random()<0.5?-1:1);
var velY = (Math.random() + 0.1) * 0.1 * (Math.random()<0.5?-1:1);
star.vel = new THREE.Vector2(velX, velY);
var starLight = new THREE.PointLight(0x000000, 0.8, 3);
starLight.position.copy(star.position);
starLight.position.y += 0.5;
starsLights.add(starLight);
// Add
lod.addLevel(sprite, 1);
lod.addLevel(star, 200);
lod.position.x = Math.random() * 180-100;
lod.position.y = Math.random() * 180-100;
lod.position.z = Math.random() * 1000-40;
group.add(lod);
// group.add(starsLights);
}
scene.add(group);
scene.add(starLight);
// Renderer
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById("universe").appendChild(renderer.domElement);
// Event handler
window.addEventListener( 'resize', onWindowResize, false );
document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('mousewheel', onMouseWheel, false);
document.addEventListener('contextmenu', onContextMenu, false);
function animate() {
target.x = ( 1 - mouse.x ) * 0.002;
target.y = ( 1 - mouse.y ) * 0.002;
camera.rotation.x += 0.05 * ( target.y - camera.rotation.x );
camera.rotation.y += 0.05 * ( target.x - camera.rotation.y );
if(isMobile) {
controls.update();
}
// Object change related to distance
group.children.forEach(function(child) {
child.update(camera);
var distance = camera.position.distanceTo(child.position);
var opacity = -1 / 400 * distance + 1;
if (opacity < 0) {
opacity = 0;
}
child.getObjectForDistance(distance).material.opacity = opacity;
})
// Render
requestAnimationFrame( animate );
render(scene, camera);
}
animate();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onMouseWheel(event) {
if(camera.position.z > 100) {
event.preventDefault();
camera.position.z -= event.deltaY * 0.2;
} else {
}
}
function render() {
const delta = clock.getDelta();
if ( mixer !== undefined ) mixer.update( delta );
renderer.render( scene, camera );
}
function onTransitionEnd( event ) {
console.log("Loading Complete");
event.target.remove();
}
// Exist functions
function checkMobile() {
var UserAgent = navigator.userAgent;
if (UserAgent.match(/iPhone|iPod|Android|Windows CE|BlackBerry|Symbian|Windows Phone|webOS|Opera Mini|Opera Mobi|POLARIS|IEMobile|lgtelecom|nokia|SonyEricsson/i) != null || UserAgent.match(/LG|SAMSUNG|Samsung/) != null) {
isMobile = true;
} else {
isMobile = false;
}
}
function onMouseMove(event) {
mouse.x = ( (event.clientX/2) - (windowHalf.x/2) );
mouse.y = ( (event.clientY/2) - (windowHalf.y/2) );
clicked.x = ( event.clientX / window.innerWidth ) * 2 - 1;
clicked.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function onResize(event) {
const width = window.innerWidth;
const height = window.innerHeight;
windowHalf.set( width / 2, height / 2 );
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
}
function onContextMenu(event) {
event.preventDefault();
}
function topFunction() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
canvas {
width: 100%;
height: 100%;
/* background: #11e8bb; Old browsers */
/* background: -moz-linear-gradient(top, #11e8bb 0%, #8200c9 100%); FF3.6-15 */
/* background: -webkit-linear-gradient(top, #11e8bb 0%,#8200c9 100%); Chrome10-25,Safari5.1-6 */
/* background: linear-gradient(to bottom, #11e8bb 0%,#8200c9 100%); W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
background: radial-gradient(circle, #ed1654, #f61e6c, #f76098);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#11e8bb', endColorstr='#8200c9',GradientType=0 ); /* IE6-9 */
}
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
#threejs {
position: absolute;
overflow: hidden;
width: 100%;
height: 100%;
}
header {
position: fixed;
z-index: 9999;
background-color: white;
width: 100%;
top: 0;
display: flex;
align-items: center;
height: 50px;
}
/* Header Left */
.header-left {
display: flex;
justify-content: center;
flex: 1;
}
.header-left img {
width: 80px;
height: 20px;
}
/* Header Right */
.header-right {
flex: 1;
padding-left: 200px;
}
.header-right a {
text-decoration: none;
color: black;
font-weight: 600;
}
.header-right a:nth-child(2) {
padding-left: 50px;
padding-right: 50px;
}
/* Main Company */
.down-btn {
display: flex;
position: absolute;
justify-content: center;
align-items: center;
bottom: 0;
color: white;
left: 50%;
font-size: 2rem;
cursor: pointer;
}
.down-btn a {
text-decoration: none;
color: white;
padding-bottom: 20px;
}
/* Section */
section {
background-color: aliceblue;
height: 100%;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<!-- <script src="three.js"></script>-->
<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>
<script src="https://unpkg.com/three.texttexture"></script>
<script src="https://unpkg.com/three.textsprite"></script>
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
</head>
<body>
<header>
<div class="header-left">
MAIN
</div>
<div class="header-right">
ABOUT
PRODUCT
CONTACT
</div>
</header>
<section id="universe"></section>
<script src="src.js"></script>
<section id="section">
SECTION
</section>
</body>
</html>
THREE.LOD can't do what you are looking for. But it's not that hard to write a custom LOD class that performs a smooth alpha blending between two levels. The advantage of this approach is that it is perceived as more continuous than discrete LODs. However, since two objects are rendered during the transition, it's computationally a bit more expensive.
The following fiddle illustrates this approach (I'm sure it's possible to implement this more elegant but it's at least a starting point^^): https://jsfiddle.net/f2Lommf5/14585/
When creating a LOD object, you can adjust a threshold value that defines the extent of the transition.
const lod = new LOD();
lod.threshold = 250;

I am so confused why my rectarea light won't target, it just won't work

I've tried multiple times to target my rect area light and it won't work. I am able to use a spot light and target and I don't understand why this would work and the other wouldn't. Normally, as in this example: threejs.org/examples/?q=obj#webgl_loader_obj2 there is an object plain. When I added the code pertaining to the target like the position and matrix the plain disappears and so does my OBJ. I honestly can't find anything on the issue pertaining to rectarea lights. Please help if you see what is wrong, thanks.
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
font-family: Monospace;
background-color: #000;
color: #fff;
margin: 0 0 0 0;
padding: 0 0 0 0;
border: none;
cursor: default;
}
#info {
color: #fff;
position: absolute;
top: 10px;
width: 100%;
text-align: center;
z-index: 100;
display:block;
}
#info a {
color: #f00;
font-weight: bold;
text-decoration: underline;
cursor: pointer
}
#glFullscreen {
width: 100%;
height: 100vh;
min-width: 640px;
min-height: 360px;
position: relative;
overflow: hidden;
z-index: 0;
}
#example {
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: #000000;
}
#feedback {
color: darkorange;
}
#dat {
user-select: none;
position: absolute;
left: 0;
top: 0;
z-Index: 200;
}
</style>
</head>
<body>
<div id="glFullscreen">
<canvas id="example"></canvas>
</div>
<div id="dat">
</div>
<div id="info">
three.js - OBJLoader2 direct loader test
<div id="feedback"></div>
</div>
<script src="Detector.js"></script>
<script src="http://threejs.org/build/three.min.js"></script>
<script src="TrackballControls.js"></script>
<script src="MTLLoader.js"></script>
<script src="dat.gui.min.js"></script>
<script src="OBJLoader2.js"></script>
<script>
'use strict';
var OBJLoader2Example = (function () {
function OBJLoader2Example( elementToBindTo ) {
this.renderer = null;
this.canvas = elementToBindTo;
this.aspectRatio = 1;
this.recalcAspectRatio();
this.scene = null;
this.cameraDefaults = {
posCamera: new THREE.Vector3( 0.0, 175.0, 500.0 ),
posCameraTarget: new THREE.Vector3( 0, 0, 0 ),
near: 0.1,
far: 10000,
fov: 45
};
this.camera = null;
this.cameraTarget = this.cameraDefaults.posCameraTarget;
this.controls = null;
this.smoothShading = true;
this.doubleSide = false;
this.cube = null;
this.pivot = null;
}
OBJLoader2Example.prototype.initGL = function () {
this.renderer = new THREE.WebGLRenderer( {
canvas: this.canvas,
antialias: true,
autoClear: true
} );
this.renderer.setClearColor( 0x050505 );
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera( this.cameraDefaults.fov, this.aspectRatio, this.cameraDefaults.near, this. cameraDefaults.far );
this.resetCamera();
this.controls = new THREE.TrackballControls( this.camera, this.renderer.domElement );
var ambientLight = new THREE.AmbientLight( 0xffffff );
var directionalLight1 = new THREE.DirectionalLight( 0xC0C090, .4 );
var directionalLight2 = new THREE.DirectionalLight( 0xC0C090, .4 );
var directionalLight3 = new THREE.DirectionalLight( 0xC0C090, .4 );
var directionalLight4 = new THREE.DirectionalLight( 0xC0C090, .4 );
directionalLight1.position.set( -100, -50, 100 );
directionalLight2.position.set( 100, 50, -100 );
directionalLight3.position.set( -100, -50, -100 );
directionalLight4.position.set( 100, 50, 100 );
/*this.scene.add(directionalLight2);
this.scene.add(directionalLight1);
this.scene.add(directionalLight3);
this.scene.add(directionalLight4);*/
var helper = new THREE.GridHelper( 1200, 60, 0xFF4444, 0x404040 );
this.scene.add( helper );
var geometry = new THREE.BoxGeometry( .5, .5, .5 );
var material = new THREE.MeshNormalMaterial();
this.cube = new THREE.Mesh( geometry, material );
this.pivot = new THREE.Object3D();
this.pivot.name = 'Pivot';
this.scene.add( this.pivot );
var lighta = new THREE.RectAreaLight( 0xffffbb, 82, 1, 2 );
lighta.position.set(0, 0, 1 );
this.scene.add( lighta);
var helper = new THREE.RectAreaLightHelper( lighta );
// light.target.position.set( 0, 0, 0 );
// lighta.target.matrixWorldNeedsUpdate();
this.scene.add( helper );
};
OBJLoader2Example.prototype.initPostGL = function ( objDef ) {
var scope = this;
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath( objDef.texturePath );
mtlLoader.setCrossOrigin( 'anonymous' );
mtlLoader.load( objDef.fileMtl, function( materials ) {
materials.preload();
var objLoader = new THREE.OBJLoader2();
objLoader.setSceneGraphBaseNode( scope.pivot );
objLoader.setMaterials( materials.materials );
objLoader.setPath( objDef.path );
objLoader.setDebug( false, false );
var onSuccess = function ( object3d ) {
console.log( 'Loading complete. Meshes were attached to: ' + object3d.name );
};
var onProgress = function ( event ) {
if ( event.lengthComputable ) {
var percentComplete = event.loaded / event.total * 100;
var output = 'Download of "' + objDef.fileObj + '": ' + Math.round( percentComplete ) + '%';
console.log(output);
}
};
var onError = function ( event ) {
console.error( 'Error of type "' + event.type + '" occurred when trying to load: ' + event.src );
};
objLoader.load( objDef.fileObj, onSuccess, onProgress, onError );
});
return true;
};
OBJLoader2Example.prototype.resizeDisplayGL = function () {
this.controls.handleResize();
this.recalcAspectRatio();
this.renderer.setSize( this.canvas.offsetWidth, this.canvas.offsetHeight, false );
this.updateCamera();
};
OBJLoader2Example.prototype.recalcAspectRatio = function () {
this.aspectRatio = ( this.canvas.offsetHeight === 0 ) ? 1 : this.canvas.offsetWidth / this.canvas.offsetHeight;
};
OBJLoader2Example.prototype.resetCamera = function () {
this.camera.position.copy( this.cameraDefaults.posCamera );
this.cameraTarget.copy( this.cameraDefaults.posCameraTarget );
this.updateCamera();
};
OBJLoader2Example.prototype.updateCamera = function () {
this.camera.aspect = this.aspectRatio;
this.camera.lookAt( this.cameraTarget );
this.camera.updateProjectionMatrix();
};
OBJLoader2Example.prototype.render = function () {
if ( ! this.renderer.autoClear ) this.renderer.clear();
this.controls.update();
this.cube.rotation.x += 0.05;
this.cube.rotation.y += 0.05;
this.renderer.render( this.scene, this.camera );
};
OBJLoader2Example.prototype.alterSmoothShading = function () {
var scope = this;
scope.smoothShading = ! scope.smoothShading;
console.log( scope.smoothShading ? 'Enabling SmoothShading' : 'Enabling FlatShading');
scope.traversalFunction = function ( material ) {
material.shading = scope.smoothShading ? THREE.SmoothShading : THREE.FlatShading;
material.needsUpdate = true;
};
var scopeTraverse = function ( object3d ) {
scope.traverseScene( object3d );
};
scope.pivot.traverse( scopeTraverse );
};
OBJLoader2Example.prototype.alterDouble = function () {
var scope = this;
scope.doubleSide = ! scope.doubleSide;
console.log( scope.doubleSide ? 'Enabling DoubleSide materials' : 'Enabling FrontSide materials');
scope.traversalFunction = function ( material ) {
material.side = scope.doubleSide ? THREE.DoubleSide : THREE.FrontSide;
};
var scopeTraverse = function ( object3d ) {
scope.traverseScene( object3d );
};
scope.pivot.traverse( scopeTraverse );
};
OBJLoader2Example.prototype.traverseScene = function ( object3d ) {
if ( object3d.material instanceof THREE.MultiMaterial ) {
var materials = object3d.material.materials;
for ( var name in materials ) {
if ( materials.hasOwnProperty( name ) ) this.traversalFunction( materials[ name ] );
}
} else if ( object3d.material ) {
this.traversalFunction( object3d.material );
}
};
return OBJLoader2Example;
})();
var app = new OBJLoader2Example( document.getElementById( 'example' ) );
// Init dat.gui and controls
var OBJLoader2Control = function() {
this.smoothShading = app.smoothShading;
this.doubleSide = app.doubleSide;
};
var objLoader2Control = new OBJLoader2Control();
var gui = new dat.GUI( {
autoPlace: false,
width: 320
} );
var menuDiv = document.getElementById( 'dat' );
menuDiv.appendChild(gui.domElement);
var folderQueue = gui.addFolder( 'OBJLoader2 Options' );
var controlSmooth = folderQueue.add( objLoader2Control, 'smoothShading' ).name( 'Smooth Shading' );
controlSmooth.onChange( function( value ) {
console.log( 'Setting smoothShading to: ' + value );
app.alterSmoothShading();
});
var controlDouble = folderQueue.add( objLoader2Control, 'doubleSide' ).name( 'Double Side Materials' );
controlDouble.onChange( function( value ) {
console.log( 'Setting doubleSide to: ' + value );
app.alterDouble();
});
folderQueue.open();
// init three.js example application
var resizeWindow = function () {
app.resizeDisplayGL();
};
var render = function () {
requestAnimationFrame( render );
app.render();
};
window.addEventListener( 'resize', resizeWindow, false );
console.log( 'Starting initialisation phase...' );
app.initGL();
app.resizeDisplayGL();
app.initPostGL( {
fileObj: 'ex.obj',
fileMtl: 'ex.mtl'
} );
//pp.position.set( 0, 0, 0 );
render();
</script>
</body>

Threejs Tooltip

this could be a duplicate, but I have not found anything as yet.
I am trying to create tooltip for on mouse hover. (perspective camera)
Tooltip issue:
https://jsfiddle.net/thehui87/d12fLr0b/14/
threejs r76
function onDocumentMouseMove( event )
{
// update sprite position
// sprite1.position.set( event.clientX, event.clientY, 1 );
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3( mouse.x, mouse.y, 0 );
vector.unproject(camera);
var dir = vector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );
sprite1.position.copy(pos);
}
But once i orbit the camera the tooltip moves away.
References for tooltip.
http://stemkoski.github.io/Three.js/Mouse-Tooltip.html
Three.js - Object follow mouse position
If anyone could kindly point me to the right answer on stackoverflow or provide an alternate solution.
Thanks
Well working tooltip is not only correctly placed text label. It has some of show and hide intelligence.
It should:
not appear immediately, but once mouse becomes stationary over some object,
not disappear immediately, rather it should fade away after mouse left the object area,
it should neither follow the mouse, nor stay where it was, if user moves the mouse around the object area not leaving it.
All of that is included in my solution: http://jsfiddle.net/mmalex/ycnh0wze/
<div id="tooltip"></div> must be initially added to HTML. The recommended CSS you find below. Crucial to have it position: fixed;, anything else is a matter of taste.
#tooltip {
position: fixed;
left: 0;
top: 0;
min-width: 100px;
text-align: center;
padding: 5px 12px;
font-family: monospace;
background: #a0c020;
display: none;
opacity: 0;
border: 1px solid black;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
transition: opacity 0.25s linear;
border-radius: 3px;
}
var scene = new THREE.Scene();
var raycaster = new THREE.Raycaster();
//create some camera
camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 5;
camera.position.y = 5;
camera.position.z = 5;
camera.lookAt(0, 0, 0);
var controls = new THREE.OrbitControls(camera);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x595959));
document.body.appendChild(renderer.domElement);
// white spotlight shining from the side, casting a shadow
var spotLight = new THREE.SpotLight(0xffffff, 2.5, 25, Math.PI / 6);
spotLight.position.set(4, 10, 7);
scene.add(spotLight);
// collect objects for raycasting,
// for better performance don't raytrace all scene
var tooltipEnabledObjects = [];
var colors = new RayysWebColors();
for (let k = 0; k < 12; k++) {
var size = 0.5;
var geometry = new THREE.BoxGeometry(size, 0.2, size);
var randomColor = colors.pickRandom();
var material = new THREE.MeshPhongMaterial({
color: randomColor.hex,
transparent: true,
opacity: 0.75
});
var cube = new THREE.Mesh(geometry, material);
cube.userData.tooltipText = randomColor.name;
cube.applyMatrix(new THREE.Matrix4().makeTranslation(-3 + 6 * Math.random(), 0, -3 + 0.5 * k));
scene.add(cube);
tooltipEnabledObjects.push(cube);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
// this will be 2D coordinates of the current mouse position, [0,0] is middle of the screen.
var mouse = new THREE.Vector2();
var latestMouseProjection; // this is the latest projection of the mouse on object (i.e. intersection with ray)
var hoveredObj; // this objects is hovered at the moment
// tooltip will not appear immediately. If object was hovered shortly,
// - the timer will be canceled and tooltip will not appear at all.
var tooltipDisplayTimeout;
// This will move tooltip to the current mouse position and show it by timer.
function showTooltip() {
var divElement = $("#tooltip");
if (divElement && latestMouseProjection) {
divElement.css({
display: "block",
opacity: 0.0
});
var canvasHalfWidth = renderer.domElement.offsetWidth / 2;
var canvasHalfHeight = renderer.domElement.offsetHeight / 2;
var tooltipPosition = latestMouseProjection.clone().project(camera);
tooltipPosition.x = (tooltipPosition.x * canvasHalfWidth) + canvasHalfWidth + renderer.domElement.offsetLeft;
tooltipPosition.y = -(tooltipPosition.y * canvasHalfHeight) + canvasHalfHeight + renderer.domElement.offsetTop;
var tootipWidth = divElement[0].offsetWidth;
var tootipHeight = divElement[0].offsetHeight;
divElement.css({
left: `${tooltipPosition.x - tootipWidth/2}px`,
top: `${tooltipPosition.y - tootipHeight - 5}px`
});
// var position = new THREE.Vector3();
// var quaternion = new THREE.Quaternion();
// var scale = new THREE.Vector3();
// hoveredObj.matrix.decompose(position, quaternion, scale);
divElement.text(hoveredObj.userData.tooltipText);
setTimeout(function() {
divElement.css({
opacity: 1.0
});
}, 25);
}
}
// This will immediately hide tooltip.
function hideTooltip() {
var divElement = $("#tooltip");
if (divElement) {
divElement.css({
display: "none"
});
}
}
// Following two functions will convert mouse coordinates
// from screen to three.js system (where [0,0] is in the middle of the screen)
function updateMouseCoords(event, coordsObj) {
coordsObj.x = ((event.clientX - renderer.domElement.offsetLeft + 0.5) / window.innerWidth) * 2 - 1;
coordsObj.y = -((event.clientY - renderer.domElement.offsetTop + 0.5) / window.innerHeight) * 2 + 1;
}
function handleManipulationUpdate() {
raycaster.setFromCamera(mouse, camera); {
var intersects = raycaster.intersectObjects(tooltipEnabledObjects);
if (intersects.length > 0) {
latestMouseProjection = intersects[0].point;
hoveredObj = intersects[0].object;
}
}
if (tooltipDisplayTimeout || !latestMouseProjection) {
clearTimeout(tooltipDisplayTimeout);
tooltipDisplayTimeout = undefined;
hideTooltip();
}
if (!tooltipDisplayTimeout && latestMouseProjection) {
tooltipDisplayTimeout = setTimeout(function() {
tooltipDisplayTimeout = undefined;
showTooltip();
}, 330);
}
}
function onMouseMove(event) {
updateMouseCoords(event, mouse);
latestMouseProjection = undefined;
hoveredObj = undefined;
handleManipulationUpdate();
}
window.addEventListener('mousemove', onMouseMove, false);
animate();
Replace:
var vector = new THREE.Vector3( mouse.x, mouse.y, 0 );
by:
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );

Resources