Provided framework, no changes

This commit is contained in:
JH159753 2022-09-17 02:45:09 -07:00
parent 8b1db96d91
commit 580b49ae55
9 changed files with 795 additions and 0 deletions

97
Boid.pde Normal file
View File

@ -0,0 +1,97 @@
/// In this file, you will have to implement seek and waypoint-following
/// The relevant locations are marked with "TODO"
class Crumb
{
PVector position;
Crumb(PVector position)
{
this.position = position;
}
void draw()
{
fill(255);
noStroke();
circle(this.position.x, this.position.y, CRUMB_SIZE);
}
}
class Boid
{
Crumb[] crumbs = {};
int last_crumb;
float acceleration;
float rotational_acceleration;
KinematicMovement kinematic;
PVector target;
Boid(PVector position, float heading, float max_speed, float max_rotational_speed, float acceleration, float rotational_acceleration)
{
this.kinematic = new KinematicMovement(position, heading, max_speed, max_rotational_speed);
this.last_crumb = millis();
this.acceleration = acceleration;
this.rotational_acceleration = rotational_acceleration;
}
void update(float dt)
{
if (target != null)
{
// TODO: Implement seek here
}
// place crumbs, do not change
if (LEAVE_CRUMBS && (millis() - this.last_crumb > CRUMB_INTERVAL))
{
this.last_crumb = millis();
this.crumbs = (Crumb[])append(this.crumbs, new Crumb(this.kinematic.position));
if (this.crumbs.length > MAX_CRUMBS)
this.crumbs = (Crumb[])subset(this.crumbs, 1);
}
// do not change
this.kinematic.update(dt);
draw();
}
void draw()
{
for (Crumb c : this.crumbs)
{
c.draw();
}
fill(255);
noStroke();
float x = kinematic.position.x;
float y = kinematic.position.y;
float r = kinematic.heading;
circle(x, y, BOID_SIZE);
// front
float xp = x + BOID_SIZE*cos(r);
float yp = y + BOID_SIZE*sin(r);
// left
float x1p = x - (BOID_SIZE/2)*sin(r);
float y1p = y + (BOID_SIZE/2)*cos(r);
// right
float x2p = x + (BOID_SIZE/2)*sin(r);
float y2p = y - (BOID_SIZE/2)*cos(r);
triangle(xp, yp, x1p, y1p, x2p, y2p);
}
void seek(PVector target)
{
this.target = target;
}
void follow(ArrayList<PVector> waypoints)
{
// TODO: change to follow *all* waypoints
this.target = waypoints.get(0);
}
}

58
CustomMaps.pde Normal file
View File

