/** * Liefert den Punkt mit dem angegebenen Index * * @param point Der Punkt * @return Der Punkt (cached) */ @ReturnsCachedValue @NotNull public final Vector3 getCornerPoint(@NotNull final BoxPoint point) { assert extent.x >= 0; assert extent.y >= 0; assert extent.z >= 0; final int id = point.pointId; return Vector3.createNew( ((id & 1) == 0) ? (center.x - extent.x) : (center.x + extent.x), ((id & 2) == 0) ? (center.y - extent.y) : (center.y + extent.y), ((id & 4) == 0) ? (center.z + extent.z) : (center.z - extent.z) // NOTE: OpenGL macht's andersrum! ); }
/** * Strahl im 3D-Raum, bestehend aus projectPointg und Richtung * * @see RayFactory */ public final class Ray3 { /** Instanz, die die Verwaltung nicht länger benötigter Instanzen übernimmt */ public static final IObjectCache<Ray3> Cache = new ThreadLocalObjectCache<Ray3>( new ObjectFactory<Ray3>() { @NotNull @Override public Ray3 createNew() { return new Ray3(); } }); /** * Erzeugt eine neue Ray-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand des Rays kann korrupt sein! * * @param originX Der Ursprung (X-Komponente) * @param originY Der Ursprung (Y-Komponente) * @param originZ Der Ursprung (Z-Komponente) * @param directionX Die RiprojectPoint(X-Komponente) * @param directionY Die Richtung (Y-Komponente) * @param directionZ Die Richtung (Z-Komponente) * @return Der neue oder aufbereitete Vektor * @see #Cache */ @NotNull public static Ray3 createNew( final float originX, final float originY, final float originZ, final float directionX, final float directionY, final float directionZ) { return Cache.getOrCreate().set(originX, originY, originZ, directionX, directionY, directionZ); } /** * Erzeugt eine neue {@link Ray3}-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand kann korrupt sein! * * @param origin Der Ursprung * @param direction Die Richtung * @return Der neue oder aufbereitete Ray * @see #Cache */ public static Ray3 createNew(@NotNull final Vector3 origin, @NotNull final Vector3 direction) { return Cache.getOrCreate().set(origin, direction); } /** * Erzeugt eine neue {@link Ray3}-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand kann korrupt sein! * * @param other Der zu kopierende Ray * @return Der neue oder aufbereitete Ray * @see #Cache */ public static Ray3 createNew(@NotNull final Ray3 other) { return Cache.getOrCreate().set(other); } /** * Erzeugt eine neue {@link Ray3}-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand kann korrupt sein! * * @return Der neue oder aufbereitete Ray * @see #Cache */ @NotNull public static Ray3 createNew() { return Cache.getOrCreate(); } /** * Recyclet einen Ray * * @param box Der zu recyclende Ray * @see #Cache * @see AxisAlignedBox#recycle() */ public static void recycle(@NotNull final Ray3 box) { Cache.registerElement(box); } /** * Registriert diesen Ray für das spätere Cache * * @see #Cache * @see Vector3#recycle(Vector3) */ public void recycle() { Cache.registerElement(this); } /** Der Ursprung des Strahls */ @NotNull public final Vector3 origin = Vector3.createNew(); /** * Die Richtung des Strahls * * <p> * * <h3>Vektor nicht manuell setzen!</h3> * * Der Vektor muss über die Methode {@link #setDirection(Vector3)} oder {@link * #setDirection(float, float, float)} gesetzt werden! * * @see #invDirection * @see #setDirection(Vector3) * @see #setDirection(float, float, float) */ @NotNull public final Vector3 direction = Vector3.createNew(0.57735f, 0.57735f, 0.57735f); /** * Die reziproke Richtung des Strahls * * @see #direction * @see #setDirection(Vector3) * @see #setDirection(float, float, float) */ @NotNull public final Vector3 invDirection = Vector3.createNew(1.0f / 0.57735f, 1.0f / 0.57735f, 1.0f / 0.57735f); /** Normalokonstruktor */ private Ray3() {} /** * Setzt Ursprung projectPointhtung * * @param origin Der Ursprung * @param direction Die Richtung */ private Ray3(@NotNull final Vector3 origin, @NotNull final Vector3 direction) { set(origin, direction); } /** * Setzt den Ursprung des Strahls * * @param origin Der Ursprung * @return Dieselbe Instanz für method chaining */ @NotNull public Ray3 setOrigin(@NotNull final Vector3 origin) { this.origin.set(origin); return this; } /** * Setzt den Ursprung des Strahls * * @param originX Der Ursprung (X-Komponente) * @param originY Der Ursprung (Y-Komponente) * @param originZ Der Ursprung (Z-Komponente) * @return Dieselbe Instanz für method chaining */ @NotNull public Ray3 setOrigin(final float originX, final float originY, final float originZ) { this.origin.set(originX, originY, originZ); return this; } /** * Setzt die Richtung des Strahls * * @param direction Die Richtung * @return Dieselbe Instanz für method chaining */ @NotNull public Ray3 setDirection(@NotNull final Vector3 direction) { return setDirection(direction.x, direction.y, direction.z); } /** * Setzt die Richtung des Strahls * * @param directionX Die Richtung (X-Komponente) * @param directionY Die Richtung (Y-Komponente) * @param directionZ Die Richtung (Z-Komponente) * @return Dieselbe Instanz für method chaining */ @NotNull public Ray3 setDirection(final float directionX, final float directionY, final float directionZ) { this.direction.set(directionX, directionY, directionZ).normalize(); this.invDirection.set( 1.0f / this.direction.x, 1.0f / this.direction.y, 1.0f / this.direction.z); return this; } /** * Kopiert einen Strahl * * @param other Der zu kopierende Ray * @return Dieselbe Instanz für method chaining */ @NotNull public Ray3 set(@NotNull final Ray3 other) { return set(other.origin, other.direction); } /** * Setzt Ursprung und Richtung des Strahls * * @param origin Der Ursprung * @param direction Die Richtung * @return Dieselbe Instanz für method chaining */ @NotNull public Ray3 set(@NotNull final Vector3 origin, @NotNull final Vector3 direction) { setOrigin(origin.x, origin.y, origin.z); setDirection(direction.x, direction.y, direction.z); return this; } /** * Setzt Ursprung und Richtung des Strahls * * @param originX Der Ursprung (X-Komponente) * @param originY Der Ursprung (Y-Komponente) * @param originZ Der Ursprung (Z-Komponente) * @param directionX Die Richtung (X-Komponente) * @param directionY Die Richtung (Y-Komponente) * @param directionZ Die Richtung (Z-Komponente) * @return Dieselbe Instanz für method chaining */ @NotNull public Ray3 set( final float originX, final float originY, final float originZ, final float directionX, final float directionY, final float directionZ) { setOrigin(originX, originY, originZ); setDirection(directionX, directionY, directionZ); return this; } /** * Bezieht einen Strahl, der in die entgegengesetzte Richtung zeigt * * @return Der Strahl */ @NotNull @ReturnsCachedValue public Ray3 getInverted() { Vector3 inverted = direction.getInverted(); Ray3 ray = Ray3.createNew(origin, inverted); inverted.recycle(); return ray; } /** * Dreht den Strahl um, so dass er in die entgegengesetzte Richtung zeigt * * @return Diese Instanz für method chaining */ @NotNull public Ray3 invert() { direction.invert(); invDirection.set(1.0f / direction.x, 1.0f / direction.y, 1.0f / direction.z); return this; } /** * Berechnet die Distanz eines Punktes zu diesem Strahl * * @param point Der Punkt * @return Die berechnete Distanz */ public float getDistanceFromPoint(@NotNull final Vector3 point) { // http://answers.yahoo.com/question/index?qid=20080912194015AAIlm9X Vector3 w = point.sub(origin).crossInPlace(direction); float length = w.getLength(); // aufräumen und raus hier w.recycle(); return length; } /** * Projiziert einen Punkt auf den Strahl * * @param point Der Punkt * @return Der projizierte Punkt */ @NotNull @ReturnsCachedValue public Vector3 projectPoint(@NotNull final Vector3 point) { // Richtung bestimmen und auf Richtungsvektor projizieren Vector3 w = point.sub(origin); Vector3 projected = direction.mul( w.dot( direction)); // TODO: Als static herausziehen, damit für diesen Test das Caching der // anderen Werte nicht nötig ist w.recycle(); return projected; } /** * Liefert einen Punkt auf dem Strahl anhand eines Skalars <code>t</code>, so dass gilt: * * <p><code>P = t*{@link #direction} + {@link #origin}</code> * * @param t Der Skalar * @return Der Punkt auf dem Strahl */ @ReturnsCachedValue @NotNull public Vector3 getPoint(final float t) { return direction.mul(t).addInPlace(origin); } }
/** Symmetrische Box */ public final class AxisAlignedBox { /** Instanz, die die Verwaltung nicht länger benötigter Instanzen übernimmt */ public static final IObjectCache<AxisAlignedBox> Cache = new ThreadLocalObjectCache<AxisAlignedBox>( new ObjectFactory<AxisAlignedBox>() { @NotNull @Override public AxisAlignedBox createNew() { return new AxisAlignedBox(); } }); /** * Erzeugt eine neue Vektor-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand der Vektors kann korrupt sein! * * @param centerX Die Position (X-Komponente) * @param centerY Die Position (Y-Komponente) * @param centerZ Die Position (Z-Komponente) * @param extentX Der Maximalvektor (X-Komponente) * @param extentY Der Maximalvektor (Y-Komponente) * @param extentZ Der Maximalvektor (Z-Komponente) * @return Der neue oder aufbereitete Vektor * @see #Cache */ @NotNull public static AxisAlignedBox createNew( final float centerX, final float centerY, final float centerZ, final float extentX, final float extentY, final float extentZ) { return Cache.getOrCreate().set(centerX, centerY, centerZ, extentX, extentY, extentZ); } /** * Erzeugt eine neue {@link AxisAlignedBox}-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand der Vektors kann korrupt sein! * * @param other Die zu kopierende Box * @return Die neue oder aufbereitete Box * @see #Cache */ public static AxisAlignedBox createNew(@NotNull final AxisAlignedBox other) { return Cache.getOrCreate().set(other); } /** * Erzeugt eine neue {@link AxisAlignedBox}-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand der Vektors kann korrupt sein! * * @param center Der Mittelpunkt der Box * @param extent Der Maximalvektor der Box * @return Die neue oder aufbereitete Box * @see #Cache */ public static AxisAlignedBox createNew( @NotNull final Vector3 center, @NotNull final Vector3 extent) { return Cache.getOrCreate().set(center, extent); } /** * Erzeugt eine neue {@link AxisAlignedBox}-Instanz. * * <p><strong>Hinweis:</strong> Der Zustand der Box kann korrupt sein! * * @return Der neue oder aufbereitete Vektor * @see #Cache */ @NotNull public static AxisAlignedBox createNew() { return Cache.getOrCreate(); } /** * Recyclet eine Box * * @param box Die zu recyclende Box * @see #Cache * @see AxisAlignedBox#recycle() */ public static void recycle(@NotNull final AxisAlignedBox box) { Cache.registerElement(box); } /** * Registriert diesen Vektor für das spätere Cache * * @see #Cache * @see Vector3#recycle(Vector3) */ public void recycle() { Cache.registerElement(this); } /** Der Mittelpunkt der Box */ @NotNull public final Vector3 center = Vector3.createNew(); /** * Der (absolute) Maximalvektor der Box. * * <p> * * <h3>Hinweis zu den Werten des Vektors</h3> * * Die Werte des Vektors müssen immer positiv sein. Um dies sicherzustellen, sollten Veränderungen * an diesem Vektor ausschließlich über die Methoden {@link #setExtent(Vector3)} oder {@link * #setExtent(float, float, float)} erfolgen. * * <p>Ist ein manuelles Setzen der Werte zwingend erforderlich, muss im Anschluss auf dem Vektor * {@link de.widemeadows.projectcore.math.Vector3#makeAbsolute()} aufgerufen werden. */ @NotNull public final Vector3 extent = Vector3.createNew(); /** Erzeugt eine neue Instanz der {@link AxisAlignedBox}-Klasse */ private AxisAlignedBox() { this.extent.set(0.5f, 0.5f, 0.5f); } /** * Erzeugt eine neue Instanz der {@link AxisAlignedBox}-Klasse * * @param center Der Mittelpunkt der Box * @param extent Der Maximalvektor der Box */ private AxisAlignedBox(@NotNull final Vector3 center, @NotNull final Vector3 extent) { this.center.set(center); this.extent.set(extent).makeAbsolute(); } /** * Setzt Mittelpunkt und Dimensionen der Box * * @param other Die zu kopierende Box * @return Diese Instanz für method chaining */ public final AxisAlignedBox set(@NotNull final AxisAlignedBox other) { return set(other.center, other.extent); } /** * Setzt Mittelpunkt und Dimensionen der Box * * @param center Der Mittelpunkt * @param extent Die Dimensionen * @return Diese Instanz für method chaining */ public final AxisAlignedBox set(@NotNull final Vector3 center, @NotNull final Vector3 extent) { setCenter(center); setExtent(extent); return this; } /** * Setzt Mittelpunkt und Dimensionen der Box * * @param centerX Die Position (X-Komponente) * @param centerY Die Position (Y-Komponente) * @param centerZ Die Position (Z-Komponente) * @param extentX Der Maximalvektor (X-Komponente) * @param extentY Der Maximalvektor (Y-Komponente) * @param extentZ Der Maximalvektor (Z-Komponente) * @return Diese Instanz für method chaining */ public final AxisAlignedBox set( final float centerX, final float centerY, final float centerZ, final float extentX, final float extentY, final float extentZ) { setCenter(centerX, centerY, centerZ); setExtent(extentX, extentY, extentZ); return this; } /** * Setzt die Dimensionen der Box * * @param extent Der Maximalvektor * @return Diese Instanz für method chaining */ public final AxisAlignedBox setExtent(@NotNull final Vector3 extent) { this.extent.set(extent).makeAbsolute(); return this; } /** * Setzt die Dimensionen der Box * * @param x Der Maximalvektor (X-Komponente) * @param y Der Maximalvektor (Y-Komponente) * @param z Der Maximalvektor (Z-Komponente) * @return Diese Instanz für method chaining */ public final AxisAlignedBox setExtent(final float x, final float y, final float z) { this.extent.set(x, y, z).makeAbsolute(); return this; } /** * Setzt die Position der Box * * @param center Die Position * @return Diese Instanz für method chaining */ public final AxisAlignedBox setCenter(@NotNull final Vector3 center) { this.center.set(center); return this; } /** * Setzt die Position der Box * * @param x Die Position (X-Komponente) * @param y Die Position (Y-Komponente) * @param z Die Position (Z-Komponente) * @return Diese Instanz für method chaining */ public final AxisAlignedBox setCenter(final float x, final float y, final float z) { this.center.set(x, y, z); return this; } /** * Liefert den Punkt mit dem angegebenen Index * * @param point Der Punkt * @return Der Punkt (cached) */ @ReturnsCachedValue @NotNull public final Vector3 getCornerPoint(@NotNull final BoxPoint point) { assert extent.x >= 0; assert extent.y >= 0; assert extent.z >= 0; final int id = point.pointId; return Vector3.createNew( ((id & 1) == 0) ? (center.x - extent.x) : (center.x + extent.x), ((id & 2) == 0) ? (center.y - extent.y) : (center.y + extent.y), ((id & 4) == 0) ? (center.z + extent.z) : (center.z - extent.z) // NOTE: OpenGL macht's andersrum! ); } /** * Berechnet die Fläche der Box * * @return Die Fläche */ public final float calculateArea() { // return (2*extent.x) * (2*extent.y) * (2*extent.z); return 8 * extent.x * extent.y * extent.z; } /** * Ermittelt, ob ein Punkt auf oder in der Box liegt * * @param vector Der zu prüfende Vektor * @return <code>true</code>, wenn der Punkt auf oder in der Box liegt */ public final boolean intersects(@NotNull final Vector3 vector) { return intersects(vector.x, vector.y, vector.z); } /** * Ermittelt, ob ein Punkt auf oder in der Box liegt * * @param x Der zu prüfende Vektor (X-Komponente) * @param y Der zu prüfende Vektor (Y-Komponente) * @param z Der zu prüfende Vektor (Z-Komponente) * @return <code>true</code>, wenn der Punkt auf oder in der Box liegt */ public final boolean intersects(float x, float y, float z) { assert extent.x >= 0; assert extent.y >= 0; assert extent.z >= 0; // Koordinaten in Box-Space transformieren x -= center.x; y -= center.y; z -= center.z; // Punkte beziehen final float maxX = extent.x; final float maxY = extent.y; final float maxZ = extent.z; final float minX = -maxX; final float minY = -maxY; final float minZ = -maxZ; // Test durchführen return x >= minX && x <= maxX && y >= minY && y <= maxY && z >= minZ && z <= maxZ; } /** * Überprüft, ob ein Strahl die Box schneidet * * @param ray Der Strahl * @param nearBound Der nähste, gültige Punkt * @param farBound Der weiteste, gültige Punkt * @return <code>true</code>, wenn der Strahl die Box schneidet */ public final boolean intersects( @NotNull final Ray3 ray, final float nearBound, final float farBound) { return !Float.isNaN(getIntersectionF(ray, nearBound, farBound)); } /** * Überprüft, ob ein Strahl die Box schneidet und liefert den Schnittpunkt in Form eines Skalars * t, so dass <code>ray.getPoint(t)}</code> den Vektor ergibt. * * @param ray Der Strahl * @return <code>{@link Float#NaN}</code>, wenn der Strahl die Box nicht schneidet, ansonsten die * Distanz vom Ursprung des Strahles */ public final float getIntersectionF(@NotNull final Ray3 ray) { float txmin, txmax, tymin, tymax, tzmin, tzmax; final float divX = ray.invDirection.x; final float divY = ray.invDirection.y; final float divZ = ray.invDirection.z; if (divX >= 0) { txmin = (center.x - extent.x - ray.origin.x) * divX; txmax = (center.x + extent.x - ray.origin.x) * divX; } else { txmin = (center.x + extent.x - ray.origin.x) * divX; txmax = (center.x - extent.x - ray.origin.x) * divX; } if (divY >= 0) { tymin = (center.y - extent.y - ray.origin.y) * divY; tymax = (center.y + extent.y - ray.origin.y) * divY; } else { tymin = (center.y + extent.y - ray.origin.y) * divY; tymax = (center.y - extent.y - ray.origin.y) * divY; } if ((txmin > tymax) || (tymin > txmax)) return Float.NaN; if (tymin > txmin) txmin = tymin; if (tymax < txmax) txmax = tymax; if (divZ >= 0) { tzmin = (center.z - extent.z - ray.origin.z) * divZ; tzmax = (center.z + extent.z - ray.origin.z) * divZ; } else { tzmin = (center.z + extent.z - ray.origin.z) * divZ; tzmax = (center.z - extent.z - ray.origin.z) * divZ; } if ((txmin > tzmax) || (tzmin > txmax)) return Float.NaN; if (tzmin > txmin) txmin = tzmin; return txmin; } /** * Überprüft, ob ein Strahl die Box schneidet und liefert den Schnittpunkt in Form eines Skalars * t, so dass <code>ray.getPoint(t)}</code> den Vektor ergibt. * * @param ray Der Strahl * @param nearBound Der nähste, gültige Punkt * @param farBound Der weiteste, gültige Punkt * @return <code>{@link Float#NaN}</code>, wenn der Strahl die Box nicht innerhalb der Range * schneidet, ansonsten die Distanz vom Ursprung des Strahles */ public final float getIntersectionF( @NotNull final Ray3 ray, final float nearBound, final float farBound) { float txmin, txmax, tymin, tymax, tzmin, tzmax; final float divX = ray.invDirection.x; final float divY = ray.invDirection.y; final float divZ = ray.invDirection.z; if (divX >= 0) { txmin = (center.x - extent.x - ray.origin.x) * divX; txmax = (center.x + extent.x - ray.origin.x) * divX; } else { txmin = (center.x + extent.x - ray.origin.x) * divX; txmax = (center.x - extent.x - ray.origin.x) * divX; } if (divY >= 0) { tymin = (center.y - extent.y - ray.origin.y) * divY; tymax = (center.y + extent.y - ray.origin.y) * divY; } else { tymin = (center.y + extent.y - ray.origin.y) * divY; tymax = (center.y - extent.y - ray.origin.y) * divY; } if ((txmin > tymax) || (tymin > txmax)) return Float.NaN; if (tymin > txmin) txmin = tymin; if (tymax < txmax) txmax = tymax; if (divZ >= 0) { tzmin = (center.z - extent.z - ray.origin.z) * divZ; tzmax = (center.z + extent.z - ray.origin.z) * divZ; } else { tzmin = (center.z + extent.z - ray.origin.z) * divZ; tzmax = (center.z - extent.z - ray.origin.z) * divZ; } if ((txmin > tzmax) || (tzmin > txmax)) return Float.NaN; if (tzmin > txmin) txmin = tzmin; // if (tzmax < txmax) txmax = tzmax; // return ((txmin >= nearBound) && (txmax <= farBound)) ? txmin : Float.NaN; return ((txmin >= nearBound) && (txmin <= farBound)) ? txmin : Float.NaN; } /** * Überprüft, ob ein Strahl die Box schneidet und liefert den Schnittpunkt in Form eines Skalars * t, so dass <code>ray.getPoint(t)}</code> den Vektor ergibt. * * @param ray Der Strahl * @param nearBound Der nähste, gültige Punkt * @param farBound Der weiteste, gültige Punkt * @return <code>null</code>, wenn der Strahl die Box nicht innerhalb der Range schneidet, * ansonsten der Punkt */ @Nullable @ReturnsCachedValue public final Vector3 getIntersectionV( @NotNull final Ray3 ray, final float nearBound, final float farBound) { final float t = getIntersectionF(ray, nearBound, farBound); return Float.isNaN(t) ? null : ray.getPoint(t); } /** * Überprüft, ob ein Strahl die Box schneidet und liefert den Schnittpunkt in Form eines Vektors. * * @param ray Der Strahl * @return <code>null</code>, wenn der Strahl die Box nicht schneidet, ansonsten der Punkt */ @Nullable @ReturnsCachedValue public final Vector3 getIntersectionV(@NotNull final Ray3 ray) { final float t = getIntersectionF(ray); return Float.isNaN(t) ? null : ray.getPoint(t); } }