Created
February 18, 2016 05:43
-
-
Save Roland09/c101fc0b7bd11e14167d to your computer and use it in GitHub Desktop.
Prototype for moving agents along scanlines
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import java.util.ArrayList; | |
import java.util.List; | |
public class Algorithm { | |
public List<Line> createScanLines( Mover sprite) { | |
PVector location = sprite.getLocation(); | |
double angle = sprite.getViewAngleRad(); | |
double sweepAngle = sprite.getSweepAngleRad(); | |
double angleStart = angle - sweepAngle / 2; | |
double angleEnd = angle + sweepAngle / 2; | |
double angleStep = (angleEnd-angleStart) / Settings.get().getScanLineCount(); | |
return createScanLines( location, angleStart, angleEnd, angleStep); | |
} | |
public List<Line> createScanLines( PVector scanLineOrigin) { | |
double angleStart = 0; | |
double angleEnd = Math.PI * 2; | |
double angleStep = Math.PI / Settings.get().getScanLineCount(); | |
return createScanLines(scanLineOrigin, angleStart, angleEnd, angleStep); | |
} | |
/** | |
* Sweep around the given circle with the given distance and create the scan lines | |
* @param scanLineOrigin The origin of the scanline generation | |
* @return | |
*/ | |
public List<Line> createScanLines( PVector scanLineOrigin, double angleStart, double angleEnd, double angleStep) { | |
List<Line> scanLines; | |
scanLines = new ArrayList<>(); | |
double scanLineLength = Settings.get().getScanLineLength(); | |
for( double angle = angleStart; angle < angleEnd; angle += angleStep) { | |
double x = scanLineOrigin.x + Math.cos(angle) * scanLineLength; | |
double y = scanLineOrigin.y + Math.sin(angle) * scanLineLength; | |
Line line = new Line( scanLineOrigin, new PVector( x, y)); | |
scanLines.add( line); | |
} | |
return scanLines; | |
} | |
/** | |
* Get all the intersecting points for the given scan lines and the given scene lines. | |
* | |
* @param scanLines | |
* @param sceneLines | |
* @return | |
*/ | |
public List<PVector> getIntersectionPoints(List<Line> scanLines, List<Line> sceneLines) { | |
List<PVector> points = new ArrayList<>(); | |
for (Line scanLine : scanLines) { | |
List<PVector> intersections = getIntersections(scanLine, sceneLines); | |
double x = 0; | |
double y = 0; | |
double dist = Double.MAX_VALUE; | |
// find the intersection that is closest to the scanline | |
if (intersections.size() > 0) { | |
for (PVector item : intersections) { | |
double currDist = scanLine.getStart().dist(item); | |
if (currDist < dist) { | |
x = item.x; | |
y = item.y; | |
dist = currDist; | |
} | |
} | |
points.add(new PVector(x, y)); | |
} | |
} | |
return points; | |
} | |
/** | |
* Find intersecting lines | |
* @param scanLine | |
* @param sceneLines | |
* @return | |
*/ | |
public List<PVector> getIntersections(Line scanLine, List<Line> sceneLines) { | |
List<PVector> list = new ArrayList<>(); | |
PVector intersection; | |
for (Line line : sceneLines) { | |
// check if 2 lines intersect | |
intersection = getLineIntersection(scanLine, line); | |
// lines intersect => we have an end point | |
PVector end = null; | |
if (intersection != null) { | |
end = new PVector(intersection.x, intersection.y); | |
} | |
// check if the intersection area should be limited to a visible area | |
if (Settings.get().isLimitToScanLineLength()) { | |
// maximum scan line length | |
double maxLength = Settings.get().getScanLineLength(); | |
PVector start = scanLine.getStart(); | |
// no intersection found => full scan line length | |
if (end == null) { | |
end = new PVector(scanLine.getEnd().x, scanLine.getEnd().y); | |
} | |
// intersection found => limit to scan line length | |
else if (start.dist(end) > maxLength) { | |
end.normalize(); | |
end.mult(maxLength); | |
} | |
} | |
// we have a valid line end, either an intersection with another line or we have the scan line limit | |
if (end != null) { | |
list.add(end); | |
} | |
} | |
return list; | |
} | |
// find intersection point of 2 line segments | |
// | |
// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect | |
// http://www.openprocessing.org/sketch/135314 | |
// http://www-cs.ccny.cuny.edu/~wolberg/capstone/intersection/Intersection%20point%20of%20two%20lines.html | |
private PVector getLineIntersection(Line lineA, Line lineB) { | |
double x1 = lineA.getStart().x; | |
double y1 = lineA.getStart().y; | |
double x2 = lineA.getEnd().x; | |
double y2 = lineA.getEnd().y; | |
double x3 = lineB.getStart().x; | |
double y3 = lineB.getStart().y; | |
double x4 = lineB.getEnd().x; | |
double y4 = lineB.getEnd().y; | |
double ax = x2 - x1; | |
double ay = y2 - y1; | |
double bx = x4 - x3; | |
double by = y4 - y3; | |
double denominator = ax * by - ay * bx; | |
if (denominator == 0) | |
return null; | |
double cx = x3 - x1; | |
double cy = y3 - y1; | |
double t = (cx * by - cy * bx) / denominator; | |
if (t < 0 || t > 1) | |
return null; | |
double u = (cx * ay - cy * ax) / denominator; | |
if (u < 0 || u > 1) | |
return null; | |
return new PVector(x1 + t * ax, y1 + t * ay); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
/** | |
* Helper class for frame rate calculation | |
*/ | |
public class FpsCounter { | |
final long[] frameTimes = new long[100]; | |
int frameTimeIndex = 0; | |
boolean arrayFilled = false; | |
double frameRate; | |
double decimalsFactor = 1000; // we want 3 decimals | |
public void update(long now) { | |
long oldFrameTime = frameTimes[frameTimeIndex]; | |
frameTimes[frameTimeIndex] = now; | |
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length; | |
if (frameTimeIndex == 0) { | |
arrayFilled = true; | |
} | |
if (arrayFilled) { | |
long elapsedNanos = now - oldFrameTime; | |
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length; | |
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame; | |
} | |
} | |
public double getFrameRate() { | |
// return frameRate; | |
return ((int) (frameRate * decimalsFactor)) / decimalsFactor; // reduce to n decimals | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Random; | |
import javafx.geometry.BoundingBox; | |
import javafx.geometry.Bounds; | |
public class Level { | |
public double cellSize = Settings.get().getCanvasWidth() / Settings.get().getHorizontalCellCount(); | |
public double margin = cellSize; | |
public double width = Settings.get().getCanvasWidth() - margin * 2; | |
public double height = Settings.get().getCanvasHeight() - margin * 2; | |
public double minX = margin; | |
public double minY = margin; | |
public double maxX = margin + width; | |
public double maxY = margin + height; | |
public double maxRoomWidth = 200; | |
public double maxRoomHeight = 200; | |
List<Line> sceneLines = null; | |
List<Bounds> roomDimensions = null; | |
Random rnd = new Random(); | |
public Level() { | |
generate(); | |
} | |
public List<Line> getLines() { | |
return sceneLines; | |
} | |
public List<Bounds> getRoomDimensions() { | |
return roomDimensions; | |
} | |
public void generate() { | |
sceneLines = new ArrayList<>(); | |
addRandomLines(Settings.get().getLineCount()); | |
addRooms(Settings.get().getRoomIterations()); | |
addOuterWalls(); | |
} | |
public void addRandomLines(int lineCount) { | |
// other lines | |
for (int i = 0; i < lineCount; i++) { | |
// random start/end location | |
PVector start = new PVector(minX + rnd.nextDouble() * width, minY + rnd.nextDouble() * height); | |
PVector end = new PVector(minX + rnd.nextDouble() * width, minY + rnd.nextDouble() * height); | |
Line line = new Line(start, end); | |
sceneLines.add(line); | |
} | |
} | |
public void addOuterWalls() { | |
sceneLines.addAll(createWallSegments(minX, minY, maxX, minY, 1)); // north | |
sceneLines.addAll(createWallSegments(maxX, minY, maxX, maxY, 1)); // east | |
sceneLines.addAll(createWallSegments(maxX, maxY, minX, maxY, 1)); // south | |
sceneLines.addAll(createWallSegments(minX, maxY, minX, minY, 1)); // west | |
} | |
private void addRooms(int roomIterations) { | |
// keep list of room dimensions to avoid overlapping rooms | |
roomDimensions = new ArrayList<>(); | |
// iterations in order to create a room; once a random room overlaps | |
// another, it gets skipped and another random room is generated | |
for (int i = 0; i < roomIterations; i++) { | |
double w = rnd.nextDouble() * maxRoomWidth; | |
double h = rnd.nextDouble() * maxRoomHeight; | |
double minX = this.minX + rnd.nextDouble() * this.width; | |
double minY = this.minY + rnd.nextDouble() * this.height; | |
double maxX = minX + w; | |
double maxY = minY + h; | |
// snap to grid | |
minX = ((int) (minX / cellSize)) * cellSize; | |
minY = ((int) (minY / cellSize)) * cellSize; | |
maxX = ((int) (maxX / cellSize)) * cellSize; | |
maxY = ((int) (maxY / cellSize)) * cellSize; | |
// avoid rooms that are dots or lines | |
if( minX == maxX || minY == maxY) | |
continue; | |
// skip rooms that start outside top/left | |
if( minX < this.minX || minY < this.minY) | |
continue; | |
// skip rooms that end outside bottom/right | |
if( maxX > this.maxX || maxY > this.maxY) | |
continue; | |
Bounds roomBounds = new BoundingBox(minX, minY, maxX - minX, maxY - minY); | |
// skip room if it overlaps another room | |
boolean overlaps = false; | |
for (Bounds bounds : roomDimensions) { | |
if (roomBounds.intersects(bounds)) { | |
overlaps = true; | |
break; | |
} | |
} | |
if (overlaps) | |
continue; | |
roomDimensions.add(roomBounds); | |
List<Line> room = createRoom(minX, minY, maxX, maxY); | |
for (Line line : room) { | |
sceneLines.add(line); | |
} | |
} | |
} | |
public List<Line> createRoom(double minX, double minY, double maxX, double maxY) { | |
List<Line> walls = new ArrayList<>(); | |
int[] wallCount = new int[] { randomWallCount(), randomWallCount(), randomWallCount(), randomWallCount() }; | |
// ensure there is at least 1 door | |
boolean hasDoor = false; | |
for( int num: wallCount) { | |
if( num > 1) { | |
hasDoor = true; | |
break; | |
} | |
} | |
// no door => split random wall | |
if( !hasDoor) { | |
wallCount[ rnd.nextInt(4)] = 2; // one of the 4 walls gets a door | |
} | |
walls.addAll(createWallSegments(minX, minY, maxX, minY, wallCount[0])); // north | |
walls.addAll(createWallSegments(maxX, minY, maxX, maxY, wallCount[1])); // east | |
walls.addAll(createWallSegments(maxX, maxY, minX, maxY, wallCount[2])); // south | |
walls.addAll(createWallSegments(minX, maxY, minX, minY, wallCount[3])); // west | |
return walls; | |
} | |
public int randomWallCount() { | |
// at least 1 wall | |
int count = 1; | |
// if randomness is satisfied, create a random number of wall segments | |
if (rnd.nextDouble() < 0.25) { | |
count += rnd.nextInt(3); | |
} | |
return count; | |
} | |
/** | |
* Single wall with multiple segments, depending on nr. of walls. A wall | |
* with 2 doors will have 3 walls. | |
* | |
* @param minX | |
* @param minY | |
* @param maxX | |
* @param maxY | |
* @param walls | |
* @return | |
*/ | |
public List<Line> createWallSegments(double minX, double minY, double maxX, double maxY, int walls) { | |
List<Line> wallSegments = new ArrayList<>(); | |
PVector start = new PVector(minX, minY); | |
PVector end = new PVector(maxX, maxY); | |
// angle between the 2 vectors | |
PVector distance = PVector.sub(end, start); | |
double angle = Math.atan2(distance.y, distance.x); | |
int numSegments = walls * 2 - 1; | |
double dist = distance.mag(); | |
dist = dist / numSegments; | |
for (int i = 0; i < numSegments; i++) { | |
// skip every 2nd segment, it's a door | |
if (i % 2 == 1) | |
continue; | |
double startX = start.x + Math.cos(angle) * dist * i; | |
double startY = start.y + Math.sin(angle) * dist * i; | |
double endX = start.x + Math.cos(angle) * dist * (i + 1); | |
double endY = start.y + Math.sin(angle) * dist * (i + 1); | |
wallSegments.add(new Line(new PVector(startX, startY), new PVector(endX, endY))); | |
} | |
return wallSegments; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
public class Line { | |
PVector start; | |
PVector end; | |
public Line(PVector start, PVector end) { | |
super(); | |
this.start = start; | |
this.end = end; | |
} | |
public PVector getStart() { | |
return start; | |
} | |
public void setStart(PVector start) { | |
this.start = start; | |
} | |
public PVector getEnd() { | |
return end; | |
} | |
public void setEnd(PVector end) { | |
this.end = end; | |
} | |
public String toString() { | |
return String.format( "[%f,%f]-[%f,%f]", start.x, start.y, end.x, end.y); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Random; | |
import javafx.animation.AnimationTimer; | |
import javafx.application.Application; | |
import javafx.beans.value.ChangeListener; | |
import javafx.geometry.Bounds; | |
import javafx.scene.Node; | |
import javafx.scene.Scene; | |
import javafx.scene.canvas.Canvas; | |
import javafx.scene.canvas.GraphicsContext; | |
import javafx.scene.control.ContextMenu; | |
import javafx.scene.control.MenuItem; | |
import javafx.scene.input.MouseEvent; | |
import javafx.scene.layout.BorderPane; | |
import javafx.scene.layout.Pane; | |
import javafx.scene.paint.Color; | |
import javafx.scene.paint.CycleMethod; | |
import javafx.scene.paint.RadialGradient; | |
import javafx.scene.paint.Stop; | |
import javafx.stage.Stage; | |
/** | |
* Brute Force Line of Sight Algorithm: Use ScanLines to detect the visible area from a given position. | |
* Regarding the movement of the agents, please read The Nature of Code: http://natureofcode.com/ | |
*/ | |
public class Main extends Application { | |
Random rnd = new Random(); | |
Canvas backgroundCanvas; | |
GraphicsContext backgroundGraphicsContext; | |
Canvas foregroundCanvas; | |
GraphicsContext foregroundGraphicsContext; | |
/** | |
* Container for canvas and other nodes like attractors and repellers | |
*/ | |
Pane layerPane; | |
AnimationTimer animationLoop; | |
Scene scene; | |
List<Line> sceneLines; | |
/** | |
* Current mouse location | |
*/ | |
MouseStatus mouseStatus = new MouseStatus(); | |
Level levelGenerator; | |
Algorithm algorithm = new Algorithm(); | |
List<Mover> players; | |
List<Mover> enemies; | |
@Override | |
public void start(Stage primaryStage) { | |
BorderPane root = new BorderPane(); | |
// canvas | |
backgroundCanvas = new Canvas(Settings.get().getCanvasWidth(), Settings.get().getCanvasHeight()); | |
backgroundGraphicsContext = backgroundCanvas.getGraphicsContext2D(); | |
foregroundCanvas = new Canvas(Settings.get().getCanvasWidth(), Settings.get().getCanvasHeight()); | |
foregroundGraphicsContext = foregroundCanvas.getGraphicsContext2D(); | |
// layers | |
layerPane = new Pane(); | |
layerPane.getChildren().addAll(backgroundCanvas, foregroundCanvas); | |
backgroundCanvas.widthProperty().bind(layerPane.widthProperty()); | |
foregroundCanvas.widthProperty().bind(layerPane.widthProperty()); | |
root.setCenter(layerPane); | |
// toolbar | |
Node toolbar = Settings.get().createToolbar(); | |
root.setRight(toolbar); | |
scene = new Scene(root, Settings.get().getSceneWidth(), Settings.get().getSceneHeight(), Settings.get().getSceneColor()); | |
primaryStage.setScene(scene); | |
primaryStage.setTitle("Demo"); | |
// primaryStage.setFullScreen(true); | |
// primaryStage.setFullScreenExitHint(""); | |
primaryStage.show(); | |
// add content | |
createObjects(); | |
createPlayers(); | |
createEnemies(); | |
// listeners for settings | |
addSettingsListeners(); | |
// add mouse location listener | |
addInputListeners(); | |
// add context menus | |
addCanvasContextMenu(backgroundCanvas); | |
// run animation loop | |
startAnimation(); | |
} | |
private void createObjects() { | |
levelGenerator = new Level(); | |
sceneLines = levelGenerator.getLines(); | |
} | |
private void createPlayers() { | |
players = new ArrayList<>(); | |
if (!Settings.hasPlayer) | |
return; | |
players.add(createPlayer()); | |
} | |
private void createEnemies() { | |
enemies = new ArrayList<>(); | |
for (int i = 0; i < Settings.ENEMY_COUNT; i++) { | |
Mover mover = createAgent(); | |
enemies.add(mover); | |
} | |
} | |
private Mover createPlayer() { | |
double x = mouseStatus.x; | |
double y = mouseStatus.y; | |
Mover agent = new Mover(layerPane); | |
agent.setLocation(x, y); | |
// player is bound to the mouse location => limitation doesn't make | |
// sense | |
agent.setMaxSpeed(1000); | |
return agent; | |
} | |
private Mover createAgent() { | |
double x = levelGenerator.minX + rnd.nextDouble() * levelGenerator.width; | |
double y = levelGenerator.minY + rnd.nextDouble() * levelGenerator.height; | |
Mover agent = new Mover(layerPane); | |
agent.setLocation(x, y); | |
return agent; | |
} | |
private void startAnimation() { | |
// start game | |
animationLoop = new AnimationTimer() { | |
FpsCounter fpsCounter = new FpsCounter(); | |
@Override | |
public void handle(long now) { | |
// update fps | |
// ---------------------------- | |
fpsCounter.update(now); | |
// ai: create scanlines & points | |
// ---------------------------- | |
applyAlgorithm(players); | |
applyAlgorithm(enemies); | |
// player ai | |
// ---------------------------- | |
for (Mover player : players) { | |
PVector newLocation = new PVector(mouseStatus.x, mouseStatus.y); | |
PVector distance = PVector.sub(newLocation, player.getLocation()); | |
PVector forceWithResetVelocity = PVector.sub(distance, player.getVelocity()); | |
player.applyForce(forceWithResetVelocity); | |
} | |
// enemy ai | |
// ---------------------------- | |
for (Mover mover : enemies) { | |
// move towards average intersection | |
PVector avg = mover.getAverageIntersection(); | |
PVector seek = mover.seek(avg); | |
mover.applyForce(seek); | |
// separate from walls | |
PVector separateFromWalls = mover.separate(avg); | |
mover.applyForce(separateFromWalls); | |
// separate from other movers | |
PVector separateFromMovers = mover.separate(enemies); | |
mover.applyForce(separateFromMovers); | |
} | |
// move | |
// ---------------------------- | |
players.forEach(Mover::move); | |
enemies.forEach(Mover::move); | |
// update ui | |
// ---------------------------- | |
players.forEach(Mover::updateUI); | |
enemies.forEach(Mover::updateUI); | |
// paint background canvas | |
// ---------------------------- | |
// clear canvas. we don't use clearRect because we want a black | |
// background | |
backgroundGraphicsContext.setFill(Settings.get().getBackgroundColor()); | |
backgroundGraphicsContext.fillRect(0, 0, backgroundCanvas.getWidth(), backgroundCanvas.getHeight()); | |
// background | |
paintGrid(Settings.get().getGridColor()); | |
// paint foreground canvas | |
// ---------------------------- | |
// draw depending on mouse button down | |
paintOnCanvas(); | |
// update overlays (statistics) | |
// ---------------------------- | |
// show fps and other debug info | |
backgroundGraphicsContext.setFill(Color.BLACK); | |
backgroundGraphicsContext.fillText("Fps: " + fpsCounter.getFrameRate(), 1, 10); | |
} | |
}; | |
animationLoop.start(); | |
} | |
private void applyAlgorithm(List<Mover> movers) { | |
for (Mover mover : movers) { | |
// get scanlines | |
List<Line> scanLines = algorithm.createScanLines(mover); | |
mover.setScanLines(scanLines); | |
// get intersection points | |
List<PVector> points = algorithm.getIntersectionPoints(scanLines, sceneLines); | |
mover.setIntersectionPoints(points); | |
} | |
} | |
private void clearCanvas() { | |
GraphicsContext gc = foregroundGraphicsContext; | |
gc.clearRect(0, 0, foregroundCanvas.getWidth(), foregroundCanvas.getHeight()); | |
} | |
private void paintScanLines(List<Mover> movers) { | |
if (!Settings.get().isDrawScanLines()) | |
return; | |
GraphicsContext gc = foregroundGraphicsContext; | |
gc.setStroke(Color.BLUE.deriveColor(1, 1, 1, 0.3)); | |
gc.setFill(Color.BLUE); | |
for (Mover mover : movers) { | |
for (Line line : mover.getScanLines()) { | |
drawLine(line); | |
} | |
} | |
} | |
private void paintScanShape(List<Mover> movers) { | |
if (!Settings.get().isDrawShape()) | |
return; | |
GraphicsContext gc = foregroundGraphicsContext; | |
for (Mover mover : movers) { | |
List<PVector> shapePoints = new ArrayList<>(); | |
shapePoints.addAll(mover.getIntersectionPoints()); | |
// if we don't have a full circle, we must start and close the shape | |
// at the user's position | |
if (mover.getSweepAngleRad() != Math.PI * 2) { | |
shapePoints.add(0, mover.getLocation()); | |
shapePoints.add(mover.getLocation()); | |
} | |
gc.setStroke(Color.GREEN); | |
if (Settings.get().isGradientShapeFill()) { | |
Color LIGHT_GRADIENT_START = Color.YELLOW.deriveColor(1, 1, 1, 0.5); | |
Color LIGHT_GRADIENT_END = Color.TRANSPARENT; | |
// TODO: don't use the center of the shape; instead calculate | |
// the center depending on the user position | |
RadialGradient gradient = new RadialGradient(0, 0, 0.5, 0.5, 0.5, true, CycleMethod.NO_CYCLE, new Stop(0, LIGHT_GRADIENT_START), new Stop(1, LIGHT_GRADIENT_END)); | |
gc.setFill(gradient); | |
gc.setFill(gradient); | |
} else { | |
gc.setFill(Color.GREEN.deriveColor(1, 1, 1, 0.2)); | |
} | |
int count = 0; | |
gc.beginPath(); | |
for (PVector point : shapePoints) { | |
if (count == 0) { | |
gc.moveTo(point.x, point.y); | |
} else { | |
gc.lineTo(point.x, point.y); | |
} | |
count++; | |
} | |
gc.closePath(); | |
// stroke | |
if (Settings.get().isShapeBorderVisible()) { | |
gc.stroke(); | |
} | |
// fill | |
gc.fill(); | |
} | |
} | |
private void paintIntersectionPoints(List<Mover> movers) { | |
if (!Settings.get().isDrawPoints()) | |
return; | |
GraphicsContext gc = foregroundGraphicsContext; | |
for (Mover mover : movers) { | |
gc.setStroke(Color.RED); | |
gc.setFill(Color.RED.deriveColor(1, 1, 1, 0.5)); | |
double w = 2; | |
double h = w; | |
for (PVector point : mover.getIntersectionPoints()) { | |
gc.strokeOval(point.x - w / 2, point.y - h / 2, w, h); | |
gc.fillOval(point.x - w / 2, point.y - h / 2, w, h); | |
} | |
} | |
} | |
private void paintEnvironment() { | |
if (!Settings.get().isEnvironmentVisible()) | |
return; | |
GraphicsContext gc = foregroundGraphicsContext; | |
// room floor | |
gc.setFill(Color.LIGHTGREY.deriveColor(1, 1, 1, 0.3)); | |
for (Bounds bounds : levelGenerator.getRoomDimensions()) { | |
gc.fillRect(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight()); | |
} | |
// scene lines | |
gc.setStroke(Color.BLACK); | |
gc.setFill(Color.BLACK); | |
for (Line line : sceneLines) { | |
drawLine(line); | |
} | |
} | |
private void paintOnCanvas() { | |
// clear canvas | |
clearCanvas(); | |
paintEnvironment(); | |
paintScanLines(players); | |
paintScanLines(enemies); | |
// draw intersection shape | |
paintScanShape(players); | |
paintScanShape(enemies); | |
// draw intersection points | |
paintIntersectionPoints(players); | |
paintIntersectionPoints(enemies); | |
} | |
private void drawLine(Line line) { | |
GraphicsContext gc = foregroundGraphicsContext; | |
gc.strokeLine(line.getStart().x, line.getStart().y, line.getEnd().x, line.getEnd().y); | |
} | |
/** | |
* Listeners for keyboard, mouse | |
*/ | |
private void addInputListeners() { | |
// capture mouse position | |
scene.addEventFilter(MouseEvent.ANY, e -> { | |
mouseStatus.setX(e.getX()); | |
mouseStatus.setY(e.getY()); | |
mouseStatus.setPrimaryButtonDown(e.isPrimaryButtonDown()); | |
mouseStatus.setSecondaryButtonDown(e.isSecondaryButtonDown()); | |
}); | |
} | |
private void paintGrid(Color color) { | |
double width = backgroundCanvas.getWidth(); | |
double height = backgroundCanvas.getHeight(); | |
double horizontalCellCount = Settings.get().getHorizontalCellCount(); | |
double cellSize = width / horizontalCellCount; | |
double verticalCellCount = height / cellSize; | |
backgroundGraphicsContext.setStroke(color); | |
backgroundGraphicsContext.setLineWidth(1); | |
// horizontal grid lines | |
for (double row = 0; row < height; row += cellSize) { | |
double y = (int) row + 0.5; | |
backgroundGraphicsContext.strokeLine(0, y, width, y); | |
} | |
// vertical grid lines | |
for (double col = 0; col < width; col += cellSize) { | |
double x = (int) col + 0.5; | |
backgroundGraphicsContext.strokeLine(x, 0, x, height); | |
} | |
// highlight cell in which the mouse cursor resides | |
if (Settings.get().isHighlightGridCell()) { | |
Color highlightColor = Color.LIGHTBLUE; | |
int col = (int) (horizontalCellCount / width * mouseStatus.getX()); | |
int row = (int) (verticalCellCount / height * mouseStatus.getY()); | |
backgroundGraphicsContext.setFill(highlightColor); | |
backgroundGraphicsContext.fillRect(col * cellSize, row * cellSize, cellSize, cellSize); | |
} | |
} | |
/** | |
* Listeners for settings changes | |
*/ | |
private void addSettingsListeners() { | |
// particle size | |
Settings.get().horizontalCellCountProperty().addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> System.out.println("Horizontal cell count: " + newValue)); | |
Settings.get().lineCountProperty().addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> createObjects()); | |
Settings.get().roomIterationsProperty().addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> createObjects()); | |
} | |
/** | |
* Context menu for the canvas | |
* | |
* @param node | |
*/ | |
public void addCanvasContextMenu(Node node) { | |
MenuItem menuItem; | |
// create context menu | |
ContextMenu contextMenu = new ContextMenu(); | |
// add custom menu item | |
menuItem = new MenuItem("Menu Item"); | |
menuItem.setOnAction(e -> System.out.println("Clicked")); | |
contextMenu.getItems().add(menuItem); | |
// context menu listener | |
node.setOnMousePressed(event -> { | |
if (event.isSecondaryButtonDown()) { | |
contextMenu.show(node, event.getScreenX(), event.getScreenY()); | |
} | |
}); | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
public class MouseStatus { | |
double x; | |
double y; | |
boolean primaryButtonDown; | |
boolean secondaryButtonDown; | |
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; | |
} | |
public boolean isPrimaryButtonDown() { | |
return primaryButtonDown; | |
} | |
public void setPrimaryButtonDown(boolean primaryButtonDown) { | |
this.primaryButtonDown = primaryButtonDown; | |
} | |
public boolean isSecondaryButtonDown() { | |
return secondaryButtonDown; | |
} | |
public void setSecondaryButtonDown(boolean secondaryButtonDown) { | |
this.secondaryButtonDown = secondaryButtonDown; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import java.util.ArrayList; | |
import java.util.List; | |
import javafx.scene.layout.Pane; | |
public class Mover extends SpriteView { | |
private double sweepAngleRad = Math.toRadians(90); | |
private double viewAngle = 0; | |
private double targetAngle = 0; | |
List<Line> scanLines = new ArrayList<>(); | |
List<PVector> intersectionPoints = new ArrayList<>(); | |
public Mover(Pane layer) { | |
super(layer); | |
} | |
public void move() { | |
super.move(); | |
updateViewAngle(); | |
} | |
public PVector getAverageIntersection() { | |
// calculatge sum | |
PVector sum = new PVector(0, 0); | |
for( PVector other: getIntersectionPoints()) { | |
sum.add(other); | |
} | |
// average | |
sum.div( getIntersectionPoints().size()); | |
return sum; | |
} | |
private void updateViewAngle() { | |
// as long as we are moving, we get a viewing angle | |
if( velocity.x != 0 || velocity.y != 0) { | |
targetAngle = getAngleRad(); | |
} | |
// check if the angle exceeds a given delta | |
double diff = targetAngle - viewAngle; | |
viewAngle += diff; | |
} | |
public double getViewAngleRad() { | |
return viewAngle; | |
} | |
public double getSweepAngleRad() { | |
return sweepAngleRad; | |
} | |
public void setSweepAngleRad(double sweepAngleRad) { | |
this.sweepAngleRad = sweepAngleRad; | |
} | |
public List<Line> getScanLines() { | |
return scanLines; | |
} | |
public void setScanLines(List<Line> scanLines) { | |
this.scanLines = scanLines; | |
} | |
public List<PVector> getIntersectionPoints() { | |
return intersectionPoints; | |
} | |
public void setIntersectionPoints(List<PVector> intersectionPoints) { | |
this.intersectionPoints = intersectionPoints; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
/** | |
Source: http://www.javased.com/?source_dir=SPaTo_Visual_Explorer/lib/src/core/src/processing/core/PVector.java | |
Modification: converted all float to double | |
Original disclaimer: | |
Part of the Processing project - http://processing.org | |
Copyright (c) 200X Dan Shiffman | |
Copyright (c) 2008 Ben Fry and Casey Reas | |
This library is free software; you can redistribute it and/or | |
modify it under the terms of the GNU Lesser General Public | |
License as published by the Free Software Foundation; either | |
version 2.1 of the License, or (at your option) any later version. | |
This library is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
Lesser General Public License for more details. | |
You should have received a copy of the GNU Lesser General | |
Public License along with this library; if not, write to the | |
Free Software Foundation, Inc., 59 Temple Place, Suite 330, | |
Boston, MA 02111-1307 USA | |
*/ | |
public class PVector { | |
/** The x component of the vector. */ | |
public double x; | |
/** The y component of the vector. */ | |
public double y; | |
/** The z component of the vector. */ | |
public double z; | |
/** Array so that this can be temporarily used in an array context */ | |
protected double[] array; | |
/** | |
* Constructor for an empty vector: x, y, and z are set to 0. | |
*/ | |
public PVector() { | |
} | |
/** | |
* Constructor for a 3D vector. | |
* | |
* @param x the x coordinate. | |
* @param y the y coordinate. | |
* @param z the y coordinate. | |
*/ | |
public PVector(double x, double y, double z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
/** | |
* Constructor for a 2D vector: z coordinate is set to 0. | |
* | |
* @param x the x coordinate. | |
* @param y the y coordinate. | |
*/ | |
public PVector(double x, double y) { | |
this.x = x; | |
this.y = y; | |
this.z = 0; | |
} | |
/** | |
* Set x, y, and z coordinates. | |
* | |
* @param x the x coordinate. | |
* @param y the y coordinate. | |
* @param z the z coordinate. | |
*/ | |
public void set(double x, double y, double z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
/** | |
* Set x, y, and z coordinates from a Vector3D object. | |
* | |
* @param v the PVector object to be copied | |
*/ | |
public void set(PVector v) { | |
x = v.x; | |
y = v.y; | |
z = v.z; | |
} | |
/** | |
* Set the x, y (and maybe z) coordinates using a double[] array as the source. | |
* @param source array to copy from | |
*/ | |
public void set(double[] source) { | |
if (source.length >= 2) { | |
x = source[0]; | |
y = source[1]; | |
} | |
if (source.length >= 3) { | |
z = source[2]; | |
} | |
} | |
/** | |
* Get a copy of this vector. | |
*/ | |
public PVector get() { | |
return new PVector(x, y, z); | |
} | |
public double[] get(double[] target) { | |
if (target == null) { | |
return new double[] { x, y, z }; | |
} | |
if (target.length >= 2) { | |
target[0] = x; | |
target[1] = y; | |
} | |
if (target.length >= 3) { | |
target[2] = z; | |
} | |
return target; | |
} | |
/** | |
* Calculate the magnitude (length) of the vector | |
* @return the magnitude of the vector | |
*/ | |
public double mag() { | |
return (double) Math.sqrt(x*x + y*y + z*z); | |
} | |
/** | |
* Add a vector to this vector | |
* @param v the vector to be added | |
*/ | |
public void add(PVector v) { | |
x += v.x; | |
y += v.y; | |
z += v.z; | |
} | |
public void add(double x, double y, double z) { | |
this.x += x; | |
this.y += y; | |
this.z += z; | |
} | |
/** | |
* Add two vectors | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return a new vector that is the sum of v1 and v2 | |
*/ | |
static public PVector add(PVector v1, PVector v2) { | |
return add(v1, v2, null); | |
} | |
/** | |
* Add two vectors into a target vector | |
* @param v1 a vector | |
* @param v2 another vector | |
* @param target the target vector (if null, a new vector will be created) | |
* @return a new vector that is the sum of v1 and v2 | |
*/ | |
static public PVector add(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x + v2.x,v1.y + v2.y, v1.z + v2.z); | |
} else { | |
target.set(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); | |
} | |
return target; | |
} | |
/** | |
* Subtract a vector from this vector | |
* @param v the vector to be subtracted | |
*/ | |
public void sub(PVector v) { | |
x -= v.x; | |
y -= v.y; | |
z -= v.z; | |
} | |
public void sub(double x, double y, double z) { | |
this.x -= x; | |
this.y -= y; | |
this.z -= z; | |
} | |
/** | |
* Subtract one vector from another | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return a new vector that is v1 - v2 | |
*/ | |
static public PVector sub(PVector v1, PVector v2) { | |
return sub(v1, v2, null); | |
} | |
static public PVector sub(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); | |
} else { | |
target.set(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); | |
} | |
return target; | |
} | |
/** | |
* Multiply this vector by a scalar | |
* @param n the value to multiply by | |
*/ | |
public void mult(double n) { | |
x *= n; | |
y *= n; | |
z *= n; | |
} | |
/** | |
* Multiply a vector by a scalar | |
* @param v a vector | |
* @param n scalar | |
* @return a new vector that is v1 * n | |
*/ | |
static public PVector mult(PVector v, double n) { | |
return mult(v, n, null); | |
} | |
/** | |
* Multiply a vector by a scalar, and write the result into a target PVector. | |
* @param v a vector | |
* @param n scalar | |
* @param target PVector to store the result | |
* @return the target vector, now set to v1 * n | |
*/ | |
static public PVector mult(PVector v, double n, PVector target) { | |
if (target == null) { | |
target = new PVector(v.x*n, v.y*n, v.z*n); | |
} else { | |
target.set(v.x*n, v.y*n, v.z*n); | |
} | |
return target; | |
} | |
/** | |
* Multiply each element of one vector by the elements of another vector. | |
* @param v the vector to multiply by | |
*/ | |
public void mult(PVector v) { | |
x *= v.x; | |
y *= v.y; | |
z *= v.z; | |
} | |
/** | |
* Multiply each element of one vector by the individual elements of another | |
* vector, and return the result as a new PVector. | |
*/ | |
static public PVector mult(PVector v1, PVector v2) { | |
return mult(v1, v2, null); | |
} | |
/** | |
* Multiply each element of one vector by the individual elements of another | |
* vector, and write the result into a target vector. | |
* @param v1 the first vector | |
* @param v2 the second vector | |
* @param target PVector to store the result | |
*/ | |
static public PVector mult(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x*v2.x, v1.y*v2.y, v1.z*v2.z); | |
} else { | |
target.set(v1.x*v2.x, v1.y*v2.y, v1.z*v2.z); | |
} | |
return target; | |
} | |
/** | |
* Divide this vector by a scalar | |
* @param n the value to divide by | |
*/ | |
public void div(double n) { | |
x /= n; | |
y /= n; | |
z /= n; | |
} | |
/** | |
* Divide a vector by a scalar and return the result in a new vector. | |
* @param v a vector | |
* @param n scalar | |
* @return a new vector that is v1 / n | |
*/ | |
static public PVector div(PVector v, double n) { | |
return div(v, n, null); | |
} | |
static public PVector div(PVector v, double n, PVector target) { | |
if (target == null) { | |
target = new PVector(v.x/n, v.y/n, v.z/n); | |
} else { | |
target.set(v.x/n, v.y/n, v.z/n); | |
} | |
return target; | |
} | |
/** | |
* Divide each element of one vector by the elements of another vector. | |
*/ | |
public void div(PVector v) { | |
x /= v.x; | |
y /= v.y; | |
z /= v.z; | |
} | |
/** | |
* Multiply each element of one vector by the individual elements of another | |
* vector, and return the result as a new PVector. | |
*/ | |
static public PVector div(PVector v1, PVector v2) { | |
return div(v1, v2, null); | |
} | |
/** | |
* Divide each element of one vector by the individual elements of another | |
* vector, and write the result into a target vector. | |
* @param v1 the first vector | |
* @param v2 the second vector | |
* @param target PVector to store the result | |
*/ | |
static public PVector div(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x/v2.x, v1.y/v2.y, v1.z/v2.z); | |
} else { | |
target.set(v1.x/v2.x, v1.y/v2.y, v1.z/v2.z); | |
} | |
return target; | |
} | |
/** | |
* Calculate the Euclidean distance between two points (considering a point as a vector object) | |
* @param v another vector | |
* @return the Euclidean distance between | |
*/ | |
public double dist(PVector v) { | |
double dx = x - v.x; | |
double dy = y - v.y; | |
double dz = z - v.z; | |
return (double) Math.sqrt(dx*dx + dy*dy + dz*dz); | |
} | |
/** | |
* Calculate the Euclidean distance between two points (considering a point as a vector object) | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return the Euclidean distance between v1 and v2 | |
*/ | |
static public double dist(PVector v1, PVector v2) { | |
double dx = v1.x - v2.x; | |
double dy = v1.y - v2.y; | |
double dz = v1.z - v2.z; | |
return (double) Math.sqrt(dx*dx + dy*dy + dz*dz); | |
} | |
/** | |
* Calculate the dot product with another vector | |
* @return the dot product | |
*/ | |
public double dot(PVector v) { | |
return x*v.x + y*v.y + z*v.z; | |
} | |
public double dot(double x, double y, double z) { | |
return this.x*x + this.y*y + this.z*z; | |
} | |
static public double dot(PVector v1, PVector v2) { | |
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; | |
} | |
/** | |
* Return a vector composed of the cross product between this and another. | |
*/ | |
public PVector cross(PVector v) { | |
return cross(v, null); | |
} | |
/** | |
* Perform cross product between this and another vector, and store the | |
* result in 'target'. If target is null, a new vector is created. | |
*/ | |
public PVector cross(PVector v, PVector target) { | |
double crossX = y * v.z - v.y * z; | |
double crossY = z * v.x - v.z * x; | |
double crossZ = x * v.y - v.x * y; | |
if (target == null) { | |
target = new PVector(crossX, crossY, crossZ); | |
} else { | |
target.set(crossX, crossY, crossZ); | |
} | |
return target; | |
} | |
static public PVector cross(PVector v1, PVector v2, PVector target) { | |
double crossX = v1.y * v2.z - v2.y * v1.z; | |
double crossY = v1.z * v2.x - v2.z * v1.x; | |
double crossZ = v1.x * v2.y - v2.x * v1.y; | |
if (target == null) { | |
target = new PVector(crossX, crossY, crossZ); | |
} else { | |
target.set(crossX, crossY, crossZ); | |
} | |
return target; | |
} | |
/** | |
* Normalize the vector to length 1 (make it a unit vector) | |
*/ | |
public void normalize() { | |
double m = mag(); | |
if (m != 0 && m != 1) { | |
div(m); | |
} | |
} | |
/** | |
* Normalize this vector, storing the result in another vector. | |
* @param target Set to null to create a new vector | |
* @return a new vector (if target was null), or target | |
*/ | |
public PVector normalize(PVector target) { | |
if (target == null) { | |
target = new PVector(); | |
} | |
double m = mag(); | |
if (m > 0) { | |
target.set(x/m, y/m, z/m); | |
} else { | |
target.set(x, y, z); | |
} | |
return target; | |
} | |
/** | |
* Limit the magnitude of this vector | |
* @param max the maximum length to limit this vector | |
*/ | |
public void limit(double max) { | |
if (mag() > max) { | |
normalize(); | |
mult(max); | |
} | |
} | |
/** | |
* Calculate the angle of rotation for this vector (only 2D vectors) | |
* @return the angle of rotation | |
*/ | |
public double heading2D() { | |
double angle = (double) Math.atan2(-y, x); | |
return -1*angle; | |
} | |
/** | |
* Calculate the angle between two vectors, using the dot product | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return the angle between the vectors | |
*/ | |
static public double angleBetween(PVector v1, PVector v2) { | |
double dot = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; | |
double v1mag = Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z); | |
double v2mag = Math.sqrt(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z); | |
return (double) Math.acos(dot / (v1mag * v2mag)); | |
} | |
public String toString() { | |
return "[ " + x + ", " + y + ", " + z + " ]"; | |
} | |
/** | |
* Return a representation of this vector as a double array. This is only for | |
* temporary use. If used in any other fashion, the contents should be copied | |
* by using the get() command to copy into your own array. | |
*/ | |
public double[] array() { | |
if (array == null) { | |
array = new double[3]; | |
} | |
array[0] = x; | |
array[1] = y; | |
array[2] = z; | |
return array; | |
} | |
public PVector rotate(double theta) { | |
double temp = x; | |
// Might need to check for rounding errors like with angleBetween function? | |
x = x * Math.cos(theta) - y*Math.sin(theta); | |
y = temp*Math.sin(theta) + y*Math.cos(theta); | |
return this; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import javafx.beans.property.BooleanProperty; | |
import javafx.beans.property.DoubleProperty; | |
import javafx.beans.property.IntegerProperty; | |
import javafx.beans.property.ObjectProperty; | |
import javafx.beans.property.Property; | |
import javafx.beans.property.SimpleBooleanProperty; | |
import javafx.beans.property.SimpleDoubleProperty; | |
import javafx.beans.property.SimpleIntegerProperty; | |
import javafx.beans.property.SimpleObjectProperty; | |
import javafx.geometry.Insets; | |
import javafx.scene.Node; | |
import javafx.scene.control.CheckBox; | |
import javafx.scene.control.Label; | |
import javafx.scene.control.Separator; | |
import javafx.scene.control.Slider; | |
import javafx.scene.layout.ColumnConstraints; | |
import javafx.scene.layout.GridPane; | |
import javafx.scene.layout.HBox; | |
import javafx.scene.layout.Priority; | |
import javafx.scene.layout.VBox; | |
import javafx.scene.paint.Color; | |
import javafx.scene.text.Font; | |
import javafx.scene.text.FontWeight; | |
/** | |
* Application settings | |
*/ | |
public class Settings { | |
public static int ENEMY_COUNT = 8; | |
public static double TURN_AROUND_THRESHOLD = 10; // when average distance is lower than this value, then the enemy rotates and turns around | |
public static double TURN_AROUND_ANGLE_STEP = Math.toRadians(10); // step size that is added to the angle when the enemy rotates | |
public static boolean hasPlayer = false; | |
// ================================================================================================ | |
// gridpane | |
// ================================================================================================ | |
GridPane gp; | |
int rowIndex = 0; | |
// toolbar properties | |
private DoubleProperty toolbarWidth = new SimpleDoubleProperty(300); | |
private Color backgroundColor = Color.WHITE; | |
private Color gridColor = Color.LIGHTGRAY; | |
// ================================================================================================ | |
// settings properties | |
// ================================================================================================ | |
// scene settings | |
// ------------------------------- | |
private DoubleProperty sceneWidth = new SimpleDoubleProperty(1280); | |
private DoubleProperty sceneHeight = new SimpleDoubleProperty(720); | |
private ObjectProperty<Color> sceneColor = new SimpleObjectProperty<>( Color.BLACK); | |
private DoubleProperty canvasWidth = new SimpleDoubleProperty(sceneWidth.doubleValue()-toolbarWidth.doubleValue()); | |
private DoubleProperty canvasHeight = new SimpleDoubleProperty(sceneHeight.doubleValue()); | |
// example properties | |
// ------------------------------- | |
private IntegerProperty gridHorizontalCellCount = new SimpleIntegerProperty(60); | |
private BooleanProperty highlightGridCell = new SimpleBooleanProperty(false); | |
private IntegerProperty lineCount = new SimpleIntegerProperty( 0); | |
private IntegerProperty roomIterations = new SimpleIntegerProperty( 200); | |
private BooleanProperty environmentVisible = new SimpleBooleanProperty(true); | |
private BooleanProperty userVisible = new SimpleBooleanProperty(true); | |
private DoubleProperty scanLineLength = new SimpleDoubleProperty(100); | |
private BooleanProperty drawPoints = new SimpleBooleanProperty( true); | |
private BooleanProperty drawShape = new SimpleBooleanProperty( true); | |
private BooleanProperty gradientShapeFill = new SimpleBooleanProperty(false); | |
private BooleanProperty shapeBorderVisible = new SimpleBooleanProperty(true); | |
private BooleanProperty drawScanLines = new SimpleBooleanProperty( false); | |
private BooleanProperty limitToScanLineLength = new SimpleBooleanProperty( true); | |
private IntegerProperty scanLineCount = new SimpleIntegerProperty( 50); | |
// ================================================================================================ | |
// methods | |
// ================================================================================================ | |
// instance handling | |
// ---------------------------------------- | |
private static Settings settings = new Settings(); | |
private Settings() { | |
} | |
/** | |
* Return the one instance of this class | |
*/ | |
public static Settings get() { | |
return settings; | |
} | |
// ------------------------------------------------------------------------------------------------ | |
// user interface: nodes for property modification | |
// ------------------------------------------------------------------------------------------------ | |
public Node createToolbar() { | |
gp = new GridPane(); | |
// gridpane layout | |
gp.setPrefWidth( Settings.get().getToolbarWidth()); | |
gp.setHgap(1); | |
gp.setVgap(1); | |
gp.setPadding(new Insets(8)); | |
// set column size in percent | |
ColumnConstraints column = new ColumnConstraints(); | |
column.setPercentWidth(50); | |
gp.getColumnConstraints().add(column); | |
column = new ColumnConstraints(); | |
column.setPercentWidth(70); | |
gp.getColumnConstraints().add(column); | |
// add components for settings to gridpane | |
// grid | |
// ------------------------------------- | |
addSeparator( "Grid"); | |
addNumberSlider( "Horiz. Cells", 0, gridHorizontalCellCount, 1, 60); | |
addCheckBox( "Highlight", highlightGridCell); | |
// Scene | |
// ------------------------------------- | |
addSeparator( "Scene"); | |
addNumberSlider( "Lines", 0, lineCount, 0, 150); | |
addNumberSlider( "Room Iterations", 0, roomIterations, 0, 4000); | |
addCheckBox( "Environment Visible", environmentVisible); | |
addCheckBox( "User Visible", userVisible); | |
// Intersections | |
// ------------------------------------- | |
addSeparator( "Intersections"); | |
addCheckBox( "Points", drawPoints); | |
addCheckBox( "Shape", drawShape); | |
addCheckBox( "Shape Border", shapeBorderVisible); | |
addCheckBox( "Gradient Fill", gradientShapeFill); | |
addCheckBox( "Limit", limitToScanLineLength); | |
// group 2 | |
// ------------------------------------- | |
addSeparator( "Scan Lines"); | |
addCheckBox( "Scanlines Visible", drawScanLines); | |
addNumberSlider( "Count", 0, scanLineCount, 1, 2000); | |
double maxLength = Math.sqrt( getCanvasWidth() * getCanvasWidth() + getCanvasHeight() * getCanvasHeight()); | |
addNumberSlider( "Length", 0, scanLineLength, 1, maxLength); | |
return gp; | |
} | |
private void addSeparator( String text) { | |
gp.addRow(rowIndex++, createSeparator( text)); | |
} | |
private void addNumberSlider( String text, int digits, Property<Number> observable, double min, double max) { | |
// number format, eg "%.3f" | |
String format = "%." + digits + "f"; | |
addNumberSlider( text, observable, min, max, format); | |
} | |
private void addNumberSlider( String text, Property<Number> observable, double min, double max, String labelFormat) { | |
Slider slider = createNumberSlider( observable, min, max); | |
Label valueLabel = new Label(); | |
valueLabel.setPrefWidth(70); | |
valueLabel.textProperty().bind(slider.valueProperty().asString( labelFormat)); | |
HBox box = new HBox(); | |
box.setSpacing(10); | |
box.getChildren().addAll( slider, valueLabel); | |
gp.addRow(rowIndex++, new Label( text), box); | |
} | |
private void addCheckBox( String text, Property<Boolean> observable) { | |
CheckBox checkBox = createCheckBox( observable); | |
gp.addRow(rowIndex++, new Label( text), checkBox); | |
} | |
// ------------------------------------------------------------------------------------------------ | |
// gui helper methods | |
// ------------------------------------------------------------------------------------------------ | |
private Node createSeparator( String text) { | |
VBox box = new VBox(); | |
Label label = new Label( text); | |
label.setFont(Font.font(null, FontWeight.BOLD, 14)); | |
Separator separator = new Separator(); | |
box.getChildren().addAll(separator, label); | |
box.setFillWidth(true); | |
GridPane.setColumnSpan(box, 2); | |
GridPane.setFillWidth(box, true); | |
GridPane.setHgrow(box, Priority.ALWAYS); | |
return box; | |
} | |
private Slider createNumberSlider( Property<Number> observable, double min, double max) { | |
Slider slider = new Slider( min, max, observable.getValue().doubleValue()); | |
slider.setShowTickLabels(true); | |
slider.setShowTickMarks(true); | |
slider.valueProperty().bindBidirectional(observable); | |
return slider; | |
} | |
private CheckBox createCheckBox( Property<Boolean> observable) { | |
CheckBox cb = new CheckBox(); | |
cb.selectedProperty().bindBidirectional(observable); | |
return cb; | |
} | |
// ================================================================================================ | |
// auto-generated begin | |
// ================================================================================================ | |
public final DoubleProperty toolbarWidthProperty() { | |
return this.toolbarWidth; | |
} | |
public final double getToolbarWidth() { | |
return this.toolbarWidthProperty().get(); | |
} | |
public final void setToolbarWidth(final double toolbarWidth) { | |
this.toolbarWidthProperty().set(toolbarWidth); | |
} | |
public final DoubleProperty sceneWidthProperty() { | |
return this.sceneWidth; | |
} | |
public final double getSceneWidth() { | |
return this.sceneWidthProperty().get(); | |
} | |
public final void setSceneWidth(final double sceneWidth) { | |
this.sceneWidthProperty().set(sceneWidth); | |
} | |
public final DoubleProperty sceneHeightProperty() { | |
return this.sceneHeight; | |
} | |
public final double getSceneHeight() { | |
return this.sceneHeightProperty().get(); | |
} | |
public final void setSceneHeight(final double sceneHeight) { | |
this.sceneHeightProperty().set(sceneHeight); | |
} | |
public final ObjectProperty<Color> sceneColorProperty() { | |
return this.sceneColor; | |
} | |
public final javafx.scene.paint.Color getSceneColor() { | |
return this.sceneColorProperty().get(); | |
} | |
public final void setSceneColor(final javafx.scene.paint.Color sceneColor) { | |
this.sceneColorProperty().set(sceneColor); | |
} | |
public final DoubleProperty canvasWidthProperty() { | |
return this.canvasWidth; | |
} | |
public final double getCanvasWidth() { | |
return this.canvasWidthProperty().get(); | |
} | |
public final void setCanvasWidth(final double canvasWidth) { | |
this.canvasWidthProperty().set(canvasWidth); | |
} | |
public final DoubleProperty canvasHeightProperty() { | |
return this.canvasHeight; | |
} | |
public final double getCanvasHeight() { | |
return this.canvasHeightProperty().get(); | |
} | |
public final void setCanvasHeight(final double canvasHeight) { | |
this.canvasHeightProperty().set(canvasHeight); | |
} | |
public final IntegerProperty lineCountProperty() { | |
return this.lineCount; | |
} | |
public final int getLineCount() { | |
return this.lineCount.get(); | |
} | |
public final IntegerProperty horizontalCellCountProperty() { | |
return this.gridHorizontalCellCount; | |
} | |
public final int getHorizontalCellCount() { | |
return this.horizontalCellCountProperty().get(); | |
} | |
public final void setHorizontalCellCount(final int horizontalCellCount) { | |
this.horizontalCellCountProperty().set(horizontalCellCount); | |
} | |
public final boolean isDrawScanLines() { | |
return drawScanLines.get(); | |
} | |
public double getScanLineLength() { | |
return scanLineLength.get(); | |
} | |
public boolean isDrawShape() { | |
return drawShape.get(); | |
} | |
public boolean isDrawPoints() { | |
return drawPoints.get(); | |
} | |
public boolean isLimitToScanLineLength() { | |
return limitToScanLineLength.get(); | |
} | |
public int getScanLineCount() { | |
return scanLineCount.get(); | |
} | |
public final BooleanProperty highlightGridCellProperty() { | |
return this.highlightGridCell; | |
} | |
public final boolean isHighlightGridCell() { | |
return this.highlightGridCellProperty().get(); | |
} | |
public final void setHighlightGridCell(final boolean highlightGridCell) { | |
this.highlightGridCellProperty().set(highlightGridCell); | |
} | |
public final BooleanProperty environmentVisibleProperty() { | |
return this.environmentVisible; | |
} | |
public final boolean isEnvironmentVisible() { | |
return this.environmentVisibleProperty().get(); | |
} | |
public final void setEnvironmentVisible(final boolean environmentVisible) { | |
this.environmentVisibleProperty().set(environmentVisible); | |
} | |
public final BooleanProperty gradientShapeFillProperty() { | |
return this.gradientShapeFill; | |
} | |
public final boolean isGradientShapeFill() { | |
return this.gradientShapeFillProperty().get(); | |
} | |
public final void setGradientShapeFill(final boolean gradientShapeFill) { | |
this.gradientShapeFillProperty().set(gradientShapeFill); | |
} | |
public final BooleanProperty shapeBorderVisibleProperty() { | |
return this.shapeBorderVisible; | |
} | |
public final boolean isShapeBorderVisible() { | |
return this.shapeBorderVisibleProperty().get(); | |
} | |
public final void setShapeBorderVisible(final boolean shapeBorderVisible) { | |
this.shapeBorderVisibleProperty().set(shapeBorderVisible); | |
} | |
public final BooleanProperty userVisibleProperty() { | |
return this.userVisible; | |
} | |
public final boolean isUserVisible() { | |
return this.userVisibleProperty().get(); | |
} | |
public final void setUserVisible(final boolean userVisible) { | |
this.userVisibleProperty().set(userVisible); | |
} | |
public final IntegerProperty roomIterationsProperty() { | |
return this.roomIterations; | |
} | |
public final int getRoomIterations() { | |
return this.roomIterationsProperty().get(); | |
} | |
public final void setRoomIterations(final int roomIterations) { | |
this.roomIterationsProperty().set(roomIterations); | |
} | |
public Color getBackgroundColor() { | |
return backgroundColor; | |
} | |
public Color getGridColor() { | |
return gridColor; | |
} | |
public void setGridColor(Color gridColor) { | |
this.gridColor = gridColor; | |
} | |
// ================================================================================================ | |
// auto-generated end | |
// ================================================================================================ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import java.util.List; | |
public class SpriteBase { | |
PVector location; | |
PVector velocity; | |
PVector acceleration; | |
double maxSpeed = 1; | |
double maxForce = 0.3; | |
public SpriteBase() { | |
this.location = new PVector(0, 0); | |
this.velocity = new PVector(0, 0); | |
this.acceleration = new PVector(0, 0); | |
} | |
public void applyForce(PVector force) { | |
acceleration.add(force); | |
} | |
public void move() { | |
// set velocity depending on acceleration | |
velocity.add(acceleration); | |
// limit velocity to max speed | |
velocity.limit(maxSpeed); | |
// change location depending on velocity | |
location.add(velocity); | |
// clear acceleration | |
acceleration.mult(0); | |
} | |
public void setLocation(double x, double y) { | |
location.x = x; | |
location.y = y; | |
} | |
public void setVelocity(double x, double y) { | |
velocity.x = x; | |
velocity.y = y; | |
} | |
public double getAngleRad() { | |
return velocity.heading2D(); | |
} | |
public PVector seek(PVector target) { | |
PVector desired = PVector.sub(target, location); | |
desired.normalize(); | |
desired.mult(maxSpeed); | |
PVector steer = PVector.sub(desired, velocity); | |
steer.limit(maxForce); | |
return steer; | |
} | |
public PVector separate(List<Mover> sprites) { | |
double desiredseparation = 100; | |
PVector steer = new PVector(0, 0, 0); | |
int count = 0; | |
for (SpriteBase other : sprites) { | |
double d = PVector.dist(location, other.location); | |
if ((d > 0) && (d < desiredseparation)) { | |
PVector diff = PVector.sub(location, other.location); | |
diff.normalize(); | |
diff.div(d); | |
steer.add(diff); | |
count++; | |
} | |
} | |
if (count > 0) { | |
steer.div((double) count); | |
} | |
if (steer.mag() > 0) { | |
steer.normalize(); | |
steer.mult(maxSpeed); | |
steer.sub(velocity); | |
steer.limit(maxForce); | |
} | |
return steer; | |
} | |
public PVector separate(PVector target) { | |
double forceLimit = 3; | |
double desiredseparation = 10; | |
PVector steer = new PVector(0, 0, 0); | |
int count = 0; | |
double d = PVector.dist(location, target); | |
if ((d > 0) && (d < desiredseparation)) { | |
PVector diff = PVector.sub(location, target); | |
diff.normalize(); | |
diff.div(d); | |
steer.add(diff); | |
count++; | |
} | |
if (count > 0) { | |
steer.div((double) count); | |
} | |
if (steer.mag() > 0) { | |
steer.normalize(); | |
steer.mult(maxSpeed); | |
steer.sub(velocity); | |
steer.limit(forceLimit); | |
} | |
return steer; | |
} | |
public PVector getLocation() { | |
return location; | |
} | |
public PVector getVelocity() { | |
return velocity; | |
} | |
public PVector getAcceleration() { | |
return acceleration; | |
} | |
public double getMaxSpeed() { | |
return maxSpeed; | |
} | |
public void setMaxSpeed(double maxSpeed) { | |
this.maxSpeed = maxSpeed; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import javafx.scene.Node; | |
import javafx.scene.layout.Pane; | |
import javafx.scene.paint.Color; | |
import javafx.scene.shape.Rectangle; | |
public class SpriteView extends SpriteBase { | |
Node view = null; | |
Pane layer = null; | |
double width = 10; | |
double height = 10; | |
double centerX = width / 2; | |
double centerY = height / 2; | |
public SpriteView( Pane layer) { | |
this.layer = layer; | |
createView(); | |
addViewToLayer(); | |
} | |
public void createView() { | |
Rectangle rectangle = new Rectangle( 0,0,width,height); | |
rectangle.setStroke(Color.BLACK); | |
rectangle.setFill(Color.LIGHTSLATEGRAY.deriveColor(1, 1, 1, 0.3)); | |
this.view = rectangle; | |
} | |
public void addViewToLayer() { | |
layer.getChildren().add( view); | |
} | |
public void removeViewFromLayer() { | |
layer.getChildren().remove( view); | |
} | |
public Node getView() { | |
return view; | |
} | |
public void updateUI() { | |
view.relocate(location.x - centerX, location.y - centerY); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment