3d collision environment (Player versus three point Plane)


#1

Hey forum, first time poster. Here I have made a environment where a player that is shaped like a cylinder can interact with planes defined by 3 points with a few extra player behaviors. All instructions and sources and in the comments. The code could use more tidying up but I cleaned and commented as best I could so without further adieu here’s the stuff :

/*use WASD to move
 space to jump
 pivot camera with mouse
 KEY COMMANDS:
 1 : raise a lot of land
 2 : raise some land
 3 : raise a little land
 4 : make bridge flush with feet
 5 : make bridge in front of player
 6 : make a big box
 7 : make a big ramp
 8 : make a rotating bridge
 r : reset player position
 q : adjust mouse
 SHIFT : switch first person / third person
 TAB : turn on/off debug mode
 
 bugs n' stuff:
 -(FIXED!)player sometimes does not glide smoothly downhill
 -(kinda fixed)player's speed is unnafected by hill steepness
 -(FIXED!) player's velocity is not 
 corrected when colliding with walls, allowing for clipping
 -(FIXED!)planes may block camera vision
 -if player is exactly alligned with a floor
 vertex, they will fall through (though this 
 will likely never happen)
-(FIXED!)rotating platforms now actually rotate player along
with it
 -(FIXED!)if the player enters a gap shorter than their height
 and there is a ceiling above, the ceiling may push the
 player through the floor
 -player can climb very steep hills
 (as opposed to losing control and sliding down)
 
 many ideas were gathered from this video breaking
 down the behaviour of surfaces in Mario 64
 https://www.youtube.com/watch?v=UnU7DJXiMAQ&t=1402s
 */
Player player;
ArrayList<Plane> planes;
ArrayList<Shape> shapes;
ArrayList<Plane> wallsAndFloors = new ArrayList<Plane>();
ArrayList<Plane> ceilings = new ArrayList<Plane>();
float camAngle = 0;
float cameraPitch = 0;
PVector camera;
float cameraLopiness = 10.0;
boolean debug = false; //if true shows surface normals and player
                       //collision points