@ -0,0 +1,58 @@
// These are custom maps (for part b), defined as lists of coordinates for the outline
// You can add additional maps as you see fit
PVector[] customMap(int nr)
{
if (nr == 1)
{
return new PVector[] {new PVector(0, 1), new PVector(0.8, 1), new PVector(0.55, 0.7), new PVector(0.75, 0.1),
new PVector(1, 0.9), new PVector(1, 0), new PVector(0.25, 0), new PVector(0.25, 0.15), new PVector(0, 0.15)};
}
else if (nr == 2)
{
return new PVector[] {new PVector(0, 1), new PVector(0.25, 1), new PVector(0.4, 0.75), new PVector(0.45, 0.5),
new PVector(0.1, 0.5), new PVector(0.12, 0.35), new PVector(0.25, 0.35), new PVector(0.5, 0.4),
new PVector(0.5, 0.65), new PVector(0.6, 0.65), new PVector(0.6, 0.8), new PVector(0.5, 0.82),
new PVector(0.3, 1), new PVector(1,1), new PVector(1,0), new PVector(0.57, 0), new PVector(0.6, 0.35),
new PVector(0.7, 0.35), new PVector(0.67, 0.1), new PVector(0.85, 0.1), new PVector(0.82,0.15),
new PVector(0.75, 0.2), new PVector(0.75, 0.75), new PVector(0.85, 0.92), new PVector(0.62, 0.9),
new PVector(0.7, 0.8), new PVector(0.7, 0.5), new PVector(0.58, 0.45), new PVector(0.45,0),
new PVector(0.25, 0), new PVector(0.25, 0.15), new PVector(0, 0.15)};
}
else if (nr == 3)
{
return new PVector[] {new PVector(0, 1), new PVector(0.45, 1), new PVector(0.45, 0.6), new PVector(0.1, 0.6),
new PVector(0.2, 0.23), new PVector(0.35, 0.25), new PVector(0.35, 0.35), new PVector(0.25, 0.35),
new PVector(0.25, 0.3), new PVector(0.2, 0.3), new PVector(0.2, 0.55), new PVector(0.3, 0.55),
new PVector(0.3, 0.46), new PVector(0.4, 0.45), new PVector(0.4, 0.55), new PVector(0.6, 0.62),
new PVector(0.6, 0.85), new PVector(0.5, 0.85), new PVector(0.5, 1), new PVector(1,1),
new PVector(1,0.9), new PVector(0.7, 0.9), new PVector(0.7, 0.62),
new PVector(0.8, 0.75), new PVector(0.8, 0.85), new PVector(1, 0.85),
new PVector(1,0), new PVector(0.9, 0), new PVector(0.93, 0.13), new PVector(0.87, 0.1),
new PVector(0.85, 0.05), new PVector(0.75, 0.05), new PVector(0.75, 0.15), new PVector(0.8, 0.15),
new PVector(0.8, 0.25), new PVector(0.95,0.25), new PVector(0.92, 0.55), new PVector(0.75, 0.55),
new PVector(0.75, 0.45), new PVector(0.85, 0.40), new PVector(0.8, 0.5), new PVector(0.9, 0.5),
new PVector(0.9, 0.35), new PVector(0.67, 0.35), new PVector(0.67, 0.2), new PVector(0.5, 0.2),
new PVector(0.5, 0.35), new PVector(0.65, 0.45), new PVector(0.45, 0.47),
new PVector(0.45, 0.1), new PVector(0.7, 0.15), new PVector(0.7, 0), new PVector(0.25, 0),
new PVector(0.25, 0.15), new PVector(0, 0.15)
};
}
else if (nr == 4)
{
return new PVector[] {new PVector(0, 1), new PVector(0.85, 1), new PVector(0.87, 0.15), new PVector(0.45, 0.15), new PVector(0.35, 0.3),
new PVector(0.15, 0.3), new PVector(0.15, 0.55), new PVector(0.5, 0.55), new PVector(0.57, 0.42), new PVector(0.45, 0.75), new PVector(0.6, 0.8),
new PVector(0.6, 0.6), new PVector(0.65, 0.35), new PVector(0.55, 0.35), new PVector(0.47, 0.45), new PVector(0.3, 0.45),
new PVector(0.32, 0.37), new PVector(0.45, 0.4), new PVector(0.5, 0.27), new PVector(0.7, 0.3), new PVector(0.7, 0.85),
new PVector(0.4, 0.8), new PVector(0.4, 0.65), new PVector(0.13, 0.65), new PVector(0.1, 0.27), new PVector(0.35, 0.2),
new PVector(0.45, 0.12), new PVector(0.9, 0.1), new PVector(0.9, 1), new PVector(1,1), new PVector(1,0), new PVector(0.25, 0),
new PVector(0.25, 0.15), new PVector(0, 0.15)
};
}
else /// you can use nr==5, nr==6, ... nr==9 to add your own custom maps
{
return new PVector[] {new PVector(0, 1), new PVector(0.8, 1), new PVector(0.55, 0.7), new PVector(0.75, 0.1),
new PVector(1, 0.9), new PVector(1, 0), new PVector(0.25, 0), new PVector(0.25, 0.15), new PVector(0, 0.15)};
}
}

10
Flocking.pde Normal file
View File

@ -0,0 +1,10 @@
/// called when "f" is pressed; should instantiate additional boids and start flocking
void flock()
{
}
/// called when "f" is pressed again; should remove the flock
void unflock()
{
}

