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.
Related
I want something like this:
https://drive.google.com/file/d/1v_4M59Ny93QwT52unfM9N_tfzo3xRAq1/view?usp=share_link
This is my code
<!DOCTYPE html>
<html>
<head>
<title>Three.js JPG Background with Custom Position</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"></script>
<script src="./js/tween.js/dist/tween.umd.js"></script>
<!-- Css -->
<style>
body {
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
}
</style>
</head>
<body>
<script>
// Initialize Three.js scene
var scene = new THREE.Scene();
var camera = new THREE.OrthographicCamera(window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 0.1,);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Load the Main BG texture
var mainBg = new THREE.TextureLoader().load("http://127.0.0.1:5500/img/main-bg.jpg");
// Create a full-screen plane to display the texture
var geometry1 = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
var material1 = new THREE.MeshBasicMaterial({ map: mainBg });
var plane1 = new THREE.Mesh(geometry1, material1);
plane1.position.set(0, 0, 0)
scene.add(plane1);
// Create a full-screen plane to display the texture
var frame1 = new THREE.TextureLoader().load("http://127.0.0.1:5500/img/frame1.png");
var geometry2 = new THREE.PlaneGeometry(90, 110);
var material2 = new THREE.MeshBasicMaterial({ map: frame1 });
var plane2 = new THREE.Mesh(geometry2, material2);
plane2.position.set(-190, 130, .1);
scene.add(plane2);
// Check if mouse is over the child asset
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var zoomed = false;
// Mouse move function
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects([plane2]);
if (intersects.length > 0 && !zoomed) {
zoomed = true;
camera.position.set(plane2.position.x, plane2.position.y, plane2.position.z);
camera.lookAt(plane2.position.x, plane2.position.y, plane2.position.z);
} else if (intersects.length === 0 && zoomed) {
zoomed = false;
camera.position.set(0, 0, 5);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
}
// Mouse move function calling on moving mouse
document.addEventListener('mousemove', onMouseMove, false);
window.addEventListener('mousedown', function () {
gsap.to(camera.position, {
z: 15,
duration: 15
});
})
// Render loop
function render() {
camera.position.z = 5;
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
</script>
</body>
</html>
assets:
frame1
and
main-bg
i made frame 1 on the center of the screen by hovering it but cannot make camera to zoom in on it. I tried tween but I think there is something wrong in the basics of my code
Sorry i am new to this and this is my first threejs project
You can apply a mousemove event on the canvas and use event.clientX or event.clientY to read live mouse pointer values. These values can then be used within the the listener to update values of camera.position.z to change zoom.
How can I have an ThreeJS object rotate on an axis that is at angle.
Like from this codepen I made, the left square rotates on its center and moved up when you click on the "rotate up" button.
I want the left cure rotate along the red axis in the image attached and move along on that axis when click on the "Rotate 45 degree" button. I can make up move along the axis, but the rotation is wrong. How can I make the cute rotate along the red axis?
****EDIT:
PS: Sorry I cannot get the snippet working here. Please use the codepen link for the demo.
https://codepen.io/eforblue/pen/MWpYggY
var upTl = gsap.timeline();
var degreeTl = gsap.timeline();
var width = window.innerWidth;
var height = window.innerHeight;
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var cubeGeometry = new THREE.CubeGeometry(100, 100, 100);
var cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);
var cube2 = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube2.position.set(200, 200, 0);
cube2.rotation.z = 45;
scene.add(cube2);
var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 10000);
camera.position.set(0, 500, 3000);
camera.lookAt(cube.position);
scene.add(camera);
var skyboxGeometry = new THREE.CubeGeometry(100, 100, 100);
var skyboxMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, side: THREE.BackSide });
var skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterial);
var pointLight = new THREE.PointLight(0xffffff);
pointLight.position.set(0, 300, 200);
scene.add(pointLight);
var clock = new THREE.Clock();
function render() {
requestAnimationFrame(render);
// cube.rotation.y -= clock.getDelta();
renderer.render(scene, camera);
}
render();
$('.up').on('click', () => {
console.log('click')
upTl.to( cube.rotation, 1, { y: Math.PI * 3 }, 0 )
.to( cube.position, 1, { y: cube.position.y + 400 }, 0 )
})
$('.degree').on('click', () => {
degreeTl.to( cube2.rotation, 1, { y: Math.PI * 3 }, 0 )
.to( cube2.position, 1, { x: cube2.position.x + 300, y:cube2.position.y + 300 }, 0 )
});
canvas {
position: fixed;
top: 0;
left: 0;
}
button {
position:relative;
z-index: 99;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="./main.js"></script>
<button class="up">rotate up</button>
<button class="degree">rotate 45deg</button>
In your case it's easier to use quaternions to rotate around your red axis, you can use the following function and define your rotation axis in the "axis" parameter.
static rotateAroundWorldAxis(mesh, point, axis, angle) {
const q = new Quaternion();
q.setFromAxisAngle(axis, angle);
mesh.applyQuaternion(q);
mesh.position.sub(point);
mesh.position.applyQuaternion(q);
mesh.position.add(point);
return mesh;
}
I have an existing project, a model of the Solar system, where planets and moons are labeled with text. To create the labels I used sprites (SpriteMaterial) and that worked well. (Below there is the result - you can zoom in to have a closer look at labels)
As you can see, labels are rendered nicely next to their parents, and also scaling is relative to the scaling of their parents.
var camera, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, .1, 2000);
camera.position.z = 3;
scene = new THREE.Scene();
var object;
var label;
var ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
scene.add(ambientLight);
var pointLight = new THREE.PointLight(0xffffff, 0.8);
camera.add(pointLight);
scene.add(camera);
var map = new THREE.TextureLoader().load('https://threejs.org/examples/textures/UV_Grid_Sm.jpg');
map.wrapS = map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 16;
var material = new THREE.MeshPhongMaterial({
map: map,
side: THREE.DoubleSide
});
//
sun = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
sun.position.set(0, 0, 0);
label = makeTextSprite("Sun");
sun.add(label)
scene.add(sun);
ratioOfJupiterToSun = 0.1
jupiter = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
jupiter.position.set(1.5, 0, 0);
jupiter.scale.set(ratioOfJupiterToSun, ratioOfJupiterToSun, ratioOfJupiterToSun)
label = makeTextSprite("Jupiter");
jupiter.add(label)
sun.add(jupiter);
ratioOfGanymedeToJupiter = 0.1
ganymede = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
ganymede.scale.set(ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter)
ganymede.position.set(4, 0, 0);
label = makeTextSprite("Ganymede");
ganymede.add(label)
jupiter.add(ganymede);
//
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minDistance = 1.7
controls.maxDistance = 3
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
camera.lookAt(scene.position);
renderer.render(scene, camera);
}
function makeTextSprite(message, centerX = 1.5, centerY = 1.7) {
var canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
var ctx = canvas.getContext("2d");
ctx.font = "38pt Arial";
ctx.fillStyle = "white";
ctx.textAlign = "left";
ctx.fillText(message, 0, 38);
var tex = new THREE.Texture(canvas);
tex.needsUpdate = true;
var spriteMat = new THREE.SpriteMaterial({
map: tex
});
var sprite = new THREE.Sprite(spriteMat);
sprite.center.set(centerX, centerY);
return sprite;
}
body {
color: #eee;
font-family: Monospace;
font-size: 13px;
text-align: center;
background-color: #000;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
}
a {
color: #0080ff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
The problem with sprites is that they use raster graphics for labeling. Sometimes the quality is not sufficient.
That's why I would like to reproduce the same effect with THREE.CSS2DRenderer. Unfortunately the best approximation I achieved is this
var camera, scene, renderer, labelRenderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, .1, 2000);
camera.position.z = 3;
scene = new THREE.Scene();
var object;
var label;
var ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
scene.add(ambientLight);
var pointLight = new THREE.PointLight(0xffffff, 0.8);
camera.add(pointLight);
scene.add(camera);
var map = new THREE.TextureLoader().load('https://threejs.org/examples/textures/UV_Grid_Sm.jpg');
map.wrapS = map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 16;
var material = new THREE.MeshPhongMaterial({
map: map,
side: THREE.DoubleSide
});
//
sun = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
sun.position.set(0, 0, 0);
label = makeTextLabel("Sun");
sun.add(label)
scene.add(sun);
ratioOfJupiterToSun = 0.1
jupiter = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
jupiter.position.set(1.5, 0, 0);
jupiter.scale.set(ratioOfJupiterToSun, ratioOfJupiterToSun, ratioOfJupiterToSun)
label = makeTextLabel("Jupiter");
jupiter.add(label)
sun.add(jupiter);
ratioOfGanymedeToJupiter = 0.1
ganymede = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
ganymede.scale.set(ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter)
ganymede.position.set(4, 0, 0);
label = makeTextLabel("Ganymede");
ganymede.add(label)
jupiter.add(ganymede);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minDistance = 2
controls.maxDistance = 3
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
var timer = Date.now() * 0.0005;
// camera.position.x = Math.sin(timer) * 400;
// camera.position.z = Math.cos(timer) * 400;
camera.lookAt(scene.position);
scene.traverse(function(object) {
if (object.isMesh === true) {
//object.rotation.x = timer;
//object.rotation.z = timer;
}
});
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.style.marginTop = '1.7em';
text.style.marginLeft = '-4em';
text.className = 'label';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
color: #eee;
font-family: Monospace;
font-size: 13px;
text-align: center;
background-color: #000;
margin: 0px;
padding: 0px;
overflow: hidden;
}
a {
color: #0080ff;
}
.label {
color: #FFF;
font-family: sans-serif;
font-size: 44px;
padding: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
As you can see it works partially: labels respect their parents' coordinate system (world matrix). That's why their position is relative. But scale isn't. All labels are of the same size.
Also I would like the labels to be slightly shifted to the bottom left, like I had it done with sprites. But the best way I could find to do it is:
text.style.marginTop = '3em';
text.style.marginLeft = '-4em';
and this obviously also doesn't take parents' scale into account.
Do you have any idea on how to make it resembling the original?
Code for the two examples at jsfiddle:
sprites (jsfiddle)
CSS2DRenderer (jsfiddle)
r. 98
Edit: hiding snippets to make the post more readable
It's not a bug to report. The problem is that you're using CSS2DRenderer, but it sounds that you're looking for behavior that's provided by CSS3DRenderer. You can see it in action in the examples section and how it behaves when you zoom in/out.
You can read more about it in the docs. Keep in mind that you'll also have to create CSS3DObjects instead of 2D. Then you can position them wherever you want just like any other 3dObject: .position.set(x, y, z);, and scale.set(x, y, z);
Update:
If you must use CSS2D, you could perform your own scaling by calculating the distance from each label to the camera.
// Create a Vec3 that holds the label's position
var start = new Vector3();
start.copy(label.position);
// Create a var that holds the distance to the camera
var dist = start.distanceTo(camera.position);
// Apply falloff, then turn that into the label's scale
var size = 1 / dist;
Now you can use size to scale the label, either via CSS transform: scale(x); or otherwise.
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 );
I am trying to create a simple image sequence using Three js and CSS3D Renderer. To create this sequence, I am referring to the CSS3D Renderer example http://www.mrdoob.com/lab/javascript/threejs/css3d/periodictable/ by mrdoob. However instead of using Divs with background color and random opacity, I am using large HD images and stacking them all in same line.
I have been able to achieve this with help of above mentioned example and can be checked here http://gauravsingh.name/webgl/
This is the JavaScript code I am using to create demo
var table = [
"img/1.jpg", "This is a test heading", "", 1, 1,
"img/2.jpg", "This is a test heading -1", "", 2, 1,
"img/3.jpg", "", "", 3, 1,
"img/4.png", "", "", 4, 1,
"img/5.jpg", "", "", 5, 1,
"img/6.jpg", "", "", 6, 1,
"img/7.jpg", "This is a test heading - 2", "", 7, 1];
var camera, scene, renderer;
var controls;
var objects = [];
var targets = {
table: []
};
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.z = 2000;
scene = new THREE.Scene();
// table
for (var i = 0; i < table.length; i += 5) {
var element = document.createElement('div');
element.className = 'element';
element.style.background = 'url("' + table[i] + '")';
var object = new THREE.CSS3DObject(element);
scene.add(object);
objects.push(object);
var object = new THREE.Object3D();
object.position.x = (table[i + 3] * 1700) - 4540;
object.position.y = -(table[i + 4] * 200) + 300;
targets.table.push(object);
}
//
renderer = new THREE.CSS3DRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.domElement.style.position = 'absolute';
document.getElementById('container').appendChild(renderer.domElement);
//
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 0.5;
controls.addEventListener('change', render);
transform(targets.table, 5000);
//
window.addEventListener('resize', onWindowResize, false);
}
function transform(targets, duration) {
TWEEN.removeAll();
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
var target = targets[i];
new TWEEN.Tween(object.position)
.to({
x: target.position.x,
y: target.position.y,
z: target.position.z
}, /*Math.random() * duration */ + duration)
.easing(TWEEN.Easing.Linear.None)
.start();
new TWEEN.Tween(object.rotation)
.to({
x: target.rotation.x,
y: target.rotation.y,
z: target.rotation.z
}, Math.random() * duration + duration)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
new TWEEN.Tween(this)
.to({}, duration * 2)
.onUpdate(render)
.start();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
controls.update();
}
function render() {
renderer.render(scene, camera);
}
Here is the CSS:
html, body {
height: 100%;
}
body {
background-color: #000000;
margin: 0;
font-family: Arial;
overflow: hidden;
}
.element {
width: 1600px;
height: 1280px;
box-shadow: 0px 0px 20px rgba(0, 255, 255, 0.5);
border: 1px solid rgba(127, 255, 255, 0.25);
cursor: default;
-webkit-box-reflect: below 0px -webkit-linear-gradient(top, transparent, transparent 0%, black 400%);
}
Problem: I have included 7 images(1600x1280px) in the code and they are present in the DOM but when the page first loads the images on the left hand part of the screen get chopped of. Trying zoom-in and tilt to see the chopped images. Also sometimes you will notice that the reflection of the extreme left image is very sharp compared to all other images.
This is strange behavior because as per my understanding I should have been able to see all images in view. I have tried playing around with camera properties to resolve this problem but no luck till now.
I will be grateful if anyone can explain me reason for this behavior and guide me to correct pointers to solve it.
Update
This problem seems to be chrome specific as I found it working well on firefox and IE. Although on firefox animations are little jerky but frames are not getting chopped.