Three.js rope / cable effect - animating thick lines - javascript

With Three.js I want to create the effect of an object swinging from a cable or rope. It doesn't require real physics as the "swinging" object simply follows a fixed animation. The easiest solution is using the THREE.Line, however the problem is that THREE.Line can only be 1px thick and looks kinda awful.
In the three.js examples there is a "fat lines" example :
https://threejs.org/examples/?q=lines#webgl_lines_fat
however the problem is that once I have created the line using LineGeometry() I cannot figure out how to animate it.
The only solution I have found so far is to delete then create a new line every single frame, which works but seems like a really uneconomical, poorly optimized way to do it.
Does anyone know of a better way to either animate Line Geometry without having to delete and replace each frame? Or is there another method within three.js which would allow me to create thicker animated lines?
Thanks!!

I actually have a small project where I animate a bulb swinging along some rope. You can access it here, the functions I'm talking about below are in helperFns.js.
Actually, what I basically do is create my attached object separately :
let geometry = new THREE.SphereGeometry( 1, 32, 32 );
var material = new THREE.MeshStandardMaterial({color:0x000000,emissive:0xffffff,emissiveIntensity:lightIntensity});
bulb = new THREE.Mesh( geometry, material );
light = new THREE.PointLight(0xF5DCAF,lightIntensity,Infinity,2)
light.power = lightIntensity*20000
light.position.set(0,length*Math.sin(theta),z0-length*Math.cos(theta))
light.add(bulb)
light.castShadow = true;
hemiLight = new THREE.HemisphereLight( 0xddeeff, 0x0f0e0d, 0.1 );
scene.add(hemiLight)
scene.add(light)
I then add a spline connected to it :
// Create the wire linking the bulb to the roof
var curveObject = drawSpline(light.position,{x:0,y:0,z:z0},0xffffff);
scene.add(curveObject)
Where drawSpline is the following function :
// Build a spline representing the wire between the roof and the bulb. The new middle point is computed as the middle point shifted orthogonally from the lign by shiftRatio
function drawSpline(beginning,end,clr){
// Compute y sign to know which way to bend the wire
let ySign = Math.sign((end.y+beginning.y)/2)
// Compute the bending strength and multiply per Math.abs(beginning.y) to ensure it decreases as the bulb gets closer to the theta = 0 position, and also to ensure
// that the shift is null if thete is null (no discontinuity in the wire movement)
let appliedRatio = -shiftRatio*Math.abs(beginning.y)
// Compute middle line position vector and the direction vector from the roof to the bulb
let midVector = new THREE.Vector3( 0, (end.y+beginning.y)/2, (end.z+beginning.z)/2 )
let positionVector = new THREE.Vector3(0,end.y-beginning.y,end.z-beginning.z)
// Compute the orthogonal vector to the direction vector (opposite sense to the bending shift)
let orthogVector = new THREE.Vector3(0,positionVector.z,-positionVector.y).normalize()
// Compute the curve passing by the three points
var curve = new THREE.CatmullRomCurve3( [
new THREE.Vector3( beginning.x, beginning.y, beginning.z ),
midVector.clone().addScaledVector(orthogVector,ySign*appliedRatio),
new THREE.Vector3( end.x, end.y, end.z ),
]);
// Build the curve line object
var points = curve.getPoints( 20 );
var geometry = new THREE.BufferGeometry().setFromPoints( points );
var material = new THREE.LineBasicMaterial( { color : clr } );
// Create the final object to add to the scene
var curveObject = new THREE.Line( geometry, material );
return curveObject;
}
It creates the CatmullRomCurve3 interpolating the 3 points (one fix end at (0, 0, 0), one middle point to apply the bend, and the bulb position. You can actually start with a straight line, and then try to compute some curve.
To do so, you want to get the vector orthogonal to the line and shift the line (on the good side) along this vector.
And finally, at each animate() call, I redraw the spline for the new position of the bulb :
scene.children[2] = drawSpline(light.position,{x:0,y:0,z:z0},0xffffff)
Tell me if there is a point you do not get, but it should help for your problem.

Just wanted to post a more detailed version of West Langleys great reply. To animate a THREE Line2 you need to use the commands :
line.geometry.attributes.instanceStart.setXYZ( index, x, y, z );
line.geometry.attributes.instanceEnd.setXYZ( index, x, y, z );
What confused me was the index value - rather than thinking about a Line2 as being vertex points (the method used for creating the line) you need to think of a Line2 as being made of separate individual lines between 2 sets of points... so each line has a Start point and and an End point.
A "W" is therefore NOT defined as 5 vertices but by 4 lines. So you can "split" a Line2 by setting a different Start point to the previous lines End point. The index is the number of lines that make up your object. In my case I have two lines forming a V shape... so I set my index to 1 to affect the end of line 0 and the start of line 1, as in West's example :
var index = 1;
line.geometry.attributes.instanceEnd.setXYZ( index - 1, x, y, z );
line.geometry.attributes.instanceStart.setXYZ( index, x, y, z );
And then you just need to update the line using :
line.geometry.attributes.instanceStart.data.needsUpdate = true;
Thanks again to West for this really useful answer. I'd never have guessed this as you cannot see these variables when you look at the Line2 object properties. Very useful info. I hope it helps someone else at some point.

Related

How to find 3D coordinates of point perpendicular to line's mid point

(I'm using Javascript/Typescript and Three.js)
I have a straight line between two vectors, say {x:1, y:3, z:5} and {x:7, y:8, z:10}.
On the mid-point of that line, imagine a disc (say having radius 1) that is oriented perpenticular to that line.
How can I get a point on the cirumfence of that imaginary disc?
I know there are infinite points, but I'm just looking to compute 1 (any) point on the disc's circumefence with a perpendicular connection to the line's mid point.
There are many ways to do this. A simple way that requires little math, and lets Three.js handle the heavy-lifting is to use an Object3D nested inside another: the child object is the "ring", and the parent object moves the ring to the midpoint and "looks" down the line to make it perpendicular.
// Create end vectors
var v1 = new THREE.Vector3(1, 3, 5);
var v2 = new THREE.Vector3(7, 8, 10);
// Get midpoint
var mid = new THREE.Vector3();
mid.addVectors(v1, v2);
mid.multiplyScalar(0.5);
// Nest child object inside parent
var parent = new THREE.Object3D();
var child = new THREE.Object3D();
parent.add(child);
// Set child position to any point in the XY plane with radius = 1
// This is a point in your "disc"
child.position.set(0, 1, 0);
// Move parent to midpoint
parent.position.copy(mid);
// Rotate parent to look towards end of the line
// This makes the "disc" perpendicular to the line
parent.lookAt(v1);
// Get world position of child
var discPoint = new THREE.Vector3();
child.getWorldPosition(discPoint);
console.log(discPoint);
The local position of child is still [0, 1, 0], but the world position, after translating and rotating the parent, is the answer you're looking for. Alternatively, you could simply use Object3D.localToWorld, but I thought this parent/child example would illustrate the process more clearly.

Graphing 2d plane in 3d space using equation and/or vectors

I'm trying to make a linear regression plane visualization tool for a math project. Currently I have the math parts completed, but I am not sure how to graph the plane. I have a equation in the form of z=C+xD+yE, where C, D, and E are known constants. How do I graph the plane using these information? Thanks.
github page: https://saxocellphone.github.io/LAProject/
z=C+xD+yE
This equation gives full information about the plane. What else you need to graph (plot, draw?) it? Probably it depends on your graphic software possibilities.
Canonical form of given equation:
xD + yE - z + C = 0
Normal to the plane is (D, E, -1). Distance to the coordinate origin Abs(C)/Sqrt(D^2+E^2+1).
Plane intersects coordinate axes at values (-C/D), (-C/E), (C)
I see your problem is not with math, but with three,
as WestLangley pointed out in his comment you can play with rotations etc. or create a simple triangle which is the easiest way
since you have your equation for the plane create 3 points to form a triangle
// z=C+xD+yE
// i assume here that the plane is not aligned with any axis
// and does not pass through the origin, otherwise choose the points in another way
var point1 = new THREE.Vector3(-C/D,0,0);//x axis intersection
var point2 = new THREE.Vector3(0,-C/E,0);//y axis intersection
var point3 = new THREE.Vector3(0,0,C);//z axis intersection
now form a new geometry as in How to make a custom triangle in three.js
var geom = new THREE.Geometry();
geom.vertices.push(point1);// adding vertices to geometry
geom.vertices.push(point2);
geom.vertices.push(point3);
// telling geometry that vertices 0,1,2 form a face = triangle
geom.faces.push( new THREE.Face3( 0, 1, 2 ) );
create a simple material and add it to a scene
var material = new THREE.MeshBasicMaterial({
color: 0xff0000, // RGB hex color for material
side: THREE.DoubleSide // do not hide object when viewing from back
});
scene.add(new THREE.Mesh(geometry,material));
that should get you going, you can add another triangles, or make it larger with choosing points that are further apart

Multi-stop Gradient in THREE.js for Lines

This shows an example of how to create a two-color gradient along a THREE.js line:
Color Gradient for Three.js line
How do you implement a multi-stop color gradient along a line? It looks like attributes will only interpolate across two values (I tried passing in three, it only worked with the first two values).
This is the do-it-yourself color gradient approach:
Create a line geometry and add some vertices:
var lineGeometry = new THREE.Geometry();
lineGeometry.vertices.push(
new THREE.Vector3( -10, 0, 0 ),
new THREE.Vector3( -10, 10, 0 )
);
Use some helper functions for convenience:
var steps = 0.2;
var phase = 1.5;
var coloredLine = getColoredBufferLine( steps, phase, lineGeometry );
scene.add( coloredLine );
jsfiddle: http://jsfiddle.net/jfd58hbm/
Explaination:
getColoredBufferLine creates a new buffer geometry from the geometry, which is just for convenience. It then iterates the vertices, assigning each vertex a color. The color is calculated using another helper: color.set ( makeColorGradient( i, frequency, phase ) );.
Where basically frequency defines how many color changes you want the line to receive.
And phase is a shift of the color spectrum (= what color does the line start with).
I have added a dat.gui so you can play around with the parameters. If you want to change the color repetition or type, you can alter the makeColorGradient function to your needs. This page offers some good explaination how gradients are generated and where my example is based upon: http://krazydad.com/tutorials/makecolors.php.

Three.js - Accurate ray casting for collision detection

I'm working with Three.js, version 68. I'm using the same method for collision detection as this guy is using here, which is great most of the time (A big "thank you" goes out to the author!): http://stemkoski.github.io/Three.js/Collision-Detection.html
Here is a link to the source if you want to download it from github. Just look for Collision-Detection.html: https://github.com/stemkoski/stemkoski.github.com
Here is the code that is important to the collision detection:
var MovingCube;
var collidableMeshList = [];
var wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.set(100, 50, -100);
scene.add(wall);
collidableMeshList.push(wall);
var wall = new THREE.Mesh(wallGeometry, wireMaterial);
wall.position.set(100, 50, -100);
scene.add(wall);
var wall2 = new THREE.Mesh(wallGeometry, wallMaterial);
wall2.position.set(-150, 50, 0);
wall2.rotation.y = 3.14159 / 2;
scene.add(wall2);
collidableMeshList.push(wall2);
var wall2 = new THREE.Mesh(wallGeometry, wireMaterial);
wall2.position.set(-150, 50, 0);
wall2.rotation.y = 3.14159 / 2;
scene.add(wall2);
var cubeGeometry = new THREE.CubeGeometry(50,50,50,1,1,1);
var wireMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe:true } );
MovingCube = new THREE.Mesh( cubeGeometry, wireMaterial );
MovingCube.position.set(0, 25.1, 0);
// collision detection:
// determines if any of the rays from the cube's origin to each vertex
// intersects any face of a mesh in the array of target meshes
// for increased collision accuracy, add more vertices to the cube;
// for example, new THREE.CubeGeometry( 64, 64, 64, 8, 8, 8, wireMaterial )
// HOWEVER: when the origin of the ray is within the target mesh, collisions do not occur
var originPoint = MovingCube.position.clone();
for (var vertexIndex = 0; vertexIndex < MovingCube.geometry.vertices.length; vertexIndex++)
{
var localVertex = MovingCube.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( MovingCube.matrix );
var directionVector = globalVertex.sub( MovingCube.position );
var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
appendText(" Hit ");
}
This works great most of the time, but there are times when I can move the cube partially into the wall, and it won't register a collision. For example, look at this image:
It should say "Hit" in the top-left corner where there are just a bunch of dots, and it's not.
NOTE: I also tried his suggestion and did the following, but it didn't seem to help much:
THREE.BoxGeometry( 64, 64, 64, 8, 8, 8, wireMaterial ) // BoxGeometry is used in version 68 instead of CubeGeometry
Does anyone know how this method could be more accurate? Another question: Does anyone know what the following if statement is for, i.e. why does the object's distance have to be less than the length of the direction vector?:
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
To answer your last question first: that line detects whether the collision happened inside your MovingCube. Your raycasting code casts a ray from the MovingCube's position towards each of its vertices. Anything that the ray intersects with is returned, along with the distance from the MovingCube's position at which the intersected object was found (collisionResults[0].distance). That distance is compared with the distance from the MovingCube's position to the relevant vertex. If the distance to the collision is less than the distance to the vertex, the collision happened inside the cube.
Raycasting is a poor method of collision detection because it only detects collisions in the exact directions rays are cast. It also has some additional edge cases. For example, if the ray is cast from inside another object, the other object might not be considered to be colliding. As another example, raycasting in Three.js uses bounding spheres (or, if unavailable, bounding boxes) to calculate ray intersection, so rays can "intersect" with objects even if they wouldn't hit them visually.
If you're only dealing with spheres or upright cuboids, it's straightforward math to check collision. (That's why Three.js uses bounding spheres and bounding boxes - and most applications that need to do collision checking use secondary collision-only geometries that are less complicated than the rendered ones.) Spheres are colliding if the distance between their centers is less than the sum of their radii. Boxes are colliding if the edges overlap (e.g. if the left edge of box 1 is to the left of the right edge of box 2, and the boxes are within a vertical distance the sum of their half-heights and a horizontal distance the sum of their half-lengths).
For certain applications you can also use voxels, e.g. divide the world into cubical units, do box math, and say that two objects are colliding if they overlap with the same cube-unit.
For more complex applications, you'll probably want to use a library like Ammo.js, Cannon.js, or Physi.js.
The reason raycasting is appealing is because it's workable with more complex geometries without using a library. As you've discovered, however, it's less than perfect. :-)
I wrote a book called Game Development with Three.js which goes into this topic in some depth. (I won't link to it here because I'm not here to promote it, but you can Google it if you're interested.) The book comes with sample code that shows how to do basic collision detection, including full code for a 3D capture-the-flag game.

Get coordinates of line-surrounding box

I've been working in JavaScript to code a line drawing system. I'd like the lines drawn to be selectable, so I've been attempting to implement line-highlighting. As you can see in the image below, I have a line (in black) with known coordinates and an equation in slope-intercept (y=mx+b). How can I calculate the corners' (circled in green) coordinates, knowing the box's radius?
This is easiest to think of in terms of vectors.
Start off by defining the point at the end of the line as A, and the other end as B
var A = new Vector(1, 1)
var B = new Vector(5, 3)
Now find the unit direction vector of the line (a vector of length 1 pointing from A to B), and its perpendicular:
var dir = B.minus(A).normalize();
var dir_perp = new Vector(dir.y, -dir.x)
And extend them to be of length thickness:
dir = dir.times(thickness);
dir_perp = dir_perp.times(thickness)
The four corners are then:
[
A.minus(dir).plus(dir_perp),
A.minus(dir).minus(dir_perp),
B.plus(dir).minus(dir_perp),
B.plus(dir).plus(dir_perp)
]
This obviously assumes you have some sort of vector math library. Here's one I made earlier

Categories