Three.js, moving a partical along an EllipseCurve - three.js

I know questions related to my problem have been asked and answered before but three.js changed a lot in the last couple years and I'm having trouble finding what I need in the currently available examples.
I have an elliptical curve that I'd like to run particles along. My code runs without error but it doesn't actually move the particle anywhere. What am I missing?
var t = 0;
var curve = new THREE.EllipseCurve( .37, .15, .35, .25, 150, 450, false, 0 );
var points = curve.getPoints( 50 );
var curveGeometry = new THREE.BufferGeometry().setFromPoints( points );
var particleGeometry = new THREE.Geometry();
var particleMap = new THREE.TextureLoader().load( "/img/spark.png" );
var vertex = new THREE.Vector3();
vertex.x = points[0].x;
vertex.y = points[0].y;
vertex.z = 0;
particleGeometry.vertices.push(vertex);
particleMaterial = new THREE.PointsMaterial({
size: .05,
map: particleMap,
blending: THREE.AdditiveBlending,
depthTest: false,
transparent : true
});
particles = new THREE.Points( particleGeometry, particleMaterial );
scene.add(particles);
animate();
function animate() {
if (t <= 1) {
particles.position = curveGeometry.getPointAt(t)
t += 0.005
} else {
t = 0;
}
requestAnimationFrame( animate );
render();
}
function render() {
renderer.render( scene, camera );
}

