How to realistically simulate car steering physics - javascript

I'm making a car simulation in Javascript and I am using this website to help me with the physics: http://www.asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html
The current progress of the simulation can be seen on my website:
https://cloudynet.tk/projects/car-sim/code.html
The problem I have is with the car steering physics. I have managed to get the low-speed steering to work correctly but the high-speed steering (where lateral forces are introduced) is very hard to get right. I understand how slip angles influence the lateral force but in my simulation, it is not working very well. I'm wondering if the implementation of the lateral force is correct or is there something wrong with the code? Also, I don't quite understand if the longitudinal force and the lateral force affects a single velocity vector or is separated into two "directional" vectors.
Here's my current physics function (whole code can be seen on the website):
applyPhysics() {
// Get car direction vector
let direction = new Vector(1, 0);
direction = Vector.rotate(direction, this.carAngle);
// LONGITUDINAL FORCES
// Traction forces
let tractionForce = direction.copy(); // Traction force (engine power)
if (this.engineForce) {
tractionForce.mult(this.engineForce);
}
else if (this.brakingForce) {
tractionForce.mult(-this.brakingForce);
}
// Frictional forces
let dragForce = this.velocity.copy(); // Air resistance force
dragForce.mult(this.velocity.getMag())
dragForce.mult(-this.drag);
let rollingResistanceForce = this.velocity.copy(); // Rolling resistance force (friction with ground)
rollingResistanceForce.mult(-this.rrDrag);
let netLongitudinalForce = tractionForce.copy(); // Total longitudinal force
netLongitudinalForce.add(dragForce)
netLongitudinalForce.add(rollingResistanceForce);
// Calculate acceleration
this.acceleration = netLongitudinalForce.copy();
this.acceleration.div(this.mass);
if (this.acceleration.getMag() < 0.001)
this.acceleration = new Vector();
// Calculate velocity
let accelerationDelta = this.acceleration.copy();
accelerationDelta.mult(dt);
this.velocity.add(accelerationDelta);
this.velDir = this.velocity.getDir();
this.sideslipAngle = this.carAngle - this.velDir; // Calculate side slip angle
if (this.speed > 20) { // High speed-turning
// LATERAL FORCES
let peakSlipAngle = 5;
// Calculate slip angle for back wheel
var c = this.wheels.baseline/2;
var omegaC = this.angularVelocity*c;
var longV = Math.cos(this.carAngle) * this.velocity.getMag();
var latV = Math.sin(this.carAngle) * this.velocity.getMag();
this.wheels.back.slipAngle = Math.atan(((latV - omegaC)/Math.abs(longV)) || 0);
var backSlipDeg = deg(this.wheels.back.slipAngle)
this.wheels.back.lateralForce = 5000*Math.sign(this.wheels.back.slipAngle);
if (backSlipDeg < peakSlipAngle && backSlipDeg > -peakSlipAngle) {
this.wheels.back.lateralForce = 5000*backSlipDeg/peakSlipAngle;
} else {
this.wheels.back.lateralForce = 5000*(1-((Math.abs(backSlipDeg)-peakSlipAngle)/500))*Math.sign(this.wheels.back.slipAngle);
}
// Calculate slip angle for front wheel
var b = this.wheels.baseline/2;
var omegaB = this.angularVelocity*b;
var longV = Math.cos(this.wheels.front.slipAngle) * this.velocity.getMag();
var latV = Math.sin(this.wheels.front.slipAngle) * this.velocity.getMag();
this.wheels.front.slipAngle = Math.atan((((latV - omegaB)/Math.abs(longV)) || 0)-this.steeringAngle*Math.sign(longV));
var frontSlipDeg = deg(this.wheels.front.slipAngle);
this.wheels.front.lateralForce = 5000*Math.sign(this.wheels.front.slipAngle);
if (frontSlipDeg < peakSlipAngle && frontSlipDeg > -peakSlipAngle) {
this.wheels.front.lateralForce = 5000*frontSlipDeg/peakSlipAngle;
} else {
this.wheels.front.lateralForce = 5000*(1-((Math.abs(frontSlipDeg)-peakSlipAngle)/500))*Math.sign(this.wheels.front.slipAngle);
}
// Calculate cornering force
this.corneringForce = this.wheels.back.lateralForce + Math.cos(this.steeringAngle) * this.wheels.front.lateralForce;
// Calculate centripetal force
this.centripetalForce = this.mass * (this.velocity.getMag() ** 2) / this.wheels.baseline/Math.sin(this.steeringAngle);
var lateralDirection = new Vector(0, -1);
lateralDirection = Vector.rotate(lateralDirection, this.carAngle);
let lateralForce = lateralDirection.copy();
lateralForce.mult(this.corneringForce);
this.latAcceleration = lateralForce.copy();
this.latAcceleration.div(this.mass);
if (this.latAcceleration.getMag() < 0.001)
this.latAcceleration = new Vector();
let latAccelerationDelta = this.latAcceleration.copy();
latAccelerationDelta.mult(dt);
this.latVelocity.add(latAccelerationDelta);
// Calculate position
let latVelocityDelta = this.latVelocity.copy();
latVelocityDelta.mult(dt);
this.pos.add(latVelocityDelta);
} else {
this.velocity = Vector.rotate(this.velocity, this.carAngle - this.velDir); // Correct velocity based on car orientation
}
// Calculate position
let velocityDelta = this.velocity.copy();
velocityDelta.mult(dt);
this.pos.add(velocityDelta);
// Calculate speed
this.speed = this.velocity.getMag();
}