67
KinematicMovement.pde Normal file
View File

@ -0,0 +1,67 @@
/// Do not change this file!
/// The only methods you can call are increaseSpeed, getPosition, getHeading, getSpeed and getRotationalVelocity
class KinematicMovement
{
// position
private PVector position;
private float heading;
private float speed;
private float rotational_velocity;
float max_speed;
float max_rotational_speed;
KinematicMovement(PVector position, float heading, float max_speed, float max_rotational_speed)
{
this.position = position;
this.heading = heading;
this.speed = 0;
this.rotational_velocity = 0;
this.max_speed = max_speed;
this.max_rotational_speed = max_rotational_speed;
}
void update(float dt)
{
PVector velocity = new PVector(cos(this.heading), sin(this.heading)).mult(speed);
PVector destination = PVector.add(this.position, PVector.mult(velocity, dt));
// check for map collisions; only move if no collisions
if (!map.collides(this.position, destination))
this.position = destination;
this.heading += this.rotational_velocity*dt;
this.heading = normalize_angle(this.heading);
}
private void setSpeed(float s, float rs)
{
this.speed = constrain(s, -max_speed, max_speed);
this.rotational_velocity = constrain(rs, -this.max_rotational_speed, this.max_rotational_speed);
}
// These are the public methods
void increaseSpeed(float ds, float drs)
{
setSpeed(this.speed + ds, this.rotational_velocity + drs);
}
PVector getPosition()
{
return position;
}
float getHeading()
{
return heading;
}
float getSpeed()
{
return speed;
}
float getRotationalVelocity()
{
return rotational_velocity;
}
// End public methods
}

286
Map.pde Normal file
View File

