Custom THREE.Curve.create displays incorrectly - javascript

As suggested in this answer, I've created a linearly interpolated curve like this:
THREE.Linear3 = THREE.Curve.create(
function ( points, label /* array of Vector3 */) {
this.points = (points == undefined) ? [] : points;
this.label = label;
},
function ( t ) {
var v = new THREE.Vector3();
var c = [];
var points = this.points, point, intPoint, weight;
point = ( points.length - 1 ) * t;
intPoint = Math.floor( point );
weight = point - intPoint;
c[ 1 ] = intPoint;
c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
var pt1 = points[ c[1] ],
pt2 = points[ c[2] ];
v.copy( pt1 ).lerp( pt2, weight );
return v;
}
);
However, when I'm trying to display a trajectory at different lengths (in an animated kinda-way) I get the following behavior i.e. instead of the curve going through the points, it kinda cuts through the space, note that in the example below each trajectory is supposed to go through the coordinates of each of the spheres (animated gif below):
I am not sure I understand the getPoint function or what is it supposed to return. Any Help is greatly appreciated.
JSFiddle
This is a minimal example but you can see how the right corner has a jerky motion as the tube expands.
http://jsfiddle.net/ElDeveloper/3uyf3sq3/1/

Cleaning some code
That helped me investigate the issue.
You are leaking geometries, you need to dispose the geometry after removing the mesh from the scene
scene.remove(c_mesh)
c_tube && c_tube.dispose();
Use WebGLRenderer. The CanvasRenderer leaks removed objects, and you're creating new objects on each frame. (If you're stuck with CanvasRenderer for some reason, sorry for you)
(For the fiddle) slow the motion, requestAnimationFrame isn't required for a test, setTimeout(animate, 500); allows the user to see what's happening.
What's the point of a 0-segment tube ?
if (index >= points.length - 1){
index = 1; //start with 2 points
}
Works as expected
The TubeGeometry does a tube of N (second argument in constructor, 16 in fiddle) segments. (I'll come back to that later, but I don't think you always want 16 segments)
The default behaviour of Curve.getPoinAt (the method used by TubeGeometry and probably lots of other geometries) is to return equidistant points. You can expect: distance(getPointAt(0),getPointAt(0.1)) == distance(getPointAt(0.1),getPointAt(0.2)) to be true.
Because of these points, no matter how many points you put in your path, the TubeGeometry will build a 16-segments tube with all segment of the same length, going from the first to the last point of your path. There is little chance that one of the 15 intermediate points will be exactly at the position of an edge. That should explain what you see.
Trying to fix the stuff
First get rid of that equidistant way-to-be of the TubeGeometry+Path. Overloading getUtoTmapping should be enough (I found that reading the source):
THREE.Linear3.prototype.getUtoTmapping = function(u) {
return u;
};
I changed your getPoint. It probably does the same thing, but I was more comfortable with my code the investigate
function ( t ) {
var points = this.points;
var index = ( points.length - 1 ) * t;
var floorIndex = Math.floor(index);
if(floorIndex == points.length-1)
return points[floorIndex];
var floorPoint = points[floorIndex];
var ceilPoint = points[floorIndex+1];
return floorPoint.clone().lerp(ceilPoint, index - floorIndex);
}
Give the correct number of segments to the TubeGeometry constructor:
var pathPoints = points.slice(0, index);
c_path = new THREE.Linear3(pathPoints, 'Test');
c_tube = new THREE.TubeGeometry(c_path, pathPoints.length-1, 10, 16, false, true);
At this point, you should have roughly what you were expecting
You should see the tube always going through the edges. You should also see that the TubeGeometry isn't meant to have angles. You could improve this angle issue either by looking at how TubeGeometry and Curve handles tangents, or (if you don't care about slowness) by increasing the number of segments to a very large number:
c_tube = new THREE.TubeGeometry(c_path, 200/*pathPoints.length-1*/, 10, 16, false, true);
That's all for the answer. You can find the last version of my experiments here: http://jsfiddle.net/dqn73m98/5/. You can also ask the three.js developers if such a feature exists, or request an implementation in a github issue (if someone as time to do it), here

Related

Is there a way to get the exact point inside navmesh so the pathfinder can work properly?