I believe the problem is with the lines regarding the slip angles. I would look over the script carefully or ctrl f to find where you typed Math.sign rather than Math.sin(). I'm not sure if this is the problem but it's something I noticed looking over your code. I'm working on a basic drifting game with javascript and it requires a lot of the same physics as your project.
Hopefully, I was able to help

Related

Attempting to make Ray Tracing inside of p5.js but function recursion is acting weird

So, I found a source online that went over ray tracing for c++ (https://www.scratchapixel.com/code.php?id=3&origin=/lessons/3d-basic-rendering/introduction-to-ray-tracing)
I decided to go into p5.js and attempt to replicate what they have in their source code, but ran into an error when I got to function recursion. To add reflections they used recursion and ran the same function again, but when I attempt the same thing I get all sorts of incorrect outputs... This is my code:
https://editor.p5js.org/20025249/sketches/0LcyoY8yS
function trace(rayorig, raydir, spheres, depth) {
let tnear = INFINITY;
let sphere;
// find intersection of this ray with the spheres in the scene
for (let i = 0; i < spheres.length; i++) {
t0 = INFINITY;
t1 = INFINITY;
if (spheres[i].intersect(rayorig, raydir)) {
if (t0 < 0) t0 = t1;
if (t0 < tnear) {
tnear = t0;
sphere = spheres[i];
}
}
}
// if there's no intersection return black or background color
if (!sphere) return createVector(2, 2, 2);
let surfaceColor = createVector(0); // color of the ray/surfaceof the object intersected by the ray
let phit = createVector(rayorig.x, rayorig.y, rayorig.z).add(createVector(raydir.x, raydir.y, raydir.z).mult(tnear)); // point of intersection
let nhit = createVector(phit.x, phit.y, phit.z).sub(sphere.center); // normal at the intersection point
nhit.normalize(); // normalize normal direction
// If the normal and the view direction are not opposite to each other
// reverse the normal direction. That also means we are inside the sphere so set
// the inside bool to true. Finally reverse the sign of IdotN which we want
// positive.
let bias = 1e-4; // add some bias to the point from which we will be tracing
let inside = false;
if (createVector(raydir.x, raydir.y, raydir.z).dot(nhit) > 0) {
nhit = -nhit;
inside = true;
}
if ((sphere.transparency > 0 || sphere.reflection > 0) && depth < MAX_RAY_DEPTH) {
let facingratio = createVector(-raydir.x, -raydir.y, -raydir.z).dot(nhit);
// change the mix value to tweak the effect
let fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1);
// compute reflection direction (not need to normalize because all vectors
// are already normalized)
let refldir = createVector(raydir.x, raydir.y, raydir.z).sub(createVector(nhit.x, nhit.y, nhit.z).mult(2).mult(createVector(raydir.x, raydir.y, raydir.z).dot(nhit)));
refldir.normalize();
// Here is the error:
let reflection = trace(createVector(phit.x, phit.y, phit.z).add(createVector(nhit.x, nhit.y, nhit.z).mult(bias)),
refldir,
spheres,
depth+1
);
let refraction = createVector(0);
// // if the sphere is also transparent compute refraction ray (transmission)
// if (sphere.transparency) {
// let ior = 1.1
// let eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface?
// let cosi = createVector(-nhit.x, -nhit.y, -nhit.z).dot(raydir);
// let k = 1 - eta * eta * (1 - cosi * cosi);
// let refrdir = createVector(raydir.x, raydir.y, raydir.z).mult(eta).add(createVector(nhit.x, nhit.y, nhit.z).mult(eta * cosi - sqrt(k)));
// refrdir.normalize();
// refraction = trace(createVector(phit.x, phit.y, phit.z).sub(createVector(nhit.x, nhit.y, nhit.z).mult(bias)),
// refrdir,
// spheres,
// depth + 1
// );
// }
// the result is a mix of reflection and refraction (if the sphere is transparent)
surfaceColor = (
createVector(reflection.x, reflection.y, reflection.z)
.mult(fresneleffect)
.add(
createVector(refraction.x, refraction.y, refraction.z).mult(1 - fresneleffect).mult(sphere.transparency)
)
)
.mult(sphere.surfaceColor);
}
return createVector(surfaceColor.x, surfaceColor.y, surfaceColor.z).add(sphere.emissionColor);
}
The error is that the reflections don't give me the same output as the c++ script and seems to be wonky. I cannot for the love of me figure out why the recursive function just doesn't work.
I have attempted to run it without the recursion and it worked perfectly fine but the recursion is where is breaks
The way I found the error was by printing on the original c++ script and printing on the one I was making and it all works up until the recursive reflections. I get the correct first output but then it all goes down hill.
Their outputs:
[-0.224259 3.89783 -19.1297]
[-0.202411 3.88842 -19.0835]
[-0.180822 3.88236 -19.0538]
My outputs:
[-0.224259 3.89783 -19.1297] // correct
[-0.000065 0.001253 -0.005654] // incorrect
[-0.000064 0.00136 -0.00618] // incorrect
Summary: I made a function that works but the recursion breaks it and I cannot figure out why

