I am trying to work with a three.js environment with multiple objects that change mass when they are clicked on. Unfortunately the documentation hasn't been helpful and I haven't been able to find any examples of the best way to do this.
Here is the portion in which I am adding a random number of objects to the scene.
var random = getRandomArbitrary(4, 50);
for (var i = 0; i <= random; i++) {
var geometry = new THREE.IcosahedronGeometry( 5, 0 );
var physijsMaterial = Physijs.createMaterial(
new THREE.MeshLambertMaterial( { color: 0xffffff, emissive: 0x333333, shading: THREE.FlatShading } ),
0, // high friction
0 // medium restitution /bouciness
);
var smallSphere = new Physijs.SphereMesh(
geometry,
physijsMaterial,
0
);
smallSphere.position.y = getRandomArbitrary(-50, 50);
smallSphere.position.z = getRandomArbitrary(-50, 50);
smallSphere.position.x = getRandomArbitrary(-50, 50);
smallSphere.name = "Crystals";
scene.add(smallSphere);
}
Here is the portion in which I am trying to update the mass of an object.
function generateGravity(event)
{
event.preventDefault();
var vector = new THREE.Vector3();
vector.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
vector.unproject( camera );
raycaster.ray.set( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects( scene.children, true );
console.log(intersects);
// intersects.object
length = intersects.length;
for (var x = 0; x < length; x++) {
if (intersects[x].object.name == "Crystals") {
console.log(intersects[x].object.mass);
intersects[x].object._physijs.mass = 50;
// intersects[x].object.remove();
}
}
}
What I'm curious to know is the best approach to updating the mass of an object during an event. Right now this function generateGravity is being called in the "render" method.
I realized this was happening because I placed scene.simulate() after render().
Related
For a web project, I would like to draw random points with Three.js.
This is my code so far:
<script type="module">
import * as THREE from 'https://threejs.org/build/three.module.js';
import { TrackballControls } from 'https://threejs.org/examples/jsm/controls/TrackballControls.js';
let camera, scene, renderer, controls;
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff );
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 0, 500 );
controls = new TrackballControls( camera, renderer.domElement );
controls.minDistance = 200;
controls.maxDistance = 500;
scene.add( new THREE.AmbientLight( 0x222222 ) );
const light = new THREE.PointLight( 0xffffff );
light.position.copy( camera.position );
scene.add( light );
//
//
const randomPoints = [];
for ( let i = 0; i < 10; i ++ ) {
randomPoints.push( new THREE.Vector3( ( i - 4.5 ) * 50, THREE.MathUtils.randFloat( - 50, 50 ), THREE.MathUtils.randFloat( - 50, 50 ) ) );
}
const randomSpline = new THREE.CatmullRomCurve3( randomPoints );
//
const extrudeSettings2 = {
steps: 120,
bevelEnabled: false,
extrudePath: randomSpline
};
const pts2 = [], numPts = 5;
for ( let i = 0; i < numPts * 2; i ++ ) {
const l = i % 2 == 1 ? 10 : 10;
const a = i / numPts * Math.PI;
pts2.push( new THREE.Vector2( Math.cos( a ) * l, Math.sin( a ) * l ) );
}
const shape2 = new THREE.Shape( pts2 );
const geometry2 = new THREE.ExtrudeGeometry( shape2, extrudeSettings2 );
const material2 = new THREE.MeshLambertMaterial( { color: 0xff8000, wireframe: false } );
const mesh2 = new THREE.Mesh( geometry2, material2 );
scene.add( mesh2 );
//
const materials = [ material2 ];
const extrudeSettings3 = {
depth: 40,
steps: 1,
bevelEnabled: true,
bevelThickness: 2,
bevelSize: 4,
bevelSegments: 1
};
const geometry3 = new THREE.ExtrudeGeometry( shape2, extrudeSettings3 );
const mesh3 = new THREE.Mesh( geometry3 );
mesh3.position.set( 150, 100, 0 );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
</script>
Currently, everything is based on splines. The result should not be based on extrusion, but on random points. I don't really know how to get random points. Is there a special function for it? Or can another function be used for it?
I would be veeeery thankful for help! :)
I think a good place to start with is using ConvexGeometry. You give it an array of points / Vector3 ( which I see you have created under the variable randomPoints ) as a parameter and it will create a shape for you.
I see you used CatmullRomCurve3, this may be a good tool to create the curves between the points as you mentioned. We can combine both of these ideas to create a somewhat curvier model.
const divisions = 25; // The amount of divisions between points
const catmullPoints = new THREE.CatmullRomCurve3( randomPoints, true, "catmullrom", 0.5 ).getPoints(divisions);
const geometryConvex = new ConvexGeometry( randomPoints );
So now you have a geometry with a somewhat randomized shape. The thing about it is that it will look a bit more "geometrical" than the example shapes you provided. So what you can try to do is divide your randomPoints to chunks, i.e multiple sub-arrays, and do a similar approach as above, basically saving the created geometries to a separate array, let's call it geometries, and then you can use mergeBufferGeometries to create a single geometry out of these geometries, this will give a more abstract looking shape. The code:
const size = THREE.MathUtils.randInt(2,10); // the number of sub arrays can be another parameter to randomize
const pointsChunk = chunk([...randomPoints], size); // you can use lodash or other chunk algorithms found online
const geometries = [];
for ( let i = 0 ; i < pointsChunk.length ; i++) {
const divisions = 25;
const catmullPoints = new THREE.CatmullRomCurve3( pointsChunk[i], true, "catmullrom", 0.5 ).getPoints( divisions );
geometries.push(new ConvexGeometry( catmullPoints ));
}
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries );
There may be more ways to go about it, but I believe ConvexGeometry is definitely a good place to start.
Here is a fiddle of my attempt : https://jsfiddle.net/9rc503tn/2
I have the following JavaScript code, taken from an example, which draws some circles, and when you click on one of them, it changes color. But I also want to change the radius/size of a circle when you click on that circle (and leave the other circles unchanged). The documentation does not help at all, and I have tried several variations in the code like
intersects[ 0 ].object.geometry.parameters.radius = 50;
intersects[ 0 ].object.geometry.radius = 50;
intersects[ 0 ].object.geometry.setRadius(50);
Anyway, here is the complete code:
var container, camera, scene, geometry, mesh, renderer, controls, particles, colors;
var objects = [];
// DOM element...
container = document.createElement('div');
document.body.appendChild(container);
// Camera...
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 0, 75);
// Scene...
scene = new THREE.Scene();
scene.add(camera);
// Renderer...
renderer = new THREE.WebGLRenderer({
clearAlpha: 1
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
// Scatter plot...
scatterPlot = new THREE.Object3D();
scene.add(scatterPlot);
// Make grid...
xzColor = 'black';
xyColor = 'black';
yzColor = 'black';
// Plot some random points...
circle = new THREE.CircleGeometry(5);
colors = [];
var max = 50;
var min = -50;
for (var i = 0; i < 10; i++) {
var object = new THREE.Mesh( circle, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
object.position.x = Math.random() * (max - min) + min;
object.position.y = Math.random() * (max - min) + min;
object.position.z = 0;
scene.add( object );
objects.push( object );
}
animate();
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
function onDocumentMouseDown( event ) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
intersects[ 0 ].object.geometry.setRadius(50);
var particle = new THREE.Sprite( particleMaterial );
particle.position.copy( intersects[ 0 ].point );
particle.scale.x = particle.scale.y = 16;
scene.add( particle );
}
}
Any idea how I can solve this? And where can I find the proper documentation?
Addendum:
I have used the following line of code:
intersects[ 0 ].object.geometry.scale(1.1,1.1,1.1);
In the code, and now the circles change their size. But ALL of them! I click on one circle, and every circle changes size. Does not make sense to me...
Don't scale your geometry. You're using the same geometry reference for all of your circles, so scaling one scales them all.
Instead, scale your Mesh, which is a unique object in the scene (even if it references the same geometry as other meshes). Much like how you're setting position for each Mesh, you also have access to scale:
intersects[0].object.scale.set(1.1, 1.1, 1.1);
That will scale the intersected Mesh object, and only that Mesh.
Cloning and creating a new Mesh is literally introducing a memory leak. The original Mesh won't go away until you remove it from the scene, and you keep making more and more Geometry objects as you clone the circle.
I have a N number of random points (in this case 20), with a X,Y and Z constrains.
How can I create ANY (preferably closed) shape (using Three.js library) , given and starting only from N random points.
There are probably many variants, please share yours.
var program = new Program(reset,step)
program.add('g',false)
function reset() {
scene.clear()
scene.add(new THREE.GridHelper(100,1))
}
function step() {
}
program.startup()
var numpoints = 20;
var dots = []; //If you want to use for other task
for (var i = 0 ; i < numpoints ; i++){
var x = Math.random() * (80 - 1) + 1 //Math.random() * (max - min) + min
var y = Math.random() * (80 - 1) + 1
var z = Math.random() * (80 - 1) + 1
var dotGeometry = new THREE.Geometry();
dots.push(dotGeometry);
dotGeometry.vertices.push(new THREE.Vector3( x, y, z));
var dotMaterial = new THREE.PointsMaterial( { size: 3, sizeAttenuation: false, color: 0xFF0000 } );
var dot = new THREE.Points( dotGeometry, dotMaterial );
scene.add(dot);
}
Triangulation, Voronoi, I don't care, just show me ANY ideas you have, will help me learn a lot!
You can create a polyhedron which is the convex hull of a set of 3D points by using a pattern like so:
var points = [
new THREE.Vector3( 100, 0, 0 ),
new THREE.Vector3( 0, 100, 0 ),
...
new THREE.Vector3( 0, 0, 100 )
];
var geometry = new THREE.ConvexGeometry( points );
var material = new THREE.MeshPhongMaterial( {
color: 0xff0000,
shading: THREE.FlatShading
} );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
You must include the following in your project
<script src="/examples/js/geometries/ConvexGeometry.js"></script>
three.js r.78
I'm visualizing UTM/WGS84 coordinates in three.js. My problem is that the granularity of the trajectories are very fine grained, meaning that I can't see any differences in the movement behaviour. I'm looking for a clean way to plot a Space-Time-Cube (X and Y is space, Z is time) but I can't figure out how to project the trajectory data into the scene that I can actually see the location changes (I normalized the data which kinda worked but I would rather prefer a more fancy method). I'm loading the trajectory info from a CSV which is stored in the variable data. I have 1500 of these tuples, with LAT, LON (EPSG 4326) and ascending seconds. As you can see the movement is very fine grained (I have movement data from an object moving over a size of approx. four football fields)
12.4309352,48.4640973,0
12.4301431,48.4655268,15
12.4288555,48.4658138,30
12.4266812,48.4653488,45
12.4245049,48.4648678,60
12.4228305,48.4639438,75
12.4217859,48.4625038,90
... ... ...
Here is my code so far with comments:
var data = $.csv.toArrays(csv);
var renderer,
scene,
camera,
controls
//using terrainSize was an experiment, it didn't change much
var terrainSize = 60;
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
renderer = new THREE.WebGLRenderer({ antialias: true });
document.body.appendChild( renderer.domElement );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColorHex( 0xeeeeee, 1.0 );
scene = new THREE.Scene();
var material = new THREE.LineBasicMaterial({
color: 0xff00cc,
fog: true
});
var geometry = new THREE.Geometry();
var x = []
var y = []
var z = []
var count = 0;
for(var row in data) {
count += parseInt(data[row][2]);
x.push(parseFloat(data[row][0]));
y.push(parseFloat(data[row][1]));
z.push(parseFloat(count));
}
//I normalize the seconds that everything is visible on the map
z_stretch = stretch_array(z,10,1)
function stretch_array(my_stretched_array, given_stretch, multiplier) {
ratio = Math.max.apply(this, my_stretched_array) / given_stretch,
l = my_stretched_array.length;
for ( i = 0; i < l; i++ ) {
my_stretched_array[i] = my_stretched_array[i] / ratio;
}
for ( i = 0; i < my_stretched_array.length; i++) {
my_stretched_array[i] = multiplier * my_stretched_array[i];
}
return my_stretched_array;
}
//I zip the data back together
var data_stretched = []
for ( i = 0; i < data.length; i++ ) {
data_stretched.push([x[i], y[i], z_stretch[i]]);
}
//I tried using d3.js but I couldn't figure out how to stretch the data accordingly
var projection = d3.geo.transverseMercator()
.translate([terrainSize / 2, terrainSize / 2])
.scale(10)
.center([12.4309352,48.4640973]);
//Looping through the data, translating it and adding each tuple to the geometry
for (var row in data_stretched) {
var x = data_stretched[row][0]
var y = data_stretched[row][2]
var z = data_stretched[row][2]
coord = translate(projection([y, x]));
geometry.vertices.push(new THREE.Vector3(parseFloat(coord[0]), parseFloat(z), parseFloat(coord[1])));
}
// Another experiment
function translate(point) {
return [point[0] - (terrainSize / 2), (terrainSize / 2) - point[1]];
}
// Plotting the line
var line = new THREE.Line(geometry, material);
scene.add( line );
// camera and control settings..
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, -terrainSize / 2, terrainSize / 2);
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 0.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
animate();
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
And this is how I want it to look like (I just stretched the values for this one, which is kinda ugly..)
Solved, I had to scale the coordinates.
var projection = d3.geo.transverseMercator()
.translate([window.innerWidth, window.innerHeight])
.scale(30000000);
I am running into a bit of difficulty selecting objects with the orthographic camera using the raycaster. Though, I have no problem with it when I use a perspective camera. The only thing I am changing when switching between the two is the type camera.
I am able to select faces on the orthographic view, but it is only loosely related to where I am clicking on the screen. When I can click far away from the object and it will still come back as if it has hit the object near its center.
Any ideas on what I am missing here?
I am basing much of my code on this example, and am hoping to achieve a very similar result from my code. (this example I'm referencing uses the perspective camera)
Any help is much appreciated
<html>
<head>
<style>
canvas {
left: 0;
top: 0;
width: 100%;
height: 100%;
position: fixed;
background-color: #111115;
}
</style>
</head>
<body id='c'>
<script src="js/three.js"></script>
<script>
var obj = [];
var mouse ={};
var zoom = 2;
var scene = new THREE.Scene();
//switch between these two and see the difference:
//var camera = new THREE.OrthographicCamera(window.innerWidth / -zoom, window.innerWidth / zoom, window.innerHeight / zoom, window.innerHeight / -zoom, -1000, 1000);
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position = new THREE.Vector3(100,100,100);
camera.lookAt(new THREE.Vector3(0,0,0));
// this material causes a mesh to use colors assigned to faces
var material = new THREE.MeshBasicMaterial(
{ color: 0xffffff, vertexColors: THREE.FaceColors } );
var sphereGeometry = new THREE.SphereGeometry( 80, 32, 16 );
for ( var i = 0; i < sphereGeometry.faces.length; i++ )
{
face = sphereGeometry.faces[ i ];
face.color.setRGB( 0, 0, 0.8 * Math.random() + 0.2 );
}
obj['box'] = {};
obj['box'] = new THREE.Mesh( sphereGeometry, material );
obj['box'].castShadow = true;
obj['box'].receiveShadow = true;
scene.add(obj['box']);
var ambientLight = new THREE.AmbientLight(0xbbbbbb);
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(-100, 40, 100);
directionalLight.castShadow = true;
directionalLight.shadowOnly = true;
directionalLight.shadowDarkness = .5;
scene.add(directionalLight);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
document.body.appendChild(renderer.domElement);
projector = new THREE.Projector();
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
function onDocumentMouseDown( event ) {
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
// event.preventDefault();
console.log("Click.");
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects( [obj['box']] );
// if there is one (or more) intersections
if ( intersects.length > 0 )
{
console.log("Hit # " + toString( intersects[0].point ) );
console.log(intersects);
// change the color of the closest face.
intersects[ 0 ].face.color.setRGB( 0.8 * Math.random() + 0.2, 0, 0 );
intersects[ 0 ].object.geometry.colorsNeedUpdate = true;
}
}
function toString(v) { return "[ " + v.x + ", " + v.y + ", " + v.z + " ]"; }
var render = function() {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
console.log(camera);
console.log(obj['box'])
render();
</script>
</body>
I am hoping it is something simple that I just don't know about yet.
three.js r60
Here is the pattern to use when raycasting with either an orthographic camera or perspective camera:
var raycaster = new THREE.Raycaster(); // create once
var mouse = new THREE.Vector2(); // create once
...
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects, recursiveFlag );
three.js r.84
One more note that might save you some trouble. If you have a camera like this:
var camera = new THREE.OrthographicCamera(0, window.innerWidth, -window.innerHeight, 0, -100, 100);
Then during raycasting, be sure to move the ray origin.z to camera.far for it to hit anything in the entire visible range:
this.ray.origin.set(0, 0, 0);
this.camera.localToWorld(this.ray.origin);
this.ray.setFromCamera(this.mouseCoord, this.camera);
this.ray.origin.z = this.camera.far;