this is my situation:
I want to show twitter tweets like this: http://threejs.org/examples/css3d_periodictable.html
This is what I have now:http://nielsvroman.be/twitter/root/index.php
The boxes are tweets from the user: #BachelorGDM.
What I would like now is that there is always one selected and that you can navigate through them with the arrow keys on the keyboard.
This is my javascript code:
// THE TWEETS
var data = loadTweets()
// FUNCTION SHOW THE TWEETS
function ShowTweets(){
// VARS TABLE WITH TWEETS / PLACE IN COLUMN / PLACE IN ROW
var table = [];
var column = 1;
var row = 1;
// LOOP THROUGH DATA AND CREATE TABLE WITH TWEET DETAILS + PLACE
$.each(data, function(i) {
// DETAILS TWEET
var idstring = data[i].id_str;
var screenname = data[i].user.screen_name;
var imageurl = data[i].user.profile_image_url;
// 9 TWEETS NEXT TO EACH OTHER
if(column % 9 == 0)
{
row++
column = 1;
}
var array = [imageurl, idstring, screenname, column, row ]
column++;
table.push(array);
});
// VARIABLES THREE JS
var camera, scene, renderer;
var controls;
var objects = [];
var targets = { table: [], sphere: [], helix: [], grid: [] };
init(); // CALL INIT FUNCTION
animate(); // CALL ANIMATE FUNCTION
function init() {
// INITIALIZE CAMERA
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 5000 );
camera.position.z = 1800;
// INITIALIZE SCENE
scene = new THREE.Scene();
// LOOP THROUGH TABLE ARRAY (ARRAY WITH ARRAYS IN IT)
for ( var i = 0; i < table.length; i ++ ) {
var item = table[i]; // ITEM IS ARRAY WITH [imageurl, idstring, screenname, column, row]
var element = document.createElement( 'div' );
element.className = 'element';
element.id = item[1]; // ITEM IDSTRING
element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')'; // BG COLOR + OPACITY FROM FRAME
var number = document.createElement( 'div' );
number.className = 'number';
number.textContent = i + 1; // NUMBER IN THE RIGHT TOP
element.appendChild( number );
var symbol = document.createElement( 'div' );
symbol.className = 'symbol';
var img = document.createElement('img');
img.src = item[0]; // IMAGE SOURCE IS LINK TO IMAGE
symbol.appendChild(img);
element.appendChild( symbol );
var details = document.createElement( 'div' );
details.className = 'details';
details.innerHTML = "" + '<br>' + item[2]; // SCREENNAME
element.appendChild( details );
var object = new THREE.CSS3DObject( element );
// POSITION OBJECTS AGAINST EACH OTHER
object.position.x = Math.random() * 4000 - 2000;
object.position.y = Math.random() * 4000 - 2000;
object.position.z = Math.random() * 4000 - 2000;
// ADD OBJECTS TO SCENE
scene.add(object);
// ADD OBJECT TO OBJECTS ARRAY
objects.push(object);
}
// TABLE VIEW
for ( var i = 0; i < objects.length; i ++ ) {
var item = table[i]; // ITEM IS ARRAY WITH [imageurl, idstring, screenname, column, row]
var object = new THREE.Object3D();
object.position.x = ( item[3] * 160 ) - 1540; // X-POSITION (COLUMN)
object.position.y = - ( item[4] * 200 ) + 1100; // Y-POSITION (ROW)
// targets = { table: [], sphere: [], helix: [], grid: [] };
targets.table.push(object); // PUSH OBJECT IN TABLE ARRAY (IN TARGETS ARRAY)
}
// SPHERE VIEW
var vector = new THREE.Vector3();
for ( var i = 0, l = objects.length; i < l; i ++ ) {
var phi = Math.acos( -1 + ( 2 * i ) / l );
var theta = Math.sqrt( l * Math.PI ) * phi;
var object = new THREE.Object3D();
object.position.x = 1000 * Math.cos( theta ) * Math.sin( phi );
object.position.y = 1000 * Math.sin( theta ) * Math.sin( phi );
object.position.z = 1000 * Math.cos( phi );
vector.copy( object.position ).multiplyScalar( 2 );
object.lookAt( vector );
// targets = { table: [], sphere: [], helix: [], grid: [] };
targets.sphere.push( object ); // PUSH OBJECT IN SPHERES ARRAY (IN TARGETS ARRAY)
}
// HELIX VIEW
var vector = new THREE.Vector3();
for ( var i = 0, l = objects.length; i < l; i ++ ) {
var phi = i * 0.175 + Math.PI;
var object = new THREE.Object3D();
object.position.x = 1100 * Math.sin( phi );
object.position.y = - ( i * 8 ) + 450;
object.position.z = 1100 * Math.cos( phi );
vector.copy( object.position );
vector.x *= 2;
vector.z *= 2;
object.lookAt( vector );
// targets = { table: [], sphere: [], helix: [], grid: [] };
targets.helix.push( object ); // PUSH OBJECT IN HELIX ARRAY (IN TARGETS ARRAY)
}
// GRID VIEW
for ( var i = 0; i < objects.length; i ++ ) {
var object = new THREE.Object3D();
object.position.x = ( ( i % 5 ) * 400 ) - 800;
object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800;
object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000;
// targets = { table: [], sphere: [], helix: [], grid: [] };
targets.grid.push( object ); // PUSH OBJECT IN GRID ARRAY (IN TARGETS ARRAY)
}
renderer = new THREE.CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.domElement.style.position = 'absolute';
// ADD RENDERER TO CONTAINER
document.getElementById( 'container' ).appendChild( renderer.domElement );
// TRACKBALLCONTROLS => WHEN YOU HOLD DOWN MOUSECLICK
controls = new THREE.TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 0.5;
//controls.minDistance = 500; // MAX ZOOM IN => MIN DISTANCE
controls.maxDistance = 2500; // MAX ZOOM OUT => MAX DISTANCE
controls.zoomSpeed = 1; // STANDARD IS 1.2
controls.keys = [ 37 /*LEFT*/, 38 /*UP*/, 39 /*RIGHT*/, 40 /*DOWN*/ ];
controls.addEventListener( 'change', render ); // RENDER ON CHANGE
var button = document.getElementById( 'table' );
button.addEventListener( 'click', function ( event ) {
transform( targets.table, 2000 );
}, false );
var button = document.getElementById( 'sphere' );
button.addEventListener( 'click', function ( event ) {
transform( targets.sphere, 2000 );
}, false );
var button = document.getElementById( 'helix' );
button.addEventListener( 'click', function ( event ) {
transform( targets.helix, 2000 );
}, false );
var button = document.getElementById( 'grid' );
button.addEventListener( 'click', function ( event ) {
transform( targets.grid, 2000 );
}, false );
transform( targets.table, 5000 );
//
window.addEventListener( 'resize', onWindowResize, false );
// WHEN PRESSED ON KEY
window.addEventListener( 'keydown', keydown, false );
function keydown( event ) {
};
}
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.Exponential.InOut )
.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();
}
// RENDER SCENE/CAMERA
function render() {
renderer.render( scene, camera );
}
}
My tweets are initially in the variable data.
I now have added this to my code:
controls.keys = [ 37 /*LEFT*/, 38 /*UP*/, 39 /*RIGHT*/, 40 /*DOWN*/ ];
window.addEventListener( 'keydown', keydown, false );
function keydown( event ) {};
But now how can I navigate through the tweets (boxes) with my arrows on the keyboard?
I want to change the css of the selected box and open an overlay of the associated tweet. (You can see this when you click on a box on the link)
I have no clue on how to start with this. Can somebody help me?
You could make a stylesheet with a "selected" class, which when you click one of the elements, it removes all other instances of the class "selected" and adds ".selected" to that specific element that was clicked. Then with keyboard input, you do the same thing, but move to the next element. It could be simplified a bit with jQuery, though done without as well.
.element .selected {
background-color: #cccccc;
}
javascript:
document.addEventListener("click", function(e) {
if (e.target === *element you want*)
// remove other selected classes code here.
document.addClass += " " + ".selected"
Related
I am trying to use raycaster to identify a row of 3d cubes to be highlighted/colored on mousehover. I followed this post Change color of mesh using mouseover in three js. The issue I am facing is that it is only highlighting one cube i.e the cube the mouse is on and not the whole row. Please find my pseudocode below:
var cubesList = new THREE.Group();
function createScene () {
var cubeSize = 2;
for ( var i = 0; i < noOfEntries; i++ ) {
var entry = entries[ i ];
var entryObjects = entry.objects;
var entryCubesGroup = new THREE.Group();
var noOfObjects = entry.objects.length;
for ( var j = 0; j < noOfObjects; j++ ) {
var object = entryObjects[ j ];
var cube = createCube( cubeSize ); //THREE.Object3d group of 9 cubes
entryCubesGroup.add( cube );
if ( j === Math.round( noOfObjects / 4 ) - 1 && i === Math.round( noOfEntries / 4 ) - 1 ) {
cameraTarget = cube;
}
}
cubesList.add( entryCubesGroup );
}
scene.add( cubesList );
camera.position.x = 15;
camera.position.y = 15;
camera.position.z = 15;
camera.lookAt( new THREE.Vector3( cameraTarget.position.x, cameraTarget.position.y, cameraTarget.position.z ) );
var light = new THREE.PointLight( 0xffffff, 1, 0 );
light.position.set( 15, 15, 5 );
light.castShadow = true;
scene.add( light );
}
function animate () {
renderer.render( scene, camera );
update();
}
function onDocumentMouseMove ( event ) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.width ) * 2 - 1;
mouse.y = -( event.clientY / renderer.domElement.height ) * 2 + 1;
animate();
}
function update() {
var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
vector.unproject(camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = ray.intersectObjects(eventCubesList.children, true);
if (intersects.length > 0) {
if (intersects[0].object != INTERSECTED) {
if (highlightedRow)
unhighlightRow(highlightedRow);
INTERSECTED = intersects[0].object;
var timestamp = INTERSECTED.userData;
var selectedRow = getSelectedRow(timestamp);
highlightedRow = selectedRow;
highlightRow(selectedRow);
}
else {
if (INTERSECTED) {
if (highlightedRow) {
var timestamp = INTERSECTED.userData;
var row = getSelectedRow(timestamp);
unhighlightRow(row);
}
highlightedRow = null;
}
INTERSECTED = null;
}
}
function unhighlightRow(cubes) {
for (var i= 0; i < cubes.length; i++) {
var cube = cubes[i];
for (var j = 0; j < cube.children.length; j++) {
var child = cube.children[j];
child.material.color.setHex(cube.originalColor);
}
}
}
function highlightRow(cubes) {
for (var i = 0; i < cubes.length; i++) {
var cube = cubes[i];
for (var j = 0; j < cube.children.length; j++) {
var child = cube.children[j];
child.material.color.setHex(0xffff00);
break;
}
}
}
Is there a way I can highlight all the cubes in a row as yellow instead of just one cube?
You need to keep your own data on which cubes are in which rows. When a cube is highlighted you need to look up the row its in and highlight the other cubes in the row
---pseudo code---
INTERSECTED = intersects[ index ].object;
row = getRowObjectIsIn(INTERSECTED)
for each object in row
highlight object
Can someone help! I am simulating a cloth attached to their 4 corners. I am trying to re-locate the 4 pins 0, 10, 88, 98 of the Cloth with an 10x10 array. I want to be able to place each Pin at a different position in x,y,z.
For this simulation I am using Three.js and Cloth.js.
Something similar to this example:
[https://threejs.org/examples/#webgl_animation_cloth][1]
Here is my Code and also the Cloth code I am using.
var pinsFormation = [];
pinsFormation.push( pins );
pins = [ 0, 10, 88, 98 ];
var container, stats;
var camera, scene, renderer, clothGeometry, object;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xFFFFFF );
camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 1000, 50, 1000 );
// cloth
var material_wire = new THREE.MeshBasicMaterial( { color : 0x000000, side: THREE.DoubleSide, wireframe: true } );
clothGeometry = new THREE.ParametricGeometry( clothFunction, cloth.w, cloth.h );
object = new THREE.Mesh( clothGeometry, material_wire ); // clothMaterial
object.position.set( 0, 0, 0 );
scene.add( object );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.maxPolarAngle = Math.PI * 1.5;
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 );
var time = Date.now();
var windStrength = Math.cos( time / 7000 ) * 20 + 40;
windForce.set( Math.sin( time / 2000 ), Math.cos( time / 3000 ), Math.sin( time / 1000 ) )
windForce.normalize()
windForce.multiplyScalar( windStrength );
simulate( time );
render();
}
function render() {
var p = cloth.particles;
for ( var i = 0, il = p.length; i < il; i ++ ) {
clothGeometry.vertices[ i ].copy( p[ i ].position );
}
clothGeometry.verticesNeedUpdate = true;
clothGeometry.computeFaceNormals();
clothGeometry.computeVertexNormals();
renderer.render( scene, camera );
}
// cloth.js
var DAMPING = 0.03;
var DRAG = 1 - DAMPING;
var MASS = 0.1;
var restDistance = 25;
var xSegs = 10;
var ySegs = 10;
var clothFunction = plane( restDistance * xSegs, restDistance * ySegs );
var cloth = new Cloth( xSegs, ySegs );
var GRAVITY = 981 * 1.4;
var gravity = new THREE.Vector3( 0, - GRAVITY, 0 ).multiplyScalar( MASS );
var TIMESTEP = 18 / 1000;
var TIMESTEP_SQ = TIMESTEP * TIMESTEP;
var pins = [];
var wind = true;
var windStrength = 2;
var windForce = new THREE.Vector3( 0, 0, 0 );
var tmpForce = new THREE.Vector3();
var lastTime;
function plane( width, height ) {
return function( u, v ) {
var x = ( u - 0.5 ) * width;
var y = ( v - 0.1 ) * height;
var z = 0;
return new THREE.Vector3( x, y, z );
};
}
function Particle( x, y, z, mass ) {
this.position = clothFunction( x, y ); // position
this.previous = clothFunction( x, y ); // previous
this.original = clothFunction( x, y );
this.a = new THREE.Vector3( 0, 0, 0 ); // acceleration
this.mass = mass;
this.invMass = 1 / mass;
this.tmp = new THREE.Vector3();
this.tmp2 = new THREE.Vector3();
}
// Force -> Acceleration
Particle.prototype.addForce = function( force ) {
this.a.add(
this.tmp2.copy( force ).multiplyScalar( this.invMass )
);
};
// Performs Verlet integration
Particle.prototype.integrate = function( timesq ) {
var newPos = this.tmp.subVectors( this.position, this.previous );
newPos.multiplyScalar( DRAG ).add( this.position );
newPos.add( this.a.multiplyScalar( timesq ) );
this.tmp = this.previous;
this.previous = this.position;
this.position = newPos;
this.a.set( 0, 0, 0 );
};
var diff = new THREE.Vector3();
function satisfyConstraints( p1, p2, distance ) {
diff.subVectors( p2.position, p1.position );
var currentDist = diff.length();
if ( currentDist === 0 ) return;
var correction = diff.multiplyScalar( 1 - distance / currentDist );
var correctionHalf = correction.multiplyScalar( 0.5 );
p1.position.add( correctionHalf );
p2.position.sub( correctionHalf );
}
function Cloth( w, h ) {
w = w || 20;
h = h || 20;
this.w = w;
this.h = h;
var particles = [];
var constraints = [];
var u, v;
// Create particles
for ( v = 0; v <= h; v ++ ) {
for ( u = 0; u <= w; u ++ ) {
particles.push(
new Particle( u / w, v / h, 0, MASS )
);
}
}
// Structural
for ( v = 0; v < h; v ++ ) {
for ( u = 0; u < w; u ++ ) {
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u, v + 1 ) ],
restDistance
] );
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u + 1, v ) ],
restDistance
] );
}
}
for ( u = w, v = 0; v < h; v ++ ) {
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u, v + 1 ) ],
restDistance
] );
}
for ( v = h, u = 0; u < w; u ++ ) {
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u + 1, v ) ],
restDistance
] );
}
this.particles = particles;
this.constraints = constraints;
function index( u, v ) {
return u + v * ( w + 1 );
}
this.index = index;
}
function simulate( time ) {
if ( ! lastTime ) {
lastTime = time;
return;
}
var i, il, particles, particle, pt, constraints, constraint;
// Aerodynamics forces
if ( wind ) {
var face, faces = clothGeometry.faces, normal;
particles = cloth.particles;
for ( i = 0, il = faces.length; i < il; i ++ ) {
face = faces[ i ];
normal = face.normal;
tmpForce.copy( normal ).normalize().multiplyScalar( normal.dot( windForce ) );
particles[ face.a ].addForce( tmpForce );
particles[ face.b ].addForce( tmpForce );
particles[ face.c ].addForce( tmpForce );
}
}
for ( particles = cloth.particles, i = 0, il = particles.length; i < il; i ++ ) {
particle = particles[ i ];
particle.addForce( gravity );
particle.integrate( TIMESTEP_SQ );
}
// Start Constraints
constraints = cloth.constraints;
il = constraints.length;
for ( i = 0; i < il; i ++ ) {
constraint = constraints[ i ];
satisfyConstraints( constraint[ 0 ], constraint[ 1 ], constraint[ 2 ] );
}
// Pin Constraints
for ( i = 0, il = pins.length; i < il; i ++ ) {
var xy = pins[ i ];
var p = particles[ xy ];
p.position.copy( particles.original );
p.previous.copy( particles.original );
}
}
The "pin" is just the index of one of the vertices.. so what you'll have to do is identify the vertex corresponding to the spot you want to pin.. you can get that from a raycast when the user clicks the mesh, or figure it our analytically.
I'm in the process of tinkering together a solar system simulator and it is all going well with the exception of one part: when a moon or a planet goes behind its parent body, you can still see it!
There are no transparent objects in the scene, and I have not made any modifications to the order in which objects are rendered, so I'm at a loss as to why objects appear when they are behind another object.
I'm using three.js r71 (problem still appears in r84, though).
To see where it goes wrong: check out http:mrhuffman.net/projects/gp and pick the Martian system; you will see how Phobos or Deimos are still visible even though they are behind Mars.
Here's the code for the scene. It's not as tidy as it could be as I just started working on it, so if you need clarifications or have questions, shoot!
import THREE from '../vendor/three';
import OrbitControls from '../vendor/OrbitControlsES6';
import ColladaLoader from '../vendor/ColladaLoaderES6';
import nBodyProblem from '../algorithms/nBodyProblem';
const scene = ( function () {
//Full screen action
let w = window.innerWidth;
let h = window.innerHeight;
let requestAnimationFrameId = null;
let scene = null;
let camera = null;
let controls = null;
let renderer = null;
let system = null;
let dae = null;
let cameraControlsWrapper = null;
let orbitButton = null;
let view3DButton = null;
let pathCanvas = document.createElement( 'canvas' );
pathCanvas.style.display = 'none';
pathCanvas.style.backgroundImage = 'url(misc/starfield.jpg)';
let ctxPath = pathCanvas.getContext( '2d' );
let massCanvas = document.createElement( 'canvas' );
massCanvas.style.display = 'none';
massCanvas.style.position = 'absolute';
massCanvas.style.zIndex = 2;
massCanvas.style.top = 0;
massCanvas.style.bottom = 0;
let ctxMass = massCanvas.getContext( '2d' );
pathCanvas.width = w;
pathCanvas.height = h;
massCanvas.width = w;
massCanvas.height = h;
ctxPath.translate( w / 2, h / 2 );
ctxMass.translate( w / 2, h / 2 );
function showOrbits() {
orbitButton.style.display = 'none';
cameraControlsWrapper.style.display = 'none';
pathCanvas.style.display = 'block';
massCanvas.style.display = 'block';
view3DButton.style.display = 'block';
}
function hideOrbits() {
view3DButton.style.display = 'none';
pathCanvas.style.display = 'none';
massCanvas.style.display = 'none';
orbitButton.style.display = 'block';
cameraControlsWrapper.style.display = 'block';
}
function createBody( radius, name, type ) {
let segments = type !== 'asteroid' ? 32 : 6;
let geometry = new THREE.SphereGeometry( radius, segments, segments );
let map;
let bumpMap;
switch( type ) {
case 'asteroid':
map = THREE.ImageUtils.loadTexture('textures/Phobos.jpg');
bumpMap = THREE.ImageUtils.loadTexture('textures/PhobosBump.jpg');
break;
case 'custom':
map = THREE.ImageUtils.loadTexture('textures/Acid.jpg');
break;
case 'star':
map = THREE.ImageUtils.loadTexture('textures/Sun.jpg');
break;
case 'spacecraft':
scene.add( dae );
return dae;
default:
map = THREE.ImageUtils.loadTexture('textures/' + name + '.jpg');
bumpMap = THREE.ImageUtils.loadTexture('textures/Phobos.jpg');
}
let material = new THREE.MeshPhongMaterial( { map: map, bumpMap: bumpMap, bumpScale: 0.02 } );
let mesh = new THREE.Mesh( geometry, material );
mesh.rotation.x = 1.5;
scene.add( mesh );
return mesh;
}
function sceneSetup( callback, scenario, container, camPos, camFocus, viewOrbits, view3D, cameraControls ) {
if ( scenario.model === undefined ) {
callback( scenario, container, camPos, camFocus, viewOrbits, view3D, cameraControls );
return;
}
let loader = new ColladaLoader();
loader.options.convertUpAxis = true;
loader.load( './models/' + scenario.model + '/' + scenario.model + '.dae', ( collada ) => {
dae = collada.scene;
dae.scale.setScalar( 1 / 800 );
callback( scenario, container, camPos, camFocus, viewOrbits, view3D, cameraControls );
});
}
function initSimulation( scenario, container, camPos, camFocus, viewOrbits, view3D, cameraControls ) {
cameraControlsWrapper = cameraControls;
orbitButton = viewOrbits;
orbitButton.addEventListener( 'click', showOrbits, false );
view3DButton = view3D;
view3DButton.addEventListener( 'click', hideOrbits, false );
hideOrbits();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, w / h, 0.000001, 1500 );
//Prevent rolling of the camera when you view a body from another
camera.up.set( 0, 0, 1 );
camera.position.set( 0, 90, 150 );
var light = new THREE.AmbientLight(0x404040);
scene.add(light);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(w, h);
renderer.setClearColor(0x000000);
container.appendChild( pathCanvas );
container.appendChild( massCanvas );
container.appendChild( renderer.domElement );
controls = new OrbitControls( camera, renderer.domElement );
//Create a new n-body problem from the selected scenario
system = new nBodyProblem( {
g: scenario.g,
law: scenario.law,
dt: scenario.dt,
masses: scenario.masses
});
//Create visual manifestations of the planets, asteroids and star(s)
for (let i = 0; i < system.masses.length; i++) {
let mass = system.masses[ i ];
mass.manifestation = createBody( mass.radius, mass.name, mass.type );
}
const render = function () {
requestAnimationFrameId = requestAnimationFrame( render );
//Update state vectors system.updatePositionVectors().updateVelocityVectors().updateBarycenter().calculateElapsedTime();
//Check if a rocket should be fired and if so check if it should be fired in this iteration; should that be the case, fire!
if ( scenario.rocketBurn === true ) {
if ( system.elapsedTime === scenario.rocketBurnTime ) {
for ( let i = 0; i < system.masses.length; i++ ) {
if ( system.masses[ i ].type === 'spacecraft' ) {
system.masses[ i ].vx = scenario.afterRocketBurnVelocity.vx;
system.masses[ i ].vy = scenario.afterRocketBurnVelocity.vy;
system.masses[ i ].vz = scenario.afterRocketBurnVelocity.vz;
}
}
}
}
ctxMass.clearRect( -0.5 * w, -0.5 * h, w, h );
//Put all the masses in their new positions and set camera position and focus
for (let i = 0; i < system.masses.length; i++) {
let mass = system.masses[ i ];
let x = mass.x * scenario.scale;
let y = mass.y * scenario.scale;
let z = mass.z * scenario.scale;
mass.manifestation.position.set( x, y, z );
let camR = camPos.value;
let name = mass.name;
if ( camR === name ) {
camera.position.set( x, y, z + ( mass.radius * 1.2 ) );
controls.enabled = false;
} else if ( camR === 'free' ) {
controls.enabled = true;
}
if ( camFocus.value === name ) {
camera.lookAt( new THREE.Vector3( x, y, z ) );
//If the camera mode is free, the user can pan, orbit and have fun
if ( camPos === 'free' ) controls.target = new THREE.Vector3( x, y, z );
}
ctxPath.fillStyle = mass.color;
ctxPath.fillRect( x, y, 1, 1 );
ctxMass.beginPath();
ctxMass.fillStyle = mass.color;
ctxMass.arc( x, y, 6, 0, 2 * Math.PI );
ctxMass.fill();
ctxMass.font = "14px Arial";
ctxMass.fillText( mass.name, x + 8, y );
}
//Put the barycenter of the system in its new position
let barycenterX = system.x * scenario.scale;
let barycenterY = system.y * scenario.scale;
ctxMass.strokeStyle = 'limegreen';
ctxMass.lineWidth = 2;
ctxMass.beginPath();
ctxMass.moveTo( barycenterX - 20, barycenterY );
ctxMass.lineTo( barycenterX + 20, barycenterY );
ctxMass.moveTo( barycenterX, barycenterY - 20 );
ctxMass.lineTo( barycenterX, barycenterY + 20 );
ctxMass.stroke();
ctxMass.fillStyle = 'limegreen';
ctxMass.font = "14px Arial";
ctxMass.fillText( 'Barycenter', barycenterX, barycenterY - 25 );
renderer.render( scene, camera );
};
//Makes the scene responsive
//Note that traces are cleared when the size of the viewport changes
window.addEventListener( 'resize', onWindowResize, false );
function onWindowResize() {
w = window.innerWidth;
h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize( w, h );
pathCanvas.width = w;
pathCanvas.height = h;
massCanvas.width = w;
massCanvas.height = h;
ctxPath.clearRect( -0.5 * w, -0.5 * h, w, h );
ctxMass.clearRect( -0.5 * w, -0.5 * h, w, h );
ctxPath.translate( w / 2, h / 2 );
ctxMass.translate( w / 2, h / 2 );
}
render();
}
//Tidy up
function resetSimulation() {
orbitButton.removeEventListener( 'click', showOrbits );
view3DButton.removeEventListener( 'click', hideOrbits );
cancelAnimationFrame( requestAnimationFrameId );
ctxPath.clearRect( -0.5 * w, -0.5 * h, w, h );
renderer.domElement.parentNode.removeChild( renderer.domElement );
pathCanvas.parentNode.removeChild( pathCanvas );
massCanvas.parentNode.removeChild( massCanvas );
}
//API
return {
startSimulation: ( scenario, container, camPos, camFocus, viewOrbits, view3D, cameraControls ) => {
sceneSetup( initSimulation, scenario, container, camPos, camFocus, viewOrbits, view3D, cameraControls );
},
resetSimulation: resetSimulation
};
}());
export default scene;
I am using the the forkit jQuery ribbon that drops down a curtain of additional content. I have managed to apply this and it works fine once the ribbon is pulled down as per the fiddle.
I want to modify it such that, the curtain also appears once a button is clicked.
Kindly help.
jQuery:
(function(){
var STATE_CLOSED = 0,
STATE_DETACHED = 1,
STATE_OPENED = 2,
TAG_HEIGHT = 30,
TAG_WIDTH = 200,
MAX_STRAIN = 40,
// Factor of page height that needs to be dragged for the
// curtain to fall
DRAG_THRESHOLD = 0.36;
VENDORS = [ 'Webkit', 'Moz', 'O', 'ms' ];
var dom = {
ribbon: null,
ribbonString: null,
ribbonTag: null,
curtain: null,
closeButton: null
},
// The current state of the ribbon
state = STATE_CLOSED,
// Ribbon text, correlates to states
closedText = '',
detachedText = '',
friction = 1.04;
gravity = 1.5,
// Resting position of the ribbon when curtain is closed
closedX = TAG_WIDTH * 0.4,
closedY = -TAG_HEIGHT * 0.5,
// Resting position of the ribbon when curtain is opened
openedX = TAG_WIDTH * 0.4,
openedY = TAG_HEIGHT,
velocity = 0,
rotation = 45,
curtainTargetY = 0,
curtainCurrentY = 0,
dragging = false,
dragTime = 0,
dragY = 0,
anchorA = new Point( closedX, closedY ),
anchorB = new Point( closedX, closedY ),
mouse = new Point();
function initialize() {
dom.ribbon = document.querySelector( '.forkit' );
dom.curtain = document.querySelector( '.forkit-curtain' );
dom.closeButton = document.querySelector( '.forkit-curtain .close-button' );
if( dom.ribbon ) {
// Fetch label texts from DOM
closedText = dom.ribbon.getAttribute( 'data-text' ) || '';
detachedText = dom.ribbon.getAttribute( 'data-text-detached' ) || closedText;
// Construct the sub-elements required to represent the
// tag and string that it hangs from
dom.ribbon.innerHTML = '<span class="string"></span><span class="tag">' + closedText + '</span>';
dom.ribbonString = dom.ribbon.querySelector( '.string' );
dom.ribbonTag = dom.ribbon.querySelector( '.tag' );
// Bind events
dom.ribbon.addEventListener( 'click', onRibbonClick, false );
document.addEventListener( 'mousemove', onMouseMove, false );
document.addEventListener( 'mousedown', onMouseDown, false );
document.addEventListener( 'mouseup', onMouseUp, false );
window.addEventListener( 'resize', layout, false );
if( dom.closeButton ) {
dom.closeButton.addEventListener( 'click', onCloseClick, false );
}
// Start the animation loop
animate();
}
}
function onMouseDown( event ) {
if( dom.curtain && state === STATE_DETACHED ) {
event.preventDefault();
dragY = event.clientY;
dragTime = Date.now();
dragging = true;
}
}
function onMouseMove( event ) {
mouse.x = event.clientX;
mouse.y = event.clientY;
}
function onMouseUp( event ) {
if( state !== STATE_OPENED ) {
state = STATE_CLOSED;
dragging = false;
}
}
function onRibbonClick( event ) {
if( dom.curtain ) {
event.preventDefault();
if( state === STATE_OPENED ) {
close();
}
else if( Date.now() - dragTime < 300 ) {
open();
}
}
}
function onCloseClick( event ) {
event.preventDefault();
close();
}
function layout() {
if( state === STATE_OPENED ) {
curtainTargetY = window.innerHeight;
curtainCurrentY = curtainTargetY;
}
}
function open() {
dragging = false;
state = STATE_OPENED;
}
function close() {
dragging = false;
state = STATE_CLOSED;
dom.ribbonTag.innerHTML = closedText;
}
function detach() {
state = STATE_DETACHED;
dom.ribbonTag.innerHTML = detachedText;
}
function animate() {
update();
render();
requestAnimFrame( animate );
}
function update() {
// Distance between mouse and top right corner
var distance = distanceBetween( mouse.x, mouse.y, window.innerWidth, 0 );
// If we're OPENED the curtainTargetY should ease towards page bottom
if( state === STATE_OPENED ) {
curtainTargetY = Math.min( curtainTargetY + ( window.innerHeight - curtainTargetY ) * 0.2, window.innerHeight );
}
else {
// Detach the tag when hovering close enough
if( distance < TAG_WIDTH * 1.5 ) {
detach();
}
// Re-attach the tag if the user moved away
else if( !dragging && state === STATE_DETACHED && distance > TAG_WIDTH * 2 ) {
close();
}
if( dragging ) {
// Updat the curtain position while dragging
curtainTargetY = Math.max( mouse.y - dragY, 0 );
// If the threshold is crossed, open the curtain
if( curtainTargetY > window.innerHeight * DRAG_THRESHOLD ) {
open();
}
}
else {
curtainTargetY *= 0.8;
}
}
// Ease towards the target position of the curtain
curtainCurrentY += ( curtainTargetY - curtainCurrentY ) * 0.3;
// If we're dragging or detached we need to simulate
// the physical behavior of the ribbon
if( dragging || state === STATE_DETACHED ) {
// Apply forces
velocity /= friction;
velocity += gravity;
var containerOffsetX = dom.ribbon.offsetLeft;
var offsetX = Math.max( ( ( mouse.x - containerOffsetX ) - closedX ) * 0.2, -MAX_STRAIN );
anchorB.x += ( ( closedX + offsetX ) - anchorB.x ) * 0.1;
anchorB.y += velocity;
var strain = distanceBetween( anchorA.x, anchorA.y, anchorB.x, anchorB.y );
if( strain > MAX_STRAIN ) {
velocity -= Math.abs( strain ) / ( MAX_STRAIN * 1.25 );
}
var dy = Math.max( mouse.y - anchorB.y, 0 ),
dx = mouse.x - ( containerOffsetX + anchorB.x );
// Angle the ribbon towards the mouse but limit it avoid extremes
var angle = Math.min( 130, Math.max( 50, Math.atan2( dy, dx ) * 180 / Math.PI ) );
rotation += ( angle - rotation ) * 0.1;
}
// Ease ribbon towards the OPENED state
else if( state === STATE_OPENED ) {
anchorB.x += ( openedX - anchorB.x ) * 0.2;
anchorB.y += ( openedY - anchorB.y ) * 0.2;
rotation += ( 90 - rotation ) * 0.02;
}
// Ease ribbon towards the CLOSED state
else {
anchorB.x += ( anchorA.x - anchorB.x ) * 0.2;
anchorB.y += ( anchorA.y - anchorB.y ) * 0.2;
rotation += ( 45 - rotation ) * 0.2;
}
}
function render() {
if( dom.curtain ) {
dom.curtain.style.top = - 100 + Math.min( ( curtainCurrentY / window.innerHeight ) * 100, 100 ) + '%';
}
dom.ribbon.style[ prefix( 'transform' ) ] = transform( 0, curtainCurrentY, 0 );
dom.ribbonTag.style[ prefix( 'transform' ) ] = transform( anchorB.x, anchorB.y, rotation );
var dy = anchorB.y - anchorA.y,
dx = anchorB.x - anchorA.x;
var angle = Math.atan2( dy, dx ) * 180 / Math.PI;
dom.ribbonString.style.width = anchorB.y + 'px';
dom.ribbonString.style[ prefix( 'transform' ) ] = transform( anchorA.x, 0, angle );
}
function prefix( property, el ) {
var propertyUC = property.slice( 0, 1 ).toUpperCase() + property.slice( 1 );
for( var i = 0, len = VENDORS.length; i < len; i++ ) {
var vendor = VENDORS[i];
if( typeof ( el || document.body ).style[ vendor + propertyUC ] !== 'undefined' ) {
return vendor + propertyUC;
}
}
return property;
}
function transform( x, y, r ) {
return 'translate('+x+'px,'+y+'px) rotate('+r+'deg)';
}
function distanceBetween( x1, y1, x2, y2 ) {
var dx = x1-x2;
var dy = y1-y2;
return Math.sqrt(dx*dx + dy*dy);
}
/**
* Defines a 2D position.
*/
function Point( x, y ) {
this.x = x || 0;
this.y = y || 0;
}
Point.prototype.distanceTo = function( x, y ) {
var dx = x-this.x;
var dy = y-this.y;
return Math.sqrt(dx*dx + dy*dy);
};
Point.prototype.clone = function() {
return new Point( this.x, this.y );
};
Point.prototype.interpolate = function( x, y, amp ) {
this.x += ( x - this.x ) * amp;
this.y += ( y - this.y ) * amp;
};
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
initialize();
})();
You need to add an event handler to a button that calls the open() method from the forkit plugin, like this:
$('#dropButton').bind('click', function(){
open();
});
Here is a working JSFiddle
Just add this to it - should work.
$('a.forkit').click(function(){
$('.forkit-curtain').slideDown();
});
https://jsfiddle.net/eaj5rvtm/1/
I'm trying to add an offset to the camera after deviceControls.update(); command. I used DeviceOrientationControls as shown in this first example.
The offset will be the result of a drag gesture, as presents in this example.
When I multiply the 2 quaternions (I have tried a x b and b x a), the final result is not correct.
Here is my operation :
const m1 = new THREE.Matrix4();
m1.lookAt(new THREE.Vector3(), camera.target, THREE.Object3D.DefaultUp.clone());
const quater = new THREE.Quaternion();
quater.setFromRotationMatrix(m1);
const finalQuater = new THREE.Quaternion();
finalQuater.multiplyQuaternions(quater, camera.quaternion);
camera.quaternion.copy(finalQuater);
camera.target is my final drag target (Vector3), and camera.quaternion has been set by deviceControls.update() and is equals to the camera orientation, according to the device gyroscope.
Thanks for your help
Update : I have tried to changer rotate order, same problem. I think it is due to the origin change after the device orientation update, but can't find how to solve.
DeviceOrientationControls now has a property alphaOffsetAngle, and a method
controls.updateAlphaOffsetAngle( angle ); // angle is in radians
that will rotate the scene around the three.js 'Y' axis.
three.js r.77
var rotY = 0;
var rotX = 0;
function setObjectQuaternion(quaternion, alpha, beta, gamma, orient) {
var zee = new THREE.Vector3( 0, 0, 1 );
var euler = new THREE.Euler();
var q0 = new THREE.Quaternion();
var q1 = new THREE.Quaternion( -Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
if (screenOrientation == 0) {
var vectorFingerY = new THREE.Vector3( 1, 0, 0 );
var fingerQY = new THREE.Quaternion();
fingerQY.setFromAxisAngle ( vectorFingerY, -rotX );
}else if (screenOrientation == 180) {
var vectorFingerY = new THREE.Vector3( 1, 0, 0 );
var fingerQY = new THREE.Quaternion();
fingerQY.setFromAxisAngle ( vectorFingerY, rotX );
}else if (screenOrientation == 90) {
var vectorFingerY = new THREE.Vector3( 0, 1, 0 );
var fingerQY = new THREE.Quaternion();
fingerQY.setFromAxisAngle ( vectorFingerY, rotX );
}else if (screenOrientation == -90) {
var vectorFingerY = new THREE.Vector3( 0, 1, 0 );
var fingerQY = new THREE.Quaternion();
fingerQY.setFromAxisAngle ( vectorFingerY, -rotX );
}
q1.multiply( fingerQY );
euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
quaternion.setFromEuler( euler ); // orient the device
quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
};
function update(camera) {
if (window.orientation !== undefined && window.orientation !== null) screenOrientation = window.orientation;
var alpha = deviceOrientation.alpha ? THREE.Math.degToRad( deviceOrientation.alpha ) : 0; // Z
var beta = deviceOrientation.beta ? THREE.Math.degToRad( deviceOrientation.beta ) : 0; // X'
var gamma = deviceOrientation.gamma ? THREE.Math.degToRad( deviceOrientation.gamma ) : 0; // Y''
var orient = screenOrientation ? THREE.Math.degToRad( screenOrientation ) : 0; // O
setObjectQuaternion( camera.quaternion, alpha, beta, gamma, orient );
};
add this to your init
container.appendChild( renderer.domElement );
renderer.domElement.addEventListener( 'touchstart', function (e) {
if (controls) {
e.preventDefault();
e.stopPropagation();
tempX = e.touches[ 0 ].pageX;
tempY = e.touches[ 0 ].pageY;
}
}, false );
renderer.domElement.addEventListener( 'touchmove', function (e) {
if (controls) {
e.preventDefault();
e.stopPropagation();
rotY += THREE.Math.degToRad((tempX - e.touches[ 0 ].pageX)/4);
rotX += THREE.Math.degToRad((tempY - e.touches[ 0 ].pageY)/4);
mesh.quaternion.copy(MeshStartQY);
var vectorFingerY = new THREE.Vector3( 0, 1, 0 );
var fingerQY = new THREE.Quaternion();
fingerQY.setFromAxisAngle ( vectorFingerY, rotY );
mesh.quaternion.multiply(fingerQY);
tempX = e.touches[ 0 ].pageX;
tempY = e.touches[ 0 ].pageY;
}
}, false );