Detect when marker crosses waypoint radius

I'm animating a marker along a route with waypoints and I am trying to detect when the marker reaches/ crosses a waypoint.
I've managed to get the animation part but seems to fail on the detections
here is my code
I'm creating the route
p = data.map(function(o){ return o.steps.map(function(g) { return g.geometry.coordinates }) });
const locations = p.reduce(function(arr, val){
arr = arr.concat(val); return arr ;
}, [] ),
route = new MultiLineString(locations);
I get the waypoints
intersections = data.reduce(function(s,c){ s=(!s?[]:s); return s = s.concat(c.steps.reduce(
function(e,a){
e = (!e? [] : e);
e.push(a.maneuver.location);
return e; },[])); return s;
},[]),
I create an extent from the waypoint on "right arrow click"
waypoint = fromLonLat(intersections[0])
.concat(fromLonLat(intersections[1]));
Then trying to detect when the marker is animated pass the waypoint
function moveFeature(event) {
const speed = 60;
const time = event.frameState.time;
const elapsedTime = time - lastTime;
distance = (distance + (speed * elapsedTime) / 1e6) % 2;
lastTime = time;
const currentCoordinate = route.getLineString(distance).getCoordinateAt(distance);
if(isNaN(currentCoordinate[0]) || (distance > 1) || Extent.containsCoordinate(waypoint, currentCoordinate) ) return stopAnimation();
position.setCoordinates(currentCoordinate);
const vectorContext = getVectorContext(event);
vectorContext.setStyle(styles.geoMarker);
vectorContext.drawGeometry(position);
map.getView().setCenter(currentCoordinate);
map.render();
}
But it doesn't seem to be true. Is there another way I can detect when the marker is past a certain point/ radius ?
Unless movefeature stops exactly on the extent Extent.containsCoordinate will not return true and the animation will continue past the point. It would be better to calculate the distance (or fraction of total distance) to the waypoint and stop when that is reached
const nearest = route.getClosestPoint(waypoint);
const partCoordinates = [];
route.forEachSegment(function(start, end){
partCoordinates.push(start.slice());
if (new LineString([start, end]).intersectsCoordinate(nearest)) {
partCoordinates.push(nearest);
return true;
}
});
const fraction = new LineString(partCoordinates).getLength() / route.getLength();