@ -0,0 +1,286 @@
/// You do not have to change this file, but you can, if you want to add a more sophisticated generator.
class Wall
{
PVector start;
PVector end;
PVector normal;
PVector direction;
float len;
Wall(PVector start, PVector end)
{
this.start = start;
this.end = end;
direction = PVector.sub(this.end, this.start);
len = direction.mag();
direction.normalize();
normal = new PVector(-direction.y, direction.x);
}
boolean crosses(PVector from, PVector to)
{
// Vector pointing from `this.start` to `from`
PVector d1 = PVector.sub(from, this.start);
// Vector pointing from `this.start` to `to`
PVector d2 = PVector.sub(to, this.start);
// If both vectors are on the same side of the wall
// their dot products with the normal will have the same sign
// If they are both positive, or both negative, their product will
// be positive.
float dist1 = normal.dot(d1);
float dist2 = normal.dot(d2);
if (dist1 * dist2 > 0) return false;
// if the start and end are on different sides, we need to determine
// how far the intersection point is along the wall
// first we determine how far the projections of from and to are
// along the wall
float ldist1 = direction.dot(d1);
float ldist2 = direction.dot(d2);
// the distance of the intersection point from the start
// is proportional to the normal distance of `from` in
// along the total movement
float t = dist1/(dist1 - dist2);
// calculate the intersection as this proportion
float intersection = ldist1 + t*(ldist2 - ldist1);
if (intersection < 0 || intersection > len) return false;
return true;
}
// Return the mid-point of this wall
PVector center()
{
return PVector.mult(PVector.add(start, end), 0.5);
}
void draw()
{
strokeWeight(3);
line(start.x, start.y, end.x, end.y);
if (SHOW_WALL_DIRECTION)
{
PVector marker = PVector.add(PVector.mult(start, 0.2), PVector.mult(end, 0.8));
circle(marker.x, marker.y, 5);
}
}
}
void AddPolygon(ArrayList<Wall> walls, PVector[] nodes)
{
for (int i = 0; i < nodes.length; ++ i)
{
int next = (i+1)%nodes.length;
walls.add(new Wall(nodes[i], nodes[next]));
}
}
void AddPolygonScaled(ArrayList<Wall> walls, PVector[] nodes)
{
for (int i = 0; i < nodes.length; ++ i)
{
int next = (i+1)%nodes.length;
walls.add(new Wall(new PVector(nodes[i].x*width, nodes[i].y*height), new PVector(nodes[next].x*width, nodes[next].y*height)));
}
}
class Obstacle
{
ArrayList<Wall> walls;
Obstacle()
{
walls = new ArrayList<Wall>();
PVector origin = new PVector(width*0.1 + random(width*0.65), height*0.12 + random(height*0.65));
if (origin.x < 100 && origin.y > 500) origin.add(new PVector(150,0));
PVector[] nodes = new PVector[] {};
float angle = random(100)/100.0;
for (int i = 0; i < 3 + random(2) && angle < TAU; ++i)
{
float distance = height*0.05 + random(height*0.15);
nodes = (PVector[])append(nodes, PVector.add(origin, new PVector(cos(-angle)*distance, sin(-angle)*distance)));
angle += 1 + random(25)/50;
}
AddPolygon(walls, nodes);
}
}
// Given a (closed!) polygon surrounded by walls, tests if the
// given point is inside that polygon.
// Note that this only works for polygons that are inside the
// visible screen (or not too far outside)
boolean isPointInPolygon(PVector point, ArrayList<Wall> walls)
{
// we create a test point "far away" horizontally
PVector testpoint = PVector.add(point, new PVector(width*2, 0));
// Then we count how often the line from the given point
// to our test point intersects the polygon outline
int count = 0;
for (Wall w: walls)
{
if (w.crosses(point, testpoint))
count += 1;
}
// If we cross an odd number of times, we started inside
// otherwise we started outside the polygon
// Intersections alternate between enter and exit,
// so if we "know" that the testpoint is outside
// and odd number means we exited one more time
// than we entered.
return (count%2) == 1;
}
class Map
{
ArrayList<Wall> walls;
ArrayList<Obstacle> obstacles;
ArrayList<Wall> outline;
ArrayList<PVector> pts;
Map()
{
walls = new ArrayList<Wall>();
outline = new ArrayList<Wall>();
obstacles = new ArrayList<Obstacle>();
}
boolean collides(PVector from, PVector to)
{
for (Wall w : walls)
{
if (w.crosses(from, to)) return true;
}
return false;
}
void doSplit(boolean xdir, float from, float to, float other, float otherend, ArrayList<PVector> points, int level)
{
float range = abs(to-from);
float sign = -1;
if (to > from) sign = 1;
if (range < 70) return;
if (level > 1 && random(0,1) < 0.05*level) return;
float split = from + sign*random(range*0.35, range*0.45);
float splitend = split + sign*random(20, range*0.35-10);
if (xdir)
points.add(new PVector(split, other));
else
points.add(new PVector(other, split));
float othersign = 1;
if (otherend < other) othersign = -1;
float otherrange = abs(other-otherend);
float spikeend = other + othersign*random(otherrange*0.4, otherrange*(0.9 - 0.1*level));
doSplit(!xdir, other, spikeend, split, from, points, level+1);
if (xdir)
{
points.add(new PVector(split, spikeend));
points.add(new PVector(splitend, spikeend));
}
else
{
points.add(new PVector(spikeend, split));
points.add(new PVector(spikeend, splitend));
}
doSplit(!xdir, spikeend, other, splitend, to, points, level + 1);
if (xdir)
points.add(new PVector(splitend, other));
else
points.add(new PVector(other, splitend));
}
void randomMap()
{
ArrayList<PVector> points = new ArrayList<PVector>();
points.add(new PVector(0, height));
doSplit(true, 50, width, height, 0, points, 0);
points.add(new PVector(width, height));
points.add(new PVector(width, 0));
points.add(new PVector(200, 0));
points.add(new PVector(200, 80));
points.add(new PVector(0, 80));
//pts = points;
AddPolygon(outline, points.toArray(new PVector[]{}));
}
void generate(int which)
{
outline.clear();
obstacles.clear();
walls.clear();
if (which < 0)
{
randomMap();
}
else if (which == 0)
{
AddPolygon(outline, new PVector[] {new PVector(-100, height+100), new PVector(width+100, height+100), new PVector(width+100, -100), new PVector(-100,-100)});
}
else
{
AddPolygonScaled(outline, customMap(which));
}
walls.addAll(outline);
for (int i = 0; i < random(MAX_OBSTACLES); ++i)
{
Obstacle obst = new Obstacle();
boolean ok = true;
// only obstacle if it doesn't intersect with any existing one (or the exterior)
for (Wall w : obst.walls)
{
if (collides(w.start, w.end)) ok = false;
if (!isReachable(w.start)) ok = false;
}
if (ok)
{
obstacles.add(obst);
walls.addAll(obst.walls);
}
}
}
void update(float dt)
{
draw();
}
void draw()
{
stroke(255);
strokeWeight(3);
for (Wall w : walls)
{
w.draw();
}
if (pts != null)
{
PVector current = new PVector(width/2, height/2);
for (PVector p : pts)
{
fill(255,0,0);
circle(p.x, p.y, 4);
line(current.x, current.y, p.x, p.y);
current = p;
}
}
}
boolean isReachable(PVector point)
{
if (!isPointInPolygon(point, outline)) return false;
for (Obstacle o: obstacles)
{
if (isPointInPolygon(point, o.walls)) return false;
}
return true;
}
}

43
NavMesh.pde Normal file
View File

@ -0,0 +1,43 @@
// Useful to sort lists by a custom key
import java.util.Comparator;
/// In this file you will implement your navmesh and pathfinding.
/// This node representation is just a suggestion
class Node
{
int id;
ArrayList<Wall> polygon;
PVector center;
ArrayList<Node> neighbors;
ArrayList<Wall> connections;
}
class NavMesh
{
void bake(Map map)
{
/// generate the graph you need for pathfinding
}
ArrayList<PVector> findPath(PVector start, PVector destination)
{
/// implement A* to find a path
ArrayList<PVector> result = null;
return result;
}
void update(float dt)
{
draw();
}
void draw()
{
/// use this to draw the nav mesh graph
}
}

182
lab1.pde Normal file
View File

@ -0,0 +1,182 @@
/// You do not need to change anything in this file, but you can
/// For example, if you want to add additional options controllable by keys
/// keyPressed would be the place for that.
ArrayList<PVector> waypoints = new ArrayList<PVector>();
Boid billy;
int lastt;
int mapnr = 0;
Map map = new Map();
NavMesh nm = new NavMesh();
boolean entering_path = false;
boolean show_nav_mesh = false;
boolean show_waypoints = false;
boolean show_help = false;
boolean flocking_enabled = false;
void setup() {
size(800, 600);
billy = new Boid(BILLY_START, BILLY_START_HEADING, BILLY_MAX_SPEED, BILLY_MAX_ROTATIONAL_SPEED, BILLY_MAX_ACCELERATION, BILLY_MAX_ROTATIONAL_ACCELERATION);
randomSeed(0);
map.generate(mapnr);
nm.bake(map);
}
void mousePressed() {
if (show_help) return;
PVector target = new PVector(mouseX, mouseY);
if (!map.isReachable(target)) return;
if (mouseButton == LEFT)
{
if (waypoints.size() == 0)
{
billy.seek(target);
}
else
{
waypoints.add(target);
entering_path = false;
billy.follow(waypoints);
}
}
else if (mouseButton == RIGHT)
{
if (!entering_path)
waypoints = new ArrayList<PVector>();
waypoints.add(target);
entering_path = true;
}
}
void keyPressed()
{
if (show_help)
{
show_help = false;
return;
}
if (key == 'h')
{
show_help = true;
}
if (show_help) return;
if (key == 'g')
{
map.generate(-1);
mapnr = -1;
nm.bake(map);
}
else if (key == 'n')
{
show_nav_mesh = !show_nav_mesh;
}
else if (key == 'w')
{
show_waypoints = !show_waypoints;
}
else if ((key >= '1' && key <= '9'))
{
mapnr = key-'1' + 1;
map.generate(mapnr);
nm.bake(map);
}
else if (key == '0')
{
mapnr = 0;
map.generate(0);
nm.bake(map);
}
else if (key == 'f')
{
flocking_enabled = !flocking_enabled;
if (flocking_enabled)
{
flock();
}
else
{
unflock();
}
}
}
void show_status(boolean active, String show, int x)
{
fill(255,255,255);
if (active)
fill(255,0,0);
text(show, x, 40);
}
void draw() {
background(0);
if (entering_path || show_waypoints)
{
stroke(255,0,0);
strokeWeight(1);
PVector current = billy.kinematic.position;
if (show_waypoints && billy.target != null)
{
line(current.x, current.y, billy.target.x, billy.target.y);
current = billy.target;
}
for (PVector wp : waypoints)
{
line(current.x, current.y, wp.x, wp.y);
current = wp;
}
if (entering_path)
line(current.x, current.y, mouseX, mouseY);
}
float dt = (millis() - lastt)/1000.0;
lastt = millis();
billy.update(dt);
map.update(dt);
if (show_nav_mesh)
nm.update(dt);
textSize(12);
show_status(show_nav_mesh, "N", 30);
show_status(show_waypoints, "W", 50);
show_status(show_help, "H", 70);
show_status(flocking_enabled, "F", 90);
if (mapnr < 0)
show_status(false, "R", 110);
else
show_status(false, String.format("%d", mapnr), 110);
if (show_help)
{
fill(255);
stroke(0,0,255);
rect(width*0.25, height*0.25, width*0.5, height*0.5);
fill(0);
textSize(32);
text("HELP", width*0.5-30, height*0.25 + 40);
textSize(18);
text("0,1,2,3,4 - Show custom map 0,1,2,3,4", width*0.25+40, height*0.25 + 70);
text("G - Generate random map", width*0.25+40, height*0.25 + 90);
text("N - Show NavMesh", width*0.25+40, height*0.25 + 110);
text("W - Show waypoints while moving", width*0.25+40, height*0.25 + 130);
text("F - Enable/disable flocking", width*0.25+40, height*0.25 + 150);
text("H - This screen", width*0.25+40, height*0.25 + 170);
text("Press any key to close", width*0.5 - 80, height*0.75 - 80);
textSize(12);
}
}

