Commit f9089a61 authored by Numanoglu's avatar Numanoglu
Browse files

Generic classes for aabb space division alg. and ConcretePolygon integrated

parent 4921f946
Pipeline #11388 passed with stage
in 1 minute and 33 seconds
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
/*
* Represents an Axis-Aligned Bounding Box (AABB) in 3D space.
*/
public class AABB {
private Point min;
private Point max;
/*
* Default constructor initializing to a zero-sized box at origin
*/
public AABB() {
this.min = new Point();
this.max = new Point();
}
/*
* Constructs an AABB from two points
*/
public AABB(Point a, Point b) {
double xmin = Math.min(a.getX(), b.getX());
double xmax = Math.max(a.getX(), b.getX());
double ymin = Math.min(a.getY(), b.getY());
double ymax = Math.max(a.getY(), b.getY());
double zmin = Math.min(a.getZ(), b.getZ());
double zmax = Math.max(a.getZ(), b.getZ());
this.min = new Point(xmin, ymin, zmin);
this.max = new Point(xmax, ymax, zmax);
}
/*
* Constructs an AABB from explicit min and max coordinates
*/
public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
this(new Point(minX, minY, minZ), new Point(maxX, maxY, maxZ));
}
/* GETTER */
public Point getMin() {
return min;
}
public Point getMax() {
return max;
}
public Point getCenter() {
return new Point(
(min.getX() + max.getX()) / 2.0,
(min.getY() + max.getY()) / 2.0,
(min.getZ() + max.getZ()) / 2.0
);
}
/*
* Returns the eight corner points of the AABB
*/
public List<Point> getCornerPoints() {
List<Point> points = new ArrayList<>();
points.add(min);
points.add(new Point(min.getX(), min.getY(), max.getZ()));
points.add(new Point(min.getX(), max.getY(), min.getZ()));
points.add(new Point(min.getX(), max.getY(), max.getZ()));
points.add(new Point(max.getX(), min.getY(), min.getZ()));
points.add(new Point(max.getX(), min.getY(), max.getZ()));
points.add(new Point(max.getX(), max.getY(), min.getZ()));
points.add(max);
return points;
}
/*
* Checks whether a point lies inside or on the surface of the AABB
*/
public boolean encloses(Point point) {
return min.getX() <= point.getX() && max.getX() >= point.getX() &&
min.getY() <= point.getY() && max.getY() >= point.getY() &&
min.getZ() <= point.getZ() && max.getZ() >= point.getZ();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AABB aabb = (AABB) obj;
return min.equals(aabb.min) && max.equals(aabb.max);
}
/*
* Liang-Barsky line clipping algorithm for intersection check.
*/
public static boolean lineClippingLiangBarsky(AABB aabb, Line line) {
Point max = aabb.max;
Point min = aabb.min;
Point A = max.subtract(line.getStart());
Point B = min.subtract(line.getStart());
double u1 = -Double.MAX_VALUE;
double u2 = Double.MAX_VALUE;
double a, b;
Point dir = line.getNormDir();
// X-Axis
if (dir.getX() == 0.0) {
// Line is parallel to AABB
if (A.getX() < 0.0 || B.getX() > 0.0) return false;
} else {
a = A.getX() / dir.getX();
b = B.getX() / dir.getX();
if (dir.getX() < 0.0) {
u1 = Math.max(u1, a);
u2 = Math.min(u2, b);
} else {
u1 = Math.max(u1, b);
u2 = Math.min(u2, a);
}
if (u2 < u1) return false;
}
// Y-Axis
if (dir.getY() == 0.0) {
if (A.getY() < 0.0 || B.getY() > 0.0) return false;
} else {
a = A.getY() / dir.getY();
b = B.getY() / dir.getY();
if (dir.getY() < 0.0) {
u1 = Math.max(u1, a);
u2 = Math.min(u2, b);
} else {
u1 = Math.max(u1, b);
u2 = Math.min(u2, a);
}
if (u2 < u1) return false;
}
// Z-Axis
if (dir.getZ() == 0.0) {
if (A.getZ() < 0.0 || B.getZ() > 0.0) return false;
} else {
a = A.getZ() / dir.getZ();
b = B.getZ() / dir.getZ();
if (dir.getZ() < 0.0) {
u1 = Math.max(u1, a);
u2 = Math.min(u2, b);
} else {
u1 = Math.max(u1, b);
u2 = Math.min(u2, a);
}
if (u2 < u1) return false;
}
return true;
}
@Override
public int hashCode() {
return min.hashCode() ^ max.hashCode();
}
public void print() {
System.out.println("min: " + min.getX() + ", " + min.getY() + ", " + min.getZ());
System.out.println("max: " + max.getX() + ", " + max.getY() + ", " + max.getZ());
}
public boolean writeToInventor(PrintStream s, double[] rgb) {
if (rgb.length < 3) return false;
s.println("Separator {");
s.printf("Material {\ndiffuseColor %.1f %.1f %.1f\n}\n", rgb[0], rgb[1], rgb[2]);
s.println("Coordinate3 {");
s.println("point [");
for (Point p : getCornerPoints()) {
s.printf("%.1f %.1f %.1f,%n", p.getX(), p.getY(), p.getZ());
}
s.println("]\n}");
s.println("IndexedLineSet {");
s.println("coordIndex [ 0, 1, 3, 2, 0, -1, 4, 5, 7, 6, 4, -1, 0, 4, -1, 1, 5, -1, 2, 6, -1, 3, 7, -1 ]");
s.println("}\n}");
return true;
}
}
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
/**
* Generic AABB Tree implementation using a binary tree structure.
*
* @param <E> The type of elements stored in the tree, which must be able to provide an AABB.
*/
public class AABBTree<E> {
private BVHStructures.Node<E> root;
private final Function<E, AABB> aabbFunction;
/**
* Creates an empty AABB tree.
*
* @param aabbFunction Function to extract an AABB from an element of type E
*/
public AABBTree(Function<E, AABB> aabbFunction) {
this.root = null;
this.aabbFunction = aabbFunction;
}
/**
* Builds the AABB tree from a list of elements.
*
* @param elements The list of elements to insert into the tree
* @param aabbFunction Function to extract an AABB from an element of type E
*/
public AABBTree(List<E> elements, Function<E, AABB> aabbFunction) {
this.aabbFunction = aabbFunction;
if (elements == null || elements.isEmpty()) {
this.root = null;
} else {
this.root = buildRecursive(elements);
}
}
/**
* Returns the root node of the AABB tree.
*/
public BVHStructures.Node<E> getRoot() {
return root;
}
/**
* Sets the root node manually.
*/
public void setRoot(BVHStructures.Node<E> root) {
this.root = root;
}
/**
* Recursively builds a balanced AABB tree from the list of elements.
*/
private BVHStructures.Node<E> buildRecursive(List<E> elements) {
if (elements.size() == 1) {
E elem = elements.get(0);
BVHStructures.Node<E> leaf = new BVHStructures.Node<>();
leaf.element = elem;
leaf.aabb = aabbFunction.apply(elem);
return leaf;
}
// Compute total AABB
AABB totalAabb = BVHStructures.getAggregateAABB(elements, aabbFunction);
int axis = BVHStructures.findLongestAxis(totalAabb);
// Sort elements along axis
elements.sort(Comparator.comparingDouble(e ->
aabbFunction.apply(e).getCenter().getComponent(axis)));
int mid = elements.size() / 2;
List<E> leftList = elements.subList(0, mid);
List<E> rightList = elements.subList(mid, elements.size());
BVHStructures.Node<E> node = new BVHStructures.Node<>();
node.aabb = totalAabb;
node.children.add(buildRecursive(leftList));
node.children.add(buildRecursive(rightList));
return node;
}
}
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import java.util.List;
/**
* @author Baris Numanoglu
*
* Utility class providing AABB-related helper methods.
*
* Contains functionality for computing axis-aligned bounding boxes (AABBs)
* from geometric data structures such as {@link ConcretePolygon} or {@link LinearRing}.
*
* All methods are static.
*/
public class AABBUtils {
public static AABB getAABB(ConcretePolygon poly) {
LinearRing ring = poly.getExteriorRing();
if (ring == null) return new AABB();
return computeAABBFromRing(ring);
}
private static AABB computeAABBFromRing(LinearRing ring) {
List<Vertex> vertices = ring.getVertices();
if (vertices == null || vertices.isEmpty()) {
return new AABB();
}
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
double maxZ = Double.NEGATIVE_INFINITY;
for (Vertex v : vertices) {
double x = v.getX();
double y = v.getY();
double z = v.getZ();
minX = Math.min(minX, x);
minY = Math.min(minY, y);
minZ = Math.min(minZ, z);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
maxZ = Math.max(maxZ, z);
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
}
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
/*
*
*
*/
public class AABB_BVH<E> {
private BVHStructures.Node<E> root;
public AABB_BVH() {
this.root = null;
}
private void deleteTree(BVHStructures.Node<E> node) {
for (BVHStructures.Node<E> child : node.children) {
deleteTree(child);
}
node.children.clear();
}
public void deleteTree() {
if (root != null) {
deleteTree(root);
}
}
public BVHStructures.Node<E> getRoot() {
return root;
}
public void setRoot(BVHStructures.Node<E> root) {
this.root = root;
}
}
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
public class BVHStructures {
/**
* Generic node used in BVH tree
*/
public static class Node<E> {
public List<Node<E>> children = new ArrayList<>();
public AABB aabb;
public E element;
public Node() {
Point zero = new Point();
this.aabb = new AABB(zero, zero);
this.element = null;
}
public boolean isLeaf() {
return children.isEmpty() && element != null;
}
}
/**
* Represents a pair of geometry lists resulting from a split
*/
public static class SplitStruct<E> {
public List<E> list1 = new ArrayList<>();
public List<E> list2 = new ArrayList<>();
}
/**
* Stack element used during recursive BVH tree construction
*/
public static class StackElement<E> {
public int level;
public Node<E> node;
public List<E> elements = new ArrayList<>();
public StackElement(int level, Node<E> node, List<E> elements) {
this.level = level;
this.node = node;
this.elements = elements;
}
}
/**
* Pair of AABBs and polygon lists used during splitting
*/
public static class SplitStructB<E> {
public AABB box1, box2;
public List<E> list1 = new ArrayList<>();
public List<E> list2 = new ArrayList<>();
}
/**
* Generic array-based BVH node.
* Stores an AABB and an associated element (typically a leaf object).
*/
public static class NodeB_Ar<E> {
public AABB aabb;
public E element;
public NodeB_Ar(AABB aabb, E element) {
this.aabb = aabb;
this.element = element;
}
}
/**
* Stack element used for array-based BVH construction
*/
public static class StackElementAr {
public int arrayIndex;
public int level;
public AABB aabb;
public List<ConcretePolygon> polygons = new ArrayList<>();
public StackElementAr(int arrayIndex, int level, AABB aabb, List<ConcretePolygon> polygons) {
this.arrayIndex = arrayIndex;
this.level = level;
this.aabb = aabb;
this.polygons = polygons;
}
}
/**
* Creates a comparator to sort elements by their AABB center X coordinate.
*/
public static <E> Comparator<E> compareCenterX(Function<E, AABB> aabbGetter) {
return Comparator.comparingDouble(e -> {
AABB box = aabbGetter.apply(e);
return (box.getMin().getX() + box.getMax().getX()) / 2.0;
});
}
/**
* Creates a comparator to sort elements by their AABB center Y coordinate.
*/
public static <E> Comparator<E> compareCenterY(Function<E, AABB> aabbGetter) {
return Comparator.comparingDouble(e -> {
AABB box = aabbGetter.apply(e);
return (box.getMin().getY() + box.getMax().getY()) / 2.0;
});
}
/**
* Creates a comparator to sort elements by their AABB center Z coordinate.
*/
public static <E> Comparator<E> compareCenterZ(Function<E, AABB> aabbGetter) {
return Comparator.comparingDouble(e -> {
AABB box = aabbGetter.apply(e);
return (box.getMin().getZ() + box.getMax().getZ()) / 2.0;
});
}
/**
* Sorts elements by AABB center along specified axis.
*
* @param elements the list to sort
* @param axis 0 = X, 1 = Y, 2 = Z
* @param aabbGetter a function to extract the AABB from an element
*/
public static <E> void sortElementsByCenterAxis(List<E> elements, int axis, Function<E, AABB> aabbGetter) {
switch (axis) {
case 0 -> elements.sort(compareCenterX(aabbGetter));
case 1 -> elements.sort(compareCenterY(aabbGetter));
case 2 -> elements.sort(compareCenterZ(aabbGetter));
default -> throw new IllegalArgumentException("Invalid axis index: " + axis);
}
}
/**
* Returns the index of the longest axis in the AABB
*/
public static int findLongestAxis(AABB aabb) {
double x = aabb.getMax().getX() - aabb.getMin().getX();
double y = aabb.getMax().getY() - aabb.getMin().getY();
double z = aabb.getMax().getZ() - aabb.getMin().getZ();
if (x > y && x > z) return 0;
if (y > x && y > z) return 1;
return 2;
}
/**
* Calculates the smallest AABB that encloses all given polygons
*/
public static AABB getAggregateAABB(List<ConcretePolygon> polygons) {
if (polygons.size() == 1) {
return AABBUtils.getAABB(polygons.get(0));
}
double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE;
double maxX = -Double.MAX_VALUE, maxY = -Double.MAX_VALUE, maxZ = -Double.MAX_VALUE;
for (ConcretePolygon p : polygons) {
AABB aabb = AABBUtils.getAABB(p);
minX = Math.min(minX, aabb.getMin().getX());
minY = Math.min(minY, aabb.getMin().getY());
minZ = Math.min(minZ, aabb.getMin().getZ());
maxX = Math.max(maxX, aabb.getMax().getX());
maxY = Math.max(maxY, aabb.getMax().getY());
maxZ = Math.max(maxZ, aabb.getMax().getZ());
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
public static <E> AABB getAggregateAABB(List<E> elements, Function<E, AABB> aabbFunc) {
double minX = Double.POSITIVE_INFINITY, minY = Double.POSITIVE_INFINITY, minZ = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY, maxZ = Double.NEGATIVE_INFINITY;
for (E e : elements) {
AABB aabb = aabbFunc.apply(e);
minX = Math.min(minX, aabb.getMin().getX());
minY = Math.min(minY, aabb.getMin().getY());
minZ = Math.min(minZ, aabb.getMin().getZ());
maxX = Math.max(maxX, aabb.getMax().getX());
maxY = Math.max(maxY, aabb.getMax().getY());
maxZ = Math.max(maxZ, aabb.getMax().getZ());
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
}
\ No newline at end of file
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
import java.io.PrintStream;
/**
* Represents a 3D line segment defined by a start and end point.
* Useful for geometric computations like intersection tests,
* ray casting, or clipping against bounding volumes such as AABBs.
*/
public class Line {
private Point start;
private Point end;
public Line() {
this.start = new Point();
this.end = new Point();
}
public Line(Point start, Point end) {
this.start = start;
this.end = end;
}
public Point getStart() {
return start;
}
public Point getEnd() {
return end;
}
// Calculates Bounding Box for this Line
public AABB getAABB() {
return new AABB(start, end);
}
/*
* calculates the square of length of the line
* to avoid squarerooth-operation
* */
public double getLengthSquared() {
double dx = end.getX() - start.getX();
double dy = end.getY() - start.getY();
double dz = end.getZ() - start.getZ();
return dx * dx + dy * dy + dz * dz;
}
// Direction vector
public Point getDir() {
return new Point(
end.getX() - start.getX(),
end.getY() - start.getY(),
end.getZ() - start.getZ()
);
}
// Normal vector
public Point getNormDir() {
double length = Math.sqrt(getLengthSquared());
if (length == 0) {
return new Point(0, 0, 0);
}
return new Point(
(end.getX() - start.getX()) / length,
(end.getY() - start.getY()) / length,
(end.getZ() - start.getZ()) / length
);
}
public void print() {
System.out.println("Line from " + start + " to " + end);
}
// writes Line as Inventor-Data
public boolean writeToInventor(PrintStream s) {
s.println("Separator {");
s.println("Material { diffuseColor 1 0 0 }"); // RGB:1 0 0 ->red
s.println("Coordinate3 {");
s.println("point [");
s.printf("%.3f %.3f %.3f,%n", start.getX(), start.getY(), start.getZ());
s.printf("%.3f %.3f %.3f%n", end.getX(), end.getY(), end.getZ());
s.println("]");
s.println("}");
s.println("IndexedLineSet { coordIndex [ 0, 1, -1 ] }");
s.println("}");
return true;
}
}
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
import java.util.*;
import de.hft.stuttgart.citydoctor2.datastructure.ConcretePolygon;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Vertex;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing.LinearRingType;
public class Main {
/**
* Entry point for AABB testing
*/
public static void main(String[] args) {
System.out.println("=== AABB TEST ===");
ConcretePolygon polygon = new ConcretePolygon();
LinearRing exterior = new LinearRing(LinearRingType.EXTERIOR);
exterior.addVertex(new Vertex(0, 0, 0));
exterior.addVertex(new Vertex(1, 0, 0));
exterior.addVertex(new Vertex(0, 1, 0));
exterior.addVertex(new Vertex(0, 0, 0));
polygon.setExteriorRing(exterior);
// Define points of a tetrahedron
Point p1 = new Point(0, 0, 0);
Point p2 = new Point(1, 0, 0);
Point p3 = new Point(0.5, 1, 0);
Point p4 = new Point(0.5, 0.5, 1);
// Define triangle faces of the tetrahedron
ConcretePolygon base = new ConcretePolygon();
LinearRing baseRing = new LinearRing(LinearRingType.EXTERIOR);
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
baseRing.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
baseRing.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
baseRing.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
base.setExteriorRing(baseRing);
ConcretePolygon side1 = new ConcretePolygon();
LinearRing ring1 = new LinearRing(LinearRingType.EXTERIOR);
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring1.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring1.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring1.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
side1.setExteriorRing(ring1);
ConcretePolygon side2 = new ConcretePolygon();
LinearRing ring2 = new LinearRing(LinearRingType.EXTERIOR);
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
ring2.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring2.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring2.addVertex(new Vertex(p2.getX(), p2.getY(), p2.getZ()));
side2.setExteriorRing(ring2);
ConcretePolygon side3 = new ConcretePolygon();
LinearRing ring3 = new LinearRing(LinearRingType.EXTERIOR);
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
ring3.addVertex(new Vertex(p1.getX(), p1.getY(), p1.getZ()));
ring3.addVertex(new Vertex(p4.getX(), p4.getY(), p4.getZ()));
ring3.addVertex(new Vertex(p3.getX(), p3.getY(), p3.getZ()));
side3.setExteriorRing(ring3);
List<ConcretePolygon> poly = Arrays.asList(base, side1, side2, side3);
AABB aabb = BVHStructures.getAggregateAABB(poly);
System.out.println("\nComputed AABB:");
aabb.print();
// Point with Test Properties
Point testInside = new Point(0.5, 0.5, 0.3);
Point testOutside = new Point(2.0, 2.0, 2.0);
Point testOnEdge = new Point(1.0, 1.0, 0.0);
System.out.println("\nTest point (inside): " + testInside);
System.out.println("-> contained?: " + aabb.encloses(testInside));
System.out.println("\nTest point (outside): " + testOutside);
System.out.println("-> contained?: " + aabb.encloses(testOutside));
System.out.println("\nTest point (on edge): " + testOnEdge);
System.out.println("-> contained?: " + aabb.encloses(testOnEdge));
}}
package de.hft.stuttgart.citydoctor2.healer.aabb.bht;
public class Point {
private double x, y, z;
public Point() {
this(0.0, 0.0, 0.0);
}
public Point(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public Point(Point other) {
this.x = other.getX();
this.y = other.getY();
this.z = other.getZ();
}
/* Operators */
public Point add(Point p) {
return new Point(this.x + p.x, this.y + p.y, this.z + p.z);
}
public Point subtract(Point p) {
return new Point(this.x - p.x, this.y - p.y, this.z - p.z);
}
public Point multiply(double a) {
return new Point(this.x * a, this.y * a, this.z * a);
}
public Point divide(double a) {
if (a == 0) throw new ArithmeticException("Zero Division!");
return new Point(this.x / a, this.y / a, this.z / a);
}
public boolean equals(Point p) {
return this.x == p.x && this.y == p.y && this.z == p.z;
}
public boolean notEquals(Point p) {
return !this.equals(p);
}
public boolean isSmallerThan(Point p) {
return this.x < p.x && this.y < p.y && this.z < p.z;
}
// Bounding Box of a Point
public AABB getAABB() {
return new AABB(this, this);
}
/*
* calculates the square of length of the line
* to avoid squarerooth-operation
* */
public double getLengthSquared() {
double tol = 1e-3;
double val = x * x + y * y + z * z;
return val < tol ? 0.0 : val;
}
/* Getter and Setter */
public double getComponent(int axis) {
return switch (axis) {
case 0 -> getX();
case 1 -> getY();
case 2 -> getZ();
default -> throw new IllegalArgumentException("Axis must be 0, 1, or 2.");
};
}
public double getX() { return x; }
public double getY() { return y; }
public double getZ() { return z; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
public void setZ(double z) { this.z = z; }
// Debug-Aid-Print
public void print() {
System.out.println("Point(" + x + ", " + y + ", " + z + ")");
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment