Skip to content

Instantly share code, notes, and snippets.

@Roland09
Created February 13, 2016 15:54
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Roland09/e72296d8de55f3051d6a to your computer and use it in GitHub Desktop.
Save Roland09/e72296d8de55f3051d6a to your computer and use it in GitHub Desktop.
Line of Sight Demo
package application;
import java.util.ArrayList;
import java.util.List;
public class Algorithm {
/**
* Sweep around the given circle with the given distance and create the scan lines
* @param startX
* @param startY
* @return
*/
public List<Line> createScanLines( double startX, double startY) {
List<Line> scanLines;
double angleStart = 0;
double angleEnd = Math.PI * 2;
double step = Math.PI / Settings.get().getScanLineCount();
scanLines = new ArrayList<>();
PVector scanLine = new PVector( startX, startY);
double scanLineLength = Settings.get().getScanLineLength();
for( double angle = angleStart; angle < angleEnd; angle += step) {
double x = scanLine.x + Math.cos(angle) * scanLineLength;
double y = scanLine.y + Math.sin(angle) * scanLineLength;
Line line = new Line( scanLine, 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);
}
}
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
}
}
package application;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
public class LevelGenerator {
Random rnd = new Random();
List<Line> sceneLines = null;
List<Bounds> roomDimensions = null;
double maxRoomWidth = 200;
double maxRoomHeight = 200;
public LevelGenerator() {
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++) {
Line line = new Line(randomBoundedVector(), randomBoundedVector());
sceneLines.add(line);
}
}
public void addOuterWalls() {
// outer walls
double w = Settings.get().getCanvasWidth();
double h = Settings.get().getCanvasHeight();
double minX = 0;
double minY = 0;
double maxX = minX + w;
double maxY = minY + h;
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 = rnd.nextDouble() * Settings.get().getCanvasWidth();
double minY = rnd.nextDouble() * Settings.get().getCanvasHeight();
double maxX = minX + w;
double maxY = minY + h;
// snap to grid
double cellSize = Settings.get().getCanvasWidth() / Settings.get().getHorizontalCellCount();
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;
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<>();
walls.addAll(createWallSegments(minX, minY, maxX, minY, randomWallCount())); // north
walls.addAll(createWallSegments(maxX, minY, maxX, maxY, randomWallCount())); // east
walls.addAll(createWallSegments(maxX, maxY, minX, maxY, randomWallCount())); // south
walls.addAll(createWallSegments(minX, maxY, minX, minY, randomWallCount())); // 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;
}
private PVector randomBoundedVector() {
return new PVector(rnd.nextDouble() * Settings.get().getCanvasWidth(), rnd.nextDouble() * Settings.get().getCanvasHeight());
}
}
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);
}
}
package application;
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.
*/
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();
LevelGenerator levelGenerator;
Algorithm algorithm = new Algorithm();
@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();
// listeners for settings
addSettingsListeners();
// add mouse location listener
addInputListeners();
// add context menus
addCanvasContextMenu( backgroundCanvas);
// run animation loop
startAnimation();
}
private void createObjects() {
levelGenerator = new LevelGenerator();
sceneLines = levelGenerator.getLines();
}
private void startAnimation() {
// start game
animationLoop = new AnimationTimer() {
FpsCounter fpsCounter = new FpsCounter();
@Override
public void handle(long now) {
// update fps
// ----------------------------
fpsCounter.update( now);
// 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 paintOnCanvas() {
// clear canvas
GraphicsContext gc = foregroundGraphicsContext;
gc.clearRect(0, 0, foregroundCanvas.getWidth(), foregroundCanvas.getHeight());
// scanlines
List<Line> scanLines = algorithm.createScanLines( mouseStatus.x, mouseStatus.y);
if( Settings.get().isDrawScanLines()) {
gc.setStroke(Color.BLUE.deriveColor(1, 1, 1, 0.3));
gc.setFill(Color.BLUE);
for( Line line: scanLines) {
drawLine(line);
}
}
// environment
if( Settings.get().isEnvironmentVisible()) {
// 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);
}
}
// intersections
// get intersection points
List<PVector> points = algorithm.getIntersectionPoints( scanLines, sceneLines);
// draw intersection shape
if( Settings.get().isDrawShape()) {
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.7));
}
int count = 0;
gc.beginPath();
for( PVector point: points) {
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();
}
// draw intersection points
if( Settings.get().isDrawPoints()) {
gc.setStroke(Color.RED);
gc.setFill(Color.RED.deriveColor(1, 1, 1, 0.5));
double w = 2;
double h = w;
for( PVector point: points) {
gc.strokeOval(point.x - w / 2, point.y - h / 2, w, h);
gc.fillOval(point.x - w / 2, point.y - h / 2, w, h);
}
}
// user
if( Settings.get().isUserVisible()) {
gc.setStroke(Color.BLACK);
gc.setFill(Color.LIGHTGRAY);
double w = 5;
double h = w;
gc.fillOval(mouseStatus.x - w / 2, mouseStatus.y - h / 2, w, h);
gc.strokeOval(mouseStatus.x - w / 2, mouseStatus.y - h / 2, w, h);
}
}
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);
}
}
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;
}
}
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;
}
}
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 {
// ================================================================================================
// 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( 100);
private BooleanProperty environmentVisible = new SimpleBooleanProperty(true);
private BooleanProperty userVisible = new SimpleBooleanProperty(true);
private DoubleProperty scanLineLength = new SimpleDoubleProperty(200);
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( 1000);
// ================================================================================================
// 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
// ================================================================================================
}
@Birdasaur
Copy link

@Roland09
This is a really cool effect and it inspired me to create a bolt on Shadow and Lighting view for JavaFX GUIs.
https://github.com/Birdasaur/LitFX/tree/shadowsthatmove
Hopefully you see this. I wanted to give you credit for that as I borrowed your Line of Sight approach for part of my implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment