diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..2a6f40e --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/.project b/.project new file mode 100644 index 0000000..0080706 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Octree + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..021167a --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=9 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=9 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=9 diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..2e73a17 --- /dev/null +++ b/src/Main.java @@ -0,0 +1,126 @@ + +import java.util.ArrayList; +import java.util.Iterator; + +import Octree.Octree; +import Octree.OctreeNode; +import Octree.OctreeObject; +import boid.Boid; +import core.Boundary; +import processing.core.PApplet; +import processing.event.KeyEvent; +import processing.event.MouseEvent; + +public class Main extends PApplet { + + public static void main(String[] args) { + PApplet.main("Main"); + } + + Octree world = new Octree(1800, 900, 3, 5); + ArrayList walls = new ArrayList<>(); + + int boids = 60; + + public void settings() { + size(world.getWidth(), world.getHeight()); + } + + public void setup() { + + for (int i = 0; i < boids; i++) { + world.add(new Boid(200, 200, world, this)); + } + + walls.add(new Boundary(0, 0, world.getWidth(), 0)); + walls.add(new Boundary(0, 0, 0, world.getHeight())); + walls.add(new Boundary(world.getWidth(), 0, world.getWidth(), world.getHeight())); + walls.add(new Boundary(0, world.getHeight(), world.getWidth(), world.getHeight())); + + } + + public void draw() { + + if (paused) + return; + + clear(); + background(255); + + ArrayList nodes = new ArrayList<>(); + nodes.add(world); + nodes.addAll(world.getAllChildren()); + + for (OctreeNode node : nodes) { + int x = node.getX(); + int y = node.getY(); + int width = node.getWidth(); + int height = node.getHeight(); + + line(x, y, x + width, y); + line(x + width, y, x + width, y + height); + line(x + width, y + height, x, y + height); + line(x, y + height, x, y); + + } + + for (Boundary wall : walls) { + line((int) wall.a.x, (int) wall.a.y, (int) wall.b.x, (int) wall.b.y); + } + + ArrayList objects = world.getAllObjects(); + + for (OctreeObject object : objects) { + Boid boid = ((Boid) object); + + boid.update(); + + line((int) boid.getX(), (int) boid.getY(), (int) (boid.getX() + boid.velocity.x * 10), + (int) (boid.getY() + boid.velocity.y * 10)); + + + + fill(((Boid)object).color); + circle((int) object.getX(), (int) object.getY(), boidSize); + + } + + } + + boolean paused = false; + + @Override + public void keyReleased(KeyEvent event) { + switch (event.getKeyCode()) { + case 32: + paused = !paused; + break; + } + ; + } + + Boid selectedBoid; + + int boidSize = 10; + + @Override + public void mouseReleased(MouseEvent event) { + int mouseX = event.getX(); + int mouseY = event.getY(); + + System.out.println(mouseX + " " + mouseY); + for (OctreeObject obj : world.getAllObjects()) { + Boid boid = (Boid) obj; + + double x = boid.getX(), y = boid.getY(); + + line((int) x, (int) y, (int) (x + boidSize), (int) (y + boidSize)); + if (mouseX >= x && mouseX < x + boidSize && mouseY >= y && mouseY < y + boidSize) + boid.drawDebug = true; + else + boid.drawDebug = false; + + } + + } +} diff --git a/src/Octree/Octree.java b/src/Octree/Octree.java new file mode 100644 index 0000000..f102255 --- /dev/null +++ b/src/Octree/Octree.java @@ -0,0 +1,25 @@ +package Octree; + +public class Octree extends OctreeNode { + + public int maxNodeSize, maxNodes, minWidth, minHeight; + + public Octree(int width, int height, int maxNodeSize, int maxNodes, int minWidth, int minHeight) { + super(0, 0, width, height, null, null); + this.octree = this; + this.maxNodeSize = maxNodeSize; + this.maxNodes = maxNodes; + this.minWidth = minWidth; + this.minHeight = minHeight; + } + + public Octree(int width, int height, int maxNodeSize, int maxNodes) { + super(0, 0, width, height, null, null); + this.octree = this; + this.maxNodeSize = maxNodeSize; + this.maxNodes = maxNodes; + this.minWidth = 10; + this.minHeight = 10; + } + +} diff --git a/src/Octree/OctreeNode.java b/src/Octree/OctreeNode.java new file mode 100644 index 0000000..ce059d7 --- /dev/null +++ b/src/Octree/OctreeNode.java @@ -0,0 +1,188 @@ +package Octree; + +import java.util.ArrayList; + +public class OctreeNode { + + private int x, y, width, height, distanceToTop = 0; + + Octree octree; + + private OctreeNode parent; + + public OctreeNode(int x, int y, int width, int height, Octree octree, OctreeNode parent) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.octree = octree; + this.parent = parent; + + if (parent != null) // means its the octree + distanceToTop++; + + } + + private ArrayList objects = new ArrayList<>(); + + private ArrayList children = new ArrayList<>(); + + public void add(OctreeObject obj) { + octree.addFull(obj); + } + + void addFull(OctreeObject obj) { + obj.setOctree(octree); + + OctreeNode node = getQuadrant(obj); + + //means no children (this node) + if (node == null) { + objects.add(obj); + obj.setOctreeNode(this); + obj.setOctreeParentNode(parent); + } else { + node.addFull(obj); + } + + if (!hasChildren() && objects.size() >= octree.maxNodeSize && distanceToTop < octree.maxNodes + && this.width > octree.minWidth && this.height > octree.minHeight) { + children = newNodes(x, y, width, height, octree, this); + + for (OctreeObject object : objects) { + OctreeNode temp = getQuadrant(object); + temp.addFull(object); + } + + objects.clear(); + + } + + } + + public void remove(OctreeObject object) { + if (hasChildren()) + for (OctreeNode octreeNode : children) { + octreeNode.remove(object); + } + else + objects.remove(object); + + if (hasChildren() && objects.size() < octree.maxNodeSize) { + + objects.addAll(getAllObjects()); + + children.clear(); + } + } + + private OctreeNode getQuadrant(OctreeObject object) { + + if (!hasChildren()) + return null; + + for (OctreeNode octreeNode : children) { + + if (object.getX() >= octreeNode.getX() && object.getY() >= octreeNode.getY() + && object.getX() <= octreeNode.getX() + octreeNode.getWidth() + && object.getY() <= octreeNode.getY() + octreeNode.getHeight()) + return octreeNode; + } + + // maybe problematisch + // if (parent != null) + // return parent.getQuadrant(object); + + return children.get(0); + } + + + + public boolean hasChildren() { + return !children.isEmpty(); + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public Octree getOctree() { + return octree; + } + + public OctreeNode getParent() { + return parent; + } + + public ArrayList getObjects() { + return (ArrayList) objects.clone(); + } + + public ArrayList getAllObjects() { + + ArrayList c = getObjects(); + + for (OctreeNode octreeNode : getChildren()) { + c.addAll(octreeNode.getAllObjects()); + } + + return c; + } + + public ArrayList getChildren() { + return (ArrayList) children.clone(); + } + + public ArrayList getAllChildren() { + + ArrayList c = getChildren(); + + for (OctreeNode octreeNode : getChildren()) { + c.addAll(octreeNode.getAllChildren()); + } + + return c; + } + + public int size() { + return objects.size(); + } + + private static ArrayList newNodes(int parenX, int parentY, int parentWidth, int parentHeight, + Octree octree, OctreeNode parent) { + ArrayList nodes = new ArrayList<>(); + + int width = parentWidth / 2; + int height = parentHeight / 2; + + nodes.add(new OctreeNode(parenX, parentY, width, height, octree, parent)); + nodes.add(new OctreeNode(parenX + width, parentY, width, height, octree, parent)); + nodes.add(new OctreeNode(parenX, parentY + height, width, height, octree, parent)); + nodes.add(new OctreeNode(parenX + width, parentY + height, width, height, octree, parent)); + + return nodes; + + } + + @Override + public String toString() { + String s = this.x + " " + this.y + " " + this.width + " " + this.height + " [ allChildren:" + + getAllChildren().size() + ", objects:" + getObjects().size() + ", allObjects:" + + getAllObjects().size() + " ]"; + + return s; + } + +} diff --git a/src/Octree/OctreeObject.java b/src/Octree/OctreeObject.java new file mode 100644 index 0000000..dc68d8f --- /dev/null +++ b/src/Octree/OctreeObject.java @@ -0,0 +1,85 @@ +package Octree; + +public class OctreeObject { + + private double x = 0, y = 0; + private OctreeNode octreeNode, OctreeParentNode; + private Octree octree; + + + + public double getX() { + return x; + }; + + public double getY() { + return y; + }; + + public void setPosition(int x, int y) { + setX(x); + setY(y); + }; + + public void setX(double x) { + if (octree != null) + this.x = (x <= 0) ? 0 : ((x > octree.getWidth()) ? octree.getWidth() : x); + else + this.x = x; + updateTree(); + } + + public void setY(double y) { + if (octree != null) + this.y = (y <= 0) ? 0 : ((y > octree.getHeight()) ? octree.getHeight() : y); + else + this.y = y; + updateTree(); + } + + public void setPosition(double x, double y) { + setX(x); + setY(y); + }; + + public void setX(int x) { + setX((double) x); + } + + public void setY(int y) { + setY((double) y); + } + + public OctreeNode getOctreeNode() { + return octreeNode; + } + + public OctreeNode getOctreeParentNode() { + return OctreeParentNode; + } + + void setOctreeNode(OctreeNode octreeNode) { + this.octreeNode = octreeNode; + } + + void setOctreeParentNode(OctreeNode octreeParentNode) { + OctreeParentNode = octreeParentNode; + } + + void setOctree(Octree octree) { + this.octree = octree; + } + + private void updateTree() { + if (octree == null) + return; + octree.remove(this); + octree.add(this); + } + + @Override + public String toString() { + return getX()+ " "+ getY(); + } + +} diff --git a/src/boid/Boid.java b/src/boid/Boid.java new file mode 100644 index 0000000..9a0ed43 --- /dev/null +++ b/src/boid/Boid.java @@ -0,0 +1,215 @@ +package boid; + +import java.util.ArrayList; + +import Octree.Octree; +import Octree.OctreeObject; +import core.Boundary; +import core.Position; +import core.Vector; +import processing.core.PApplet; + +public class Boid extends OctreeObject { + + public Vector velocity = new Vector(1, 1); + public Vector direction = new Vector(3, 0); + + public static int COLOR_RED = 16711680; + public static int COLOR_GREEN = 65280; + public static int COLOR_BLUE = 255; + + + + private PApplet p5; + + public Boid(int x, int y, Octree world, PApplet p5) { + setPosition(x, y); + this.world = world; + this.p5 = p5; + } + + public Octree world; + + public int color = 16777215; + + public boolean drawDebug = false; + + public static double edgeValue = 100; + + public static double turnFactor = 0.3; + public static double avoidFactor = 0.05; + public static double matchingFactor = 0.05; + public static double centeringFactor = 0.00005; + public static double minSpeed = 3, maxSpeed = 8; + public static double protectedRange = 8; + public static double visibleRange = 40; + + public static double randomMovementAmount = 0.01; + + public void update() { + + double boidX = getX(), boidY = getY(); + + updateBoidBehavoir(boidX, boidY); + + randomMovement(); + min_maxSpeed(); + walls(boidX, boidY); + + setX(getX() + velocity.x); + setY(getY() + velocity.y); + + if(drawDebug) { + p5.noFill(); + p5.circle((int) getX(), (int) getY(), (int) protectedRange); + p5.circle((int) getX(), (int) getY(), (int) visibleRange); + p5.fill(255); + } + + } + + private void min_maxSpeed() { + double speed = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y); + + if (speed > maxSpeed) { + velocity.x = (velocity.x / speed) * maxSpeed; + velocity.y = (velocity.y / speed) * maxSpeed; + } + if (speed < minSpeed) { + velocity.x = (velocity.x / speed) * minSpeed; + velocity.y = (velocity.y / speed) * minSpeed; + } + } + + private void randomMovement() { + double rand = Math.random(); + if (rand > 0.5) + velocity.x = velocity.x + randomMovementAmount * rand; + else + velocity.y = velocity.y + randomMovementAmount * rand; + + } + + private void updateBoidBehavoir(double boidX, double boidY) { + xvel_avg = 0; + yvel_avg = 0; + xpos_avg = 0; + ypos_avg = 0; + + neighboring_boids = 0; + otherBoid_dx = 0; + otherBoid_dy = 0; + + for (OctreeObject ob : world.getAllObjects()) { + Boid otherBoid = (Boid) ob; + + double dist = new Vector(new Position(boidX, boidY), new Position(otherBoid.getX(), otherBoid.getY())) + .getLength(); + + if (dist < protectedRange) { + + otherBoid.color=COLOR_RED; + + // seperation + otherBoid_dx += boidX - otherBoid.getX(); + otherBoid_dy += boidY - otherBoid.getY(); + } + + if (dist < visibleRange && dist > protectedRange) { + + otherBoid.color=COLOR_GREEN; + + // cohesion + xpos_avg += boidX - otherBoid.getX(); + ypos_avg += boidY - otherBoid.getY(); + + // allignment + xvel_avg = xvel_avg + otherBoid.velocity.x; + yvel_avg = yvel_avg + otherBoid.velocity.y; + + neighboring_boids++; + + } + } + + + endBehavoirUpdate(); + + } + + private void endBehavoirUpdate() { + + if (neighboring_boids > 0) { + + // cohesion + xpos_avg = xpos_avg / neighboring_boids; + ypos_avg = ypos_avg / neighboring_boids; + + // alignment + xvel_avg = xvel_avg / neighboring_boids; + yvel_avg = yvel_avg / neighboring_boids; + } + + // cohesion + velocity.x += (xpos_avg - velocity.x) * centeringFactor; + velocity.y += (ypos_avg - velocity.y) * centeringFactor; + + // alignment + velocity.x += (xvel_avg - velocity.x) * matchingFactor; + velocity.y += (yvel_avg - velocity.y) * matchingFactor; + + // seperation + velocity.x = velocity.x + otherBoid_dx * avoidFactor; + velocity.y = velocity.y + otherBoid_dy * avoidFactor; + } + + double xvel_avg = 0, yvel_avg = 0, neighboring_boids = 0; + double otherBoid_dx = 0, otherBoid_dy = 0, xpos_avg = 0, ypos_avg = 0; + + private void walls(double boidX, double boidY) { + if (boidX < edgeValue) + velocity.x = velocity.x + turnFactor; + if (boidX > world.getWidth() - edgeValue) + velocity.x = velocity.x - turnFactor; + if (boidY < edgeValue) + velocity.y = velocity.y + turnFactor; + if (boidY > world.getHeight() - edgeValue) + velocity.y = velocity.y - turnFactor; + + } + + private double wallForceStart = 150, maxBoundaryForce = 1, wfs = maxBoundaryForce / wallForceStart; + + public Boundary hitBoundary(ArrayList walls) { + + Position position = new Position(getX(), getY()); + + Boundary hit = null; + + for (Boundary boundary : walls) { + + // double l = (y3 * x4+ x1* y4 - x3* y4 - y1*x4)/ ( y2 *x4 - x2* y4) + double l = (boundary.a.y * boundary.direction.x + position.x * boundary.direction.y + - boundary.a.x * boundary.direction.y - position.y * boundary.direction.x) + / (direction.y * boundary.direction.x - direction.x * boundary.direction.y); + + double dirLength = direction.getLength(); + + if (l < dirLength) + if (dirLength >= 0 && l >= 0 || dirLength <= 0 && l <= 0) { + + double sx = (position.x + l * direction.x - boundary.a.x) / boundary.direction.x; + double sy = (position.y + l * direction.y - boundary.a.y) / boundary.direction.y; + + if (sx >= 0 || sy >= 0) + if (sx <= boundary.direction.getLength() || sy <= boundary.direction.getLength()) { + dirLength = l; + hit = boundary; + } + } + } + + return hit; + } + +} diff --git a/src/core/Boundary.java b/src/core/Boundary.java new file mode 100644 index 0000000..c2911ba --- /dev/null +++ b/src/core/Boundary.java @@ -0,0 +1,26 @@ +package core; + +public class Boundary { + + public Position a, b; + public Vector direction; + + public Boundary(Position a, Position b) { + this.a = a; + this.b = b; + this.direction = new Vector(a, b); + } + + public Boundary(double x, double y, double x2, double y2) { + this.a = new Position(x, y); + this.b = new Position(x2, y2); + this.direction = new Vector(a, b); + } + + public Boundary(Position a, Vector direction) { + this.a = a; + this.b = direction.pointFrom(a); + this.direction = direction; + } + +} diff --git a/src/core/NormalizedVector.java b/src/core/NormalizedVector.java new file mode 100644 index 0000000..1fd3a30 --- /dev/null +++ b/src/core/NormalizedVector.java @@ -0,0 +1,125 @@ +package core; + +public class NormalizedVector { + + public double x, y; + public double lenght; + + public NormalizedVector(double x, double y) { + this.x = x; + this.y = y; + normalize(); + } + + public NormalizedVector(Position position) { + this.x = position.getX(); + this.y = position.getY(); + normalize(); + } + + public NormalizedVector(Position a, Position b) { + this.x = b.getX() - a.getX(); + this.y = b.getY() - a.getY(); + normalize(); + + } + + public NormalizedVector fromLength(double lenght) { + return new NormalizedVector(x * lenght, y * lenght); + } + + public Position pointFrom(Position a) { + return new Position(a.x + x * lenght, a.y + y * lenght); + } + + public NormalizedVector subtract(NormalizedVector b) { + return new NormalizedVector(getX() - b.getX(), getY() - b.getY()); + } + + public NormalizedVector add(NormalizedVector b) { + return new NormalizedVector(getX() + b.getX(), getY() + b.getY()); + } + + public NormalizedVector multiply(double b) { + return new NormalizedVector(getX() * b, getY() * b); + } + + public NormalizedVector divide(double b) { + return new NormalizedVector(getX() / b, getY() / b); + } + + public void subtractME(NormalizedVector b) { + x = getX() - b.getX(); + y = getY() - b.getY(); + normalize(); + } + + public void addME(NormalizedVector b) { + x = getX() + b.getX(); + y = getY() + b.getY(); + normalize(); + } + + public void multiplyME(double b) { + x = getX() * b; + y = getY() * b; + normalize(); + } + + public void divideME(double b) { + x = getX() / b; + y = getY() / b; + normalize(); + } + + public void normalize() { + + lenght = Math.abs(Math.sqrt(x * x + y * y)); + + if (lenght == 0) + return; + + x = x / lenght; + y = y / lenght; + + } + + public double getX() { + return (x * lenght); + } + + public void setX(double x) { + this.x = x; + y = y * lenght; + + normalize(); + } + + public double getY() { + return (y * lenght); + } + + public void setY(double y) { + this.y = y; + + x = x * lenght; + + normalize(); + } + + public void clear() { + this.x = 0; + this.y = 0; + this.lenght = 0; + } + + public NormalizedVector clone() { + return new NormalizedVector(x * lenght, y * lenght); + } + + @Override + public String toString() { + return "[" + this.x + "," + this.y + "->" + this.lenght + "]"; + } + +} diff --git a/src/core/Position.java b/src/core/Position.java new file mode 100644 index 0000000..79a0c63 --- /dev/null +++ b/src/core/Position.java @@ -0,0 +1,30 @@ +package core; + +public class Position { + + public Position(double x, double y) { + this.x = x; + this.y = y; + } + + public double x,y; + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + + +} diff --git a/src/core/Vector.java b/src/core/Vector.java new file mode 100644 index 0000000..5018abe --- /dev/null +++ b/src/core/Vector.java @@ -0,0 +1,90 @@ +package core; + +public class Vector { + + public double x, y; + + public Vector(double x, double y) { + this.x = x; + this.y = y; + } + + public Vector(Position position) { + this.x = position.x; + this.y = position.y; + } + + public Vector(Position a, Position b) { + this.x = b.x - a.x; + this.y = b.y - a.y; + } + + public Vector fromLength(double lenght) { + return new Vector(x, y); + } + + public Position pointFrom(Position a) { + return new Position(a.x + x, a.y + y); + } + + public Vector subtract(Vector b) { + return new Vector(x - b.x, y - b.y); + } + + public Vector add(Vector b) { + return new Vector(x + b.x, y + b.y); + } + + public Vector multiply(double b) { + return new Vector(x * b, y * b); + } + + public Vector divide(double b) { + return new Vector(x / b, y / b); + } + + public void subtractME(Vector b) { + x = x - b.x; + y = y - b.y; + } + + public void addME(Vector b) { + x = x + b.x; + y = y + b.y; + } + + public void multiplyME(double b) { + x = x * b; + y = y * b; + } + + public void divideME(double b) { + x = x / b; + y = y / b; + } + + public double getLength() { + return Math.abs(Math.sqrt(x * x + y * y)); + } + + + + public NormalizedVector normalize() { + return new NormalizedVector(x, y); + } + + public void clear() { + this.x = 0; + this.y = 0; + } + + public Vector clone() { + return new Vector(x, y); + } + + @Override + public String toString() { + return "[" + this.x + "," + this.y + "->" + getLength() + "]"; + } + +}