I am making a first person shooter in Three.js and I wanted to use Don McCurdy's pathfinding algorithm for my enemy AI. For the navmesh I followed Nik Lever's Three.JS Pathfinding Tutorial. In order to use the pathfinder, both the player and the enemies need to be on the surface of the navmesh. The problem is that since the navmesh is slightly off the ground (IDK how to get it on the same elevation as the level) and I want the player to be able to jump, I can't have them simply stick to the navmesh and offset the models. I tried to fix it by raycasting from the player/enemy to the navmesh and using the intersection point, but because of floating point arithmetic the intersection point is always slightly off.
There is a lot of code in my project, so I'm only sharing the troubling bits. If more code is needed, I will add it.
Entity.js
rayCaster is a globally scoped Three.js Raycaster
update(map, navmesh) {
// ...some code...
rayCaster.set(new THREE.Vector3(this.pos.x, this.pos.y + 10, this.pos.z), new THREE.Vector3(0, -1, 0));
var hits = rayCaster.intersectObject(navMesh);
if(hits && hits[0]) this.point = hits[0].point;
}
Game.js
this.pathfinder = new Pathfinding();
this.pathfinder.setZoneData("lvl1", Pathfinding.createZone(this.navmesh.geometry, 3));
update() {
// ...more code...
for(var i = this.enemies.length - 1; i >= 0; i--) {
var group = this.pathfinder.getGroup("lvl1", this.enemies[i].point), path = 0;
if(group != null && group != undefined) path = this.pathfinder.findPath(this.enemies[i].point, player.point, "lvl1", group);
// Path is always null
this.zombies[i].update(this.map, this.navmesh);
}
}

Three JS Raycasting - Find point closest to cursor

Three.js r85
When raycasting with Three JS, a series of points is returned, and I'd like to find the point that is closest to the cursor. The first point returned seems to be the point that is closest to the camera.
Is there a way to find the distance between the cursor position and a point?
Here's the code I'm using to debug this right now:
var raygun = new THREE.Raycaster();
raygun.setFromCamera(mouse, camera);
var hits = raygun.intersectObjects([plotpoints]);
if (hits.length > 0) {
scope.remove(dotPlot);
scope.remove(dotPlot2);
// All points except the first one - Grey
dotGeo = new THREE.Geometry();
for (var i=1; i < hits.length; i++) {
dotGeo.vertices.push(plotpoints.geometry.vertices[hits[i].index]);
}
dotPlot = new THREE.Points(dotGeo, dotMat);
scope.add(dotPlot);
// First point - Orange
var geo2 = new THREE.Geometry();
geo2.vertices.push(plotpoints.geometry.vertices[hits[0].index]);
dotPlot2 = new THREE.Points(geo2, dotMat2);
scope.add(dotPlot2);
scope.render();
}
And here's what I'm seeing:
Ah, figured it out with math!
First thing to note is that hits[].points returns a point directly under the cursor, but it doesn't "snap" to points.
In order to get the actual position of the point, we need to use hits[].index first to get the index number of the point/vertex we hit. We can then access that vertex directly by using GEOMETRY.vertices[] which returns a THREE.Vector3 of the vertex point we hit with our raycast.
So by feeding in the index, we can get the exact position of each vertex hit by our raycast:
GEOMETRY.vertices[hits[i].index]
This provides rudimentary "snapping" to vertices.
Note: When using THREE.LineSegments, the result will always be the starting point, and not the ending point. To get the ending point, you can just add 1 to the index value:
GEOMETRY.vertices[hits[i+1].index]
To snap directly to the vertex closest to the cursor, we need to find the vertex that has the shortest perpendicular distance from the raycaster's ray. To do this we use a cross product of 2 vectors. This is more of a math concept than a programming concept though, so if you want to understand the why behind this, look up something like: perpendicular distance from a point to a line
I just took the code from this question and translated it: http://answers.unity3d.com/questions/568773/shortest-distance-from-a-point-to-a-vector.html
And the end result:
// Variables to record and compare
var smallestDist = 99;
var smallestPointIndex = 0;
// Declare variables outside of loop to save memory
var m_ray = raycaster.ray;
var raydir = m_ray.direction;
var origin = m_ray.origin;
var hitray = new THREE.Vector3(0,0,0);
var dist = 1;
// Loop over all points to find the closest
for (var i=0; i<hits.length; i++){
// Math is magic
hitray.subVectors(plotpoints.geometry.vertices[hits[i].index], origin);
dist = new THREE.Vector3().crossVectors(raydir, hitray).lengthSq();
// Record the closest point
if (dist < smallestDist) {
smallestDist = dist;
smallestPointIndex = i;
}
}
// Now we can use that single point
Here's the result :)

Find polygon perimeter of points quickly in Javascript

I'm making a terrain editor and I need to find the perimeter polygon of a set of points. If I just needed a convex hull then the speed would be no issue. To make a concave hull, I must go through a few hoops. I've figured out that I can triangulate the points and then throw away any triangles with a side longer than the known distance between the points.
The next step is the problem: Combining the triangles (as mini polygons) into one large polygon using the JSTS geometry library (http://github.com/bjornharrtell/jsts) is really slow.
See the full code: http://codepen.io/anon/pen/oCfDh
I've got an array (polys) that gets merged to form the final polygon. The problem is that with 552 points (I want to support 15k+), it takes ~3500ms to run. Look at the console in the codepen link for your speed.
var reader = new jsts.io.WKTReader(),
merged = reader.read(polys[0]).union(reader.read(polys[1]));
console.time('jsts mergization');
for(var i = 2; i<polys.length; i++){
try{
merged = merged.union(reader.read(polys[i]));
}catch(err){
console.log('Error triangulating points!');
};
};
console.timeEnd('jsts mergization');
Does anybody know of any faster way to either merge triangles into a polygon or to go even wider and build a concave polygon from a set a points in a whole different way?
Thanks simonzack!
I've rewritten the algorithm using your suggestion and it's much faster!
Reworked codepen: http://codepen.io/anon/pen/Btdyj
The same example now runs in ~15ms!
function pointsToPolygon(points, triangles, maxEdgeLength){
console.time('homebrewed mergization');
var dist = function(a, b){
if(typeof a === "number"){
a = points[a];
};
if(typeof b === "number"){
b = points[b];
};
return Math.sqrt(Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2));
};
if(!points.length){
return undefined;
};
var pointFreq = [];
points.forEach(function(v){
pointFreq.push(0);
});
for(var i = triangles.length; i; i-=3){
if(dist(triangles[i-1], triangles[i-2]) < maxEdgeLength &&
dist(triangles[i-3], triangles[i-2]) < maxEdgeLength &&
dist(triangles[i-1], triangles[i-3]) < maxEdgeLength){
pointFreq[triangles[i-1]]++;
pointFreq[triangles[i-2]]++;
pointFreq[triangles[i-3]]++;
};
};
// Keep points that are used in 3 or fewer triangles
var output =[];
pointFreq.forEach(function(freq, i){
if(freq<4){
output.push(points[i]);
};
});
// Sort points by looping around by each next closest point
var sorted = [];
while(output.length>0){
sorted.push(output.pop());
output=output.sort(function(a,b){
var distA =dist(sorted[sorted.length-1], a),
distB =dist(sorted[sorted.length-1], b);
if(distA < distB){
return 1;
}else if(distA === distB){
return 0;
};
return -1;
});
};
sorted=simplifyPath(sorted,0.1);
console.timeEnd('homebrewed mergization');
return sorted;
};
I can find the boundary by filtering the points that are used in 3 or fewer triangles then sort points by looping around by each next closest point from any arbitrary point.
Maybe not 100% as accurate due to the Douglas-Peucker simplification algorithm (adapted from https://gist.github.com/adammiller/826148) but it seems good enough for me.

Finding inaccessible points on a 2D plane

I have been working on JavaScript / JQuery code which allows arrow key movement between input boxes (yes, I am aware this breaks standard UI).
It works by by looping through each element and finding the closest in each direction (left, right, up and down).
Example
P1:(0, 0), P2:(1, 0), P3:(0, 2)
P1 has one point to the right (P2) and one point up (P3).
P2 has one point to the left (P1) and one point up (P3).
No picture
P3 has two points down (P1 & P2) but P1 is closer.
Therefore the final movements are:
Up
1 -> 3
2 -> 3
Right
1 -> 2
Down
3 -> 1
Left
2 -> 1
For this example:
P1 has two incoming and two outgoing connections.
P2 has one incoming and two outgoing connections.
P3 has two incoming and one outgoing connections.
This made me think.
Is there a set of points such that one or more point is inaccessible (0 incoming connections), or can it be proven no such set exists?
Side note:
If you ignore the up / down component (only using left and right with a vertical split) then point P3 in inaccessible in P1: (0, 0), P2: (2, 0), P3: (1, 4).
Here is the JavaScript / JQuery code if it will help anyone.
function arrowKeyNavigation(elements) {
// Get the position of each element.
var elementOffsets = [];
elements.each(function(key, element) {
elementOffsets[key] = $(element).offset();
});
// Find the closest point in each direction and store the points in data('keyNav') for later use.
for (var i = 0; i < elementOffsets.length; i++) {
var closestPoints = [];
for (var j = 0; j < elementOffsets.length; j++) {
if (i != j) {
var distance = calcDistanceSquared(elementOffsets[i], elementOffsets[j]);
var quadrant = calcQuadrant(elementOffsets[i], elementOffsets[j]);
if (closestPoints[quadrant] == undefined || calcDistanceSquared(elementOffsets[i], elementOffsets[closestPoints[quadrant]]) > distance) {
closestPoints[quadrant] = j;
}
}
}
var closestElements = [];
for (var j = 0; j < closestPoints.length; j++) {
closestElements[j] = elements[closestPoints[j]];
}
$(elements[i]).data('keyNav', closestElements);
}
}
// Returns the distance between two points squared.
function calcDistanceSquared(offset1, offset2) {
...
}
// Returns 0, 1, 2 or 3 for left, up, right and down respectively.
// If a point is EXACTLY 45 degrees it will be classified as either left / right.
function calcQuadrant(offset1, offset2) {
...
}
I've thought about it more and I think I have the solution.
The proof sketch goes as follows:
Assume you have a finite number of points in the plane (R^2). Take an arbitrary point and call it your destination. Then take any other point. That point divides R^2 into four quadrants, as you've drawn in red. By definition the destination is in one of those four quadrants. Move in that direction. One of two things can happen:
1) you arrive at the destination and you're done
2) you move to another point.
If 2, then you've moved closer (EDIT: by the 1-norm distance, d((x1,y1),(x2,y2)) = |x1-x2|+|y1-y2|). This requires more proof but I'm just sketching. The destination is now in some quadrant of this new point.
Now note that If you repeat this, always moving one step closer in the direction of the destination (which may change each step) your distance to the destination point will decrease each step; you can never revisit a point because you distance is always decreasing. So, if you have a finite number of points you'll eventually reach your destination.

