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;
Related
Here I'm trying to capture the element Id of each image when the button clicked. Say for every image snapshot I have to capture a id for each image. I can able to print the image on console but I'm stuck in creating id for each image.Thanks in advance. Here's the fiddle https://jsfiddle.net/bg17oja3/1/
<!DOCTYPE html>
<html>
<head>
<title>Snapshot Three JS</title>
</head>
<body>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
<script type="text/javascript">
var camera, scene, renderer;
var mesh;
var strDownloadMime = "image/octet-stream";
init();
animate();
function init() {
var saveLink = document.createElement('div');
saveLink.style.position = 'absolute';
saveLink.style.top = '10px';
saveLink.style.width = '100%';
saveLink.style.background = '#FFFFFF';
saveLink.style.textAlign = 'center';
saveLink.innerHTML =
'<button href="#" id="clickButton">Save Frame</button>';
document.body.appendChild(saveLink);
// document.getElementById("saveLink").addEventListener('click', saveAsImage);
renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true,
captureId
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
scene = new THREE.Scene();
var geometry = new THREE.BoxGeometry(200, 200, 200);
var captureId = document.getElementById('image');
// var renderer = new Three.WebGLRenderer({captureId});
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00
});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
console.log(captureId);
window.addEventListener('resize', onWindowResize, false);
document.getElementById('clickButton').addEventListener('click', buttonClick);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
function buttonClick() {
renderer.render(scene, camera);
document.getElementById('image').src = renderer.domElement.toDataURL();
console.log(image);
}
</script>
<style type="text/css">
html,
body {
padding: 0px;
margin: 0px;
}
canvas {
width: 100%;
height: 100%
}
#clickButton {
position: absolute;
top: 10px;
left: 10px;
z-index: 2;
cursor: pointer;
}
#image {
position: absolute;
top: 10px;
right: 10px;
max-width: 150px;
width: 150px;
height: 90px;
z-index: 3;
border: 1px solid #fff;
}
</style>
<img id="image" src="" />
</body>
I have forked your fiddle to demonstrate the solution: https://jsfiddle.net/mmalex/o0k2fu8t/
How to test it:
Take a snapshot,
Click the image to return to serialized scene.
The idea is:
serialize the whole scene when you take screenshot,
set data attribute on image,
deserialize scene from image data attribute whenever you need it.
function buttonClick()
{
renderer.render(scene, camera);
let imageEl = document.getElementById('image');
imageEl.src = renderer.domElement.toDataURL();
// convert scene to json format
let sceneJson = scene.toJSON();
// don't forget to make it text
let sceneJsonText = JSON.stringify(sceneJson);
// set serialized scene to image data-scene attribute
imageEl.setAttribute('data-scene', sceneJsonText);
}
function imageClick()
{
let imageEl = document.getElementById('image');
// access serialized scene on image click
var sceneJsonText = imageEl.getAttribute('data-scene');
// parse it into json object
let sceneJson = JSON.parse(sceneJsonText)
// convert json object into THREE.Scene
var loader = new THREE.ObjectLoader();
scene = loader.parse(sceneJson);
// don't forget to update referenced, as deserialized scene will stop rotation if you don't do that
mesh = scene.children[0];
}
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.
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.
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.
This is my first time attempting to import an obj into three.js and use it. I am attempting to use the object loader that is included in the three master build.
The issue seems to be in how the object file loads, as it errors about something being undefined in some kind of loop. Trouble is, I don't know what is undefined that shouldn't be. To create my obj file I used https://developer.cdn.mozilla.net/media/uploads/demos/w/i/wizgrav/f232e401f674086f9aae99b474e27ed0/running-image-triang_1372626220_demo_package/index.html
What am I doing wrong? This is my first time using Three, so I tried to make as few changes as possible (only to where the files are in my directory structure).
Since this could be the object, it is at:
http://geevideoproduction.com/geeTop3D.obj
<!DOCTYPE html>
<html lang="en"><head>
<title>three.js webgl - loaders - OBJ loader</title>
<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: 0px;
overflow: hidden;
}
#info {
color: #fff;
position: absolute;
top: 10px;
width: 100%;
text-align: center;
z-index: 100;
display:block;
}
#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
</style>
</head>
<body>
<script src="./jscript/three.min.js"></script>
<script src="./jscript/js/loaders/OBJLoader.js"></script>
<script src="./jscript/js/Detector.js"></script>
<script src="./jscript/js/libs/stats.min.js"></script>
<script>
var container, stats;
var camera, scene, renderer;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 100;
// scene
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight( 0x101030 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffeedd );
directionalLight.position.set( 0, 0, 1 );
scene.add( directionalLight );
// texture
var manager = new THREE.LoadingManager();
manager.onProgress = function ( item, loaded, total ) {
console.log( item, loaded, total );
};
var texture = new THREE.Texture();
var loader = new THREE.ImageLoader( manager );
loader.load( '/PlayFiles/geeVideoProduction/images/GeeTop.png', function ( image ) {
texture.image = image;
texture.needsUpdate = true;
} );
// model
var loader = new THREE.OBJLoader( manager );
loader.load( '/PlayFiles/geeVideoProduction/images/geeTop3D.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.map = texture;
}
} );
object.position.y = - 80;
scene.add( object );
} );
//
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove( event ) {
mouseX = ( event.clientX - windowHalfX ) / 2;
mouseY = ( event.clientY - windowHalfY ) / 2;
}
//
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
camera.position.x += ( mouseX - camera.position.x ) * .05;
camera.position.y += ( - mouseY - camera.position.y ) * .05;
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
</script><div><canvas width="1244" height="500"></canvas></div>
</body></html>