# Prevent tires from tunneling in 2d sidescrolling car game

I am working on creating a 2d sidescoller car game (Think hill climb racing). The land is a line and generated at once. I have been setting up the physics for car tires and them hitting the lines and have the problem of tunneling. Currently when the tires are touching the ground I add 1 to the height of the car. This is a basic fix and does not solve my tunneling. Any idea on getting the tunneling to stop? If you need my code here it is. Also the code for the collisions is in Car.collide

 var keys = [];
var keyPressed = function() {
keys[keyCode] = true;
};
var keyReleased = function() {
keys[keyCode] = false;
};
var transX = 0;
var transY = 0;
var level = [

];
var collide = [];
{
var createTerrain = function(size, dist, store, scale, scm) {
this.dist = dist;
this.size = size;
this.storage = store;
this.scale = scale;
this.scm = scm;
};

createTerrain.prototype.pushNoise = function() {
for (var i = 0; i < this.size; i++) {

noiseDetail(7, 0.69);
this.storage.push([abs((noise(i * random(0, 100) * 0.02, i * random(0, 100) * 0.02) - 1) + 1) * 165]);
//this.storage.push([1,1]);
}

};
createTerrain.prototype.drawMap = function() {

for (var i = 0; i < this.storage.length - 1; i++) {
stroke(82, 35, 35);
//nnslate(10,10);
// scale(this.scale);
line((30 * i + 110) * this.scale - this.scale * this.scm + transX, (this.storage[i] / 1.5 + 100) * this.scale - this.scale * this.scm+transY, (30 * i + 140) * this.scale - this.scale * this.scm + transX, (this.storage[i + 1] / 1.5 + 100) * this.scale - this.scale * this.scm+transY);

}
};
} //Terrain
{
var isBetween = function(c, a, b) {

return (a - c) * (b - c) <= 0;

};

var isInCircle = function(x, y, cx, cy, diam) {

var dx = x - cx;

var dy = y - cy;

return dx * dx + dy * dy <= diam * diam / 4;

};

var lineCircleCollide = function(x1, y1, x2, y2, cx, cy, diam) {

var m = (y2 - y1) / (x2 - x1);

if (abs(m) > 1024) {

return lineCircleCollide(y1, x1, y2, x2, cy, cx, diam);

}

if (isInCircle(x2, y2, cx, cy, diam)) {

return true;

}

x1 -= cx;

x2 -= cx;

y1 -= cy;

y2 -= cy;

var r = diam * diam / 4;

var k = y1 - m * x1;

var a = (1 + m * m) / r;

var b = 2 * m * k / r;

var c = k * k / r - 1;

var discrim = b * b - 4 * a * c;

if (discrim < 0) {

return false;

}

discrim = sqrt(discrim);

a *= 2;

return isBetween((-b - discrim) / a, x1, x2) || isBetween((-b + discrim) / a, x1, x2);
};
} //Bob lyon
var distance = function (P1, P2, C) {
return abs( (P2.y - P1.y)*C.x - (P2.x - P1.x)*C.y + P2.x*P1.y - P2.y*P1.x ) / sqrt( pow(P2.y - P1.y, 2) + pow(P2.x - P1.x, 2) ) < C.radius; //If the distance from the center of C to the closest point on line P1P2 is less than the radius of C, returns true. Otherwise, returns false.
};
{
var car = function(locatio, accel, image, dragcoof) {
this.loc = locatio;
this.accel = accel;
//this.power = power;
this.image = image;
this.volocity = new PVector(0, 0);
this.acceleration = new PVector(0, 0);
this.drag = new PVector(0, 0);
this.dragcoof = new PVector(dragcoof.x, dragcoof.y);
//this.trans = new PVector(0,0);
this.rot = 0;
this.wheelX = 0;
this.wheelY = 0;
this.canFall = true;
};
car.prototype.draw = function() {
if (this.image === null) {
//println("please put a nice image but I will supply a simple box for testing");

pushMatrix();
translate(this.loc.x, this.loc.y);
rotate(this.rot);
fill(255, 0, 0);
rect(0, 0, 50, 10);
ellipse(7, 10, 10, 10);
ellipse(45, 10, 10, 10);
//this.wheelX= this.loc.x+7;
//this.wheelY = this.loc.y+10;

popMatrix();
this.wheelX = mouseX;
this.wheelY = mouseY;
} else {
this.image(this.loc.x, this.loc.y);
}
};
car.prototype.update = function() {
//this.momentum = this.volocity.mult(this.mass);
if (this.volocity.x >= 10) {
this.volocity.x = 9.99;
}
if (this.volocity.x <= 0.1) {
// this.volocity.x = 0;
}
if(this.canFall){
}
if(!this.canFall){
this.acceleration.y = 0;
this.volocity.y = 0;
}
transX -= (this.volocity.x);
this.drag = PVector.mult(this.volocity, this.dragcoof);
this.volocity.sub(this.drag);
transY -= (this.volocity.y);
//translate(-this.trans.x,0);
};
car.prototype.move = function(keycodeLeft, keycodeRight) {
if (keys[RIGHT] && this.volocity.x < 10) {
}
if (!keys[RIGHT] && !keys[LEFT]) {

this.acceleration.set(0, 0);
}
if (keys[LEFT] && this.volocity.x >= -0.5) {
this.acceleration.set(-0.1, 0);
}
if(keys[DOWN]){
this.rot--;
}
if(keys[UP]){
this.rot++;
}

}; // x1, y1, x2, y2, cx, cy, diam
car.prototype.collide = function(lanvar) {

for (var i = 0; i < level.length - 1; i++) {

//     fill(255, 255, 255,       2);
//      ellipse(this.loc.x+6,this.loc.y+10,10,10);
if (lineCircleCollide((30 * i + 110) * lanvar.scale - lanvar.scale * lanvar.scm + transX, (lanvar.storage[i] / 1.5 +100) * lanvar.scale - lanvar.scale * lanvar.scm+transY, (30 * i + 140) * lanvar.scale - lanvar.scale * lanvar.scm + transX, (lanvar.storage[i + 1] / 1.5 + 100) * lanvar.scale - lanvar.scale * lanvar.scm+transY, this.loc.x+6,this.loc.y+10, 10)) {
this.canFall=false;
//   collide.push([i, i + 1]);
//  println(this.loc.x+45-transX);
//            println(this.loc.x+55-transX+"  "+this.loc.y);
transY++;
fill(3, 3, 3);
text("Back Wheel hit",100,100);
break;
}
if (lineCircleCollide((30 * i + 110) * lanvar.scale - lanvar.scale * lanvar.scm + transX, (lanvar.storage[i] / 1.5 + 100) * lanvar.scale - lanvar.scale * lanvar.scm+transY, (30 * i + 140) * lanvar.scale - lanvar.scale * lanvar.scm + transX, (lanvar.storage[i + 1] / 1.5 + 100) * lanvar.scale - lanvar.scale * lanvar.scm+transY, this.loc.x+45,this.loc.y+10, 10)) {
this.canFall=false;
//  collide.push([i, i + 1]);
fill(3, 3, 3);

text("Front Wheel hit",100,100);
{
var n = new PVector(-this.loc.x+45+(30 * i + 110) * lanvar.scale - lanvar.scale * lanvar.scm + transX,-(this.loc.y+10)+(lanvar.storage[i] / 1.5 + 100) * lanvar.scale - lanvar.scale * lanvar.scm);
///println(n);
var u = new PVector((this.volocity.x*n.x)/(n.x*n.x),(this.volocity.y*n.y)/(n.y*n.y));
//var w = new PVector(PVector.subt(this.volocity,u));
var w = new PVector(this.volocity.x-u.x,this.volocity.y-u.y);
}//Just some math that is not used (now but also never is possible)
//
//transX-=this.volocity.x;

/**

var slopeX = ((30 * i + 140) * lanvar.scale - lanvar.scale * lanvar.scm + transX)-((30 * i + 110) * lanvar.scale - lanvar.scale * lanvar.scm + transX);
var lastX=this.loc.x-this.volocity.x;
if((this.loc.x-lastX)%slopeX){
println(this.loc.x+" ");
println(lastX+" lastX");
println(slopeX+" slopeX");
var slopeY = ((lanvar.storage[i + 1] / 1.5 + 100) * lanvar.scale - lanvar.scale * lanvar.scm+transY)-((lanvar.storage[i] / 1.5 + 100) * lanvar.scale - lanvar.scale * lanvar.scm+transY);

println(slopeY);
if(slopeY<0){

this.transY+=slopeY;
}

*///junk
transY++;

break;
}

this.canFall=true;
}
};
} //Car
{
var a = new car(new PVector(300, 200), new PVector(0.01, 0), null, new PVector(0.022, 0));

var b = new createTerrain(200, 2000, level, 4, 130);
b.pushNoise();

var draw = function() {
background(255, 255, 255);
a.draw();
a.update();
a.move();
text(a.acceleration + "\n" + a.volocity + "\n" + a.drag + "\n" + mouseX + "  " + mouseY + "\n" + a.loc, 400, 100.5);

b.drawMap();
a.collide(b);
//println(collide[0]);
//  println(collide[1]);
};
} //Draw and other things that don't deserve another bracket


