I'm trying to use a Raycaster to intercept particles in a PointCloud object.
The point cloud is being created as such:
... other initializations, like the scene object...
var geometry = new THREE.Geometry();
var sprite = THREE.ImageUtils.loadTexture("myAwesomeIcon");
for (i = 0; i < 100000; i++) {
var vertex = new THREE.Vector3();
vertex.x = 15000 * Math.random() - 1000;
vertex.y = 15000 * Math.random() - 1000;
vertex.z = 15000 * Math.random() - 1000;
geometry.vertices.push(vertex);
}
var material = new THREE.PointCloudMaterial({
size : 30,
sizeAttenuation : true,
map : sprite,
transparent : true
});
var particles = new THREE.PointCloud(geometry, material);
particles.sortParticles = true;
scene.add(particles);
The raycaster, in its turn, is created as such:
var raycaster = new THREE.Raycaster();
raycaster.params.PointCloud.threshold = 15;
However, given that I want the sizes of the particles to change based on the camera's proximity (sizeAttenuation : true), I need the threshold param to "adapt" to each particle.
Right now, it may detect the particle too soon (when it's closer to the camera), or too late (when it's further from the camera. By too soon/late I mean in respect to the mouse position over the particle.
I found this solution online, but it assumes that I know the sizes of each particle, which I don't. I only know the initial (non-attenuated) size.
Thank you very much.
Related
I'm trying to generate random planes that would be in random position in the screen and of random size. The problem I have is that I really don't understand the set.position values. What are they representing? I tried to search every where for an answer but couldn't find.
Here's my code for the planes:
var planes = [];
for (i = 0; i < 20; i++) {
var geometry = new THREE.PlaneGeometry(sizeWidth*((randomSO(1,20)*0.00001)), sizeHeight*((randomSO(1,10)*0.0001)));
var material = new THREE.MeshBasicMaterial( {color: 0x00ff00});
var plane = new THREE.Mesh(geometry, material);
plane.drawMode = THREE.TrianglesDrawMode;
plane.position.set(-10+i, 0, 0);
scene.add(plane);
planes[i] = plane;
}
The X-co-ordinate in above example isn't how I'd want it but is like that for testing. I would like to work according to screensize, that each plane would locate in random % of the width and height. The width of my screen is 1280 and now when I set the the x co-ordinate to let's say -20 it's already out of the screen. Should I set the Z co-ordinate of the camera differently? I really don't understand the position co-ordinates in this :-(
I'm working on a solar system in three.js and am curious if there is an easy way to make the labels for the planets I have below all show up the same size regardless of how far they are from the camera? I can't seem to find a solution to this. I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that. Seems like there would be an easier way to accomplish this?
Thanks!
Updated with answer from prisoner849. Works excellent!
I figure you could calculate the distance from each label to the camera then come up with some sort of scaling factor based on that.
And it's very simple. Let's say, a THREE.Sprite() object (label) is a child of a THREE.Mesh() object (planet), then in your animation loop you need to do
var scaleVector = new THREE.Vector3();
var scaleFactor = 4;
var sprite = planet.children[0];
var scale = scaleVector.subVectors(planet.position, camera.position).length() / scaleFactor;
sprite.scale.set(scale, scale, 1);
I've made a very simple example of the Solar System, using this technique.
For the benefit of future visitors, the transform controls example does exactly this:
https://threejs.org/examples/misc_controls_transform.html
Here's how its done in the example code:
var factor;
if ( this.camera.isOrthographicCamera ) {
factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
} else {
factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
}
handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 );
Finally I found the answer to your question:
First, create a DOM Element:
<div class="element">Not Earth</div>
Then set CSS styles for it:
.element {position: absolute; top:0; left:0; color: white}
// |-------------------------------| |-----------|
// make the element on top of canvas is
// the canvas black, so text
// must be white
After that, create moveDom() function and run it every time you render the scene requestAnimationFrame()
geometry is the geometry of the mesh
cube is the mesh you want to create label
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * innerWidth/2) + innerWidth/2;
vector.y = -(vector.y * innerHeight/2) + innerHeight/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
You can create a for loop to set label for all the mesh in your scene.
Because this trick only set 2D position of DOM Element, the size of label is the same even if you zoom (the label is not part of three.js scene).
Full test case: https://jsfiddle.net/0L1rpayz/1/
var renderer, scene, camera, cube, vector, geometry;
var ww = window.innerWidth,
wh = window.innerHeight;
function init(){
renderer = new THREE.WebGLRenderer({canvas : document.getElementById('scene')});
renderer.setSize(ww,wh);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50,ww/wh, 0.1, 10000 );
camera.position.set(0,0,500);
scene.add(camera);
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set( 0, 0, 500 );
scene.add(light);
//Vector use to get position of vertice
vector = new THREE.Vector3();
//Generate Not Earth
geometry = new THREE.BoxGeometry(50,50,50);
var material = new THREE.MeshLambertMaterial({color: 0x00ff00});
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
//Render my scene
render();
}
var moveDom = function(){
vector = geometry.vertices[0].clone();
vector.applyMatrix4(cube.matrix);
vector.project(camera);
vector.x = (vector.x * ww/2) + ww/2;
vector.y = -(vector.y * wh/2) + wh/2;
//Get the DOM element and apply transforms on it
document.querySelectorAll(".element")[0].style.webkitTransform = "translate("+vector.x+"px,"+vector.y+"px)";
document.querySelectorAll(".element")[0].style.transform = "translate("+vector.x+"px,"+vector.y+"px)";
};
var counter = 0;
var render = function (a) {
requestAnimationFrame(render);
counter++;
//Move my cubes
cube.position.x = Math.cos((counter+1*150)/200)*(ww/6+1*80);
cube.position.y = Math.sin((counter+1*150)/200)*(70+1*80);
cube.rotation.x += .001*1+.002;
cube.rotation.y += .001*1+.02;
//Move my dom elements
moveDom();
renderer.render(scene, camera);
};
init();
body,html, canvas{width:100%;height:100%;padding:0;margin:0;overflow: hidden;}
.element{color:white;position:absolute;top:0;left:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script>
<!-- My scene -->
<canvas id="scene"></canvas>
<div class="element">
<h1>Not Earth</h1>
</div>
If you downvote this, please tell me why. I will try my best to improve my posts.
If you are using spriteMaterial to present your text, you could try to set the sizeAttenuation attribute to false.
var spriteMaterial = new THREE.SpriteMaterial( { map: spriteMap, color: 0xffffff, sizeAttenuation:false } );
See more information from here:
https://threejs.org/docs/index.html#api/en/materials/SpriteMaterial.sizeAttenuation
I am trying to create a terrain from a heightmap with a "closed" bottom see the example here:
http://demos.playfuljs.com/terrain/
My terrain generation function is as so:
var img = document.getElementById("landscape-image");
var numSegments = img.width - 1; // We have one less vertex than pixel
var geometry = new THREE.PlaneGeometry(2400, 2400, numSegments, numSegments);
var material = new THREE.MeshLambertMaterial({
color: 0xccccff,
wireframe: false
});
plane = new THREE.Mesh(geometry, material);
plane.name = 'Terrain';
// set height of vertices
for (var i = 0; i < plane.geometry.vertices.length; i++) {
plane.geometry.vertices[i].z = (terrain[i]/255) * height;
}
geometry.computeFaceNormals();
geometry.computeVertexNormals();
plane.position.x = 0;
plane.rotation.x = 0;
plane.position.y = -10;
The problem I am having is how do I create that connected bottom part of the terrain with a THREE.PlaneGeometry. I can't extrude as:
The bottom must be flat if I extrude it will be bumpy like the
terrain.
Extrude takes a Shape object, not a Geometry object.
Really scratching my head here anyone done this before.
Edit: Maybe I could use two planes and merge them but how would I merge the side faces into a single piece of Geometery ?
P.S. The example draws straight to the canvas
create a plane for each side which has your amount of Segments in width and 1 in height. them set the top Vertices according to your heightmap
something like this
var geometry_l = new THREE.PlaneGeometry(2400, 0, numSegments, 1);
plane_l = new THREE.Mesh(geometry_l, material);
for (var i = 0; i < numSegments; i++) {
plane_l.geometry_l.vertices[i].z = (Terrain[0][i]/255) * height;
}
//Offset to the edge of your main plane
you might want to Change your Terrain Array to be two-dimensional for this. so you can always read the row you want.
I have this piece of code (see below) that I used to draw a cube with three.js:
// revolutions per second
var angularSpeed = 0.0;
var lastTime = 0;
function animate(){
// update
var time = (new Date()).getTime();
var timeDiff = time - lastTime;
var angleChange = angularSpeed * timeDiff * 2 * Math.PI / 1000;
cube.rotation.y += angleChange;
lastTime = time;
// render
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(animate);
}
// renderer
var container = document.getElementById("container");
var renderer = new THREE.WebGLRenderer();
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 700;
// scene
var scene = new THREE.Scene();
// cube Length, Height, Width
var cube = new THREE.Mesh(new THREE.CubeGeometry(400, 200, 200), new THREE.MeshBasicMaterial({
wireframe: true,
color: '#ff0000'
}));
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
// start animation
animate();
Does anyone know whether is it possible to allow the user to change the size of the cube by dragging the edges using the mouse?
Check this jsfiddle. I reused the structure of draggableCubes, plus little changes :
to drag the vertices i created vertexHelpers (little spheres);
to avoid maths the trick is to use an invisible plane to drag your objects/vertices on, perpendicular to the camera. To see it in action, just set plane.visible=true
now we can correctly drag a vertexHelper, its distance to the center of the cube changes. We just have to scale the cube at the same ratio :
Within the mouseMove listener's function it becomes:
if(SELECTED){
var intersects=raycaster.intersectObject(plane);
//so we get the mouse 3D coordinates in intersects[0].point
var previousDistance=SELECTED.position.sub(cube.position).length();
var increaseRatio=intersects[0].point.sub(cube.position).length()/previousDistance;
cube.scale.set(
cube.scale.x*increaseRatio,
cube.scale.y*increaseRatio,
cube.scale.z*increaseRatio
);
//then update the vertexHelpers position (copy the new vertices positions)
}
EDIT :
In your question you precisely ask to resize a cube by dragging its edges. I did not remember it in the example and did not think about it intuitively, but it can be done the same way.
However, given lineWidth is not implemented in ANGLE (the program used on windows to translate WebGL), it is not easy to pick lines with a 1px-width. I remember a threejs example I could not find, where a geometry is associated to the line so it looks outlined. Basically you could do it by creating a cylinder as custom 'edgesHelpers' (i'm precisely not talking about the THREE.EdgesHelper) and they have to be resized each time the cube is too.
In your setup code add an eventlistener for mousemove:
// Event Handlers
renderer.domElement.addEventListener('mousemove', onDocumentMouseMove, false);
Then in the event handler code, you can check which object is selected and adjust the scale parameter.
function onDocumentMouseMove(event) {
...
var selected = raycaster.intersectObjects(objects);
if (selected.length > 0) {
// Do things to the scale parameter(s)... Just for demo purposes
selected.scale.x = selected.scale.x + selected[0].point.sub(offset).x / 1000;
selected.scale.y = selected.scale.y + selected[0].point.sub(offset).y / 1000;
return;
}
...
}
Since I am typing pseudo code here, all too easy to make an error, so I have left a test version here for you to try: http://www.numpty.co.uk/cubedrag.html
As you can see, size of selected object changes in horrible ways with the dragging of the mouse. You have me interested, so will look into making it proportional to movement if I get more time.
I am trying to render a large forest of 100,000+ very simple-looking trees in ThreeJS. Creating many individual meshes is of course out of the question. My current method uses GeometryUtils.merge to create one large geometry which reduces the number of draw calls and this works pretty well. But approaching 100k, it bogs down. I need to improve performance further and I have a feeling there may be another trick or two to increase performance by another factor of 10 or more.
The code is below and I've also created a JSFiddle demonstrating my current technique: http://jsfiddle.net/RLtNL/
//tree geometry (two intersecting y-perpendicular triangles)
var triangle = new THREE.Shape();
triangle.moveTo(5, 0);
triangle.lineTo(0, 12);
triangle.lineTo(-5, 0);
triangle.lineTo(5, 0);
var tree_geometry1 = new THREE.ShapeGeometry(triangle);
var matrix = new THREE.Matrix4();
var tree_geometry2 = new THREE.ShapeGeometry(triangle);
tree_geometry2.applyMatrix(matrix.makeRotationY(Math.PI / 2));
//tree material
var basic_material = new THREE.MeshBasicMaterial({color: 0x14190f});
basic_material.color = new THREE.Color(0x14190f);
basic_material.side = THREE.DoubleSide;
//merge into giant geometry for max efficiency
var forest_geometry = new THREE.Geometry();
var dummy = new THREE.Mesh();
for (var i = 0; i < 1000; i++)
{
dummy.position.x = Math.random() * 1000 - 500;
dummy.position.z = Math.random() * 1000 - 500;
dummy.position.y = 0;
dummy.geometry = tree_geometry1;
THREE.GeometryUtils.merge(forest_geometry, dummy);
dummy.geometry = tree_geometry2;
THREE.GeometryUtils.merge(forest_geometry, dummy);
}
//create mesh and add to scene
var forest_mesh = new THREE.Mesh(forest_geometry, basic_material);
scene.add(forest_mesh);
Can anyone suggest further techniques to make this load and render more quickly?
How about using billboards to render the trees? The 2D nature of billboards seem to suit this particular problem. Create a simple png tree texture with transparency, and add each tree as a PointCloud object - http://threejs.org/docs/#Reference/Objects/PointCloud
Most low-end graphics cards can render well over 10,000 billboard objects without a drop in framerate. I've updated your code using the billboards technique (changing the number of trees to 10,000 and using a 100 pixel high tree texture): http://jsfiddle.net/wayfarer_boy/RLtNL/8/ - extract of the code below:
geometry = new THREE.Geometry();
sprite = new THREE.Texture(image);
sprite.needsUpdate = true;
for (var i = 0; i < 10000; i++) {
var vertex = new THREE.Vector3();
vertex.x = Math.random() * 1000 - 500;
vertex.y = 0;
vertex.z = Math.random() * 1000 - 500;
geometry.vertices.push(vertex);
}
material = new THREE.PointCloudMaterial({
size: 50,
sizeAttenuation: true,
map: sprite,
transparent: true,
alphaTest: 0.5
});
particles = new THREE.PointCloud(geometry, material);
// particles.sortParticles = true;
// Removed line above and using material.alphaTest instead
// Thanks #WestLangley
scene.add(particles);
renderer = new THREE.WebGLRenderer({
clearAlpha: 1,
alpha: true
});