Just a rough concept of how you can do it, using THREE.Geometry():
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 50);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0x404040);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var grid = new THREE.GridHelper(40, 40, "white", "gray");
grid.rotation.x = Math.PI * -0.5;
scene.add(grid);
var curve = new THREE.EllipseCurve(0, 0, 20, 20, 0, Math.PI * 2, false, 0);
// using of .getPoints(division) will give you a set of points of division + 1
// so, let's get the points manually :)
var count = 10;
var inc = 1 / count;
var pointAt = 0;
var points = [];
for (let i = 0; i < count; i++) {
let point = curve.getPoint(pointAt); // get a point of THREE.Vector2()
point.z = 0; // geometry needs points of x, y, z; so add z
point.pointAt = pointAt; // save position along the curve in a custom property
points.push(point);
pointAt += inc; // increment position along the curve for next point
}
var pointsGeom = new THREE.Geometry();
pointsGeom.vertices = points;
console.log(points);
var pointsObj = new THREE.Points(pointsGeom, new THREE.PointsMaterial({
size: 1,
color: "aqua"
}));
scene.add(pointsObj);
var clock = new THREE.Clock();
var time = 0;
render();
function render() {
requestAnimationFrame(render);
time = clock.getDelta();
points.forEach(p => {
p.pointAt = (p.pointAt + time * 0.1) % 1; // it always will be from 0 to 1
curve.getPoint(p.pointAt, p); //re-using of the current point
});
pointsGeom.verticesNeedUpdate = true;
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Related

I am having a problem with three.js collision detection

We are creating a three.js based game where players can eat food, currently we have a collision script but it is not working properly. Any help to get it working so our players can eat would be greatly appreciated.
The code is below.
Code snippet:
//sorry in advance for the crazy code structure o.o
//variables
var scene, renderer, rayCaster;
var WORLD, floor, FOOD, MWORLD;
var plr, camera, controls;
function debugupdate()
{
window.plr = plr
window.floor = floor
window.WORLD = WORLD
window.camera = camera
window.controls = controls
window.scene = scene
window.FOOD = FOOD
}
setInterval(debugupdate, 1000)
//setup scene for gameplay
function InitGame()
{
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
rayCaster = new THREE.Raycaster();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
camera.position.y = 8;
camera.position.z = 8;
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.autoRotate = false;
controls.enablePan = false;
//controls.update() must be called after any manual changes to the camera's transform
//camera.position.set( 0, 20, 100 );
//controls.update();
//MWORLD = stuff mouse can detect
MWORLD = new THREE.Object3D();
MWORLD.name = 'MWORLD'
var floorgeo = new THREE.BoxGeometry(30, 0.5, 30);
var floormat = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
floor = new THREE.Mesh( floorgeo, floormat );
MWORLD.add(floor)
WORLD = new THREE.Object3D();
WORLD.add( MWORLD );
WORLD.name = 'WORLD';
scene.add(WORLD);
}
InitGame();
//Mouse Stuff
var MousePos;
var PlrTarget;
document.addEventListener('mousemove', MouseToWorld, false);
function MouseToWorld(event) {
event.preventDefault();
var mouse = {};
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject( camera );
var dir = vector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
MousePos = camera.position.clone().add( dir.multiplyScalar( distance ) );
//console.log(MousePos)
rayCaster.setFromCamera(mouse, camera);
var intersects = rayCaster.intersectObjects(WORLD.getObjectByName('MWORLD').children, true);
if (intersects.length > 0)
// console.log(intersects[0].point);
PlrTarget = intersects[0].point
// Make the sphere follow the mouse
// mouseMesh.position.set(event.clientX, event.clientY, 0);
};
//Food Parent
FOOD = new THREE.Object3D();
FOOD.name = 'FOOD'
WORLD.add(FOOD)
var fid = -1
//Add Food Object should this be different?
function AddFood()
{
fid = fid + 1
var colors = ['red', 'blue', 'orange', 'yellow', 'pink', 'cyan'];
var geometry = new THREE.SphereGeometry( 0.05 * 1.5, 32 / 4, 32 / 4 );
var material = new THREE.MeshBasicMaterial( {color: colors[Math.floor(Math.random() * colors.length)]} );
var sphere = new THREE.Mesh( geometry, material );
var geometry = new THREE.BoxGeometry( 0.1 * 1.5, 10, 0.1 * 1.5);//BoxGeometry for collision detection spheres were lagging like crazy :(
var material = new THREE.MeshBasicMaterial( {color: 'red'} );
material.transparent = true
material.opacity = 0.2
var cube = new THREE.Mesh( geometry, material );
var foodrange = 15
cube.add(sphere);
cube.position.y = 0.25
cube.position.z = ((Math.random() * foodrange + 1) * (Math.round(Math.random()) * 2 - 1));
cube.position.x = ((Math.random() * foodrange + 1) * (Math.round(Math.random()) * 2 - 1));
cube.name = 'f' + fid
WORLD.getObjectByName('FOOD').add(cube)
}
//adds lots of food
function InitFood()
{
var i
for(i = 0; i < 150; i++)
{
AddFood();
}
}
InitFood();
//Eats the food working I think...
function ConsumeFood(fid)
{
FOOD.remove(FOOD.getObjectByName(fid))
plr.scale.x = plr.scale.x + 0.01
plr.scale.y = plr.scale.y + 0.01
plr.scale.z = plr.scale.z + 0.01
}
//Creates Player
function CreatePlr()
{
var geometry = new THREE.SphereGeometry( 0.5, 32, 32);//32 / 2
var material = new THREE.MeshBasicMaterial( {color: 0xffff00} );
var sphere = new THREE.Mesh( geometry, material );
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( {color: 'blue'} );
material.transparent = true
material.opacity = 0.2
var cube = new THREE.Mesh( geometry, material );
plr = new THREE.Object3D();
plr.add(sphere);
plr.add(cube);
scene.add(plr)
controls.target = plr.position
}
CreatePlr();
setTimeout(Eat, 1500)
//DETECT FOOD PLEASE HELP :(
//sometimes works ok you have to have the food fairly deep within the player to detect
//never eats as soon as you touch it
//sometimes totally fails to detect piece of food until you go over it multiple times
//sometimes random pieces of food are eaten even though they are not touched
function Eat() {
var originPoint = plr.position.clone();
for (var vertexIndex = 0; vertexIndex < plr.children[1].geometry.vertices.length; vertexIndex++)
{
var localVertex = plr.children[1].geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( plr.children[1].matrix );
var directionVector = globalVertex.sub( plr.position );
var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( FOOD.children );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) //if your touching the food or its in your player eat it
collisionResults.forEach(function(food){
console.log(food.object)//shows in console that food was detected and what piece of food it was
ConsumeFood(food.object.name)//consume food based on name (f1, f2, f3)
})
}
setTimeout(Eat, 50)
}
var Time = new THREE.Clock();
function PlrLerpSpeed(speed)
{
var distance = plr.position.distanceTo(PlrTarget);
var finalSpeed = (distance / speed);
return Time.deltaTime / finalSpeed
}
var animate = function () {
requestAnimationFrame( animate );
if(PlrTarget){
plr.lookAt(PlrTarget)
//plr.position.lerp(PlrTarget, PlrLerpSpeed(1));
plr.position.lerp(PlrTarget, 0.01 / (plr.position.distanceTo(PlrTarget) / 2));
}
controls.update();
//plr.position = MousePos
/*var speed = 5; // units a second, the speed we want
var currentPoint = new THREE.Vector3(); // we will re-use it
// this part is in a function of event listener of, for example, a button
currentPoint.copy(plr.position); // cube is the object to move
var distance = currentPoint.distanceTo(MousePos)
var duration = (distance / speed) * 1000; // in milliseconds
new TWEEN.Tween(plr.position)
.to(MousePos, duration) // destinationPoint is the object of destination
.start();
*/
renderer.render( scene, camera );
};
animate();
body { margin: 0; }
canvas { display: block; }
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
Since your player and food objects are basically spheres, you can implement a super fast and accurate collision detection with bounding spheres. Try it like so:
//variables
var scene, renderer, rayCaster;
var WORLD, floor, FOOD, MWORLD;
var plr, camera, controls;
function debugupdate() {
window.plr = plr
window.floor = floor
window.WORLD = WORLD
window.camera = camera
window.controls = controls
window.scene = scene
window.FOOD = FOOD
}
setInterval(debugupdate, 1000)
//setup scene for gameplay
function InitGame() {
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
rayCaster = new THREE.Raycaster();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 8;
camera.position.z = 8;
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.autoRotate = false;
controls.enablePan = false;
//controls.update() must be called after any manual changes to the camera's transform
//camera.position.set( 0, 20, 100 );
//controls.update();
//MWORLD = stuff mouse can detect
MWORLD = new THREE.Object3D();
MWORLD.name = 'MWORLD'
var floorgeo = new THREE.BoxGeometry(30, 0.5, 30);
var floormat = new THREE.MeshBasicMaterial({
color: 0x00ff00
});
floor = new THREE.Mesh(floorgeo, floormat);
MWORLD.add(floor)
WORLD = new THREE.Object3D();
WORLD.add(MWORLD);
WORLD.name = 'WORLD';
scene.add(WORLD);
}
InitGame();
//Mouse Stuff
var MousePos;
var PlrTarget;
document.addEventListener('mousemove', MouseToWorld, false);
function MouseToWorld(event) {
event.preventDefault();
var mouse = {};
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);
var dir = vector.sub(camera.position).normalize();
var distance = -camera.position.z / dir.z;
MousePos = camera.position.clone().add(dir.multiplyScalar(distance));
//console.log(MousePos)
rayCaster.setFromCamera(mouse, camera);
var intersects = rayCaster.intersectObjects(WORLD.getObjectByName('MWORLD').children, true);
if (intersects.length > 0)
// console.log(intersects[0].point);
PlrTarget = intersects[0].point
// Make the sphere follow the mouse
// mouseMesh.position.set(event.clientX, event.clientY, 0);
};
//Food Parent
FOOD = new THREE.Object3D();
FOOD.name = 'FOOD'
WORLD.add(FOOD)
var fid = -1
//Add Food Object should this be different?
function AddFood() {
fid = fid + 1
var colors = ['red', 'blue', 'orange', 'yellow', 'pink', 'cyan'];
var geometry = new THREE.SphereGeometry(0.05 * 1.5, 32 / 4, 32 / 4);
var material = new THREE.MeshBasicMaterial({
color: colors[Math.floor(Math.random() * colors.length)]
});
var sphere = new THREE.Mesh(geometry, material);
var foodrange = 15
sphere.position.y = 0.25
sphere.position.z = ((Math.random() * foodrange + 1) * (Math.round(Math.random()) * 2 - 1));
sphere.position.x = ((Math.random() * foodrange + 1) * (Math.round(Math.random()) * 2 - 1));
sphere.name = 'f' + fid
WORLD.getObjectByName('FOOD').add(sphere)
}
//adds lots of food
function InitFood() {
var i
for (i = 0; i < 150; i++) {
AddFood();
}
}
InitFood();
//Eats the food working I think...
function ConsumeFood(fid) {
FOOD.remove(FOOD.getObjectByName(fid))
plr.scale.x = plr.scale.x + 0.01
plr.scale.y = plr.scale.y + 0.01
plr.scale.z = plr.scale.z + 0.01
}
//Creates Player
function CreatePlr() {
var geometry = new THREE.SphereGeometry(0.5, 32, 32); //32 / 2
var material = new THREE.MeshBasicMaterial({
color: 0xffff00
});
plr = new THREE.Mesh(geometry, material);
scene.add(plr)
controls.target = plr.position
}
CreatePlr();
setTimeout(Eat, 1500)
var spherePlayer = new THREE.Sphere();
var sphereFood = new THREE.Sphere();
//DETECT FOOD PLEASE HELP :(
//sometimes works ok you have to have the food fairly deep within the player to detect
//never eats as soon as you touch it
//sometimes totally fails to detect piece of food until you go over it multiple times
//sometimes random pieces of food are eaten even though they are not touched
function Eat() {
spherePlayer.copy( plr.geometry.boundingSphere ).applyMatrix4( plr.matrixWorld );
for ( var i = 0; i < FOOD.children.length; i ++ ) {
var food = FOOD.children[ i ];
sphereFood.copy( food.geometry.boundingSphere ).applyMatrix4( food.matrixWorld );
if ( spherePlayer.intersectsSphere( sphereFood ) === true ) {
ConsumeFood(food.name);
}
}
setTimeout(Eat, 50)
}
var Time = new THREE.Clock();
function PlrLerpSpeed(speed) {
var distance = plr.position.distanceTo(PlrTarget);
var finalSpeed = (distance / speed);
return Time.deltaTime / finalSpeed
}
var animate = function() {
requestAnimationFrame(animate);
if (PlrTarget) {
plr.lookAt(PlrTarget)
//plr.position.lerp(PlrTarget, PlrLerpSpeed(1));
plr.position.lerp(PlrTarget, 0.01 / (plr.position.distanceTo(PlrTarget) / 2));
}
controls.update();
//plr.position = MousePos
/*var speed = 5; // units a second, the speed we want
var currentPoint = new THREE.Vector3(); // we will re-use it
// this part is in a function of event listener of, for example, a button
currentPoint.copy(plr.position); // cube is the object to move
var distance = currentPoint.distanceTo(MousePos)
var duration = (distance / speed) * 1000; // in milliseconds
new TWEEN.Tween(plr.position)
.to(MousePos, duration) // destinationPoint is the object of destination
.start();
*/
renderer.render(scene, camera);
};
animate();
body { margin: 0; }
canvas { display: block; }
<script src="https://cdn.jsdelivr.net/npm/three#0.116.1/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.116.1/examples/js/controls/OrbitControls.js"></script>