Your code is a bit confusing to follow, due to poor indentation, useless commented code and lack of intermediate variables width a meaningful name.

That said, I can locate your problem to in the current algo to the following pseudo-code:

For each(roadSegment: in road) {
carHeight++
}
}


I can point to several issues in this approach, by order of priority:

• You're artificially raising the height of the car, but do nothing to the vertical speed. As a result, the car lifts by 1px, but keeps its downward speed (which will actually increase due to gravity!) until it is fast enough downward to tunnel one full tyre diameter's length through the road segment. You need to at least cancel that speed to prevent this.

• You should aim at matching the car's vertical speed with the slope of the road upon contact (i.e. to cancel the relative vertical velocity), to make it follow the ground instead of sinking through an uphill, or flying over a downhill. Try the following

RoadSlope=(y2-y1)/(x2-x1)

vertSpeed=max(vertSpeed, horizSpeed*roadSlope)

• carHeight++ is very arbitrary. It could be replaced by carHeight=roadHeightUnderTheTyre+tyreRadius or something similar

• You're not using a timestep (or, more accurately you're using a timestep of 1, implicitly). This will give you raw, jumpy movement, prevent increasing time resolution of collisions, and increase likelihood of tunneling. I really recommend you to read this excellent blog entry as a reference (I'm not affiliated to this blog in any way, but it is a good read)

• The car has no suspensions, so the collisions will feel very "sticky", try to implement springs in the tyres. This will mitigate contact forces and also reduce tunneling as collisions will be smooth

• The car has no angle, so it will stay awkwardly horizontal on slopes. While this is fine for now, be careful that a collision event on the front tyre doesn't cancel a rear tyre collision reaction and vice-versa. That may undo your hard work and insist on tunneling again.

Happy coding!

• Oh wow thanks! (Not sarcastic) I would upvote but I don't have enough rep :( the pseudo code it very helpful to figure it out. +Accepted (if you have another SE accounts I am going to upvote what I can) – Christopher Sep 16 '16 at 0:15
• One question with the vert speed wouldn't that just make me fall faster? Would vertSpeed=-max(codestuff); be correct – Christopher Sep 16 '16 at 0:17
• Hum I was assuming your vertical axis was pointed up. So the higher the speed value, the more upwards the car goes... the won't make it worse, that'll induce bouncing at worst. The max is here because if the car is already going up fast enough upwards that it lifts off the slope (maybe the rear tire does that?), then there's no need for the front tire to make it even faster... Does that make sense? – MrBrushy Sep 16 '16 at 6:21
• Oh, and no need to upvote unrelated stuff, simply accept the answer if it actually helps (mass upvote will be reversed by S:O anyway). Thanks! – MrBrushy Sep 16 '16 at 6:28