Three.js performance very slow using onMouseMove with RayCaster

I'm building an application in three.js, however I'm having real problems with performance. This part of the application is based upon the Voxel Painter example. In my version, the user clicks on a cell to begin placement, drags the cursor to where they wish to end placement, and clicks to end.
function onDocumentMouseMove(event) {
//set up mouse and raycaster
event.preventDefault();
mouse.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
switch (buildMode) {
case buildModes.CORRIDOR:
scene.add(rollOverFloor);
var intersects = raycaster.intersectObjects(gridObject);
if (intersects.length > 0) {
var intersect = intersects[0];
if (beginPlace == true) {
//store the intersection position
var endPlace = new THREE.Vector3(0, 0, 0);
endPlace.copy(intersect.point).add(intersect.face.normal);
endPlace.divideScalar(step).floor().multiplyScalar(step).addScalar(step / step);
endPlace.set(endPlace.x, 0, endPlace.z);
corridorDrag(endPlace);
}
//if user hasn't begun to place the wall
else {
//show temporary wall on grid
rollOverFloor.position.copy(intersect.point).add(intersect.face.normal);
rollOverFloor.position.divideScalar(step).floor().multiplyScalar(step).addScalar(step / step);
rollOverFloor.position.set(rollOverFloor.position.x, 0, rollOverFloor.position.z);
}
}
break;
}
render();
}
The code above is called when the user moves the mouse (there are many buildmodes in the main application, but I have not included them here). This function simply gets a start and end point, the corridorDrag() function fills in the cells between the start and end points:
function corridorDrag(endPlace) {
deleteFromScene(stateType.CORRIDOR_DRAG);
var startPoint = startPlace;
var endPoint = endPlace;
var zIntersect = new THREE.Vector3(startPoint.x, 0, endPoint.z);
var xIntersect = new THREE.Vector3(endPoint.x, 0, startPoint.z);
var differenceZ = Math.abs(startPlace.z - zIntersect.z);
var differenceX = Math.abs(startPlace.x - xIntersect.x);
var mergedGeometry = new THREE.Geometry();
for (var i = 0; i <= (differenceZ / step); i++) {
for (var j = 0; j <= (differenceX / step); j++) {
var x = startPlace.x;
var y = startPlace.y;
var z = startPlace.z;
if (endPoint.x <= (startPlace.x )) {
if (endPoint.z <= (startPlace.z)) {
x = x - (step * j);
z = z - (step * i);
}
else if (endPoint.z >= (startPlace.z)) {
x = x - (step * j);
z = z + (step * i);
}
} else if (endPoint.x >= (startPlace.x)) {
if (endPoint.z <= (startPlace.z)) {
x = x + (step * j);
z = z - (step * i);
}
else if (endPoint.z >= (startPlace.z)) {
x = x + (step * j);
z = z + (step * i);
}
}
floorGeometry.translate(x, y, z);
mergedGeometry.merge(floorGeometry);
floorGeometry.translate(-x, -y, -z);
}
}
var voxel = new THREE.Mesh(mergedGeometry, tempMaterial);
voxel.state = stateType.CORRIDOR_DRAG;
scene.add(voxel);
tempObjects.push(voxel);
}
Firstly, the deleteFromScene() function removes all current highlighted cells from the scene (see below). The code then (I believe), should create a number of meshes, depending on the start and end points, and add them to the scene.
function deleteFromScene(state) {
tempObjects = [];
var i = scene.children.length;
while (i--) {
if (scene.children[i].state != undefined)
if (scene.children[i].state == state)
scene.children.splice(i, 1);
}
}
As I said, it is very, very slow. It also appears to be adding an obscene amount of vertices to the renderer, as seen in the WebGLRenderer stats window. I have no idea why it's adding so many vertices, but I'm assuming that's why it's rendering so slowly.
The application can be viewed here - the problem can be seen by clicking on one cell, dragging the cursor to the other end of the grid, and observing the time taken to fill in the cells.
Thank you in advance, this really is a last resort.
A few years ago Twitter put out an update. In this update they had just introduced infinite scrolling and on the day of its release the update was crashing users browsers. Twitter engineers did some investigating and found that the crashes were the result of the scroll event firing hundreds of times a second.
Mouse events can fire many MANY times a second and can cause your code to execute too often, which slows down the browser and (in many cases) crashes it. The solution for Twitter (and hopefully you) was simple: Poll your event.
Inside your mousemove event handler check that it has been some number of milliseconds since the last move event.
var lastMove = Date.now();
function onDocumentMouseMove(event) {
if (Date.now() - lastMove < 31) { // 32 frames a second
return;
} else {
lastMove = Date.now();
}
// your code here
}
I hope that helps!