37
settings.pde Normal file
View File

@ -0,0 +1,37 @@
// Currently not used, but you may want to use it to enable/disable
// draw-calls or debug output as needed
boolean DEBUG = false;
// The radius of the circle representing the boid body
int BOID_SIZE = 20;
// Where does billy start?
PVector BILLY_START = new PVector(50,500);
float BILLY_START_HEADING = 0;
// How fast can billy go and turn?
float BILLY_MAX_SPEED = 80;
float BILLY_MAX_ROTATIONAL_SPEED = 3;
float BILLY_MAX_ACCELERATION = 1;
float BILLY_MAX_ROTATIONAL_ACCELERATION = 1;
// Should boids leave breadcrumbs behind?
boolean LEAVE_CRUMBS = true;
// How many crumbs?
int MAX_CRUMBS = 1000;
// Time between crumbs
int CRUMB_INTERVAL = 200;
// How big are the crumbs?
int CRUMB_SIZE = 2;
// use for debugging, if you want to see where walls start/end (a circle is drawn closer to the end)
boolean SHOW_WALL_DIRECTION = false;
// How many obstacles should be generated *at most*
// Note that maps 2-4 are pretty dense and obstacles
// are only placed if they won't intersect with the map
int MAX_OBSTACLES = 0;

15
util.pde Normal file
View File

@ -0,0 +1,15 @@
// Normalize an angle to be between 0 and TAU (= 2 PI)
float normalize_angle(float angle)
{
while (angle < 0) angle += TAU;
while (angle > TAU) angle -= TAU;
return angle;
}
// Normalize an angle to be between -PI and PI
float normalize_angle_left_right(float angle)
{
while (angle < -PI) angle += TAU;
while (angle > PI) angle -= TAU;
return angle;
}