Vehicles over 3D terrain

Hello

Does anyone know of a reference for creating a system of vehicles moving over a 3D rendered terrain?

Thank you.

Interesting. “System of vehicles” as in flocking? How are they moving as a system?

Or do you want a flow field that works like a topographical map?

This is an old project, not processing, but it is one example of the concepts involved found on a keyword search – flocking plus 3D terrain

https://digitalcommons.unl.edu/cgi/viewcontent.cgi?httpsredir=1&article=1127&context=csetechreports

1 Like

Yes, a flocking system will do it. I am just interested in agents moving about a tridimensional landscape. They may wander or flock or whatever. A topographical field might be useful.

Thanks for the link, though I only know processing I’ll check it out.

If you are building your own, a good way to get started is to generate a 3d terrain

then have an agent move over on in a straight line – that is, update its x and y, but take its z from the terrain intercept. So, given a mesh / terrain and an x,y coordinate on it, what is the height intercept?

Working this out for one agent with simple motion, THEN adding 2D flocking on the x,y behaviors of your agents, and you will have vehicles flocking on 3D terrain. In essence, the 2d flocking sketch and the 3D single agent on terrain sketch can be easily combined.

After that, you may wish to then indicate impassible terrain, or modify motion for angle of attack on a steep grade etc.

Edit: I should note that just projecting z does have a downside – things move faster on the grade than the do on the flat, which may look odd for boyds heading uphill–depending on what you are trying to simulate. Still, it is a simple way to start.

1 Like

Flocking

Steering behaviours

I succeeded in making a thing move through the mountains coherently, i.e, its z coordinate adapts to the particular value of where it is standing on the 2D array.

Here’s the code.



int cols, rows;
int scl = 20; //scale
int w = 1200; //width, adjustable
int h = 900; //height, adjustable

int atX = 0;
int atY = 0;

float [][] z;

float moveOffX = 0;
float moveOffY = 0;

void setup() {
  size(1600, 1100, P3D);
  frameRate(60);


  cols = w / scl;
  rows = h / scl;
  z = new float[cols][rows];
  float yoff = 0; //small float offset for smooth changes in value
  for (int y = 0; y < rows; y++) {
    float xoff = 0;
    for (int x = 0; x < cols; x++) {
      z[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100); //output of map(last numbers) controlled for height of mountains.
      xoff += 0.2;//change offsets to control smoothness.
    }
    yoff +=0.2;
  }
}

void draw() {
  background(0);
  stroke(255);
  fill(111, 222, 55);

  translate(width/2, height/2); //prepares to rotate everything relative to center of window
  rotateX(radians(45));             


  translate(-w/2, -h/2); //offsets beginning of coordinates so that all of them appear in the screen. 

  //TRIANGLE_STRIP: LAND
  for (int y = 0; y < rows-1; y++) {
    beginShape(TRIANGLE_STRIP);
    for (int x = 0; x < cols; x++) {
      vertex(x*scl, y*scl, z[x][y]);
      vertex(x*scl, (y+1)*scl, z[x][y+1]);
    }
    endShape();
  }

  for (int y = 0; y < rows-1; y++) {
    for (int x = 0; x < cols; x++) {

      if (x == atX && y == atY) {      
        fill(255, 0, 0);
        pushMatrix();
        translate(x*scl, y*scl, z[x][y] + 9); //the addition makes the ellipse not intersect the mountains for the most part. 
        ellipse(0, 0, 19, 19);
        popMatrix();
      }
    }
  }

  //these are the coordinates now. I can control these to control the thing's movement. 
  atX = int(map(mouseX, 0, width, 0, cols)); 
  atY = int(map(mouseY, 0, height, 0, rows)); 

}

I am at odds however not knowing how to introduce an agent system over such a rugged plane. The agent system in question is irrelevant for now. I don’t need to learn flocking. I just need to figure out how to put an agent-based, vector-based system over the mountainous terrain and look natural (natural enough). To begin with, the simplest agent based system could implement simple seeking behavior, in which a single Vehicle pursues a target. The target could be the red circle that you can move with the mouse in my sketch. I know how to do this on a regular window, but I need it to occur over the terrain.

2 Likes

Congrats on solving this. If you know how to do it in 2d, is it possible to extend that method to 3d, ie given that your vehicle will have 3 components x,y,z if you know where it is in the x and z location, would it be possible to calculate the angle the vehicle needs to be based on the height of the tiles below it. So that if all tiles where flat the vehicle would be completely flat in the y axis, then step through each wheel location and adjust how high they should be

1 Like

Natural how? Is the idea that you want the agent going uphill to be slower than downhill, and for the agent to choose a path to the target that optimizes their speed or their arrival time? Or just avoids grades steeper than x?

1 Like

Natural how? Is the idea that you want the agent going uphill to be slower than downhill, and for the agent to choose a path to the target that optimizes their speed or their arrival time? Or just avoids grades steeper than x?

Oh no. I am not worrying about that stuff yet. I just want its movement to look less choppy. I think the reason why it does is because the agent uses as its coordinates the nodes of the two-dimensional array, which have to be integers as far as I can tell. You can see in the following example what I mean: the rotation of the triangle is continuous, but the movement through the map is not. I wish it was.

Also, I haven’t quite figured out how to create more than one agent.


Vehicle wanderer;

int cols, rows;
int scl = 20; //scale
int w = 1200; //width, adjustable
int h = 900; //height, adjustable

int atX = 0;
int atY = 0;

float [][] z;

float moveOffX = 0;
float moveOffY = 0;

void setup() {
  size(1600, 1100, P3D);
  frameRate(60);

  wanderer = new Vehicle(width/5, height/5);


  cols = w / scl;
  rows = h / scl;
  z = new float[cols][rows];
  float yoff = 0; //small float offset for smooth changes in value
  for (int y = 0; y < rows; y++) {
    float xoff = 0;
    for (int x = 0; x < cols; x++) {
      z[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100); //output of map(last numbers) controlled for height of mountains.
      xoff += 0.2;//change offsets to control smoothness.
    }
    yoff +=0.2;
  }
}

void draw() {
  background(0);
  stroke(255);
  fill(111, 222, 55);

  translate(width/2, height/2); //prepares to rotate everything relative to center of window
  rotateX(radians(45));             


  translate(-w/2, -h/2); //offsets beginning of coordinates so that all of them appear in the screen. 

  //TRIANGLE_STRIP: LAND
  for (int y = 0; y < rows-1; y++) {
    beginShape(TRIANGLE_STRIP);
    for (int x = 0; x < cols; x++) {
      vertex(x*scl, y*scl, z[x][y]);
      vertex(x*scl, (y+1)*scl, z[x][y+1]);
    }
    endShape();
  }

  for (int y = 0; y < rows-1; y++) {
    for (int x = 0; x < cols; x++) {

      //atX and atY are the coordinates
      if (x == atX && y == atY) {      
        fill(255, 0, 0);
        pushMatrix();
        translate(x*scl, y*scl, z[x][y] + 12); //the addition makes the triangle not intersect the mountains for the most part. 
        wanderer.wander();
        wanderer.run();
        popMatrix();
      }
    }
  }
}


class Vehicle {

  PVector position;
  PVector velocity;
  PVector acceleration;
  float r;
  float wandertheta;
  float maxforce;    // Maximum steering force
  float maxspeed;    // Maximum speed

  Vehicle(float x, float y) {
    acceleration = new PVector(0, 0);
    velocity = new PVector(0, 0);
    position = new PVector(x, y);
    r = 6;
    wandertheta = 0;
    maxspeed = 2;
    maxforce = 0.05;
  }

  void run() {
    update();
    borders();
    display();
  }

  // Method to update position
  void update() {
    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(maxspeed);
    position.add(velocity);
    // Reset accelertion to 0 each cycle
    acceleration.mult(0);

    atX = int(map(position.x, 0, width, 0, cols)); 
    atY = int(map(position.y, 0, height, 0, rows));
  }

  void wander() {
    float wanderR = 25;         // Radius for our "wander circle"
    float wanderD = 80;         // Distance for our "wander circle"
    float change = 0.3;
    wandertheta += random(-change, change);     // Randomly change wander theta

    // Now we have to calculate the new position to steer towards on the wander circle
    PVector circlepos = velocity.get();    // Start with velocity
    circlepos.normalize();            // Normalize to get heading
    circlepos.mult(wanderD);          // Multiply by distance
    circlepos.add(position);               // Make it relative to boid's position

    float h = velocity.heading2D();        // We need to know the heading to offset wandertheta

    PVector circleOffSet = new PVector(wanderR*cos(wandertheta+h), wanderR*sin(wandertheta+h));
    PVector target = PVector.add(circlepos, circleOffSet);
    seek(target);
  }

  void applyForce(PVector force) {
    // We could add mass here if we want A = F / M
    acceleration.add(force);
  }


  // A method that calculates and applies a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  void seek(PVector target) {
    PVector desired = PVector.sub(target, position);  // A vector pointing from the position to the target

    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(maxspeed);
    // Steering = Desired minus Velocity
    PVector steer = PVector.sub(desired, velocity);
    steer.limit(maxforce);  // Limit to maximum steering force

    applyForce(steer);
  }

  void display() {
    // Draw a triangle rotated in the direction of velocity
    float theta = velocity.heading2D() + radians(90);
    fill(127);
    stroke(0);
    pushMatrix();
    //translate(position.x, position.y);
    rotate(theta);
    beginShape(TRIANGLES);
    vertex(0, -r*2);
    vertex(-r, r*2);
    vertex(r, r*2);
    endShape();
    popMatrix();
  }

  // Wraparound
  void borders() {
    if (position.x < -r) position.x = width+r;
    if (position.y < -r) position.y = height+r;
    if (position.x > width+r) position.x = -r;
    if (position.y > height+r) position.y = -r;
  }
}


1 Like

Just from glancing over the part of the code you indicate: Why are atx/aty stored globally outside the class? Why are they ints, not floats?

@jeremydouglass

I stored atX and atY inside the class, and that allowed me to make two wanderers moving independently. Now I am attempting to create several agents with an Arraylist instead of declaring each one separately, but haven’t figured out how to have each one adapt to the z hight value. I will be working on that.

As for why are those variables ints and not floats; it is because their coordinates are evaluated by for loops (two nested for loops) which, as far as I know, only may employ ints.

you should consider making them float. Your for loop argument against is not valid I think.

In the line I quoted you even use int() - unnecessary when having atx and atz as floats

I hope this ain‘t part of your current sketch version anymore