Google Maps API V3 - Showing progress along a route

I want to show a custom route on a route along with the current progress. I've overlayed the a Polyline to show the route and I am able to find the LatLng of the current postion and place a marker. What I want to do now is to highlight the traveled part of the Polyline using a different colour. I'm pretty sure it's not possible to have multiple colours for one Polyline so I plan to overlay a second Polyline over the first the first to give this effect. Here's my question:
Knowing the LatLng of the current position and armed with the array of LatLng points used to create the orginal Polyline how best can I create the second 'Progess' route?
Thanks ahead.
With a few assumptions:
Your path is quite dense (if not, you could interpolate intermediate points)
Your route doesn't overlap with itself (wouldn't work with a repeating circular path, say)
..one crude way would be as follows:
Using Python'y pseudo-code, say you have a route like this:
points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3), LatLng(4, 4)]
You draw this as a PolyLine as usual.
Then, given your current position, you would find the nearest point on the route:
cur_pos = LatLng(3.1, 3.0123)
nearest_latlng = closest_point(points, to = cur_pos)
Then nearest_latlng would contain LatLng(3, 3)
Find nearest_latlng in the list, then simply draw a second polyline up to this point. In other words, you truncate the points list at the current LatLng:
progress_points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3)]
..then draw that on the map
As mentioned, this will break if the path loops back on itself (closest_point would only ever find the first or last point)
If you know how far has been travelled, there is an epoly extension which gives a few methods that could be used, mainly:
.GetIndexAtDistance(metres)
Returns the vertex number at or after the specified distance along the path.
That index could be used instead of the closest_point calculated one above
Typescript example:
getRouteProgressPoints(directionResult: google.maps.DirectionsResult | undefined, targetDistance: number) {
if (!directionResult || targetDistance <= 0) return [];
const route = directionResult.routes[0];
const leg: google.maps.DirectionsLeg = route.legs[0];
const routePoints: google.maps.LatLng[] = [];
// collect all points
leg.steps.forEach((step) => {
step.path.forEach((stepPath) => {
routePoints.push(stepPath);
});
});
let dist = 0;
let olddist = 0;
const points = [];
// go throw all points until target
for (let i = 0; i < routePoints.length && dist < targetDistance; i++) {
const currentPoint = routePoints[i];
// add first point
if (!i) {
points.push(currentPoint);
continue;
}
const prevPoint = routePoints[i - 1];
olddist = dist;
// add distance between points
dist += google.maps.geometry.spherical.computeDistanceBetween(prevPoint, currentPoint);
if (dist > targetDistance) {
const m = (targetDistance - olddist) / (dist - olddist);
const targetPoint = new google.maps.LatLng(
prevPoint.lat() + (currentPoint.lat() - prevPoint.lat()) * m,
prevPoint.lng() + (currentPoint.lng() - prevPoint.lng()) * m,
);
points.push(targetPoint);
} else {
points.push(currentPoint);
}
}
return points;
}
That is going to be really difficult unless you already have a way of determining that the vehicle has passed certain points already. If you are aware that it has passed certain points you just create the second polyline with the passed points and the current marker as the end of the polyline. You will probably want to change the width of the polyline in order to make it viewable when it is overlaying the initial route.
All of that being said if you don't know if the points have been passed I am not sure what you can do other than write some sort of code that determines if your current position is past a certain set of points, but I don't know how you do that if a route zigzags all over the place possibly..... Now if your points go from straight west to straight east etc, than coding something will be easy, but I doubt that is the case.
Hope this helps somewhat....

Categories