How do I determine the efficiency of mouse movement from Point A to Point B in Javascript?

I am trying to create an analytical program which keeps track of user mouse movement on a website and stores the data in a DB. Here is where I am stuck:
Assuming the mouse is always starting at the middle of the screen, and the user is instructed to move it to a particular element, how do I determine the efficiency and accuracy of that movement. I need to keep in mind the duration from start of hovering till the click, but I want to also include the hovering path of the mouse.
A perfect score would be a perfect line from Point A to Point B in x seconds, how do I determine the score of a curved path in 2x seconds, or an instance where the path goes in the wrong direction before proceeding to Point B? Are there any algorithms in existence?
Thanks for your help!
Here is a JSFiddle that I created. Click on the START box and then click on the FINISH box. Hopefully this will help you get started.
var start = false;
var start_time,end_time;
var points = [];
$("#start").click(function() {
start = true;
points = [];
start_time = Date.now();
});
$("#finish").click(function() {
start = false;
distance = travelledDistance();
time = (Date.now() - start_time)/1000;
var center_x_start = $("#start").offset().left + $("#start").width() / 2;
var center_y_start = $("#start").offset().top + $("#start").height() / 2;
var center_x_finish = $("#finish").offset().left + $("#finish").width() / 2;
var center_y_finish = $("#finish").offset().top + $("#finish").height() / 2;
var straight_distance = Math.round(Math.sqrt(Math.pow(center_x_finish - center_x_start, 2) + Math.pow(center_y_finish - center_y_start, 2)));
$("#time").text(+time+"s");
$("#distance").text(distance+"px");
$("#straight_distance").text(straight_distance+"px");
});
$(document).mousemove(function( event ) {
if(!start)
return;
points.push(event.pageX + "," + event.pageY);
});
function travelledDistance(){
var distance = 0;
for (i = 0; i < points.length - 1; i++) {
start_point = points[i].split(",");
end_point = points[i+1].split(",");
distance += Math.round(Math.sqrt(Math.pow(end_point[0] - start_point[0], 2) + Math.pow(end_point[1] - start_point[1], 2)));
}
return distance;
}
UPDATE
I made a new version here. Now you can drag the targets to check the different results.

Html5 canvas how to generate multiple images

Im making a simple zombie game in html5 canvas and wanted to know how to create a zombie every x seconds in a random place? so far i have
var zombies = new Array();
function SummonZombies (){
TotalZombies++;
zombies[TotalZombies] = new Image();
zombies[TotalZombies].src = 'images/monster.png';
ctx.drawImage(zombies[TotalZombies], zombie_x, zombie_y);
}
Only one zombie is being created with this? how would i get it to generate more.
First of all, where are you declaring the variable TotalZombies?
Try something like this :
var zombies = new Array();
for (var i = 0; i < 100; i++) {
var zombie = new Image();
zombie.src = 'images/monster.png';
ctx.drawImage(zombie, Math.floor((Math.random()*100)+1), Math.floor((Math.random()*100)+1));
zombies.push(zombie);
}
This will create 100 zombies, with random x and y positions between 1 and 100. It will add each zombie to the zombies array after they have been instantiated.
You should iterate through zombies array, and invoke drawImage() on everyone.
Extra tip: remember to change x and y after all iteration.
You must separate a Zombi from your zombies :
create a class that will describe what a Zombi is, and only after you will define a collection of such lovely guys and girls :
// This Class defines what a Zombi is.
function Zombi(x,y) {
this.x = x;
this.y = y;
}
var ZombiImage = new Image();
ZombiImage.src = "images/monster.png";
// image of a zombi is shared amongst all zombies, so it is
// defined on the prototype
Zombi.prototype.image = ZombiImage;
// draw the zombi on provided context
Zombi.prototype.draw = function(ctx) {
ctx.drawImage(this.image, this.x, this.y);
}
Now for the collection :
// This class defines a collection of Zombies.
function Zombies() {
this.zombies = [];
}
// summons a zombi at a random place. returns the summoned zombi.
myZombies.prototype.summon() {
var randX = Math.random()*100;
var randY = Math.random()*100;
return this.summonAt(randX, randY);
}
// summons a zombi at x,y. returns the summoned zombi.
myZombies.prototype.summonAt = function (x,y) {
var newZombi = new Zombi(x,y);
this.zombies.push();
return newZombi;
}
// draws all zombies on provided context.
myZombies.prototype.drawAll = function (ctx) {
var i=0;
var __zombies = this.zombies;
for (;i<__zombies.length; i++) {
__zombies[i].draw(ctx);
}
}
// collection of all zombies for your game.
var zombies = new Zombies();
// here you can call zombies.summon(); or zombies.drawAll();
// and even zombies.summonAt(x,y);
In fact the code above is simplified : you must handle the onload event of the image to start the game only after the image was loaded.
But you should get the idea : separate the issues (handle ONE zombi vs a collection of zombies) will get you faster to your goal.
With this -simple- design, you'll be able to easily add-up behaviour to your zombies.
Just one more example in which i will add the seekBrain and walk behaviour :
// This Class defines what a Zombi is.
function Zombi(x,y) {
this.x = x;
this.y = y;
this.dirX = 0 ; // direction X
this.dirY = 0; // direction Y
this.speed = 0.1; // common speed for all zombies
}
// have the zombi seek the brain located at (x,y)
Zombi.prototype.seekBrain = function (x,y) {
this.dirX = (x - this.x );
this.dirY = (y - this.y );
// normalize direction
var norm = Math.sqrt( this.dirX*this.dirX + this.dirY*this.dirY );
this.dirX/=norm;
this.dirY/=norm;
}
// Have the zombi walk in its current direction
Zombi.prototype.walk = function() {
this.x += this.dirX * this.speed;
this.y += this.dirY * this.speed;
}
// image and draw remains the same
And now you might want for your collection :
// makes all zombies walk.
Zombies.walkAll = function() {
var i=0;
var __zombies = this.zombies;
for (;i<__zombies.length; i++) {
__zombies[i].walk();
}
}
// constructor, summon, summonAt, and drawAll remains the same.
So to summon a zombi at random place every xxx ms, do something like :
// summons a zombi at a random place every 2 seconds (==2000 ms)
setTimeInterval(2000, function() { zombies.summon(); } );
now, if hero.x and hero.y are what we guess, you can do :
// Have a random zombi hunt for hero's brain every 2 seconds
setTimeInterval(2000, function() {
var which = Math.floor(zombies.zombies.length * Math.random());
zombies.zombies[which].seekBrain(hero.x, hero.y);
} );
provided you call to zombies.walkAll(); and zombies.drawAll(); on a regular basis, you've got the start of a game ! (i love so much zombies :-) )

Categories

Resources