Initial fork from spacecraft-tactics
This commit is contained in:
832
src/main/java/de/extio/game_engine/spatial2/WorldUtils2.java
Normal file
832
src/main/java/de/extio/game_engine/spatial2/WorldUtils2.java
Normal file
@@ -0,0 +1,832 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import de.extio.game_engine.spatial2.model.Area2;
|
||||
import de.extio.game_engine.spatial2.model.CoordD2;
|
||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||
import de.extio.game_engine.spatial2.model.Edge2;
|
||||
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||
import de.extio.game_engine.spatial2.model.HasPositionAndDimension2;
|
||||
import de.extio.game_engine.spatial2.model.ImmutableCoordD2;
|
||||
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||
import de.extio.game_engine.spatial2.model.MutableCoordD2;
|
||||
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||
import de.extio.game_engine.spatial2.model.RectI2;
|
||||
import de.extio.game_engine.util.rng.ThreadLocalXorShift128Random;
|
||||
|
||||
/**
|
||||
* Library for geometric problems
|
||||
*/
|
||||
public final class WorldUtils2 {
|
||||
|
||||
/**
|
||||
* The Pythagorean theorem
|
||||
* @param v Vector
|
||||
* @return Length of vector v
|
||||
*/
|
||||
public static double getDistance(final CoordI2 v) {
|
||||
return Math.sqrt(Math.pow(v.getX(), 2.0) + Math.pow(v.getY(), 2.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Pythagorean theorem
|
||||
* @param v Vector
|
||||
* @return Length of vector v
|
||||
*/
|
||||
public static double getDistance(final CoordD2 v) {
|
||||
return Math.sqrt(Math.pow(v.getX(), 2.0) + Math.pow(v.getY(), 2.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Pythagorean theorem
|
||||
* @param p0 Coordinate 0
|
||||
* @param p1 Coordinate 1
|
||||
* @return Distance
|
||||
*/
|
||||
public static double getDistance(final HasPosition2 p0, final HasPosition2 p1) {
|
||||
return getDistance(p0.getPosition(), p1.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* The Pythagorean theorem
|
||||
* @param c0 Coordinate 0
|
||||
* @param c1 Coordinate 1
|
||||
* @return Distance
|
||||
*/
|
||||
public static double getDistance(final CoordI2 c0, final CoordI2 c1) {
|
||||
return Math.sqrt(Math.pow(c0.getX() - c1.getX(), 2.0) + Math.pow(c0.getY() - c1.getY(), 2.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* The Pythagorean theorem
|
||||
* @param c0 Coordinate 0
|
||||
* @param c1 Coordinate 1
|
||||
* @return Distance
|
||||
*/
|
||||
public static double getDistance(final CoordD2 c0, final CoordD2 c1) {
|
||||
return Math.sqrt(Math.pow(c0.getX() - c1.getX(), 2.0) + Math.pow(c0.getY() - c1.getY(), 2.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the angle between 2 vectors
|
||||
* @param v0 Vector 0
|
||||
* @param v1 Vector 1
|
||||
* @return Angle (radians)
|
||||
*/
|
||||
public static double getVectorAngle(final CoordI2 v0, final CoordI2 v1) {
|
||||
return Math.atan2(v0.getX() * v1.getY() - v0.getY() * v1.getX(), v0.getX() * v1.getX() + v0.getY() * v1.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the angle between 2 vectors
|
||||
* @param v0 Vector 0
|
||||
* @param v1 Vector 1
|
||||
* @return Angle (radians)
|
||||
*/
|
||||
public static double getVectorAngle(final CoordD2 v0, final CoordD2 v1) {
|
||||
// final double dotV = v0.getX() * v1.getX() + v0.getY() * v1.getY();
|
||||
// final double distV = getDistance(v0) * getDistance(v1);
|
||||
// if (distV == 0.0) {
|
||||
// return 0.0;
|
||||
// }
|
||||
// return Math.acos(Math.min(dotV / distV, 1.0));
|
||||
return Math.atan2(v0.getX() * v1.getY() - v0.getY() * v1.getX(), v0.getX() * v1.getX() + v0.getY() * v1.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the direction of a vector
|
||||
* @param v0 Vector 0
|
||||
* @param v1 Vector 1
|
||||
* @return Angle (radians)
|
||||
*/
|
||||
public static double getVectorAngle(final CoordI2 v) {
|
||||
return Math.atan2(v.getX(), v.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the direction of a vector
|
||||
* @param v0 Vector 0
|
||||
* @param v1 Vector 1
|
||||
* @return Angle (radians)
|
||||
*/
|
||||
public static double getVectorAngle(final CoordD2 v) {
|
||||
return Math.atan2(v.getX(), v.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the dot product
|
||||
* @param v0 Vector 0
|
||||
* @param v1 Vector 1
|
||||
* @return Dot product
|
||||
*/
|
||||
public static double getVectorDotProduct(final CoordD2 v0, final CoordD2 v1) {
|
||||
return v0.getX() * v1.getX() + v0.getY() * v1.getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates a vector
|
||||
* @return Rotated vector
|
||||
*/
|
||||
public static CoordD2 rotateVector(final CoordD2 v, final double θ) {
|
||||
return ImmutableCoordD2.create(v.getX() * Math.cos(θ) - v.getY() * Math.sin(θ), v.getX() * Math.sin(θ) + v.getY() * Math.cos(θ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether coord is within bounds (e.g. composite entity)
|
||||
* @param hasPosition
|
||||
* @return
|
||||
*/
|
||||
public static boolean isInBounds(final HasPosition2 hasPosition, final CoordI2 dimension) {
|
||||
return isInBounds(hasPosition.getPosition(), dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether coord is within bounds (e.g. composite entity)
|
||||
* @param coord
|
||||
* @return
|
||||
*/
|
||||
public static boolean isInBounds(final CoordI2 coord, final CoordI2 dimension) {
|
||||
if (coord == null ||
|
||||
coord.getX() < 0 || coord.getX() >= dimension.getX() ||
|
||||
coord.getY() < 0 || coord.getY() >= dimension.getY()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signum function that returns -1 or 1.
|
||||
* @param n
|
||||
* @return -1 if n is negative and 1 if n is positive (including 0)
|
||||
*/
|
||||
public static int copySign(final int n) {
|
||||
return (Integer.signum(n) >> 31) | 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signum function that returns -1 or 1.
|
||||
* @param n
|
||||
* @return -1 if n is negative and 1 if n is positive (including 0)
|
||||
*/
|
||||
public static int copySign(final double n) {
|
||||
return ((int) Math.signum(n) >> 31) | 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersects 2 recangles
|
||||
* <!-- @deprecated Use Area2 based function intersectAreas() instead -->
|
||||
* @param r0 Rectangle 0
|
||||
* @param r1 Rectangle 1
|
||||
* @return The intersected area if r0 and r1 overlap, else null
|
||||
*/
|
||||
// @Deprecated
|
||||
public static RectI2 intersectRectangles(final RectI2 r0, final RectI2 r1) {
|
||||
final CoordI2 size0 = ImmutableCoordI2.create(r0.getC1().getX() - r0.getC0().getX(), r0.getC1().getY() - r0.getC0().getY());
|
||||
final CoordI2 size1 = ImmutableCoordI2.create(r1.getC1().getX() - r1.getC0().getX(), r1.getC1().getY() - r1.getC0().getY());
|
||||
final Rectangle rect0 = new Rectangle(r0.getC0().getX(), r0.getC0().getY(), size0.getX(), size0.getY());
|
||||
final Rectangle rect1 = new Rectangle(r1.getC0().getX(), r1.getC0().getY(), size1.getX(), size1.getY());
|
||||
|
||||
final Rectangle intersection = rect0.intersection(rect1);
|
||||
if (intersection.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new RectI2(
|
||||
ImmutableCoordI2.create((int) intersection.getX(), (int) intersection.getY()),
|
||||
ImmutableCoordI2.create((int) (intersection.getX() + intersection.getWidth()), (int) (intersection.getY() + intersection.getHeight())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersects 2 areas
|
||||
* @param r0 Area 0
|
||||
* @param r1 Area 1
|
||||
* @return The intersected area if r0 and r1 overlap, else null
|
||||
*/
|
||||
public static Area2 intersectAreas(final HasPositionAndDimension2 a0, final HasPositionAndDimension2 a1) {
|
||||
final Rectangle rect0 = new Rectangle(a0.getPosition().getX(), a0.getPosition().getY(), a0.getDimension().getX(), a0.getDimension().getY());
|
||||
final Rectangle rect1 = new Rectangle(a1.getPosition().getX(), a1.getPosition().getY(), a1.getDimension().getX(), a1.getDimension().getY());
|
||||
|
||||
final Rectangle intersection = rect0.intersection(rect1);
|
||||
if (intersection.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Area2(
|
||||
ImmutableCoordI2.create((int) intersection.getX(), (int) intersection.getY()),
|
||||
ImmutableCoordI2.create((int) intersection.getWidth(), (int) intersection.getHeight()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether 2 objects intersect
|
||||
* @param obj0 HasPositionAndDimension2 object 0
|
||||
* @param obj1 HasPositionAndDimension2 object 1
|
||||
* @return true / false whether obj0 intersects with obj1
|
||||
*/
|
||||
public static boolean intersects(final HasPositionAndDimension2 obj0, final HasPositionAndDimension2 obj1) {
|
||||
return intersects(obj0.getPosition(), obj0.getDimension(), obj1.getPosition(), obj1.getDimension());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether 2 objects intersect
|
||||
* @param position0 position of object 0
|
||||
* @param dimension0 dimension of object 0
|
||||
* @param position1 position of object 1
|
||||
* @param dimension1 dimension of object 1
|
||||
* @return true / false whether obj0 intersects with obj1
|
||||
*/
|
||||
public static boolean intersects(final CoordI2 position0, final CoordI2 dimension0, final CoordI2 position1, final CoordI2 dimension1) {
|
||||
int d0x = dimension0.getX();
|
||||
int d0y = dimension0.getY();
|
||||
int d1x = dimension1.getX();
|
||||
int d1y = dimension1.getY();
|
||||
if (d0x <= 0 || d0y <= 0 || d1x <= 0 || d1y <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
d0x += position0.getX();
|
||||
d0y += position0.getY();
|
||||
d1x += position1.getX();
|
||||
d1y += position1.getY();
|
||||
|
||||
return ((d0x < position0.getX() || d0x > position1.getX()) &&
|
||||
(d0y < position0.getY() || d0y > position1.getY()) &&
|
||||
(d1x < position1.getX() || d1x > position0.getX()) &&
|
||||
(d1y < position1.getY() || d1y > position0.getY()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds an aligned Area2 from arbitrary points
|
||||
* @return
|
||||
*/
|
||||
public static Area2 pointsToArea(final CoordI2... points) {
|
||||
if (points == null || points.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final CoordI2 corner0 = MutableCoordI2.create(points[0]);
|
||||
final CoordI2 corner1 = MutableCoordI2.create(points[0]).add(1, 1);
|
||||
for (int i = 1; i < points.length; i++) {
|
||||
corner0.setXY(Math.min(corner0.getX(), points[i].getX()), Math.min(corner0.getY(), points[i].getY()));
|
||||
corner1.setXY(Math.max(corner1.getX(), points[i].getX() + 1), Math.max(corner1.getY(), points[i].getY() + 1));
|
||||
}
|
||||
|
||||
final CoordI2 dimension = ImmutableCoordI2.create(Math.max(1, corner1.getX() - corner0.getX()), Math.max(1, corner1.getY() - corner0.getY()));
|
||||
return new Area2(ImmutableCoordI2.create(corner0), dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the point of intersection to intercept target from origin with velocity velOrigin
|
||||
* @param pOrigin_ Position of origin (e.g. ship that intercepts target)
|
||||
* @param velOrigin Velocity of origin
|
||||
* @param pTarget_ Position of target
|
||||
* @param vTarget_ Velocity vector of target
|
||||
* @return Point of interception or null if interception is not possible
|
||||
*/
|
||||
public static CoordI2 intercept(final CoordI2 pOrigin_, final double velOrigin, final CoordI2 pTarget_, final CoordD2 vTarget_) {
|
||||
final CoordD2 pOrigin = ImmutableCoordD2.create(pOrigin_);
|
||||
final CoordD2 pTarget = ImmutableCoordD2.create(pTarget_);
|
||||
final CoordD2 vTarget = ImmutableCoordD2.create(vTarget_);
|
||||
|
||||
final double a = (vTarget.getX() * vTarget.getX()) + (vTarget.getY() * vTarget.getY()) - (velOrigin * velOrigin);
|
||||
final double b = 2 * ((pTarget.getX() * vTarget.getX()) + (pTarget.getY() * vTarget.getY()) - (pOrigin.getX() * vTarget.getX()) - (pOrigin.getY() * vTarget.getY()));
|
||||
final double c = (pTarget.getX() * pTarget.getX()) + (pTarget.getY() * pTarget.getY()) + (pOrigin.getX() * pOrigin.getX()) + (pOrigin.getY() * pOrigin.getY()) - (2 * pOrigin.getX() * pTarget.getX()) - (2 * pOrigin.getY() * pTarget.getY());
|
||||
final double t0 = (-b + Math.sqrt((b * b) - (4.0 * a * c))) / (2.0 * a);
|
||||
final double t1 = (-b - Math.sqrt((b * b) - (4.0 * a * c))) / (2.0 * a);
|
||||
|
||||
final boolean t0Pass = !Double.isNaN(t0) && t0 > 0.0;
|
||||
final boolean t1Pass = !Double.isNaN(t1) && t1 > 0.0;
|
||||
double t;
|
||||
if (t0Pass && t1Pass) {
|
||||
t = Math.min(t0, t1);
|
||||
}
|
||||
else if (t0Pass) {
|
||||
t = t0;
|
||||
}
|
||||
else if (t1Pass) {
|
||||
t = t1;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ImmutableCoordI2.create(MutableCoordD2.create(vTarget).multiply(t).add(pTarget));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sector the point is located from source's view
|
||||
* @param point
|
||||
* @param source
|
||||
* @return Sector (Edge2)
|
||||
*/
|
||||
public static Edge2 getSector(final CoordI2 point, final HasPositionAndDimension2 source) {
|
||||
if (ImmutableCoordI2.zero().equals(source.getDimension())) {
|
||||
throw new IllegalArgumentException("Source's dimension is zero");
|
||||
}
|
||||
|
||||
final DistanceAndSector dists[] = new DistanceAndSector[4];
|
||||
final CoordI2 targetPos = ImmutableCoordI2.create(source.getPosition());
|
||||
|
||||
dists[0] = new DistanceAndSector(getDistance(point, targetPos), 0b1);
|
||||
dists[1] = new DistanceAndSector(getDistance(point, targetPos.add(source.getDimension().getX(), 0)), 0b10);
|
||||
dists[2] = new DistanceAndSector(getDistance(point, targetPos.add(0, source.getDimension().getY())), 0b100);
|
||||
dists[3] = new DistanceAndSector(getDistance(point, targetPos.add(source.getDimension().getX(), source.getDimension().getY())), 0b1000);
|
||||
|
||||
Arrays.sort(dists, (e0, e1) -> Double.compare(e0.dist, e1.dist));
|
||||
|
||||
final int sector = dists[0].sector | dists[1].sector;
|
||||
|
||||
if (sector == 0b11) {
|
||||
return Edge2.TOP;
|
||||
}
|
||||
else if (sector == 0b101) {
|
||||
return Edge2.LEFT;
|
||||
}
|
||||
else if (sector == 0b1010) {
|
||||
return Edge2.RIGHT;
|
||||
}
|
||||
else if (sector == 0b1100) {
|
||||
return Edge2.BOTTOM;
|
||||
}
|
||||
throw new RuntimeException("Unknown sector 0b" + Integer.toString(sector, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the pedal of altitude c of triangle A.B,C
|
||||
* @param a Point A
|
||||
* @param b Point B
|
||||
* @param c Point C
|
||||
* @return Pedal of altitude c
|
||||
*/
|
||||
public static CoordD2 getPedal(final CoordI2 a, final CoordI2 b, final CoordI2 c) {
|
||||
final double distA = getDistance(a, b);
|
||||
if (distA == 0.0) {
|
||||
return null;
|
||||
}
|
||||
final double distASquare = Math.pow(distA, 2.0);
|
||||
final double distB = getDistance(b, c);
|
||||
final double distBSquare = Math.pow(distB, 2.0);
|
||||
final double distC = getDistance(c, a);
|
||||
final double distCSquare = Math.pow(distC, 2.0);
|
||||
|
||||
final double altA = Math.sqrt((2.0 * ((distASquare * distBSquare) + (distBSquare * distCSquare) + (distCSquare * distASquare))) - (Math.pow(distA, 4.0) + Math.pow(distB, 4.0) + Math.pow(distC, 4.0))) / (2.0 * distA);
|
||||
if (Double.isNaN(altA)) {
|
||||
return null;
|
||||
}
|
||||
final double alpha = getVectorAngle(b.substract(a), c.substract(a));
|
||||
final double scaleA = (Math.sqrt(distCSquare - Math.pow(altA, 2.0)) / distA) * copySign(Math.PI / 2.0 - Math.abs(alpha));
|
||||
if (Double.isNaN(scaleA)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ImmutableCoordD2.create(
|
||||
(((double) b.getX() - (double) a.getX()) * scaleA) + a.getX(),
|
||||
(((double) b.getY() - (double) a.getY()) * scaleA) + a.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the pedal of altitude c of triangle A.B,C or the closest point on line segment a,b
|
||||
* <pre>
|
||||
* a-----p2-------b p1
|
||||
* | \ |
|
||||
* | \ |
|
||||
* | \|
|
||||
* c2 c1
|
||||
* </pre>
|
||||
*
|
||||
* Returns null if there is no pedal.
|
||||
*/
|
||||
public static CoordD2 getPedalOnLineSegmentAB(final CoordI2 a, final CoordI2 b, final CoordI2 c) {
|
||||
final CoordD2 pedal = getPedal(a, b, c);
|
||||
if (pedal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
double x = pedal.getX();
|
||||
final double minX = Math.min(a.getX(), b.getX());
|
||||
if (x < minX) {
|
||||
x = minX;
|
||||
}
|
||||
final double maxX = Math.max(a.getX(), b.getX());
|
||||
if (x > maxX) {
|
||||
x = maxX;
|
||||
}
|
||||
|
||||
double y = pedal.getY();
|
||||
final double minY = Math.min(a.getY(), b.getY());
|
||||
if (y < minY) {
|
||||
y = minY;
|
||||
}
|
||||
final double maxY = Math.max(a.getY(), b.getY());
|
||||
if (y > maxY) {
|
||||
y = maxY;
|
||||
}
|
||||
|
||||
return ImmutableCoordD2.create(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the closest point at area target from origin
|
||||
* <pre>
|
||||
* *-----*
|
||||
* | |
|
||||
* | x _______ origin
|
||||
* | |
|
||||
* *-----*
|
||||
* </pre>
|
||||
* @param origin
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
public static CoordI2 getClosestPoint(final CoordI2 origin, final HasPositionAndDimension2 target) {
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
CoordI2 result = null;
|
||||
|
||||
final List<CoordI2> vertices = getVertices(target);
|
||||
for (int i = 1; i < 4; i++) {
|
||||
final CoordD2 pedal = getPedalOnLineSegmentAB(vertices.get(i - 1), vertices.get(i), origin);
|
||||
if (pedal != null) {
|
||||
final CoordI2 pedalI = pedal.toImmutableCoordI2();
|
||||
final double distance = getDistance(origin, pedalI);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
result = pedalI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates waypoints to evade a collision between origin and target.
|
||||
* @param pOrigin Origin
|
||||
* @param velOrigin Origin velocity
|
||||
* @param vOrigin Origin velocity vector, origin course not necessarily the current velocity vector
|
||||
* @param pTarget Target
|
||||
* @param vTarget Target velocity vector
|
||||
* @param oTarget Collision offset on target (entityPos)
|
||||
* @param pInterception (Optional) Point of interception. Can be null, in this case it is calculated based on the velocity of source and the velocity vector of target.
|
||||
* @return Waypoints for evasion or null if there is no collision
|
||||
*/
|
||||
public static List<CoordI2> evade(final HasPositionAndDimension2 origin, final double velOrigin, final CoordD2 vOrigin, final HasPositionAndDimension2 target, final CoordD2 vTarget, final CoordI2 oTarget, final CoordI2 pInterception_) {
|
||||
final double THRESHOLD_NOT_MOVING = 0.2;
|
||||
|
||||
final CoordI2 pOrigin = ImmutableCoordI2.create(origin.getPosition()).add(ImmutableCoordI2.create(origin.getDimension()).divide(2));
|
||||
final CoordI2 pTarget = ImmutableCoordI2.create(target.getPosition()).add(ImmutableCoordI2.create(target.getDimension()).divide(2));
|
||||
final double velTarget = getDistance(vTarget);
|
||||
|
||||
if (velOrigin < THRESHOLD_NOT_MOVING) {
|
||||
// Corner case: Origin is not moving
|
||||
|
||||
final CoordD2 vOriginToTarget = ImmutableCoordD2.create(pTarget).substract(ImmutableCoordD2.create(pOrigin));
|
||||
CoordD2 vEvasion = MutableCoordD2
|
||||
.create(-vOriginToTarget.getY(), vOriginToTarget.getX()) // Rotate by PI/2
|
||||
.toVNorm()
|
||||
.multiply(getDistance(origin.getDimension()) * 2.0);
|
||||
vEvasion = vEvasion.setXY(Math.ceil(vEvasion.getX()), Math.ceil(vEvasion.getY()));
|
||||
return List.of(pOrigin.add(ImmutableCoordI2.create(vEvasion)));
|
||||
}
|
||||
|
||||
// Point of interception
|
||||
|
||||
CoordI2 pInterception;
|
||||
if (pInterception_ == null) {
|
||||
pInterception = intercept(pOrigin, velOrigin, pTarget, vTarget);
|
||||
if (pInterception == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
pInterception = pInterception_;
|
||||
}
|
||||
|
||||
// Create vertices
|
||||
|
||||
final CoordI2 boxDim = ImmutableCoordI2.create(
|
||||
Math.max(origin.getDimension().getX(), origin.getDimension().getY()) + 4,
|
||||
Math.max(origin.getDimension().getX(), origin.getDimension().getY()) + 4);
|
||||
final CoordI2 targetDimHalf = target.getDimension().divide(2);
|
||||
final Area2 box = new Area2(pInterception
|
||||
.add(targetDimHalf.substract(oTarget))
|
||||
.substract(targetDimHalf)
|
||||
.substract(boxDim),
|
||||
targetDimHalf
|
||||
.add(boxDim)
|
||||
.multiply(2));
|
||||
final List<CoordI2> vertices = getVertices(box);
|
||||
|
||||
// Rule out one or more vertices, because ...
|
||||
|
||||
if (velTarget >= THRESHOLD_NOT_MOVING && Math.abs(getVectorAngle(vOrigin, vTarget)) < Math.PI / 4.0) { // Velocity vectors are similar -> collision from behind
|
||||
// Remove both vertices in origin move direction
|
||||
|
||||
int removeVertexIdxLow = -1;
|
||||
int removeVertexIdxHigh = -1;
|
||||
|
||||
double aLow = Math.PI;
|
||||
double aHigh = 0.0;
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
final CoordI2 curVertex = vertices.get(i);
|
||||
|
||||
final CoordD2 direction = pInterception.substract(curVertex).toImmutableCoordD2();
|
||||
final double a = Math.abs(getVectorAngle(vOrigin, direction));
|
||||
if (removeVertexIdxLow == -1 || a < aLow) {
|
||||
aLow = a;
|
||||
removeVertexIdxLow = i;
|
||||
}
|
||||
if (removeVertexIdxHigh == -1 || a > aHigh) {
|
||||
aHigh = a;
|
||||
removeVertexIdxHigh = i;
|
||||
}
|
||||
}
|
||||
|
||||
vertices.set(removeVertexIdxLow, null);
|
||||
vertices.set(removeVertexIdxHigh, null);
|
||||
}
|
||||
else if (velTarget < velOrigin - 0.75) { // Origin speed is higher
|
||||
// Remove vertex farthest from origin move direction, but closest vertex will never be ruled out (-> primary)
|
||||
|
||||
int safeVertexIdx = 0;
|
||||
double vLen = -1.0;
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
final CoordI2 curVertex = vertices.get(i);
|
||||
final double curVLen = getDistance(pOrigin, curVertex);
|
||||
if (vLen < 0.0 || curVLen < vLen) {
|
||||
vLen = curVLen;
|
||||
safeVertexIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
int removeVertexIdx = 0;
|
||||
double aHigh = 0.0;
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
if (i == safeVertexIdx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final CoordI2 curVertex = vertices.get(i);
|
||||
final double a = Math.abs(getVectorAngle(curVertex.substract(pOrigin).toImmutableCoordD2(), vOrigin));
|
||||
if (a > aHigh) {
|
||||
aHigh = a;
|
||||
removeVertexIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
vertices.set(removeVertexIdx, null);
|
||||
}
|
||||
else {
|
||||
// Remove vertex closest to target move direction
|
||||
|
||||
int removeVertexIdx = -1;
|
||||
|
||||
double vLen = -1.0;
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
final CoordI2 curVertex = vertices.get(i);
|
||||
final CoordD2 curV = ImmutableCoordD2.create(pInterception.substract(curVertex)).add(vTarget);
|
||||
final double curVLen = getDistance(curV);
|
||||
if (vLen < 0.0 || curVLen < vLen) {
|
||||
vLen = curVLen;
|
||||
removeVertexIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
vertices.set(removeVertexIdx, null);
|
||||
}
|
||||
|
||||
// Get closest vertex -> primary
|
||||
|
||||
int primaryVertexIdx = -1;
|
||||
double closestDist = -1.0;
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
if (vertices.get(i) != null) {
|
||||
final double curDist = getDistance(vertices.get(i), pOrigin);
|
||||
if (closestDist < 0.0 || curDist < closestDist) {
|
||||
closestDist = curDist;
|
||||
primaryVertexIdx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Choose secondary vertex
|
||||
|
||||
final int candidate0 = (primaryVertexIdx + 1) % vertices.size();
|
||||
final int candidate1 = Math.floorMod(primaryVertexIdx - 1, vertices.size());
|
||||
int secondVertexIdx;
|
||||
if (vertices.get(candidate0) != null && vertices.get(candidate1) != null) {
|
||||
// Center vertex, choose secondary which is closer to origin velocity vector
|
||||
final double a0 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(candidate0).substract(pOrigin));
|
||||
final double a1 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(candidate1).substract(pOrigin));
|
||||
if (Math.abs(a0) < Math.abs(a1)) {
|
||||
secondVertexIdx = candidate0;
|
||||
}
|
||||
else {
|
||||
secondVertexIdx = candidate1;
|
||||
}
|
||||
}
|
||||
// Outer vertex, there is only one choice for the secondary one
|
||||
else if (vertices.get(candidate0) != null) {
|
||||
secondVertexIdx = candidate0;
|
||||
}
|
||||
else {
|
||||
secondVertexIdx = candidate1;
|
||||
}
|
||||
|
||||
// Check whether path from primary vertex to secondary intersects with origin path -> in this case skip the primary vertex except the closest vertex is in target velocity direction
|
||||
|
||||
// final double a0 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(primaryVertexIdx).substract(pOrigin));
|
||||
// final double a1 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(secondVertexIdx).substract(pOrigin));
|
||||
// final double intersecting = Math.signum(a0 * a1);
|
||||
// if (intersecting < 0.0) {
|
||||
// if (closestDist < getDistance(removedVertex, pOrigin)) {
|
||||
// primaryVertexIdx = secondVertexIdx;
|
||||
// secondVertexIdx = -1;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (secondVertexIdx == -1 || vertices.get(secondVertexIdx) == null) {
|
||||
return List.of(vertices.get(primaryVertexIdx));
|
||||
}
|
||||
return List.of(vertices.get(primaryVertexIdx), vertices.get(secondVertexIdx));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* x-----x
|
||||
* | |
|
||||
* | |
|
||||
* x-----x
|
||||
* </pre>
|
||||
* Returns the 4 vertices of a rectangle
|
||||
* @param obj Rectangle
|
||||
* @return 4 vertices ordered clock-wise
|
||||
*/
|
||||
public static List<CoordI2> getVertices(final HasPositionAndDimension2 obj) {
|
||||
final List<CoordI2> result = new ArrayList<>(4);
|
||||
final CoordI2 objPos = ImmutableCoordI2.create(obj.getPosition());
|
||||
result.add(objPos);
|
||||
result.add(objPos.add(obj.getDimension().getX(), 0));
|
||||
result.add(objPos.add(obj.getDimension()));
|
||||
result.add(objPos.add(0, obj.getDimension().getY()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* x-----x
|
||||
* | |
|
||||
* | |
|
||||
* x-----x
|
||||
* </pre>
|
||||
* Returns the 4 sides of a rectangle
|
||||
* @param obj Rectangle
|
||||
* @return 4 sides ordered clock-wise
|
||||
*/
|
||||
public static List<Area2> getSides(final HasPositionAndDimension2 obj) {
|
||||
final List<Area2> result = new ArrayList<>(4);
|
||||
final CoordI2 objPos = ImmutableCoordI2.create(obj.getPosition());
|
||||
result.add(new Area2(objPos, ImmutableCoordI2.create(obj.getDimension().getX(), 1)));
|
||||
result.add(new Area2(objPos.add(obj.getDimension().getX(), 0), ImmutableCoordI2.create(1, obj.getDimension().getY())));
|
||||
result.add(new Area2(objPos.add(1, obj.getDimension().getY()), ImmutableCoordI2.create(obj.getDimension().getX(), 1)));
|
||||
result.add(new Area2(objPos.add(0, 1), ImmutableCoordI2.create(1, obj.getDimension().getY())));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random point in a circle
|
||||
* @param center of the circle
|
||||
* @param radius of the circle
|
||||
* @return random point in the circle
|
||||
*/
|
||||
public static CoordI2 getRandomPointInCircle(final CoordI2 center, final int radius) {
|
||||
return getRandomPointInCircle(center, radius, ThreadLocalXorShift128Random.current());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a point in a circle
|
||||
* @param center of the circle
|
||||
* @param radius of the circle
|
||||
* @param random rng
|
||||
* @return random point in the circle
|
||||
*/
|
||||
public static CoordI2 getRandomPointInCircle(final CoordI2 center, final int radius, final Random random) {
|
||||
return MutableCoordD2
|
||||
.create(Math.cos(random.nextDouble() * Math.PI * 2.0 - Math.PI), Math.sin(random.nextDouble() * Math.PI * 2.0 - Math.PI))
|
||||
.multiply(radius)
|
||||
.toImmutableCoordI2()
|
||||
.add(center);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random point on a circle
|
||||
* @param center of the circle
|
||||
* @param radius of the circle
|
||||
* @return random point in the circle
|
||||
*/
|
||||
public static CoordI2 getRandomPointOnCircle(final CoordI2 center, final int radius) {
|
||||
return getRandomPointOnCircle(center, radius, ThreadLocalXorShift128Random.current());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random point on a circle
|
||||
* @param center of the circle
|
||||
* @param radius of the circle
|
||||
* @param random rng
|
||||
* @return random point in the circle
|
||||
*/
|
||||
public static CoordI2 getRandomPointOnCircle(final CoordI2 center, final int radius, final Random random) {
|
||||
final double angle = random.nextDouble() * Math.PI * 2.0 - Math.PI;
|
||||
return MutableCoordD2
|
||||
.create(Math.cos(angle), Math.sin(angle))
|
||||
.multiply(radius)
|
||||
.toImmutableCoordI2()
|
||||
.add(center);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reproducible point on a circle
|
||||
* @param center of the circle
|
||||
* @param radius of the circle
|
||||
* @param counter decides where the point is
|
||||
* @return random point in the circle
|
||||
*/
|
||||
public static CoordI2 getReproduciblePointOnCircle(final CoordI2 center, final int radius, final AtomicInteger counter) {
|
||||
final double angle = counter.incrementAndGet() / 3.0 % Math.PI * 2.0 - Math.PI;
|
||||
return MutableCoordD2
|
||||
.create(Math.cos(angle), Math.sin(angle))
|
||||
.multiply(radius)
|
||||
.toImmutableCoordI2()
|
||||
.add(center);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates edge required at c1 to block the path from c0 to c1. Swap both parameters to get edge required at c0 to block the path from c1 to c0.<br/>
|
||||
* <br/>
|
||||
* c0==> |c1<br/>
|
||||
* c0| <==c1<br/>
|
||||
*
|
||||
* @param c0 Position
|
||||
* @param c1 Target
|
||||
* @return Edges that are required as bit mask
|
||||
*/
|
||||
public static int calcEdgeRequirement(final CoordI2 c0, final CoordI2 c1) {
|
||||
final int dx = c0.getX() - c1.getX();
|
||||
final int dy = c0.getY() - c1.getY();
|
||||
|
||||
int edgeReq = 0;
|
||||
if (dx < 0 && dy < 0) {
|
||||
edgeReq = Edge2.LEFT.getBit() | Edge2.TOP.getBit();
|
||||
}
|
||||
else if (dx < 0 && dy == 0) {
|
||||
edgeReq = Edge2.LEFT.getBit();
|
||||
}
|
||||
else if (dx < 0 && dy > 0) {
|
||||
edgeReq = Edge2.LEFT.getBit() | Edge2.BOTTOM.getBit();
|
||||
}
|
||||
else if (dx == 0 && dy < 0) {
|
||||
edgeReq = Edge2.TOP.getBit();
|
||||
}
|
||||
else if (dx == 0 && dy > 0) {
|
||||
edgeReq = Edge2.BOTTOM.getBit();
|
||||
}
|
||||
else if (dx > 0 && dy < 0) {
|
||||
edgeReq = Edge2.RIGHT.getBit() | Edge2.TOP.getBit();
|
||||
}
|
||||
else if (dx > 0 && dy == 0) {
|
||||
edgeReq = Edge2.RIGHT.getBit();
|
||||
}
|
||||
else if (dx > 0 && dy > 0) {
|
||||
edgeReq = Edge2.RIGHT.getBit() | Edge2.BOTTOM.getBit();
|
||||
}
|
||||
|
||||
return edgeReq;
|
||||
}
|
||||
|
||||
private static class DistanceAndSector {
|
||||
|
||||
double dist;
|
||||
|
||||
int sector;
|
||||
|
||||
public DistanceAndSector(final double dist, final int sector) {
|
||||
this.dist = dist;
|
||||
this.sector = sector;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||
|
||||
/**
|
||||
* Implements a grid index that can query objects with dimension 1 very fast at the expense of memory consumption. Use QuadTree instead for very large grids or 2 dimensional objects.
|
||||
*/
|
||||
public final class GridIndex2D<T extends HasPosition2> implements Iterable<T>, AutoCloseable {
|
||||
|
||||
private final List<List<List<T>>> index = new ArrayList<>();
|
||||
|
||||
private long modCnt;
|
||||
|
||||
public GridIndex2D() {
|
||||
|
||||
}
|
||||
|
||||
public void add(final T obj) {
|
||||
this.ensureBounds(obj.getPosition());
|
||||
|
||||
this.index.get(obj.getPosition().getX()).get(obj.getPosition().getY()).add(obj);
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
public void addAll(final Iterable<T> objs) {
|
||||
objs.forEach(this::add);
|
||||
}
|
||||
|
||||
public void addAll(final Iterator<T> it) {
|
||||
it.forEachRemaining(this::add);
|
||||
}
|
||||
|
||||
public void remove(final T obj) {
|
||||
if (!this.checkBounds(obj.getPosition())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeInternal(obj);
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
public List<T> get(final HasPosition2 hasPosition2) {
|
||||
final CoordI2 coord = hasPosition2.getPosition();
|
||||
if (!this.checkBounds(coord)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return this.index.get(coord.getX()).get(coord.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears but keeps internal state allocated
|
||||
*/
|
||||
public void clear() {
|
||||
this.index.forEach(list -> list.forEach(List::clear));
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears and frees internal state
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
this.index.clear();
|
||||
this.modCnt = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<>() {
|
||||
|
||||
int x;
|
||||
|
||||
int y;
|
||||
|
||||
int i;
|
||||
|
||||
long modCnt;
|
||||
|
||||
{
|
||||
this.modCnt = GridIndex2D.this.modCnt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.x < GridIndex2D.this.index.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (this.modCnt != GridIndex2D.this.modCnt) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
|
||||
final T result = GridIndex2D.this.index.get(this.x).get(this.y).get(this.i);
|
||||
this.forward();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
GridIndex2D.this.removeInternal(GridIndex2D.this.index.get(this.x).get(this.y).get(this.i));
|
||||
if (!this.validate()) {
|
||||
this.forward();
|
||||
}
|
||||
}
|
||||
|
||||
private void forward() {
|
||||
while (this.hasNext()) {
|
||||
if (++this.i >= GridIndex2D.this.index.get(this.x).get(this.y).size()) {
|
||||
this.i = 0;
|
||||
if (++this.y >= GridIndex2D.this.index.get(this.x).size()) {
|
||||
this.y = 0;
|
||||
this.x++;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.validate()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validate() {
|
||||
return this.x < GridIndex2D.this.index.size() && this.y < GridIndex2D.this.index.get(this.x).size() && this.i < GridIndex2D.this.index.get(this.x).get(this.y).size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(final Consumer<? super T> action) {
|
||||
for (int x = 0; x < this.index.size(); x++) {
|
||||
for (int y = 0; y < this.index.get(x).size(); y++) {
|
||||
for (int i = 0; i < this.index.get(x).get(y).size(); i++) {
|
||||
action.accept(this.index.get(x).get(y).get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void forEach(final BiConsumer<CoordI2, List<T>> action) {
|
||||
final CoordI2 coord = MutableCoordI2.create();
|
||||
|
||||
for (int x = 0; x < this.index.size(); x++) {
|
||||
for (int y = 0; y < this.index.get(x).size(); y++) {
|
||||
if (this.index.get(x).get(y).size() > 0) {
|
||||
coord.setXY(x, y);
|
||||
action.accept(coord, this.index.get(x).get(y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureBounds(final CoordI2 coord) {
|
||||
int delta = this.index.size() - coord.getX();
|
||||
if (delta < 1) {
|
||||
for (delta = -delta; delta >= 0; delta--) {
|
||||
this.index.add(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
delta = this.index.get(coord.getX()).size() - coord.getY();
|
||||
if (delta < 1) {
|
||||
for (delta = -delta; delta >= 0; delta--) {
|
||||
this.index.get(coord.getX()).add(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkBounds(final CoordI2 coord) {
|
||||
return coord.getX() >= 0 && coord.getY() >= 0 && coord.getX() < this.index.size() && coord.getY() < this.index.get(coord.getX()).size();
|
||||
}
|
||||
|
||||
private void removeInternal(final T obj) {
|
||||
this.index.get(obj.getPosition().getX()).get(obj.getPosition().getY()).remove(obj);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import de.extio.game_engine.spatial2.WorldUtils2;
|
||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||
import de.extio.game_engine.spatial2.model.SpatialIndex2Capable;
|
||||
|
||||
/**
|
||||
* LinearSearchIndex2D doing linear search only. Use it only for very special use cases. Implementation is mainly intended for performance comparison.
|
||||
*/
|
||||
public final class LinearSearchIndex2D<T extends SpatialIndex2Capable> implements SpatialIndex2D<T> {
|
||||
|
||||
private static final int RESULT_DIM_START = 8;
|
||||
|
||||
private static final int RESULT_DIM_MAX = 96;
|
||||
|
||||
private List<T> objects = new ArrayList<>();
|
||||
|
||||
private int resultDim = RESULT_DIM_START;
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return this.objects.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(final T obj) {
|
||||
this.objects.add(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final T obj) {
|
||||
this.objects.remove(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move(final T obj, final CoordI2 oldPosition, final CoordI2 oldDimension) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> find(final CoordI2 position, final CoordI2 dimension, final Predicate<T> filter) {
|
||||
List<T> result = null;
|
||||
|
||||
for (int i = 0; i < this.objects.size(); i++) {
|
||||
final T obj = this.objects.get(i);
|
||||
|
||||
if (WorldUtils2.intersects(position, dimension, obj.getPosition(), obj.getDimension())) {
|
||||
if (filter == null || filter.test(obj)) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<>(dimension.equals(ImmutableCoordI2.one()) ? RESULT_DIM_START : this.resultDim);
|
||||
}
|
||||
result.add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (result.size() > this.resultDim) {
|
||||
this.resultDim = Math.min(result.size(), RESULT_DIM_MAX);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T findFirstAt(final CoordI2 position, final Predicate<T> filter) {
|
||||
for (int i = 0; i < this.objects.size(); i++) {
|
||||
final T obj = this.objects.get(i);
|
||||
|
||||
if (WorldUtils2.intersects(position, ImmutableCoordI2.one(), obj.getPosition(), obj.getDimension())) {
|
||||
if (filter == null || filter.test(obj)) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAt(final CoordI2 position, final Consumer<T> consumer, final Predicate<T> filter) {
|
||||
for (int i = 0; i < this.objects.size(); i++) {
|
||||
final T obj = this.objects.get(i);
|
||||
|
||||
if (WorldUtils2.intersects(position, ImmutableCoordI2.one(), obj.getPosition(), obj.getDimension())) {
|
||||
if (filter == null || filter.test(obj)) {
|
||||
consumer.accept(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.objects = new ArrayList<>();
|
||||
this.resultDim = RESULT_DIM_START;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.objects.clear();
|
||||
}
|
||||
|
||||
}
|
||||
293
src/main/java/de/extio/game_engine/spatial2/index/Matrix2.java
Normal file
293
src/main/java/de/extio/game_engine/spatial2/index/Matrix2.java
Normal file
@@ -0,0 +1,293 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.index;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||
import de.extio.game_engine.spatial2.model.Edge2;
|
||||
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||
|
||||
/**
|
||||
* Implements a 2D matrix
|
||||
*/
|
||||
public final class Matrix2<T> implements Iterable<T> {
|
||||
|
||||
private List<List<T>> matrix = new ArrayList<>();
|
||||
|
||||
private final CoordI2 dimension = MutableCoordI2.create();
|
||||
|
||||
private long modCnt;
|
||||
|
||||
public Matrix2() {
|
||||
|
||||
}
|
||||
|
||||
public Matrix2(final int x, final int y) {
|
||||
this.allocate(Math.max(0, x - 1), Math.max(0, y - 1));
|
||||
}
|
||||
|
||||
public Matrix2(final Matrix2<T> other) {
|
||||
this.dimension.setX(other.matrix.size());
|
||||
|
||||
this.matrix = new ArrayList<>(other.matrix);
|
||||
for (int x = 0; x < this.matrix.size(); x++) {
|
||||
final List<T> column = other.matrix.get(x);
|
||||
this.matrix.set(x, new ArrayList<>(column));
|
||||
|
||||
if (column.size() > this.dimension.getY()) {
|
||||
this.dimension.setY(column.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears and frees internal state
|
||||
*/
|
||||
public void close() {
|
||||
this.matrix.clear();
|
||||
this.dimension.setXY(0, 0);
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears but keeps internal state allocated
|
||||
*/
|
||||
public void clear() {
|
||||
this.matrix.forEach(col -> col.replaceAll((t) -> null));
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
public void put(final HasPosition2 hasPosition, final T obj) {
|
||||
this.put(hasPosition.getPosition().getX(), hasPosition.getPosition().getY(), obj);
|
||||
}
|
||||
|
||||
public void put(final int x, final int y, final T obj) {
|
||||
if (x < 0 || y < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.allocate(x, y);
|
||||
|
||||
this.matrix.get(x).set(y, obj);
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
public void remove(final HasPosition2 hasPosition) {
|
||||
this.remove(hasPosition.getPosition().getX(), hasPosition.getPosition().getY());
|
||||
}
|
||||
|
||||
public void remove(final int x, final int y) {
|
||||
if (!this.checkBounds(x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.matrix.get(x).set(y, null);
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
for (int x = 0; x < this.matrix.size(); x++) {
|
||||
for (int y = 0; y < this.matrix.get(x).size(); y++) {
|
||||
this.matrix.get(x).set(y, null);
|
||||
}
|
||||
}
|
||||
this.modCnt++;
|
||||
}
|
||||
|
||||
public T get(final HasPosition2 hasPosition) {
|
||||
return this.get(hasPosition.getPosition().getX(), hasPosition.getPosition().getY());
|
||||
}
|
||||
|
||||
public T get(final int x, final int y) {
|
||||
if (!this.checkBounds(x, y)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.matrix.get(x).get(y);
|
||||
}
|
||||
|
||||
public boolean contains(final HasPosition2 hasPosition) {
|
||||
return this.contains(hasPosition.getPosition().getX(), hasPosition.getPosition().getY());
|
||||
}
|
||||
|
||||
public boolean contains(final int x, final int y) {
|
||||
return this.checkBounds(x, y) && this.matrix.get(x).get(y) != null;
|
||||
}
|
||||
|
||||
// x and y are indices, not the dimension or size!
|
||||
public void allocate(final int x, final int y) {
|
||||
if (x < 0 || y < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int delta = this.matrix.size() - x;
|
||||
if (delta < 1) {
|
||||
for (delta = -delta; delta >= 0; delta--) {
|
||||
final List<T> row = new ArrayList<>(y + 1);
|
||||
for (int i = 0; i <= y; i++) {
|
||||
row.add(null);
|
||||
}
|
||||
this.matrix.add(row);
|
||||
}
|
||||
|
||||
if (x >= this.dimension.getX()) {
|
||||
this.dimension.setX(x + 1);
|
||||
}
|
||||
if (y >= this.dimension.getY()) {
|
||||
this.dimension.setY(y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
delta = this.matrix.get(x).size() - y;
|
||||
if (delta < 1) {
|
||||
for (delta = -delta; delta >= 0; delta--) {
|
||||
this.matrix.get(x).add(null);
|
||||
}
|
||||
|
||||
if (y >= this.dimension.getY()) {
|
||||
this.dimension.setY(y + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void rotate(final Edge2 direction) {
|
||||
final List<List<T>> original = new ArrayList<>(this.matrix);
|
||||
final CoordI2 origDim = ImmutableCoordI2.create(this.dimension);
|
||||
|
||||
this.close();
|
||||
this.allocate(origDim.getY() - 1, origDim.getX() - 1);
|
||||
|
||||
final CoordI2 posSrc = MutableCoordI2.create();
|
||||
for (int x = 0; x < origDim.getY(); x++) {
|
||||
for (int y = 0; y < origDim.getX(); y++) {
|
||||
if (direction == Edge2.LEFT) {
|
||||
posSrc.setXY(origDim.getX() - y - 1, x);
|
||||
}
|
||||
else {
|
||||
posSrc.setXY(y, origDim.getY() - x - 1);
|
||||
}
|
||||
|
||||
final List<T> originalObjs = original.get(posSrc.getX());
|
||||
if (originalObjs.size() > posSrc.getY()) {
|
||||
this.matrix.get(x).set(y, originalObjs.get(posSrc.getY()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<>() {
|
||||
|
||||
private int x = 0;
|
||||
|
||||
private int y = 0;
|
||||
|
||||
private T obj = null;
|
||||
|
||||
private long modCnt;
|
||||
|
||||
{
|
||||
this.modCnt = Matrix2.this.modCnt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.findNext(false) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
try {
|
||||
if (this.modCnt != Matrix2.this.modCnt) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
|
||||
return this.findNext(true);
|
||||
}
|
||||
finally {
|
||||
this.obj = null;
|
||||
}
|
||||
}
|
||||
|
||||
private T findNext(final boolean nseExc) {
|
||||
if (this.obj != null) {
|
||||
return this.obj;
|
||||
}
|
||||
|
||||
int x = this.x;
|
||||
int y = this.y;
|
||||
for (; x < Matrix2.this.matrix.size(); x++) {
|
||||
for (; y < Matrix2.this.matrix.get(x).size(); y++) {
|
||||
this.obj = Matrix2.this.matrix.get(x).get(y);
|
||||
if (this.obj != null) {
|
||||
this.x = x;
|
||||
this.y = y + 1;
|
||||
if (this.y >= Matrix2.this.matrix.get(x).size()) {
|
||||
this.y = 0;
|
||||
this.x++;
|
||||
}
|
||||
|
||||
return this.obj;
|
||||
}
|
||||
}
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if (nseExc) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(final Consumer<? super T> action) {
|
||||
for (int x = 0; x < this.matrix.size(); x++) {
|
||||
for (int y = 0; y < this.matrix.get(x).size(); y++) {
|
||||
final T obj = this.matrix.get(x).get(y);
|
||||
if (obj != null) {
|
||||
action.accept(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void forEach(final BiConsumer<CoordI2, T> consumer) {
|
||||
final CoordI2 coord = MutableCoordI2.create();
|
||||
|
||||
for (int x = 0; x < this.matrix.size(); x++) {
|
||||
for (int y = 0; y < this.matrix.get(x).size(); y++) {
|
||||
final T obj = this.matrix.get(x).get(y);
|
||||
if (obj != null) {
|
||||
coord.setXY(x, y);
|
||||
consumer.accept(coord, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CoordI2 getDimension() {
|
||||
return ImmutableCoordI2.create(this.dimension);
|
||||
}
|
||||
|
||||
private boolean checkBounds(final int x, final int y) {
|
||||
return x >= 0 && y >= 0 && x < this.matrix.size() && y < this.matrix.get(x).size();
|
||||
}
|
||||
}
|
||||
733
src/main/java/de/extio/game_engine/spatial2/index/QuadTree.java
Normal file
733
src/main/java/de/extio/game_engine/spatial2/index/QuadTree.java
Normal file
@@ -0,0 +1,733 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.index;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||
import de.extio.game_engine.spatial2.model.RectI2;
|
||||
import de.extio.game_engine.spatial2.model.SpatialIndex2Capable;
|
||||
|
||||
/**
|
||||
* This is the preferred SpatialIndex2D implementation to index bigger amounts of spatial data and query them efficiently.
|
||||
* You can consider using GridIndex2D instead for small grids with one-dimensional objects only.
|
||||
* A quadtree is a tree data structure in which each internal node has exactly four children. Quadtrees are the two-dimensional analog of octrees and are most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions.
|
||||
*/
|
||||
public final class QuadTree<T extends SpatialIndex2Capable> implements SpatialIndex2D<T> {
|
||||
|
||||
private static final int ORDER = 4;
|
||||
|
||||
private static final int DEPTH_START = 8;
|
||||
|
||||
private static final int RESULT_DIM_START = 6;
|
||||
|
||||
private static final int RESULT_DIM_MAX = 96;
|
||||
|
||||
private Node<T> root;
|
||||
|
||||
private int depth;
|
||||
|
||||
private int[] traverseStack;
|
||||
|
||||
private Node<T>[] nodesCache;
|
||||
|
||||
private int resultDim = RESULT_DIM_START;
|
||||
|
||||
private final RectI2 rectCached = new RectI2(MutableCoordI2.create(), MutableCoordI2.create());
|
||||
|
||||
private final AtomicReference<List<T>> resultHolder = new AtomicReference<>();
|
||||
|
||||
public QuadTree() {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void add(final T obj) {
|
||||
final RectI2 rect = this.toRectI2(obj.getPosition(), obj.getDimension());
|
||||
this.expandTree(rect);
|
||||
|
||||
Node<T> node = this.root;
|
||||
traversal: while (true) {
|
||||
if (node.branches == null) {
|
||||
// Add leaf to fresh branch
|
||||
this.addLeave(node, obj);
|
||||
if (node.leaves.size() < ORDER) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Overflow -> Split if not already at lowest level
|
||||
if (node.createBranches()) {
|
||||
final List<T> toSplit = node.leaves;
|
||||
if (toSplit != null) {
|
||||
node.leaves = null;
|
||||
|
||||
splitLeaves: for (int l = 0; l < toSplit.size(); l++) {
|
||||
final T leaf = toSplit.get(l);
|
||||
|
||||
for (int b = 0; b < ORDER; b++) {
|
||||
if (this.contains(node.branches[b], leaf)) {
|
||||
this.addLeave(node.branches[b], leaf);
|
||||
continue splitLeaves;
|
||||
}
|
||||
}
|
||||
|
||||
this.addLeave(node, leaf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// Traverse
|
||||
for (int i = 0; i < ORDER; i++) {
|
||||
if (this.contains(node.branches[i], rect)) {
|
||||
node = node.branches[i];
|
||||
continue traversal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Node does not fit into branches -> add to current level
|
||||
this.addLeave(node, obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void expandTree(final RectI2 rect) {
|
||||
if (this.root == null) {
|
||||
final int side = 1 << DEPTH_START;
|
||||
this.root = new Node<>(rect.getC0().getX() - side, rect.getC0().getY() - side, side << 1);
|
||||
this.root.createBranches();
|
||||
this.recalcDepth();
|
||||
}
|
||||
|
||||
for (int i = 0; !this.contains(this.root, rect); i++) {
|
||||
final RectI2 expansion = new RectI2(MutableCoordI2.create(), MutableCoordI2.create());
|
||||
final int[] branchesIntersect = new int[4];
|
||||
int intersects = 0;
|
||||
int containsBranch = -1;
|
||||
|
||||
expansion.getC0().setXY(this.root.xMin, this.root.yMin);
|
||||
expansion.getC1().setXY(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
if (this.contains(expansion, rect)) {
|
||||
containsBranch = 0;
|
||||
}
|
||||
else {
|
||||
if (this.intersects(expansion, rect)) {
|
||||
branchesIntersect[intersects++] = 0;
|
||||
}
|
||||
expansion.getC0().setXY(Integer.MIN_VALUE, this.root.yMin);
|
||||
expansion.getC1().setXY(this.root.xMax, Integer.MAX_VALUE);
|
||||
if (this.contains(expansion, rect)) {
|
||||
containsBranch = 2;
|
||||
}
|
||||
else {
|
||||
if (this.intersects(expansion, rect)) {
|
||||
branchesIntersect[intersects++] = 2;
|
||||
}
|
||||
expansion.getC0().setXY(Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||
expansion.getC1().setXY(this.root.xMax, this.root.yMax);
|
||||
if (this.contains(expansion, rect)) {
|
||||
containsBranch = 3;
|
||||
}
|
||||
else {
|
||||
if (this.intersects(expansion, rect)) {
|
||||
branchesIntersect[intersects++] = 3;
|
||||
}
|
||||
expansion.getC0().setXY(this.root.xMin, Integer.MIN_VALUE);
|
||||
expansion.getC1().setXY(Integer.MAX_VALUE, this.root.yMax);
|
||||
if (this.contains(expansion, rect)) {
|
||||
containsBranch = 1;
|
||||
}
|
||||
else if (this.intersects(expansion, rect)) {
|
||||
branchesIntersect[intersects++] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node<T> rootNew = null;
|
||||
final int branch = containsBranch != -1 ? containsBranch : branchesIntersect[i % intersects];
|
||||
final int size = this.root.xMax - this.root.xMin + 1;
|
||||
switch (branch) {
|
||||
case 0:
|
||||
rootNew = new Node<>(this.root.xMin, this.root.yMin, size << 1);
|
||||
break;
|
||||
case 1:
|
||||
rootNew = new Node<>(this.root.xMin, this.root.yMin - size, size << 1);
|
||||
break;
|
||||
case 2:
|
||||
rootNew = new Node<>(this.root.xMin - size, this.root.yMin, size << 1);
|
||||
break;
|
||||
case 3:
|
||||
rootNew = new Node<>(this.root.xMin - size, this.root.yMin - size, size << 1);
|
||||
break;
|
||||
}
|
||||
|
||||
rootNew.createBranches();
|
||||
rootNew.branches[branch] = this.root;
|
||||
rootNew.branches[branch].parent = rootNew;
|
||||
|
||||
this.root = rootNew;
|
||||
this.recalcDepth();
|
||||
}
|
||||
|
||||
// Alternative approach, center around the origin. In principle very elegant but the tree always expands to all directions whereas the data may only expand to a specific direction
|
||||
// while (!this.contains(this.root, rect)) {
|
||||
// final int side = this.root.xMax - this.root.xMin + 1;
|
||||
// final Node<T> rootNew = new Node<>(this.root.xMin - (side >> 1), this.root.yMin - (side >> 1), side << 1);
|
||||
// rootNew.createBranches();
|
||||
// for (int i = 0; i < ORDER; i++) {
|
||||
// rootNew.branches[i].createBranches();
|
||||
// }
|
||||
// rootNew.branches[0].branches[3] = this.root.branches[0];
|
||||
// rootNew.branches[0].branches[3].parent = rootNew.branches[0];
|
||||
// rootNew.branches[1].branches[2] = this.root.branches[1];
|
||||
// rootNew.branches[1].branches[2].parent = rootNew.branches[1];
|
||||
// rootNew.branches[2].branches[1] = this.root.branches[2];
|
||||
// rootNew.branches[2].branches[1].parent = rootNew.branches[2];
|
||||
// rootNew.branches[3].branches[0] = this.root.branches[3];
|
||||
// rootNew.branches[3].branches[0].parent = rootNew.branches[3];
|
||||
// rootNew.leaves = this.root.leaves;
|
||||
//
|
||||
// this.root = rootNew;
|
||||
// this.recalcDepth();
|
||||
// }
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void recalcDepth() {
|
||||
this.depth = Integer.numberOfTrailingZeros(Integer.highestOneBit(this.root.xMax - this.root.xMin)) + 1;
|
||||
this.traverseStack = new int[this.depth];
|
||||
this.nodesCache = new Node[this.depth];
|
||||
}
|
||||
|
||||
private void addLeave(final Node<T> node, final T obj) {
|
||||
if (node.leaves == null) {
|
||||
node.leaves = new ArrayList<>();
|
||||
}
|
||||
node.leaves.add(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final T obj) {
|
||||
final RectI2 rect = this.toRectI2Bounded(obj.getPosition(), obj.getDimension());
|
||||
if (rect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.remove(obj, rect);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void remove(final T obj, final RectI2 rect) {
|
||||
Node<T> node = this.root;
|
||||
traversal: while (true) {
|
||||
if (node.leaves != null) {
|
||||
node.leaves.remove(obj);
|
||||
if (node.leaves.isEmpty()) {
|
||||
node.leaves = null;
|
||||
}
|
||||
|
||||
this.shrink(node);
|
||||
}
|
||||
|
||||
if (node.branches != null) {
|
||||
for (int i = 0; i < ORDER; i++) {
|
||||
if (this.contains(node.branches[i], rect)) {
|
||||
node = node.branches[i];
|
||||
continue traversal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void shrink(final Node<T> node_) {
|
||||
Node<T> node = node_;
|
||||
while (node.parent != null) {
|
||||
for (int i = 0; i < ORDER; i++) {
|
||||
if (node.parent.branches[i].leaves != null || node.parent.branches[i].branches != null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
node.parent.branches = null;
|
||||
node = node.parent;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void move(final T obj, final CoordI2 oldPosition, final CoordI2 oldDimension) {
|
||||
final RectI2 rect = this.toRectI2Bounded(oldPosition, oldDimension);
|
||||
if (rect != null) {
|
||||
int nodes = 0;
|
||||
Node<T> node = this.root;
|
||||
traversal: while (true) {
|
||||
if (node.leaves != null) {
|
||||
this.nodesCache[nodes++] = node;
|
||||
}
|
||||
|
||||
if (node.branches != null) {
|
||||
for (int i = 0; i < ORDER; i++) {
|
||||
if (this.contains(node.branches[i], rect)) {
|
||||
node = node.branches[i];
|
||||
continue traversal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.contains(node, obj)) {
|
||||
while (nodes-- > 0) {
|
||||
final Node<T> curNode = this.nodesCache[nodes];
|
||||
if (curNode.leaves != null) {
|
||||
curNode.leaves.remove(obj);
|
||||
if (curNode.leaves.isEmpty()) {
|
||||
curNode.leaves = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.shrink(node);
|
||||
|
||||
this.add(obj);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> find(final CoordI2 position, final CoordI2 dimension, final Predicate<T> filter) {
|
||||
if (dimension.equals(ImmutableCoordI2.one())) {
|
||||
return this.findDim1(position, filter);
|
||||
}
|
||||
|
||||
final RectI2 rect = this.toRectI2Bounded(position, dimension);
|
||||
if (rect == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return this.findDimN(rect, filter);
|
||||
}
|
||||
|
||||
private List<T> findDim1(final CoordI2 position, final Predicate<T> filter) {
|
||||
this.resultHolder.set(null);
|
||||
|
||||
this.executeAt(position, obj -> {
|
||||
if (this.resultHolder.get() == null) {
|
||||
this.resultHolder.set(new ArrayList<>(RESULT_DIM_START));
|
||||
}
|
||||
this.resultHolder.get().add(obj);
|
||||
}, filter);
|
||||
|
||||
final List<T> result = this.resultHolder.get();
|
||||
if (result == null) {
|
||||
return List.of();
|
||||
}
|
||||
this.resultHolder.set(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<T> findDimN(final RectI2 rect, final Predicate<T> filter) {
|
||||
List<T> result = null;
|
||||
|
||||
int level = 0;
|
||||
int branch = 0;
|
||||
Node<T> node = this.root;
|
||||
|
||||
traversal: while (true) {
|
||||
if (node.branches != null) {
|
||||
for (int b = branch; b < ORDER; b++) {
|
||||
if (this.intersects(node.branches[b], rect)) {
|
||||
node = node.branches[b];
|
||||
this.traverseStack[level++] = b + 1;
|
||||
branch = 0;
|
||||
|
||||
continue traversal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.leaves != null) {
|
||||
for (int i = 0; i < node.leaves.size(); i++) {
|
||||
final T obj = node.leaves.get(i);
|
||||
if (this.intersects(obj, rect) && (filter == null || filter.test((T) obj))) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<>(this.resultDim);
|
||||
}
|
||||
result.add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.parent == null) {
|
||||
break;
|
||||
}
|
||||
node = node.parent;
|
||||
branch = this.traverseStack[--level];
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (result.size() > this.resultDim) {
|
||||
this.resultDim = Math.min(result.size(), RESULT_DIM_MAX);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T findFirstAt(final CoordI2 position, final Predicate<T> filter) {
|
||||
final RectI2 rect = this.toRectI2Bounded(position, ImmutableCoordI2.one());
|
||||
if (rect == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Node<T> node = this.root;
|
||||
traversal: while (true) {
|
||||
if (node.leaves != null) {
|
||||
for (int i = 0; i < node.leaves.size(); i++) {
|
||||
final T obj = node.leaves.get(i);
|
||||
if (this.contains(obj, rect) && (filter == null || filter.test((T) obj))) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.branches != null) {
|
||||
for (int i = 0; i < ORDER; i++) {
|
||||
if (this.contains(node.branches[i], rect)) {
|
||||
node = node.branches[i];
|
||||
continue traversal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void executeAt(final CoordI2 position, final Consumer<T> consumer, final Predicate<T> filter) {
|
||||
final RectI2 rect = this.toRectI2Bounded(position, ImmutableCoordI2.one());
|
||||
if (rect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Node<T> node = this.root;
|
||||
traversal: while (true) {
|
||||
if (node.leaves != null) {
|
||||
for (int i = 0; i < node.leaves.size(); i++) {
|
||||
final T obj = node.leaves.get(i);
|
||||
if (this.contains(obj, rect) && (filter == null || filter.test((T) obj))) {
|
||||
consumer.accept(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.branches != null) {
|
||||
for (int i = 0; i < ORDER; i++) {
|
||||
if (this.contains(node.branches[i], rect)) {
|
||||
node = node.branches[i];
|
||||
continue traversal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<>() {
|
||||
|
||||
private final Deque<Integer> branchStack = new ArrayDeque<>(QuadTree.this.depth);
|
||||
|
||||
private T next;
|
||||
|
||||
private Node<T> node = QuadTree.this.root;
|
||||
|
||||
private int branch;
|
||||
|
||||
private int leaf;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (this.next != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
traversal: while (true) {
|
||||
if (this.node.branches != null) {
|
||||
if (this.branch < ORDER) {
|
||||
this.node = this.node.branches[this.branch];
|
||||
this.branchStack.addFirst(this.branch + 1);
|
||||
this.branch = 0;
|
||||
continue traversal;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.node.leaves != null) {
|
||||
if (this.leaf < this.node.leaves.size()) {
|
||||
final T obj = this.node.leaves.get(this.leaf++);
|
||||
this.next = obj;
|
||||
break traversal;
|
||||
}
|
||||
this.leaf = 0;
|
||||
}
|
||||
|
||||
if (this.node.parent == null) {
|
||||
break;
|
||||
}
|
||||
this.node = this.node.parent;
|
||||
this.branch = this.branchStack.removeFirst();
|
||||
}
|
||||
|
||||
return this.next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
try {
|
||||
return this.next;
|
||||
}
|
||||
finally {
|
||||
this.next = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (this.leaf > 0 && this.leaf <= this.node.leaves.size()) {
|
||||
this.node.leaves.remove(this.leaf-- - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(final Consumer<? super T> action) {
|
||||
this.traverseAll(node -> {
|
||||
if (node.leaves != null) {
|
||||
for (int i = 0; i < node.leaves.size(); i++) {
|
||||
final T obj = node.leaves.get(i);
|
||||
action.accept(obj);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.resultDim = RESULT_DIM_START;
|
||||
this.traverseStack = null;
|
||||
this.nodesCache = null;
|
||||
this.root = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.traverseAll(node -> {
|
||||
if (node.leaves != null) {
|
||||
node.leaves = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void traverseAll(final Consumer<Node<T>> consumer) {
|
||||
if (this.root == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int level = 0;
|
||||
int branch = 0;
|
||||
Node<T> node = this.root;
|
||||
|
||||
traversal: while (true) {
|
||||
if (node.branches != null && branch < ORDER) {
|
||||
node = node.branches[branch];
|
||||
this.traverseStack[level++] = branch + 1;
|
||||
branch = 0;
|
||||
|
||||
continue traversal;
|
||||
}
|
||||
|
||||
consumer.accept(node);
|
||||
|
||||
if (node.parent == null) {
|
||||
break;
|
||||
}
|
||||
node = node.parent;
|
||||
branch = this.traverseStack[--level];
|
||||
}
|
||||
}
|
||||
|
||||
private RectI2 toRectI2(final CoordI2 position, final CoordI2 dimension) {
|
||||
this.rectCached.getC0().setXY(position.getX(), position.getY());
|
||||
this.rectCached.getC1().setXY(position.getX() + dimension.getX() - 1, position.getY() + dimension.getY() - 1);
|
||||
|
||||
return this.rectCached;
|
||||
}
|
||||
|
||||
private RectI2 toRectI2Bounded(final CoordI2 position, final CoordI2 dimension) {
|
||||
if (this.root == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.rectCached.getC0().setXY(Math.max(position.getX(), this.root.xMin), Math.max(position.getY(), this.root.yMin));
|
||||
this.rectCached.getC1().setXY(Math.min(position.getX() + dimension.getX() - 1, this.root.xMax), Math.min(position.getY() + dimension.getY() - 1, this.root.yMax));
|
||||
|
||||
if (this.rectCached.getC1().getX() - this.rectCached.getC0().getX() < 0 || this.rectCached.getC1().getY() - this.rectCached.getC0().getY() < 0) {
|
||||
return null;
|
||||
}
|
||||
return this.rectCached;
|
||||
}
|
||||
|
||||
private boolean contains(final Node<T> node, final RectI2 rect) {
|
||||
return rect.getC0().getX() >= node.xMin &&
|
||||
rect.getC0().getY() >= node.yMin &&
|
||||
rect.getC1().getX() <= node.xMax &&
|
||||
rect.getC1().getY() <= node.yMax;
|
||||
}
|
||||
|
||||
private boolean contains(final Node<T> node, final T obj) {
|
||||
return obj.getPosition().getX() >= node.xMin &&
|
||||
obj.getPosition().getY() >= node.yMin &&
|
||||
obj.getPosition().getX() + obj.getDimension().getX() - 1 <= node.xMax &&
|
||||
obj.getPosition().getY() + obj.getDimension().getY() - 1 <= node.yMax;
|
||||
}
|
||||
|
||||
private boolean contains(final T obj, final RectI2 rect) {
|
||||
return rect.getC0().getX() >= obj.getPosition().getX() &&
|
||||
rect.getC0().getY() >= obj.getPosition().getY() &&
|
||||
rect.getC1().getX() <= obj.getPosition().getX() + obj.getDimension().getX() - 1 &&
|
||||
rect.getC1().getY() <= obj.getPosition().getY() + obj.getDimension().getY() - 1;
|
||||
}
|
||||
|
||||
private boolean contains(final RectI2 rect0, final RectI2 rect1) {
|
||||
return rect1.getC0().getX() >= rect0.getC0().getX() &&
|
||||
rect1.getC0().getY() >= rect0.getC0().getY() &&
|
||||
rect1.getC1().getX() <= rect0.getC1().getX() &&
|
||||
rect1.getC1().getY() <= rect0.getC1().getY();
|
||||
}
|
||||
|
||||
public boolean intersects(final Node<T> node, final RectI2 rect) {
|
||||
return ((node.xMax + 1 < node.xMin || node.xMax >= rect.getC0().getX()) &&
|
||||
(node.yMax + 1 < node.yMin || node.yMax >= rect.getC0().getY()) &&
|
||||
(rect.getC1().getX() + 1 < rect.getC0().getX() || rect.getC1().getX() >= node.xMin) &&
|
||||
(rect.getC1().getY() + 1 < rect.getC0().getY() || rect.getC1().getY() >= node.yMin));
|
||||
}
|
||||
|
||||
public boolean intersects(final T obj, final RectI2 rect) {
|
||||
final int oUpperX = obj.getDimension().getX() + obj.getPosition().getX();
|
||||
final int oUpperY = obj.getDimension().getY() + obj.getPosition().getY();
|
||||
return ((oUpperX < obj.getPosition().getX() || oUpperX > rect.getC0().getX()) &&
|
||||
(oUpperY < obj.getPosition().getY() || oUpperY > rect.getC0().getY()) &&
|
||||
(rect.getC1().getX() + 1 < rect.getC0().getX() || rect.getC1().getX() >= obj.getPosition().getX()) &&
|
||||
(rect.getC1().getY() + 1 < rect.getC0().getY() || rect.getC1().getY() >= obj.getPosition().getY()));
|
||||
}
|
||||
|
||||
public boolean intersects(final RectI2 rect0, final RectI2 rect1) {
|
||||
return ((rect0.getC1().getX() + 1 < rect0.getC0().getX() || rect0.getC1().getX() >= rect1.getC0().getX()) &&
|
||||
(rect0.getC1().getY() + 1 < rect0.getC0().getY() || rect0.getC1().getY() >= rect1.getC0().getY()) &&
|
||||
(rect1.getC1().getX() + 1 < rect1.getC0().getX() || rect1.getC1().getX() >= rect0.getC0().getX()) &&
|
||||
(rect1.getC1().getY() + 1 < rect1.getC0().getY() || rect1.getC1().getY() >= rect0.getC0().getY()));
|
||||
}
|
||||
|
||||
private static class Node<T extends SpatialIndex2Capable> {
|
||||
|
||||
int xMin;
|
||||
|
||||
int xMax;
|
||||
|
||||
int yMin;
|
||||
|
||||
int yMax;
|
||||
|
||||
List<T> leaves;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
Node[] branches;
|
||||
|
||||
Node<T> parent;
|
||||
|
||||
Node(final int xMin, final int yMin, final int size) {
|
||||
this.xMin = xMin;
|
||||
this.xMax = xMin + size - 1;
|
||||
this.yMin = yMin;
|
||||
this.yMax = yMin + size - 1;
|
||||
}
|
||||
|
||||
Node(final int xMin, final int xMax, final int yMin, final int yMax, final Node<T> parent) {
|
||||
this.xMin = xMin;
|
||||
this.xMax = xMax;
|
||||
this.yMin = yMin;
|
||||
this.yMax = yMax;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
boolean createBranches() {
|
||||
final int partition = (this.xMax - this.xMin + 1) >> 1;
|
||||
if (partition == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.branches = new Node[ORDER];
|
||||
this.branches[0] = new Node<>(this.xMin, this.xMin + partition - 1, this.yMin, this.yMin + partition - 1, this);
|
||||
this.branches[1] = new Node<>(this.xMin, this.xMin + partition - 1, this.yMin + partition, this.yMax, this);
|
||||
this.branches[2] = new Node<>(this.xMin + partition, this.xMax, this.yMin, this.yMin + partition - 1, this);
|
||||
this.branches[3] = new Node<>(this.xMin + partition, this.xMax, this.yMin + partition, this.yMax, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("Node [xMin=");
|
||||
builder.append(this.xMin);
|
||||
builder.append(", xMax=");
|
||||
builder.append(this.xMax);
|
||||
builder.append(", yMin=");
|
||||
builder.append(this.yMin);
|
||||
builder.append(", yMax=");
|
||||
builder.append(this.yMax);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.index;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||
import de.extio.game_engine.spatial2.model.HasPositionAndDimension2;
|
||||
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||
import de.extio.game_engine.spatial2.model.RectI2;
|
||||
import de.extio.game_engine.spatial2.model.SpatialIndex2Capable;
|
||||
|
||||
/**
|
||||
* Spatial indexes are used for spatial queries. See also QuadTree
|
||||
*/
|
||||
public interface SpatialIndex2D<T extends SpatialIndex2Capable> extends Iterable<T>, AutoCloseable {
|
||||
|
||||
static AtomicLong SESSION = new AtomicLong(1);
|
||||
|
||||
/**
|
||||
* Adds an object to the index
|
||||
*/
|
||||
void add(T obj);
|
||||
|
||||
/**
|
||||
* Removes an object from the index
|
||||
*/
|
||||
void remove(T obj);
|
||||
|
||||
/**
|
||||
* Moves an indexed object to a new location. Implementations are usually faster than doing separate remove() and add() operations
|
||||
*/
|
||||
void move(T obj, CoordI2 oldPosition, CoordI2 oldDimension);
|
||||
|
||||
/**
|
||||
* Queries all object intersecting with a given area
|
||||
*/
|
||||
default Collection<T> find(final RectI2 rect) {
|
||||
return this.find(rect.getC0(), ImmutableCoordI2.create(rect.getC1()).substract(rect.getC0()), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries all object intersecting with a given area
|
||||
*/
|
||||
default List<T> find(final HasPositionAndDimension2 hasPositionAndDimension2) {
|
||||
return this.find(hasPositionAndDimension2.getPosition(), hasPositionAndDimension2.getDimension(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries all object intersecting with a given area
|
||||
*/
|
||||
default List<T> find(final CoordI2 position, final CoordI2 dimension) {
|
||||
return this.find(position, dimension, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries all object intersecting with a given area. Objects can be pre-filtered which is usually faster than returning them first and then filter in your own code.
|
||||
*/
|
||||
List<T> find(CoordI2 position, CoordI2 dimension, Predicate<T> filter);
|
||||
|
||||
/**
|
||||
* Returns the first object at a given position
|
||||
*/
|
||||
default T findFirstAt(final CoordI2 position) {
|
||||
return this.findFirstAt(position, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first object at a given position. Objects can be pre-filtered which is usually faster than returning them first and then filter in your own code.
|
||||
*/
|
||||
T findFirstAt(CoordI2 position, Predicate<T> filter);
|
||||
|
||||
/**
|
||||
* Executes a consumer for all objects at a given position.
|
||||
*/
|
||||
default void executeAt(final CoordI2 position, final Consumer<T> consumer) {
|
||||
this.executeAt(position, consumer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a consumer for all objects at a given position. Objects can be pre-filtered which is usually faster than returning them first and then filter in your own code.
|
||||
*/
|
||||
void executeAt(CoordI2 position, Consumer<T> consumer, Predicate<T> filter);
|
||||
|
||||
/**
|
||||
* Clears and frees internal state
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Clears but keeps internal state allocated. Optimization for re-use.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
}
|
||||
92
src/main/java/de/extio/game_engine/spatial2/model/Area2.java
Normal file
92
src/main/java/de/extio/game_engine/spatial2/model/Area2.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* A 2D area, having a position (top left corner) and a dimension
|
||||
*/
|
||||
public class Area2 extends Point2 implements HasPositionAndDimension2, SpatialIndex2Capable {
|
||||
|
||||
private CoordI2 dimension;
|
||||
|
||||
private transient long bsi2Session;
|
||||
|
||||
public Area2() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Area2(final CoordI2 coord, final CoordI2 dimension) {
|
||||
super(coord);
|
||||
this.setDimension(dimension);
|
||||
}
|
||||
|
||||
public Area2(final HasPositionAndDimension2 other) {
|
||||
super(ImmutableCoordI2.create(other.getPosition()));
|
||||
this.setDimension(ImmutableCoordI2.create(other.getDimension()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordI2 getDimension() {
|
||||
return this.dimension;
|
||||
}
|
||||
|
||||
public void setDimension(final CoordI2 dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInSpatialIndex2ResultSet(final long session) {
|
||||
return this.bsi2Session == session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void includeInSpatialIndex2ResultSet(final long session) {
|
||||
this.bsi2Session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((this.dimension == null) ? 0 : this.dimension.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
final Area2 other = (Area2) obj;
|
||||
if (this.dimension == null) {
|
||||
if (other.dimension != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!this.dimension.equals(other.dimension)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("Area2 [getPosition()=");
|
||||
builder.append(this.getPosition());
|
||||
builder.append(", dimension=");
|
||||
builder.append(this.dimension);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
import de.extio.game_engine.spatial2.WorldUtils2;
|
||||
|
||||
/**
|
||||
* Helper to convert between mutable and immutable representations of tuples (coordinates and vectors)
|
||||
*/
|
||||
public interface Coord2Conversion {
|
||||
|
||||
default CoordI2 toMutableCoordI2() {
|
||||
if (this instanceof CoordI2) {
|
||||
return MutableCoordI2.create((CoordI2) this);
|
||||
}
|
||||
else {
|
||||
return MutableCoordI2.create((CoordD2) this);
|
||||
}
|
||||
}
|
||||
|
||||
default CoordI2 toImmutableCoordI2() {
|
||||
if (this instanceof CoordI2) {
|
||||
return ImmutableCoordI2.create((CoordI2) this);
|
||||
}
|
||||
else {
|
||||
return ImmutableCoordI2.create((CoordD2) this);
|
||||
}
|
||||
}
|
||||
|
||||
default CoordD2 toMutableCoordD2() {
|
||||
if (this instanceof CoordI2) {
|
||||
return MutableCoordD2.create((CoordI2) this);
|
||||
}
|
||||
else {
|
||||
return MutableCoordD2.create((CoordD2) this);
|
||||
}
|
||||
}
|
||||
|
||||
default CoordD2 toImmutableCoordD2() {
|
||||
if (this instanceof CoordI2) {
|
||||
return ImmutableCoordD2.create((CoordI2) this);
|
||||
}
|
||||
else {
|
||||
return ImmutableCoordD2.create((CoordD2) this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a vector to a unit normal vector
|
||||
*/
|
||||
default CoordD2 toVNorm() {
|
||||
CoordD2 vNorm;
|
||||
|
||||
if (this instanceof ImmutableCoordI2) {
|
||||
vNorm = ImmutableCoordD2.create((ImmutableCoordI2) this);
|
||||
}
|
||||
else if (this instanceof MutableCoordI2) {
|
||||
vNorm = MutableCoordD2.create((MutableCoordI2) this);
|
||||
}
|
||||
else {
|
||||
vNorm = (CoordD2) this;
|
||||
}
|
||||
|
||||
final double length = WorldUtils2.getDistance(vNorm);
|
||||
if (length > 0.0) {
|
||||
vNorm = vNorm.divide(length);
|
||||
}
|
||||
|
||||
return vNorm;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* A floating point number tuple (can represent a point, a coordinate or a vector in 2D space).
|
||||
*/
|
||||
public interface CoordD2 extends Comparable<CoordD2>, Coord2Conversion {
|
||||
|
||||
double getX();
|
||||
|
||||
void setX(final double x);
|
||||
|
||||
double getY();
|
||||
|
||||
void setY(final double y);
|
||||
|
||||
CoordD2 setXY(final CoordD2 other);
|
||||
|
||||
CoordD2 setXY(final double x, final double y);
|
||||
|
||||
CoordD2 add(final CoordD2 delta);
|
||||
|
||||
CoordD2 add(final double dx, final double dy);
|
||||
|
||||
CoordD2 add(double n);
|
||||
|
||||
CoordD2 substract(final CoordD2 delta);
|
||||
|
||||
CoordD2 substract(final double dx, final double dy);
|
||||
|
||||
CoordD2 substract(double n);
|
||||
|
||||
CoordD2 multiply(final CoordD2 delta);
|
||||
|
||||
CoordD2 multiply(final double dx, final double dy);
|
||||
|
||||
CoordD2 multiply(final double n);
|
||||
|
||||
CoordD2 divide(final CoordD2 delta);
|
||||
|
||||
CoordD2 divide(final double dx, final double dy);
|
||||
|
||||
CoordD2 divide(final double n);
|
||||
|
||||
@Override
|
||||
default int compareTo(final CoordD2 other) {
|
||||
return this.getY() == other.getY() ? Double.compare(this.getX(), other.getX()) : Double.compare(this.getY(), other.getY());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* An integer tuple (can represent a point, a coordinate or a vector in 2D space).
|
||||
*/
|
||||
public interface CoordI2 extends HasPosition2, Comparable<CoordI2>, Coord2Conversion {
|
||||
|
||||
int getX();
|
||||
|
||||
void setX(final int x);
|
||||
|
||||
int getY();
|
||||
|
||||
void setY(final int y);
|
||||
|
||||
CoordI2 setXY(final HasPosition2 other);
|
||||
|
||||
CoordI2 setXY(final int x, final int y);
|
||||
|
||||
CoordI2 add(final HasPosition2 delta);
|
||||
|
||||
CoordI2 add(final int dx, final int dy);
|
||||
|
||||
CoordI2 add(final int n);
|
||||
|
||||
CoordI2 substract(final HasPosition2 delta);
|
||||
|
||||
CoordI2 substract(final int dx, final int dy);
|
||||
|
||||
CoordI2 substract(final int n);
|
||||
|
||||
CoordI2 multiply(final HasPosition2 delta);
|
||||
|
||||
CoordI2 multiply(final int dx, final int dy);
|
||||
|
||||
CoordI2 multiply(final int n);
|
||||
|
||||
CoordI2 divide(final HasPosition2 delta);
|
||||
|
||||
CoordI2 divide(final int dx, final int dy);
|
||||
|
||||
CoordI2 divide(final int n);
|
||||
|
||||
@Override
|
||||
default CoordI2 getPosition() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default int compareTo(final CoordI2 other) {
|
||||
return this.getY() == other.getY() ? Integer.compare(this.getX(), other.getX()) : Integer.compare(this.getY(), other.getY());
|
||||
}
|
||||
|
||||
}
|
||||
156
src/main/java/de/extio/game_engine/spatial2/model/Edge2.java
Normal file
156
src/main/java/de/extio/game_engine/spatial2/model/Edge2.java
Normal file
@@ -0,0 +1,156 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Identifies a vertex or a side
|
||||
*/
|
||||
public enum Edge2 {
|
||||
|
||||
RIGHT(),
|
||||
BOTTOM(),
|
||||
LEFT(),
|
||||
TOP();
|
||||
|
||||
public final static Edge2[] VALUES_CACHED = new Edge2[] {
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
LEFT,
|
||||
TOP
|
||||
};
|
||||
|
||||
private final static CoordD2 VD_BOTTOM = MutableCoordD2.create(0.0, 1.0);
|
||||
|
||||
private final static CoordD2 VD_LEFT = MutableCoordD2.create(-1.0, 0.0);
|
||||
|
||||
private final static CoordD2 VD_RIGHT = MutableCoordD2.create(1.0, 0.0);
|
||||
|
||||
private final static CoordD2 VD_TOP = MutableCoordD2.create(0.0, -1.0);
|
||||
|
||||
private final static CoordI2 VI_BOTTOM = ImmutableCoordI2.create(0, 1);
|
||||
|
||||
private final static CoordI2 VI_LEFT = ImmutableCoordI2.create(-1, 0);
|
||||
|
||||
private final static CoordI2 VI_RIGHT = ImmutableCoordI2.create(1, 0);
|
||||
|
||||
private final static CoordI2 VI_TOP = ImmutableCoordI2.create(0, -1);
|
||||
|
||||
public static Edge2 fromBit(final int bit) {
|
||||
if ((bit & RIGHT.bit) == RIGHT.bit) {
|
||||
return RIGHT;
|
||||
}
|
||||
else if ((bit & BOTTOM.bit) == BOTTOM.bit) {
|
||||
return BOTTOM;
|
||||
}
|
||||
else if ((bit & LEFT.bit) == LEFT.bit) {
|
||||
return LEFT;
|
||||
}
|
||||
else if ((bit & TOP.bit) == TOP.bit) {
|
||||
return TOP;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates edge from RIGHT to destination clock-wise. BOTTOM rotates by 1 stop, LEFT by 2 and TOP by 3.
|
||||
* @param dest stops
|
||||
* @return Rotated edge2
|
||||
*/
|
||||
public Edge2 rotate(final Edge2 dest) {
|
||||
return Edge2.fromBit(Edge2.rotate(this.getBit(), dest));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates edges represented by bit mask (Edge2::getBit)
|
||||
* @param edges Bit mask of edges
|
||||
* @param dest Rotate from RIGHT to destination clock-wise. BOTTOM rotates by 1 stop, LEFT by 2 and TOP by 3.
|
||||
* @return Rotated bit mask
|
||||
*/
|
||||
public static int rotate(final int edges, final Edge2 dest) {
|
||||
// Formula:
|
||||
// bits: Number of significant bits, in our case 4 (4 edges)
|
||||
// shift: rotate by n steps
|
||||
//
|
||||
// ((x << shift) | (x >> (bits - shift))) & ((1 << bits) - 1)
|
||||
// |----- Circular left shift ----| |-- Cut significant bits --|
|
||||
|
||||
if (dest == null) {
|
||||
return edges;
|
||||
}
|
||||
|
||||
return ((edges << Integer.numberOfTrailingZeros(dest.bit)) | (edges >> (Edge2.VALUES_CACHED.length - Integer.numberOfTrailingZeros(dest.bit)))) & ((1 << Edge2.VALUES_CACHED.length) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a vector with length 1 pointing to the direction edge2 representates
|
||||
* @param edge2
|
||||
* @return Vector
|
||||
*/
|
||||
public static CoordD2 toVectorD(final Edge2 edge2) {
|
||||
switch (edge2) {
|
||||
case BOTTOM:
|
||||
return VD_BOTTOM;
|
||||
case LEFT:
|
||||
return VD_LEFT;
|
||||
case RIGHT:
|
||||
return VD_RIGHT;
|
||||
case TOP:
|
||||
return VD_TOP;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a vector with length 1 pointing to the direction edge2 representates
|
||||
* @param edge2
|
||||
* @return Vector
|
||||
*/
|
||||
public static CoordI2 toVectorI(final Edge2 edge2) {
|
||||
switch (edge2) {
|
||||
case BOTTOM:
|
||||
return VI_BOTTOM;
|
||||
case LEFT:
|
||||
return VI_LEFT;
|
||||
case RIGHT:
|
||||
return VI_RIGHT;
|
||||
case TOP:
|
||||
return VI_TOP;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String toString(final int edges) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Edges [");
|
||||
|
||||
for (final Edge2 edge : Edge2.VALUES_CACHED) {
|
||||
if ((edges & (1 << edge.ordinal())) != 0) {
|
||||
sb.append(edge.name());
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int bit;
|
||||
|
||||
Edge2() {
|
||||
this.bit = 1 << this.ordinal();
|
||||
}
|
||||
|
||||
public int getBit() {
|
||||
return this.bit;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Interface for objects having a position (top left corner)
|
||||
*/
|
||||
public interface HasPosition2 {
|
||||
|
||||
CoordI2 getPosition();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Interface for objects occupying 2D area, having a position (top left corner) and a dimension
|
||||
*/
|
||||
public interface HasPositionAndDimension2 extends HasPosition2 {
|
||||
|
||||
CoordI2 getDimension();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Immutable implementation of CoordD2
|
||||
*/
|
||||
public final class ImmutableCoordD2 implements CoordD2 {
|
||||
|
||||
private final static ImmutableCoordD2 COORD_ZERO = new ImmutableCoordD2(0, 0);
|
||||
|
||||
private final static ImmutableCoordD2 COORD_ONE = new ImmutableCoordD2(1, 1);
|
||||
|
||||
public static ImmutableCoordD2 zero() {
|
||||
return COORD_ZERO;
|
||||
}
|
||||
|
||||
public static ImmutableCoordD2 one() {
|
||||
return COORD_ONE;
|
||||
}
|
||||
|
||||
public static ImmutableCoordD2 create() {
|
||||
return COORD_ZERO;
|
||||
}
|
||||
|
||||
public static ImmutableCoordD2 create(final CoordD2 other) {
|
||||
if (other == null) {
|
||||
return null;
|
||||
}
|
||||
if (other instanceof ImmutableCoordD2) {
|
||||
return (ImmutableCoordD2) other;
|
||||
}
|
||||
return new ImmutableCoordD2(other.getX(), other.getY());
|
||||
}
|
||||
|
||||
public static ImmutableCoordD2 create(final double x, final double y) {
|
||||
return new ImmutableCoordD2(x, y);
|
||||
}
|
||||
|
||||
public static ImmutableCoordD2 create(final HasPosition2 hasPosition2) {
|
||||
return new ImmutableCoordD2(hasPosition2.getPosition().getX(), hasPosition2.getPosition().getY());
|
||||
}
|
||||
|
||||
private double x;
|
||||
|
||||
private double y;
|
||||
|
||||
ImmutableCoordD2() {
|
||||
|
||||
}
|
||||
|
||||
ImmutableCoordD2(final double x, final double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(final double x) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(final double y) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 setXY(final CoordD2 other) {
|
||||
return create(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 setXY(final double x, final double y) {
|
||||
return create(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 add(final CoordD2 delta) {
|
||||
return create(this.x + delta.getX(), this.y + delta.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 add(final double dx, final double dy) {
|
||||
return create(this.x + dx, this.y + dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 add(final double n) {
|
||||
return create(this.x + n, this.y + n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 substract(final CoordD2 delta) {
|
||||
return create(this.x - delta.getX(), this.y - delta.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 substract(final double dx, final double dy) {
|
||||
return create(this.x - dx, this.y - dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 substract(final double n) {
|
||||
return create(this.x - n, this.y - n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 multiply(final CoordD2 delta) {
|
||||
return create(this.x * delta.getX(), this.y * delta.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 multiply(final double dx, final double dy) {
|
||||
return create(this.x * dx, this.y * dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordD2 multiply(final double n) {
|
||||
return create(this.x * n, this.y * n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 divide(final CoordD2 delta) {
|
||||
final double dx = delta.getX() != 0.0 ? delta.getX() : 1.0;
|
||||
final double dy = delta.getY() != 0.0 ? delta.getY() : 1.0;
|
||||
return create(this.x / dx, this.y / dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordD2 divide(final double dx, final double dy) {
|
||||
final double dx_ = dx != 0.0 ? dx : 1.0;
|
||||
final double dy_ = dy != 0.0 ? dy : 1.0;
|
||||
return create(this.x / dx_, this.y / dy_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordD2 divide(final double n) {
|
||||
final double n_ = n != 0.0 ? n : 1.0;
|
||||
return create(this.x / n_, this.y / n_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(this.x);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(this.y);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if ((obj == null) || !(obj instanceof CoordD2)) {
|
||||
return false;
|
||||
}
|
||||
final CoordD2 other = (CoordD2) obj;
|
||||
if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.getX())) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.getY())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "D2i[" + this.x + ", " + this.y + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Immutable implementation of CoordI2
|
||||
*/
|
||||
public final class ImmutableCoordI2 implements CoordI2 {
|
||||
|
||||
private final static int COORD_CACHE_DIM = 100;
|
||||
|
||||
private final static int COORD_CACHE_BOUND = 50;
|
||||
|
||||
private final static int COORD_CACHE_SIZE = COORD_CACHE_DIM * COORD_CACHE_DIM;
|
||||
|
||||
private final static ImmutableCoordI2[] COORD_CACHE = new ImmutableCoordI2[COORD_CACHE_SIZE];
|
||||
|
||||
private volatile static ImmutableCoordI2 LAST_CACHED = new ImmutableCoordI2(0, 0);
|
||||
|
||||
private final static ImmutableCoordI2 COORD_ZERO = new ImmutableCoordI2(0, 0);
|
||||
|
||||
private final static ImmutableCoordI2 COORD_ONE = new ImmutableCoordI2(1, 1);
|
||||
|
||||
static {
|
||||
int i = 0;
|
||||
for (int x = -COORD_CACHE_BOUND; x < COORD_CACHE_BOUND; x++) {
|
||||
for (int y = -COORD_CACHE_BOUND; y < COORD_CACHE_BOUND; y++) {
|
||||
COORD_CACHE[i++] = new ImmutableCoordI2(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableCoordI2 zero() {
|
||||
return COORD_ZERO;
|
||||
}
|
||||
|
||||
public static ImmutableCoordI2 one() {
|
||||
return COORD_ONE;
|
||||
}
|
||||
|
||||
public static ImmutableCoordI2 create() {
|
||||
return COORD_ZERO;
|
||||
}
|
||||
|
||||
public static ImmutableCoordI2 create(final HasPosition2 other) {
|
||||
if (other == null) {
|
||||
return null;
|
||||
}
|
||||
if (other instanceof ImmutableCoordI2) {
|
||||
return (ImmutableCoordI2) other;
|
||||
}
|
||||
return ImmutableCoordI2.create(other.getPosition().getX(), other.getPosition().getY());
|
||||
}
|
||||
|
||||
public static ImmutableCoordI2 create(final int x, final int y) {
|
||||
if (x >= -COORD_CACHE_BOUND && x < COORD_CACHE_BOUND && y >= -COORD_CACHE_BOUND && y < COORD_CACHE_BOUND) {
|
||||
final int idx = (x + COORD_CACHE_BOUND) * COORD_CACHE_DIM + (y + COORD_CACHE_BOUND);
|
||||
if (idx >= 0 && idx < COORD_CACHE_SIZE) {
|
||||
return COORD_CACHE[idx];
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableCoordI2 cached = LAST_CACHED;
|
||||
if (cached.getX() != x || cached.getY() != y) {
|
||||
cached = new ImmutableCoordI2(x, y);
|
||||
LAST_CACHED = cached;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
public static ImmutableCoordI2 create(final CoordD2 other) {
|
||||
return ImmutableCoordI2.create((int) other.getX(), (int) other.getY());
|
||||
}
|
||||
|
||||
private int x;
|
||||
|
||||
private int y;
|
||||
|
||||
ImmutableCoordI2() {
|
||||
|
||||
}
|
||||
|
||||
ImmutableCoordI2(final int x, final int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(final int x) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(final int y) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 setXY(final HasPosition2 other) {
|
||||
return create(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 setXY(final int x, final int y) {
|
||||
return create(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 add(final HasPosition2 delta) {
|
||||
return create(this.x + delta.getPosition().getX(), this.y + delta.getPosition().getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 add(final int dx, final int dy) {
|
||||
return create(this.x + dx, this.y + dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 add(final int n) {
|
||||
return create(this.x + n, this.y + n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 substract(final HasPosition2 delta) {
|
||||
return create(this.x - delta.getPosition().getX(), this.y - delta.getPosition().getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 substract(final int dx, final int dy) {
|
||||
return create(this.x - dx, this.y - dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 substract(final int n) {
|
||||
return create(this.x - n, this.y - n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 multiply(final HasPosition2 delta) {
|
||||
return create(this.x * delta.getPosition().getX(), this.y * delta.getPosition().getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 multiply(final int dx, final int dy) {
|
||||
return create(this.x * dx, this.y * dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 multiply(final int n) {
|
||||
return create(this.x * n, this.y * n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 divide(final HasPosition2 delta) {
|
||||
final int dx = delta.getPosition().getX() != 0 ? delta.getPosition().getX() : 1;
|
||||
final int dy = delta.getPosition().getY() != 0 ? delta.getPosition().getY() : 1;
|
||||
return create(this.x / dx, this.y / dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 divide(final int dx, final int dy) {
|
||||
final int dx_ = dx != 0 ? dx : 1;
|
||||
final int dy_ = dy != 0 ? dy : 1;
|
||||
return create(this.x / dx_, this.y / dy_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCoordI2 divide(final int n) {
|
||||
final int n_ = n != 0 ? n : 1;
|
||||
return create(this.x / n_, this.y / n_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + this.x;
|
||||
result = prime * result + this.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if ((obj == null) || !(obj instanceof CoordI2)) {
|
||||
return false;
|
||||
}
|
||||
final CoordI2 other = (CoordI2) obj;
|
||||
if (this.x != other.getX()) {
|
||||
return false;
|
||||
}
|
||||
if (this.y != other.getY()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "I2i[" + this.x + ", " + this.y + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Immutable implementation of CoordD2
|
||||
*/
|
||||
public final class MutableCoordD2 implements CoordD2 {
|
||||
|
||||
public static MutableCoordD2 create() {
|
||||
return new MutableCoordD2(0.0, 0.0);
|
||||
}
|
||||
|
||||
public static MutableCoordD2 create(final double x, final double y) {
|
||||
return new MutableCoordD2(x, y);
|
||||
}
|
||||
|
||||
public static MutableCoordD2 create(final CoordD2 other) {
|
||||
return new MutableCoordD2(other.getX(), other.getY());
|
||||
}
|
||||
|
||||
public static MutableCoordD2 create(final HasPosition2 hasPosition2) {
|
||||
return new MutableCoordD2(hasPosition2.getPosition().getX(), hasPosition2.getPosition().getY());
|
||||
}
|
||||
|
||||
private double x;
|
||||
|
||||
private double y;
|
||||
|
||||
MutableCoordD2() {
|
||||
|
||||
}
|
||||
|
||||
MutableCoordD2(final double x, final double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(final double x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(final double y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 setXY(final CoordD2 other) {
|
||||
this.x = other.getX();
|
||||
this.y = other.getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 setXY(final double x, final double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 add(final CoordD2 delta) {
|
||||
this.x += delta.getX();
|
||||
this.y += delta.getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 add(final double dx, final double dy) {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 add(final double n) {
|
||||
this.x += n;
|
||||
this.y += n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 substract(final CoordD2 delta) {
|
||||
this.x -= delta.getX();
|
||||
this.y -= delta.getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 substract(final double dx, final double dy) {
|
||||
this.x -= dx;
|
||||
this.y -= dy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 substract(final double n) {
|
||||
this.x -= n;
|
||||
this.y -= n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 multiply(final CoordD2 delta) {
|
||||
this.x *= delta.getX();
|
||||
this.y *= delta.getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 multiply(final double dx, final double dy) {
|
||||
this.x *= dx;
|
||||
this.y *= dy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordD2 multiply(final double n) {
|
||||
this.x *= n;
|
||||
this.y *= n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 divide(final CoordD2 delta) {
|
||||
if (delta.getX() != 0.0) {
|
||||
this.x /= delta.getX();
|
||||
}
|
||||
if (delta.getY() != 0.0) {
|
||||
this.y /= delta.getY();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordD2 divide(final double dx, final double dy) {
|
||||
if (dx != 0.0) {
|
||||
this.x /= dx;
|
||||
}
|
||||
if (dy != 0.0) {
|
||||
this.y /= dy;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordD2 divide(final double n) {
|
||||
if (n != 0.0) {
|
||||
this.x /= n;
|
||||
this.y /= n;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(this.x);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(this.y);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if ((obj == null) || !(obj instanceof CoordD2)) {
|
||||
return false;
|
||||
}
|
||||
final CoordD2 other = (CoordD2) obj;
|
||||
if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.getX())) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.getY())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "D2m[" + this.x + ", " + this.y + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Immutable implementation of CoordI2
|
||||
*/
|
||||
public final class MutableCoordI2 implements CoordI2 {
|
||||
|
||||
public static MutableCoordI2 create() {
|
||||
return new MutableCoordI2(0, 0);
|
||||
}
|
||||
|
||||
public static MutableCoordI2 create(final int x, final int y) {
|
||||
return new MutableCoordI2(x, y);
|
||||
}
|
||||
|
||||
public static MutableCoordI2 create(final HasPosition2 other) {
|
||||
return new MutableCoordI2(other.getPosition().getX(), other.getPosition().getY());
|
||||
}
|
||||
|
||||
public static MutableCoordI2 create(final CoordD2 other) {
|
||||
return new MutableCoordI2((int) other.getX(), (int) other.getY());
|
||||
}
|
||||
|
||||
private int x;
|
||||
|
||||
private int y;
|
||||
|
||||
MutableCoordI2() {
|
||||
|
||||
}
|
||||
|
||||
MutableCoordI2(final int x, final int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setX(final int x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setY(final int y) {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 setXY(final HasPosition2 other) {
|
||||
this.x = other.getPosition().getX();
|
||||
this.y = other.getPosition().getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 setXY(final int x, final int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 add(final HasPosition2 delta) {
|
||||
this.x += delta.getPosition().getX();
|
||||
this.y += delta.getPosition().getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 add(final int dx, final int dy) {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 add(final int n) {
|
||||
this.x += n;
|
||||
this.y += n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 substract(final HasPosition2 delta) {
|
||||
this.x -= delta.getPosition().getX();
|
||||
this.y -= delta.getPosition().getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 substract(final int dx, final int dy) {
|
||||
this.x -= dx;
|
||||
this.y -= dy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 substract(final int n) {
|
||||
this.x -= n;
|
||||
this.y -= n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 multiply(final HasPosition2 delta) {
|
||||
this.x *= delta.getPosition().getX();
|
||||
this.y *= delta.getPosition().getY();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 multiply(final int dx, final int dy) {
|
||||
this.x *= dx;
|
||||
this.y *= dy;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 multiply(final int n) {
|
||||
this.x *= n;
|
||||
this.y *= n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 divide(final HasPosition2 delta) {
|
||||
if (delta.getPosition().getX() != 0) {
|
||||
this.x /= delta.getPosition().getX();
|
||||
}
|
||||
if (delta.getPosition().getY() != 0) {
|
||||
this.y /= delta.getPosition().getY();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 divide(final int dx, final int dy) {
|
||||
if (dx != 0) {
|
||||
this.x /= dx;
|
||||
}
|
||||
if (dy != 0) {
|
||||
this.y /= dy;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutableCoordI2 divide(final int n) {
|
||||
if (n != 0) {
|
||||
this.x /= n;
|
||||
this.y /= n;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + this.x;
|
||||
result = prime * result + this.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if ((obj == null) || !(obj instanceof CoordI2)) {
|
||||
return false;
|
||||
}
|
||||
final CoordI2 other = (CoordI2) obj;
|
||||
if (this.x != other.getX()) {
|
||||
return false;
|
||||
}
|
||||
if (this.y != other.getY()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "I2m[" + this.x + ", " + this.y + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* A point in 2D space
|
||||
*/
|
||||
public class Point2 implements HasPosition2 {
|
||||
|
||||
private CoordI2 coord;
|
||||
|
||||
public Point2() {
|
||||
//
|
||||
}
|
||||
|
||||
public Point2(final CoordI2 coord) {
|
||||
this.coord = coord;
|
||||
}
|
||||
|
||||
public void setPosition(final CoordI2 coord) {
|
||||
this.coord = coord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoordI2 getPosition() {
|
||||
return this.coord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this.coord == null) ? 0 : this.coord.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if ((obj == null) || (this.getClass() != obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
final Point2 other = (Point2) obj;
|
||||
if (this.coord == null) {
|
||||
if (other.coord != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!this.coord.equals(other.coord)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("Point2 [coord=");
|
||||
builder.append(this.coord);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
109
src/main/java/de/extio/game_engine/spatial2/model/RectI2.java
Normal file
109
src/main/java/de/extio/game_engine/spatial2/model/RectI2.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* A rectangle represented by top left (c0) and bottom right (c1) vertices
|
||||
*/
|
||||
public class RectI2 {
|
||||
|
||||
private CoordI2 c0;
|
||||
|
||||
private CoordI2 c1;
|
||||
|
||||
public RectI2() {
|
||||
|
||||
}
|
||||
|
||||
public RectI2(final CoordI2 c0, final CoordI2 c1) {
|
||||
this.c0 = c0;
|
||||
this.c1 = c1;
|
||||
}
|
||||
|
||||
public RectI2(final RectI2 other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
if (other.c0 != null) {
|
||||
if (other.c0 instanceof ImmutableCoordI2) {
|
||||
this.c0 = ImmutableCoordI2.create(other.c0);
|
||||
}
|
||||
else {
|
||||
this.c0 = MutableCoordI2.create(other.c0);
|
||||
}
|
||||
}
|
||||
if (other.c1 != null) {
|
||||
if (other.c1 instanceof ImmutableCoordI2) {
|
||||
this.c1 = ImmutableCoordI2.create(other.c1);
|
||||
}
|
||||
else {
|
||||
this.c1 = MutableCoordI2.create(other.c1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CoordI2 getC0() {
|
||||
return this.c0;
|
||||
}
|
||||
|
||||
public void setC0(final CoordI2 c0) {
|
||||
this.c0 = c0;
|
||||
}
|
||||
|
||||
public CoordI2 getC1() {
|
||||
return this.c1;
|
||||
}
|
||||
|
||||
public void setC1(final CoordI2 c1) {
|
||||
this.c1 = c1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this.c0 == null) ? 0 : this.c0.hashCode());
|
||||
result = prime * result + ((this.c1 == null) ? 0 : this.c1.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if ((obj == null) || (this.getClass() != obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
final RectI2 other = (RectI2) obj;
|
||||
if (this.c0 == null) {
|
||||
if (other.c0 != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!this.c0.equals(other.c0)) {
|
||||
return false;
|
||||
}
|
||||
if (this.c1 == null) {
|
||||
if (other.c1 != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!this.c1.equals(other.c1)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RectI2 [c0=" + this.c0 + ", c1=" + this.c1 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package de.extio.game_engine.spatial2.model;
|
||||
|
||||
/**
|
||||
* Objects that can be indexed in SpatialIndex2D implementations
|
||||
*/
|
||||
public interface SpatialIndex2Capable extends HasPositionAndDimension2 {
|
||||
|
||||
boolean isInSpatialIndex2ResultSet(long session);
|
||||
|
||||
void includeInSpatialIndex2ResultSet(long session);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user