How do you plot elliptic paraboloid in ThreeJS

I am trying to create a elliptic paraboloid in threeJS using parametric curves. Here is the link to what I am looking to plot:
https://mathinsight.org/level_sets.
Here is what I have right now:
Here is the current function definition, however it seems to be producing a different result.
let planeCreator = function(u,v,w){
var height = 15;
var size = 20;
var u = u * height;
var v = (v * 2 * Math.PI);
var x = size * Math.sqrt(u) * Math.cos(v);
var y = u;
var z = size * Math.sqrt(u) * Math.sin(v);
w.set(x,-y,z);
};
let geometry = new THREE.ParametricBufferGeometry( planeCreator, 50, 50 );
geometry.center();
let material = new THREE.MeshPhongMaterial( {
color: 0xff0000,
side: THREE.DoubleSide,
wireframe: true
} );
let object = new THREE.Mesh( geometry, material );
scene.add( object );
Here is what is currently plotted:
As an option (without parametric geometries):
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(-4, 2, 5);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var planeGeom = new THREE.PlaneBufferGeometry(4, 4, 20, 20);
planeGeom.rotateX(-Math.PI * 0.5);
var v = new THREE.Vector3();
var positions = planeGeom.attributes.position;
for (var i = 0; i < positions.count; i++) {
v.fromBufferAttribute(positions, i);
positions.setY(i, (-(v.x * v.x) - (2 * v.z * v.z)) * 0.25);
}
planeGeom.center();
planeGeom.computeVertexNormals();
var ellipticParaboloidSurface = new THREE.Mesh(planeGeom, new THREE.MeshNormalMaterial({
side: THREE.DoubleSide
}));
scene.add(ellipticParaboloidSurface);
var boxHelper = new THREE.BoxHelper(ellipticParaboloidSurface, "yellow");
scene.add(boxHelper);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
})
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