boolean firstPerson = false;
void setup() {
  size(500, 500, P3D);
  player = new Player();
  player.position = new PVector(width/2+0.01, -250, -50+0.01);
  planes = new ArrayList<Plane>();
  shapes = new ArrayList<Shape>();
  camera = new PVector();
  PVector[][] points = new PVector[100][100];
  //make grid
  for (int i = 0; i < 100; i++) {
    for (int j = -50; j < 50; j++) {
      points[i][j+50]=new PVector(i*50, 0, j*50);
    }
  }
  float yoff = random(.2);
  for (int y = 0; y < points.length; y++) {
    float xoff = 0;
    for (int x = 0; x < points[y].length; x++) {
      points[x][y].y = map(noise(xoff, yoff), 0, 1, -300, 300);
      xoff += 0.02;
    }
    yoff += 0.02;
  }
  //connect points with planes
  for (int i = 0; i < points.length-1; i++) {
    for (int j = 0; j < points[i].length-1; j++) {
      Plane p = new Plane(points[i+1][j], points[i+1][j+1], points[i][j]);
      planes.add(p);
      p = new Plane(points[i][j+1], points[i][j], points[i+1][j+1]);
      planes.add(p);
    }
  }
  textureMode(NORMAL);
}
void draw() {
  background(#74AAEA);
  if (!debug) {
    lights();
    //extra lighting
    pointLight(125, 125, 125, width/2, 0, 0);
  }
  float cAM = 10;
  float cAC = pmouseX-mouseX;
  if (cAC<-cAM) {
    cAC=-cAM;
  }
  if (cAC>cAM) {
    cAC=cAM;
  }
  if (keyPressed) {
    if (key=='q'||key=='Q') {
      cAC=0;
    }
  }
  camAngle+=(cAC)/20.0;
  float yOff = width/2.6*player.tiltZ*cos(camAngle)+width/2.6*player.tiltX*sin(camAngle);
  PVector targetCamera = new PVector(width/2.0+(width/2.0)*sin(camAngle), height/1.2-mouseY+yOff, (height/2.0)*cos(camAngle));
  if (firstPerson) {
    cAC = pmouseY-mouseY;
    if (cAC<-cAM) {
      cAC=-cAM;
    }
    if (cAC>cAM) {
      cAC=cAM;
    }
    if (keyPressed) {
      if (key=='q'||key=='Q') {
        cAC=0;
      }
    }
    cameraPitch+=(cAC)/40.0;
    targetCamera = new PVector(width/2.0+45*sin(camAngle), height/2.35-cameraPitch, 45*cos(camAngle));
  }
  targetCamera = PVector.sub(targetCamera, camera);
  targetCamera.div(cameraLopiness);
  camera.add(targetCamera);
  //3rd person camera
  camera(camera.x, camera.y, camera.z, width/2, mouseY, 0, 0, 1, 0);
  player.prevPos=player.position.copy();
  player.update();
  player.display();
  player.offSetAngle=0.0;
  wallsAndFloors = new ArrayList<Plane>();
  ceilings = new ArrayList<Plane>();
  for (int i = 0; i < shapes.size(); i++) {
    shapes.get(i).Rotate(.01);
  }
  //player actually stays still. We translate the whole world around the player
  translate(-player.position.x+width/2, -player.position.y+height/2, -player.position.z);
  if (keyPressed) {
    if (key=='1') {
      moveUp(1000, 1);
    }
    if (key=='2') {
      moveUp(500, 1);
    }
    if (key=='3') {
      moveUp(250, 1);
    }
  }
  player.grounded=false;
  player.againstWall=false; 
  for (int i = 0; i < shapes.size(); i++) {
    shapes.get(i).update();
    shapes.get(i).display();
  }
  for (int i = 0; i < planes.size(); i++) {
    if (Dist(planes.get(i).getAverage(), player.position)<2500) {
      planes.get(i).update();
      planes.get(i).display();
      if (planes.get(i).type=="ceiling") {
        ceilings.add(planes.get(i));
      } else {
        wallsAndFloors.add(planes.get(i));
      }
    }
  }

  for (int i = 0; i < wallsAndFloors.size(); i++) {
    wallsAndFloors.get(i).collision();
  }
  for (int i = 0; i < ceilings.size(); i++) {
    ceilings.get(i).collision();
  }
  if (player.cancelMove) {
    player.cancelMove=false;
    player.position = player.prevPos.copy();
  }
}
public class Player {
  PVector position, dimensions, velocity, gravity, prevPos;
  boolean grounded;
  boolean cancelMove=false;
  boolean againstWall;
  boolean[] moves;
  int jumpTolerance = 9;
  int jumpTimer = 0;
  float offSetAngle = 0.0;
  float speed = 9;
  float targetSpeed = speed;
  float walkAngle;
  //offSetAngle should always be included with walkAngle
  float tiltX, tiltZ;
  Player() {
    tiltX=0;
    tiltZ=0;
    position = new PVector();
    prevPos = new PVector();
    dimensions = new PVector(50, 80, 50);
    velocity = new PVector();
    gravity = new PVector(0, .3);
    moves = new boolean[5];
  }
  void update() {
    velocity.add(gravity);
    position.add(velocity);
    walkAngle = getWalkAngle();

    if (!againstWall) {
      if (jumpTimer>0) {
        jumpTimer=0;
      }
    }

    float glideAngle = atan2(velocity.x, velocity.z);
    //the player needs to be moved up or down
    //corresponding to the angle of the floor
    position.y+=tiltZ*velocity.mag()*cos(glideAngle);
    position.y+=tiltX*velocity.mag()*sin(glideAngle);
    if (grounded) {
      if (isMoving()) {
        velocity.z=(velocity.z-speed*cos(walkAngle+offSetAngle))/2;
        velocity.x=(velocity.x-speed*sin(walkAngle+offSetAngle))/2;
      } else {
        jumpTimer=0;
      }
      if (jumpTimer>=jumpTolerance) {
        jumpTimer=0;
        velocity.y-=5;
        velocity.x*=.25;
        velocity.z*=.25;
        position.y-=18;
        grounded=false;
      }
      if (moves[4]) { //jump
        grounded=false;
        velocity.y=-10;
        //18 px is the distance from the floor the player will snap
        //we need to move the player this distance to avoid being snapped
        position.y-=18;
      }
    }
  }
  float getWalkAngle() {
    float r = walkAngle;
    if (moves[0]) {
      r=camAngle+HALF_PI;
    }
    if (moves[1]) {
      r=camAngle;
    }
    if (moves[2]) {
      r=camAngle+PI;
    }
    if (moves[3]) {
      r=camAngle+PI+HALF_PI;
    }
    if (moves[0]&&moves[1]) {
      r=camAngle+QUARTER_PI;
    }
    if (moves[3]&&moves[1]) {
      r=camAngle+PI+HALF_PI+QUARTER_PI;
    }
    if (moves[0]&&moves[2]) {
      r=camAngle+HALF_PI+QUARTER_PI;
    }
    if (moves[3]&&moves[2]) {
      r=camAngle+PI+QUARTER_PI;
    }
    return r;
  }
  boolean isMoving() {
    if (moves[0]||moves[1]||moves[2]||moves[3]) {
      return true;
    }
    return false;
  }
  void display() {
    if (!firstPerson) {
      pushMatrix();
      fill(175);
      translate(width/2, height/2, 0);    
      tiltX/=1.2;
      tiltZ/=1.2;
      rotateZ(tiltX/1.2);
      rotateX(-tiltZ/1.2);
      if (debug) {
        stroke(100);
        line(0, -dimensions.y/2, 0, 0, dimensions.y/2, 0);
        pushMatrix();
        rotateX(HALF_PI);
        ellipse(0, 0, dimensions.x, dimensions.z);
        popMatrix();
      } else {
        noStroke();
        drawCylinder(20, dimensions.z/2, dimensions.y);
      }
      popMatrix();
    }
  }
}
public class Plane {
  PVector p1, p2, p3;
  int text = 1;
  int uv = 1;
  String type = "floor";
  boolean touch = false;
  PVector touchPoint = new PVector();
  Plane(PVector P1, PVector P2, PVector P3) {
    p1 = P1;
    p2 = P2;
    p3 = P3;
  }
  void update() {
    touchPoint = new PVector();
    //we can easily decide which planes are which
    //by using the y value of its normal
    float y = getPlaneNormal(this).y;
    if (y<-.25) {
      type="floor";
    } else if (y>.25) {
      type="ceiling";
    } else {
      type="wall";
    }
  }
  void collision() {
    fill(175);

    //plane/line intersections are used to solve collisions
    if (type=="floor"||type=="ceiling") {
      PVector p = intersectPoint2(new Vector3D(0, -getPlaneNormal(this).y, 0), new Vector3D(player.position.x, player.position.y, player.position.z), toVec3D(getPlaneNormal(this)), toVec3D(p3.copy()));
      //this function solves for intersections assuming this is an infinite plane
      //therefore we need to check if the intersection is inside the triangle
      PVector[] arr = {p1, p2, p3};
      //for floors and ceilings, we can do a 2D polygon/point test
      //we just replace the y axis with the z axis
      if (polyPoint(arr, p.x, p.z)) {
        //then we check to see if player is close enough to be colliding
        //if yes, then we solve the collision

        //apply friction and other variables
        if (type=="floor") {
          //snap player to floor if they're above by certain amount
          //we account for this when the player jumps
          float snap = 18;
          if (p.y<player.position.y+player.dimensions.y/2+snap&&p.y>player.position.y) {
            touchPoint=p.copy();
            touch=true;
            player.velocity.x/=1.15;
            player.velocity.z/=1.15;
            player.speed=player.targetSpeed*-1*(getPlaneNormal(this).y);
            player.grounded=true;
            player.tiltX=getPlaneNormal(this).x;
            player.tiltZ=getPlaneNormal(this).z;
            if (player.velocity.y>0) {
              player.velocity.y=0;
            }
            player.position.y=p.y-player.dimensions.y/2;
          }
        } else {
          if (p.y<player.position.y&&p.y>player.position.y-player.dimensions.y/2) {
            touchPoint=p.copy();
            touch=true;
            if (player.grounded) {
              player.cancelMove=true;
            } else {
              player.position.y=p.y+player.dimensions.y/2;
            }
            if (!player.isMoving()) {
              player.velocity.x=0;
              player.velocity.z=0;
            }
            if (player.velocity.y<0) {
              player.velocity.y=0;
            }
          }
        }
      }
    } else if (type=="wall") {
      PVector p = intersectPoint2(new Vector3D(-getPlaneNormal(this).x, 0, -getPlaneNormal(this).z), new Vector3D(player.position.x, player.position.y, player.position.z), toVec3D(getPlaneNormal(this)), toVec3D(p3.copy()));
      float a1 = area3D(p, p1, p2);
      float a2 = area3D(p, p2, p3);
      float a3 = area3D(p, p3, p1);
      float area1 = a1+a2+a3;
      float area2 = area3D(p1, p2, p3);
      //for walls we can't do the same poly-point test,
      //so we check if the point is inside the triangle
      //if the sum of the area of the triangles formed between
      //all 4 points is equal to the original area
      if (area1/area2>.99&&area1/area2<1.01) {
        if (dist(p.x, p.y, p.z, player.position.x, player.position.y, player.position.z)<25) {
          touchPoint=p.copy();
          touch=true;
          //here we use the plane normal to help us move the player
          float cA = 25-dist(p.x, p.y, p.z, player.position.x, player.position.y, player.position.z);
          player.position.x+=getPlaneNormal(this).x*cA;
          player.position.z+=getPlaneNormal(this).z*cA;
          float dif = abs(player.walkAngle-atan2(-getPlaneNormal(this).x, -getPlaneNormal(this).z));
          while (dif > PI) {
            dif -= PI;
          }      
          if (dif>PI-.13&&dif<PI+.13) {
            player.againstWall=true;
            if (p.y<getLowest()+50) {
              player.jumpTimer++;
            }
          } else {
            //velocity correction
            if (!player.grounded) {
              PVector newVel = intersectPoint2(new Vector3D(-getPlaneNormal(this).x, 0, -getPlaneNormal(this).z), new Vector3D(player.position.x+player.velocity.x, player.position.y, player.position.z+player.velocity.z), toVec3D(getPlaneNormal(this)), toVec3D(p3.copy()));
              float y = player.velocity.y;
              player.velocity = PVector.sub(newVel, p);
              player.velocity.y=y;
            }
          }
        }
        if (debug) {
          pushMatrix();
          translate(p.x, p.y, p.z);
          noStroke();
          fill(175, 100);
          sphere(10);
          popMatrix();
        }
      }
    }
  }
  void display() {
touch=false;
    if (inView()) {
      PVector avg = new PVector((p1.x+p2.x+p3.x)/3, (p1.y+p2.y+p3.y)/3, (p1.z+p2.z+p3.z)/3);
      if (debug) {
        stroke(255, 0, 0, 100);
        line(avg.x, avg.y, avg.z, avg.x+getPlaneNormal(this).x*10, avg.y+getPlaneNormal(this).y*10, avg.z+getPlaneNormal(this).z*10);
        stroke(0, 125);
      } else {
        noStroke();
      }
      beginShape(TRIANGLES);
      //desaturated colors are less jarring
      if (type=="floor") {
        fill(129, 123, 255);
        //texture(blueprint);
      } else if (type=="wall") {
        fill(123, 255, 129);
        //texture(greenprint);
      } else {
        fill(255, 129, 123);
        //texture(redprint);
      }
      vertex(p1.x, p1.y, p1.z, 0, 0);
      vertex(p2.x, p2.y, p2.z, 1, 0);
      vertex(p3.x, p3.y, p3.z, 0, 1);
      endShape(CLOSE);
    }
  }
  boolean inView() {
    PVector a = new PVector(camera.x-width/2.0+player.position.x,
    camera.y-height/2.0+player.position.y,
    camera.z+player.position.z);
    //a.normalize();
    PVector b = p1.copy();
    PVector normalB = getPlaneNormal(this);
    PVector AB = PVector.sub(a,b);
    float dot = AB.dot(normalB);
    if (dot>0) {
      return true;
    }
    return false;
  }
  PVector getAverage() {
    return new PVector((p1.x+p2.x+p3.x)/3, (p1.y+p2.y+p3.y)/3, (p1.z+p2.z+p3.z)/3);
  }
  float getLowest() {
    float r = min(p1.y, p2.y);
    return min(r, p3.y);
  }
}
public class Shape {
  PVector position, axisOfRotation, centerOfRotation, floorTouch;
  boolean onFloor = false;
  String rotationType = "XY";
  int prolonger = 0;
  float rotation = 0.0;
  ArrayList<Plane> planes;
  Shape() {
    position=new PVector();
    planes = new ArrayList<Plane>();
    axisOfRotation = new PVector(1, 1, 0);
    axisOfRotation.normalize();
  }
  void update() {
    onFloor=false;
    for (int i = 0; i < planes.size(); i++) {
      planes.get(i).update();
      if (planes.get(i).type=="floor"&&planes.get(i).touch) {
        onFloor=true;
        floorTouch=planes.get(i).touchPoint;
        prolonger=10;
      }
    }
  }
  void display() {
    for (int i = 0; i < planes.size(); i++) {
      planes.get(i).display();
      if (planes.get(i).type=="ceiling") {
        ceilings.add(planes.get(i));
      } else {
        wallsAndFloors.add(planes.get(i));
      }
    }
  }
  void Rotate(float ang) {
    ArrayList<PVector> temp = new ArrayList<PVector>();
    for (int i = 0; i < planes.size(); i++) {
      if (!temp.contains(planes.get(i).p1)) {
        temp.add(planes.get(i).p1);
      }
      if (!temp.contains(planes.get(i).p2)) {
        temp.add(planes.get(i).p2);
      }
      if (!temp.contains(planes.get(i).p3)) {
        temp.add(planes.get(i).p3);
      }
    }
    for (int i = 0; i < temp.size(); i++) {
      if (rotationType=="XZ") {
        PVector vec = RotateXZ(temp.get(i), ang);
        //PVector vec2 = RotateXY(temp.get(i), ang);
        temp.get(i).x=vec.x;
        temp.get(i).z=vec.z;
        //temp.get(i).y=vec2.y;
      } else if (rotationType=="XY") {
        PVector vec = RotateXY(temp.get(i), ang);
        temp.get(i).x=vec.x;
        temp.get(i).y=vec.y;
      }
      //pointRotate(temp.get(i),axisOfRotation,.01);
    }
    if (rotationType=="XZ") {
      if (onFloor||prolonger>0) {
        PVector vec = RotateXZ(player.position, ang);
        PVector rot = PVector.sub(vec, player.position);
        rot.y=0;
        player.position.add(rot);
        player.walkAngle-=ang;   
        player.offSetAngle = (QUARTER_PI*10)*ang;
        camAngle-=ang;
        prolonger--;
      }
    }
  }
  void pointRotate (PVector v, PVector axis, float ang) {
    float par = axis.dot(v);
    PVector par2 = PVector.mult(axis, par);
    PVector perp = PVector.sub(par2, v);
    PVector cross = v.cross(axis);
    par2.add(cross.mult(sin(-ang)));
    par2.add(perp.mult(cos(-ang)));
    v.x = par2.x;
    v.y = par2.y;
    v.z = par2.z;
  }
  PVector RotateXZ(PVector v, float amount) {
    PVector c = centerOfRotation.copy();
    //we have to use model x or the shape
    //will quickly degrade
    float x1 = modelX(c.x, c.y, c.z);
    float x2 = modelZ(c.x, c.y, c.z);
    float x3 = modelX(v.x, v.y, v.z);
    float x4 = modelZ(v.x, v.y, v.z);
    float dist = dist(x1, x2, x3, x4);
    float angle = atan2(x4-x2, x3-x1);
    angle+=amount;
    return new PVector(x1+cos(angle)*dist, v.y, x2+sin(angle)*dist);
  }
  PVector RotateXY(PVector v, float amount) {
    PVector c = centerOfRotation.copy();
    float x1 = modelX(c.x, c.y, c.z);
    float x2 = modelY(c.x, c.y, c.z);
    float x3 = modelX(v.x, v.y, v.z);
    float x4 = modelY(v.x, v.y, v.z);
    float dist = dist(x1, x2, x3, x4);
    float angle = atan2(x4-x2, x3-x1);
    angle+=amount;
    return new PVector(x1+cos(angle)*dist, x2+sin(angle)*dist, v.z);
  }
  PVector RotateZY(PVector v, float amount) {
    PVector c = getCenter();
    float dist = dist(c.x, c.y, v.x, v.y);
    float angle = atan2(v.y-c.y, v.x-c.x);
    angle+=amount;
    return new PVector(c.x+cos(angle)*dist, c.y+sin(angle)*dist, v.z);
  }
  PVector getCenter() {
    ArrayList<PVector> temp = new ArrayList<PVector>();
    for (int i = 0; i < planes.size(); i++) {
      if (!temp.contains(planes.get(i).p1)) {
        temp.add(planes.get(i).p1);
      }
      if (!temp.contains(planes.get(i).p2)) {
        temp.add(planes.get(i).p2);
      }
      if (!temp.contains(planes.get(i).p3)) {
        temp.add(planes.get(i).p3);
      }
    }
    PVector r = new PVector();
    for (int i = 0; i < temp.size(); i++) {
      r.add(temp.get(i));
    }
    r.div(temp.size());
    return r;
  }
  void collision() {
    for (int i = 0; i < planes.size(); i++) {
      //planes.get(i).collision();
    }
  }
}
void keyPressed() {
  if (key=='r') {
    player.position.y=-1000;
    player.position.z=0;
    player.velocity.y=0;
    player.position.x=width/2.0;
  }
  if (key=='a'||key=='A') {
    player.moves[0]=true;
  }
  if (key=='w'||key=='W') {
    player.moves[1]=true;
  }
  if (key=='s'||key=='S') {
    player.moves[2]=true;
  }
  if (key=='d'||key=='D') {
    player.moves[3]=true;
  }
  if (key==' ') {
    player.moves[4]=true;
  }
}
void keyReleased() {
  if (key==TAB) {
    debug=!debug;
  }
  if (keyCode==SHIFT) {
    firstPerson=!firstPerson;
  }
  if (key=='a'||key=='A') {
    player.moves[0]=false;
  }
  if (key=='w'||key=='W') {
    player.moves[1]=false;
  }
  if (key=='s'||key=='S') {
    player.moves[2]=false;
  }
  if (key=='d'||key=='D') {
    player.moves[3]=false;
  }
  if (key==' ') {
    player.moves[4]=false;
  }
  if (key=='4') {
    makeBridge(400, 100, 50, 0);
  }
  if (key=='5') {
    makeBridge(400, 100, 100, -50);
  }
  if (key=='6') {
    makeBridge(400, 400, 400, -200);
  }
  if (key=='7') {
    makeRamp(400, 400, 400, -200);
  }
  if (key=='8') {
    makeBridge(400, 100, 100, -50);
    shapes.get(shapes.size()-1).rotationType="XZ";
  }
}
void makeBridge(float l, float w, float h, float yOff) {
  ArrayList<Plane> buffer = new ArrayList<Plane>();
  PVector x1 = player.position.copy();
  //this line helps prevent overlapping
  x1.add(new PVector(random(-.1, .1), random(-.1, .1), random(-.1, .1)));
  x1.y+=player.dimensions.y/2;
  x1.x-=sin(camAngle)*50;//move in front of player
  x1.z-=cos(camAngle)*50;
  x1.x+=sin(camAngle+HALF_PI)*w/2;
  x1.z+=cos(camAngle+HALF_PI)*w/2;
  PVector x2 = player.position.copy();
  x2.add(new PVector(random(-.1, .1), random(-.1, .1), random(-.1, .1)));
  x2.y+=player.dimensions.y/2;
  x2.x-=sin(camAngle)*l;
  x2.z-=cos(camAngle)*l;
  x2.x+=sin(camAngle+HALF_PI)*w/2;
  x2.z+=cos(camAngle+HALF_PI)*w/2;
  PVector x3 = x1.copy();
  x3.x-=sin(camAngle+HALF_PI)*w;
  x3.z-=cos(camAngle+HALF_PI)*w;
  PVector x4 = x2.copy();
  x4.x-=sin(camAngle+HALF_PI)*w;
  x4.z-=cos(camAngle+HALF_PI)*w;
  PVector x5 = x1.copy();
  PVector x6 = x2.copy();
  PVector x7 = x3.copy();
  PVector x8 = x4.copy();
  x5.y+=h;
  x6.y+=h;
  x7.y+=h;
  x8.y+=h;
  Plane p = new Plane(x1, x3, x2);
  Plane p2 = new Plane(x4, x2, x3);
  buffer.add(p); //top
  buffer.add(p2); //top
  Plane p3 = new Plane(x5, x6, x7);
  Plane p4 = new Plane(x8, x7, x6);
  buffer.add(p3); //bottom
  buffer.add(p4); //bottom
  p = new Plane(x3, x1, x7);
  p2 = new Plane(x5, x7, x1);
  buffer.add(p); //back
  buffer.add(p2); //back
  p = new Plane(x3, x7, x4);
  p2 = new Plane(x8, x4, x7);
  buffer.add(p); //right
  buffer.add(p2); //right
  p = new Plane(x1, x2, x5);
  p2 = new Plane(x6, x5, x2);
  buffer.add(p); //left
  buffer.add(p2); //left
  p = new Plane(x4, x8, x2);
  p2 = new Plane(x6, x2, x8);
  buffer.add(p); //front
  buffer.add(p2); //front
  ArrayList<PVector> visited = new ArrayList<PVector>();
  for (int i = 0; i < buffer.size(); i++) {
    if (!visited.contains(buffer.get(i).p1)) {
      visited.add(buffer.get(i).p1);
      buffer.get(i).p1.y+=yOff;
    }
    if (!visited.contains(buffer.get(i).p2)) {
      visited.add(buffer.get(i).p2);
      buffer.get(i).p2.y+=yOff;
    }
    if (!visited.contains(buffer.get(i).p3)) {
      visited.add(buffer.get(i).p3);
      buffer.get(i).p3.y+=yOff;
    }
  }
  Shape shape = new Shape();
  for (int i = 0; i < buffer.size(); i++) {
    shape.planes.add(buffer.get(i));
  }
  shape.rotationType="none";
  shape.centerOfRotation=shape.getCenter();
  shapes.add(shape);
}
void makeRamp(float l, float w, float h, float yOff) {
  ArrayList<Plane> buffer = new ArrayList<Plane>();
  PVector x1 = player.position.copy();
  x1.y+=player.dimensions.y/2;
  x1.x-=sin(camAngle)*50;//move in front of player
  x1.z-=cos(camAngle)*50;
  x1.x+=sin(camAngle+HALF_PI)*w/2;
  x1.z+=cos(camAngle+HALF_PI)*w/2;
  PVector x2 = player.position.copy();
  x2.y+=player.dimensions.y/2;
  x2.x-=sin(camAngle)*l;
  x2.z-=cos(camAngle)*l;
  x2.x+=sin(camAngle+HALF_PI)*w/2;
  x2.z+=cos(camAngle+HALF_PI)*w/2;
  PVector x3 = x1.copy();
  x3.x-=sin(camAngle+HALF_PI)*w;
  x3.z-=cos(camAngle+HALF_PI)*w;
  PVector x4 = x2.copy();
  x4.x-=sin(camAngle+HALF_PI)*w;
  x4.z-=cos(camAngle+HALF_PI)*w;
  Plane p = new Plane(new PVector(x1.x, x1.y+h, x1.z), new PVector(x3.x, x3.y+h, x3.z), x2);
  Plane p2 = new Plane(x2, new PVector(x3.x, x3.y+h, x3.z), x4);
  buffer.add(p); //top
  buffer.add(p2); //top
  Plane p3 = new Plane(x1.copy(), x2.copy(), x3.copy());
  Plane p4 = new Plane(x3.copy(), x2.copy(), x4.copy());
  p3.p1.y+=h;
  p3.p2.y+=h;
  p3.p3.y+=h;
  p4.p1.y+=h;
  p4.p2.y+=h;
  p4.p3.y+=h;
  buffer.add(p3); //bottom
  buffer.add(p4); //bottom
  p = new Plane(x1, new PVector(x3.x, x3.y+h, x3.z), x3);
  p2 = new Plane(x1, new PVector(x1.x, x1.y+h, x1.z), new PVector(x3.x, x3.y+h, x3.z));
  //buffer.add(p); //back
  //buffer.add(p2); //back
  p = new Plane(x3, new PVector(x3.x, x3.y+h, x3.z), x4);
  p2 = new Plane(x4, new PVector(x3.x, x3.y+h, x3.z), new PVector(x4.x, x4.y+h, x4.z));
  //buffer.add(p); //right
  buffer.add(p2); //right
  p = new Plane(x1, x2, new PVector(x1.x, x1.y+h, x1.z));
  p2 = new Plane(x2, new PVector(x2.x, x2.y+h, x2.z), new PVector(x1.x, x1.y+h, x1.z));
  //buffer.add(p); //left
  buffer.add(p2); //left
  p = new Plane(x2, x4, new PVector(x4.x, x4.y+h, x4.z));
  p2 = new Plane(x2, new PVector(x4.x, x4.y+h, x4.z), new PVector(x2.x, x2.y+h, x2.z));
  buffer.add(p); //front
  buffer.add(p2); //front
  ArrayList<PVector> visited = new ArrayList<PVector>();
  for (int i = 0; i < buffer.size(); i++) {
    if (!visited.contains(buffer.get(i).p1)) {
      visited.add(buffer.get(i).p1);
      buffer.get(i).p1.y+=yOff;
    }
    if (!visited.contains(buffer.get(i).p2)) {
      visited.add(buffer.get(i).p2);
      buffer.get(i).p2.y+=yOff;
    }
    if (!visited.contains(buffer.get(i).p3)) {
      visited.add(buffer.get(i).p3);
      buffer.get(i).p3.y+=yOff;
    }
  }
  for (int i = 0; i < buffer.size(); i++) {
    planes.add(buffer.get(i));
  }
}
void moveUp(float radius, float amount) {
  ArrayList<PVector> temp = new ArrayList<PVector>();
  for (int i = 0; i < planes.size(); i++) {
    if (!temp.contains(planes.get(i).p1)) {
      if (dist(player.position.x, player.position.y, player.position.z, planes.get(i).p1.x, planes.get(i).p1.y, planes.get(i).p1.z)<radius) {
        temp.add(planes.get(i).p1);
      }
    }
    if (!temp.contains(planes.get(i).p2)) {
      if (dist(player.position.x, player.position.y, player.position.z, planes.get(i).p1.x, planes.get(i).p2.y, planes.get(i).p2.z)<radius) {
        temp.add(planes.get(i).p2);
      }
    }
    if (!temp.contains(planes.get(i).p3)) {
      if (dist(player.position.x, player.position.y, player.position.z, planes.get(i).p1.x, planes.get(i).p3.y, planes.get(i).p3.z)<radius) {
        temp.add(planes.get(i).p3);
      }
    }
  }
  for (int i = 0; i < temp.size(); i++) {
    temp.get(i).y-=amount;
  }
  player.position.y-=amount;
}
boolean polyPoint(PVector[] vertices, float px, float py) {
  //source : http://www.jeffreythompson.org/collision-detection/poly-point.php
  boolean collision = false;

  // go through each of the vertices, plus
  // the next vertex in the list
  int next = 0;
  for (int current=0; current<vertices.length; current++) {

    // get next vertex in list
    // if we've hit the end, wrap around to 0
    next = current+1;
    if (next == vertices.length) next = 0;

    // get the PVectors at our current position
    // this makes our if statement a little cleaner
    PVector vc = vertices[current];    // c for "current"
    PVector vn = vertices[next];       // n for "next"

    // compare position, flip 'collision' variable
    // back and forth
    if (((vc.z >= py && vn.z < py) || (vc.z < py && vn.z >= py)) &&
      (px < (vn.x-vc.x)*(py-vc.z) / (vn.z-vc.z)+vc.x)) {
      collision = !collision;
    }
  }
  return collision;
}
float area3D(PVector a, PVector b, PVector c){
  //source : https://www.youtube.com/watch?v=PMLWa5JjH70
  PVector AB = PVector.sub(b,a);
  PVector AC = PVector.sub(c,a);
  //gives us the area of the square
  //formed by AB and AC
  PVector cross = AB.cross(AC);
  //half of that is the area of the triangle
  return .5*cross.mag();
}
PVector getPlaneNormal(Plane p) {
  PVector a = p.p2.copy().sub(p.p1.copy());
  PVector b = p.p3.copy().sub(p.p1.copy());
  PVector r = a.cross(b); 
  r.normalize();
  return r;
}
void drawCylinder(int sides, float r, float h)
{
  //source : http://vormplus.be/blog/article/drawing-a-cylinder-with-processing
  float angle = 360 / sides;
  float halfHeight = h / 2;
  // draw top shape
  pushMatrix();
  rotateX(HALF_PI);
  beginShape();
  for (int i = 0; i < sides; i++) {
    float x = cos( radians( i * angle ) ) * r;
    float y = sin( radians( i * angle ) ) * r;
    vertex( x, y, -halfHeight );
  }
  endShape(CLOSE);
  // draw bottom shape
  beginShape();
  for (int i = 0; i < sides; i++) {
    float x = cos( radians( i * angle ) ) * r;
    float y = sin( radians( i * angle ) ) * r;
    vertex( x, y, halfHeight );
  }
  endShape(CLOSE);
  beginShape(TRIANGLE_STRIP);
  for (int i = 0; i < sides + 1; i++) {
    float x = cos( radians( i * angle ) ) * r;
    float y = sin( radians( i * angle ) ) * r;
    vertex( x, y, halfHeight);
    vertex( x, y, -halfHeight);
  }
  endShape(CLOSE);
  popMatrix();
} 
Vector3D toVec3D(PVector p) {
  return new Vector3D(p.x, p.y, p.z);
}
float Dist(PVector p1, PVector p2) {
  return dist(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
}
//apologies for using two types of vectors in this code
//the same math using PVector wasn't working for some reason
PVector intersectPoint2(Vector3D rayVector, Vector3D rayPoint, Vector3D planeNormal, Vector3D planePoint) {
  //source : https://rosettacode.org/wiki/Find_the_intersection_of_a_line_with_a_plane#
  Vector3D diff = rayPoint.minus(planePoint);
  double prod1 = diff.dot(planeNormal);
  double prod2 = rayVector.dot(planeNormal);
  double prod3 = prod1 / prod2;
  Vector3D r = rayPoint.minus(rayVector.times(prod3));
  return new PVector((float)r.x, (float)r.y, (float)r.z);
}
class Vector3D {
  private double x, y, z;

  Vector3D(double x, double y, double z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  Vector3D plus(Vector3D v) {
    return new Vector3D(x + v.x, y + v.y, z + v.z);
  }

  Vector3D minus(Vector3D v) {
    return new Vector3D(x - v.x, y - v.y, z - v.z);
  }

  Vector3D times(double s) {
    return new Vector3D(s * x, s * y, s * z);
  }

  double dot(Vector3D v) {
    return x * v.x + y * v.y + z * v.z;
  }

  @Override
    public String toString() {
    return String.format("(%f, %f, %f)", x, y, z);
  }
}

#2

Looks awesome! I sort of find it amusing how that video from Pannenkoek2012 got someone to understand it well enough to make their own collision system!

Now, I wonder if one can perform BLJ in this environment of yours… :smiley: