Three.js object moving to target - javascript

I have a object that moves to a target. The problem is that the x position is quicker than the z position or the z position is quicker than the x position.
What can I do that my object slows down for the x position if the z position needs more time to move?
My code in the animation function:
var distanceX = objectX - targetX;
var distanceZ = objectZ - targetZ;
if( distanceX < 0) {
visitor.translateX( 0.05 );
}else {
if( distanceX > 0) {
visitor.translateX( -0.05 );
}
}
if( distanceZ < 0) {
visitor.translateZ( 0.05 );
}else{
if( distanceZ > 0) {
visitor.translateZ( -0.05 );
}
}

I would recommend to compute the current value as an absolute value instead of incrementally. What you are doing is basically something like this:
x = xLast + constantValue;
Instead, you could do this without needing the last value (this is called linear interpolation):
progress = Math.min((Date.now() - startTime) / duration, 1);
x = xStart + progress * (xEnd - xStart);
The progress value in this case is a value from 0 (animation start) to 1 (animation end).
If you write your animations like this, you can just set the duration for both parts to the same value. Using the three.js math utils you can even do this all at once:
var end = new THREE.Vector3(1, 0, 2);
var start = new THREE.Vector3(2, 0, -1);
// in animation-loop
var tmp = new THREE.Vector3();
var progress = Math.min((Date.now() - startTime) / duration, 1);
// tmp = progress * (end - start)
tmp.copy(end).sub(start).multiplyScalar(progress);
object.position.copy(from).add(tmp);
That way you will have all components of the position move at a constant speed that arrives at the destination exactly at the same time.
You might want to have a look at animation-libaries like tween.js. They can handle a lot of this quite comfortably.

Related

Plotting Space Ship Distance over Time

I am working on some logic for point-to-point spaceship travel across a cartesian map using force, acceleration and mass. The ship will accelerate and burn at 1G towards its destination, flip 180 degrees at the half-way mark, and decelerate at 1G to arrive at a relative stop at its destination.
The problem I am having is determining the (x, y) coordinate using the time traveled while the ship is either under acceleration or deceleration.
Here are the specs on the ship:
ship = {
mass: 135000, // kg
force: 1324350, // Newtons
p: { x: -1, y: -5 } // (x,y) coordinates
}
dest: {
p: { x: 15, y: 30 } // (x,y) coordinates
}
For the first part of the problem I calculate the time to destination:
var timeToDestination = function(ship, dest) {
// calculate acceleration (F = ma)
var acceleration = ship.force / ship.mass; // ~9.81 (1G)
// calculate distance between 2 points (Math.sqrt((a - x)^2+(b - y)^2))
var totalDistance = Math.sqrt(
Math.pow(dest.p.x - ship.p.x, 2) +
Math.pow(dest.p.y - ship.p.y, 2)
); // 38.48376280978771
// multiply grid system to galactic scale
var actualDistance = totalDistance * 1e9; // 1 = 1Mkm (38,483,763km) Earth -> Venus
// determine the mid-point where ship should flip and burn to decelerate
var midPoint = actualDistance / 2;
// calculate acceleration + deceleration time by solving t for each: (Math.sqrt(2d/a))
return Math.sqrt( 2 * midPoint / acceleration ) * 2; // 125,266s or 34h or 1d 10h
}
The second part is a little trickier, getting the (x, y) coordinate after delta time. This is where I get stuck, but here is what I have so far:
var plotCurrentTimeDistance = function(ship, dest, time) {
// recalculate acceleration (F = ma)
var acc = ship.force / ship.mass; //~9.81m/s^2
// recalculate total distance
var distance = Math.sqrt(
Math.pow(dest.p.x - ship.p.x, 2) +
Math.pow(dest.p.y - ship.p.y, 2)
) * 1e9; // 38,483,762,810m
// get distance traveled (d = (1/2) at^2)
var traveled = (acc * Math.pow(time, 2)) / 2;
// get ratio of distance traveled to actualDistance
var ratio = traveled / distance;
// midpoint formula to test # 50% time ((x+a)/2,(y+b)/2)
console.log({ x: (ship.p.x+dest.p.x)/2, y: (ship.p.y+dest.p.y)/2})
// get the point using this formula (((1−t)a+tx),((1−t)b+ty))
return {
x: (( 1 - ratio ) * ship.p.x) + (ratio * dest.p.x),
y: (( 1 - ratio ) * ship.p.y) + (ratio * dest.p.y)
};
}
# 50% time, 62,633s point returns as (~7, ~12.5) which matches the midpoint formula which returns as (~7, ~12.5). However, any other distance/time you input will be wildly wrong. My guess is that acceleration is messing up the calculations but I can't figure out how to change the formula to make it work. Thank you for your time.
First, you say that distance is the total distance, but it really is the distance left from the ship to the destination. I don't fully understand your plan on how to do the calculations, so I will suggest something different below.
Lets call the start position start, and it has a x, and y coordinate: start.x and start.y. Similarly with end.
Now, for a simulation like this to work we need velocity as a property on the ship as well, so
ship = {
...
v : {x : 0, y : 0}
}
It will start from rest, and it should reach rest. Ideally it should have acceleration a for a general movement, but we can skip that right now. We will have two different behaviours
The ship starts from rest, accelerates with 9.8 m/s^2 towards the goal until the half point is reached.
The ship starts at speed v at the midpoint, decelerates with -9.8 m/s^2 towards the goal until the speed is 0. Now we should have reached the goal.
To get velocity from accelerations we use v = v_start + a*time, and to get position from velocity we use x = x_start + v*time. The current position of the ship is then for the two cases
// Case 1
current = start + a*time*time
// the above is due to the fact that
// current = start + v*time
// and the fact that v = v_start + a*time as I wrote previously,
// with v_start.x = v_start.y = 0
//Case 2
current = midpoint + (v_at_midpoint - a*time_since_midpoint)*time_since_midpoint
Note here that start, current and a here are vectors, so they will have a x and y (and potentially z) coordinate each.
The acceleration you get by the following algorithm
a = (start - end)/totalDistance * 9.81 // 9.81 is the desired acceleration -- change it to whatever you want
If you want to understand what the above actually means, it calculates what is called a unit vector, which tells us what direction the force points at.
What you will need to do now is as follows:
Calculate the total distance and the acceleration
Determine if you're in case 1 or 2 given the input time in the function.
Once you've reached the midpoint, store the velocity and how long it took to get there and use it to determine the motion in case 2.
Stop once you've reached the destination, or you will go back to the start eventually!
Good luck!
P.S I should also note here that this does not take into account special relativity, so if your distances are too far apart you will get non-physical speeds. It gets a lot messier if you want to take this into account, however.
So thanks to #pingul I was able to get the answer using insights from his suggestions.
var ship = {
mass: 135000,
force: 1324350,
accel: 0,
nav: {
startPos: { x: 0, y: 0 },
endPos: { x: 0, y: 0 },
distanceToDest: 0,
distanceTraveled: 0,
departTime: 0,
timeToDest: 0,
arriveTime: 0,
startDeceleration: 0
}
};
var log = [];
var start = { x: -1, y: -5 };
var end = { x: 15, y: 30 };
var updateLog = function() {
document.getElementById('ship').textContent = JSON.stringify(ship, null, 2);
document.getElementById('pos').textContent = JSON.stringify(log, null, 2);
}
var plotCourse = function(ship, start, end) {
// calculate acceleration (F = ma)
var acceleration = ship.force / ship.mass; // ~9.81 (1G)
// calculate distance between 2 points (Math.sqrt((a - x)^2+(b - y)^2))
var totalDistance = Math.sqrt(
Math.pow(end.x - start.x, 2) +
Math.pow(end.y - start.y, 2)
); // 38.48376280978771
// multiply grid system to galactic scale
var actualDistance = totalDistance * 1e9; // 1 = 1Mkm (38,483,763km) Earth -> Venus
// determine the mid-point where ship should flip and burn to decelerate
var midpoint = actualDistance / 2;
// calculate acceleration + deceleration time by solving t for each: (Math.sqrt(2d/a))
var time = Math.sqrt( 2 * midpoint / acceleration ) * 2; // 125,266s or 34h or 1d 10h
// load data into ship nav
ship.nav = {
startPos: start,
endPos: end,
distanceToDest: actualDistance,
timeToDest: time,
startDeceleration: time / 2
}
ship.accel = acceleration
//log it
updateLog();
};
var goUnderway = function(ship) {
var arrivalEl = document.getElementById('arrivalTime');
// set depart and arrive times
var timeToDest = ship.nav.timeToDest * 1000; // convert to ms
ship.nav['departTime'] = moment().valueOf(); // returns now as unix ms
ship.nav['arriveTime'] = moment(ship.nav.departTime).add(timeToDest).valueOf();
//log it
arrivalEl.textContent = 'Your ship will arrive ' + moment(ship.nav.arriveTime).calendar();
updateLog();
};
var getPosition = function(ship, date) {
var currentTime = date ? moment(date).valueOf() : moment().valueOf() // unix ms
var elapsedTime = (currentTime - ship.nav.departTime) / 1000; // convert to s
var remainingTime = (ship.nav.arriveTime - currentTime) / 1000; // convert to s
var distanceAtMidpoint = 0;
var timeSinceMidpoint = 0;
var pos = { x: 0, y: 0 };
// calculate velocity from elapsed time
if (elapsedTime < ship.nav.startDeceleration) {
// if ship is accelerating use this function
ship.nav.distanceTraveled = 0 + ship.accel * Math.pow(elapsedTime, 2) / 2;
} else if (elapsedTime > ship.nav.startDeceleration) {
// else if ship is decelerating use this function
distanceAtMidpoint = 0 + ship.accel * Math.pow(ship.nav.startDeceleration, 2) / 2; // distance at midpoint
timeSinceMidpoint = elapsedTime - ship.nav.startDeceleration;
ship.nav.distanceTraveled = distanceAtMidpoint + ship.accel * Math.pow(timeSinceMidpoint, 2) / 2;
}
if (remainingTime <= 0) {
ship.force = ship.vel = ship.accel = 0;
pos = ship.nav.endPos;
} else {
// get ratio of distance traveled to actualDistance
var ratio = ship.nav.distanceTraveled / ship.nav.distanceToDest;
// get the point using this formula (((1−t)a+tx),((1−t)b+ty))
pos = {
x: (( 1 - ratio ) * start.x) + (ratio * end.x),
y: (( 1 - ratio ) * start.y) + (ratio * end.y)
};
}
log.push({
timestamp: moment(currentTime),
pos: pos
})
//log it
updateLog();
};
plotCourse(ship, start, end);
goUnderway(ship);
for (var i = 0; i < 35; i++) {
getPosition(ship, moment().add(i, 'hour'));
}
pre {
outline: 1px solid #ccc;
padding: 5px;
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.1/moment.min.js"></script>
Ship: <span id="arrivalTime"></span>
<pre id="ship"></pre> Last Known Positions:
<pre id="pos"></pre>

How to lerp back and forth between two values X,Y in a loop?

Using setInterval or RequestAnimationFrame, I'd like to get the progression value from lerping between X and Y. Assuming that X is 0 and Y is 1, I want to have 0 when it starts, 0.5 in the half, and 1 when finished.
I'd like this to happen in a given timeframe, let's say 5 seconds. Meaning that the half value 0.5 would happen when the setInterval/RequestAnimationFrame reaches 2.5seconds.
Finally, I'd like it to pingPong, so when it reaches the 5 seconds the values are decreasing and not increasing, such as 0.9, 0.8, 0.7, etc and then start again from 0, 0.1, 0.2...
Here is my code so far:
/*
function lerp(start, end, time) {
return start * (1.0 - time) + end * time;
}
*/
function lerp (start, end, amt){
return (1-amt)*start+amt*end;
}
function repeat(t, len) {
console.log('t: ' + t + ', len: ' + len);
return t - Math.floor(t / len) * len;
}
function pingPong(t, len) {
t = repeat(t, len * 2);
return len - Math.abs(t-len);
}
var transitionDuration = 1;
var startTime = Date.now()/1000;
var startPos = 0;
var endPos = 1;
setInterval(function () {
var currentTime = Date.now()/1000;
console.log('currentTime:', currentTime);
var adjustedTime = pingPong(currentTime-startTime, transitionDuration);
var x = lerp(startPos, endPos, adjustedTime);
console.log(Math.abs(x.toFixed(2)));
}, 100);
How can I do this?
The basic formula for linear interpolation would be something like
InterpolatedValue = X*t + Y*(1-t)
where X and Y are the values to be interpolated between and t is a parameter between 0 and 1 determining the degree of interpolation; 0 yields X and 1 yields Y. Furthermore, you would like to have some periodic movement with a period length of 5, alternating the direction of interpolation; this can be achieved as follows. If t is a nonnegative number growing over time, calculate
t' = t - t / 10
to remove all previous periods which have occured and
t'' = t' : t' in [0,5)
5 - t' : t' in [5,10)
and afterwards set
t''' = t' / 5
to normalize the parameter into [0,1] and use the basic interpolation formula from the beginning.
Note that linear interpolation and various other methods are collected here.
From your description, at any given frame there are 6 pieces of state:
Start time of current lerp
Lerp timespan
Current direction
Current time
Start value
End value
From these you can calculate the required progress value, say:
function progressValue(startTime, lerpSpanSeconds, dir,
currentTime X, Y, dir, currentTime) {
// lerp
return 0.42;
}
For requestAnimationFrame, you need a simple callback to pass in. That is, the function has to know everything it needs except what it can acquire when it runs. Here, when it runs it just needs to get the current time and work the rest out from there.
function animableThing() {
var startTime = 7;
var lerpSpanSeconds = 3;
var dir = +1;
var X = 0;
var Y = 1;
var currentTime = GetCurrentUnicornTime();
var thingToAnimate = document.getElementById('myAnimableElement');
var progress = progressValue(startTime, lerpSpanSeconds, dir,
currentTime, X, Y, dir, currentTime);
// reverse when we hit the end
if(progress > Y) {
dir *= -1;
startTime = currentTime;
progress = Y;
}
DrawAnimationThing(thingToAnimate, progress);
// continue the animation
window.requestAnimationFrame(animableThing);
}
But there's a problem; if you want to be able to set up the animation using values from the script or inputs from the screen, or up-to-date information about the elements on the screen, then you need to be able to make an animableThing callback fresh when you have new values. Behold, the mother:
function MotherOfAnimableThings(startTime, lerpSpanSeconds, dir, X, Y,
thingToAnimate)
{
// Passed in variables have scope outside the animableThing, these
// will be private to the animableThing function.
// Consider defaulting or validation here
// Construct a new function freshly each time the Mother is called,
// and return it to the caller. Note that we assign a variable here
// so that we can re-call RequestAnimationFrame to continue the loop
var callback = (function() {
var currentTime = GetCurrentUnicornTime();
var progress = progressValue(startTime, lerpSpanSeconds, dir,
currentTime, X, Y, dir, currentTime);
// reverse when we hit the end
if(progress > Y) {
dir *= -1;
startTime = currentTime;
progress = Y;
}
DrawAnimationThing(thingToAnimate, progress);
window.requestAnimationFrame(callback);
});
return callback;
}
We could go further, and make this general for other types of thing by letting the caller pass in a progressValue function to call, or in fact a callback, so that you could take any element, Draw function and setup function and make a thing that animates, but this is a reasonable starting point.
With the above, we just need to call Mother to create an animableThing function and call RequestAnimationFrame with that. From then on, it calls RequestAnimationFrame internally to continue the cycle.
Now, having done that, you will want to make it stop, so add in a variable in the callback which it can check, so that you can do
var animableThing = MotherOfAnimableThings(...);
window.requestAnimationFrame(animableThing);
// ... later
animableThing.stop = true; // it should stop on the next frame

Easing functions for my animation not working

I'm not the best at maths, and im sorta trying to guess how to implement this for my animation. But currently it is not working, and I believe i have misunderstood how to do my easing function for my animation.
I have a object which is meant to represent a plane, on my canvas of which has the follow properties:
Current Velocity = obj.velocity
Braking Distance = obj.stopDist
Current Position = obj.posX & obj.posY
Destination = obj.destX & obj.destY
So i then incorporate the maths to try to have the plane land on a runway with an easing function so it looks half decent visually even if its not real world physics like this:
function ease(easeDelta,accelerateBool){
if(accelerateBool){
// accelerating
return (easeDelta * easeDelta * easeDelta);
} else {
//decelerating
return ((easeDelta--) * easeDelta * easeDelta + 1);
}
}
function InRange(delta, minValue, maxValue){
var range = (maxValue - minValue);
var valueInRange = (range * delta);
var finalValue = (valueInRange + minValue);
return finalValue;
}
function landing(){ //part of animation loop
var delta = new Date().getTime() - obj.timer, //ms since previous frame
vectorX = obj.destX - obj.posX,
vectorY = obj.destY - obj.posY,
normal = Math.sqrt(vectorX*vectorX + vectorY*vectorY), //distance to destination
targetSpeed = 20,
easeDelta = (normal / obj.stopDist),
newSpeed = InRange(ease(easeDelta,false), obj.velocity, targetSpeed),
distance = delta * newSpeed;
obj.posX += (distance * vectorX);
obj.posY += (distance * vectorY);
obj.timer = new Date().getTime(); //ready for next frame
}
The problem is the plane doesn't slow down as it goes a long the runway towards its destination. It just stays really slow.
Have i confused my maths with how easing functions work ?
Here are some extremely simple easing in and out equations written in that you might find helpful.
// accelerating from zero velocity
function easeIn(t)
{
return t*t
}
// decelerating to zero velocity
function easeOut(t)
{
return t*(2-t)
}
so you could use them like so:
function ease(easeDelta,accelerateBool){
if(accelerateBool){
// accelerating
return (easeDelta * easeDelta);
} else {
//decelerating
return ((easeDelta*(easeDelta - 2)));
}
}

How to bind a timer to a progress ring with JS?

I'm playing around with a progress ring, and I can't seem to get it to run on a timer. I am trying to make the progress ring propagate automatically and take, say, 0.5 seconds to go from 0% to whatever percent I set (65% in this case).
I used this progress ring as a base: http://llinares.github.io/ring-progress-bar/
This is my fiddle: http://jsfiddle.net/gTtGW/
I tried using a timer function, but I may not have been integrating that properly. In the fiddle, I have added:
for (var i = 0; i< 65; i++){
range += i;
setTimeout(timer,800);
}
However, this breaks the progress ring. I thought that any time the range is updated (with the += i), the draw function would be called. What am I doing wrong? Thank you so much in advance.
If you're not planning to use the input[type=range] element, you can change your code to this:
(function (window) {
'use strict';
var document = window.document,
ring = document.getElementsByTagName('path')[0],
range = 0,
text = document.getElementsByTagName('text')[0],
Math = window.Math,
toRadians = Math.PI / 180,
r = 100;
function draw() {
// Update the wheel giving to it a value in degrees, getted from the percentage of the input value a.k.a. (value * 360) / 100
var degrees = range * 3.5999,
// Convert the degrees value to radians
rad = degrees * toRadians,
// Determine X and cut to 2 decimals
x = (Math.sin(rad) * r).toFixed(2),
// Determine Y and cut to 2 decimals
y = -(Math.cos(rad) * r).toFixed(2),
// The another half ring. Same as (deg > 180) ? 1 : 0
lenghty = window.Number(degrees > 180),
// Moveto + Arcto
descriptions = ['M', 0, 0, 'v', -r, 'A', r, r, 1, lenghty, 1, x, y, 'z'];
// Apply changes to the path
ring.setAttribute('d', descriptions.join(' '));
// Update the numeric display
text.textContent = range;
range++;
if(range > 100) {
clearInterval(timer);
}
}
// Translate the center axis to a half of total size
ring.setAttribute('transform', 'translate(' + r + ', ' + r + ')');
var timer = setInterval(draw,100);
}(this));
Basically changing range to a simple variable starting at 0, and increasing its value every time draw() is called. Creating an interval (named timer) to run every 0.1 seconds in this case (of course it's up to you), and clearing that interval from draw() when appropriate...
JSFiddle Demo
I think you want something like:
function inc() {
var val = parseInt(range.value, 10)
range.value = val + 1;
draw(); // updating the value doesn't cause `onchange`.
if (val < 100) { // don't go over 100
setTimeout(inc, 100);
}
}
inc();

Using THREE.Raycaster to detect collisions

I've created this function which is called in my render loop to detect collisions and move the player/camera (it's a first-person game) The collisions are detected using a CubeGeometry named pCube which is moved to match the camera every frame:
// Player movements
function pMovements() {
mPlayer.colBottom = false;
pCube.position.x = mPlayer.yawObject.position.x + 50; // The cube is placed +50 so we can see/debug it.
pCube.position.y = mPlayer.yawObject.position.y - 10;
pCube.position.z = mPlayer.yawObject.position.z;
// -- COLLISION DETECTION START --
var originPoint = pCube.position.clone();
for (var vertexIndex = 0; vertexIndex < pCube.geometry.vertices.length; vertexIndex++)
{
var localVertex = pCube.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( pCube.matrix );
var directionVector = globalVertex.sub( pCube.position );
var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) {
// Bottom vertices
if (vertexIndex == 2 || vertexIndex == 3 || vertexIndex == 6 || vertexIndex == 7) {
mPlayer.colBottom = true;
mPlayer.velocity.y = Math.max( 0, mPlayer.velocity.y ); // Stop falling
}
}
}
// -- COLLISION DETECTION END --
var delta = (Date.now() - time) * 0.1;
mPlayer.velocity.x += (-mPlayer.velocity.x) * 0.08 * delta; // walking
mPlayer.velocity.z += (-mPlayer.velocity.z) * 0.08 * delta; // walking
if (mPlayer.colBottom == false) {
mPlayer.velocity.y -= 0.1 * delta; // falling
}
if (mPlayer.moveForward) mPlayer.velocity.z -= mPlayer.speed * delta;
if (mPlayer.moveBack) mPlayer.velocity.z += mPlayer.speed * delta;
if (mPlayer.moveLeft) mPlayer.velocity.x -= mPlayer.speed * delta;
if (mPlayer.moveRight) mPlayer.velocity.x += mPlayer.speed * delta;
mPlayer.yawObject.translateX(mPlayer.velocity.x);
mPlayer.yawObject.translateY(mPlayer.velocity.y);
mPlayer.yawObject.translateZ(mPlayer.velocity.z);
if (mPlayer.yawObject.position.y < -2000) {
// Player has fallen out of bounds :( so re-initialise the players position
mPlayer.velocity.y = 0;
mPlayer.yawObject.position.y = 100;
mPlayer.yawObject.position.x = 0;
mPlayer.yawObject.position.z = 0;
mPlayer.yawObject.rotation.y = 0;
mPlayer.pitchObject.rotation.x = 0;
}
if (mPlayer.moveDown) {
mPlayer.yawObject.position.y -= 1;
}
if (mPlayer.moveUp) {
mPlayer.yawObject.position.y += 1;
}
}
Click here for the demo.
WASD to move. Space to jump (sort of). The black cube/rectangle mirrors the cameras position +50 on the x-axis. Collisions are detected on the cube.
Basically I have two questions about this. Should I be using the vertices of the cube to detect collisions, or the faces? If an object was smaller than the cube, no collision would be detected because it wouldn't hit any vertices. So should I rewrite it for the faces instead?
Secondly, how can I prevent the cube falling too far down when a collision is detected. If you check the demo, whenever the cube falls off something it will keep falling for a while before stopping. I assume has something to do with mPlayer.velocity.y but I haven't been able to fix it. Even jumping makes the cube sink to far down into the floor.
To increase the "resolution" of your collision detection, you could add more vertices to the cube, e.g. when you declare pCube try:
pCube = new THREE.CubeGeometry(100,100,100, 5,5,5);
and the rest of your code could remain unchanged.
With regards to smaller objects "slipping between" the rays created for collision detection, in general if you use this method but have the small object create the rays, then you will detect collisions more accurately.

Categories