set camera relative to point collection

I'm trying to set the camera to be 3 units away from a collection of points I would like this to be relative to the group of points since the points will change later on.
So far I can retrieve x,y,z coordinates from the database and are returned using djangos {{coord_x}} I will have to return the correct length, (I could do this on the python side - len()) for now the database query is limited to 20 rows. These points are brought into three.js using a for loop.
How do I set a camera relative to the objects? Do I need to calculate a bounding box?
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.001, 100000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// allow resizing of the window
window.addEventListener('resize', function()
{
var width = window.innerWidth;
var height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
//Controls
controls = new THREE.OrbitControls(camera, renderer.domElement)
//create the shape
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({color: 0x007654, wireframe: false});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
var numpoints = 20;
var dots = []; //If you want to use for other task
for (var i = 0 ; i < numpoints ; i++) {
var x = "{{coord_x}}";
var y = "{{coord_y}}";
var z = "{{coord_z}}";
// var x = Math.random() * (0 - 1) + 1
// var y = Math.random() * (0 - 1) + 1
// var z = Math.random() * (0 - 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);
}
camera.position.z = 30
//game logic, allow rotation
var update = function()
{
//cube.rotation.x += 0.00;
//cube.rotation.y += 0.0025;
//dot.rotation.x += 0.00;
//dot.rotation.y += 0.005;
};
// draw scene
var render = function()
{
renderer.render(scene, camera);
};
// run game loop (update, render, repeat)
var GameLoop = function()
{
requestAnimationFrame(GameLoop);
update();
render();
};
GameLoop();
</script>
That's how you can work with THREE.Sphere() object to set the position of your camera:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geom = new THREE.Geometry();
for (let i = 0; i < 100; i++) {
geom.vertices.push(
new THREE.Vector3(
Math.random() - 0.5,
Math.random() - 0.5,
Math.random() - 0.5
).multiplyScalar(10)
);
}
var points = new THREE.Points(geom, new THREE.PointsMaterial({
size: 0.25,
color: "aqua"
}));
scene.add(points);
var sphere = new THREE.Sphere().setFromPoints(geom.vertices);
console.log(sphere);
camera.position.copy(sphere.center);
camera.position.z += sphere.radius / Math.sin(THREE.Math.degToRad(camera.fov / 2));
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Can not get uniform speed when camera move along the curve

I want to make camera move along the curve,it works, but when pass the turning corner,camera speed changed,looks like slowly.
curve = new THREE.CatmullRomCurve3(vectors);
curve.type = 'catmullrom';
curve.tension = 0.2;
this.MKY.Camera.current = this.roamCamera;
cameraWrap.add(this.roamCamera);
this.MKY.scene.add(cameraWrap);
this.MKY.update.push(roam);
function roam() {
if(!isAutoRoam){return}
if(progress>1 || progress==1){
progress = 0;
return
}
progress += 0.0005;
var position = curve.getPointAt(progress);
position.y += 1.5;
var tangent = curve.getTangentAt(progress);
cameraWrap.position.copy(position);
cameraWrap.lookAt(position.sub(tangent));
};
getPointAt returns a vector for point at a relative position in curve according to arc length. I think if progress not change ,i will get the average speed,but it is not. I do not understend.
There is the approach, using .getUtoTmapping() method of THREE.Curve() (in the example, it's THREE.CatmullRomCurve3).
The documentation says:
.getUtoTmapping ( u, distance )
Given u in the range ( 0 .. 1 ), returns t also in the range ( 0 .. 1 ). u and t can then be used to give you points which are equidistant from the ends of the curve, using .getPoint.
So, when you provide the second parameter in this method, then, if I got it correctly from the source code, it ignores the first parameter, thus you can find the point on your curve by the distance on it.
In the given picture:
small yellow points - points, taken with .getPoints() method;
big maroon points - points, whose distance between each other along the curve is 1 unit;
The code for the maroon points:
var unitPoints = [];
for (let i = 0; i < spline.getLength(); i++){
let p = spline.getUtoTmapping(0, i);
let p1 = spline.getPoint(p);
unitPoints.push(p1);
}
var unitPointsGeometry = new THREE.Geometry();
unitPointsGeometry.vertices = unitPoints;
var units = new THREE.Points(unitPointsGeometry, new THREE.PointsMaterial({size: .125, color: "maroon"}));
scene.add(units);
Look at the source code of the code snippet and pay attention to the getProgress() function.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, .1, 1000);
camera.position.set(0, 1.5, 3);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x181818);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper(4, 8));
var spline = new THREE.CatmullRomCurve3(
[
new THREE.Vector3(-2, 0, 0),
new THREE.Vector3(-1.9, .1, .1),
new THREE.Vector3(1, 1, 1),
new THREE.Vector3(0, -1, -2),
new THREE.Vector3(2, 0, 1)
]
);
spline.closed = true;
var splinePoints = spline.getPoints(200);
var lineGeom = new THREE.Geometry();
lineGeom.vertices = splinePoints;
var line = new THREE.Line(lineGeom, new THREE.LineBasicMaterial({
color: "orange"
}));
scene.add(line);
var sPoints = new THREE.Points(lineGeom, new THREE.PointsMaterial({
size: .0312,
color: "yellow"
}));
scene.add(sPoints);
var unitPoints = [];
for (let i = 0; i < spline.getLength(); i++) {
let p = spline.getUtoTmapping(0, i);
let p1 = spline.getPoint(p);
unitPoints.push(p1);
}
var unitPointsGeometry = new THREE.Geometry();
unitPointsGeometry.vertices = unitPoints;
var units = new THREE.Points(unitPointsGeometry, new THREE.PointsMaterial({
size: .125,
color: "maroon"
}));
scene.add(units);
var marker = new THREE.Mesh(new THREE.SphereGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
color: "red",
wireframe: true
}));
marker.geometry.translate(0, 0, 0.0625);
marker.geometry.vertices[2].z = 0.25;
marker.geometry.vertices[4].z = 0;
scene.add(marker);
var markerLineGeometry = new THREE.Geometry();
markerLineGeometry.vertices.push(new THREE.Vector3(), new THREE.Vector3());
var line = new THREE.Line(markerLineGeometry, new THREE.LineBasicMaterial({
color: "white"
}));
scene.add(line);
var clock = new THREE.Clock();
var progress = 0;
var totalLength = spline.getLength();
var speed = .66; // unit a second
var ratio = speed / totalLength;
var shift = 0;
var basePoint = 0;
var lookAtPoint = 0;
var oldPosition = spline.getPoint(0);
var speedVector = new THREE.Vector3();
function setProgress(delta) {
if (progress > totalLength) progress = 0;
shift = progress + speed * 2;
shift = shift > totalLength ? shift - totalLength : shift;
basePoint = spline.getUtoTmapping(0, progress);
lookAtPoint = spline.getUtoTmapping(0, shift);
line.geometry.vertices[0].copy(spline.getPoint(basePoint));
line.geometry.vertices[1].copy(spline.getPoint(lookAtPoint));
line.geometry.verticesNeedUpdate = true;
marker.position.copy(line.geometry.vertices[0]);
marker.lookAt(line.geometry.vertices[1]);
progress += speed * delta;
}
render();
function render() {
requestAnimationFrame(render);
setProgress(clock.getDelta());
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Three.js - Create new mesh from certain faces/vertices of another mesh

I´ve been several days struggling with a particular Three.js issue, and I cannot find any way to do it. This is my case:
1) I have a floating mesh, formed by several triangled faces. This mesh is created from the geometry returned by a loader, after obtaining its vertices and faces using getAttribute('position'): How to smooth mesh triangles in STL loaded BufferGeometry
2) What I want to do now is to "project" the bottom face agains the floor.
3) Later, with this new face added, create the resulting mesh of filling the space between the 3 vertices of both faces.
I already have troubles in step 2... To create a new face I´m supossed to have its 3 vertices already added to geometry.vertices. I did it, cloning the original face vertices. I use geometry.vertices.push() results to know their new indexes, and later I use that indexes (-1) to finally create the new face. But its shape is weird, also the positions and the size. I think I´m not getting the world/scene/vector position equivalence theory right :P
I tried applying this, with no luck:
How to get the absolute position of a vertex in three.js?
Converting World coordinates to Screen coordinates in Three.js using Projection
http://barkofthebyte.azurewebsites.net/post/2014/05/05/three-js-projecting-mouse-clicks-to-a-3d-scene-how-to-do-it-and-how-it-works
I discovered that if I directly clone the full original face and simply add it to the mesh, the face is added but in the same position, so I cannot then change its vertices to place it on the floor (or at least without modifying the original face vertices!). I mean, I can change their x, y, z properties, but they are in a very small measure that doesn´t match the original mesh dimensions.
Could someone help me get this concept right?
EDIT: source code
// Create geometry
var geo = new THREE.Geometry();
var geofaces = [];
var geovertices = [];
original_geometry.updateMatrixWorld();
for(var index in original_geometry.faces){
// Get original face vertexNormals to know its 3 vertices
var face = original_geometry[index];
var vertexNormals = face.vertexNormals;
// Create 3 new vertices, add it to the array and then create a new face using the vertices indexes
var vertexIndexes = [null, null, null];
for (var i = 0, l = vertexNormals.length; i < l; i++) {
var vectorClone = vertexNormals[i].clone();
vectorClone.applyMatrix4( original_geometry.matrixWorld );
//vectorClone.unproject(camera); // JUST TESTING
//vectorClone.normalize(); // JUST TESTING
var vector = new THREE.Vector3(vectorClone.x, vectorClone.z, vectorClone.y)
//vector.normalize(); // JUST TESTING
//vector.project(camera); // JUST TESTING
//vector.unproject(camera); // JUST TESTING
vertexIndexes[i] = geovertices.push( vector ) - 1;
}
var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
geofaces.push(newFace);
}
// Assign filled arrays to the geometry
geo.faces = geofaces;
geo.vertices = geovertices;
geo.mergeVertices();
geo.computeVertexNormals();
geo.computeFaceNormals();
// Create a new mesh with resulting geometry and add it to scene (in this case, to the original mesh to keep the positions)
new_mesh = new THREE.Mesh( geo, new THREE.MeshFaceMaterial(material) ); // material is defined elsewhere
new_mesh.position.set(0, -100, 0);
original_mesh.add( new_mesh );
I created a fully operational JSFiddle with the case to try things and see the problem more clear. With this STL (smaller than my local example) I cannot even see the badly cloned faces added to the scene.. Maybe they are too small or out of focus.
Take a look to the calculateProjectedMesh() function, here is where I tried to clone and place the bottom faces (already detected because they have a different materialIndex):
JSFiddle: https://jsfiddle.net/tc39sgo1/
var container;
var stlPath = 'https://dl.dropboxusercontent.com/s/p1xp4lhy4wxmf19/Handle_Tab_floating.STL';
var camera, controls, scene, renderer, model;
var mouseX = 0,
mouseY = 0;
var test = true;
var meshPlane = null, meshStl = null, meshCube = null, meshHang = null;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
/*THREE.FrontSide = 0;
THREE.BackSide = 1;
THREE.DoubleSide = 2;*/
var materials = [];
materials.push( new THREE.MeshPhongMaterial({color : 0x00FF00, side:0, shading: THREE.FlatShading, transparent: true, opacity: 0.9, overdraw : true, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0xFF0000, transparent: true, opacity: 0.8, side:0, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
materials.push( new THREE.MeshPhongMaterial({color : 0x0000FF, side:2, shading: THREE.FlatShading, overdraw : true, metal: false, wireframe: false}) );
var lineMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.05 });
init();
animate();
function webglAvailable() {
try {
var canvas = document.createElement('canvas');
return !!(window.WebGLRenderingContext && (
canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
} catch (e) {
return false;
}
}
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 100000000);
camera.position.x = 1500;
camera.position.z = -2000;
camera.position.y = 1000;
controls = new THREE.OrbitControls(camera);
// scene
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight(0x101030); //0x101030
scene.add(ambient);
var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(0, 3, 0).normalize();
scene.add(directionalLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(0, 1, -2).normalize();
scene.add(directionalLight);
if (webglAvailable()) {
renderer = new THREE.WebGLRenderer();
} else {
renderer = new THREE.CanvasRenderer();
}
renderer.setClearColor( 0xCDCDCD, 1 );
// renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
document.addEventListener('mousemove', onDocumentMouseMove, false);
window.addEventListener('resize', onWindowResize, false);
createPlane(500, 500);
createCube(500);
loadStl();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfX) / 2;
mouseY = (event.clientY - windowHalfY) / 2;
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
function createPlane(width, height) {
var planegeometry = new THREE.PlaneBufferGeometry(width, height, 0, 0);
var material = new THREE.MeshLambertMaterial({
color: 0xFFFFFF,
side: THREE.DoubleSide
});
planegeometry.computeBoundingBox();
planegeometry.center();
meshPlane = new THREE.Mesh(planegeometry, material);
meshPlane.rotation.x = 90 * (Math.PI/180);
//meshPlane.position.y = -height/2;
scene.add(meshPlane);
}
function createCube(size) {
var geometry = new THREE.BoxGeometry( size, size, size );
geometry.computeFaceNormals();
geometry.mergeVertices();
geometry.computeVertexNormals();
geometry.center();
var material = new THREE.MeshPhongMaterial({
color: 0xFF0000,
opacity: 0.04,
transparent: true,
wireframe: true,
side: THREE.DoubleSide
});
meshCube = new THREE.Mesh(geometry, material);
meshCube.position.y = size/2;
scene.add(meshCube);
}
function loadStl() {
var loader = new THREE.STLLoader();
loader.load( stlPath, function ( geometry ) {
// Convert BufferGeometry to Geometry
var geometry = new THREE.Geometry().fromBufferGeometry( geometry );
geometry.computeBoundingBox();
geometry.computeVertexNormals();
geometry.center();
var faces = geometry.faces;
for(var index in faces){
var face = faces[index];
var faceNormal = face.normal;
var axis = new THREE.Vector3(0,-1,0);
var angle = Math.acos(axis.dot(faceNormal));
var angleReal = (angle / (Math.PI/180));
if(angleReal <= 70){
face.materialIndex = 1;
}
else{
face.materialIndex = 0;
}
}
geometry.computeFaceNormals();
geometry.computeVertexNormals();
meshStl = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
meshStl.position.x = 0;
meshStl.position.y = 400;
scene.add( meshStl );
// Once loaded, calculate projections mesh
calculateProjectedMesh();
});
}
function calculateProjectedMesh(){
var geometry = meshStl.geometry;
var faces = geometry.faces;
var vertices = geometry.vertices;
var geometry_projected = new THREE.Geometry();
var faces_projected = [];
var vertices_projected = [];
meshStl.updateMatrixWorld();
for(var index in faces){
var face = faces[index];
// This are the faces
if(face.materialIndex == 1){
var vertexIndexes = [face.a, face.b, face.c];
for (var i = 0, l = vertexIndexes.length; i < l; i++) {
var relatedVertice = vertices[ vertexIndexes[i] ];
var vectorClone = relatedVertice.clone();
console.warn(vectorClone);
vectorClone.applyMatrix4( meshStl.matrixWorld );
////////////////////////////////////////////////////////////////
// TEST: draw line
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
//geometry.vertices.push(new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z));
geometry.vertices.push(new THREE.Vector3(vectorClone.x, meshPlane.position.y, vectorClone.z));
var line = new THREE.Line(geometry, lineMaterial);
scene.add(line);
console.log("line added");
////////////////////////////////////////////////////////////////
vectorClone.y = 0;
var vector = new THREE.Vector3(vectorClone.x, vectorClone.y, vectorClone.z);
vertexIndexes[i] = vertices_projected.push( vector ) - 1;
}
var newFace = new THREE.Face3( vertexIndexes[0], vertexIndexes[1], vertexIndexes[2] );
newFace.materialIndex = 2;
faces_projected.push(newFace);
}
}
geometry_projected.faces = faces_projected;
geometry_projected.vertices = vertices_projected;
geometry_projected.mergeVertices();
console.info(geometry_projected);
meshHang = new THREE.Mesh(geometry_projected, new THREE.MeshFaceMaterial(materials));
var newY = -(2 * meshStl.position.y) + 0;
var newY = -meshStl.position.y;
meshHang.position.set(0, newY, 0);
meshStl.add( meshHang );
}
EDIT: Finally!! I got it! To clone the original faces I must access their 3 original vertices using "a", "b" and "c" properties, which are indexes referencing Vector3 instances in the "vertices" array of the original geometry.
I cloned the 3 vertices flatting the Z position to zero, use their new indexes to create the new face and add it to the projection mesh (in blue).
I´m also adding lines as a visual union between both faces. Now I´m ready for step 3, but I think this is complex enough to close this question.
Thanks for the updateMatrixWorld clue! It was vital to achieve my goal ;)
try this
original_geometry.updateMatrixWorld();
var vertexIndexes = [null, null, null];
for (var i = 0, l = vertexNormals.length; i < l; i++) {
var position = original_geometry.geometry.vertices[i].clone();
position.applyMatrix4( original_geometry.matrixWorld );
var vector = new THREE.Vector3(position.x, position.y, position.z)
vertexIndexes[i] = geovertices.push( vector ) - 1;
}

Resources