Three.CurvePath and custom markers - javascript

I am in the process of creating a game using Three.JS and I have modeled and successfully imported a city created in Sketchup. I now need to dynamically add some "follow me" arrows (as per the yellow arrows in the mockup below). I believe I might need to use Three.CurvePath to achieve this but am not sure if this is the best approach - do I need to manually model the path and calculate the tangent for each of the arrow objects so they point naturally around corners (as per left turn in the mockup)?
Hope this makes sense!

I might have a solution.
Haven't used three.js in a while so not sure if this is the most elegant solution.
I started with the Shapes Example since it shows:
How to build a path procedurally (using commands that also handle curves)
How to extract points from such a path
So I've split the problem into:
Generating the path
Traversing the path and interpolation (position and rotation)
Generating the path
I've reused the rounded rectangle definition which looks similar to a part of your screenshot.
var roundedRectShape = new THREE.Shape();
( function roundedRect( ctx, x, y, width, height, radius ){
ctx.moveTo( x, y + radius );
ctx.lineTo( x, y + height - radius );
ctx.quadraticCurveTo( x, y + height, x + radius, y + height );
ctx.lineTo( x + width - radius, y + height) ;
ctx.quadraticCurveTo( x + width, y + height, x + width, y + height - radius );
ctx.lineTo( x + width, y + radius );
ctx.quadraticCurveTo( x + width, y, x + width - radius, y );
ctx.lineTo( x + radius, y );
ctx.quadraticCurveTo( x, y, x, y + radius );
} )( roundedRectShape, 0, 0, 200, 200, 20 );
Your path might not be a rounded rectangle, but the available types of curve functions(quadraticCurveTo,bezierCurveTo,splineThru) are really useful.
Another idea that comes to mind is using a Ruby script to export path coordinates from Sketchup to three.js. Either you write that from scratch or use existing scripts. Here's one
easily found on google.
Traversing the path
Luckily three.js already implements this through Path's getPoint(t) where t is a number from 0.0 to 1.0 representing the traversal on the path. So getting the position is trivial as getting the next interpolated position on the path. Then it's just a matter of using Math.atan2() to get the rotation:
t = (t + s)%1.0;//increment t while maintaining it between 0.0 and 1.0
var p = path.getPoint(t);//point at t
var pn = path.getPoint((t+s)%1.0);//point at next t iteration
if(p != null && pn != null){
//move to current position
arrow.position.x = p.x;
arrow.position.y = p.y;
//get orientation based on next position
arrow.rotation.z = Math.atan2(pn.y-p.y,pn.x-p.x);
}
In conclusion bellow is a basic example (using a cube instead of an arrow shape) to illustrate generating and traversing a path in three.js based on the shapes example:
<!DOCTYPE html>
<html lang="en">
<head>
<title>path interpolation</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: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="debug" style="position:absolute; left:100px"></canvas>
<script src="../build/three.min.js"></script>
<script src="js/libs/stats.min.js"></script>
<script>
var container, stats;
var camera, scene, renderer;
var text, plane;
var targetRotation = 0;
var targetRotationOnMouseDown = 0;
var mouseX = 0;
var mouseXOnMouseDown = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
var t = 0.0;//traversal on path
var s = 0.001;//speed of traversal
var arrow;//mesh to move/rotate on path
var path;//Path object to traverse
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 150, 500 );
scene = new THREE.Scene();
parent = new THREE.Object3D();
parent.position.y = 50;
scene.add( parent );
arrow = new THREE.Mesh( new THREE.CubeGeometry(20,10,10),new THREE.MeshBasicMaterial({color: 0x009900}));
parent.add(arrow);
//this is helpful as a visual aid but not crucial
function addShape( shape, extrudeSettings, color, x, y, z, rx, ry, rz, s ) {
var points = shape.createPointsGeometry();
var spacedPoints = shape.createSpacedPointsGeometry( 50 );
// transparent line from equidistance sampled points
var line = new THREE.Line( spacedPoints, new THREE.LineBasicMaterial( { color: color, opacity: 0.2 } ) );
line.rotation.set( rx, ry, rz );
parent.add( line );
// equidistance sampled points
var pgeo = spacedPoints.clone();
var particles2 = new THREE.ParticleSystem( pgeo, new THREE.ParticleBasicMaterial( { color: color, size: 2, opacity: 0.5 } ) );
particles2.rotation.set( rx, ry, rz );
parent.add( particles2 );
}
// Rounded rectangle
//generating the path and populating it is crucial tough
var roundedRectShape = new THREE.Shape();
( function roundedRect( ctx, x, y, width, height, radius ){
ctx.moveTo( x, y + radius );
ctx.lineTo( x, y + height - radius );
ctx.quadraticCurveTo( x, y + height, x + radius, y + height );
ctx.lineTo( x + width - radius, y + height) ;
ctx.quadraticCurveTo( x + width, y + height, x + width, y + height - radius );
ctx.lineTo( x + width, y + radius );
ctx.quadraticCurveTo( x + width, y, x + width - radius, y );
ctx.lineTo( x + radius, y );
ctx.quadraticCurveTo( x, y, x, y + radius );
} )( roundedRectShape, 0, 0, 200, 200, 20 );
path = roundedRectShape;
var extrudeSettings = { amount: 20 }; // bevelSegments: 2, steps: 2 , bevelSegments: 5, bevelSize: 8, bevelThickness:5
extrudeSettings.bevelEnabled = true;
extrudeSettings.bevelSegments = 2;
extrudeSettings.steps = 2;
addShape( roundedRectShape, extrudeSettings, 0x000000, -150, 150, 0, 0, 0, 0, 1 );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
document.addEventListener( 'touchmove', onDocumentTouchMove, 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 onDocumentMouseDown( event ) {
event.preventDefault();
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
document.addEventListener( 'mouseout', onDocumentMouseOut, false );
mouseXOnMouseDown = event.clientX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
function onDocumentMouseMove( event ) {
mouseX = event.clientX - windowHalfX;
targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
}
function onDocumentMouseUp( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}
function onDocumentMouseOut( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}
function onDocumentTouchStart( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
t = (t + s)%1.0;//increment t while maintaining it between 0.0 and 1.0
var p = path.getPoint(t);//point at t
var pn = path.getPoint((t+s)%1.0);//point at next t iteration
if(p != null && pn != null){
//move to current position
arrow.position.x = p.x;
arrow.position.y = p.y;
//get orientation based on next position
arrow.rotation.z = Math.atan2(pn.y-p.y,pn.x-p.x);
}
parent.rotation.y += ( targetRotation - parent.rotation.y ) * 0.05;
renderer.render( scene, camera );
}
</script>
</body>
</html>
Thought I'd add a runnable snippet straight on this page:
var container;
var camera, scene, renderer;
var text, plane;
var targetRotation = 0;
var targetRotationOnMouseDown = 0;
var mouseX = 0;
var mouseXOnMouseDown = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
var t = 0.0;//traversal on path
var s = 0.001;//speed of traversal
var arrows;//mesh to move/rotate on path
var path;//Path object to traverse
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 150, 500 );
scene = new THREE.Scene();
parent = new THREE.Object3D();
parent.position.y = 50;
scene.add( parent );
arrows = [];
for(var i = 0 ; i < 50; i++){
arrows[i] = new THREE.Mesh( new THREE.CubeGeometry(10,5,5),new THREE.MeshBasicMaterial({color: 0x009900}));
parent.add(arrows[i]);
}
//this is helpful as a visual aid but not crucial
function addShape( shape, extrudeSettings, color, x, y, z, rx, ry, rz, s ) {
var points = shape.createPointsGeometry();
var spacedPoints = shape.createSpacedPointsGeometry( 50 );
// transparent line from equidistance sampled points
var line = new THREE.Line( spacedPoints, new THREE.LineBasicMaterial( { color: color, opacity: 0.2 } ) );
line.rotation.set( rx, ry, rz );
parent.add( line );
// equidistance sampled points
var pgeo = spacedPoints.clone();
var particles2 = new THREE.ParticleSystem( pgeo, new THREE.ParticleBasicMaterial( { color: color, size: 2, opacity: 0.5 } ) );
particles2.rotation.set( rx, ry, rz );
parent.add( particles2 );
}
// Rounded rectangle
//generating the path and populating it is crucial tough
var roundedRectShape = new THREE.Shape();
( function roundedRect( ctx, x, y, width, height, radius ){
ctx.moveTo( x, y + radius );
ctx.lineTo( x, y + height - radius );
ctx.quadraticCurveTo( x, y + height, x + radius, y + height );
ctx.lineTo( x + width - radius, y + height) ;
ctx.quadraticCurveTo( x + width, y + height, x + width, y + height - radius );
ctx.lineTo( x + width, y + radius );
ctx.quadraticCurveTo( x + width, y, x + width - radius, y );
ctx.lineTo( x + radius, y );
ctx.quadraticCurveTo( x, y, x, y + radius );
} )( roundedRectShape, 0, 0, 200, 200, 20 );
path = roundedRectShape;
var extrudeSettings = { amount: 20 }; // bevelSegments: 2, steps: 2 , bevelSegments: 5, bevelSize: 8, bevelThickness:5
extrudeSettings.bevelEnabled = true;
extrudeSettings.bevelSegments = 2;
extrudeSettings.steps = 2;
addShape( roundedRectShape, extrudeSettings, 0x000000, -150, 150, 0, 0, 0, 0, 1 );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
document.addEventListener( 'touchmove', onDocumentTouchMove, 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 onDocumentMouseDown( event ) {
event.preventDefault();
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
document.addEventListener( 'mouseout', onDocumentMouseOut, false );
mouseXOnMouseDown = event.clientX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
function onDocumentMouseMove( event ) {
mouseX = event.clientX - windowHalfX;
targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
}
function onDocumentMouseUp( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}
function onDocumentMouseOut( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}
function onDocumentTouchStart( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
t = (t + s)%1.0;//increment t while maintaining it between 0.0 and 1.0 - could map mouse x position/window width for fun :)
for(var i = 0 ; i < 50; i++){//for each box
var ti = ((i/50.0)+t)%1.0;//compute the traversval including each box's own offset on the path
var p = path.getPoint(ti);//point at t
var pn = path.getPoint((ti+s)%1.0);//point at next t iteration
if(p != null && pn != null){
//move to current position
arrows[i].position.x = p.x;
arrows[i].position.y = p.y;
//get orientation based on next position
arrows[i].rotation.z = Math.atan2(pn.y-p.y,pn.x-p.x);
}
}
parent.rotation.y += ( targetRotation - parent.rotation.y ) * 0.05;
renderer.render( scene, camera );
}
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>

Related

three.js canvas, sphere turn or float right

i try out to get the sphere to turn right in a very slow tempo. here is my source code : i have created a new contaier with canvas inside, you can see the code. what i have to include that the sphere turn in a slow tempo? here I'm also i have my git repository on github: see the image inside https://github.com/SpaceG/SpaceSphere. here i have also, found another example, with a sphere: this roundball truns a float very slow in the same point, this is excat waht i need: http://rectangleworld.com/demos/DustySphere/DustySphere.html This turn left i need in my code below]. You can also download my repo or fork it and get fixxed my issue here.
Here is the Html Doc :
<!DOCTYPE html>
<html lang="en">
<head>
<title></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 {
background-color: #000000;
margin: 0px;
overflow: hidden;
}
a {
color:#0078ff;
}
</style>
<script src="../build/three.js"></script>
<script src="js/renderers/Projector.js"></script>
<script src="js/renderers/CanvasRenderer.js"></script>
</head>
<body>
The Javascript Code to make the Sphere possible:
<script>
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mouseX = 0, mouseY = 0,
windowHalfX = window.innerWidth / 4,
windowHalfY = window.innerHeight / 4,
SEPARATION = 600,
AMOUNTX = 10,
AMOUNTY = 10,
camera, scene, renderer;
init();
animate();
function init() {
var container, turnAngle, separation = 100, amountX = 50, amountY = 50,
particles, particle;
container = document.createElement('div');
container.setAttribute('class', 'space-id');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera( 275, SCREEN_WIDTH / SCREEN_HEIGHT, 5, 10000000 );
camera.position.z = 100;
scene = new THREE.Scene();
renderer = new THREE.CanvasRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
container.appendChild( renderer.domElement );
// particles
var PI2 = Math.PI * 2;
var material = new THREE.SpriteCanvasMaterial( {
color: 0xffffff,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
for ( var i = 0; i < 1000; i ++ ) {
particle = new THREE.Sprite( material );
particle.position.x = Math.random() * 2 - 1;
particle.position.y = Math.random() * 2 - 1;
particle.position.z = Math.random() * 2 - 1;
particle.position.normalize();
particle.position.multiplyScalar( Math.random() * 10 + 450 );
particle.scale.multiplyScalar( 2 );
scene.add( particle );
}
// lines
for (var i = 0; i < 300; i++) {
var geometry = new THREE.Geometry();
var vertex = new THREE.Vector3( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
vertex.normalize();
vertex.multiplyScalar( 450 );
geometry.vertices.push( vertex );
var vertex2 = vertex.clone();
vertex2.multiplyScalar( Math.random() * 0.3 + 1 );
geometry.vertices.push( vertex2 );
var line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0xffffff, opacity: Math.random() } ) );
scene.add( line );
}
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
document.addEventListener( 'touchmove', onDocumentTouchMove, false );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
windowHalfX = window.innerWidth / 4;
windowHalfY = window.innerHeight / 4;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
//
function onDocumentMouseMove(event) {
mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;
}
function onDocumentTouchStart( event ) {
if ( event.touches.length > 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
camera.position.x += ( mouseX - camera.position.x ) * .05;
camera.position.y += ( - mouseY + 200 - camera.position.y ) * .05;
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
</script>
</body>
</html>
Here is an Image about the Goal of the Project, that the round ball turn to left in slow tempo, are some one here give me tips? lets up the Good Work. Thanks lot.
If you just want the ball to spin like it does in the example, you can do the following:
// Declare these before the call to init();
var group = new THREE.Group();
var rotationSpeed = 0.001; // Increase/decrease to control speed of rotation.
And, instead of scene.add(particle);
you do group.add(particle);
then scene.add(group);
The same goes for the lines:
instead of scene.add(line);
do group.add(line); (here you don't need to add to the scene since you're adding to the group that is already in the scene.)
Then the key line that was missing in the render loop:
group.rotateY(rotationSpeed); // Rotates the ball with speed 'rotationSpeed'
Here is a fiddle of what it looks like with these changes:
JSFiddle

Magnet effect using particles in Three js

I'm using the following code based on one of the official Three.js examples:
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mouseX = 0, mouseY = 0,
windowHalfX = window.innerWidth / 2,
windowHalfY = window.innerHeight / 2,
SEPARATION = 200,
AMOUNTX = 10,
AMOUNTY = 10,
camera, scene, renderer, startx, starty, startz, currentx, currenty, currentz;
init();
animate();
function init() {
var container, separation = 100, amountX = 50, amountY = 50,
particles, particle;
container = document.createElement('div');
var currentDiv = document.getElementById("container");
currentDiv.appendChild(container);
$('#container > div').hide();
camera = new THREE.PerspectiveCamera( 75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000 );
camera.position.z = 1000;
scene = new THREE.Scene();
renderer = new THREE.CanvasRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
renderer.setClearColor( 0xe0e0e0, 1);
container.appendChild( renderer.domElement );
// particles
var PI2 = Math.PI * 2;
var material = new THREE.SpriteCanvasMaterial( {
color: 0x0000ee,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
group = new THREE.Group();
scene.add( group );
for ( var i = 0; i < 200; i ++ ) {
particle = new THREE.Sprite( material );
startx = particle.position.x = Math.random() * 2 - 1;
starty = particle.position.y = Math.random() * 2 - 1;
startz = particle.position.z = Math.random() * 2 - 1;
particle.position.normalize();
particle.position.multiplyScalar( Math.random() * 1 + 450 );
particle.scale.multiplyScalar( 2 );
group.add( particle );
}
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
document.addEventListener( 'touchmove', onDocumentTouchMove, 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;
mouseY = event.clientY - windowHalfY;
}
function onDocumentTouchStart( event ) {
if ( event.touches.length > 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
}
setTimeout( function(){
$('#container > div').fadeIn(3000);
}, 1000 );
function render() {
camera.position.x += ( mouseX - camera.position.x ) * .05;
camera.position.y += ( - mouseY + 20 - camera.position.y ) * .05;
camera.lookAt( scene.position );
for (var i = 0; i < group.children.length; i++) {
group.children[i].position.x += Math.random() * 2 - 1;
group.children[i].position.y += Math.random() * 2 - 1;
group.children[i].position.z += Math.random() * 2 - 1;
/*currentx = group.children[i].position.x;
currenty = group.children[i].position.y;
currentz = group.children[i].position.z;
if (Math.sqrt(mouseX*mouseX+currentx*currentx)<330 && Math.sqrt(mouseY*mouseY+currenty*currenty)<330) {
var boosterx = (mouseX==group.children[i].position.x) ? '' : 1;
var boostery = (mouseY==group.children[i].position.y) ? '' : 1;
group.children[i].position.x += boosterx;
group.children[i].position.y += boostery;*/
}
}
renderer.render( scene, camera );
}
Everything works fine but there's one effect that I can't get to work. I want the particles to react whenever cursor comes close after reaching certain point (sort of magnet-like effect - the closer the cursor is, the faster particles are going towards it). I know it may be difficult to achieve since there is some 3d/2d translation. I am noob when it comes to 3d canvas and js in general but something tells me it can be done. I tried to do it on my own and thats where the messy part (commented) at the end comes from. Thanks!

Making Geometries clickable Hyperlinks in Three.js (WebGl Renderer)

So I've been trying to make a spherical 360 panorama using three.js which implements clickable objects, which at the moment I would like to make hyperlinks.I've read many of the previous examples of raycasting and such, but have had no luck in getting the object to actually redirect me to the site. If someone could tell me where I'm going wrong in the code I'd greatly appreciate it.
I have a feeling the orbiting/panning function under "onDocumentMouseDown" is interfering with the raycasting? I'm still new to this and figuring it out.
<div id="container"></div>
<script src="three.min.js"></script>
<script>
var container, stats;
var camera, controls, scene, renderer;
var objects = [], plane;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2(),
offset = new THREE.Vector3();
var fov = 70,
texture_placeholder,
isUserInteracting = false,
onMouseDownMouseX = 0, onMouseDownMouseY = 0,
lon = 0, onMouseDownLon = 0,
lat = 0, onMouseDownLat = 0,
phi = 0, theta = 0;
init();
animate();
function init() {
var container, mesh1, sphere1, cube1;
container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 1, 1100 );
camera.target = new THREE.Vector3( 0, 0, 0 );
scene = new THREE.Scene();
mesh1 = new THREE.Mesh( new THREE.SphereGeometry( 500, 60, 40 ), new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'spherical_map_small.jpg' ), transparent: true} ) );
mesh1.scale.x = -1;
scene.add( mesh1 );
meshMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff});
var sphere1 = new THREE.Mesh( new THREE.SphereGeometry( 2.5,20,20 ), meshMaterial );
sphere1.position.set( 50, 10, 0 );
scene.add( sphere1 );
sphere1.userData = { URL:"http://www.google.com"};
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
document.addEventListener( 'DOMMouseScroll', onDocumentMouseWheel, false);
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseDown( event ) {
event.preventDefault();
isUserInteracting = true;
onPointerDownPointerX = event.clientX;
onPointerDownPointerY = event.clientY;
onPointerDownLon = lon;
onPointerDownLat = lat;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( sphere1 );
if ( intersects.length > 0 ) {
controls.enabled = true;
SELECTED = intersects[ 0 ].sphere1;
var intersects = raycaster.intersectObject( sphere1 );
if ( intersects.length > 0 ) {
window.open(intersects[0].object.userData.URL);
}
}
}
function onDocumentMouseMove( event ) {
if ( isUserInteracting ) {
lon = ( onPointerDownPointerX - event.clientX ) * 0.1 + onPointerDownLon;
lat = ( event.clientY - onPointerDownPointerY ) * 0.1 + onPointerDownLat;
}
}
function onDocumentMouseUp( event ) {
isUserInteracting = false;
}
function onDocumentMouseWheel( event ) {
isUserInteracting = false;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
lat = Math.max( - 85, Math.min( 85, lat ) );
phi = THREE.Math.degToRad( 90 - lat );
theta = THREE.Math.degToRad( lon );
camera.target.x = 500 * Math.sin( phi ) * Math.cos( theta );
camera.target.y = 500 * Math.cos( phi );
camera.target.z = 500 * Math.sin( phi ) * Math.sin( theta );
camera.lookAt( camera.target );
renderer.render( scene, camera );
}
</script>
Looking at your code I notice that you create var mouse = new THREE.Vector2(), then you don't set its values at any point in the supplied code. Then in onDocumentMouseDown() you cast a ray into the scene with undefined mouse coordinates raycaster.setFromCamera( mouse, camera ); As var mouse has not been set it is very likely that that is the reason you are not getting the navigation to start.
What you need to do is get the normalised screen coordiantes of the mouse and pass that into the raycaster.setFromCamera I can not quite remenber if the screen is one or two units but something like
mouse.x = (event.clientX / window.innerWidth); // normalise the mouse screen pos
mouse.y = (event.clientY / window.innerHeight); // same
mouse.x *= 2; // 0 is the center. -1 is left and 1 is right
mouse.y -= 1; // center
mouse.y *= -2; // Think this is upside down If it does not work try positive 2
mouse.y += 1; // center
if it does not work try switching mouse.y the otherway around;
mouse.y *= 2; // remove the -2 and put in 2
mouse.y -= 1; // remove the += and put in -=
What I find very handy when messing about in 3D is to have a spare debug object in the scene. Something simple like a box or sphere. Use it to show a point on the raycaster's ray.
Something like
// creat a box
var box... blah blah box creation code/
boxP = new vector3(); // position of debug object
// position it halfway in the raycasters range
boxP.x = camera.x + rayCaster.ray.x* ((rayCaster.near+rayCaster.far)/2);
boxP.y = camera.y + rayCaster.ray.y* ((rayCaster.near+rayCaster.far)/2);
boxP.z = camera.z + rayCaster.ray.z* ((rayCaster.near+rayCaster.far)/2);
box.position.set(boxP.x,boxP.y,boxP.z);
Now with luck you should see where you clicks are going.
Also, I am not sure but if you are looking at the sphere from the inside you may have to set the material to doubleSided (I can't see it in your code) as the raycaster ignores faces with normals pointing away. Or try reversing the direction of each polygon.
That's about all I can suggest at the moment. Hope you find the problem.

How to position sprites in Three.js?

I'm currently using PointCloud to generate a particle system but within that I would like one single sprite that floats in the position of my indication. When I tried using this three.js example: http://threejs.org/examples/#webgl_sprites I found that the Orthographic Camera limited my ability to still zoom about.
var container, stats;
var camera, scene, renderer, particles, geometry, materials =[], i, h, color, sprite, size;
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( 55, window.innerWidth / window.innerHeight, 2, 2000 );
camera.position.z = 1000;
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2( 0x000000, 0.001 );
geometry = new THREE.Geometry();
sprite = THREE.ImageUtils.loadTexture( "disc.png" );
for ( i = 0; i < 5000; i ++ ) {
var vertex = new THREE.Vector3();
vertex.x = 2000 * Math.random() - 1000;
vertex.y = 2000 * Math.random() - 1000;
vertex.z = 2000 * Math.random() - 1000;
geometry.vertices.push( vertex );
}
// size = Math.random() * 10;
material = new THREE.PointCloudMaterial( { size: 5, sizeAttenuation: false, map: sprite, alphaTest: 0.5, transparent: true } );
particles = new THREE.PointCloud( geometry, material );
scene.add( particles );
var map2 = THREE.ImageUtils.loadTexture( "astronaut.png" );
var material2 = new THREE.SpriteMaterial( { map: map2, color: 0xffffff, fog: true } );
var sprite2 = new THREE.Sprite( material2 );
sprite2.position.x = 0;
sprite2.position.y = 0;
sprite2.position.z = 498;
scene.add( sprite2 );
//
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
//
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
document.addEventListener( 'touchmove', onDocumentTouchMove, 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;
mouseY = event.clientY - windowHalfY;
}
function onDocumentTouchStart( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length == 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
mouseY = event.touches[ 0 ].pageY - windowHalfY;
}
}
//
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
var time = Date.now() * 0.00005;
camera.position.x += ( mouseX - camera.position.x ) * 0.05;
camera.position.y += ( - mouseY - camera.position.y ) * 0.05;
camera.lookAt( scene.position );
h = ( 360 * ( 1.0 + time ) % 360 ) / 360;
renderer.render( scene, camera );
}
My attempt at solving it was:
var map2 = THREE.ImageUtils.loadTexture( "astronaut.png" );
var material2 = new THREE.SpriteMaterial( { map: map2, color: 0xffffff, fog: true } );
var sprite2 = new THREE.Sprite( material2 );
sprite2.position.x = 0;
sprite2.position.y = 0;
sprite2.position.z = 498;
scene.add( sprite2 );
Right now the sprite is in the center of the screen when I first load but instantly disappears when I begin to move the camera. Ideally, I would like the astronaut.png sprite to move with the other particles but if this is difficult, having him always fixed to the center of the screen would work fine as well.
Resolved this on my own. I created a second THREE.Geometry and THREE.Vector3 an positioned it with vertices.
geometry2 = new THREE.Geometry();
var vertex2 = new THREE.Vector3(0, 0, -50);
geometry2.vertices.push( vertex2 );
var material2 = new THREE.PointCloudMaterial( { size: 100, sizeAttenuation: false, map: map2, alphaTest: 0.5, transparent: true } );
particles2 = new THREE.PointCloud( geometry2, material2 );
scene.add( particles2 );
It seems to me that your values for mouse position would be way too high for camera positions. OpenGL works on the (-1,1) (1,1) (1,-1) (-1,-1) bounding rectangle as a unit. Pixels for your cursor position are in screen pixels like 350,720 etc.
When you increment by the half distance, your numbers are still too large. So here you have to divide by your width/height:
camera.position.x += ( mouseX / window.innerWidth- camera.position.x ) * 0.05;
camera.position.y += ( - mouseY /window.innerHeight- camera.position.y ) * 0.05;
assuming your GL portal is the same size as the window.

three.js - zoom in/out complete tube geometry

I've created a tube geometry with data of 200 points loaded from external javascript file in JSON format. Please find the below code.
<!DOCTYPE html>
<html lang="en">
<head>
<title>3d Model using HTML5 and three.js</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: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<input type="button" value="plot" onClick="return plotPath();" />
<script src="three.min.js" type="text/javascript"></script>
<script src="Curve.js" type="text/javascript"></script>
<script src="TubeGeometry.js" type="text/javascript"></script>
<script src="Stats.js" type="text/javascript"></script>
<script src="Detector.js" type="text/javascript"></script>
<script src="path.js" type="text/javascript"></script>
<script>
// variables
var container, stats;
var camera, scene, renderer, controls, stats;
var text, plane, tube, tubeMesh, parent;
var targetRotation = 0;
var targetRotationOnMouseDown = 0;
var mouseX = 0;
var mouseXOnMouseDown = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var radius = 600;
var theta = 0;
var PI2 = Math.PI * 2;
function plotPath()
{
var obj = getPath();
var segments = 50;
var closed = false;
var debug = true;
var radiusSegments = 12;
var tube;
var points = [];
var x=0,y=0,z=0;
for(var i=0; i<obj.path.length; i++)
{
console.log(obj.path[i].point);
points.push(obj.path[i].point);
extrudePath = new THREE.SplineCurve3(points);
extrudePath.dynamic = true;
tube = new THREE.TubeGeometry(extrudePath, segments, 2, radiusSegments, closed, debug);
tube.dynamic = true;
tubeMesh = new THREE.Mesh(tube ,new THREE.MeshBasicMaterial({
color: 0x000000, side: THREE.DoubleSide,
opacity: 0.5, transparent: true, wireframe: true}));
tubeMesh.__dirtyVertices = true;
tubeMesh.dynamic = true;
parent = new THREE.Object3D();
parent.position.y = 100;
if ( tube.debug ) tubeMesh.add( tube.debug );
parent.add( tubeMesh );
}
scene.add( tubeMesh );
scene.add(parent);
animate();
}
init();
function init(){
// container
container = document.createElement( 'div' );
document.body.appendChild( container );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setClearColorHex(0xEEEEEE, 1.0);
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.clear();
container.appendChild( renderer.domElement );
// camera
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set(-100,75,75);
// scene
scene = new THREE.Scene();
camera.lookAt(scene.position);
// light
scene.add( new THREE.AmbientLight( 0x404040 ) );
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 0, 1, 0 );
scene.add( light );
// CONTROLS
controls = new THREE.TrackballControls( camera );
// Grid
geometry = new THREE.Geometry();
geometry.vertices.push( new THREE.Vector3( - 500, 0, 0 ) );
geometry.vertices.push( new THREE.Vector3( 500, 0, 0 ) );
for ( var i = 0; i <= 20; i ++ ) {
line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.2 } ) );
line.position.z = ( i * 50 ) - 500;
scene.add( line );
line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.2 } ) );
line.position.x = ( i * 50 ) - 500;
line.rotation.y = 90 * Math.PI / 180;
scene.add( line );
}
// projector
projector = new THREE.Projector();
plotPath();
// stats
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'mouseover', onDocumentMouseOver, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
document.addEventListener( 'touchmove', onDocumentTouchMove, false );
window.addEventListener('DOMMouseScroll', mousewheel, false);
window.addEventListener('mousewheel', mousewheel, false);
window.addEventListener( 'resize', onWindowResize, false );
}
function mousewheel(event) {
var fovMAX = 160;
var fovMIN = 1;
camera.fov -= event.wheelDeltaY * 0.05;
camera.fov = Math.max( Math.min( camera.fov, fovMAX ), fovMIN );
camera.projectionMatrix = new THREE.Matrix4().makePerspective(camera.fov, window.innerWidth / window.innerHeight, camera.near, camera.far);
}
function onWindowResize() {
camera.left = window.innerWidth / - 2;
camera.right = window.innerWidth / 2;
camera.top = window.innerHeight / 2;
camera.bottom = window.innerHeight / - 2;
camera.aspect = window.innerWidth / window.innerHeight;
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseDown( event ) {
event.preventDefault();
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
document.addEventListener( 'mouseout', onDocumentMouseOut, false );
mouseXOnMouseDown = event.clientX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
function onDocumentMouseMove( event ) {
mouseX = event.clientX - windowHalfX;
targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
}
function onDocumentMouseUp( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}
function onDocumentMouseOut( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}
function onDocumentMouseOver( event ) {
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
}
function onDocumentTouchStart( event ) {
if ( event.touches.length === 1 ) {
event.preventDefault();
mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
}
function onDocumentTouchMove( event ) {
if ( event.touches.length === 1 ) {
event.preventDefault();
mouseX = event.touches[ 0 ].pageX - windowHalfX;
targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
}
}
function animate() {
requestAnimationFrame( animate, renderer.domElement );
render();
update();
}
function update(){
controls.update();
}
function toCameraCoords(position) {
return camera.matrixWorldInverse.multiplyVector3(position.clone());
}
function render() {
tubeMesh.rotation.y += ( targetRotation - tubeMesh.rotation.y ) * 0.15;
camera.lookAt(scene.position);
camera.updateMatrixWorld();
renderer.render( scene, camera );
}
</script>
</body>
</html>
When I use mouse wheel to zoom in, the camera will zoom in at starting point of the tube. How do I make the tube geometry can be zoomed completely or in other words any part of the tube can be zoomed ?
As with any conventional real life camera, you can change the focal length (zoom) of THREE.PerspectiveCamera by calling setLens method. I find this way very intuitive and much simplier than moving the camera to zoom in/out.
Here is the method docs in three.js:
/**
* Uses Focal Length (in mm) to estimate and set FOV
* 35mm (fullframe) camera is used if frame size is not specified;
* Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html
*/
THREE.PerspectiveCamera.prototype.setLens = function ( focalLength, frameHeight )
...
And here is an example usage:
var focalLength = 25.734; // equivalent to FOV=50
$('#scene').mousewheel(function (event, delta, deltaX, deltaY) {
event.preventDefault();
focalLength += deltaY;
camera.setLens(focalLength);
});
The method jQuery.fn.mousewheel is provided by Mousewheel Plugin.
document.body.addEventListener( 'mousewheel', mousewheel, false );
document.body.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
function mousewheel( e ) {
var d = ((typeof e.wheelDelta != "undefined")?(-e.wheelDelta):e.detail);
d = 100 * ((d>0)?1:-1);
var cPos = camera.position;
if (isNaN(cPos.x) || isNaN(cPos.y) || isNaN(cPos.y))
return;
var r = cPos.x*cPos.x + cPos.y*cPos.y;
var sqr = Math.sqrt(r);
var sqrZ = Math.sqrt(cPos.z*cPos.z + r);
var nx = cPos.x + ((r==0)?0:(d * cPos.x/sqr));
var ny = cPos.y + ((r==0)?0:(d * cPos.y/sqr));
var nz = cPos.z + ((sqrZ==0)?0:(d * cPos.z/sqrZ));
if (isNaN(nx) || isNaN(ny) || isNaN(nz))
return;
cPos.x = nx;
cPos.y = ny;
cPos.z = nz;
}
or even simpler ajusting only the camera :
var mousewheel = function ( e ) {
var d = ((typeof e.wheelDelta != "undefined")?(-e.wheelDelta):e.detail);
d = 100 * ((d>0)?1:-1);
var cPos = camera.position;
if (isNaN(cPos.x) || isNaN(cPos.y) || isNaN(cPos.y)) return;
// Your zomm limitation
// For X axe you can add anothers limits for Y / Z axes
if (cPos.x > _YOUR_ZOOM_MIN_X_ || cPos.x < _YOUR_ZOOM_MAX_X_ ){
return ;
}
mb = d>0 ? 1.1 : 0.9;
cPos.x = cPos.x * mb;
cPos.y = cPos.y * mb;
cPos.z = cPos.z * mb;
}

Categories

Resources