diff --git a/Doubles_README.md b/Doubles_README.md new file mode 100644 index 00000000..7bbe67e9 --- /dev/null +++ b/Doubles_README.md @@ -0,0 +1,35 @@ +# toxiclibs +Official master repo (Git version) + +Worked up versions of the files in + toxi.geom + toxi.geom.mesh + toxi.geom.mesh2d + test +which are based on doubles. +Needed this for my own puropses, as I am developing a CNC machine with 30 meter scale, and 0.01mm resolution. + +Tactic was to make a new double based class for each of the current toxicLib classes. +Appended D to the class name where it was just a word ie + SphereD vs Sphere +Where the current class defined dimensionality, put the D preceeding the dimensionality ie + VecD3D vs Vec3D + +Some few of the existing classes have new double methods inserted as they were allready of mixed precisions. +These include + toxi.math.MathUtils as only a double version of random and EPSD for a double epsilon + STLReader no double version is appropriate, as STL has only 32bit numbers + STLWriter convert any incomming double classes to floats before writing. + Matrix The existing 3 flavors are mixed mode, no changes made. + GMatrix is mixed mode, no changes made. + +For those classes which have constructors from self-same classes, and which have constructors from more primitive types, included cross +precision constructors, and a file in test for same. +These classes are: + AABB AABBD + Circle CircleD + Quaternion QuaternionD + Sphere SphereD + Vec2D Vec3D + Vec4D VecD2D + VecD3D VecD4D diff --git a/src.core/toxi/geom/AABB.java b/src.core/toxi/geom/AABB.java index f60fae0c..1d532c6b 100644 --- a/src.core/toxi/geom/AABB.java +++ b/src.core/toxi/geom/AABB.java @@ -102,6 +102,16 @@ public AABB() { public AABB(AABB box) { this(box, box.getExtent()); } + + /** + * Creates an independent copy of the passed in boxD + * + * @param box + */ + public AABB(AABBD boxd) { + this(new Vec3D((float)boxd.x,(float)boxd.y,(float)boxd.z),new Vec3D(boxd.getExtent())); + System.out.println("min="+min+" max="+max); + } /** * Creates a new box of the given size at the world origin. @@ -559,8 +569,12 @@ public Mesh3D toMesh(Mesh3D mesh) { */ public String toString() { StringBuffer sb = new StringBuffer(); - sb.append(" pos: ").append(super.toString()).append(" ext: ") - .append(extent); + //sb.append(" pos: ").append(super.toString()).append(" ext: ").append(extent); + sb.append( " pos: ").append(super.toString()) + .append("\n ext: ").append(extent) + .append("\n min: ").append(getMin()) + .append("\n max: ").append(getMax()) + ; return sb.toString(); } diff --git a/src.core/toxi/geom/AABBD.java b/src.core/toxi/geom/AABBD.java new file mode 100644 index 00000000..7513fe56 --- /dev/null +++ b/src.core/toxi/geom/AABBD.java @@ -0,0 +1,613 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; + +import toxi.geom.mesh.MeshD3D; +import toxi.geom.mesh.TriangleMeshD; +import toxi.math.MathUtils; + +/** + * Axis-aligned bounding box with basic intersection features for Ray, AABBD and + * Sphere classes. + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class AABBD extends VecD3D implements ShapeD3D { + + /** + * Creates a new instance from two vectors specifying opposite corners of + * the box + * + * @param min + * first corner point + * @param max + * second corner point + * @return new AABBD with centre at the half point between the 2 input + * vectors + */ + public static final AABBD fromMinMax(VecD3D min, VecD3D max) { + VecD3D a = VecD3D.min(min, max); + VecD3D b = VecD3D.max(min, max); + return new AABBD(a.interpolateTo(b, 0.5f), b.sub(a).scaleSelf(0.5f)); + } + + /** + * Factory method, computes & returns the bounding box for the given list of + * points. + * + * @param points + * @return bounding rect + */ + public static final AABBD getBoundingBox(List points) { + if (points == null || points.size() == 0) { + return null; + } + VecD3D first = points.get(0); + VecD3D min = first.copy(); + VecD3D max = first.copy(); + for (VecD3D p : points) { + min.minSelf(p); + max.maxSelf(p); + } + return fromMinMax(min, max); + } + + @XmlElement(required = true) + protected VecD3D extent; + + @XmlTransient + protected VecD3D min, max; + + public AABBD() { + super(); + setExtent(new VecD3D()); + } + + /** + * Creates an independent copy of the passed in boxD + * + * @param box + */ + public AABBD(AABBD boxd) { + this(boxd, boxd.getExtent()); + } + /** + * Creates an independent copy of the passed in box + * + * @param box + */ + public AABBD(AABB box) { + this(new VecD3D(box.x,box.y,box.z),new VecD3D(box.getExtent())); + } + /** + * Creates a new box of the given size at the world origin. + * + * @param extent + */ + public AABBD(double extent) { + this(new VecD3D(), extent); + } + + /** + * Creates a new instance from centre point and uniform extent in all + * directions. + * + * @param pos + * @param extent + */ + public AABBD(ReadonlyVecD3D pos, double extent) { + super(pos); + setExtent(new VecD3D(extent, extent, extent)); + } + + /** + * Creates a new instance from centre point and extent + * + * @param pos + * @param extent + * box dimensions (the box will be double the size in each + * direction) + */ + public AABBD(ReadonlyVecD3D pos, ReadonlyVecD3D extent) { + super(pos); + setExtent(extent); + } + + //public boolean containsPoint(ReadonlyVecD3D p) { + // return p.isInAABBD(this); + //} + public boolean containsPoint(ReadonlyVecD3D p) { + return p.isInAABBD(this); + } + + public AABBD copy() { + return new AABBD(this); + } + + public SphereD getBoundingSphere() { + return new SphereD(this, extent.magnitude()); + } + + /** + * Returns the current box size as new VecD3D instance (updating this vector + * will NOT update the box size! Use {@link #setExtent(ReadonlyVecD3D)} for + * those purposes) + * + * @return box size + */ + public final VecD3D getExtent() { + return extent.copy(); + } + + public final VecD3D getMax() { + // return this.add(extent); + return max.copy(); + } + + public final VecD3D getMin() { + return min.copy(); + } + + + public VecD3D getNormalForPoint(ReadonlyVecD3D p) { + p = p.sub(this); + VecD3D pabs = extent.sub(p.getAbs()); + VecD3D psign = p.getSignum(); + VecD3D normal = VecD3D.X_AXIS.scale(psign.x); + double minDist = pabs.x; + if (pabs.y < minDist) { + minDist = pabs.y; + normal = VecD3D.Y_AXIS.scale(psign.y); + } + if (pabs.z < minDist) { + normal = VecD3D.Z_AXIS.scale(psign.z); + } + return normal; + } + + /** + * Adjusts the box size and position such that it includes the given point. + * + * @param p + * point to include + * @return itself + */ + + public AABBD growToContainPoint(ReadonlyVecD3D p) { + min.minSelf(p); + max.maxSelf(p); + set(min.interpolateTo(max, 0.5)); + extent.set(max.sub(min).scaleSelf(0.5)); + return this; + } + + /** + * Checks if the box intersects the passed in one. + * + * @param box + * box to check + * @return true, if boxes overlap + */ + //public boolean intersectsBox(AABBD box) { + // VecD3D t = box.sub(this); + // return MathUtils.abs(t.x) <= (extent.x + box.extent.x) + // && MathUtils.abs(t.y) <= (extent.y + box.extent.y) + // && MathUtils.abs(t.z) <= (extent.z + box.extent.z); + //} + public boolean intersectsBox(AABBD box) { + VecD3D t = box.sub(this); + return MathUtils.abs(t.x) <= (extent.x + box.extent.x) + && MathUtils.abs(t.y) <= (extent.y + box.extent.y) + && MathUtils.abs(t.z) <= (extent.z + box.extent.z); + } + + /** + * Calculates intersection with the given ray between a certain distance + * interval. + * + * Ray-box intersection is using IEEE numerical properties to ensure the + * test is both robust and efficient, as described in: + * + * Amy Williams, Steve Barrus, R. Keith Morley, and Peter Shirley: "An + * Efficient and Robust Ray-Box Intersection Algorithm" Journal of graphics + * tools, 10(1):49-54, 2005 + * + * @param ray + * incident ray + * @param minDist + * @param maxDist + * @return intersection point on the bounding box (only the first is + * returned) or null if no intersection + */ + + public VecD3D intersectsRay(RayD3D ray, double minDist, double maxDist) { + VecD3D invDir = ray.getDirection().reciprocal(); + boolean signDirX = invDir.x < 0; + boolean signDirY = invDir.y < 0; + boolean signDirZ = invDir.z < 0; + VecD3D bbox = signDirX ? max : min; + double tmin = (bbox.x - ray.x) * invDir.x; + bbox = signDirX ? min : max; + double tmax = (bbox.x - ray.x) * invDir.x; + bbox = signDirY ? max : min; + double tymin = (bbox.y - ray.y) * invDir.y; + bbox = signDirY ? min : max; + double tymax = (bbox.y - ray.y) * invDir.y; + if ((tmin > tymax) || (tymin > tmax)) { + return null; + } + if (tymin > tmin) { + tmin = tymin; + } + if (tymax < tmax) { + tmax = tymax; + } + bbox = signDirZ ? max : min; + double tzmin = (bbox.z - ray.z) * invDir.z; + bbox = signDirZ ? min : max; + double tzmax = (bbox.z - ray.z) * invDir.z; + if ((tmin > tzmax) || (tzmin > tmax)) { + return null; + } + if (tzmin > tmin) { + tmin = tzmin; + } + if (tzmax < tmax) { + tmax = tzmax; + } + if ((tmin < maxDist) && (tmax > minDist)) { + return ray.getPointAtDistance(tmin); + } + return null; + } + + public boolean intersectsSphereD(SphereD s) { + return intersectsSphereD(s, s.radius); + } + + /** + * @param c + * sphere centre + * @param r + * sphere radius + * @return true, if AABBD intersects with sphere + */ + public boolean intersectsSphereD(VecD3D c, double r) { + double s, d = 0; + // find the square of the distance + // from the sphere to the box + if (c.x < min.x) { + s = c.x - min.x; + d = s * s; + } else if (c.x > max.x) { + s = c.x - max.x; + d += s * s; + } + + if (c.y < min.y) { + s = c.y - min.y; + d += s * s; + } else if (c.y > max.y) { + s = c.y - max.y; + d += s * s; + } + + if (c.z < min.z) { + s = c.z - min.z; + d += s * s; + } else if (c.z > max.z) { + s = c.z - max.z; + d += s * s; + } + + return d <= r * r; + } + public boolean intersectsTriangle(Triangle3D tri) { + return intersectsTriangle(new TriangleD3D(tri)); + } + public boolean intersectsTriangle(TriangleD3D tri) { + // use separating axis theorem to test overlap between triangle and box + // need to test for overlap in these directions: + // + // 1) the {x,y,z}-directions (actually, since we use the AABBD of the + // triangle + // we do not even need to test these) + // 2) normal of the triangle + // 3) crossproduct(edge from tri, {x,y,z}-directin) + // this gives 3x3=9 more tests + VecD3D v0, v1, v2; + VecD3D normal, e0, e1, e2, f; + + // move everything so that the boxcenter is in (0,0,0) + v0 = tri.a.sub(this); + v1 = tri.b.sub(this); + v2 = tri.c.sub(this); + + // compute triangle edges + e0 = v1.sub(v0); + e1 = v2.sub(v1); + e2 = v0.sub(v2); + + // test the 9 tests first (this was faster) + f = e0.getAbs(); + if (testAxis(e0.z, -e0.y, f.z, f.y, v0.y, v0.z, v2.y, v2.z, extent.y, + extent.z)) { + return false; + } + if (testAxis(-e0.z, e0.x, f.z, f.x, v0.x, v0.z, v2.x, v2.z, extent.x, + extent.z)) { + return false; + } + if (testAxis(e0.y, -e0.x, f.y, f.x, v1.x, v1.y, v2.x, v2.y, extent.x, + extent.y)) { + return false; + } + + f = e1.getAbs(); + if (testAxis(e1.z, -e1.y, f.z, f.y, v0.y, v0.z, v2.y, v2.z, extent.y, + extent.z)) { + return false; + } + if (testAxis(-e1.z, e1.x, f.z, f.x, v0.x, v0.z, v2.x, v2.z, extent.x, + extent.z)) { + return false; + } + if (testAxis(e1.y, -e1.x, f.y, f.x, v0.x, v0.y, v1.x, v1.y, extent.x, + extent.y)) { + return false; + } + + f = e2.getAbs(); + if (testAxis(e2.z, -e2.y, f.z, f.y, v0.y, v0.z, v1.y, v1.z, extent.y, + extent.z)) { + return false; + } + if (testAxis(-e2.z, e2.x, f.z, f.x, v0.x, v0.z, v1.x, v1.z, extent.x, + extent.z)) { + return false; + } + if (testAxis(e2.y, -e2.x, f.y, f.x, v1.x, v1.y, v2.x, v2.y, extent.x, + extent.y)) { + return false; + } + + // first test overlap in the {x,y,z}-directions + // find min, max of the triangle each direction, and test for overlap in + // that direction -- this is equivalent to testing a minimal AABBD around + // the triangle against the AABBD + + // test in X-direction + if (MathUtils.min(v0.x, v1.x, v2.x) > extent.x + || MathUtils.max(v0.x, v1.x, v2.x) < -extent.x) { + return false; + } + + // test in Y-direction + if (MathUtils.min(v0.y, v1.y, v2.y) > extent.y + || MathUtils.max(v0.y, v1.y, v2.y) < -extent.y) { + return false; + } + + // test in Z-direction + if (MathUtils.min(v0.z, v1.z, v2.z) > extent.z + || MathUtils.max(v0.z, v1.z, v2.z) < -extent.z) { + return false; + } + + // test if the box intersects the plane of the triangle + // compute plane equation of triangle: normal*x+d=0 + normal = e0.cross(e1); + double d = -normal.dot(v0); + if (!planeBoxOverlap(normal, d, extent)) { + return false; + } + return true; + } + + private boolean planeBoxOverlap(VecD3D normal, double d, VecD3D maxbox) { + VecD3D vmin = new VecD3D(); + VecD3D vmax = new VecD3D(); + + if (normal.x > 0.0f) { + vmin.x = -maxbox.x; + vmax.x = maxbox.x; + } else { + vmin.x = maxbox.x; + vmax.x = -maxbox.x; + } + + if (normal.y > 0.0f) { + vmin.y = -maxbox.y; + vmax.y = maxbox.y; + } else { + vmin.y = maxbox.y; + vmax.y = -maxbox.y; + } + + if (normal.z > 0.0f) { + vmin.z = -maxbox.z; + vmax.z = maxbox.z; + } else { + vmin.z = maxbox.z; + vmax.z = -maxbox.z; + } + if (normal.dot(vmin) + d > 0.0f) { + return false; + } + if (normal.dot(vmax) + d >= 0.0f) { + return true; + } + return false; + } + + public AABBD set(AABBD box) { + extent.set(box.extent); + return set((ReadonlyVecD3D) box); + } + + /** + * Updates the position of the box in space and calls + * {@link #updateBounds()} immediately + * + * @see toxi.geom.VecD3D#set(double, double, double) + */ + public VecD3D set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + updateBounds(); + return this; + } + + /** + * Updates the position of the box in space and calls + * {@link #updateBounds()} immediately + * + * @see toxi.geom.VecD3D#set(toxi.geom.VecD3D) + */ + public AABBD set(ReadonlyVecD3D v) { + x = v.x(); + y = v.y(); + z = v.z(); + updateBounds(); + return this; + } + + /** + * Updates the size of the box and calls {@link #updateBounds()} immediately + * + * @param extent + * new box size + * @return itself, for method chaining + */ + public AABBD setExtent(ReadonlyVecD3D extent) { + this.extent = extent.copy(); + return updateBounds(); + } + + private boolean testAxis(double a, double b, double fa, double fb, double va, + double vb, double wa, double wb, double ea, double eb) { + double p0 = a * va + b * vb; + double p2 = a * wa + b * wb; + double min, max; + if (p0 < p2) { + min = p0; + max = p2; + } else { + min = p2; + max = p0; + } + double rad = fa * ea + fb * eb; + return (min > rad || max < -rad); + } + + public MeshD3D toMeshD() { + return toMeshD(null); + } + + public MeshD3D toMeshD(MeshD3D mesh) { + if (mesh == null) { + mesh = new TriangleMeshD("aabb", 8, 12); + } + VecD3D a = min; + VecD3D g = max; + VecD3D b = new VecD3D(a.x, a.y, g.z); + VecD3D c = new VecD3D(g.x, a.y, g.z); + VecD3D d = new VecD3D(g.x, a.y, a.z); + VecD3D e = new VecD3D(a.x, g.y, a.z); + VecD3D f = new VecD3D(a.x, g.y, g.z); + VecD3D h = new VecD3D(g.x, g.y, a.z); + VecD2D ua = new VecD2D(0, 0); + VecD2D ub = new VecD2D(1, 0); + VecD2D uc = new VecD2D(1, 1); + VecD2D ud = new VecD2D(0, 1); + // left + mesh.addFaceD(a, b, f, ud, uc, ub); + mesh.addFaceD(a, f, e, ud, ub, ua); + // front + mesh.addFaceD(b, c, g, ud, uc, ub); + mesh.addFaceD(b, g, f, ud, ub, ua); + // right + mesh.addFaceD(c, d, h, ud, uc, ub); + mesh.addFaceD(c, h, g, ud, ub, ua); + // back + mesh.addFaceD(d, a, e, ud, uc, ub); + mesh.addFaceD(d, e, h, ud, ub, ua); + // top + mesh.addFaceD(e, f, h, ua, ud, ub); + mesh.addFaceD(f, g, h, ud, uc, ub); + // bottom + mesh.addFaceD(a, d, b, ud, uc, ua); + mesh.addFaceD(b, d, c, ua, uc, ub); + return mesh; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.VecD3D#toString() + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append( " pos: ").append(super.toString()) + .append("\n ext: ").append(extent) + .append("\n min: ").append(getMin()) + .append("\n max: ").append(getMax()) + ; + return sb.toString(); + } + + public AABBD union(AABBD box) { + min.minSelf(box.getMin()); + max.maxSelf(box.getMax()); + set(min.interpolateTo(max, 0.5f)); + extent.set(max.sub(min).scaleSelf(0.5f)); + return this; + } + + /** + * Updates the min/max corner points of the box. MUST be called after moving + * the box in space by manipulating the public x,y,z coordinates directly. + * + * @return itself + */ + public final AABBD updateBounds() { + // this is check is necessary for the constructor + if (extent != null) { + this.min = this.sub(extent); + this.max = this.add(extent); + } + return this; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/AxisAlignedCylinderD.java b/src.core/toxi/geom/AxisAlignedCylinderD.java new file mode 100644 index 00000000..fac83a7c --- /dev/null +++ b/src.core/toxi/geom/AxisAlignedCylinderD.java @@ -0,0 +1,131 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.geom.mesh.MeshD3D; + +public abstract class AxisAlignedCylinderD implements ShapeD3D { + + protected VecD3D pos; + protected double radius; + protected double radiusSquared; + protected double length; + + protected AxisAlignedCylinderD(ReadonlyVecD3D pos, double radius, double length) { + this.pos = pos.copy(); + setRadius(radius); + setLength(length); + } + + /** + * Checks if the given point is inside the cylinder. + * + * @param p + * @return true, if inside + */ + public abstract boolean containsPoint(ReadonlyVecD3D p); + + /** + * @return the length + */ + public double getLength() { + return length; + } + + /** + * @return the cylinder's orientation axis + */ + public abstract VecD3D.AxisD getMajorAxis(); + + /** + * Returns the cylinder's position (centroid). + * + * @return the pos + */ + public VecD3D getPosition() { + return pos.copy(); + } + + /** + * @return the cylinder radius + */ + public double getRadius() { + return radius; + } + + /** + * @param length + * the length to set + */ + public void setLength(double length) { + this.length = length; + } + + /** + * @param pos + * the pos to set + */ + public void setPosition(VecD3D pos) { + this.pos.set(pos); + } + + /** + * @param radius + */ + public void setRadius(double radius) { + this.radius = radius; + this.radiusSquared = radius * radius; + } + + /** + * Builds a TriangleMesh representation of the cylinder at a default + * resolution 30 degrees. + * + * @return mesh instance + */ + public MeshD3D toMeshD() { + return toMeshD(12, 0); + } + + /** + * Builds a TriangleMesh representation of the cylinder using the given + * number of steps and start angle offset. + * + * @param steps + * @param thetaOffset + * @return mesh + */ + public MeshD3D toMeshD(int steps, double thetaOffset) { + return toMeshD(null, steps, thetaOffset); + } + + public MeshD3D toMeshD(MeshD3D mesh, int steps, double thetaOffset) { + return new ConeD(pos, getMajorAxis().getVector(), radius, radius, length) + .toMeshD(mesh, steps, thetaOffset, true, true); + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/AxisD3D.java b/src.core/toxi/geom/AxisD3D.java new file mode 100644 index 00000000..193bfdfc --- /dev/null +++ b/src.core/toxi/geom/AxisD3D.java @@ -0,0 +1,96 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * An immutable origin + axis in 3D-Space. + */ +public class AxisD3D { + + /** + * Creates a new x-Axis3D object from the world origin. + */ + public static final AxisD3D xAxis() { + return new AxisD3D(VecD3D.X_AXIS); + } + + /** + * Creates a new y-Axis3D object from the world origin. + */ + public static final AxisD3D yAxis() { + return new AxisD3D(VecD3D.Y_AXIS); + } + + /** + * Creates a new z-Axis3D object from the world origin. + */ + public static final AxisD3D zAxis() { + return new AxisD3D(VecD3D.Z_AXIS); + } + + public final ReadonlyVecD3D origin; + public final ReadonlyVecD3D dir; + + /** + * Creates a new z-Axis3D object from the world origin. + */ + public AxisD3D() { + this(VecD3D.Z_AXIS); + } + + public AxisD3D(double x, double y, double z) { + this(new VecD3D(x, y, z)); + } + + public AxisD3D(RayD3D ray) { + this(ray, ray.getDirection()); + } + + /** + * Creates a new Axis3D from the world origin in the given direction. + * + * @param dir + * direction vector + */ + public AxisD3D(ReadonlyVecD3D dir) { + this(new VecD3D(), dir); + } + + /** + * Creates a new Axis3D from the given origin and direction. + * + * @param o + * origin + * @param dir + * direction + */ + public AxisD3D(ReadonlyVecD3D o, ReadonlyVecD3D dir) { + this.origin = o; + this.dir = dir.getNormalized(); + } +} diff --git a/src.core/toxi/geom/BernsteinPolynomialD.java b/src.core/toxi/geom/BernsteinPolynomialD.java new file mode 100644 index 00000000..68b06f7b --- /dev/null +++ b/src.core/toxi/geom/BernsteinPolynomialD.java @@ -0,0 +1,63 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * Helper class for the spline3d classes in this package. Used to compute + * subdivision points of the curve. + */ +public class BernsteinPolynomialD { + + public double[] b0, b1, b2, b3; + public int resolution; + + /** + * @param res + * number of subdivision steps between each control point of the + * spline3d + */ + public BernsteinPolynomialD(int res) { + resolution = res; + b0 = new double[res]; + b1 = new double[res]; + b2 = new double[res]; + b3 = new double[res]; + double t = 0; + double dt = 1.0f / (resolution - 1); + for (int i = 0; i < resolution; i++) { + double t1 = 1 - t; + double t12 = t1 * t1; + double t2 = t * t; + b0[i] = t1 * t12; + b1[i] = 3 * t * t12; + b2[i] = 3 * t2 * t1; + b3[i] = t * t2; + t += dt; + } + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/BezierCurveD2D.java b/src.core/toxi/geom/BezierCurveD2D.java new file mode 100644 index 00000000..11d74846 --- /dev/null +++ b/src.core/toxi/geom/BezierCurveD2D.java @@ -0,0 +1,117 @@ +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +/** + * Standard multi-segment bezier curve implementation with optional automatic + * handle alignment between segments. Can be used to create closed curves which + * can then be converted into {@link PolygonD2D} instances. Also provides curve + * tangent calculation feature. Usage of this class is very similar to + * {@link Spline2D}. + */ +public class BezierCurveD2D { + + public static VecD2D computePointInSegment(VecD2D a, VecD2D b, VecD2D c, + VecD2D d, double t) { + double it = 1.0f - t; + double it2 = it * it; + double t2 = t * t; + return a.scale(it2 * it).addSelf(b.scale(3 * t * it2)) + .addSelf(c.scale(3 * t2 * it)).addSelf(d.scale(t2 * t)); + } + + public static VecD2D computeTangentInSegment(VecD2D a, VecD2D b, VecD2D c, + VecD2D d, double t) { + double t2 = t * t; + double x = (3 * t2 * (-a.x + 3 * b.x - 3 * c.x + d.x) + 6 * t + * (a.x - 2 * b.x + c.x) + 3 * (-a.x + b.x)); + double y = (3 * t2 * (-a.y + 3 * b.y - 3 * c.y + d.y) + 6 * t + * (a.y - 2 * b.y + c.y) + 3 * (-a.y + b.y)); + return new VecD2D(x, y).normalize(); + } + + private List points; + + public BezierCurveD2D() { + points = new ArrayList(); + } + + public BezierCurveD2D(List points) { + this.points = points; + } + + public BezierCurveD2D add(VecD2D p) { + points.add(p); + return this; + } + + public void alignAllHandles() { + for (int i = 0, num = points.size() - 1; i < num; i += 3) { + alignHandlesForPoint(i); + } + } + + public void alignHandlesForPoint(int id) { + if (id < points.size() - 1) { + VecD2D c; + if (id == 0 && isClosed()) { + c = points.get(points.size() - 2); + } else { + c = points.get(id - 1); + } + VecD2D d = points.get(id); + VecD2D e = points.get(id + 1); + VecD2D cd = d.sub(c); + VecD2D de = e.sub(d); + VecD2D cd2 = cd.interpolateTo(de, 0.5f); + c.set(d.sub(cd2)); + e.set(d.add(de.interpolateToSelf(cd, 0.5f))); + } else { + throw new IllegalArgumentException("invalid point index"); + } + } + + /** + * @return true, if the curve is closed. I.e. the first and last control + * point coincide. + */ + public boolean isClosed() { + return points.get(0).equals(points.get(points.size() - 1)); + } + + /** + * Computes a list of intermediate curve points for all segments. For each + * curve segment the given number of points will be produced. + * + * @param res + * number of points per segment + * @return list of VecD2Ds + */ + public LineStripD2D toLineStripD2D(int res) { + LineStripD2D strip = new LineStripD2D(); + int i = 0; + int maxRes = res; + for (int num = points.size(); i < num - 3; i += 3) { + VecD2D a = points.get(i); + VecD2D b = points.get(i + 1); + VecD2D c = points.get(i + 2); + VecD2D d = points.get(i + 3); + if (i + 3 > num - 3) { + maxRes++; + } + for (int t = 0; t < maxRes; t++) { + strip.add(computePointInSegment(a, b, c, d, t / res)); + } + } + return strip; + } + + public PolygonD2D toPolygonD2D(int res) { + PolygonD2D poly = new PolygonD2D(toLineStripD2D(res).getVertices()); + if (isClosed()) { + poly.vertices.remove(poly.vertices.get(poly.vertices.size() - 1)); + } + return poly; + } +} diff --git a/src.core/toxi/geom/BezierCurveD3D.java b/src.core/toxi/geom/BezierCurveD3D.java new file mode 100644 index 00000000..cdd13ce0 --- /dev/null +++ b/src.core/toxi/geom/BezierCurveD3D.java @@ -0,0 +1,111 @@ +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +/** + * Standard multi-segment bezier curve implementation with optional automatic + * handle alignment between segments. Also provides curve tangent calculation + * feature. Can be used to create closed curves. Usage of this class is very + * similar to {@link Spline3D}. + */ +public class BezierCurveD3D { + + public static VecD3D computePointInSegment(VecD3D a, VecD3D b, VecD3D c, + VecD3D d, double t) { + double it = 1.0f - t; + double it2 = it * it; + double t2 = t * t; + return a.scale(it2 * it).addSelf(b.scale(3 * t * it2)) + .addSelf(c.scale(3 * t2 * it)).addSelf(d.scale(t2 * t)); + } + + public static VecD3D computeTangentInSegment(VecD3D a, VecD3D b, VecD3D c, + VecD3D d, double t) { + double t2 = t * t; + double x = (3 * t2 * (-a.x + 3 * b.x - 3 * c.x + d.x) + 6 * t + * (a.x - 2 * b.x + c.x) + 3 * (-a.x + b.x)); + double y = (3 * t2 * (-a.y + 3 * b.y - 3 * c.y + d.y) + 6 * t + * (a.y - 2 * b.y + c.y) + 3 * (-a.y + b.y)); + double z = (3 * t2 * (-a.z + 3 * b.z - 3 * c.z + d.z) + 6 * t + * (a.z - 2 * b.z + c.z) + 3 * (-a.z + b.z)); + return new VecD3D(x, y, z).normalize(); + } + + private List points; + + public BezierCurveD3D() { + points = new ArrayList(); + } + + public BezierCurveD3D(List points) { + this.points = points; + } + + public BezierCurveD3D add(VecD3D p) { + points.add(p); + return this; + } + + public void alignAllHandles() { + for (int i = 0, num = points.size() - 1; i < num; i += 3) { + alignHandlesForPoint(i); + } + } + + public void alignHandlesForPoint(int id) { + if (id < points.size() - 1) { + VecD3D c; + if (id == 0 && isClosed()) { + c = points.get(points.size() - 2); + } else { + c = points.get(id - 1); + } + VecD3D d = points.get(id); + VecD3D e = points.get(id + 1); + VecD3D cd = d.sub(c); + VecD3D de = e.sub(d); + VecD3D cd2 = cd.interpolateTo(de, 0.5f); + c.set(d.sub(cd2)); + e.set(d.add(de.interpolateToSelf(cd, 0.5f))); + } else { + throw new IllegalArgumentException("invalid point index"); + } + } + + /** + * @return true, if the curve is closed. I.e. the first and last control + * point coincide. + */ + public boolean isClosed() { + return points.get(0).equals(points.get(points.size() - 1)); + } + + /** + * Computes a list of intermediate curve points for all segments. For each + * curve segment the given number of points will be produced. + * + * @param res + * number of points per segment + * @return list of VecD3Ds + */ + public LineStripD3D toLineStripD3D(int res) { + LineStripD3D strip = new LineStripD3D(); + int i = 0; + int maxRes = res; + for (int num = points.size(); i < num - 3; i += 3) { + VecD3D a = points.get(i); + VecD3D b = points.get(i + 1); + VecD3D c = points.get(i + 2); + VecD3D d = points.get(i + 3); + if (i + 3 > num - 3) { + maxRes++; + } + for (int t = 0; t < maxRes; t++) { + strip.add(computePointInSegment(a, b, c, d, t / res)); + } + } + return strip; + } + +} diff --git a/src.core/toxi/geom/BooleanShapeBuilderD.java b/src.core/toxi/geom/BooleanShapeBuilderD.java new file mode 100644 index 00000000..6770b022 --- /dev/null +++ b/src.core/toxi/geom/BooleanShapeBuilderD.java @@ -0,0 +1,135 @@ +package toxi.geom; + +import java.awt.Shape; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; + +public class BooleanShapeBuilderD { + + public enum Type { + UNION, + INTERSECTION, + DIFFERENCE, + XOR; + } + + private int bezierRes; + + private final Area area; + private final Type type; + + public BooleanShapeBuilderD(Type type) { + this(type, 8); + } + + public BooleanShapeBuilderD(Type type, int bezierRes) { + this.type = type; + this.bezierRes = bezierRes; + area = new Area(); + } + + public BooleanShapeBuilderD addShape(ShapeD2D s) { + return combineWithArea(new Area(convertToAWTShape(s))); + } + + public BooleanShapeBuilderD combineWithArea(Area a) { + switch (type) { + case UNION: + area.add(a); + break; + case INTERSECTION: + area.intersect(a); + break; + case DIFFERENCE: + area.subtract(a); + break; + case XOR: + area.exclusiveOr(a); + break; + } + return this; + } + + public List computeShapes() { + List shapes = new ArrayList(); + PathIterator i = area.getPathIterator(null); + double[] buf = new double[6]; + VecD2D prev = new VecD2D(); + PolygonD2D s = null; + while (!i.isDone()) { + int id = i.currentSegment(buf); + switch (id) { + case PathIterator.SEG_MOVETO: + s = new PolygonD2D(); + shapes.add(s); + prev.set(buf[0], buf[1]); + s.add(prev.copy()); + break; + case PathIterator.SEG_LINETO: + prev.set(buf[0], buf[1]); + s.add(prev.copy()); + break; + case PathIterator.SEG_CUBICTO: + VecD2D pa = new VecD2D(buf[0], buf[1]); + VecD2D pb = new VecD2D(buf[2], buf[3]); + VecD2D pc = new VecD2D(buf[4], buf[5]); + for (int t = 0; t <= bezierRes; t++) { + s.add(BezierCurveD2D.computePointInSegment(prev, pa, pb, + pc, t / bezierRes)); + } + prev.set(pc); + break; + case PathIterator.SEG_CLOSE: + break; + default: + throw new UnsupportedOperationException( + "Unsupported path segment type: " + id); + } + i.next(); + } + return shapes; + } + + private Shape convertToAWTShape(ShapeD2D s) { + if (s instanceof RectD) { + RectD r = (RectD) s; + return new Rectangle2D.Double(r.x, r.y, r.width, r.height); + } + if (s instanceof TriangleD2D) { + TriangleD2D t = (TriangleD2D) s; + Path2D path = new Path2D.Float(); + path.moveTo(t.a.x, t.a.y); + path.lineTo(t.b.x, t.b.y); + path.lineTo(t.c.x, t.c.y); + path.closePath(); + return path; + } + if (s instanceof EllipseD) { + EllipseD e = (EllipseD) s; + VecD2D r = e.getRadii(); + return new Ellipse2D.Double(e.x - r.x, e.y - r.y, r.x * 2, r.y * 2); + } + if (!(s instanceof PolygonD2D)) { + s = s.toPolygonD2D(); + } + PolygonD2D poly = (PolygonD2D) s; + Path2D path = new Path2D.Float(); + VecD2D p = poly.get(0); + path.moveTo(p.x, p.y); + for (int i = 1, num = poly.getNumVertices(); i < num; i++) { + p = poly.get(i); + path.lineTo(p.x, p.y); + } + path.closePath(); + return path; + } + + public Area getArea() { + return area; + } +} diff --git a/src.core/toxi/geom/BoxDIntersector.java b/src.core/toxi/geom/BoxDIntersector.java new file mode 100644 index 00000000..2a6d2408 --- /dev/null +++ b/src.core/toxi/geom/BoxDIntersector.java @@ -0,0 +1,42 @@ +package toxi.geom; + +public class BoxDIntersector implements IntersectorD3D { + + private AABBD box; + private final IsectDataD3D isec; + + public BoxDIntersector(AABBD box) { + this.box = box; + this.isec = new IsectDataD3D(); + } + + /** + * @return the box + */ + public AABBD getBoxD() { + return box; + } + + public IsectDataD3D getIntersectionDataD() { + return isec; + } + + public boolean intersectsRayD(RayD3D ray) { + final VecD3D pos = box.intersectsRay(ray, 0, Float.MAX_VALUE); + isec.pos = pos; + isec.isIntersection = pos != null; + if (isec.isIntersection) { + isec.normal = box.getNormalForPoint(pos); + isec.dist = ray.distanceTo(pos); + } + return isec.isIntersection; + } + + /** + * @param box + * the box to set + */ + public void setBoxD(AABBD box) { + this.box = box; + } +} diff --git a/src.core/toxi/geom/Circle.java b/src.core/toxi/geom/Circle.java index 2127f17e..ada6e97e 100644 --- a/src.core/toxi/geom/Circle.java +++ b/src.core/toxi/geom/Circle.java @@ -114,6 +114,9 @@ public Circle() { public Circle(Circle c) { this(c, c.radius.x); } + public Circle(CircleD cd) { + this(new Vec2D((float)cd.x,(float)cd.y), (float)cd.radius.x); + } public Circle(float radius) { this(0, 0, radius); diff --git a/src.core/toxi/geom/CircleD.java b/src.core/toxi/geom/CircleD.java new file mode 100644 index 00000000..166df099 --- /dev/null +++ b/src.core/toxi/geom/CircleD.java @@ -0,0 +1,187 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.List; + +import toxi.math.MathUtils; + +/** + * This class overrides {@link Ellipse} to define a 2D circle and provides + * several utility methods for it, including factory methods to construct + * circles from points. + */ +public class CircleD extends EllipseD { + + /** + * Factory method to construct a circle which has the two given points lying + * on its perimeter. If the points are coincident, the circle will have a + * radius of zero. + * + * @param p1 + * @param p2 + * @return new circle instance + */ + public static CircleD from2Points(VecD2D p1, VecD2D p2) { + VecD2D m = p1.interpolateTo(p2, 0.5f); + return new CircleD(m, m.distanceTo(p1)); + } + + /** + * Factory method to construct a circle which has the three given points + * lying on its perimeter. The function returns null, if the 3 points are + * co-linear (in which case it's impossible to find a circle). + * + * Based on CPP code by Paul Bourke: + * http://local.wasp.uwa.edu.au/~pbourke/geometry/circlefrom3/ + * + * @param p1 + * @param p2 + * @param p3 + * @return new circle instance or null + */ + public static CircleD from3Points(VecD2D p1, VecD2D p2, VecD2D p3) { + CircleD circle = null; + VecD2D deltaA = p2.sub(p1); + VecD2D deltaB = p3.sub(p2); + if (MathUtils.abs(deltaA.x) <= 0.0000001f + && MathUtils.abs(deltaB.y) <= 0.0000001f) { + VecD2D centroid = new VecD2D(p2.x + p3.x, p1.y + p2.y) + .scaleSelf(0.5f); + double radius = centroid.distanceTo(p1); + circle = new CircleD(centroid, radius); + } else { + double aSlope = deltaA.y / deltaA.x; + double bSlope = deltaB.y / deltaB.x; + if (MathUtils.abs(aSlope - bSlope) > 0.0000001f) { + double x = (aSlope * bSlope * (p1.y - p3.y) + bSlope + * (p1.x + p2.x) - aSlope * (p2.x + p3.x)) + / (2 * (bSlope - aSlope)); + double y = -(x - (p1.x + p2.x) / 2) / aSlope + (p1.y + p2.y) / 2; + VecD2D centroid = new VecD2D(x, y); + double radius = centroid.distanceTo(p1); + circle = new CircleD(centroid, radius); + } + } + return circle; + } + + public static CircleD newBoundingCircleD(List vertices) { + VecD2D origin = new VecD2D(); + double maxD = 0; + for (VecD2D v : vertices) { + origin.addSelf(v); + } + origin.scaleSelf(1f / vertices.size()); + for (VecD2D v : vertices) { + double d = origin.distanceToSquared(v); + if (d > maxD) { + maxD = d; + } + } + return new CircleD(origin, Math.sqrt(maxD)); + } + + public CircleD() { + this(new VecD2D(), 1); + } + + public CircleD(CircleD c) { + this(c, c.radius.x); + } + public CircleD(Circle c) { + this(new VecD2D(c.x,c.y), c.radius.x); + } + + public CircleD(double radius) { + this(0, 0, radius); + } + + public CircleD(double x, double y, double radius) { + super(x, y, radius, radius); + } + + public CircleD(ReadonlyVecD2D origin, double radius) { + super(origin, radius); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.Ellipse#containsPoint(toxi.geom.VecD2D) + */ + @Override + public boolean containsPoint(ReadonlyVecD2D p) { + return distanceToSquared(p) <= radius.x * radius.x; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.Ellipse#getCircumference() + */ + @Override + public double getCircumference() { + return MathUtils.TWO_PI * radius.x; + } + + public double getRadius() { + return radius.x; + } + + public VecD2D[] getTangentPoints(ReadonlyVecD2D p) { + VecD2D m = interpolateTo(p, 0.5f); + return intersectsCircleD(new CircleD(m, m.distanceTo(p))); + } + + public VecD2D[] intersectsCircleD(CircleD c) { + VecD2D[] res = null; + VecD2D delta = c.sub(this); + double d = delta.magnitude(); + double r1 = radius.x; + double r2 = c.radius.x; + if (d <= r1 + r2 && d >= Math.abs(r1 - r2)) { + double a = (r1 * r1 - r2 * r2 + d * d) / (2.0f * d); + d = 1 / d; + VecD2D p = add(delta.scale(a * d)); + double h = Math.sqrt(r1 * r1 - a * a); + delta.perpendicular().scaleSelf(h * d); + VecD2D i1 = p.add(delta); + VecD2D i2 = p.sub(delta); + res = new VecD2D[] { + i1, i2 + }; + } + return res; + } + + public CircleD setRadius(double r) { + super.setRadii(r, r); + return this; + } +} diff --git a/src.core/toxi/geom/CircleDIntersector.java b/src.core/toxi/geom/CircleDIntersector.java new file mode 100644 index 00000000..de4540c4 --- /dev/null +++ b/src.core/toxi/geom/CircleDIntersector.java @@ -0,0 +1,71 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * This class handles CircleD-RayD2D intersections by implementing the + * {@link Intersector2D} interface. + * + */ +public class CircleDIntersector implements IntersectorD2D { + + private IsectDataD2D isec = new IsectDataD2D(); + private CircleD circle; + + public CircleDIntersector(CircleD circle) { + this.circle = circle; + } + + public CircleD getCircleD() { + return circle; + } + + public IsectDataD2D getIntersectionDataD() { + return isec; + } + + public boolean intersectsRayD(RayD2D ray) { + isec.clear(); + VecD2D q = circle.sub(ray); + double distSquared = q.magSquared(); + double v = q.dot(ray.getDirection()); + double r = circle.getRadius(); + double d = r * r - (distSquared - v * v); + if (d >= 0.0) { + isec.isIntersection = true; + isec.dist = v - Math.sqrt(d); + isec.pos = ray.getPointAtDistance(isec.dist); + isec.normal = isec.pos.sub(circle).normalize(); + } + return isec.isIntersection; + } + + public void setCircleD(CircleD circle) { + this.circle = circle; + } +} diff --git a/src.core/toxi/geom/ConeD.java b/src.core/toxi/geom/ConeD.java new file mode 100644 index 00000000..1eea01ac --- /dev/null +++ b/src.core/toxi/geom/ConeD.java @@ -0,0 +1,115 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.geom.mesh.MeshD3D; +import toxi.geom.mesh.TriangleMeshD; +import toxi.math.MathUtils; + +/** + * A geometric definition of a cone (and cylinder as a special case) with + * support for mesh creation/representation. The class is currently still + * incomplete in that it doesn't provide any other features than the + * construction of a cone shaped mesh. + */ +public class ConeD extends VecD3D { + + public VecD3D dir; + public double radiusSouth; + public double radiusNorth; + public double length; + + /** + * Constructs a new cone instance. + * + * @param pos + * centre position + * @param dir + * direction + * @param rNorth + * radius on the side in the forward direction + * @param rSouth + * radius on the side in the opposite direction + * @param len + * length of the cone + */ + public ConeD(ReadonlyVecD3D pos, ReadonlyVecD3D dir, double rNorth, + double rSouth, double len) { + super(pos); + this.dir = dir.getNormalized(); + this.radiusNorth = rNorth; + this.radiusSouth = rSouth; + this.length = len; + } + + public MeshD3D toMeshD(int steps) { + return toMeshD(steps, 0); + } + + public MeshD3D toMeshD(int steps, double thetaOffset) { + return toMeshD(null, steps, thetaOffset, true, true); + } + + public MeshD3D toMeshD(MeshD3D mesh, int steps, double thetaOffset, + boolean topClosed, boolean bottomClosed) { + ReadonlyVecD3D c = this.add(0.01f, 0.01f, 0.01f); + ReadonlyVecD3D n = c.cross(dir.getNormalized()).normalize(); + VecD3D halfAxis = dir.scale(length * 0.5f); + VecD3D p = sub(halfAxis); + VecD3D q = add(halfAxis); + VecD3D[] south = new VecD3D[steps]; + VecD3D[] north = new VecD3D[steps]; + double phi = MathUtils.TWO_PI / steps; + for (int i = 0; i < steps; i++) { + double theta = i * phi + thetaOffset; + ReadonlyVecD3D nr = n.getRotatedAroundAxis(dir, theta); + south[i] = nr.scale(radiusSouth).addSelf(p); + north[i] = nr.scale(radiusNorth).addSelf(q); + } + int numV = steps * 2 + 2; + int numF = steps * 2 + (topClosed ? steps : 0) + + (bottomClosed ? steps : 0); + if (mesh == null) { + mesh = new TriangleMeshD("cone", numV, numF); + } + for (int i = 0, j = 1; i < steps; i++, j++) { + if (j == steps) { + j = 0; + } + mesh.addFaceD(south[i], north[i], south[j], null, null, null, null); + mesh.addFaceD(south[j], north[i], north[j], null, null, null, null); + if (bottomClosed) { + mesh.addFaceD(p, south[i], south[j], null, null, null, null); + } + if (topClosed) { + mesh.addFaceD(north[i], q, north[j], null, null, null, null); + } + } + return mesh; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/ConvexPolygonDClipper.java b/src.core/toxi/geom/ConvexPolygonDClipper.java new file mode 100644 index 00000000..a1f17d9f --- /dev/null +++ b/src.core/toxi/geom/ConvexPolygonDClipper.java @@ -0,0 +1,108 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +/** + * A more generic version of the Sutherland-Hodgeman algorithm to limit 2D + * polygons to convex clipping regions. Uses the clipping region's centroid and + * {@link Line2D#classifyPoint(ReadonlyVecD2D)} to identify if an edge needs to + * be clipped or not. + * + * More information: http://en.wikipedia.org/wiki/Sutherland-Hodgman_algorithm + * + * @see SutherlandHodgemanClipper + * @since 0021 + */ +public class ConvexPolygonDClipper implements PolygonClipperD2D { + + protected PolygonD2D bounds; + protected VecD2D boundsCentroid; + + public ConvexPolygonDClipper(PolygonD2D bounds) { + setBounds(bounds); + } + + public PolygonD2D clipPolygonD(PolygonD2D poly) { + List points = new ArrayList(poly.vertices); + List clipped = new ArrayList(); + points.add(points.get(0)); + for (LineD2D clipEdge : bounds.getEdges()) { + clipped.clear(); + double sign = clipEdge.classifyPoint(boundsCentroid); + for (int i = 0, num = points.size() - 1; i < num; i++) { + VecD2D p = points.get(i); + VecD2D q = points.get(i + 1); + if (clipEdge.classifyPoint(p) == sign) { + if (clipEdge.classifyPoint(q) == sign) { + clipped.add(q.copy()); + } else { + clipped.add(getClippedPosOnEdge(clipEdge, p, q)); + } + continue; + } + if (clipEdge.classifyPoint(q) == sign) { + clipped.add(getClippedPosOnEdge(clipEdge, p, q)); + clipped.add(q.copy()); + } + } + if (clipped.size() > 0 + && clipped.get(0) != clipped.get(clipped.size() - 1)) { + clipped.add(clipped.get(0)); + } + List t = points; + points = clipped; + clipped = t; + } + return new PolygonD2D(points).removeDuplicates(0.001f); + } + + public PolygonD2D getBounds() { + return bounds; + } + + protected VecD2D getClippedPosOnEdge(LineD2D clipEdge, VecD2D p, VecD2D q) { + return clipEdge.intersectLine(new LineD2D(p, q)).getPos(); + } + + protected boolean isKnownVertex(List list, VecD2D q) { + for (VecD2D p : list) { + if (p.equalsWithTolerance(q, 0.001f)) { + return true; + } + } + return false; + } + + public void setBounds(PolygonD2D bounds) { + this.bounds = bounds; + this.boundsCentroid = bounds.getCentroid(); + } +} diff --git a/src.core/toxi/geom/EllipseD.java b/src.core/toxi/geom/EllipseD.java new file mode 100644 index 00000000..8fb6dad8 --- /dev/null +++ b/src.core/toxi/geom/EllipseD.java @@ -0,0 +1,200 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.List; + +import toxi.math.MathUtils; +import toxi.util.datatypes.BiasedFloatRange; + +/** + * This class defines a 2D ellipse and provides several utility methods for it. + */ +public class EllipseD extends VecD2D implements ShapeD2D { + + public static int DEFAULT_RES = 20; + + protected VecD2D radius = new VecD2D(); + protected double focus; + + public EllipseD() { + this(0, 0, 1); + } + + public EllipseD(double rx, double ry) { + this(0, 0, rx, ry); + } + + public EllipseD(double x, double y, double r) { + this(x, y, r, r); + } + + public EllipseD(double x, double y, double rx, double ry) { + super(x, y); + setRadii(rx, ry); + } + + public EllipseD(ReadonlyVecD2D v, double r) { + this(v.x(), v.y(), r, r); + } + + public EllipseD(ReadonlyVecD2D v, ReadonlyVecD2D r) { + this(v.x(), v.y(), r.x(), r.y()); + } + + public boolean containsPoint(ReadonlyVecD2D p) { + VecD2D[] foci = getFoci(); + return p.distanceTo(foci[0]) + p.distanceTo(foci[1]) < 2 * MathUtils + .max(radius.x, radius.y); + } + + /** + * Computes the area covered by the ellipse. + * + * @return area + */ + public double getArea() { + return MathUtils.PI * radius.x * radius.y; + } + + public CircleD getBoundingCircleD() { + return new CircleD(x, y, MathUtils.max(radius.x, radius.y)); + } + + /** + * Returns the ellipse's bounding rect. + * + * @return bounding rect + * @see toxi.geom.Shape2D#getBounds() + */ + public RectD getBounds() { + return new RectD(sub(radius), add(radius)); + } + + /** + * Computes the approximate circumference of the ellipse, using this + * equation: 2 * PI * sqrt(1/2 * (rx*rx+ry*ry)). + * + * The precise value is an infinite series elliptical integral, but the + * approximation comes sufficiently close. See Wikipedia for more details: + * + * http://en.wikipedia.org/wiki/Ellipse + * + * @return circumference + */ + public double getCircumference() { + // wikipedia solution: + // return (float) (MathUtils.PI * (3 * (radius.x + radius.y) - Math + // .sqrt((3 * radius.x + radius.y) * (radius.x + 3 * radius.y)))); + return Math.sqrt(0.5 * radius.magSquared()) * MathUtils.TWO_PI; + } + + public List getEdges() { + return toPolygonD2D().getEdges(); + } + + /** + * @return the focus + */ + public VecD2D[] getFoci() { + VecD2D[] foci = new VecD2D[2]; + if (radius.x > radius.y) { + foci[0] = sub(focus, 0); + foci[1] = add(focus, 0); + } else { + foci[0] = sub(0, focus); + foci[1] = add(0, focus); + } + return foci; + } + + /** + * @return the 2 radii of the ellipse as a VecD2D + */ + public VecD2D getRadii() { + return radius.copy(); + } + + /** + * Creates a random point within the ellipse using a + * {@link BiasedFloatRange} to create a more uniform distribution. + * + * @return VecD2D + */ + public VecD2D getRandomPoint() { + double theta = MathUtils.random(MathUtils.TWO_PI); + BiasedFloatRange rnd = new BiasedFloatRange(0f, 1f, 1f, MathUtils.SQRT2); + return VecD2D.fromTheta(theta).scaleSelf(radius.scale(rnd.pickRandom())) + .addSelf(this); + } + + /** + * Sets the radii of the ellipse to the new values. + * + * @param rx + * @param ry + * @return itself + */ + + public EllipseD setRadii(double rx, double ry) { + radius.set(rx, ry); + focus = radius.magnitude(); + return this; + } + + /** + * Sets the radii of the ellipse to the values given by the vector. + * + * @param r + * @return itself + */ + public EllipseD setRadii(ReadonlyVecD3D r) { + return setRadii(r.x(), r.y()); + } + + public PolygonD2D toPolygonD2D() { + return toPolygonD2D(DEFAULT_RES); + } + + /** + * Creates a {@link Polygon2D} instance of the ellipse sampling it at the + * given resolution. + * + * @param res + * number of steps + * @return ellipse as polygon + */ + public PolygonD2D toPolygonD2D(int res) { + PolygonD2D poly = new PolygonD2D(); + double step = MathUtils.TWO_PI / res; + for (int i = 0; i < res; i++) { + poly.add(VecD2D.fromTheta(i * step).scaleSelf(radius).addSelf(this)); + } + return poly; + } +} diff --git a/src.core/toxi/geom/GVector.java b/src.core/toxi/geom/GVector.java index 34719124..1cda599f 100644 --- a/src.core/toxi/geom/GVector.java +++ b/src.core/toxi/geom/GVector.java @@ -119,6 +119,12 @@ public GVector(ReadonlyVec2D v) { }; length = 2; } + public GVector(ReadonlyVecD2D v) { + values = new double[] { + v.x(), v.y() + }; + length = 2; + } /** * Constructs a new GVector and copies the initial values from the specified @@ -133,6 +139,12 @@ public GVector(ReadonlyVec3D v) { }; length = 3; } + public GVector(ReadonlyVecD3D v) { + values = new double[] { + v.x(), v.y(), v.z() + }; + length = 3; + } /** * Constructs a new GVector and copies the initial values from the specified @@ -147,6 +159,12 @@ public GVector(ReadonlyVec4D v) { }; length = 4; } + public GVector(ReadonlyVecD4D v) { + values = new double[] { + v.x(), v.y(), v.z(), v.w() + }; + length = 4; + } /** * Creates the vector sum of this vector and the given one (must be equal diff --git a/src.core/toxi/geom/GlobalGridTesselatorD.java b/src.core/toxi/geom/GlobalGridTesselatorD.java new file mode 100644 index 00000000..f23338e6 --- /dev/null +++ b/src.core/toxi/geom/GlobalGridTesselatorD.java @@ -0,0 +1,43 @@ +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +import toxi.math.MathUtils; + +/** + * A concrete implementation of the abstract {@link GridTesselator} using a grid + * in global coordinate space for generating additional points within a polygon. + * The resolution setting of this class defines the axis-aligned distance + * between grid points. E.g. a resolution of 10 means grid points are created a + * world space positions of multiples of 10 (i.e. 0,10,20 etc.). This resolution + * is used independently on polygon size, so depending on the chosen resolution + * and polygon size no additional inliers MIGHT be created at all. This behavior + * property is useful in cases where you want to adjust the number of resulting + * triangles dynamically, e.g. based on polygon size. Use the + * {@link LocalGridTesselator} for an alternative behavior. + * + * @see GridTesselator + * @see LocalGridTesselator + * @see PolygonTesselator + */ +public class GlobalGridTesselatorD extends GridTesselatorD { + + public GlobalGridTesselatorD(double res) { + super(res); + } + + protected List createInsidePoints(PolygonD2D poly, RectD bounds) { + List points = new ArrayList(); + for (double y = bounds.y; y < bounds.getBottom(); y += res) { + double yy = MathUtils.roundTo(y, res); + for (double x = bounds.x; x < bounds.getRight(); x += res) { + VecD2D p = new VecD2D(MathUtils.roundTo(x, res), yy); + if (poly.containsPoint(p)) { + points.add(p); + } + } + } + return points; + } +} diff --git a/src.core/toxi/geom/GridTesselatorD.java b/src.core/toxi/geom/GridTesselatorD.java new file mode 100644 index 00000000..c310ff48 --- /dev/null +++ b/src.core/toxi/geom/GridTesselatorD.java @@ -0,0 +1,93 @@ +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.mesh2d.DelaunayTriangulationD; +import toxi.geom.mesh2d.VoronoiD; +import toxi.math.MathUtils; + +/** + * This is an implementation of the {@link PolygonTesselator} interface and + * abstract parent class for tesselating 2D polygons using a grid of additional + * points created within the polygon. These inlier points are connected to the + * original polygon vertices using a {@link DelaunayTriangulation}. The quality + * and final amount of triangles used can be adjusted via the number of + * additional grid points. This class currently has two concrete + * implementations: {@link GlobalGridTesselator} and {@link LocalGridTesselator} + * . + */ +public abstract class GridTesselatorD implements PolygonTesselatorD { + + protected double res; + private double rootSize; + + /** + * Creates a new instance with the given grid resolution. + * + * @param res + * snap distance for grid points + */ + public GridTesselatorD(double res) { + this(res, VoronoiD.DEFAULT_SIZE); + } + + /** + * Creates a new instance with the given grid resolution. + * + * @param res + * snap distance for grid points + */ + public GridTesselatorD(double res, double rootSize) { + this.res = res; + this.rootSize = rootSize; + } + + protected abstract List createInsidePoints(PolygonD2D poly, + RectD bounds); + + public double getResolution() { + return res; + } + + public void setResolution(double res) { + this.res = res; + } + + /** + * Tesselates/decomposes the given polygon into a list of 2D triangles using + * the currently set grid resolution. + * + * @param poly + * polygon to be tesselated + * @return list of triangles + */ + public List tesselatePolygonD(PolygonD2D poly) { + List triangles = new ArrayList(); + RectD bounds = poly.getBounds(); + // a Voronoi diagram relies on a Delaunay triangulation behind the + // scenes + VoronoiD voronoi = new VoronoiD(rootSize); + // add perimeter points + for (VecD2D v : poly.vertices) { + voronoi.addPoint(v); + } + // add random inliers + for (VecD2D v : createInsidePoints(poly, bounds)) { + voronoi.addPoint(v); + } + // get filtered delaunay triangles: + // ignore any triangles which share a vertex with the initial root + // triangle or whose centroid is outside the polygon + for (TriangleD2D t : voronoi.getTriangles()) { + if (MathUtils.abs(t.a.x) != VoronoiD.DEFAULT_SIZE + && MathUtils.abs(t.a.y) != VoronoiD.DEFAULT_SIZE) { + if (poly.containsPoint(t.computeCentroid())) { + triangles.add(t); + } + } + } + return triangles; + } + +} \ No newline at end of file diff --git a/src.core/toxi/geom/IntersectorD2D.java b/src.core/toxi/geom/IntersectorD2D.java new file mode 100644 index 00000000..6e9c100e --- /dev/null +++ b/src.core/toxi/geom/IntersectorD2D.java @@ -0,0 +1,49 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * Generic interface for ray intersection with 2D geometry + */ +public interface IntersectorD2D { + + /** + * @return intersection data parcel + */ + public IsectDataD2D getIntersectionDataD(); + + /** + * Check if entity intersects with the given ray + * + * @param ray + * ray to check + * @return true, if ray hits the entity + */ + public boolean intersectsRayD(RayD2D ray); + +} \ No newline at end of file diff --git a/src.core/toxi/geom/IntersectorD3D.java b/src.core/toxi/geom/IntersectorD3D.java new file mode 100644 index 00000000..9bab708c --- /dev/null +++ b/src.core/toxi/geom/IntersectorD3D.java @@ -0,0 +1,51 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * Generic interface for ray intersection with 3D geometry + */ +public interface IntersectorD3D { + + /** + * @return intersection data parcel + */ + public IsectDataD3D getIntersectionDataD(); + + /** + * Checks if entity intersects with the given ray. Further intersection + * details can then be queried via the {@link IsectData3D} instance returned + * by {@link #getIntersectionData()}. + * + * @param ray + * ray to check + * @return true, if ray hits the entity + */ + public boolean intersectsRayD(RayD3D ray); + +} \ No newline at end of file diff --git a/src.core/toxi/geom/IsectData2D.java b/src.core/toxi/geom/IsectData2D.java index 3fe3511c..14aa425d 100644 --- a/src.core/toxi/geom/IsectData2D.java +++ b/src.core/toxi/geom/IsectData2D.java @@ -35,9 +35,7 @@ public class IsectData2D { public ReadonlyVec2D dir; public ReadonlyVec2D normal; - public IsectData2D() { - - } + public IsectData2D() {} public IsectData2D(IsectData2D isec) { isIntersection = isec.isIntersection; diff --git a/src.core/toxi/geom/IsectDataD2D.java b/src.core/toxi/geom/IsectDataD2D.java new file mode 100644 index 00000000..e7ead474 --- /dev/null +++ b/src.core/toxi/geom/IsectDataD2D.java @@ -0,0 +1,65 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +public class IsectDataD2D { + + public boolean isIntersection; + public double dist; + public ReadonlyVecD2D pos; + public ReadonlyVecD2D dir; + public ReadonlyVecD2D normal; + + public IsectDataD2D() { + + } + + public IsectDataD2D(IsectDataD2D isec) { + isIntersection = isec.isIntersection; + dist = isec.dist; + pos = isec.pos.copy(); + dir = isec.dir.copy(); + normal = isec.normal.copy(); + } + + public void clear() { + isIntersection = false; + dist = 0; + pos = new VecD2D(); + dir = new VecD2D(); + normal = new VecD2D(); + } + + public String toString() { + String s = "isec: " + isIntersection; + if (isIntersection) { + s += " at:" + pos + " dist:" + dist + "normal:" + normal; + } + return s; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/IsectDataD3D.java b/src.core/toxi/geom/IsectDataD3D.java new file mode 100644 index 00000000..7d49d1fd --- /dev/null +++ b/src.core/toxi/geom/IsectDataD3D.java @@ -0,0 +1,64 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +public class IsectDataD3D { + + public boolean isIntersection; + public double dist; + public ReadonlyVecD3D pos; + public ReadonlyVecD3D dir; + public ReadonlyVecD3D normal; + + public IsectDataD3D() { + } + + public IsectDataD3D(IsectDataD3D isec) { + isIntersection = isec.isIntersection; + dist = isec.dist; + pos = isec.pos.copy(); + dir = isec.dir.copy(); + normal = isec.normal.copy(); + } + + public void clear() { + isIntersection = false; + dist = 0; + pos = new VecD3D(); + dir = new VecD3D(); + normal = new VecD3D(); + } + + public String toString() { + String s = "isec: " + isIntersection; + if (isIntersection) { + s += " at:" + pos + " dist:" + dist + "normal:" + normal; + } + return s; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/LineD2D.java b/src.core/toxi/geom/LineD2D.java new file mode 100644 index 00000000..19c43187 --- /dev/null +++ b/src.core/toxi/geom/LineD2D.java @@ -0,0 +1,373 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +import toxi.geom.LineD2D.LineIntersection.Type; + +public class LineD2D { + + public static class LineIntersection { + + public static enum Type { + COINCIDENT, + COINCIDENT_NO_INTERSECT, + PARALLEL, + NON_INTERSECTING, + INTERSECTING + } + + private final Type type; + private final ReadonlyVecD2D pos; + private final double[] coeff; + + public LineIntersection(Type type, ReadonlyVecD2D pos) { + this(type, pos, 0, 0); + } + + public LineIntersection(Type type, ReadonlyVecD2D pos, double ua, double ub) { + this.type = type; + this.pos = pos; + this.coeff = new double[] { + ua, ub + }; + } + + public double[] getCoefficients() { + return coeff; + } + + /** + * Returns copy of intersection point. + * + * @return point + */ + public VecD2D getPos() { + return pos != null ? pos.copy() : null; + } + + /** + * Returns intersection type enum. + * + * @return type + */ + public Type getType() { + return type; + } + + public String toString() { + return "type: " + type + " pos: " + pos; + } + } + + /** + * Splits the line between A and B into segments of the given length, + * starting at point A. The tweened points are added to the given result + * list. The last point added is B itself and hence it is likely that the + * last segment has a shorter length than the step length requested. The + * first point (A) can be omitted and not be added to the list if so + * desired. + * + * @param a + * start point + * @param b + * end point (always added to results) + * @param stepLength + * desired distance between points + * @param segments + * existing array list for results (or a new list, if null) + * @param addFirst + * false, if A is NOT to be added to results + * @return list of result vectors + */ + public static final List splitIntoSegments(VecD2D a, VecD2D b, + double stepLength, List segments, boolean addFirst) { + if (segments == null) { + segments = new ArrayList(); + } + if (addFirst) { + segments.add(a.copy()); + } + double dist = a.distanceTo(b); + if (dist > stepLength) { + VecD2D pos = a.copy(); + VecD2D step = b.sub(a).limit(stepLength); + while (dist > stepLength) { + pos.addSelf(step); + segments.add(pos.copy()); + dist -= stepLength; + } + } + segments.add(b.copy()); + return segments; + } + + @XmlElement + public VecD2D a, b; + + public LineD2D(double x1, double y1, double x2, double y2) { + this.a = new VecD2D(x1, y1); + this.b = new VecD2D(x2, y2); + } + + public LineD2D(ReadonlyVecD2D a, ReadonlyVecD2D b) { + this.a = a.copy(); + this.b = b.copy(); + } + + public LineD2D(VecD2D a, VecD2D b) { + this.a = a; + this.b = b; + } + + /** + * Computes the dot product of these 2 vectors: line start -> point and the + * perpendicular line direction. If the result is negative + * + * @param p + * @return classifier double + */ + public double classifyPoint(ReadonlyVecD2D p) { + VecD2D normal = b.sub(a).perpendicular(); + double d = p.sub(a).dot(normal); + return Math.signum(d); + } + + /** + * Computes the closest point on this line to the point given. + * + * @param p + * point to check against + * @return closest point on the line + */ + public VecD2D closestPointTo(ReadonlyVecD2D p) { + final VecD2D v = b.sub(a); + final double t = p.sub(a).dot(v) / v.magSquared(); + // Check to see if t is beyond the extents of the line segment + if (t < 0.0f) { + return a.copy(); + } else if (t > 1.0f) { + return b.copy(); + } + // Return the point between 'a' and 'b' + return a.add(v.scaleSelf(t)); + } + + public LineD2D copy() { + return new LineD2D(a.copy(), b.copy()); + } + + public double distanceToPoint(ReadonlyVecD2D p) { + return closestPointTo(p).distanceTo(p); + } + + public double distanceToPointSquared(ReadonlyVecD2D p) { + return closestPointTo(p).distanceToSquared(p); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof LineD2D)) { + return false; + } + LineD2D l = (LineD2D) obj; + return (a.equals(l.a) || a.equals(l.b)) + && (b.equals(l.b) || b.equals(l.a)); + } + + public CircleD getBoundingCircle() { + return CircleD.from2Points(a, b); + } + + /** + * Returns the line's bounding rect. + * + * @return bounding rect + */ + public RectD getBounds() { + return new RectD(a, b); + } + + public VecD2D getDirection() { + return b.sub(a).normalize(); + } + + public double getHeading() { + return b.sub(a).heading(); + } + + public double getLength() { + return a.distanceTo(b); + } + + public double getLengthSquared() { + return a.distanceToSquared(b); + } + + public VecD2D getMidPoint() { + return a.add(b).scaleSelf(0.5f); + } + + public VecD2D getNormal() { + return b.sub(a).perpendicular(); + } + + public boolean hasEndPoint(VecD2D p) { + return a.equals(p) || b.equals(p); + } + + /** + * Computes a hash code ignoring the directionality of the line. + * + * @return hash code + * + * @see java.lang.Object#hashCode() + * @see #hashCodeWithDirection() + */ + public int hashCode() { + return a.hashCode() + b.hashCode(); + } + + /** + * Computes the hash code for this instance taking directionality into + * account. A->B will produce a different hash code than B->A. If + * directionality is not required or desired use the default + * {@link #hashCode()} method. + * + * @return hash code + * + * @see #hashCode() + */ + public int hashCodeWithDirection() { + long bits = 1L; + bits = 31L * bits + a.hashCode(); + bits = 31L * bits + b.hashCode(); + return (int) (bits ^ (bits >> 32)); + } + + /** + * Computes intersection between this and the given line. The returned value + * is a {@link LineIntersection} instance and contains both the type of + * intersection as well as the intersection points (if existing). The + * intersection points are ALWAYS computed for the types INTERSECTING and + * NON_INTERSECTING. In the latter case the points will lie outside the two + * given line segments and constitute the intersections of the infinite + * versions of each line. + * + * Based on: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ + * + * @param l + * line to intersect with + * @return intersection result + */ + public LineIntersection intersectLine(LineD2D l) { + LineIntersection isec = null; + double denom = (l.b.y - l.a.y) * (b.x - a.x) - (l.b.x - l.a.x) + * (b.y - a.y); + + double na = (l.b.x - l.a.x) * (a.y - l.a.y) - (l.b.y - l.a.y) + * (a.x - l.a.x); + double nb = (b.x - a.x) * (a.y - l.a.y) - (b.y - a.y) * (a.x - l.a.x); + + if (denom != 0.0) { + double ua = na / denom; + double ub = nb / denom; + final VecD2D i = a.interpolateTo(b, ua); + if (ua >= 0.0f && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) { + isec = new LineIntersection(Type.INTERSECTING, i, ua, ub); + } else { + isec = new LineIntersection(Type.NON_INTERSECTING, i, ua, ub); + } + } else { + if (na == 0.0 && nb == 0.0) { + if (distanceToPoint(l.a) == 0.0) { + isec = new LineIntersection(Type.COINCIDENT, null); + } else { + isec = new LineIntersection(Type.COINCIDENT_NO_INTERSECT, + null); + } + } else { + isec = new LineIntersection(Type.PARALLEL, null); + } + } + return isec; + } + + public LineD2D offsetAndGrowBy(double offset, double scale, VecD2D ref) { + VecD2D m = getMidPoint(); + VecD2D d = getDirection(); + VecD2D n = d.getPerpendicular(); + if (ref != null && m.sub(ref).dot(n) < 0) { + n.invert(); + } + n.normalizeTo(offset); + a.addSelf(n); + b.addSelf(n); + d.scaleSelf(scale); + a.subSelf(d); + b.addSelf(d); + return this; + } + + public LineD2D scaleLength(double scale) { + double delta = (1 - scale) * 0.5f; + VecD2D newA = a.interpolateTo(b, delta); + b.interpolateToSelf(a, delta); + a.set(newA); + return this; + } + + public LineD2D set(VecD2D a, VecD2D b) { + this.a = a; + this.b = b; + return this; + } + + public List splitIntoSegments(List segments, + double stepLength, boolean addFirst) { + return splitIntoSegments(a, b, stepLength, segments, addFirst); + } + + public RayD2D toRayD2D() { + return new RayD2D(a.copy(), getDirection()); + } + + public String toString() { + return a.toString() + " -> " + b.toString(); + } +} diff --git a/src.core/toxi/geom/LineD3D.java b/src.core/toxi/geom/LineD3D.java new file mode 100644 index 00000000..a163f51c --- /dev/null +++ b/src.core/toxi/geom/LineD3D.java @@ -0,0 +1,350 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +import toxi.geom.LineD3D.LineIntersection.Type; +import toxi.math.MathUtils; + +public class LineD3D { + + public static class LineIntersection { + + public static enum Type { + NON_INTERSECTING, + INTERSECTING + } + + private final Type type; + private final LineD3D line; + private final double[] coeff; + + private LineIntersection(Type type) { + this(type, null, 0, 0); + } + + private LineIntersection(Type type, LineD3D line, double mua, double mub) { + this.type = type; + this.line = line; + this.coeff = new double[] { + mua, mub + }; + } + + public double[] getCoefficients() { + return coeff; + } + + public double getLength() { + return line.getLength(); + } + + /** + * @return the pos + */ + public LineD3D getLine() { + return line.copy(); + } + + /** + * @return the type + */ + public Type getType() { + return type; + } + + public boolean isIntersectionInside() { + return type == Type.INTERSECTING && coeff[0] >= 0 && coeff[0] <= 1 + && coeff[1] >= 0 && coeff[1] <= 1; + } + + public String toString() { + return "type: " + type + " line: " + line; + } + } + + /** + * Splits the line between A and B into segments of the given length, + * starting at point A. The tweened points are added to the given result + * list. The last point added is B itself and hence it is likely that the + * last segment has a shorter length than the step length requested. The + * first point (A) can be omitted and not be added to the list if so + * desired. + * + * @param a + * start point + * @param b + * end point (always added to results) + * @param stepLength + * desired distance between points + * @param segments + * existing array list for results (or a new list, if null) + * @param addFirst + * false, if A is NOT to be added to results + * @return list of result vectors + */ + public static final List splitIntoSegments(VecD3D a, VecD3D b, + double stepLength, List segments, boolean addFirst) { + if (segments == null) { + segments = new ArrayList(); + } + if (addFirst) { + segments.add(a.copy()); + } + double dist = a.distanceTo(b); + if (dist > stepLength) { + VecD3D pos = a.copy(); + VecD3D step = b.sub(a).limit(stepLength); + while (dist > stepLength) { + pos.addSelf(step); + segments.add(pos.copy()); + dist -= stepLength; + } + } + segments.add(b.copy()); + return segments; + } + + @XmlElement + public VecD3D a, b; + + public LineD3D(double x1, double y1, double z1, double x2, double y2, double z2) { + this.a = new VecD3D(x1, y1, z1); + this.b = new VecD3D(x2, y2, z2); + } + + public LineD3D(ReadonlyVecD3D a, ReadonlyVecD3D b) { + this.a = a.copy(); + this.b = b.copy(); + } + + public LineD3D(VecD3D a, VecD3D b) { + this.a = a; + this.b = b; + } + + /** + * Calculates the line segment that is the shortest route between this line + * and the given one. Also calculates the coefficients where the end points + * of this new line lie on the existing ones. If these coefficients are + * within the 0.0 .. 1.0 interval the endpoints of the intersection line are + * within the given line segments, if not then the intersection line is + * outside. + * + *

+ * Code based on original by Paul Bourke:
+ * http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/ + *

+ */ + public LineIntersection closestLineTo(LineD3D l) { + VecD3D p43 = l.a.sub(l.b); + if (p43.isZeroVector()) { + return new LineIntersection(Type.NON_INTERSECTING); + } + VecD3D p21 = b.sub(a); + if (p21.isZeroVector()) { + return new LineIntersection(Type.NON_INTERSECTING); + } + VecD3D p13 = a.sub(l.a); + + double d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z; + double d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z; + double d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z; + double d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z; + double d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z; + + double denom = d2121 * d4343 - d4321 * d4321; + if (MathUtils.abs(denom) < MathUtils.EPS) { + return new LineIntersection(Type.NON_INTERSECTING); + } + double numer = d1343 * d4321 - d1321 * d4343; + double mua = (numer / denom); + double mub = ((d1343 + d4321 * mua) / d4343); + + VecD3D pa = a.add(p21.scaleSelf(mua)); + VecD3D pb = l.a.add(p43.scaleSelf(mub)); + return new LineIntersection(Type.INTERSECTING, new LineD3D(pa, pb), mua, + mub); + } + + /** + * Computes the closest point on this line to the given one. + * + * @param p + * point to check against + * @return closest point on the line + */ + public VecD3D closestPointTo(ReadonlyVecD3D p) { + final VecD3D v = b.sub(a); + final double t = p.sub(a).dot(v) / v.magSquared(); + // Check to see if t is beyond the extents of the line segment + if (t < 0.0f) { + return a.copy(); + } else if (t > 1.0f) { + return b.copy(); + } + // Return the point between 'a' and 'b' + return a.add(v.scaleSelf(t)); + } + + public LineD3D copy() { + return new LineD3D(a.copy(), b.copy()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof LineD3D)) { + return false; + } + LineD3D l = (LineD3D) obj; + return (a.equals(l.a) || a.equals(l.b)) + && (b.equals(l.b) || b.equals(l.a)); + } + + /** + * Returns the line's axis-aligned bounding box. + * + * @return aabb + * @see toxi.geom.AABB + */ + public AABBD getBounds() { + return AABBD.fromMinMax(a, b); + } + + public VecD3D getDirection() { + return b.sub(a).normalize(); + } + + public double getLength() { + return a.distanceTo(b); + } + + public double getLengthSquared() { + return a.distanceToSquared(b); + } + + public VecD3D getMidPoint() { + return a.add(b).scaleSelf(0.5f); + } + + public VecD3D getNormal() { + return b.cross(a); + } + + public boolean hasEndPoint(VecD3D p) { + return a.equals(p) || b.equals(p); + } + + /** + * Computes a hash code ignoring the directionality of the line. + * + * @return hash code + * + * @see java.lang.Object#hashCode() + * @see #hashCodeWithDirection() + */ + public int hashCode() { + return a.hashCode() + b.hashCode(); + } + + /** + * Computes the hash code for this instance taking directionality into + * account. A->B will produce a different hash code than B->A. If + * directionality is not required or desired use the default + * {@link #hashCode()} method. + * + * @return hash code + * + * @see #hashCode() + */ + public int hashCodeWithDirection() { + long bits = 1L; + bits = 31L * bits + a.hashCode(); + bits = 31L * bits + b.hashCode(); + return (int) (bits ^ (bits >> 32)); + } + + public LineD3D offsetAndGrowBy(double offset, double scale, VecD3D ref) { + VecD3D m = getMidPoint(); + VecD3D d = getDirection(); + VecD3D n = a.cross(d).normalize(); + if (ref != null && m.sub(ref).dot(n) < 0) { + n.invert(); + } + n.normalizeTo(offset); + a.addSelf(n); + b.addSelf(n); + d.scaleSelf(scale); + a.subSelf(d); + b.addSelf(d); + return this; + } + + public LineD3D scaleLength(double scale) { + double delta = (1 - scale) * 0.5f; + VecD3D newA = a.interpolateTo(b, delta); + b.interpolateToSelf(a, delta); + a.set(newA); + return this; + } + + public LineD3D set(ReadonlyVecD3D a, ReadonlyVecD3D b) { + this.a = a.copy(); + this.b = b.copy(); + return this; + } + + public LineD3D set(VecD3D a, VecD3D b) { + this.a = a; + this.b = b; + return this; + } + + public List splitIntoSegments(List segments, + double stepLength, boolean addFirst) { + return splitIntoSegments(a, b, stepLength, segments, addFirst); + } + + public RayD3D toRayD3D() { + return new RayD3D(a.copy(), getDirection()); + } + + public String toString() { + return a.toString() + " -> " + b.toString(); + } +} diff --git a/src.core/toxi/geom/LineStripD2D.java b/src.core/toxi/geom/LineStripD2D.java new file mode 100644 index 00000000..606ccf1b --- /dev/null +++ b/src.core/toxi/geom/LineStripD2D.java @@ -0,0 +1,319 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +import toxi.geom.LineD2D.LineIntersection; +import toxi.geom.LineD2D.LineIntersection.Type; +import toxi.math.MathUtils; + +public class LineStripD2D implements Iterable { + + @XmlElement(name = "v") + protected List vertices = new ArrayList(); + + protected double[] arcLenIndex; + + public LineStripD2D() { + } + + public LineStripD2D(Collection vertices) { + this.vertices = new ArrayList(vertices); + } + + public LineStripD2D add(double x, double y) { + vertices.add(new VecD2D(x, y)); + return this; + } + + public LineStripD2D add(ReadonlyVecD2D p) { + vertices.add(p.copy()); + return this; + } + + public LineStripD2D add(VecD2D p) { + vertices.add(p); + return this; + } + + /** + * Returns the vertex at the given index. This function follows Python + * convention, in that if the index is negative, it is considered relative + * to the list end. Therefore the vertex at index -1 is the last vertex in + * the list. + * + * @param i + * index + * @return vertex + */ + public VecD2D get(int i) { + if (i < 0) { + i += vertices.size(); + } + return vertices.get(i); + } + + public CircleD getBoundingCircleD() { + return CircleD.newBoundingCircleD(vertices); + } + + public RectD getBounds() { + return RectD.getBoundingRectD(vertices); + } + + public VecD2D getCentroid() { + int num = vertices.size(); + if (num > 0) { + VecD2D centroid = new VecD2D(); + for (VecD2D v : vertices) { + centroid.addSelf(v); + } + return centroid.scaleSelf(1f / num); + } + return null; + } + + /** + * Computes a list of points along the spline which are uniformly separated + * by the given step distance. + * + * @param step + * @return point list + */ + public List getDecimatedVertices(double step) { + return getDecimatedVertices(step, true); + } + + /** + * Computes a list of points along the spline which are close to uniformly + * separated by the given step distance. The uniform distribution is only an + * approximation and is based on the estimated arc length of the polyline. + * The distance between returned points might vary in places, especially if + * there're sharp angles between line segments. + * + * @param step + * @param doAddFinalVertex + * true, if the last vertex computed should be added regardless + * of its distance. + * @return point list + */ + public List getDecimatedVertices(double step, boolean doAddFinalVertex) { + ArrayList uniform = new ArrayList(); + if (vertices.size() < 3) { + if (vertices.size() == 2) { + new LineD2D(vertices.get(0), vertices.get(1)).splitIntoSegments( + uniform, step, true); + if (!doAddFinalVertex) { + uniform.remove(uniform.size() - 1); + } + } else { + return null; + } + } + double arcLen = getLength(); + if (arcLen > 0) { + double delta = step / arcLen; + int currIdx = 0; + for (double t = 0; t < 1.0; t += delta) { + double currT = t * arcLen; + while (currT >= arcLenIndex[currIdx]) { + currIdx++; + } + ReadonlyVecD2D p = vertices.get(currIdx - 1); + ReadonlyVecD2D q = vertices.get(currIdx); + double frac = ((currT - arcLenIndex[currIdx - 1]) / (arcLenIndex[currIdx] - arcLenIndex[currIdx - 1])); + VecD2D i = p.interpolateTo(q, frac); + uniform.add(i); + } + if (doAddFinalVertex) { + uniform.add(vertices.get(vertices.size() - 1).copy()); + } + } + return uniform; + } + + /** + * Returns a list of {@link Line2D} segments representing the segments + * between the vertices of this strip. + * + * @return list of lines + */ + public List getEdges() { + int num = vertices.size(); + List edges = new ArrayList(num - 1); + for (int i = 1; i < num; i++) { + edges.add(new LineD2D(vertices.get(i - 1), vertices.get(i))); + } + return edges; + } + + public double getLength() { + if (arcLenIndex == null + || (arcLenIndex != null && arcLenIndex.length != vertices + .size())) { + arcLenIndex = new double[vertices.size()]; + } + double arcLen = 0; + for (int i = 1; i < arcLenIndex.length; i++) { + ReadonlyVecD2D p = vertices.get(i - 1); + ReadonlyVecD2D q = vertices.get(i); + arcLen += p.distanceTo(q); + arcLenIndex[i] = arcLen; + } + return arcLen; + } + + /** + * Computes point at position t, where t is the normalized position along + * the strip. If t<0 then the first vertex of the strip is returned. If + * t>=1.0 the last vertex is returned. If the strip contains less than 2 + * vertices, this method returns null. + * + * @param t + * @return + */ + public VecD2D getPointAt(double t) { + int num = vertices.size(); + if (num > 1) { + if (t <= 0.0) { + return vertices.get(0); + } else if (t >= 1.0) { + return vertices.get(num - 1); + } + double totalLength = this.getLength(); + double offp = 0, offq = 0; + for (int i = 1; i < num; i++) { + VecD2D p = vertices.get(i - 1); + VecD2D q = vertices.get(i); + offq += q.distanceTo(p) / totalLength; + if (offp <= t && offq >= t) { + return p.interpolateTo(q, MathUtils.mapInterval(t, + offp, offq, 0.0, 1.0)); + } + offp = offq; + } + } + return null; + } + + public List getSegments() { + final int num = vertices.size(); + List segments = new ArrayList(num - 1); + for (int i = 1; i < num; i++) { + segments.add(new LineD2D(vertices.get(i - 1), vertices.get(i))); + } + return segments; + } + + /** + * @return the vertices + */ + public List getVertices() { + return vertices; + } + + public LineIntersection intersectLine(LineD2D line) { + LineD2D l = new LineD2D(new VecD2D(), new VecD2D()); + for (int i = 1, num = vertices.size(); i < num; i++) { + l.set(vertices.get(i - 1), vertices.get(i)); + LineIntersection isec = l.intersectLine(line); + if (isec.getType() == Type.INTERSECTING + || isec.getType() == Type.COINCIDENT) { + return isec; + } + } + return null; + } + + public Iterator iterator() { + return vertices.iterator(); + } + + public LineStripD2D rotate(double theta) { + for (VecD2D v : vertices) { + v.rotate(theta); + } + return this; + } + + public LineStripD2D scale(double scale) { + return scale(scale, scale); + } + + public LineStripD2D scale(double x, double y) { + for (VecD2D v : vertices) { + v.scaleSelf(x, y); + } + return this; + } + + public LineStripD2D scale(ReadonlyVecD2D scale) { + return scale(scale.x(), scale.y()); + } + + public LineStripD2D scaleSize(double scale) { + return scaleSize(scale, scale); + } + + public LineStripD2D scaleSize(double x, double y) { + VecD2D centroid = getCentroid(); + for (VecD2D v : vertices) { + v.subSelf(centroid).scaleSelf(x, y).addSelf(centroid); + } + return this; + } + + public LineStripD2D scaleSize(ReadonlyVecD2D scale) { + return scaleSize(scale.x(), scale.y()); + } + + /** + * @param vertices + * the vertices to set + */ + public void setVertices(List vertices) { + this.vertices = vertices; + } + + public LineStripD2D translate(double x, double y) { + for (VecD2D v : vertices) { + v.addSelf(x, y); + } + return this; + } + + public LineStripD2D translate(ReadonlyVecD2D offset) { + return translate(offset.x(), offset.y()); + } +} diff --git a/src.core/toxi/geom/LineStripD3D.java b/src.core/toxi/geom/LineStripD3D.java new file mode 100644 index 00000000..fd974b49 --- /dev/null +++ b/src.core/toxi/geom/LineStripD3D.java @@ -0,0 +1,183 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +public class LineStripD3D implements Iterable { + + @XmlElement(name = "v") + protected List vertices = new ArrayList(); + + protected double[] arcLenIndex; + + public LineStripD3D() { + } + + public LineStripD3D(Collection vertices) { + this.vertices = new ArrayList(vertices); + } + + public LineStripD3D add(double x, double y, double z) { + vertices.add(new VecD3D(x, y, z)); + return this; + } + + public LineStripD3D add(ReadonlyVecD3D p) { + vertices.add(p.copy()); + return this; + } + + public LineStripD3D add(VecD3D p) { + vertices.add(p); + return this; + } + + /** + * Returns the vertex at the given index. This function follows Python + * convention, in that if the index is negative, it is considered relative + * to the list end. Therefore the vertex at index -1 is the last vertex in + * the list. + * + * @param i + * index + * @return vertex + */ + public VecD3D get(int i) { + if (i < 0) { + i += vertices.size(); + } + return vertices.get(i); + } + + /** + * Computes a list of points along the spline which are uniformly separated + * by the given step distance. + * + * @param step + * @return point list + */ + public List getDecimatedVertices(double step) { + return getDecimatedVertices(step, true); + } + + /** + * Computes a list of points along the spline which are close to uniformly + * separated by the given step distance. The uniform distribution is only an + * approximation and is based on the estimated arc length of the polyline. + * The distance between returned points might vary in places, especially if + * there're sharp angles between line segments. + * + * @param step + * @param doAddFinalVertex + * true, if the last vertex computed should be added regardless + * of its distance. + * @return point list + */ + public List getDecimatedVertices(double step, boolean doAddFinalVertex) { + ArrayList uniform = new ArrayList(); + if (vertices.size() < 3) { + if (vertices.size() == 2) { + new LineD3D(vertices.get(0), vertices.get(1)).splitIntoSegments( + uniform, step, true); + if (!doAddFinalVertex) { + uniform.remove(uniform.size() - 1); + } + } else { + return null; + } + } + double arcLen = getEstimatedArcLength(); + double delta = (double) step / arcLen; + int currIdx = 0; + for (double t = 0; t < 1.0; t += delta) { + double currT = t * arcLen; + while (currT >= arcLenIndex[currIdx]) { + currIdx++; + } + ReadonlyVecD3D p = vertices.get(currIdx - 1); + ReadonlyVecD3D q = vertices.get(currIdx); + double frac = ((currT - arcLenIndex[currIdx - 1]) / (arcLenIndex[currIdx] - arcLenIndex[currIdx - 1])); + VecD3D i = p.interpolateTo(q, frac); + uniform.add(i); + } + if (doAddFinalVertex) { + uniform.add(vertices.get(vertices.size() - 1).copy()); + } + return uniform; + } + + public double getEstimatedArcLength() { + if (arcLenIndex == null + || (arcLenIndex != null && arcLenIndex.length != vertices + .size())) { + arcLenIndex = new double[vertices.size()]; + } + double arcLen = 0; + for (int i = 1; i < arcLenIndex.length; i++) { + ReadonlyVecD3D p = vertices.get(i - 1); + ReadonlyVecD3D q = vertices.get(i); + arcLen += p.distanceTo(q); + arcLenIndex[i] = arcLen; + } + return arcLen; + } + + public List getSegments() { + final int num = vertices.size(); + List segments = new ArrayList(num - 1); + for (int i = 1; i < num; i++) { + segments.add(new LineD3D(vertices.get(i - 1), vertices.get(i))); + } + return segments; + } + + /** + * @return the vertices + */ + public List getVertices() { + return vertices; + } + + public Iterator iterator() { + return vertices.iterator(); + } + + /** + * @param vertices + * the vertices to set + */ + public void setVertices(List vertices) { + this.vertices = vertices; + } +} diff --git a/src.core/toxi/geom/LocalGridTesselatorD.java b/src.core/toxi/geom/LocalGridTesselatorD.java new file mode 100644 index 00000000..c9974c58 --- /dev/null +++ b/src.core/toxi/geom/LocalGridTesselatorD.java @@ -0,0 +1,46 @@ +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +import toxi.math.ScaleMap; + +/** + * A concrete implementation of the abstract {@link GridTesselator} using a grid + * in polygon-local coordinate space for generating additional points within a + * polygon. The resolution setting of this class defines number of desired grid + * points in X & Y direction. E.g. a resolution of 10 means up to 10x10 grid + * points are created a within the polygon bounding rect. For smaller polygons, + * the resulting triangles will simply be smaller too. This resolution is used + * independently on polygon size. Use the {@link GlobalGridTesselator} for an + * alternative behavior, resulting in more uniformly sized triangles. + * + * @see GridTesselator + * @see GlobalGridTesselator + * @see PolygonTesselator + */ +public class LocalGridTesselatorD extends GridTesselatorD { + + public LocalGridTesselatorD(int res) { + super(res); + } + + protected List createInsidePoints(PolygonD2D poly, RectD bounds) { + List points = new ArrayList(); + int ires = (int) res; + ScaleMap xmap = new ScaleMap(0, ires, bounds.getLeft(), + bounds.getRight()); + ScaleMap ymap = new ScaleMap(0, ires, bounds.getTop(), + bounds.getBottom()); + for (int y = 0; y < ires; y++) { + float yy = (float) ymap.getMappedValueFor(y); + for (int x = 0; x < ires; x++) { + VecD2D p = new VecD2D((float) xmap.getMappedValueFor(x), yy); + if (poly.containsPoint(p)) { + points.add(p); + } + } + } + return points; + } +} diff --git a/src.core/toxi/geom/Matrix4x4.java b/src.core/toxi/geom/Matrix4x4.java index aa9b1c33..9b88800f 100644 --- a/src.core/toxi/geom/Matrix4x4.java +++ b/src.core/toxi/geom/Matrix4x4.java @@ -310,6 +310,9 @@ public Matrix4x4 addSelf(Matrix4x4 m) { public Vec3D applyTo(ReadonlyVec3D v) { return applyToSelf(new Vec3D(v)); } + public VecD3D applyTo(ReadonlyVecD3D v) { + return applyToSelf(new VecD3D(v)); + } public Vec3D applyToSelf(Vec3D v) { for (int i = 0; i < 4; i++) { @@ -320,6 +323,14 @@ public Vec3D applyToSelf(Vec3D v) { (float) (1.0 / temp[3])); return v; } + public VecD3D applyToSelf(VecD3D v) { + for (int i = 0; i < 4; i++) { + double[] m = matrix[i]; + temp[i] = v.x * m[0] + v.y * m[1] + v.z * m[2] + m[3]; + } + v.set(temp[0], temp[1], temp[2]).scaleSelf((1.0 / temp[3])); + return v; + } public Matrix4x4 copy() { return new Matrix4x4(this); @@ -332,6 +343,9 @@ public Matrix4x4 getInverted() { public Matrix4x4 getRotatedAroundAxis(ReadonlyVec3D axis, double theta) { return new Matrix4x4(this).rotateAroundAxis(axis, theta); } + public Matrix4x4 getRotatedAroundAxis(ReadonlyVecD3D axis, double theta) { + return new Matrix4x4(this).rotateAroundAxis(axis, theta); + } public Matrix4x4 getRotatedX(double theta) { return new Matrix4x4(this).rotateX(theta); @@ -485,14 +499,21 @@ public Matrix4x4 invert() { return this; } - public Matrix4x4 lookAt(ReadonlyVec3D eye, ReadonlyVec3D target, - ReadonlyVec3D up) { + public Matrix4x4 lookAt(ReadonlyVec3D eye, ReadonlyVec3D target, ReadonlyVec3D up) { Vec3D f = eye.sub(target).normalize(); Vec3D s = up.cross(f).normalize(); Vec3D t = f.cross(s).normalize(); return set(s.x, s.y, s.z, -s.dot(eye), t.x, t.y, t.z, -t.dot(eye), f.x, f.y, f.z, -f.dot(eye), 0, 0, 0, 1); } + public Matrix4x4 lookAt(ReadonlyVecD3D eye, ReadonlyVecD3D target, ReadonlyVecD3D up) { + VecD3D f = eye.sub(target).normalize(); + VecD3D s = up.cross(f).normalize(); + VecD3D t = f.cross(s).normalize(); + return set(s.x, s.y, s.z, -s.dot(eye), t.x, t.y, t.z, -t.dot(eye), f.x, + f.y, f.z, -f.dot(eye), 0, 0, 0, 1); + } + public Matrix4x4 multiply(double factor) { return new Matrix4x4(this).multiply(factor); @@ -566,6 +587,21 @@ public Matrix4x4 rotateAroundAxis(ReadonlyVec3D axis, double theta) { t * z * z + c, 0, 0, 0, 0, 1); return this.multiplySelf(TEMP); } + public Matrix4x4 rotateAroundAxis(ReadonlyVecD3D axis, double theta) { + double x, y, z, s, c, t, tx, ty; + x = axis.x(); + y = axis.y(); + z = axis.z(); + s = Math.sin(theta); + c = Math.cos(theta); + t = 1 - c; + tx = t * x; + ty = t * y; + TEMP.set(tx * x + c, tx * y + s * z, tx * z - s * y, 0, tx * y - s * z, + ty * y + c, ty * z + s * x, 0, tx * z + s * y, ty * z - s * x, + t * z * z + c, 0, 0, 0, 0, 1); + return this.multiplySelf(TEMP); + } /** * Applies rotation about X to this matrix. @@ -617,6 +653,9 @@ public Matrix4x4 scale(double scaleX, double scaleY, double scaleZ) { public Matrix4x4 scale(ReadonlyVec3D scale) { return new Matrix4x4(this).scaleSelf(scale.x(), scale.y(), scale.z()); } + public Matrix4x4 scale(ReadonlyVecD3D scale) { + return new Matrix4x4(this).scaleSelf(scale.x(), scale.y(), scale.z()); + } public Matrix4x4 scaleSelf(double scale) { return scaleSelf(scale, scale, scale); @@ -631,6 +670,9 @@ public Matrix4x4 scaleSelf(double scaleX, double scaleY, double scaleZ) { public Matrix4x4 scaleSelf(ReadonlyVec3D scale) { return scaleSelf(scale.x(), scale.y(), scale.z()); } + public Matrix4x4 scaleSelf(ReadonlyVecD3D scale) { + return scaleSelf(scale.x(), scale.y(), scale.z()); + } public Matrix4x4 set(double a, double b, double c, double d, double e, double f, double g, double h, double i, double j, double k, @@ -789,6 +831,11 @@ public Matrix4x4 translate(ReadonlyVec3D trans) { return new Matrix4x4(this).translateSelf(trans.x(), trans.y(), trans.z()); } + public Matrix4x4 translate(ReadonlyVecD3D trans) { + return new Matrix4x4(this).translateSelf(trans.x(), trans.y(), + trans.z()); + } + public Matrix4x4 translateSelf(double dx, double dy, double dz) { TEMP.identity(); @@ -799,6 +846,10 @@ public Matrix4x4 translateSelf(double dx, double dy, double dz) { public Matrix4x4 translateSelf(ReadonlyVec3D trans) { return translateSelf(trans.x(), trans.y(), trans.z()); } + public Matrix4x4 translateSelf(ReadonlyVecD3D trans) { + return translateSelf(trans.x(), trans.y(), trans.z()); + } + /** * Converts the matrix (in-place) between column-major to row-major order diff --git a/src.core/toxi/geom/OctreeVisitorD.java b/src.core/toxi/geom/OctreeVisitorD.java new file mode 100644 index 00000000..a6301a6d --- /dev/null +++ b/src.core/toxi/geom/OctreeVisitorD.java @@ -0,0 +1,44 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * This interface is the core part of the visitor pattern application for + * {@link PointOctree}s. It can be used to apply a procedure to all tree nodes + * when passed to {@link PointOctree#applyVisitor(OctreeVisitorD)}. + */ +public interface OctreeVisitorD { + + /** + * Applies the procedure defined by an implementation of this interface to + * the given tree node. + * + * @param node + */ + void visitNode(PointOctreeD node); +} diff --git a/src.core/toxi/geom/OriginD3D.java b/src.core/toxi/geom/OriginD3D.java new file mode 100644 index 00000000..c0d3c7ac --- /dev/null +++ b/src.core/toxi/geom/OriginD3D.java @@ -0,0 +1,125 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.geom.VecD3D.AxisD; + +/** + * This class defines an origin and set of axis vectors for a 3D cartesian + * coordinate system. + */ +public class OriginD3D { + + public ReadonlyVecD3D origin; + public ReadonlyVecD3D xAxis, yAxis, zAxis; + + /** + * Creates a new origin at the world origin using the standard XYZ axes + */ + public OriginD3D() { + this(new VecD3D()); + } + + public OriginD3D(double x, double y, double z) { + this(new VecD3D(x, y, z)); + } + + public OriginD3D(Matrix4x4 mat) { + this.origin = mat.applyToSelf(new VecD3D()); + this.xAxis = mat.applyTo(VecD3D.X_AXIS).subSelf(origin).normalize(); + this.yAxis = mat.applyTo(VecD3D.Y_AXIS).subSelf(origin).normalize(); + zAxis = xAxis.crossInto(yAxis, new VecD3D()); + } + + /** + * Creates a new origin at the given origin using the standard XYZ axes + * + * @param o + * origin + */ + public OriginD3D(VecD3D o) { + origin = o; + xAxis = VecD3D.X_AXIS; + yAxis = VecD3D.Y_AXIS; + zAxis = VecD3D.Z_AXIS; + } + + /** + * Attempts to create a cartesian coordinate system with the given point as + * its origin and the direction as its Z-axis. In cases when two of the + * direction vector components are equal, the constructor will throw an + * {@link IllegalArgumentException}. + * + * @param o + * origin of the coordinate system + * @param dir + * z-axis + */ + public OriginD3D(VecD3D o, VecD3D dir) { + this.origin = o; + this.zAxis = dir.getNormalized(); + VecD3D av = null; + AxisD a = (AxisD) zAxis.getClosestAxis(); + if (a == VecD3D.AxisD.X) { + av = VecD3D.AxisD.Z.getVector().getInverted(); + } else if (a == VecD3D.AxisD.Y) { + av = VecD3D.AxisD.Z.getVector().getInverted(); + } else if (a == VecD3D.AxisD.Z) { + av = VecD3D.AxisD.X.getVector().getInverted(); + } + if (av == null) { + throw new IllegalArgumentException( + "can't create a coordinate system for direction: " + dir); + } + xAxis = av.cross(dir).normalize(); + yAxis = xAxis.cross(zAxis).normalize(); + + } + + /** + * @param o + * origin of the coordinate system + * @param x + * x-direction of the coordinate system + * @param y + * y-direction of the coordinate system + * @throws IllegalArgumentException + * if x and y vectors are not orthogonal + */ + public OriginD3D(VecD3D o, VecD3D x, VecD3D y) throws IllegalArgumentException { + origin = o; + xAxis = x; + yAxis = y; + xAxis.getNormalized(); + yAxis.getNormalized(); + if (Math.abs(xAxis.dot(yAxis)) > 0.0001) { + throw new IllegalArgumentException("Axis vectors aren't orthogonal"); + } + zAxis = xAxis.crossInto(yAxis, new VecD3D()); + } +} diff --git a/src.core/toxi/geom/PlaneD.java b/src.core/toxi/geom/PlaneD.java new file mode 100644 index 00000000..1454d864 --- /dev/null +++ b/src.core/toxi/geom/PlaneD.java @@ -0,0 +1,232 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +import toxi.geom.mesh.MeshD3D; +import toxi.geom.mesh.TriangleMeshD; +import toxi.math.MathUtils; + +/** + * Class to describe and work with infinite generic 3D planes. Useful for + * intersection problems and classifying points. + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class PlaneD extends VecD3D implements ShapeD3D { + + /** + * Classifier constant for {@link PlaneD#classifyPoint(ReadonlyVecD3D, double)} + */ + public enum Classifier { + FRONT, + BACK, + ON_PLANE; + } + + public static final PlaneD XY = new PlaneD(new VecD3D(), VecD3D.Z_AXIS); + public static final PlaneD XZ = new PlaneD(new VecD3D(), VecD3D.Y_AXIS); + public static final PlaneD YZ = new PlaneD(new VecD3D(), VecD3D.X_AXIS); + + @XmlElement(required = true) + public VecD3D normal; + + public PlaneD() { + super(); + normal = VecD3D.Y_AXIS.copy(); + } + + public PlaneD(ReadonlyVecD3D origin, ReadonlyVecD3D norm) { + super(origin); + normal = norm.getNormalized(); + } + + public PlaneD(TriangleD3D t) { + this(t.computeCentroid(), t.computeNormal()); + } + + /** + * Classifies the relative position of the given point to the plane using + * the given tolerance. + * + * @return One of the 3 classification types: FRONT, BACK, ON_PLANE + */ + public Classifier classifyPoint(ReadonlyVecD3D p, double tolerance) { + double d = this.sub(p).normalize().dot(normal); + if (d < -tolerance) { + return Classifier.FRONT; + } else if (d > tolerance) { + return Classifier.BACK; + } + return Classifier.ON_PLANE; + } + + public boolean containsPoint(ReadonlyVecD3D p) { + return classifyPoint(p, MathUtils.EPS) == Classifier.ON_PLANE; + } + + public double getDCoeff() { + return this.dot(normal); + } + + /** + * Calculates distance from the plane to point P. + * + * @param p + * @return distance + */ + public double getDistanceToPoint(VecD3D p) { + double sn = -normal.dot(p.sub(this)); + double sd = normal.magSquared(); + VecD3D isec = p.add(normal.scale(sn / sd)); + return isec.distanceTo(p); + } + + /** + * Calculates the intersection point between plane and ray (line). + * + * @param r + * @return intersection point or null if ray doesn't intersect plane + */ + public ReadonlyVecD3D getIntersectionWithRay(RayD3D r) { + double denom = normal.dot(r.getDirection()); + if (denom > MathUtils.EPS) { + double u = normal.dot(this.sub(r)) / denom; + return r.getPointAtDistance(u); + } else { + return null; + } + } + + public VecD3D getProjectedPoint(VecD3D p) { + VecD3D dir; + if (normal.dot(sub(p)) < 0) { + dir = normal.getInverted(); + } else { + dir = normal; + } + VecD3D proj = new RayD3D(p, dir) + .getPointAtDistance(getDistanceToPoint(p)); + return proj; + } + + /** + * Calculates the distance of the vector to the given plane in the specified + * direction. A plane is specified by a 3D point and a normal vector + * perpendicular to the plane. Normalized directional vectors expected (for + * rayDir and planeNormal). + * + * @param ray + * intersection ray + * @return distance to plane in world units, -1 if no intersection. + */ + public double intersectRayDistance(RayD3D ray) { + double d = -normal.dot(this); + double numer = normal.dot(ray) + d; + double denom = normal.dot(ray.dir); + + // normal is orthogonal to vector, cant intersect + if (MathUtils.abs(denom) < MathUtils.EPS) { + return -1; + } + + return -(numer / denom); + } + + /** + * Computes the intersection ray between this plane and the given one. If + * the planes are parallel or coincident the method returns null. If the + * planes are intersecting, the returned {@link RayD3D} will start at a point + * lying on both planes and point along the infinite intersection line + * between them. + * + * Code ported from: + * http://forums.create.msdn.com/forums/p/39074/234178.aspx#234178 + * + * @param plane + * intersection partner + * @return intersection ray or null + */ + public RayD3D intersectsPlaneD(PlaneD plane) { + double d = getDCoeff(); + double d2 = plane.getDCoeff(); + + if (normal.equalsWithTolerance(plane.normal, 0.0001f) || d == d2) { + return null; + } + + double offDiagonal = normal.dot(plane.normal); + double det = 1.0 / (1 - offDiagonal * offDiagonal); + double a = (d - d2 * offDiagonal) * det; + double b = (d2 - d * offDiagonal) * det; + VecD3D anchor = normal.scale(a).addSelf( + plane.normal.scale( b)); + VecD3D dir = normal.cross(plane.normal); + + return new RayD3D(anchor, dir); + } + + /** + * Creates a TriangleMesh representation of the plane as a finite, squared + * quad of the requested size, centred around the current plane point. + * + * @param size + * desired edge length + * @return mesh + */ + public MeshD3D toMesh(double size) { + return toMesh(null, size); + } + + public MeshD3D toMesh(MeshD3D mesh, double size) { + if (mesh == null) { + mesh = new TriangleMeshD("plane", 4, 2); + } + ReadonlyVecD3D p = equalsWithTolerance(VecD3D.ZERO, 0.01f) ? add(0.01f, + 0.01f, 0.01f) : this; + size *= 0.5f; + VecD3D n = p.cross(normal).normalizeTo(size); + VecD3D m = n.cross(normal).normalizeTo(size); + VecD3D a = this.add(n).addSelf(m); + VecD3D b = this.add(n).subSelf(m); + VecD3D c = this.sub(n).subSelf(m); + VecD3D d = this.sub(n).addSelf(m); + mesh.addFaceD(a, d, b, null, null, null, null); + mesh.addFaceD(b, d, c, null, null, null, null); + return mesh; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("origin: ").append(super.toString()).append(" norm: ") + .append(normal.toString()); + return sb.toString(); + } +} diff --git a/src.core/toxi/geom/PlaneDIntersector.java b/src.core/toxi/geom/PlaneDIntersector.java new file mode 100644 index 00000000..333b579e --- /dev/null +++ b/src.core/toxi/geom/PlaneDIntersector.java @@ -0,0 +1,47 @@ +package toxi.geom; + +import toxi.math.MathUtils; + +public class PlaneDIntersector implements IntersectorD3D { + + private PlaneD plane; + private final IsectDataD3D isec; + + public PlaneDIntersector(PlaneD p) { + this.plane = p; + this.isec = new IsectDataD3D(); + } + + public IsectDataD3D getIntersectionDataD() { + return isec; + } + + /** + * @return the box + */ + public PlaneD getPlane() { + return plane; + } + + public boolean intersectsRayD(RayD3D ray) { + double d = -plane.normal.dot(plane); + double numer = plane.normal.dot(ray) + d; + double denom = plane.normal.dot(ray.dir); + + // normal is orthogonal to vector, can't intersect + if (isec.isIntersection = (MathUtils.abs(denom) >= MathUtils.EPS)) { + isec.dist = -(numer / denom); + isec.pos = ray.getPointAtDistance(isec.dist); + isec.normal = plane.normal; + } + return isec.isIntersection; + } + + /** + * @param p + * the plane to set + */ + public void setPlaneD(PlaneD p) { + this.plane = p; + } +} diff --git a/src.core/toxi/geom/PointCloudD3D.java b/src.core/toxi/geom/PointCloudD3D.java new file mode 100644 index 00000000..9c45cdcf --- /dev/null +++ b/src.core/toxi/geom/PointCloudD3D.java @@ -0,0 +1,205 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import toxi.math.MathUtils; + +public class PointCloudD3D implements Iterable { + + protected List points; + + protected VecD3D min, max; + protected VecD3D centroid; + + protected double radiusSquared; + + public PointCloudD3D() { + this(100); + } + + public PointCloudD3D(int numPoints) { + points = new ArrayList(numPoints); + clear(); + } + + public PointCloudD3D addAll(List plist) { + for (VecD3D p : plist) { + addPoint(p); + } + return this; + } + + public PointCloudD3D addPoint(VecD3D p) { + points.add(p); + min.minSelf(p); + max.maxSelf(p); + centroid.set(min.add(max).scaleSelf(0.5f)); + radiusSquared = MathUtils.max(radiusSquared, + p.distanceToSquared(centroid)); + return this; + } + + /** + * Applies the given transformation matrix to all points in the cloud. + * + * @param m + * transformation matrix + * @return itself + */ + public PointCloudD3D applyMatrix(Matrix4x4 m) { + for (VecD3D p : points) { + p.set(m.applyTo(p)); + } + updateBounds(); + return this; + } + + /** + * Updates all points in the cloud so that their new centroid is at the + * origin. + * + * @return itself + */ + public PointCloudD3D center() { + return center(null); + } + + /** + * Updates all points in the cloud so that their new centroid is at the + * given point. + * + * @param origin + * new centroid + * @return itself + */ + public PointCloudD3D center(ReadonlyVecD3D origin) { + getCentroid(); + VecD3D delta = origin != null ? origin.sub(centroid) : centroid + .getInverted(); + for (VecD3D p : points) { + p.addSelf(delta); + } + min.addSelf(delta); + max.addSelf(delta); + centroid.addSelf(delta); + return this; + } + + /** + * Removes all points from the cloud and resets the bounds and centroid. + * + * @return itself + */ + public PointCloudD3D clear() { + points.clear(); + min = VecD3D.MAX_VALUE.copy(); + max = VecD3D.NEG_MAX_VALUE.copy(); + centroid = new VecD3D(); + return this; + } + + /** + * Creates a deep copy of the cloud + * + * @return copied instance + */ + public PointCloudD3D copy() { + PointCloudD3D c = new PointCloudD3D(points.size()); + for (ReadonlyVecD3D p : points) { + c.addPoint(p.copy()); + } + return c; + } + + public AABBD getBoundingBox() { + return AABBD.fromMinMax(min, max); + } + + public SphereD getBoundingSphereD() { + return new SphereD(getCentroid(), Math.sqrt(radiusSquared)); + } + + /** + * @return the cloud centroid + */ + public VecD3D getCentroid() { + return centroid; + } + + /** + * @return an iterator for the backing point collection. + * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return points.iterator(); + } + + /** + * Removes the point from the cloud, but doesn't update the bounds + * automatically. + * + * @param p + * @return true, if point has been removed. + */ + public boolean removePoint(ReadonlyVecD3D p) { + return points.remove(p); + } + + /** + * @return the current number of points in the cloud + */ + public int size() { + return points.size(); + } + + /** + * Recalculates the bounding box, bounding sphere and centroid of the cloud. + * + * @return itself + */ + public PointCloudD3D updateBounds() { + min = VecD3D.MAX_VALUE.copy(); + max = VecD3D.NEG_MAX_VALUE.copy(); + for (VecD3D p : points) { + min.minSelf(p); + max.maxSelf(p); + } + centroid.set(min.add(max).scaleSelf(0.5f)); + radiusSquared = 0; + for (ReadonlyVecD3D p : points) { + radiusSquared = MathUtils.max(radiusSquared, + p.distanceToSquared(centroid)); + } + return this; + } +} diff --git a/src.core/toxi/geom/PointOctreeD.java b/src.core/toxi/geom/PointOctreeD.java new file mode 100644 index 00000000..66b1824c --- /dev/null +++ b/src.core/toxi/geom/PointOctreeD.java @@ -0,0 +1,461 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Implements a spatial subdivision tree to work efficiently with large numbers + * of 3D particles. This octree can only be used for particle type objects and + * does NOT support 3D mesh geometry as other forms of Octrees do. + * + * For further reference also see the OctreeDemo in the /examples folder. + * + */ +public class PointOctreeD extends AABBD implements ShapeD3D { + + /** + * alternative tree recursion limit, number of world units when cells are + * not subdivided any further + */ + protected double minNodeSize = 4; + + /** + * + */ + protected PointOctreeD parent; + + protected PointOctreeD[] children; + + protected byte numChildren; + + protected ArrayList points; + + protected double size, halfSize; + + protected VecD3D offset; + + private int depth = 0; + + private boolean isAutoReducing = false; + + /** + * Constructs a new PointOctreeD node within the AABBD cube volume: {o.x, o.y, + * o.z} ... {o.x+size, o.y+size, o.z+size} + * + * @param p + * parent node + * @param o + * tree origin + * @param halfSize + * half length of the tree volume along a single axis + */ + private PointOctreeD(PointOctreeD p, VecD3D o, double halfSize) { + super(o.add(halfSize, halfSize, halfSize), new VecD3D(halfSize, + halfSize, halfSize)); + this.parent = p; + this.halfSize = halfSize; + this.size = halfSize * 2; + this.offset = o; + this.numChildren = 0; + if (parent != null) { + depth = parent.depth + 1; + minNodeSize = parent.minNodeSize; + } + } + + /** + * Constructs a new PointOctree node within the AABBD cube volume: {o.x, o.y, + * o.z} ... {o.x+size, o.y+size, o.z+size} + * + * @param o + * tree origin + * @param size + * size of the tree volume along a single axis + */ + public PointOctreeD(VecD3D o, double size) { + this(null, o, size / 2); + } + + /** + * Adds all points of the collection to the octree. IMPORTANT: Points need + * be of type VecD3D or have subclassed it. + * + * @param points + * point collection + * @return true, if all points have been added successfully. + */ + public boolean addAll(Collection points) { + boolean addedAll = true; + for (VecD3D p : points) { + addedAll &= addPoint(p); + } + return addedAll; + } + + /** + * Adds a new point/particle to the tree structure. All points are stored + * within leaf nodes only. The tree implementation is using lazy + * instantiation for all intermediate tree levels. + * + * @param p + * @return true, if point has been added successfully + */ + public boolean addPoint(VecD3D p) { + // check if point is inside cube + if (containsPoint(p)) { + // only add points to leaves for now + if (halfSize <= minNodeSize) { + if (points == null) { + points = new ArrayList(); + } + points.add(p); + return true; + } else { + VecD3D plocal = p.sub(offset); + if (children == null) { + children = new PointOctreeD[8]; + } + int octant = getOctantID(plocal); + if (children[octant] == null) { + VecD3D off = offset.add(new VecD3D( + (octant & 1) != 0 ? halfSize : 0, + (octant & 2) != 0 ? halfSize : 0, + (octant & 4) != 0 ? halfSize : 0)); + children[octant] = new PointOctreeD(this, off, + halfSize * 0.5f); + numChildren++; + } + return children[octant].addPoint(p); + } + } + return false; + } + + /** + * Applies the given {@link OctreeVisitor} implementation to this node and + * all of its children. + */ + public void applyVisitor(OctreeVisitorD visitor) { + visitor.visitNode(this); + if (numChildren > 0) { + for (PointOctreeD c : children) { + if (c != null) { + c.applyVisitor(visitor); + } + } + } + } + + public boolean containsPoint(ReadonlyVecD3D p) { + return p.isInAABBD(this); + } + + public void empty() { + numChildren = 0; + children = null; + points = null; + } + + /** + * @return a copy of the child nodes array + */ + public PointOctreeD[] getChildren() { + if (children != null) { + PointOctreeD[] clones = new PointOctreeD[8]; + System.arraycopy(children, 0, clones, 0, 8); + return clones; + } + return null; + } + + /** + * @return the depth + */ + public int getDepth() { + return depth; + } + + /** + * Finds the leaf node which spatially relates to the given point + * + * @param p + * point to check + * @return leaf node or null if point is outside the tree dimensions + */ + public PointOctreeD getLeafForPoint(ReadonlyVecD3D p) { + // if not a leaf node... + if (p.isInAABBD(this)) { + if (numChildren > 0) { + int octant = getOctantID(p.sub(offset)); + if (children[octant] != null) { + return children[octant].getLeafForPoint(p); + } + } else if (points != null) { + return this; + } + } + return null; + } + + /** + * Returns the minimum size of nodes (in world units). This value acts as + * tree recursion limit since nodes smaller than this size are not + * subdivided further. Leaf node are always smaller or equal to this size. + * + * @return the minimum size of tree nodes + */ + public double getMinNodeSize() { + return minNodeSize; + } + + public double getNodeSize() { + return size; + } + + /** + * @return the number of child nodes (max. 8) + */ + public int getNumChildren() { + return numChildren; + } + + /** + * Computes the local child octant/cube index for the given point + * + * @param plocal + * point in the node-local coordinate system + * @return octant index + */ + protected final int getOctantID(VecD3D plocal) { + return (plocal.x >= halfSize ? 1 : 0) + (plocal.y >= halfSize ? 2 : 0) + + (plocal.z >= halfSize ? 4 : 0); + } + + /** + * @return the offset + */ + public ReadonlyVecD3D getOffset() { + return offset; + } + + /** + * @return the parent + */ + public PointOctreeD getParent() { + return parent; + } + + /** + * @return the points + */ + public List getPoints() { + List results = null; + if (points != null) { + results = new ArrayList(points); + } else if (numChildren > 0) { + for (int i = 0; i < 8; i++) { + if (children[i] != null) { + List childPoints = children[i].getPoints(); + if (childPoints != null) { + if (results == null) { + results = new ArrayList(); + } + results.addAll(childPoints); + } + } + } + } + return results; + } + + /** + * Selects all stored points within the given axis-aligned bounding box. + * + * @param b + * AABBD + * @return all points with the box volume + */ + public List getPointsWithinBox(AABBD b) { + ArrayList results = null; + if (this.intersectsBox(b)) { + if (points != null) { + for (VecD3D q : points) { + if (q.isInAABBD(b)) { + if (results == null) { + results = new ArrayList(); + } + results.add(q); + } + } + } else if (numChildren > 0) { + for (int i = 0; i < 8; i++) { + if (children[i] != null) { + List points = children[i].getPointsWithinBox(b); + if (points != null) { + if (results == null) { + results = new ArrayList(); + } + results.addAll(points); + } + } + } + } + } + return results; + } + + /** + * Selects all stored points within the given sphere volume + * + * @param s + * sphere + * @return selected points + */ + public List getPointsWithinSphereD(SphereD s) { + ArrayList results = null; + if (this.intersectsSphereD(s)) { + if (points != null) { + for (VecD3D q : points) { + if (s.containsPoint(q)) { + if (results == null) { + results = new ArrayList(); + } + results.add(q); + } + } + } else if (numChildren > 0) { + for (int i = 0; i < 8; i++) { + if (children[i] != null) { + List points = children[i] + .getPointsWithinSphereD(s); + if (points != null) { + if (results == null) { + results = new ArrayList(); + } + results.addAll(points); + } + } + } + } + } + return results; + } + + /** + * Selects all stored points within the given sphere volume + * + * @param sphereOrigin + * @param clipRadius + * @return selected points + */ + public List getPointsWithinSphereD(VecD3D sphereOrigin, + double clipRadius) { + return getPointsWithinSphereD(new SphereD(sphereOrigin, clipRadius)); + } + + /** + * @return the size + */ + public double getSize() { + return size; + } + + private void reduceBranch() { + if (points != null && points.size() == 0) { + points = null; + } + if (numChildren > 0) { + for (int i = 0; i < 8; i++) { + if (children[i] != null && children[i].points == null) { + children[i] = null; + } + } + } + if (parent != null) { + parent.reduceBranch(); + } + } + + /** + * Removes a point from the tree and (optionally) tries to release memory by + * reducing now empty sub-branches. + * + * @param p + * point to delete + * @return true, if the point was found & removed + */ + public boolean remove(ReadonlyVecD3D p) { + boolean found = false; + PointOctreeD leaf = getLeafForPoint(p); + if (leaf != null) { + if (leaf.points.remove(p)) { + found = true; + if (isAutoReducing && leaf.points.size() == 0) { + leaf.reduceBranch(); + } + } + } + return found; + } + + public void removeAll(Collection points) { + for (ReadonlyVecD3D p : points) { + remove(p); + } + } + + /** + * @param minNodeSize + */ + public void setMinNodeSize(double minNodeSize) { + this.minNodeSize = minNodeSize * 0.5f; + } + + /** + * Enables/disables auto reduction of branches after points have been + * deleted from the tree. Turned off by default. + * + * @param state + * true, to enable feature + */ + public void setTreeAutoReduction(boolean state) { + isAutoReducing = state; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.AABBD#toString() + */ + public String toString() { + return " offset: " + super.toString() + " size: " + size; + } +} diff --git a/src.core/toxi/geom/PointQuadtreeD.java b/src.core/toxi/geom/PointQuadtreeD.java new file mode 100644 index 00000000..513a2fa9 --- /dev/null +++ b/src.core/toxi/geom/PointQuadtreeD.java @@ -0,0 +1,284 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implements a spatial subdivision tree to work efficiently with large numbers + * of 2D particles. This quadtree can only be used for particle type objects and + * does NOT support 2D mesh geometry as other forms of quadtree might do. + * + * For further reference also see the QuadtreeDemo in the /examples folder. + * + */ +public class PointQuadtreeD extends RectD implements SpatialIndexD { + + public enum Type { + EMPTY, + BRANCH, + LEAF; + } + + private PointQuadtreeD parent; + private PointQuadtreeD childNW, childNE, childSW, childSE; + + private Type type; + + private VecD2D value; + private double mx, my; + + public PointQuadtreeD(double x, double y, double w, double h) { + this(null, x, y, w, h); + } + + public PointQuadtreeD(PointQuadtreeD parent, double x, double y, double w, + double h) { + super(x, y, w, h); + this.parent = parent; + this.type = Type.EMPTY; + mx = x + w * 0.5f; + my = y + h * 0.5f; + } + + public PointQuadtreeD(RectD r) { + this(null, r.x, r.y, r.width, r.height); + } + + private void balance() { + switch (type) { + case EMPTY: + case LEAF: + if (parent != null) { + parent.balance(); + } + break; + + case BRANCH: + PointQuadtreeD leaf = null; + if (childNW.type != Type.EMPTY) { + leaf = childNW; + } + if (childNE.type != Type.EMPTY) { + if (leaf != null) { + break; + } + leaf = childNE; + } + if (childSW.type != Type.EMPTY) { + if (leaf != null) { + break; + } + leaf = childSW; + } + if (childSE.type != Type.EMPTY) { + if (leaf != null) { + break; + } + leaf = childSE; + } + if (leaf == null) { + type = Type.EMPTY; + childNW = childNE = childSW = childSE = null; + } else if (leaf.type == Type.BRANCH) { + break; + } else { + type = Type.LEAF; + childNW = childNE = childSW = childSE = null; + value = leaf.value; + } + if (parent != null) { + parent.balance(); + } + } + } + + public void clear() { + childNW = childNE = childSW = childSE = null; + type = Type.EMPTY; + value = null; + } + + public PointQuadtreeD findNode(VecD2D p) { + switch (type) { + case EMPTY: + return null; + case LEAF: + return value.x == x && value.y == y ? this : null; + case BRANCH: + return getQuadrantForPoint(p.x, p.y).findNode(p); + default: + throw new IllegalStateException("Invalid node type"); + } + }; + + private PointQuadtreeD getQuadrantForPoint(double x, double y) { + if (x < mx) { + return y < my ? childNW : childSW; + } else { + return y < my ? childNE : childSE; + } + } + + public boolean index(VecD2D p) { + if (containsPoint(p)) { + switch (type) { + case EMPTY: + setPoint(p); + return true; + + case LEAF: + if (value.x == p.x && value.y == p.y) { + return false; + } else { + split(); + return getQuadrantForPoint(p.x, p.y).index(p); + } + + case BRANCH: + return getQuadrantForPoint(p.x, p.y).index(p); + } + } + return false; + } + + public boolean isIndexed(VecD2D p) { + return findNode(p) != null; + } + + public List itemsWithinRadius(VecD2D p, double radius, + List results) { + if (intersectsCircleD(p, radius)) { + if (type == Type.LEAF) { + if (value.distanceToSquared(p) < radius * radius) { + if (results == null) { + results = new ArrayList(); + } + results.add(value); + } + } else if (type == Type.BRANCH) { + PointQuadtreeD[] children = new PointQuadtreeD[] { + childNW, childNE, childSW, childSE + }; + for (int i = 0; i < 4; i++) { + if (children[i] != null) { + results = children[i].itemsWithinRadius(p, radius, + results); + } + } + } + } + return results; + } + + public List itemsWithinRectD(RectD bounds, List results) { + if (bounds.intersectsRectD(this)) { + if (type == Type.LEAF) { + if (bounds.containsPoint(value)) { + if (results == null) { + results = new ArrayList(); + } + results.add(value); + } + } else if (type == Type.BRANCH) { + PointQuadtreeD[] children = new PointQuadtreeD[] { + childNW, childNE, childSW, childSE + }; + for (int i = 0; i < 4; i++) { + if (children[i] != null) { + results = children[i].itemsWithinRectD(bounds, results); + } + } + } + } + return results; + } + + public void prewalk(QuadtreeVisitorD visitor) { + switch (type) { + case LEAF: + visitor.visitNode(this); + break; + + case BRANCH: + visitor.visitNode(this); + childNW.prewalk(visitor); + childNE.prewalk(visitor); + childSW.prewalk(visitor); + childSE.prewalk(visitor); + break; + } + } + + public boolean reindex(VecD2D p, VecD2D q) { + unindex(p); + return index(q); + } + + private void setPoint(VecD2D p) { + if (type == Type.BRANCH) { + throw new IllegalStateException("invalid node type: BRANCH"); + } + type = Type.LEAF; + value = p; + } + + public int size() { + return 0; + } + + private void split() { + VecD2D oldPoint = value; + value = null; + + type = Type.BRANCH; + + double w2 = width * 0.5f; + double h2 = height * 0.5f; + + childNW = new PointQuadtreeD(this, x, y, w2, h2); + childNE = new PointQuadtreeD(this, x + w2, y, w2, h2); + childSW = new PointQuadtreeD(this, x, y + h2, w2, h2); + childSE = new PointQuadtreeD(this, x + w2, y + h2, w2, h2); + + index(oldPoint); + } + + public boolean unindex(VecD2D p) { + PointQuadtreeD node = findNode(p); + if (node != null) { + node.value = null; + node.type = Type.EMPTY; + node.balance(); + return true; + } else { + return false; + } + } +} diff --git a/src.core/toxi/geom/PolygonClipperD2D.java b/src.core/toxi/geom/PolygonClipperD2D.java new file mode 100644 index 00000000..6f4bfdcb --- /dev/null +++ b/src.core/toxi/geom/PolygonClipperD2D.java @@ -0,0 +1,45 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * Defines an interface for clipping 2D polygons. Currently the only + * implementation for this available is {@link SutherlandHodgemanClipper}. + */ +public interface PolygonClipperD2D { + + /** + * Creates a clipped version of the polygon to the boundary shape set. + * + * @param poly + * polygon to be clipped + * @return clipped poly + */ + public PolygonD2D clipPolygonD(PolygonD2D poly); + +} \ No newline at end of file diff --git a/src.core/toxi/geom/PolygonD2D.java b/src.core/toxi/geom/PolygonD2D.java new file mode 100644 index 00000000..62d41703 --- /dev/null +++ b/src.core/toxi/geom/PolygonD2D.java @@ -0,0 +1,925 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; + +import toxi.geom.LineD2D.LineIntersection; +import toxi.geom.LineD2D.LineIntersection.Type; +import toxi.geom.mesh.MeshD3D; +import toxi.geom.mesh.TriangleMeshD; +import toxi.math.MathUtils; + +/** + * Container type for convex polygons. Implements {@link Shape2D}. + */ +public class PolygonD2D implements ShapeD2D, Iterable { + + /** + * Constructs a new regular polygon from the given base line/edge. + * + * @param baseA + * left point of the base edge + * @param baseB + * right point of the base edge + * @param res + * number of polygon vertices + * @return polygon + */ + public static PolygonD2D fromBaseEdge(VecD2D baseA, VecD2D baseB, int res) { + double theta = -(MathUtils.PI - (MathUtils.PI * (res - 2) / res)); + VecD2D dir = baseB.sub(baseA); + VecD2D prev = baseB; + PolygonD2D poly = new PolygonD2D(baseA, baseB); + for (int i = 1; i < res - 1; i++) { + VecD2D p = prev.add(dir.getRotated(theta * i)); + poly.add(p); + prev = p; + } + return poly; + } + + /** + * Constructs a regular polygon from the given edge length and number of + * vertices. This automatically computes the radius of the circle the + * polygon is inscribed in. + * + *

+ * More information: http://en.wikipedia.org/wiki/Regular_polygon#Radius + *

+ * + * @param len + * desired edge length + * @param res + * number of vertices + * @return polygon + */ + public static PolygonD2D fromEdgeLength(double len, int res) { + return new CircleD(getRadiusForEdgeLength(len, res)).toPolygonD2D(res); + } + + /** + * Computes the radius of the circle the regular polygon with the desired + * edge length is inscribed in + * + * @param len + * edge length + * @param res + * number of polygon vertices + * @return radius + */ + public static double getRadiusForEdgeLength(double len, int res) { + return len / (2 * MathUtils.sin(MathUtils.PI / res)); + } + + @XmlElement(name = "v") + public List vertices = new ArrayList(); + + public PolygonD2D() { + } + + public PolygonD2D(List points) { + for (VecD2D p : points) { + add(p.copy()); + } + } + + public PolygonD2D(VecD2D... points) { + for (VecD2D p : points) { + add(p.copy()); + } + } + + /** + * Adds a new vertex to the polygon (builder pattern). + * + * @param x + * @param y + * @return itself + */ + public PolygonD2D add(double x, double y) { + return add(new VecD2D(x, y)); + } + + /** + * Adds a new vertex to the polygon (builder pattern). + * + * @param p + * vertex point to add + * @return itself + */ + public PolygonD2D add(VecD2D p) { + if (!vertices.contains(p)) { + vertices.add(p); + } + return this; + } + + /** + * Centers the polygon around the world origin (0,0). + * + * @return itself + */ + public PolygonD2D center() { + return center(null); + } + + /** + * Centers the polygon so that its new centroid is at the given point. + * + * @param origin + * new centroid or null to center around (0,0) + * @return itself + */ + public PolygonD2D center(ReadonlyVecD2D origin) { + VecD2D centroid = getCentroid(); + VecD2D delta = origin != null ? origin.sub(centroid) : centroid.invert(); + for (VecD2D v : vertices) { + v.addSelf(delta); + } + return this; + } + + public boolean containsPoint(ReadonlyVecD2D p) { + int num = vertices.size(); + int i, j = num - 1; + boolean oddNodes = false; + double px = p.x(); + double py = p.y(); + for (i = 0; i < num; i++) { + VecD2D vi = vertices.get(i); + VecD2D vj = vertices.get(j); + if (vi.y < py && vj.y >= py || vj.y < py && vi.y >= py) { + if (vi.x + (py - vi.y) / (vj.y - vi.y) * (vj.x - vi.x) < px) { + oddNodes = !oddNodes; + } + } + j = i; + } + return oddNodes; + } + + public boolean containsPolygonD(PolygonD2D poly) { + for (VecD2D p : poly.vertices) { + if (!containsPoint(p)) { + return false; + } + } + return true; + } + + public PolygonD2D copy() { + return new PolygonD2D(vertices); + } + + /** + * Flips the ordering of the polygon's vertices. + * + * @return itself + */ + public PolygonD2D flipVertexOrder() { + Collections.reverse(vertices); + return this; + } + + /** + * Returns the vertex at the given index. This function follows Python + * convention, in that if the index is negative, it is considered relative + * to the list end. Therefore the vertex at index -1 is the last vertex in + * the list. + * + * @param i + * index + * @return vertex + */ + public VecD2D get(int i) { + if (i < 0) { + i += vertices.size(); + } + return vertices.get(i); + } + + /** + * Computes the length of this polygon's apothem. This will only be valid if + * the polygon is regular. More info: http://en.wikipedia.org/wiki/Apothem + * + * @return apothem length + */ + public double getApothem() { + return vertices.get(0).interpolateTo(vertices.get(1), 0.5f) + .distanceTo(getCentroid()); + } + + /** + * Computes the area of the polygon, provided it isn't self intersecting. + * Code ported from: + * http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/ + * + * @return polygon area + */ + public double getArea() { + double area = 0; + for (int i = 0, num = vertices.size(); i < num; i++) { + VecD2D a = vertices.get(i); + VecD2D b = vertices.get((i + 1) % num); + area += a.x * b.y; + area -= a.y * b.x; + } + area *= 0.5f; + return area; + } + + public CircleD getBoundingCircleD() { + return CircleD.newBoundingCircleD(vertices); + } + + /** + * Returns the polygon's bounding rect. + * + * @return bounding rect + * @see toxi.geom.Shape2D#getBounds() + */ + public RectD getBounds() { + return RectD.getBoundingRectD(vertices); + } + + /** + * Computes the polygon's centre of mass. Code ported from: + * http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/ + * + * @return centroid point + */ + public VecD2D getCentroid() { + VecD2D res = new VecD2D(); + for (int i = 0, num = vertices.size(); i < num; i++) { + VecD2D a = vertices.get(i); + VecD2D b = vertices.get((i + 1) % num); + double crossP = a.x * b.y - b.x * a.y; + res.x += (a.x + b.x) * crossP; + res.y += (a.y + b.y) * crossP; + } + return res.scale(1f / (6 * getArea())); + } + + /** + * Computes the polygon's circumference, the length of its perimeter. + * + * @return perimiter length + * + * @see toxi.geom.Shape2D#getCircumference() + */ + public double getCircumference() { + double circ = 0; + for (int i = 0, num = vertices.size(); i < num; i++) { + circ += vertices.get(i).distanceTo(vertices.get((i + 1) % num)); + } + return circ; + } + + public VecD2D getClosestPointTo(ReadonlyVecD2D p) { + double minD = Double.MAX_VALUE; + VecD2D q = null; + for (LineD2D l : getEdges()) { + VecD2D c = l.closestPointTo(p); + double d = c.distanceToSquared(p); + if (d < minD) { + q = c; + minD = d; + } + } + return q; + } + + public VecD2D getClosestVertexTo(ReadonlyVecD2D p) { + double minD = Double.MAX_VALUE; + VecD2D q = null; + for (VecD2D v : vertices) { + double d = v.distanceToSquared(p); + if (d < minD) { + q = v; + minD = d; + } + } + return q; + } + + /** + * Returns a list of {@link LineD2D} segments representing the polygon edges. + * + * @return list of lines + */ + public List getEdges() { + int num = vertices.size(); + List edges = new ArrayList(num); + for (int i = 0; i < num; i++) { + edges.add(new LineD2D(vertices.get(i), vertices.get((i + 1) % num))); + } + return edges; + } + + /** + * @see #getNumVertices() + */ + @Deprecated + public int getNumPoints() { + return getNumVertices(); + } + + /** + * Returns the number of polygon vertices. + * + * @return vertex count + */ + public int getNumVertices() { + return vertices.size(); + } + + /** + * Creates a random point within the polygon. This is only guaranteed to + * work with regular polygons. + * + * @return VecD2D + */ + public VecD2D getRandomPoint() { + List edges = getEdges(); + int numEdges = edges.size(); + LineD2D ea = edges.get(MathUtils.random(numEdges)); + LineD2D eb = null; + // and another one, making sure it's different + while (eb == null || eb.equals(ea)) { + eb = edges.get(MathUtils.random(numEdges)); + } + // pick a random point on edge A + VecD2D p = ea.a.interpolateTo(ea.b, MathUtils.random(1f)); + // then randomly interpolate to another random point on edge B + return p.interpolateToSelf( + eb.a.interpolateTo(eb.b, MathUtils.random(1f)), + MathUtils.random(1f)); + } + + /** + * Repeatedly inserts vertices as mid points of the longest edges until the + * new vertex count is reached. + * + * @param count + * new vertex count + * @return itself + */ + public PolygonD2D increaseVertexCount(int count) { + int num = vertices.size(); + while (num < count) { + // find longest edge + int longestID = 0; + double maxD = 0; + for (int i = 0; i < num; i++) { + double d = vertices.get(i).distanceToSquared( + vertices.get((i + 1) % num)); + if (d > maxD) { + longestID = i; + maxD = d; + } + } + // insert mid point of longest segment in vertex list + VecD2D m = vertices.get(longestID) + .add(vertices.get((longestID + 1) % num)).scaleSelf(0.5f); + vertices.add(longestID + 1, m); + num++; + } + return this; + } + + protected boolean intersectsLine(LineD2D l, List edges) { + for (LineD2D e : edges) { + final Type isec = l.intersectLine(e).getType(); + if (isec == Type.INTERSECTING || isec == Type.COINCIDENT) { + return true; + } + } + return false; + } + + /** + * Checks if the given polygon intersect this one by checking all edges for + * line intersections. + * + * @param poly + * @return true, if polygons intersect. + */ + public boolean intersectsPolygonD(PolygonD2D poly) { + List edgesB = poly.getEdges(); + for (LineD2D ea : getEdges()) { + if (intersectsLine(ea, edgesB)) { + return true; + } + } + return false; + } + + public boolean intersectsRectD(RectD r) { + List edges = r.getEdges(); + for (LineD2D ea : getEdges()) { + if (intersectsLine(ea, edges)) { + return true; + } + } + return false; + } + + /** + * Checks if the vertices of this polygon are in clockwise ordering by + * examining all vertices as a sequence of triangles. The test relies on the + * fact that the {@link #getArea()} method will produce negative results for + * ant-clockwise ordered polygons. + * + * @return true, if clockwise + */ + public boolean isClockwise() { + return getArea() > 0; + } + + /** + * Checks if the polygon is convex. + * + * @return true, if convex. + */ + public boolean isConvex() { + boolean isPositive = false; + int num = vertices.size(); + for (int i = 0; i < num; i++) { + int prev = (i == 0) ? num - 1 : i - 1; + int next = (i == num - 1) ? 0 : i + 1; + VecD2D d0 = vertices.get(i).sub(vertices.get(prev)); + VecD2D d1 = vertices.get(next).sub(vertices.get(i)); + boolean newIsP = (d0.cross(d1) > 0); + if (i == 0) { + isPositive = newIsP; + } else if (isPositive != newIsP) { + return false; + } + } + return true; + } + + public Iterator iterator() { + return vertices.iterator(); + } + + /** + * Given the sequentially connected points p1, p2, p3, this function returns + * a bevel-offset replacement for point p2. + * + * Note: If vectors p1->p2 and p2->p3 are exactly 180 degrees opposed, or if + * either segment is zero then no offset will be applied. + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + * @param x3 + * @param y3 + * @param distance + * @param out + * + * @see http://alienryderflex.com/polygon_inset/ + */ + protected void offsetCorner(double x1, double y1, double x2, double y2, + double x3, double y3, double distance, VecD2D out) { + + double c1 = x2, d1 = y2, c2 = x2, d2 = y2; + double dx1, dy1, dist1, dx2, dy2, dist2, insetX, insetY; + + dx1 = x2 - x1; + dy1 = y2 - y1; + dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); + dx2 = x3 - x2; + dy2 = y3 - y2; + dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); + + if (dist1 < MathUtils.EPS || dist2 < MathUtils.EPS) { + return; + } + dist1 = distance / dist1; + dist2 = distance / dist2; + + insetX = dy1 * dist1; + insetY = -dx1 * dist1; + x1 += insetX; + c1 += insetX; + y1 += insetY; + d1 += insetY; + insetX = dy2 * dist2; + insetY = -dx2 * dist2; + x3 += insetX; + c2 += insetX; + y3 += insetY; + d2 += insetY; + + if (c1 == c2 && d1 == d2) { + out.set(c1, d1); + return; + } + + LineD2D l1 = new LineD2D(new VecD2D(x1, y1), new VecD2D(c1, d1)); + LineD2D l2 = new LineD2D(new VecD2D(c2, d2), new VecD2D(x3, y3)); + LineIntersection isec = l1.intersectLine(l2); + final VecD2D ipos = isec.getPos(); + if (ipos != null) { + out.set(ipos); + } + } + + /** + * Moves each line segment of the polygon in/outward perpendicular by the + * given distance. New line segments and polygon vertices are created by + * computing the intersection points of the displaced segments. Choosing an + * too large displacement amount will result in deformation/undefined + * behavior with various self intersections. Should that happen, please try + * to clean up the shape using the {@link #toOutline()} method. + * + * @param distance + * offset/inset distance (negative for inset) + * @return itself + */ + public PolygonD2D offsetShape(double distance) { + int num = vertices.size() - 1; + if (num > 1) { + double startX = vertices.get(0).x; + double startY = vertices.get(0).y; + double c = vertices.get(num).x; + double d = vertices.get(num).y; + double e = startX; + double f = startY; + for (int i = 0; i < num; i++) { + double a = c; + double b = d; + c = e; + d = f; + e = vertices.get(i + 1).x; + f = vertices.get(i + 1).y; + offsetCorner(a, b, c, d, e, f, distance, vertices.get(i)); + } + offsetCorner(c, d, e, f, startX, startY, distance, + vertices.get(num)); + } + return this; + } + + /** + * Reduces the number of vertices in the polygon based on the given minimum + * edge length. Only vertices with at least this distance between them will + * be kept. + * + * @param minEdgeLen + * @return itself + */ + public PolygonD2D reduceVertices(double minEdgeLen) { + minEdgeLen *= minEdgeLen; + List reduced = new ArrayList(); + VecD2D prev = vertices.get(0); + reduced.add(prev); + int num = vertices.size() - 1; + for (int i = 1; i < num; i++) { + VecD2D v = vertices.get(i); + if (prev.distanceToSquared(v) >= minEdgeLen) { + reduced.add(v); + prev = v; + } + } + if (vertices.get(0).distanceToSquared(vertices.get(num)) >= minEdgeLen) { + reduced.add(vertices.get(num)); + } + vertices = reduced; + return this; + } + + /** + * Removes duplicate vertices from the polygon. Only successive points are + * recognized as duplicates. + * + * @param tolerance + * snap distance for finding duplicates + * @return itself + */ + public PolygonD2D removeDuplicates(double tolerance) { + VecD2D prev = null; + for (Iterator iv = vertices.iterator(); iv.hasNext();) { + VecD2D p = iv.next(); + if (p.equalsWithTolerance(prev, tolerance)) { + iv.remove(); + } else { + prev = p; + } + } + int num = vertices.size(); + if (num > 0) { + VecD2D last = vertices.get(num - 1); + if (last.equalsWithTolerance(vertices.get(0), tolerance)) { + vertices.remove(last); + } + } + return this; + } + + public PolygonD2D rotate(double theta) { + for (VecD2D v : vertices) { + v.rotate(theta); + } + return this; + } + + public PolygonD2D scale(double scale) { + return scale(scale, scale); + } + + public PolygonD2D scale(double x, double y) { + for (VecD2D v : vertices) { + v.scaleSelf(x, y); + } + return this; + } + + public PolygonD2D scale(ReadonlyVecD2D scale) { + return scale(scale.x(), scale.y()); + } + + public PolygonD2D scaleSize(double scale) { + return scaleSize(scale, scale); + } + + public PolygonD2D scaleSize(double x, double y) { + VecD2D centroid = getCentroid(); + for (VecD2D v : vertices) { + v.subSelf(centroid).scaleSelf(x, y).addSelf(centroid); + } + return this; + } + + public PolygonD2D scaleSize(ReadonlyVecD2D scale) { + return scaleSize(scale.x(), scale.y()); + } + + /** + * Applies a laplacian-style smooth operation to all polygon vertices, + * causing sharp corners/angles to widen and results in a general smoother + * shape. Let the current vertex be A and its neighbours P and Q, then A + * will be moved by a specified amount into the direction given by + * (P-A)+(Q-A). Additionally, and to avoid shrinking of the shape through + * repeated iteration of this procedure, the vector A - C (PolygonD centroid) + * is added as counter force and a weight for its impact can be specified. + * To keep the average size of the polygon stable, this weight value should + * be ~1/2 of the smooth amount. + * + * @param amount + * smooth amount (between 0 < x < 0.5) + * @param baseWeight + * counter weight (0 <= x < 1/2 * smooth amount) + * @return itself + */ + public PolygonD2D smooth(double amount, double baseWeight) { + VecD2D centroid = getCentroid(); + int num = vertices.size(); + List filtered = new ArrayList(num); + for (int i = 0, j = num - 1, k = 1; i < num; i++) { + VecD2D a = vertices.get(i); + VecD2D dir = vertices.get(j).sub(a).addSelf(vertices.get(k).sub(a)) + .addSelf(a.sub(centroid).scaleSelf(baseWeight)); + filtered.add(a.add(dir.scaleSelf(amount))); + j++; + if (j == num) { + j = 0; + } + k++; + if (k == num) { + k = 0; + } + } + vertices.clear(); + vertices.addAll(filtered); + return this; + } + + public MeshD3D toMesh(MeshD3D mesh) { + return toMesh(mesh, null, 0); + } + + public MeshD3D toMesh(MeshD3D mesh, VecD2D centroid2D, double extrude) { + if (mesh == null) { + mesh = new TriangleMeshD(); + } + final int num = vertices.size(); + if (centroid2D == null) { + centroid2D = getCentroid(); + } + VecD3D centroid = centroid2D.toD3DXY(); + centroid.z = extrude; + RectD bounds = getBounds(); + VecD2D boundScale = new VecD2D(1f / bounds.width, 1f / bounds.height); + VecD2D uvC = centroid2D.sub(bounds.getTopLeft()).scaleSelf(boundScale); + for (int i = 1; i <= num; i++) { + VecD2D a = vertices.get(i % num); + VecD2D b = vertices.get(i - 1); + VecD2D uvA = a.sub(bounds.getTopLeft()).scaleSelf(boundScale); + VecD2D uvB = b.sub(bounds.getTopLeft()).scaleSelf(boundScale); + mesh.addFaceD(centroid, a.toD3DXY(), b.toD3DXY(), uvC, uvA, uvB); + } + return mesh; + } + + /** + * Attempts to remove all internal self-intersections and creates a new + * polygon only consisting of perimeter vertices. Ported from: + * http://alienryderflex.com/polygon_perimeter/ + * + * @return true, if process completed succcessfully. + */ + public boolean toOutline() { + int corners = vertices.size(); + int maxSegs = corners * 3; + List newVerts = new ArrayList(corners); + VecD2D[] segments = new VecD2D[maxSegs]; + VecD2D[] segEnds = new VecD2D[maxSegs]; + double[] segAngles = new double[maxSegs]; + VecD2D start = vertices.get(0).copy(); + double lastAngle = MathUtils.PI; + double a, b, c, d, e, f; + double angleDif, bestAngleDif; + int i, j = corners - 1, segs = 0; + + if (corners > maxSegs) { + return false; + } + + // 1,3. Reformulate the polygon as a set of line segments, and choose a + // starting point that must be on the perimeter. + for (i = 0; i < corners; i++) { + VecD2D pi = vertices.get(i); + VecD2D pj = vertices.get(j); + if (!pi.equals(pj)) { + segments[segs] = pi; + segEnds[segs++] = pj; + } + j = i; + if (pi.y > start.y || (pi.y == start.y && pi.x < start.x)) { + start.set(pi); + } + } + if (segs == 0) { + return false; + } + + // 2. Break the segments up at their intersection points. + for (i = 0; i < segs - 1; i++) { + for (j = i + 1; j < segs; j++) { + LineD2D li = new LineD2D(segments[i], segEnds[i]); + LineD2D lj = new LineD2D(segments[j], segEnds[j]); + LineIntersection isec = li.intersectLine(lj); + if (isec.getType() == Type.INTERSECTING) { + VecD2D ipos = isec.getPos(); + if (!ipos.equals(segments[i]) && !ipos.equals(segEnds[i])) { + if (segs == maxSegs) { + return false; + } + segments[segs] = segments[i].copy(); + segEnds[segs++] = ipos.copy(); + segments[i] = ipos.copy(); + } + if (!ipos.equals(segments[j]) && !ipos.equals(segEnds[j])) { + if (segs == maxSegs) { + return false; + } + segments[segs] = segments[j].copy(); + segEnds[segs++] = ipos.copy(); + segments[j] = ipos.copy(); + } + } + } + } + + // Calculate the angle of each segment. + for (i = 0; i < segs; i++) { + segAngles[i] = segEnds[i].sub(segments[i]).positiveHeading(); + } + + // 4. Build the perimeter polygon. + c = start.x; + d = start.y; + a = c - 1; + b = d; + e = 0; + f = 0; + newVerts.add(new VecD2D(c, d)); + corners = 1; + while (true) { + bestAngleDif = MathUtils.TWO_PI; + for (i = 0; i < segs; i++) { + if (segments[i].x == c && segments[i].y == d + && (segEnds[i].x != a || segEnds[i].y != b)) { + angleDif = lastAngle - segAngles[i]; + while (angleDif >= MathUtils.TWO_PI) { + angleDif -= MathUtils.TWO_PI; + } + while (angleDif < 0) { + angleDif += MathUtils.TWO_PI; + } + if (angleDif < bestAngleDif) { + bestAngleDif = angleDif; + e = segEnds[i].x; + f = segEnds[i].y; + } + } + if (segEnds[i].x == c && segEnds[i].y == d + && (segments[i].x != a || segments[i].y != b)) { + angleDif = lastAngle - segAngles[i] + MathUtils.PI; + while (angleDif >= MathUtils.TWO_PI) { + angleDif -= MathUtils.TWO_PI; + } + while (angleDif < 0) { + angleDif += MathUtils.TWO_PI; + } + if (angleDif < bestAngleDif) { + bestAngleDif = angleDif; + e = segments[i].x; + f = segments[i].y; + } + } + } + if (corners > 1 && c == newVerts.get(0).x && d == newVerts.get(0).y + && e == newVerts.get(1).x && f == newVerts.get(1).y) { + corners--; + vertices = newVerts; + return true; + } + if (bestAngleDif == MathUtils.TWO_PI || corners == maxSegs) { + return false; + } + lastAngle -= bestAngleDif + MathUtils.PI; + newVerts.add(new VecD2D(e, f)); + corners++; + a = c; + b = d; + c = e; + d = f; + } + } + + /** + * Only needed for {@link Shape2D} interface. Returns itself. + * + * @return itself + */ + public PolygonD2D toPolygonD2D() { + return this; + } + + public String toString() { + StringBuilder buf = new StringBuilder(); + for (Iterator i = vertices.iterator(); i.hasNext();) { + buf.append(i.next().toString()); + if (i.hasNext()) { + buf.append(", "); + } + } + return buf.toString(); + } + + public PolygonD2D translate(double x, double y) { + for (VecD2D v : vertices) { + v.addSelf(x, y); + } + return this; + } + + public PolygonD2D translate(ReadonlyVecD2D offset) { + return translate(offset.x(), offset.y()); + } +} diff --git a/src.core/toxi/geom/PolygonTesselatorD.java b/src.core/toxi/geom/PolygonTesselatorD.java new file mode 100644 index 00000000..00f372cd --- /dev/null +++ b/src.core/toxi/geom/PolygonTesselatorD.java @@ -0,0 +1,16 @@ +package toxi.geom; + +import java.util.List; + +public interface PolygonTesselatorD { + + /** + * Tesselates the given polygon into a set of triangles. + * + * @param poly + * polygon + * @return list of triangles + */ + public List tesselatePolygonD(PolygonD2D poly); + +} \ No newline at end of file diff --git a/src.core/toxi/geom/QuadtreeVisitorD.java b/src.core/toxi/geom/QuadtreeVisitorD.java new file mode 100644 index 00000000..e8779639 --- /dev/null +++ b/src.core/toxi/geom/QuadtreeVisitorD.java @@ -0,0 +1,44 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * This interface is the core part of the visitor pattern application for + * {@link PointQuadtree}s. It can be used to apply a procedure to all tree nodes + * when passed to {@link PointQuadtree#applyVisitor(QuadtreeVisitorD)}. + */ +public interface QuadtreeVisitorD { + + /** + * Applies the procedure defined by an implementation of this interface to + * the given tree node. + * + * @param node + */ + void visitNode(PointQuadtreeD node); +} diff --git a/src.core/toxi/geom/Quaternion.java b/src.core/toxi/geom/Quaternion.java index 25c48147..9bb7d6ee 100644 --- a/src.core/toxi/geom/Quaternion.java +++ b/src.core/toxi/geom/Quaternion.java @@ -205,6 +205,12 @@ public Quaternion(Quaternion q) { this.y = q.y; this.z = q.z; } + public Quaternion(QuaternionD q) { + this.w = (float)q.w; + this.x = (float)q.x; + this.y = (float)q.y; + this.z = (float)q.z; + } public Quaternion add(Quaternion q) { return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w); diff --git a/src.core/toxi/geom/QuaternionD.java b/src.core/toxi/geom/QuaternionD.java new file mode 100644 index 00000000..a160b0cf --- /dev/null +++ b/src.core/toxi/geom/QuaternionD.java @@ -0,0 +1,517 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; + +/** + * Quaternion implementation with SLERP based on http://is.gd/2n9s + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class QuaternionD { + + public static final double DOT_THRESHOLD = 0.9995f; + + /** + * Creates a Quaternion from a axis and a angle. + * + * @param axis + * axis vector (will be normalized) + * @param angle + * angle in radians. + * + * @return new quaternion + */ + public static QuaternionD createFromAxisAngle(ReadonlyVecD3D axis, double angle) { + angle *= 0.5; + double sin = MathUtils.sin(angle); + double cos = MathUtils.cos(angle); + QuaternionD q = new QuaternionD(cos, axis.getNormalizedTo(sin)); + return q; + } + + /** + * Creates a Quaternion from Euler angles. + * + * @param pitch + * X-angle in radians. + * @param yaw + * Y-angle in radians. + * @param roll + * Z-angle in radians. + * + * @return new quaternion + */ + public static QuaternionD createFromEuler(double pitch, double yaw, double roll) { + pitch *= 0.5; + yaw *= 0.5; + roll *= 0.5; + double sinPitch = MathUtils.sin(pitch); + double cosPitch = MathUtils.cos(pitch); + double sinYaw = MathUtils.sin(yaw); + double cosYaw = MathUtils.cos(yaw); + double sinRoll = MathUtils.sin(roll); + double cosRoll = MathUtils.cos(roll); + double cosPitchCosYaw = cosPitch * cosYaw; + double sinPitchSinYaw = sinPitch * sinYaw; + + QuaternionD q = new QuaternionD(); + + q.x = sinRoll * cosPitchCosYaw - cosRoll * sinPitchSinYaw; + q.y = cosRoll * sinPitch * cosYaw + sinRoll * cosPitch * sinYaw; + q.z = cosRoll * cosPitch * sinYaw - sinRoll * sinPitch * cosYaw; + q.w = cosRoll * cosPitchCosYaw + sinRoll * sinPitchSinYaw; + + // alternative solution from: + // http://is.gd/6HdEB + // + // double c1 = Math.cos(yaw/2); + // double s1 = Math.sin(yaw/2); + // double c2 = Math.cos(pitch/2); + // double s2 = Math.sin(pitch/2); + // double c3 = Math.cos(roll/2); + // double s3 = Math.sin(roll/2); + // double c1c2 = c1*c2; + // double s1s2 = s1*s2; + // w =c1c2*c3 - s1s2*s3; + // x =c1c2*s3 + s1s2*c3; + // y =s1*c2*c3 + c1*s2*s3; + // z =c1*s2*c3 - s1*c2*s3; + + return q; + } + + /** + * Creates a quaternion from a rotation matrix. The algorithm used is from + * Allan and Mark Watt's "Advanced Animation and Rendering Techniques" (ACM + * Press 1992). + * + * @param m + * rotation matrix + * @return quaternion + */ + public static QuaternionD createFromMatrix(Matrix4x4 m) { + + double s = 0.0f; + double[] q = new double[4]; + double trace = m.matrix[0][0] + m.matrix[1][1] + m.matrix[2][2]; + if (trace > 0.0f) { + s = 0.5 / Math.sqrt(trace + 1.0); + q[0] = (m.matrix[2][1] - m.matrix[1][2]) * s; + q[1] = (m.matrix[0][2] - m.matrix[2][0]) * s; + q[2] = (m.matrix[1][0] - m.matrix[0][1]) * s; + q[3] = 0.25 / s; + } else { + int[] nxt = new int[] { + 1, 2, 0 + }; + int i = 0, j = 0, k = 0; + + if (m.matrix[1][1] > m.matrix[0][0]) { + i = 1; + } + + if (m.matrix[2][2] > m.matrix[i][i]) { + i = 2; + } + + j = nxt[i]; + k = nxt[j]; + s = 2.0f * Math + .sqrt((m.matrix[i][i] - m.matrix[j][j] - m.matrix[k][k]) + 1.0f); + + double ss = 1.0 / s; + q[i] = s * 0.25f; + q[j] = (m.matrix[j][i] + m.matrix[i][j]) * ss; + q[k] = (m.matrix[k][i] + m.matrix[i][k]) * ss; + q[3] = (m.matrix[k][j] - m.matrix[j][k]) * ss; + } + + return new QuaternionD( q[3], q[0], q[1], q[2]); + } + + /** + * Constructs a quaternion that rotates the vector given by the "forward" + * param into the direction given by the "dir" param. + * + * @param dir + * @param forward + * @return quaternion + */ + public static QuaternionD getAlignmentQuat(ReadonlyVecD3D dir, + ReadonlyVecD3D forward) { + VecD3D target = dir.getNormalized(); + ReadonlyVecD3D axis = forward.cross(target); + double length = axis.magnitude() + 0.0001f; + double angle = Math.atan2(length, forward.dot(target)); + return createFromAxisAngle(axis, angle); + } + + @XmlAttribute(required = true) + public double x, y, z, w; + + public QuaternionD() { + identity(); + } + + public QuaternionD(double w, double x, double y, double z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + } + + public QuaternionD(double w, ReadonlyVecD3D v) { + this.x = v.x(); + this.y = v.y(); + this.z = v.z(); + this.w = w; + } + + public QuaternionD(QuaternionD q) { + this.w = q.w; + this.x = q.x; + this.y = q.y; + this.z = q.z; + } + public QuaternionD(Quaternion q) { + this.w = q.w; + this.x = q.x; + this.y = q.y; + this.z = q.z; + } + + + public QuaternionD add(QuaternionD q) { + return new QuaternionD(x + q.x, y + q.y, z + q.z, w + q.w); + } + + public QuaternionD addSelf(QuaternionD q) { + x += q.x; + y += q.y; + z += q.z; + w += q.w; + return this; + } + + public VecD3D applyTo(VecD3D v) { + double ix = w * v.x + y * v.z - z * v.y; + double iy = w * v.y + z * v.x - x * v.z; + double iz = w * v.z + x * v.y - y * v.x; + double iw = -x * v.x - y * v.y - z * v.z; + double xx = ix * w - iw * x - iy * z + iz * y; + double yy = iy * w - iw * y - iz * x + ix * z; + double zz = iz * w - iw * z - ix * y + iy * x; + v.set(xx, yy, zz); + return v; + } + + public QuaternionD copy() { + return new QuaternionD(w, x, y, z); + } + + /** + * Computes the dot product with the given quaternion. + * + * @param q + * @return dot product + */ + public double dot(QuaternionD q) { + return (x * q.x) + (y * q.y) + (z * q.z) + (w * q.w); + } + + /** + * Computes this quaternion's conjugate, defined as the same w around the + * inverted axis. + * + * @return new conjugate quaternion + */ + public QuaternionD getConjugate() { + QuaternionD q = new QuaternionD(); + q.x = -x; + q.y = -y; + q.z = -z; + q.w = w; + return q; + } + + /** + * @deprecated use {@link #toMatrix4x4()} instead + * @return result matrix + */ + @Deprecated + public Matrix4x4 getMatrix() { + return toMatrix4x4(); + } + + /** + * Computes normalized version of this quaternion. + * + * @return new normalized quaternion + */ + public QuaternionD getNormalized() { + return new QuaternionD(this).normalize(); + } + + public QuaternionD identity() { + w = 1.0f; + x = 0.0f; + y = 0.0f; + z = 0.0f; + return this; + } + + /** + * Spherical interpolation to target quaternion (code ported from GamaSutra) + * + * @param target + * quaternion + * @param t + * interpolation factor (0..1) + * @return new interpolated quat + */ + public QuaternionD interpolateTo(QuaternionD target, double t) { + return copy().interpolateToSelf(target, t); + } + + /** + * @param target + * @param t + * @param is + * @return interpolated quaternion as new instance + */ + public QuaternionD interpolateTo(QuaternionD target, double t, + InterpolateStrategy is) { + return copy().interpolateToSelf(target, is.interpolate(0, 1, t)); + } + + /** + * Spherical interpolation to target quaternion (code ported from GamaSutra) + * + * @param target + * quaternion + * @param t + * interpolation factor (0..1) + * @return new interpolated quat + */ + public QuaternionD interpolateToSelf(QuaternionD target, double t) { + double scale; + double invscale; + double dot = dot(target); + double theta = Math.acos(dot); + double sintheta = Math.sin(theta); + if (sintheta > 0.001f) { + scale = Math.sin(theta * (1.0 - t)) / sintheta; + invscale = Math.sin(theta * t) / sintheta; + } else { + scale = 1 - t; + invscale = t; + } + if (dot < 0) { + w = (scale * w - invscale * target.w); + x = (scale * x - invscale * target.x); + y = (scale * y - invscale * target.y); + z = (scale * z - invscale * target.z); + } else { + w = (scale * w + invscale * target.w); + x = (scale * x + invscale * target.x); + y = (scale * y + invscale * target.y); + z = (scale * z + invscale * target.z); + } + return normalize(); + } + + /** + * Uses spherical interpolation to approach the target quaternion. The + * interpolation factor is manipulated by the chosen + * {@link InterpolateStrategy} first. + * + * @param target + * @param t + * @param is + * @return itself + */ + public QuaternionD interpolateToSelf(QuaternionD target, double t, + InterpolateStrategy is) { + return interpolateToSelf(target, is.interpolate(0, 1, t)); + } + + public double magnitude() { + return Math.sqrt(x * x + y * y + z * z + w * w); + } + + public QuaternionD multiply(QuaternionD q2) { + QuaternionD res = new QuaternionD(); + res.w = w * q2.w - x * q2.x - y * q2.y - z * q2.z; + res.x = w * q2.x + x * q2.w + y * q2.z - z * q2.y; + res.y = w * q2.y + y * q2.w + z * q2.x - x * q2.z; + res.z = w * q2.z + z * q2.w + x * q2.y - y * q2.x; + + return res; + } + + public QuaternionD normalize() { + double mag = Math.sqrt(x * x + y * y + z * z + w * w); + if (mag > MathUtils.EPS) { + mag = 1.0 / mag; + x *= mag; + y *= mag; + z *= mag; + w *= mag; + } + return this; + } + + public QuaternionD scale(double t) { + return new QuaternionD(x * t, y * t, z * t, w * t); + } + + public QuaternionD scaleSelf(double t) { + x *= t; + y *= t; + z *= t; + w *= t; + return this; + } + + public QuaternionD set(double w, double x, double y, double z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public QuaternionD set(double w, VecD3D v) { + this.w = w; + x = v.x; + y = v.y; + z = v.z; + return this; + } + + public QuaternionD set(QuaternionD q) { + w = q.w; + x = q.x; + y = q.y; + z = q.z; + return this; + } + + public QuaternionD sub(QuaternionD q) { + return new QuaternionD(x - q.x, y - q.y, z - q.z, w - q.w); + } + + public QuaternionD subSelf(QuaternionD q) { + x -= q.x; + y -= q.y; + z -= q.z; + w -= q.w; + return this; + } + + public double[] toArray() { + return new double[] { + w, x, y, z + }; + } + + /** + * Converts the quaternion into a double array consisting of: rotation angle + * in radians, rotation axis x,y,z + * + * @return 4-element double array + */ + public double[] toAxisAngle() { + double[] res = new double[4]; + double sa = Math.sqrt(1.0f - w * w); + if (sa < MathUtils.EPS) { + sa = 1.0f; + } else { + sa = 1.0f / sa; + } + res[0] = Math.acos(w) * 2.0f; + res[1] = x * sa; + res[2] = y * sa; + res[3] = z * sa; + return res; + } + + /** + * Converts the quat to a 4x4 rotation matrix (in row-major format). Assumes + * the quat is currently normalized (if not, you'll need to call + * {@link #normalize()} first). + * + * @return result matrix + */ + public Matrix4x4 toMatrix4x4() { + return toMatrix4x4(new Matrix4x4()); + } + + public Matrix4x4 toMatrix4x4(Matrix4x4 result) { + // Converts this quaternion to a rotation matrix. + // + // | 1 - 2(y^2 + z^2) 2(xy + wz) 2(xz - wy) 0 | + // | 2(xy - wz) 1 - 2(x^2 + z^2) 2(yz + wx) 0 | + // | 2(xz + wy) 2(yz - wx) 1 - 2(x^2 + y^2) 0 | + // | 0 0 0 1 | + + double x2 = x + x; + double y2 = y + y; + double z2 = z + z; + double xx = x * x2; + double xy = x * y2; + double xz = x * z2; + double yy = y * y2; + double yz = y * z2; + double zz = z * z2; + double wx = w * x2; + double wy = w * y2; + double wz = w * z2; + + return result.set(1 - (yy + zz), xy - wz, xz + wy, 0, xy + wz, + 1 - (xx + zz), yz - wx, 0, xz - wy, yz + wx, 1 - (xx + yy), 0, + 0, 0, 0, 1); + } + + public String toString() { + StringBuffer sb = new StringBuffer(48); + sb.append("{axis: [").append(x).append(",").append(y).append(",") + .append(z).append("], w: ").append(w).append("}"); + return sb.toString(); + } +} diff --git a/src.core/toxi/geom/RayD2D.java b/src.core/toxi/geom/RayD2D.java new file mode 100644 index 00000000..81d82a71 --- /dev/null +++ b/src.core/toxi/geom/RayD2D.java @@ -0,0 +1,115 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +/** + * A simple 2D ray datatype + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class RayD2D extends VecD2D { + + @XmlElement(required = true) + protected VecD2D dir; + + public RayD2D() { + super(); + dir = VecD2D.Y_AXIS.copy(); + } + + public RayD2D(double x, double y, ReadonlyVecD2D d) { + super(x, y); + dir = d.getNormalized(); + } + + public RayD2D(ReadonlyVecD2D o, ReadonlyVecD2D d) { + this(o.x(), o.y(), d); + } + + /** + * Returns a copy of the ray's direction vector. + * + * @return vector + */ + public VecD2D getDirection() { + return dir.copy(); + } + + /** + * Calculates the distance between the given point and the infinite line + * coinciding with this ray. + * + * @param p + * @return distance + */ + public double getDistanceToPoint(VecD2D p) { + VecD2D sp = p.sub(this); + return sp.distanceTo(dir.scale(sp.dot(dir))); + } + + public VecD2D getPointAtDistance(double dist) { + return add(dir.scale(dist)); + } + + /** + * Uses a normalized copy of the given vector as the ray direction. + * + * @param d + * new direction + * @return itself + */ + public RayD2D setDirection(ReadonlyVecD2D d) { + dir.set(d).normalize(); + return this; + } + + public RayD2D setNormalizedDirection(ReadonlyVecD2D d) { + dir.set(d); + return this; + } + + /** + * Converts the ray into a 2D Line segment with its start point coinciding + * with the ray origin and its other end point at the given distance along + * the ray. + * + * @param dist + * end point distance + * @return line segment + */ + public LineD2D toLineD2DWithPointAtDistance(double dist) { + return new LineD2D(this, getPointAtDistance(dist)); + } + + public String toString() { + return "origin: " + super.toString() + " dir: " + dir; + } +} diff --git a/src.core/toxi/geom/RayD3D.java b/src.core/toxi/geom/RayD3D.java new file mode 100644 index 00000000..314f2793 --- /dev/null +++ b/src.core/toxi/geom/RayD3D.java @@ -0,0 +1,122 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; + +/** + * A simple 3D ray datatype + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class RayD3D extends VecD3D { + + @XmlElement(required = true) + protected VecD3D dir; + + public RayD3D() { + super(); + dir = VecD3D.Y_AXIS.copy(); + } + + public RayD3D(double x, double y, double z, ReadonlyVecD3D d) { + super(x, y, z); + dir = d.getNormalized(); + } + + public RayD3D(ReadonlyVecD3D o, ReadonlyVecD3D d) { + this(o.x(), o.y(), o.z(), d); + } + + /** + * Returns a copy of the ray's direction vector. + * + * @return vector + */ + public VecD3D getDirection() { + return dir.copy(); + } + + /** + * Calculates the distance between the given point and the infinite line + * coinciding with this ray. + * + * @param p + * @return distance + */ + public double getDistanceToPoint(VecD3D p) { + VecD3D sp = p.sub(this); + return sp.distanceTo(dir.scale(sp.dot(dir))); + } + + /** + * Returns the point at the given distance on the ray. The distance can be + * any real number. + * + * @param dist + * @return vector + */ + public VecD3D getPointAtDistance(double dist) { + return add(dir.scale(dist)); + } + + /** + * Uses a normalized copy of the given vector as the ray direction. + * + * @param d + * new direction + * @return itself + */ + public RayD3D setDirection(ReadonlyVecD3D d) { + dir.set(d).normalize(); + return this; + } + + public RayD3D setNormalizedDirection(ReadonlyVecD3D d) { + dir.set(d); + return this; + } + + /** + * Converts the ray into a 3D Line segment with its start point coinciding + * with the ray origin and its other end point at the given distance along + * the ray. + * + * @param dist + * end point distance + * @return line segment + */ + public LineD3D toLineD3DWithPointAtDistance(double dist) { + return new LineD3D(this, getPointAtDistance(dist)); + } + + public String toString() { + return "origin: " + super.toString() + " dir: " + dir; + } +} diff --git a/src.core/toxi/geom/RayD3DIntersector.java b/src.core/toxi/geom/RayD3DIntersector.java new file mode 100644 index 00000000..240a9ebc --- /dev/null +++ b/src.core/toxi/geom/RayD3DIntersector.java @@ -0,0 +1,64 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +public class RayD3DIntersector implements IntersectorD3D { + + public RayD3D ray; + private IsectDataD3D isec; + + public RayD3DIntersector(RayD3D ray) { + this.ray = ray; + isec = new IsectDataD3D(); + } + + public IsectDataD3D getIntersectionDataD() { + return isec; + } + + public boolean intersectsRayD(RayD3D other) { + VecD3D n = ray.dir.cross(other.dir); + VecD3D sr = ray.sub(other); + double absX = MathUtils.abs(n.x); + double absY = MathUtils.abs(n.y); + double absZ = MathUtils.abs(n.z); + double t; + if (absZ > absX && absZ > absY) { + t = (sr.x * other.dir.y - sr.y * other.dir.x) / n.z; + } else if (absX > absY) { + t = (sr.y * other.dir.z - sr.z * other.dir.y) / n.x; + } else { + t = (sr.z * other.dir.x - sr.x * other.dir.z) / n.y; + } + isec.isIntersection = (t <= MathUtils.EPS && !Double.isInfinite(t)); + isec.pos = ray.getPointAtDistance(-t); + return isec.isIntersection; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/ReadonlyVecD2D.java b/src.core/toxi/geom/ReadonlyVecD2D.java new file mode 100644 index 00000000..d976af39 --- /dev/null +++ b/src.core/toxi/geom/ReadonlyVecD2D.java @@ -0,0 +1,573 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.geom.VecD2D.AxisD; +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; + +/** + * Readonly, immutable interface wrapper for Vec2D instances. Used throughout + * the library for safety purposes. + */ +public interface ReadonlyVecD2D { + + /** + * Adds vector {a,b,c} and returns result as new vector. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @return result as new vector + */ + public VecD2D add(float a, float b); + public VecD2D add(double a, float b); + public VecD2D add(float a, double b); + public VecD2D add(double a, double b); + + public VecD2D VecD2D(Vec2D v); + public VecD2D VecD2D(ReadonlyVec2D v); + + /** + * Add vector v and returns result as new vector. + * + * @param v + * vector to add + * @return result as new vector + */ + public VecD2D add(ReadonlyVec2D v); + public VecD2D add(ReadonlyVecD2D v); + + + /** + * Computes the angle between this vector and vector V. This function + * assumes both vectors are normalized, if this can't be guaranteed, use the + * alternative implementation {@link #angleBetween(ReadonlyVec2D, boolean)} + * + * @param v + * vector + * @return angle in radians, or NaN if vectors are parallel + */ + public double angleBetween(ReadonlyVec2D v); + public double angleBetween(ReadonlyVecD2D v); + + /** + * Computes the angle between this vector and vector V + * + * @param v + * vector + * @param forceNormalize + * true, if normalized versions of the vectors are to be used + * (Note: only copies will be used, original vectors will not be + * altered by this method) + * @return angle in radians, or NaN if vectors are parallel + */ + public double angleBetween(ReadonlyVec2D v, boolean forceNormalize); + public double angleBetween(ReadonlyVecD2D v, boolean forceNormalize); + + /** + * Computes the perpendicular bisector of two points. + * + * @param p + * other point + * @return bisector coefficients as {@link Vec3D} + */ + public VecD3D bisect(Vec2D p); + public VecD3D bisect(VecD2D p); + + /** + * Compares the length of the vector with another one. + * + * @param v + * vector to compare with + * @return -1 if other vector is longer, 0 if both are equal or else +1 + */ + public int compareTo(ReadonlyVec2D v); + public int compareTo(ReadonlyVecD2D v); + + /** + * @return a new independent instance/copy of a given vector + */ + public VecD2D copy(); + + /** + * Calculates the cross-product with the given vector. + * + * @param v + * vector + * @return the magnitude of the vector that would result from a regular 3D + * cross product of the input vectors, taking their Z values + * implicitly as 0 (i.e. treating the 2D space as a plane in the 3D + * space). The 3D cross product will be perpendicular to that plane, + * and thus have 0 X & Y components (thus the scalar returned is the + * Z value of the 3D cross product vector). + * @see Stackoverflow + * entry + */ + public double cross(ReadonlyVec2D v); + public double cross(ReadonlyVecD2D v); + + /** + * Calculates distance to another vector + * + * @param v + * non-null vector + * @return distance or Double.NaN if v=null + */ + public double distanceTo(ReadonlyVec2D v); + public double distanceTo(ReadonlyVecD2D v); + + /** + * Calculates the squared distance to another vector + * + * @see #magSquared() + * @param v + * non-null vector + * @return distance or NaN if v=null + */ + public double distanceToSquared(ReadonlyVec2D v); + public double distanceToSquared(ReadonlyVecD2D v); + + /** + * Computes the scalar product (dot product) with the given vector. + * + * @see Wikipedia entry< + * /a> + * + * @param v + * @return dot product + */ + public double dot(ReadonlyVec2D v); + public double dot(ReadonlyVecD2D v); + + public boolean equals(Object obj); + + /** + * Compares this vector with the one given. The vectors are deemed equal if + * the individual differences of all component values are within the given + * tolerance. + * + * @param v + * the v + * @param tolerance + * the tolerance + * + * @return true, if equal + */ + public boolean equalsWithTolerance(ReadonlyVec2D v, float tolerance); + public boolean equalsWithTolerance(ReadonlyVecD2D v, float tolerance); + public boolean equalsWithTolerance(ReadonlyVec2D v, double tolerance); + public boolean equalsWithTolerance(ReadonlyVecD2D v, double tolerance); + + public VecD2D getAbs(); + + /** + * Converts the vector from polar to Cartesian space. Assumes this order: + * x=radius, y=theta + * + * @return new vector + */ + public VecD2D getCartesian(); + + public double getComponent(AxisD id); + public double getComponent(int id); + + /** + * Creates a copy of the vector which forcefully fits in the given + * rectangle. + * + * @param r + * @return fitted vector + */ + public VecD2D getConstrained(Rect r); + + /** + * Creates a new vector whose components are the integer value of their + * current values + * + * @return result as new vector + */ + public VecD2D getFloored(); + + /** + * Creates a new vector whose components are the fractional part of their + * current values + * + * @return result as new vector + */ + public VecD2D getFrac(); + + /** + * Scales vector uniformly by factor -1 ( v = -v ) + * + * @return result as new vector + */ + public VecD2D getInverted(); + + /** + * Creates a copy of the vector with its magnitude limited to the length + * given + * + * @param lim + * new maximum magnitude + * @return result as new vector + */ + public VecD2D getLimited(float lim); + public VecD2D getLimited(double lim); + + /** + * Produces a new vector with its coordinates passed through the given + * {@link ScaleMap}. + * + * @param map + * @return mapped vector + */ + public VecD2D getMapped(ScaleMap map); + + /** + * Produces the normalized version as a new vector + * + * @return new vector + */ + public VecD2D getNormalized(); + + /** + * Produces a new vector normalized to the given length. + * + * @param len + * new desired length + * + * @return new vector + */ + public VecD2D getNormalizedTo(float len); + public VecD2D getNormalizedTo(double len); + + public VecD2D getPerpendicular(); + + /** + * Converts the current vector into polar coordinates. After the conversion + * the x component of the vector contains the radius (magnitude) and y the + * rotation angle. + * + * @return new vector + */ + public VecD2D getPolar(); + + public VecD2D getReciprocal(); + + public VecD2D getReflected(ReadonlyVec2D normal); + public VecD2D getReflected(ReadonlyVecD2D normal); + + /** + * Creates a new vector rotated by the given angle around the Z axis. + * + * @param theta + * @return rotated vector + */ + public VecD2D getRotated(float theta); + public VecD2D getRotated(double theta); + + /** + * Creates a new vector with its coordinates rounded to the given precision + * (grid alignment). + * + * @param prec + * @return grid aligned vector + */ + public VecD2D getRoundedTo(float prec); + public VecD2D getRoundedTo(double prec); + + /** + * Creates a new vector in which all components are replaced with the signum + * of their original values. In other words if a components value was + * negative its new value will be -1, if zero => 0, if positive => +1 + * + * @return result vector + */ + public VecD2D getSignum(); + + /** + * Computes the vector's direction in the XY plane (for example for 2D + * points). The positive X axis equals 0 degrees. + * + * @return rotation angle + */ + public double heading(); + + /** + * Interpolates the vector towards the given target vector, using linear + * interpolation + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * @return result as new vector + */ + public VecD2D interpolateTo(ReadonlyVec2D v, float f); + public VecD2D interpolateTo(ReadonlyVecD2D v, float f); + public VecD2D interpolateTo(ReadonlyVec2D v, double f); + public VecD2D interpolateTo(ReadonlyVecD2D v, double f); + + /** + * Interpolates the vector towards the given target vector, using the given + * {@link InterpolateStrategy} + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * @param s + * InterpolateStrategy instance + * @return result as new vector + */ + public VecD2D interpolateTo(ReadonlyVec2D v, float f, InterpolateStrategy s); + public VecD2D interpolateTo(ReadonlyVecD2D v, float f, InterpolateStrategy s); + public VecD2D interpolateTo(ReadonlyVec2D v, double f, InterpolateStrategy s); + public VecD2D interpolateTo(ReadonlyVecD2D v, double f, InterpolateStrategy s); + + + /** + * Checks if the point is inside the given sphere. + * + * @param sO + * circle origin/centre + * @param sR + * circle radius + * @return true, if point is in sphere + */ + + public boolean isInCircleD(ReadonlyVec2D sO, float sR); + public boolean isInCircleD(ReadonlyVecD2D sO, float sR); + public boolean isInCircleD(ReadonlyVec2D sO, double sR); + public boolean isInCircleD(ReadonlyVecD2D sO, double sR); + + /** + * Checks if the point is inside the given rectangle. + * + * @param r + * bounding rectangle + * @return true, if point is inside + */ + public boolean isInRectangle(Rect r); + + /** + * Checks if point vector is inside the triangle created by the points a, b + * and c. These points will create a plane and the point checked will have + * to be on this plane in the region between a,b,c. + * + * Note: The triangle must be defined in clockwise order a,b,c + * + * @return true, if point is in triangle. + */ + public boolean isInTriangleD(Vec2D a, Vec2D b, Vec2D c); + public boolean isInTriangleD(VecD2D a, Vec2D b, Vec2D c); + public boolean isInTriangleD(Vec2D a, VecD2D b, Vec2D c); + public boolean isInTriangleD(Vec2D a, Vec2D b, VecD2D c); + public boolean isInTriangleD(VecD2D a, VecD2D b, Vec2D c); + public boolean isInTriangleD(VecD2D a, Vec2D b, VecD2D c); + public boolean isInTriangleD(Vec2D a, VecD2D b, VecD2D c); + public boolean isInTriangleD(VecD2D a, VecD2D b, VecD2D c); + + /** + * Checks if the vector is parallel with either the X or Y axis (any + * direction). + * + * @param tolerance + * @return true, if parallel within the given tolerance + */ + public boolean isMajorAxis(float tolerance); + public boolean isMajorAxis(double tolerance); + + /** + * Checks if vector has a magnitude equals or close to zero (tolerance used + * is {@link MathUtils#EPS}). + * + * @return true, if zero vector + */ + public boolean isZeroVector(); + + /** + * Calculates the magnitude/eucledian length of the vector + * + * @return vector length + */ + public double magnitude(); + + /** + * Calculates only the squared magnitude/length of the vector. Useful for + * inverse square law applications and/or for speed reasons or if the real + * eucledian distance is not required (e.g. sorting). + * + * Please note the vector should contain cartesian (not polar) coordinates + * in order for this function to work. The magnitude of polar vectors is + * stored in the x component. + * + * @return squared magnitude (x^2 + y^2) + */ + public double magSquared(); + + /** + * Constructs a new vector consisting of the largest components of both + * vectors. + * + * @param v + * @return result as new vector + */ + public VecD2D max(ReadonlyVec2D v); + public VecD2D max(ReadonlyVecD2D v); + + /** + * Constructs a new vector consisting of the smallest components of both + * vectors. + * + * @param v + * comparing vector + * @return result as new vector + */ + public VecD2D min(ReadonlyVec2D v); + public VecD2D min(ReadonlyVecD2D v); + + /** + * Scales vector uniformly and returns result as new vector. + * + * @param s + * scale factor + * @return new vector + */ + public VecD2D scale(float s); + public VecD2D scale(double s); + + /** + * Scales vector non-uniformly and returns result as new vector. + * + * @param a + * scale factor for X coordinate + * @param b + * scale factor for Y coordinate + * @return new vector + */ + public VecD2D scale(float a, float b); + public VecD2D scale(double a, float b); + public VecD2D scale(float a, double b); + public VecD2D scale(double a, double b); + + public VecD2D scale(ReadonlyVec2D s); + public VecD2D scale(ReadonlyVecD2D s); + + /** + * Scales vector non-uniformly by vector v and returns result as new vector + * + * @param s + * scale vector + * @return new vector + */ + public VecD2D scale(Vec2D s); + public VecD2D scale(VecD2D s); + + /** + * Subtracts vector {a,b,c} and returns result as new vector. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @return result as new vector + */ + public VecD2D sub(float a, float b); + public VecD2D sub(double a, float b); + public VecD2D sub(float a, double b); + public VecD2D sub(double a, double b); + + public VecD2D sub(ReadonlyVec2D v); + public VecD2D sub(ReadonlyVecD2D v); + + /** + * Subtracts vector v and returns result as new vector. + * + * @param v + * vector to be subtracted + * @return result as new vector + */ + public VecD2D sub(Vec2D v); + public VecD2D sub(VecD2D v); + + /** + * Calculates the normal vector on the given ellipse in the direction of the + * current point. + * + * @param eO + * ellipse origin/centre + * @param eR + * ellipse radii + * @return a unit normal vector to the tangent plane of the ellipsoid in the + * point. + */ + + public VecD2D tangentNormalOfEllipse(Vec2D eO, Vec2D eR); + public VecD2D tangentNormalOfEllipse(VecD2D eO, Vec2D eR); + public VecD2D tangentNormalOfEllipse(Vec2D eO, VecD2D eR); + public VecD2D tangentNormalOfEllipse(VecD2D eO, VecD2D eR); + + /** + * Creates a 3D version of this vector in the XY plane. + * + * @return D3D vector + */ + public VecD3D toD3DXY(); + + /** + * Creates a 3D version of this vector in the XZ plane. (The 2D Y coordinate + * interpreted as Z) + * + * @return D3D vector + */ + public VecD3D toD3DXZ(); + + /** + * Creates a 3D version of this vector in the YZ plane. (The 2D X coordinate + * interpreted as Y & 2D Y as Z) + * + * @return 3D vector + */ + public VecD3D toD3DYZ(); + + /* + * (non-Javadoc) + * + * @see toxi.geom.DimensionalVector#toArray() + */ + public double[] toArray(); + + public double x(); + + public double y(); + +} \ No newline at end of file diff --git a/src.core/toxi/geom/ReadonlyVecD3D.java b/src.core/toxi/geom/ReadonlyVecD3D.java new file mode 100644 index 00000000..9b03bc50 --- /dev/null +++ b/src.core/toxi/geom/ReadonlyVecD3D.java @@ -0,0 +1,637 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.geom.VecD3D.AxisD; +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; + +/** + * Readonly, immutable interface wrapper for VecD3D instances. Used throughout + * the library for safety purposes. + * @param + */ +public interface ReadonlyVecD3D { + + /** + * Adds vector {a,b,c} and returns result as new vector. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @param c + * Z coordinate + * + * @return result as new vector + */ + public VecD3D add(float a, float b, float c); + public VecD3D add(double a, double b, double c); + + public VecD3D add(ReadonlyVec3D v); + public VecD3D add(ReadonlyVecD3D v); + + /** + * Add vector v and returns result as new vector. + * + * @param v + * vector to add + * + * @return result as new vector + */ + public VecD3D add(Vec3D v); + public VecD3D add(VecD3D v); + + /** + * Computes the angle between this vector and vector V. This function + * assumes both vectors are normalized, if this can't be guaranteed, use the + * alternative implementation {@link #angleBetween(ReadonlyVec3D, boolean)} + * + * @param v + * vector + * + * @return angle in radians, or NaN if vectors are parallel + */ + public double angleBetween(ReadonlyVec3D v); + public double angleBetween(ReadonlyVecD3D v); + + /** + * Computes the angle between this vector and vector V. + * + * @param v + * vector + * @param forceNormalize + * true, if normalized versions of the vectors are to be used + * (Note: only copies will be used, original vectors will not be + * altered by this method) + * + * @return angle in radians, or NaN if vectors are parallel + */ + public double angleBetween(ReadonlyVec3D v, boolean forceNormalize); + public double angleBetween(ReadonlyVecD3D v, boolean forceNormalize); + + /** + * Compares the length of the vector with another one. + * + * @param v + * vector to compare with + * + * @return -1 if other vector is longer, 0 if both are equal or else +1 + */ + public int compareTo(ReadonlyVec3D v); + public int compareTo(ReadonlyVecD3D v); + + /** + * Copy. + * + * @return a new independent instance/copy of a given vector + */ + public VecD3D copy(); + + /** + * Calculates cross-product with vector v. The resulting vector is + * perpendicular to both the current and supplied vector. + * + * @param v + * vector to cross + * + * @return cross-product as new vector + */ + public VecD3D cross(ReadonlyVec3D v); + public VecD3D cross(ReadonlyVecD3D v); + + /** + * Calculates cross-product with vector v. The resulting vector is + * perpendicular to both the current and supplied vector and stored in the + * supplied result vector. + * + * @param v + * vector to cross + * @param result + * result vector + * + * @return result vector + */ + public VecD3D crossInto(ReadonlyVec3D v, VecD3D result); + public VecD3D crossInto(ReadonlyVecD3D v, VecD3D result); + + /** + * Calculates distance to another vector. + * + * @param v + * non-null vector + * + * @return distance or Float.NaN if v=null + */ + public double distanceTo(ReadonlyVec3D v); + public double distanceTo(ReadonlyVecD3D v); + + + /** + * Calculates the squared distance to another vector. + * + * @param v + * non-null vector + * + * @return distance or NaN if v=null + * + * @see #magSquared() + */ + public double distanceToSquared(ReadonlyVec3D v); + public double distanceToSquared(ReadonlyVecD3D v); + + /** + * Computes the scalar product (dot product) with the given vector. + * + * @param v + * the v + * + * @return dot product + * + * @see Wikipedia + * entry + */ + public double dot(ReadonlyVec3D v); + public double dot(ReadonlyVecD3D v); + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj); + + /** + * Compares this vector with the one given. The vectors are deemed equal if + * the individual differences of all component values are within the given + * tolerance. + * + * @param v + * the v + * @param tolerance + * the tolerance + * + * @return true, if equal + */ + public boolean equalsWithTolerance(ReadonlyVec3D v, float tolerance); + public boolean equalsWithTolerance(ReadonlyVecD3D v, float tolerance); + public boolean equalsWithTolerance(ReadonlyVec3D v, double tolerance); + public boolean equalsWithTolerance(ReadonlyVecD3D v, double tolerance); + + /** + * Gets the abs. + * + * @return the abs + */ + public VecD3D getAbs(); + + /** + * Converts the spherical vector back into cartesian coordinates. + * + * @return new vector + */ + public VecD3D getCartesian(); + + public AxisD getClosestAxis(); + + public double getComponent(AxisD id); + + public double getComponent(int id); + + /** + * Creates a copy of the vector which forcefully fits in the given AABB. + * + * @param box + * the box + * + * @return fitted vector + */ + public VecD3D getConstrained(AABB box); + public VecD3D getConstrained(AABBD box); + + /** + * Creates a new vector whose components are the integer value of their + * current values. + * + * @return result as new vector + */ + public VecD3D getFloored(); + + /** + * Creates a new vector whose components are the fractional part of their + * current values. + * + * @return result as new vector + */ + public VecD3D getFrac(); + + /** + * Scales vector uniformly by factor -1 ( v = -v ). + * + * @return result as new vector + */ + public VecD3D getInverted(); + + /** + * Creates a copy of the vector with its magnitude limited to the length + * given. + * + * @param lim + * new maximum magnitude + * + * @return result as new vector + */ + public VecD3D getLimited(float lim); + public VecD3D getLimited(double lim); + + /** + * Produces a new vector with its coordinates passed through the given + * {@link ScaleMap}. + * + * @param map + * @return mapped vector + */ + public VecD3D getMapped(ScaleMap map); + + /** + * Produces the normalized version as a new vector. + * + * @return new vector + */ + public VecD3D getNormalized(); + + /** + * Produces a new vector normalized to the given length. + * + * @param len + * new desired length + * + * @return new vector + */ + public VecD3D getNormalizedTo(float len); + public VecD3D getNormalizedTo(double len); + + /** + * Returns a multiplicative inverse copy of the vector. + * + * @return new vector + */ + public VecD3D getReciprocal(); + + public VecD3D getReflected(ReadonlyVec3D normal); + public VecD3D getReflected(ReadonlyVecD3D normal); + + /** + * Gets the rotated around axis. + * + * @param axis + * the axis + * @param theta + * the theta + * + * @return new result vector + */ + public VecD3D getRotatedAroundAxis(ReadonlyVec3D axis, float theta); + public VecD3D getRotatedAroundAxis(ReadonlyVecD3D axis, float theta); + public VecD3D getRotatedAroundAxis(ReadonlyVec3D axis, double theta); + public VecD3D getRotatedAroundAxis(ReadonlyVecD3D axis, double theta); + + /** + * Creates a new vector rotated by the given angle around the X axis. + * + * @param theta + * the theta + * + * @return rotated vector + */ + public VecD3D getRotatedX(float theta); + public VecD3D getRotatedX(double theta); + + /** + * Creates a new vector rotated by the given angle around the Y axis. + * + * @param theta + * the theta + * + * @return rotated vector + */ + public VecD3D getRotatedY(float theta); + public VecD3D getRotatedY(double theta); + + /** + * Creates a new vector rotated by the given angle around the Z axis. + * + * @param theta + * the theta + * + * @return rotated vector + */ + public VecD3D getRotatedZ(float theta); + public VecD3D getRotatedZ(double theta); + + /** + * Creates a new vector with its coordinates rounded to the given precision + * (grid alignment). + * + * @param prec + * @return grid aligned vector + */ + public VecD3D getRoundedTo(float prec); + public VecD3D getRoundedTo(double prec); + + /** + * Creates a new vector in which all components are replaced with the signum + * of their original values. In other words if a components value was + * negative its new value will be -1, if zero => 0, if positive => +1 + * + * @return result vector + */ + public VecD3D getSignum(); + + /** + * Converts the vector into spherical coordinates. After the conversion the + * vector components are to be interpreted as: + *
    + *
  • x = radius
  • + *
  • y = azimuth
  • + *
  • z = theta
  • + *
+ * + * @return new vector + */ + public VecD3D getSpherical(); + + /** + * Computes the vector's direction in the XY plane (for example for 2D + * points). The positive X axis equals 0 degrees. + * + * @return rotation angle + */ + public double headingXY(); + + /** + * Computes the vector's direction in the XZ plane. The positive X axis + * equals 0 degrees. + * + * @return rotation angle + */ + public double headingXZ(); + + /** + * Computes the vector's direction in the YZ plane. The positive Z axis + * equals 0 degrees. + * + * @return rotation angle + */ + public double headingYZ(); + + /** + * Interpolates the vector towards the given target vector, using linear + * interpolation. + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * + * @return result as new vector + */ + public VecD3D interpolateTo(ReadonlyVec3D v, float f); + public VecD3D interpolateTo(ReadonlyVecD3D v, float f); + public VecD3D interpolateTo(ReadonlyVec3D v, double f); + public VecD3D interpolateTo(ReadonlyVecD3D v, double f); + + /** + * Interpolates the vector towards the given target vector, using the given + * {@link InterpolateStrategy}. + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * @param s + * InterpolateStrategy instance + * + * @return result as new vector + */ + public VecD3D interpolateTo(ReadonlyVec3D v, float f, InterpolateStrategy s); + public VecD3D interpolateTo(ReadonlyVecD3D v, float f, InterpolateStrategy s); + public VecD3D interpolateTo(ReadonlyVec3D v, double f, InterpolateStrategy s); + public VecD3D interpolateTo(ReadonlyVecD3D v, double f, InterpolateStrategy s); + + /** + * Checks if the point is inside the given AABB. + * + * @param box + * bounding box to check + * + * @return true, if point is inside + */ + public boolean isInAABBD(AABB box); + public boolean isInAABBD(AABBD box); + + /** + * Checks if the point is inside the given axis-aligned bounding box. + * + * @param boxOrigin + * bounding box origin/center + * @param boxExtent + * bounding box extends (half measure) + * + * @return true, if point is inside the box + */ + + public boolean isInAABBD(Vec3D boxOrigin, Vec3D boxExtent); + public boolean isInAABBD(VecD3D boxOrigin, Vec3D boxExtent); + public boolean isInAABBD(Vec3D boxOrigin, VecD3D boxExtent); + public boolean isInAABBD(VecD3D boxOrigin, VecD3D boxExtent); + + /** + * Checks if the vector is parallel with either the X or Y axis (any + * direction). + * + * @param tolerance + * @return true, if parallel within the given tolerance + */ + public boolean isMajorAxisD(float tolerance); + public boolean isMajorAxisD(double tolerance); + + /** + * Checks if vector has a magnitude equals or close to zero (tolerance used + * is {@link MathUtils#EPS}). + * + * @return true, if zero vector + */ + public boolean isZeroVector(); + + /** + * Calculates the magnitude/eucledian length of the vector. + * + * @return vector length + */ + public double magnitude(); + + /** + * Calculates only the squared magnitude/length of the vector. Useful for + * inverse square law applications and/or for speed reasons or if the real + * eucledian distance is not required (e.g. sorting). + * + * @return squared magnitude (x^2 + y^2 + z^2) + */ + public double magSquared(); + + /** + * Scales vector uniformly and returns result as new vector. + * + * @param s + * scale factor + * + * @return new vector + */ + public VecD3D scale(float s); + public VecD3D scale(double s); + + /** + * Scales vector non-uniformly and returns result as new vector. + * + * @param a + * scale factor for X coordinate + * @param b + * scale factor for Y coordinate + * @param c + * scale factor for Z coordinate + * + * @return new vector + */ + public VecD3D scale(float a, float b, float c); + public VecD3D scale(double a, float b, float c); + public VecD3D scale(float a, double b, float c); + public VecD3D scale(float a, float b, double c); + public VecD3D scale(double a, double b, float c); + public VecD3D scale(double a, float b, double c); + public VecD3D scale(float a, double b, double c); + public VecD3D scale(double a, double b, double c); + + /** + * Scales vector non-uniformly by vector v and returns result as new vector. + * + * @param s + * scale vector + * + * @return new vector + */ + public VecD3D scale(ReadonlyVec3D s); + public VecD3D scale(ReadonlyVecD3D s); + + /** + * Subtracts vector {a,b,c} and returns result as new vector. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @param c + * Z coordinate + * + * @return result as new vector + */ + public VecD3D sub(float a, float b, float c); + public VecD3D sub(double a, float b, float c); + public VecD3D sub(float a, double b, float c); + public VecD3D sub(float a, float b, double c); + public VecD3D sub(double a, double b, float c); + public VecD3D sub(double a, float b, double c); + public VecD3D sub(float a, double b, double c); + public VecD3D sub(double a, double b, double c); + + /** + * Convert from float to double versions of Vec3Dr + */ + public VecD3D VecD3D(Vec3D v); + + /** + * Subtracts vector v and returns result as new vector. + * + * @param v + * vector to be subtracted + * + * @return result as new vector + */ + public VecD3D sub(ReadonlyVec3D v); + public VecD3D sub(ReadonlyVecD3D v); + + /** + * Creates a new 2D vector of the XY components. + * + * @return new vector + */ + public VecD2D toD2DXY(); + + /** + * Creates a new 2D vector of the XZ components. + * + * @return new vector + */ + public VecD2D toD2DXZ(); + + /** + * Creates a new 2D vector of the YZ components. + * + * @return new vector + */ + public VecD2D toD2DYZ(); + + /** + * Creates a Vec4D instance of this vector with the w component set to 1.0 + * + * @return 4d vector + */ + public VecD4D toD4D(); + + /** + * Creates a Vec4D instance of this vector with it w component set to the + * given value. + * + * @param w + * @return weighted 4d vector + */ + public VecD4D toD4D(float w); + public VecD4D toD4D(double w); + + public double[] toArray(); + + public double[] toArray4(float w); + public double[] toArray4(double w); + + public double x(); + + public double y(); + + public double z(); +} \ No newline at end of file diff --git a/src.core/toxi/geom/ReadonlyVecD4D.java b/src.core/toxi/geom/ReadonlyVecD4D.java new file mode 100644 index 00000000..c04bbd4f --- /dev/null +++ b/src.core/toxi/geom/ReadonlyVecD4D.java @@ -0,0 +1,378 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; + +/** + * Readonly, immutable interface wrapper for VecD3D instances. Used throughout + * the library for safety purposes. + */ +public interface ReadonlyVecD4D { + + /** + * Add vector v and returns result as new vector. + * + * @param v + * vector to add + * + * @return result as new vector + */ + public VecD4D add(ReadonlyVecD4D v); + + public VecD4D addScaled(ReadonlyVecD4D t, double s); + + /** + * Adds vector {a,b,c} and returns result as new vector. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @param c + * Z coordinate + * + * @return result as new vector + */ + public VecD4D addXYZ(double a, double b, double c); + + /** + * Returns the (4-space) angle in radians between this vector and the vector + * parameter; the return value is constrained to the range [0,PI]. + * + * @param v + * the other vector + * @return the angle in radians in the range [0,PI] + */ + public double angleBetween(ReadonlyVecD4D v); + + /** + * Compares the length of the vector with another one. + * + * @param v + * vector to compare with + * + * @return -1 if other vector is longer, 0 if both are equal or else +1 + */ + public int compareTo(ReadonlyVecD4D v); + + /** + * Copy. + * + * @return a new independent instance/copy of a given vector + */ + public VecD4D copy(); + + /** + * Calculates distance to another vector. + * + * @param v + * non-null vector + * + * @return distance or Float.NaN if v=null + */ + public double distanceTo(ReadonlyVecD4D v); + + /** + * Calculates the squared distance to another vector. + * + * @param v + * non-null vector + * + * @return distance or NaN if v=null + * + * @see #magSquared() + */ + public double distanceToSquared(ReadonlyVecD4D v); + + /** + * Computes the scalar product (dot product) with the given vector. + * + * @param v + * the v + * + * @return dot product + * + * @see Wikipedia + * entry + */ + public double dot(ReadonlyVecD4D v); + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj); + + /** + * Compares this vector with the one given. The vectors are deemed equal if + * the individual differences of all component values are within the given + * tolerance. + * + * @param v + * the v + * @param tolerance + * the tolerance + * + * @return true, if equal + */ + public boolean equalsWithTolerance(ReadonlyVecD4D v, double tolerance); + + /** + * Gets the abs. + * + * @return the abs + */ + public VecD4D getAbs(); + + /** + * Scales vector uniformly by factor -1 ( v = -v ). + * + * @return result as new vector + */ + public VecD4D getInvertedXYZ(); + + /** + * Produces a new vector with all of its coordinates passed through the + * given {@link ScaleMap}. This version also maps the w coordinate. For + * mapping only XYZ use the {@link #getMappedXYZ(ScaleMap)} version. + * + * @param map + * @return mapped vector + */ + public VecD4D getMapped(ScaleMap map); + + /** + * Produces a new vector with only its XYZ coordinates passed through the + * given {@link ScaleMap}. This version keeps the original w coordinate. For + * mapping all XYZW, use the {@link #getMapped(ScaleMap)} version. + * + * @param map + * @return mapped vector + */ + public VecD4D getMappedXYZ(ScaleMap map); + + /** + * Produces the normalized version as a new vector. + * + * @return new vector + */ + public VecD4D getNormalized(); + + /** + * Produces a new vector normalized to the given length. + * + * @param len + * new desired length + * + * @return new vector + */ + public VecD4D getNormalizedTo(double len); + + /** + * Gets the rotated around axis. + * + * @param axis + * the axis + * @param theta + * the theta + * + * @return new result vector + */ + public VecD4D getRotatedAroundAxis(ReadonlyVecD3D axis, double theta); + + /** + * Creates a new vector rotated by the given angle around the X axis. + * + * @param theta + * the theta + * + * @return rotated vector + */ + public VecD4D getRotatedX(double theta); + + /** + * Creates a new vector rotated by the given angle around the Y axis. + * + * @param theta + * the theta + * + * @return rotated vector + */ + public VecD4D getRotatedY(double theta); + + /** + * Creates a new vector rotated by the given angle around the Z axis. + * + * @param theta + * the theta + * + * @return rotated vector + */ + public VecD4D getRotatedZ(double theta); + + /** + * Creates a new vector with its XYZ coordinates rounded to the given + * precision (grid alignment). The weight component remains unmodified. + * + * @param prec + * @return grid aligned vector + */ + public VecD4D getRoundedXYZTo(double prec); + + public VecD4D getUnweighted(); + + public VecD4D getWeighted(); + + /** + * Interpolates the vector towards the given target vector, using linear + * interpolation. + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * + * @return result as new vector + */ + public VecD4D interpolateTo(ReadonlyVecD4D v, double f); + + /** + * Interpolates the vector towards the given target vector, using the given + * {@link InterpolateStrategy}. + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * @param s + * InterpolateStrategy instance + * + * @return result as new vector + */ + public VecD4D interpolateTo(ReadonlyVecD4D v, double f, InterpolateStrategy s); + + /** + * Checks if vector has a magnitude equals or close to zero (tolerance used + * is {@link MathUtils#EPS}). + * + * @return true, if zero vector + */ + public boolean isZeroVector(); + + /** + * Calculates the magnitude/eucledian length of the vector. + * + * @return vector length + */ + public double magnitude(); + + /** + * Calculates only the squared magnitude/length of the vector. Useful for + * inverse square law applications and/or for speed reasons or if the real + * eucledian distance is not required (e.g. sorting). + * + * @return squared magnitude (x^2 + y^2 + z^2) + */ + public double magSquared(); + + /** + * Scales vector uniformly and returns result as new vector. + * + * @param s + * scale factor + * + * @return new vector + */ + public VecD4D scale(double s); + + /** + * Scales vector non-uniformly and returns result as new vector. + * + * @param x + * scale factor for X coordinate + * @param y + * scale factor for Y coordinate + * @param z + * scale factor for Z coordinate + * + * @return new vector + */ + public VecD4D scale(double x, double y, double z, double w); + + /** + * Scales vector non-uniformly by vector v and returns result as new vector. + * + * @param s + * scale vector + * + * @return new vector + */ + public VecD4D scale(ReadonlyVecD4D s); + + /** + * Subtracts vector v and returns result as new vector. + * + * @param v + * vector to be subtracted + * + * @return result as new vector + */ + public VecD4D sub(ReadonlyVecD4D v); + + /** + * Subtracts vector {a,b,c} and returns result as new vector. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @param c + * Z coordinate + * + * @return result as new vector + */ + public VecD4D subXYZ(double a, double b, double c); + + public double[] toArray(); + + public VecD3D unweightInto(VecD3D out); + + public double w(); + + public VecD3D weightInto(VecD3D out); + + public double x(); + + public double y(); + + public double z(); +} \ No newline at end of file diff --git a/src.core/toxi/geom/RectD.java b/src.core/toxi/geom/RectD.java new file mode 100644 index 00000000..4fca70f5 --- /dev/null +++ b/src.core/toxi/geom/RectD.java @@ -0,0 +1,610 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.MathUtils; + +@XmlAccessorType(XmlAccessType.FIELD) +public class RectD implements ShapeD2D { + + /** + * Factory method, constructs a new rectangle from a center point and extent + * vector. + * + * @param center + * @param extent + * @return new rect + */ + public static final RectD fromCenterExtent(ReadonlyVecD2D center, VecD2D extent) { + return new RectD(center.sub(extent), center.add(extent)); + } + + /** + * Factory method, computes & returns the bounding rect for the given list + * of points. + * + * @param points + * @return bounding rect + * @since 0021 + */ + public static final RectD getBoundingRectD(List points) { + final VecD2D first = points.get(0); + final RectD bounds = new RectD(first.x, first.y, 0, 0); + for (int i = 1, num = points.size(); i < num; i++) { + bounds.growToContainPoint(points.get(i)); + } + return bounds; + } + + @XmlAttribute(required = true) + public double x, y, width, height; + + /** + * Constructs an empty rectangle at point 0,0 with no dimensions. + */ + public RectD() { + + } + + /** + * Constructs a new rectangle using a point and dimensions + * + * @param x + * x of top left + * @param y + * y of top left + * @param width + * @param height + */ + public RectD(double x, double y, double width, double height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Constructs a new rectangle defined by the two points given. + * + * @param p1 + * @param p2 + */ + public RectD(ReadonlyVecD2D p1, ReadonlyVecD2D p2) { + VecD2D tl = VecD2D.min(p1, p2); + VecD2D br = VecD2D.max(p1, p2); + x = tl.x; + y = tl.y; + width = br.x - x; + height = br.y - y; + } + + /** + * Checks if the given point is within the rectangle's bounds. + * + * @param p + * point to check + * @return true, if point is contained + */ + public boolean containsPoint(ReadonlyVecD2D p) { + double px = p.x(); + double py = p.y(); + if (px < x || px >= x + width) { + return false; + } + if (py < y || py >= y + height) { + return false; + } + return true; + } + + /** + * Creates a copy of this rectangle + * + * @return new instance + */ + public RectD copy() { + return new RectD(x, y, width, height); + } + + /** + * Returns true if the Object o is of type RectD and all of the data members + * of o are equal to the corresponding data members in this rectangle. + * + * @param o + * the Object with which the comparison is made + * @return true or false + */ + public boolean equals(Object o) { + try { + RectD r = (RectD) o; + return (x == r.x && y == r.y && width == r.width && height == r.height); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + public final double getArea() { + return width * height; + } + + /** + * Computes the aspect ratio of the rect as width over height. + * + * @return aspect ratio + */ + public final double getAspect() { + return width / height; + } + + public double getBottom() { + return y + height; + } + + public VecD2D getBottomLeft() { + return new VecD2D(x, y + height); + } + + public final VecD2D getBottomRight() { + return new VecD2D(x + width, y + height); + } + + public CircleD getBoundingCircleD() { + return new CircleD(getCentroid(), + new VecD2D(width, height).magnitude() / 2); + } + + /** + * Only provided because the RectD class implements {@link Shape2D}. The + * bounding rect of a rectangle is itself. + * + * @return itself + * + * @see toxi.geom.Shape2D#getBounds() + */ + public RectD getBounds() { + return this; + } + + /** + * Returns the centroid of the rectangle. + * + * @return centroid vector + */ + public final VecD2D getCentroid() { + return new VecD2D(x + width * 0.5f, y + height * 0.5f); + } + + public final double getCircumference() { + return 2 * width + 2 * height; + } + + /** + * Returns a vector containing the width and height of the rectangle. + * + * @return dimension vector + */ + public final VecD2D getDimensions() { + return new VecD2D(width, height); + } + + /** + * Returns one of the rectangles edges as {@link LineD2D}. The edge IDs are: + *
    + *
  • 0 - top
  • + *
  • 1 - right
  • + *
  • 2 - bottom
  • + *
  • 3 - left
  • + *
+ * + * @param id + * edge ID + * @return edge as LineD2D + */ + public LineD2D getEdge(int id) { + LineD2D edge = null; + switch (id) { + // top + case 0: + edge = new LineD2D(x, y, x + width, y); + break; + // right + case 1: + edge = new LineD2D(x + width, y, x + width, y + height); + break; + // bottom + case 2: + edge = new LineD2D(x, y + height, x + width, y + height); + break; + // left + case 3: + edge = new LineD2D(x, y, x, y + height); + break; + default: + throw new IllegalArgumentException("edge ID needs to be 0...3"); + } + return edge; + } + + public List getEdges() { + List edges = new ArrayList(); + for (int i = 0; i < 4; i++) { + edges.add(getEdge(i)); + } + return edges; + } + + public double getLeft() { + return x; + } + + /** + * Computes the normalized position of the given point within this + * rectangle, so that a point at the top-left corner becomes {0,0} and + * bottom-right {1,1}. The original point is not modified. Together with + * {@link #getUnmappedPointInRectD(VecD2D)} this function can be used to map a + * point from one rectangle to another. + * + * @param p + * point to be mapped + * @return mapped VecD2D + */ + public VecD2D getMappedPointInRectD(VecD2D p) { + return new VecD2D((p.x - x) / width, (p.y - y) / height); + } + + /** + * Creates a random point within the rectangle. + * + * @return VecD2D + */ + public VecD2D getRandomPoint() { + return new VecD2D(MathUtils.random(x, x + width), MathUtils.random(y, y + + height)); + } + + public double getRight() { + return x + width; + } + + public double getTop() { + return y; + } + + public final VecD2D getTopLeft() { + return new VecD2D(x, y); + } + + public VecD2D getTopRight() { + return new VecD2D(x + width, y); + } + + /** + * Inverse operation of {@link #getMappedPointInRectD(VecD2D)}. Given a + * normalized point it computes the position within this rectangle, so that + * a point at {0,0} becomes the top-left corner and {1,1} bottom-right. The + * original point is not modified. Together with + * {@link #getUnmappedPointInRectD(VecD2D)} this function can be used to map a + * point from one rectangle to another. + * + * @param p + * point to be mapped + * @return mapped VecD2D + */ + public VecD2D getUnmappedPointInRectD(VecD2D p) { + return new VecD2D(p.x * width + x, p.y * height + y); + } + + public RectD growToContainPoint(ReadonlyVecD2D p) { + if (!containsPoint(p)) { + if (p.x() < x) { + width = getRight() - p.x(); + x = p.x(); + } else if (p.x() > getRight()) { + width = p.x() - x; + } + if (p.y() < y) { + height = getBottom() - p.y(); + y = p.y(); + } else if (p.y() > getBottom()) { + height = p.y() - y; + } + } + return this; + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different RectD objects with identical data values (i.e., RectD.equals + * returns true) will return the same hash code value. Two objects with + * different data members may return the same hash value, although this is + * not likely. + * + * @return the integer hash code value + */ + public int hashCode() { + return ((Double)x).hashCode()+((Double)y).hashCode()+((Double)width).hashCode()+((Double)height).hashCode(); + } + + /** + * Creates a new rectangle by forming the intersection of this rectangle and + * the given other rect. The resulting bounds will be the rectangle of the + * overlay area or null if the rects do not intersect. + * + * @param r + * intersection partner rect + * @return new RectD or null + */ + public final RectD intersectionRectDWith(RectD r) { + RectD isec = null; + if (intersectsRectD(r)) { + double x1 = MathUtils.max(x, r.x); + double y1 = MathUtils.max(y, r.y); + double x2 = MathUtils.min(getRight(), r.getRight()); + double y2 = MathUtils.min(getBottom(), r.getBottom()); + isec = new RectD(x1, y1, x2 - x1, y2 - y1); + } + return isec; + } + + public boolean intersectsCircleD(VecD2D c, double r) { + double s, d = 0; + double x2 = x + width; + double y2 = y + height; + if (c.x < x) { + s = c.x - x; + d = s * s; + } else if (c.x > x2) { + s = c.x - x2; + d += s * s; + } + if (c.y < y) { + s = c.y - y; + d += s * s; + } else if (c.y > y2) { + s = c.y - y2; + d += s * s; + } + return d <= r * r; + } + + /** + * Checks if the rectangle intersects with the given ray and if so computes + * the first intersection point. The method takes a min/max distance + * interval along the ray in which the intersection must occur. + * + * @param ray + * intersection ray + * @param minDist + * minimum distance + * @param maxDist + * max distance + * @return intersection point or null if no intersection in the given + * interval + */ + public ReadonlyVecD2D intersectsRayD(RayD2D ray, double minDist, double maxDist) { + VecD2D invDir = ray.getDirection().reciprocal(); + boolean signDirX = invDir.x < 0; + boolean signDirY = invDir.y < 0; + VecD2D min = getTopLeft(); + VecD2D max = getBottomRight(); + VecD2D bbox = signDirX ? max : min; + double tmin = (bbox.x - ray.x) * invDir.x; + bbox = signDirX ? min : max; + double tmax = (bbox.x - ray.x) * invDir.x; + bbox = signDirY ? max : min; + double tymin = (bbox.y - ray.y) * invDir.y; + bbox = signDirY ? min : max; + double tymax = (bbox.y - ray.y) * invDir.y; + if ((tmin > tymax) || (tymin > tmax)) { + return null; + } + if (tymin > tmin) { + tmin = tymin; + } + if (tymax < tmax) { + tmax = tymax; + } + if ((tmin < maxDist) && (tmax > minDist)) { + return ray.getPointAtDistance(tmin); + } + return null; + } + + /** + * Checks if this rectangle intersects/overlaps the given one. + * + * @param r + * another rect + * @return true, if intersecting + */ + public boolean intersectsRectD(RectD r) { + return !(x > r.x + r.width || x + width < r.x || y > r.y + r.height || y + + height < r.y); + } + + public RectD scale(double s) { + VecD2D c = getCentroid(); + width *= s; + height *= s; + x = c.x - width * 0.5f; + y = c.y - height * 0.5f; + return this; + } + + /** + * Sets new bounds for this rectangle. + * + * @param x + * x of top left + * @param y + * y of top right + * @param w + * width + * @param h + * height + * @return itself + */ + public final RectD set(double x, double y, double w, double h) { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + return this; + } + + public final RectD set(RectD r) { + x = r.x; + y = r.y; + width = r.width; + height = r.height; + return this; + } + + public final RectD setDimension(VecD2D dim) { + width = dim.x; + height = dim.y; + return this; + } + + public final RectD setPosition(VecD2D pos) { + x = pos.x; + y = pos.y; + return this; + } + + /** + * Adds corner vertices for rounded rectangles polygon construction. + * + * @param poly + * @param o + * @param radius + * @param theta + * @param res + */ + private void toPolyArc(PolygonD2D poly, VecD2D o, double radius, double theta, + int res) { + for (int i = 0; i <= res; i++) { + poly.add(o.add(VecD2D.fromTheta(theta + i * MathUtils.HALF_PI / res) + .scaleSelf(radius))); + } + } + + /** + * Creates a {@link Polygon2D} instance of the rect. + * + * @return rect as polygon + */ + public PolygonD2D toPolygonD2D() { + PolygonD2D poly = new PolygonD2D(); + poly.add(new VecD2D(x, y)); + poly.add(new VecD2D(x + width, y)); + poly.add(new VecD2D(x + width, y + height)); + poly.add(new VecD2D(x, y + height)); + return poly; + } + + /** + * Turns this rectangle into a rounded rectangle shaped {@link Polygon2D} + * instance with the given corner radius. The number of corner vertices to + * be used, can be specified as well. + * + * @param radius + * corner radius + * @param res + * number of vertices per corner + * @return rounded rect as polygon + */ + public PolygonD2D toPolygonD2D(double radius, int res) { + PolygonD2D poly = new PolygonD2D(); + toPolyArc(poly, new VecD2D(x + width - radius, y + radius), radius, + -MathUtils.HALF_PI, res); + toPolyArc(poly, new VecD2D(x + width - radius, y + height - radius), + radius, 0, res); + toPolyArc(poly, new VecD2D(x + radius, y + height - radius), radius, + MathUtils.HALF_PI, res); + toPolyArc(poly, new VecD2D(x + radius, y + radius), radius, + MathUtils.PI, res); + return poly; + } + + @Override + public String toString() { + return "rect: {x:" + x + ", y:" + y + ", width:" + width + ", height:" + + height + "}"; + } + + public RectD translate(double dx, double dy) { + x += dx; + y += dy; + return this; + } + + public RectD translate(ReadonlyVecD2D offset) { + x += offset.x(); + y += offset.y(); + return this; + } + + /** + * @deprecated use {@link #unionRectDWith(RectD)} instead. Also note, that + * {@link #unionRectDWith(RectD)} does NOT modify the original + * RectD anymore, but produces a new RectD instance. + * @param r + * @return new RectD + */ + @Deprecated + public final RectD union(RectD r) { + return unionRectDWith(r); + } + + /** + * Creates a new rectangle by forming an union of this rectangle and the + * given other Rect. The resulting bounds will be inclusive of both. + * + * @param r + * @return new RectD + */ + public final RectD unionRectDWith(RectD r) { + double x1 = MathUtils.min(x, r.x); + double x2 = MathUtils.max(x + width, r.x + r.width); + double w = x2 - x1; + double y1 = MathUtils.min(y, r.y); + double y2 = MathUtils.max(y + height, r.y + r.height); + double h = y2 - y1; + return new RectD(x1, y1, w, h); + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/ReflectorD3D.java b/src.core/toxi/geom/ReflectorD3D.java new file mode 100644 index 00000000..7ab9091d --- /dev/null +++ b/src.core/toxi/geom/ReflectorD3D.java @@ -0,0 +1,58 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * Generic interface for ray reflection with 3D geometry + */ +public interface ReflectorD3D extends IntersectorD3D { + + /** + * Returns the point on the reflected ray at given distance from the + * intersection point + * + * @param dist + * distance from isect position + * @return point on reflected ray + */ + public ReadonlyVecD3D getReflectedRayDPointAtDistance(double dist); + + /** + * @return angle between incident ray and surface normal + */ + public double getReflectionAngle(); + + /** + * Reflects given ray on the entity's surface + * + * @param ray + * incident ray + * @return reflected ray starting from intersection point + */ + public RayD3D reflectRayD(RayD3D ray); +} \ No newline at end of file diff --git a/src.core/toxi/geom/ShapeD2D.java b/src.core/toxi/geom/ShapeD2D.java new file mode 100644 index 00000000..184e7369 --- /dev/null +++ b/src.core/toxi/geom/ShapeD2D.java @@ -0,0 +1,93 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.List; + +/** + * Interface description of common operations supported by 2D geometry types. + */ +public interface ShapeD2D { + + /** + * Checks if the point is within the given shape. + * + * @return true, if inside + */ + boolean containsPoint(ReadonlyVecD2D p); + + /** + * Computes the area of the shape. + * + * @return area + */ + double getArea(); + + /** + * Computes the bounding circle of the shape. + * + * @return circle + */ + CircleD getBoundingCircleD(); + + /** + * Returns the shape's axis-aligned bounding rect. + * + * @return bounding rect + */ + RectD getBounds(); + + /** + * Computes the shape's circumference. + * + * @return circumference + */ + double getCircumference(); + + /** + * Returns a list of the shape's perimeter edges. + * + * @return list of {@link Line2D} elements + */ + List getEdges(); + + /** + * Computes a random point within the shape's perimeter. + * + * @return Vec2D + */ + VecD2D getRandomPoint(); + + /** + * Converts the shape into a {@link Polygon2D} instance (possibly via a + * default resolution, e.g. for circles/ellipses) + * + * @return shape as polygon + */ + PolygonD2D toPolygonD2D(); +} diff --git a/src.core/toxi/geom/ShapeD3D.java b/src.core/toxi/geom/ShapeD3D.java new file mode 100644 index 00000000..cccfbc5c --- /dev/null +++ b/src.core/toxi/geom/ShapeD3D.java @@ -0,0 +1,41 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +/** + * Interface description of common operations supported by double 3D geometry types. + */ +public interface ShapeD3D { + + /** + * Checks if the point is within the given shape/volume. + * + * @return true, if inside + */ + boolean containsPoint(ReadonlyVecD3D p); +} diff --git a/src.core/toxi/geom/SpatialBins.java b/src.core/toxi/geom/SpatialBins.java index b4bcfe98..112a41e0 100644 --- a/src.core/toxi/geom/SpatialBins.java +++ b/src.core/toxi/geom/SpatialBins.java @@ -78,6 +78,19 @@ public List itemsWithinRadius(T p, float radius, List results) { } return results; } + public List itemsWithinRadius(T p, double radius, List results) { + int id = (int) MathUtils.clip((extractor.coordinate(p) - minOffset) + * invBinWidth, 0, numBins); + int tol = (int) Math.ceil(radius * invBinWidth); + for (int i = Math.max(id - tol, 0), n = Math.min( + Math.min(id + tol, numBins), numBins - 1); i <= n; i++) { + if (results == null) { + results = new ArrayList(); + } + results.addAll(bins.get(i)); + } + return results; + } /* * (non-Javadoc) diff --git a/src.core/toxi/geom/SpatialIndexD.java b/src.core/toxi/geom/SpatialIndexD.java new file mode 100644 index 00000000..98a821aa --- /dev/null +++ b/src.core/toxi/geom/SpatialIndexD.java @@ -0,0 +1,21 @@ +package toxi.geom; + +import java.util.List; + +public interface SpatialIndexD { + + public void clear(); + + public boolean index(T p); + + public boolean isIndexed(T item); + + public List itemsWithinRadius(T p, double radius, List results); + + public boolean reindex(T p, T q); + + public int size(); + + public boolean unindex(T p); + +} \ No newline at end of file diff --git a/src.core/toxi/geom/Sphere.java b/src.core/toxi/geom/Sphere.java index dae9f147..6d5ad058 100644 --- a/src.core/toxi/geom/Sphere.java +++ b/src.core/toxi/geom/Sphere.java @@ -63,6 +63,9 @@ public Sphere(ReadonlyVec3D v, float r) { public Sphere(Sphere s) { this(s, s.radius); } + public Sphere(SphereD s) { + this(new Vec3D((float)s.x,(float)s.y,(float)s.z), (float)s.radius); + } public boolean containsPoint(ReadonlyVec3D p) { float d = this.sub(p).magSquared(); diff --git a/src.core/toxi/geom/SphereD.java b/src.core/toxi/geom/SphereD.java new file mode 100644 index 00000000..9fef9c0c --- /dev/null +++ b/src.core/toxi/geom/SphereD.java @@ -0,0 +1,192 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.geom.mesh.MeshD3D; +import toxi.geom.mesh.SphereDFunction; +import toxi.geom.mesh.SurfaceMeshDBuilder; + +@XmlAccessorType(XmlAccessType.FIELD) +public class SphereD extends VecD3D implements ShapeD3D { + + /** + * Earth's mean radius in km + * (http://en.wikipedia.org/wiki/Earth_radius#Mean_radii) + */ + public static final double EARTH_RADIUS = ((2 * 6378.1370 + 6356.752314245) / 3.0); + + @XmlAttribute(required = true) + public double radius; + + public SphereD() { + this(new VecD3D(), 1); + } + + public SphereD(double radius) { + this(new VecD3D(), radius); + } + + public SphereD(ReadonlyVecD3D v, double r) { + super(v); + radius = r; + } + + public SphereD(SphereD s) { + this(s, s.radius); + } + public SphereD(Sphere s) { + this(new VecD3D(s.x,s.y,s.z), s.radius); + } + + public boolean containsPoint(ReadonlyVecD3D p) { + double d = this.sub(p).magSquared(); + return (d <= radius * radius); + } + + /** + * Alternative to {@link SphereDIntersectorReflector}. Computes primary & + * secondary intersection points of this sphere with the given ray. If no + * intersection is found the method returns null. In all other cases, the + * returned array will contain the distance to the primary intersection + * point (i.e. the closest in the direction of the ray) as its first index + * and the other one as its second. If any of distance values is negative, + * the intersection point lies in the opposite ray direction (might be + * useful to know). To get the actual intersection point coordinates, simply + * pass the returned values to {@link Ray3D#getPointAtDistance(double)}. + * + * @param ray + * @return 2-element double array of intersection points or null if ray + * doesn't intersect sphere at all. + */ + public double[] intersectRay(RayD3D ray) { + double[] result = null; + ReadonlyVecD3D q = ray.sub(this); + double distSquared = q.magSquared(); + double v = -q.dot(ray.getDirection()); + double d = radius * radius - (distSquared - v * v); + if (d >= 0.0) { + d = Math.sqrt(d); + double a = v + d; + double b = v - d; + if (!(a < 0 && b < 0)) { + if (a > 0 && b > 0) { + if (a > b) { + double t = a; + a = b; + b = t; + } + } else { + if (b > 0) { + double t = a; + a = b; + b = t; + } + } + } + result = new double[] { + a, b + }; + } + return result; + } + + /** + * Considers the current vector as centre of a collision sphere with radius + * r and checks if the triangle abc intersects with this sphere. The VecD3D p + * The point on abc closest to the sphere center is returned via the + * supplied result vector argument. + * + * @param t + * triangle to check for intersection + * @param result + * a non-null vector for storing the result + * @return true, if sphere intersects triangle ABC + */ + public boolean intersectSphereDTriangleD(TriangleD3D t, VecD3D result) { + // Find VecD3D P on triangle ABC closest to sphere center + result.set(t.closestPointOnSurface(this)); + + // SphereD and TriangleD intersect if the (squared) distance from sphere + // center to VecD3D p is less than the (squared) sphere radius + ReadonlyVecD3D v = result.sub(this); + return v.magSquared() <= radius * radius; + } + + /** + * Computes the surface distance on this sphere between two points given as + * lon/lat coordinates. The x component of each vector needs to contain the + * longitude and the y component the latitude (both in radians). + * + * Algorithm from: http://www.csgnetwork.com/gpsdistcalc.html + * + * @param p + * @param q + * @return distance on the sphere surface + */ + public double surfaceDistanceBetween(VecD2D p, VecD2D q) { + double t1 = Math.sin(p.y) * Math.sin(q.y); + double t2 = Math.cos(p.y) * Math.cos(q.y); + double t3 = Math.cos(p.x - q.x); + double t4 = t2 * t3; + double t5 = t1 + t4; + double dist = Math.atan(-t5 / Math.sqrt(-t5 * t5 + 1)) + 2 + * Math.atan(1); + if (Double.isNaN(dist)) { + dist = 0; + } else { + dist *= radius; + } + return dist; + } + + /** + * Calculates the normal vector on the sphere in the direction of the + * current point. + * + * @param q + * @return a unit normal vector to the tangent plane of the ellipsoid in the + * point. + */ + public VecD3D tangentPlaneNormalAt(ReadonlyVecD3D q) { + return q.sub(this).normalize(); + } + + public MeshD3D toMesh(int res) { + return toMesh(null, res); + } + + public MeshD3D toMesh(MeshD3D mesh, int res) { + SurfaceMeshDBuilder builder = new SurfaceMeshDBuilder(new SphereDFunction( + this)); + return builder.createMeshD(mesh, res, 1); + } +} diff --git a/src.core/toxi/geom/SphereDIntersectorReflector.java b/src.core/toxi/geom/SphereDIntersectorReflector.java new file mode 100644 index 00000000..e2a648ab --- /dev/null +++ b/src.core/toxi/geom/SphereDIntersectorReflector.java @@ -0,0 +1,159 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +public class SphereDIntersectorReflector implements IntersectorD3D, ReflectorD3D { + + protected SphereD sphere; + protected IsectDataD3D isectData; + + protected ReadonlyVecD3D reflectedDir, reflectedPos; + protected double reflectTheta; + + public SphereDIntersectorReflector(SphereD s) { + sphere = s; + isectData = new IsectDataD3D(); + } + + public SphereDIntersectorReflector(VecD3D o, double r) { + this(new SphereD(o, r)); + } + + public IsectDataD3D getIntersectionDataD() { + return isectData; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.Reflector3D#getReflectedRayPointAtDistance + */ + public ReadonlyVecD3D getReflectedRayDPointAtDistance(double dist) { + if (reflectedDir != null) { + return isectData.pos.add(reflectedDir.scale(dist)); + } else { + return null; + } + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.Reflector3D#getReflectionAngle() + */ + public double getReflectionAngle() { + return reflectTheta; + } + + /** + * @return the sphere + */ + public SphereD getSphereD() { + return sphere; + } + + /** + * Calculates the distance of the vector to the given sphere in the + * specified direction. A sphere is defined by a 3D point and a radius. + * Normalized directional vectors expected. + * + * @param ray + * intersection ray + * @return distance to sphere in world units, -1 if no intersection. + */ + + public double intersectRayDDistance(RayD3D ray) { + ReadonlyVecD3D q = sphere.sub(ray); + double distSquared = q.magSquared(); + double v = q.dot(ray.dir); + double d = sphere.radius * sphere.radius - (distSquared - v * v); + + // If there was no intersection, return -1 + if (d < 0.0) { + return -1; + } + + // Return the distance to the [first] intersecting point + return v - Math.sqrt(d); + } + + public boolean intersectsRayD(RayD3D ray) { + isectData.dist = intersectRayDDistance(ray); + isectData.isIntersection = isectData.dist >= 0; + if (isectData.isIntersection) { + // get the intersection point + isectData.pos = ray.add(ray.getDirection().scale(isectData.dist)); + // calculate the direction from our point to the intersection pos + isectData.dir = isectData.pos.sub(ray); + isectData.normal = sphere.tangentPlaneNormalAt(isectData.pos); + } + return isectData.isIntersection; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.Reflector3D#reflectRay(toxi.geom.VecD3D, toxi.geom.VecD3D) + */ + public RayD3D reflectRayD(RayD3D ray) { + if (intersectsRayD(ray)) { + // compute the normal vector of the sphere at the intersection + // position + // compute the reflection angle + reflectTheta = isectData.dir.angleBetween(isectData.normal, true) + * 2 + MathUtils.PI; + // then form a perpendicular vector standing on the plane spanned by + // isectDir and sphereNormal + // this vector will be used to mirror the ray around the + // intersection point + VecD3D reflectNormal = isectData.dir.getNormalized() + .cross(isectData.normal).normalize(); + if (!reflectNormal.isZeroVector()) { + // compute the reflected ray direction + reflectedDir = isectData.dir.getNormalized().rotateAroundAxis( + reflectNormal, reflectTheta); + } else { + reflectedDir = isectData.dir.getInverted(); + } + return new RayD3D(isectData.pos, reflectedDir); + } else { + return null; + } + } + + /** + * @param sphere + * the sphere to set + */ + public void setSphereD(SphereD sphere) { + this.sphere = sphere; + } + +} diff --git a/src.core/toxi/geom/SutherlandHodgemanClipperD.java b/src.core/toxi/geom/SutherlandHodgemanClipperD.java new file mode 100644 index 00000000..e2312a82 --- /dev/null +++ b/src.core/toxi/geom/SutherlandHodgemanClipperD.java @@ -0,0 +1,142 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.List; + +/** + * A version of the Sutherland-Hodgeman algorithm to clip 2D polygons optimized + * for rectangular clipping regions. + * + * More information: http://en.wikipedia.org/wiki/Sutherland-Hodgman_algorithm + * + * @see ConvexPolygonClipper + */ +public class SutherlandHodgemanClipperD implements PolygonClipperD2D { + + protected RectD bounds; + + public SutherlandHodgemanClipperD(RectD bounds) { + this.bounds = bounds; + } + + public PolygonD2D clipPolygonD(PolygonD2D poly) { + List points = new ArrayList(poly.vertices); + List clipped = new ArrayList(); + points.add(points.get(0)); + for (int edgeID = 0; edgeID < 4; edgeID++) { + clipped.clear(); + for (int i = 0, num = points.size() - 1; i < num; i++) { + VecD2D p = points.get(i); + VecD2D q = points.get(i + 1); + if (isInsideEdge(p, edgeID)) { + if (isInsideEdge(q, edgeID)) { + clipped.add(q.copy()); + } else { + clipped.add(getClippedPosOnEdge(edgeID, p, q)); + } + continue; + } + if (isInsideEdge(q, edgeID)) { + clipped.add(getClippedPosOnEdge(edgeID, p, q)); + clipped.add(q.copy()); + } + } + if (clipped.size() > 0 + && clipped.get(0) != clipped.get(clipped.size() - 1)) { + clipped.add(clipped.get(0)); + } + List t = points; + points = clipped; + clipped = t; + } + return new PolygonD2D(points).removeDuplicates(0.001f); + } + + /** + * @return the bounding rect + */ + public RectD getBounds() { + return bounds; + } + + private final VecD2D getClippedPosOnEdge(int edgeID, VecD2D p1, VecD2D p2) { + switch (edgeID) { + case 0: + return new VecD2D(p1.x + ((bounds.y - p1.y) * (p2.x - p1.x)) + / (p2.y - p1.y), bounds.y); + case 2: + double by = bounds.y + bounds.height; + return new VecD2D(p1.x + ((by - p1.y) * (p2.x - p1.x)) + / (p2.y - p1.y), by); + case 1: + double bx = bounds.x + bounds.width; + return new VecD2D(bx, p1.y + ((bx - p1.x) * (p2.y - p1.y)) + / (p2.x - p1.x)); + + case 3: + return new VecD2D(bounds.x, p1.y + + ((bounds.x - p1.x) * (p2.y - p1.y)) / (p2.x - p1.x)); + default: + return null; + } + } + + private final boolean isInsideEdge(VecD2D p, int edgeID) { + switch (edgeID) { + case 0: + return p.y >= bounds.y; + case 2: + return p.y < bounds.y + bounds.height; + case 3: + return p.x >= bounds.x; + case 1: + return p.x < bounds.x + bounds.width; + default: + return false; + } + } + + protected boolean isKnownVertex(List list, VecD2D q) { + for (VecD2D p : list) { + if (p.equalsWithTolerance(q, 0.0001f)) { + return true; + } + } + return false; + } + + /** + * @param bounds + * the bounding rect to set + */ + public void setBounds(RectD bounds) { + this.bounds = bounds; + } +} diff --git a/src.core/toxi/geom/TriangleD2D.java b/src.core/toxi/geom/TriangleD2D.java new file mode 100644 index 00000000..9eece7c2 --- /dev/null +++ b/src.core/toxi/geom/TriangleD2D.java @@ -0,0 +1,307 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; + +import toxi.geom.LineD2D.LineIntersection.Type; +import toxi.math.MathUtils; + +@XmlAccessorType(XmlAccessType.FIELD) +public class TriangleD2D implements ShapeD2D { + + public static TriangleD2D createEquilateralFrom(ReadonlyVecD2D a, + ReadonlyVecD2D b) { + VecD2D c = a.interpolateTo(b, 0.5f); + VecD2D dir = a.sub(b); + VecD2D n = dir.getPerpendicular(); + c.addSelf(n.normalizeTo(dir.magnitude() * MathUtils.SQRT3 / 2)); + return new TriangleD2D(a, b, c); + } + + public static boolean isClockwise(VecD2D a, VecD2D b, VecD2D c) { + double determ = (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y); + return (determ > 0.0); + } + + @XmlElement(required = true) + public VecD2D a, b, c; + + @XmlTransient + public VecD2D centroid; + + public TriangleD2D() { + } + + public TriangleD2D(ReadonlyVecD2D a, ReadonlyVecD2D b, ReadonlyVecD2D c) { + this.a = a.copy(); + this.b = b.copy(); + this.c = c.copy(); + } + + public TriangleD2D adjustTriangleSizeBy(double offset) { + return adjustTriangleSizeBy(offset, offset, offset); + } + + public TriangleD2D adjustTriangleSizeBy(double offAB, double offBC, double offCA) { + computeCentroid(); + LineD2D ab = new LineD2D(a.copy(), b.copy()).offsetAndGrowBy(offAB, + 100000, centroid); + LineD2D bc = new LineD2D(b.copy(), c.copy()).offsetAndGrowBy(offBC, + 100000, centroid); + LineD2D ca = new LineD2D(c.copy(), a.copy()).offsetAndGrowBy(offCA, + 100000, centroid); + a = ab.intersectLine(ca).getPos(); + b = ab.intersectLine(bc).getPos(); + c = bc.intersectLine(ca).getPos(); + computeCentroid(); + return this; + } + + public VecD2D computeCentroid() { + centroid = a.add(b).addSelf(c).scaleSelf(1f / 3); + return centroid; + } + + /** + * Checks if the given point is inside the triangle created by the points a, + * b and c. The triangle vertices are inclusive themselves. + * + * @return true, if point is in triangle. + */ + public boolean containsPoint(ReadonlyVecD2D p) { + VecD2D v1 = p.sub(a); + VecD2D v2 = p.sub(b); + VecD2D v3 = p.sub(c); + if (v1.isZeroVector() || v2.isZeroVector() || v3.isZeroVector()) { + return true; + } + v1.normalize(); + v2.normalize(); + v3.normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.001); + } + + public TriangleD2D copy() { + return new TriangleD2D(a.copy(), b.copy(), c.copy()); + } + + public TriangleD2D flipVertexOrder() { + VecD2D t = a; + a = c; + c = t; + return this; + } + + public VecD2D fromBarycentric(ReadonlyVecD3D p) { + return new VecD2D(a.x * p.x() + b.x * p.y() + c.x * p.z(), a.y * p.x() + + b.y * p.y() + c.y * p.z()); + } + + public double getArea() { + return b.sub(a).cross(c.sub(a)) * 0.5f; + } + + public CircleD getBoundingCircleD() { + return CircleD.from3Points(a, b, c); + } + + public RectD getBounds() { + return new RectD(VecD2D.min(VecD2D.min(a, b), c), VecD2D.max(VecD2D.max(a, b), c)); + } + + public CircleD getCircumCircleD() { + VecD3D cr = a.bisect(b).cross(b.bisect(c)); + VecD2D circ = new VecD2D(cr.x / cr.z, cr.y / cr.z); + double sa = a.distanceTo(b); + double sb = b.distanceTo(c); + double sc = c.distanceTo(a); + double radius = sa + * sb + * sc + / Math.sqrt((sa + sb + sc) * (-sa + sb + sc) + * (sa - sb + sc) * (sa + sb - sc)); + return new CircleD(circ, radius); + } + + public double getCircumference() { + return a.distanceTo(b) + b.distanceTo(c) + c.distanceTo(a); + } + + /** + * Finds and returns the closest point on any of the triangle edges to the + * point given. + * + * @param p + * point to check + * @return closest point + */ + public VecD2D getClosestPointTo(ReadonlyVecD2D p) { + LineD2D edge = new LineD2D(a, b); + VecD2D Rab = edge.closestPointTo(p); + VecD2D Rbc = edge.set(b, c).closestPointTo(p); + VecD2D Rca = edge.set(c, a).closestPointTo(p); + + double dAB = p.sub(Rab).magSquared(); + double dBC = p.sub(Rbc).magSquared(); + double dCA = p.sub(Rca).magSquared(); + + double min = dAB; + VecD2D result = Rab; + + if (dBC < min) { + min = dBC; + result = Rbc; + } + if (dCA < min) { + result = Rca; + } + + return result; + } + + public List getEdges() { + return toPolygonD2D().getEdges(); + } + + /** + * Creates a random point within the triangle using barycentric coordinates. + * + * @return VecD2D + */ + public VecD2D getRandomPoint() { + List barycentric = new ArrayList(3); + barycentric.add(MathUtils.random(1d)); + barycentric.add(MathUtils.random(1d - barycentric.get(0))); + barycentric.add(1 - (barycentric.get(0) + barycentric.get(1))); + Collections.shuffle(barycentric); + return fromBarycentric(new VecD3D(barycentric.get(0), + barycentric.get(1), barycentric.get(2))); + } + + public VecD2D[] getVertexArray() { + return getVertexArray(null, 0); + } + + public VecD2D[] getVertexArray(VecD2D[] array, int offset) { + if (array == null) { + array = new VecD2D[3]; + } + array[offset++] = a; + array[offset++] = b; + array[offset] = c; + return array; + } + + /** + * Checks if this triangle intersects the given one. The check handles both + * partial and total containment as well as intersections of all edges. + * + * @param tri + * @return true, if intersecting + */ + public boolean intersectsTriangle(TriangleD2D tri) { + if (containsPoint(tri.a) || containsPoint(tri.b) + || containsPoint(tri.c)) { + return true; + } + if (tri.containsPoint(a) || tri.containsPoint(b) + || tri.containsPoint(c)) { + return true; + } + LineD2D[] ea = new LineD2D[] { + new LineD2D(a, b), new LineD2D(b, c), new LineD2D(c, a) + }; + LineD2D[] eb = new LineD2D[] { + new LineD2D(tri.a, tri.b), new LineD2D(tri.b, tri.c), + new LineD2D(tri.c, tri.a) + }; + for (LineD2D la : ea) { + for (LineD2D lb : eb) { + Type type = la.intersectLine(lb).getType(); + if (type != Type.NON_INTERSECTING && type != Type.PARALLEL) { + return true; + } + } + } + return false; + } + + public boolean isClockwise() { + return TriangleD2D.isClockwise(a, b, c); + } + + public void set(VecD2D a2, VecD2D b2, VecD2D c2) { + a = a2; + b = b2; + c = c2; + } + + /** + * Produces the barycentric coordinates of the given point within this + * triangle. These coordinates can then be used to re-project the point into + * a different triangle using its {@link #fromBarycentric(ReadonlyVecD3D)} + * method. + * + * @param p + * point in world space + * @return barycentric coords as {@link VecD3D} + */ + public VecD3D toBarycentric(ReadonlyVecD2D p) { + return new TriangleD3D(a.toD3DXY(), b.toD3DXY(), c.toD3DXY()) + .toBarycentric(p.toD3DXY()); + } + + /** + * Creates a {@link Polygon2D} instance of the triangle. The vertices of + * this polygon are disconnected from the ones defining this triangle. + * + * @return triangle as polygon + */ + public PolygonD2D toPolygonD2D() { + PolygonD2D poly = new PolygonD2D(); + poly.add(a.copy()); + poly.add(b.copy()); + poly.add(c.copy()); + return poly; + } + + public String toString() { + return "Triangle2D: " + a + "," + b + "," + c; + } +} diff --git a/src.core/toxi/geom/TriangleD3D.java b/src.core/toxi/geom/TriangleD3D.java new file mode 100644 index 00000000..e5145170 --- /dev/null +++ b/src.core/toxi/geom/TriangleD3D.java @@ -0,0 +1,336 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; +import toxi.geom.VecD3D; + +import toxi.math.MathUtils; + +@XmlAccessorType(XmlAccessType.FIELD) +public class TriangleD3D implements ShapeD3D { + + public static TriangleD3D createEquilateralFrom(VecD3D a, VecD3D b) { + VecD3D c = a.interpolateTo(b, 0.5f); + VecD3D dir = b.sub(a); + VecD3D n = a.cross(dir.normalize()); + c.addSelf(n.normalizeTo(dir.magnitude() * MathUtils.SQRT3 / 2)); + return new TriangleD3D(a, b, c); + } + + public static boolean isClockwiseInXY(VecD3D a, VecD3D b, VecD3D c) { + double determ = (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y); + return (determ < 0.0); + } + + public static boolean isClockwiseInXZ(VecD3D a, VecD3D b, VecD3D c) { + double determ = (b.x - a.x) * (c.z - a.z) - (c.x - a.x) * (b.z - a.z); + return (determ < 0.0); + } + + public static boolean isClockwiseInYZ(VecD3D a, VecD3D b, VecD3D c) { + double determ = (b.y - a.y) * (c.z - a.z) - (c.y - a.y) * (b.z - a.z); + return (determ < 0.0); + } + + @XmlElement(required = true) + public VecD3D a, b, c; + + @XmlElement(required = true) + public VecD3D normal; + + @XmlTransient + public VecD3D centroid; + + public TriangleD3D() { + } + + public TriangleD3D(VecD3D a, VecD3D b, VecD3D c) { + this.a = a; + this.b = b; + this.c = c; + } + public TriangleD3D(Triangle3D t) { + this.a = new VecD3D(t.a); + this.b = new VecD3D(t.a); + this.c = new VecD3D(t.a); + } + + /** + * Computes the the point closest to the current vector on the surface of + * triangle abc. + * + * From Real-Time Collision Detection by Christer Ericson, published by + * Morgan Kaufmann Publishers, Copyright 2005 Elsevier Inc + * + * @return closest point on triangle (result may also be one of a, b or c) + */ + public VecD3D closestPointOnSurface(VecD3D p) { + VecD3D ab = b.sub(a); + VecD3D ac = c.sub(a); + VecD3D bc = c.sub(b); + + ReadonlyVecD3D pa = p.sub(a); + ReadonlyVecD3D pb = p.sub(b); + ReadonlyVecD3D pc = p.sub(c); + + VecD3D ap = a.sub(p); + VecD3D bp = b.sub(p); + VecD3D cp = c.sub(p); + + // Compute parametric position s for projection P' of P on AB, + // P' = A + s*AB, s = snom/(snom+sdenom) + double snom = pa.dot(ab); + + // Compute parametric position t for projection P' of P on AC, + // P' = A + t*AC, s = tnom/(tnom+tdenom) + double tnom = pa.dot(ac); + + if (snom <= 0.0f && tnom <= 0.0f) { + return a; // Vertex region early out + } + + double sdenom = pb.dot(a.sub(b)); + double tdenom = pc.dot(a.sub(c)); + + // Compute parametric position u for projection P' of P on BC, + // P' = B + u*BC, u = unom/(unom+udenom) + double unom = pb.dot(bc); + double udenom = pc.dot(b.sub(c)); + + if (sdenom <= 0.0f && unom <= 0.0f) { + return b; // Vertex region early out + } + if (tdenom <= 0.0f && udenom <= 0.0f) { + return c; // Vertex region early out + } + + // P is outside (or on) AB if the triple scalar product [N PA PB] <= 0 + ReadonlyVecD3D n = ab.cross(ac); + double vc = n.dot(ap.crossSelf(bp)); + + // If P outside AB and within feature region of AB, + // return projection of P onto AB + if (vc <= 0.0f && snom >= 0.0f && sdenom >= 0.0f) { + // return a + snom / (snom + sdenom) * ab; + return a.add(ab.scaleSelf(snom / (snom + sdenom))); + } + + // P is outside (or on) BC if the triple scalar product [N PB PC] <= 0 + double va = n.dot(bp.crossSelf(cp)); + // If P outside BC and within feature region of BC, + // return projection of P onto BC + if (va <= 0.0f && unom >= 0.0f && udenom >= 0.0f) { + // return b + unom / (unom + udenom) * bc; + return b.add(bc.scaleSelf(unom / (unom + udenom))); + } + + // P is outside (or on) CA if the triple scalar product [N PC PA] <= 0 + double vb = n.dot(cp.crossSelf(ap)); + // If P outside CA and within feature region of CA, + // return projection of P onto CA + if (vb <= 0.0f && tnom >= 0.0f && tdenom >= 0.0f) { + // return a + tnom / (tnom + tdenom) * ac; + return a.add(ac.scaleSelf(tnom / (tnom + tdenom))); + } + + // P must project inside face region. Compute Q using barycentric + // coordinates + double u = va / (va + vb + vc); + double v = vb / (va + vb + vc); + double w = 1.0f - u - v; // = vc / (va + vb + vc) + // return u * a + v * b + w * c; + return a.scale(u).addSelf(b.scale(v)).addSelf(c.scale(w)); + } + + public VecD3D computeCentroid() { + centroid = a.add(b).addSelf(c).scaleSelf(1f / 3); + return centroid; + } + + public VecD3D computeNormal() { + normal = a.sub(c).crossSelf(a.sub(b)).normalize(); + return normal; + } + + /** + * Checks if point vector is inside the triangle created by the points a, b + * and c. These points will create a plane and the point checked will have + * to be on this plane in the region between a,b,c (triangle vertices + * inclusive). + * + * @return true, if point is in triangle. + */ + public boolean containsPoint(ReadonlyVecD3D p) { + VecD3D v0 = c.sub(a); + VecD3D v1 = b.sub(a); + VecD3D v2 = p.sub(a); + + // Compute dot products + double dot00 = v0.dot(v0); + double dot01 = v0.dot(v1); + double dot02 = v0.dot(v2); + double dot11 = v1.dot(v1); + double dot12 = v1.dot(v2); + + // Compute barycentric coordinates + double invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + double u = (dot11 * dot02 - dot01 * dot12) * invDenom; + double v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // Check if point is in triangle + return (u >= 0.0) && (v >= 0.0) && (u + v <= 1.0); + } + + public TriangleD3D flipVertexOrder() { + VecD3D t = a; + a = c; + c = t; + return this; + } + + public VecD3D fromBarycentric(ReadonlyVecD3D p) { + return new VecD3D(a.x * p.x() + b.x * p.y() + c.x * p.z(), a.y * p.x() + + b.y * p.y() + c.y * p.z(), a.z * p.x() + b.z * p.y() + c.z + * p.z()); + } + + public AABBD getBoundingBox() { + VecD3D min = VecD3D.min(VecD3D.min(a, b), c); + VecD3D max = VecD3D.max(VecD3D.max(a, b), c); + return AABBD.fromMinMax(min, max); + } + + /** + * Finds and returns the closest point on any of the triangle edges to the + * point given. + * + * @param p + * point to check + * @return closest point + */ + + public VecD3D getClosestPointTo(ReadonlyVecD3D p) { + LineD3D edge = new LineD3D(a, b); + final VecD3D Rab = edge.closestPointTo(p); + final VecD3D Rbc = edge.set(b, c).closestPointTo(p); + final VecD3D Rca = edge.set(c, a).closestPointTo(p); + + final double dAB = p.sub(Rab).magSquared(); + final double dBC = p.sub(Rbc).magSquared(); + final double dCA = p.sub(Rca).magSquared(); + + double min = dAB; + VecD3D result = Rab; + + if (dBC < min) { + min = dBC; + result = Rbc; + } + if (dCA < min) { + result = Rca; + } + + return result; + } + + public VecD3D[] getVertexArray() { + return getVertexArray(null, 0); + } + + public VecD3D[] getVertexArray(VecD3D[] array, int offset) { + if (array == null) { + array = new VecD3D[3]; + } + array[offset++] = a; + array[offset++] = b; + array[offset] = c; + return array; + } + + public boolean isClockwiseInXY() { + return TriangleD3D.isClockwiseInXY(a, b, c); + } + + public boolean isClockwiseInXZ() { + return TriangleD3D.isClockwiseInXY(a, b, c); + } + + public boolean isClockwiseInYZ() { + return TriangleD3D.isClockwiseInXY(a, b, c); + } + + private boolean isSameClockDir(VecD3D a, VecD3D b, ReadonlyVecD3D p, VecD3D norm) { + double bax = b.x - a.x; + double bay = b.y - a.y; + double baz = b.z - a.z; + double pax = p.x() - a.x; + double pay = p.y() - a.y; + double paz = p.z() - a.z; + double nx = bay * paz - pay * baz; + double ny = baz * pax - paz * bax; + double nz = bax * pay - pax * bay; + double dotprod = nx * norm.x + ny * norm.y + nz * norm.z; + return dotprod < 0; + } + + public void set(VecD3D a2, VecD3D b2, VecD3D c2) { + a = a2; + b = b2; + c = c2; + } + + public VecD3D toBarycentric(ReadonlyVecD3D p) { + VecD3D e = b.sub(a).cross(c.sub(a)); + VecD3D n = e.getNormalized(); + + // Compute twice area of triangle ABC + double areaABC = n.dot(e); + // Compute lambda1 + double areaPBC = n.dot(b.sub(p).cross(c.sub(p))); + double l1 = areaPBC / areaABC; + + // Compute lambda2 + double areaPCA = n.dot(c.sub(p).cross(a.sub(p))); + double l2 = areaPCA / areaABC; + + // Compute lambda3 + double l3 = 1.0f - l1 - l2; + + return new VecD3D(l1, l2, l3); + // return new VecD3D(a.x * l1 + b.x * l2 + c.x * l3, a.y * l1 + b.y * l2 + // + c.y * l3, a.z * l1 + b.z * l2 + c.z * l3); + } + + public String toString() { + return "Triangle3D: " + a + "," + b + "," + c; + } +} diff --git a/src.core/toxi/geom/TriangleDIntersector.java b/src.core/toxi/geom/TriangleDIntersector.java new file mode 100644 index 00000000..42e554cc --- /dev/null +++ b/src.core/toxi/geom/TriangleDIntersector.java @@ -0,0 +1,88 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +public class TriangleDIntersector implements IntersectorD3D { + + public TriangleD3D triangle; + private IsectDataD3D isectData; + + public TriangleDIntersector() { + this(new TriangleD3D()); + } + + public TriangleDIntersector(TriangleD3D t) { + this.triangle = t; + this.isectData = new IsectDataD3D(); + } + + public IsectDataD3D getIntersectionDataD() { + return isectData; + } + + /** + * @return the triangle + */ + public TriangleD3D getTriangleD() { + return triangle; + } + + public boolean intersectsRayD(RayD3D ray) { + isectData.isIntersection = false; + VecD3D n = triangle.computeNormal(); + double dotprod = n.dot(ray.dir); + if (dotprod < 0) { + VecD3D rt = ray.sub(triangle.a); + double t = -(double) (n.x * rt.x + n.y * rt.y + n.z * rt.z) + / (n.x * ray.dir.x + n.y * ray.dir.y + n.z * ray.dir.z); + if (t >= MathUtils.EPS) { + VecD3D pos = ray.getPointAtDistance(t); + // check if pos is inside triangle + if (triangle.containsPoint(pos)) { + isectData.isIntersection = true; + isectData.pos = pos; + isectData.normal = n; + isectData.dist = t; + isectData.dir = isectData.pos.sub(ray).normalize(); + } + } + } + return isectData.isIntersection; + } + + /** + * @param tri + * the triangle to set + */ + public TriangleDIntersector setTriangleD(TriangleD3D tri) { + this.triangle = tri; + return this; + } +} diff --git a/src.core/toxi/geom/Vec2D.java b/src.core/toxi/geom/Vec2D.java index 90231bd3..0cc9d095 100644 --- a/src.core/toxi/geom/Vec2D.java +++ b/src.core/toxi/geom/Vec2D.java @@ -922,6 +922,10 @@ public final Vec2D set(float x, float y) { this.y = y; return this; } + public Vec2D(VecD2D v) { + this.x = (float)v.x; + this.y = (float)v.y; + } public final Vec2D set(ReadonlyVec2D v) { x = v.x(); diff --git a/src.core/toxi/geom/Vec3D.java b/src.core/toxi/geom/Vec3D.java index 9436a8ec..9cdbab97 100644 --- a/src.core/toxi/geom/Vec3D.java +++ b/src.core/toxi/geom/Vec3D.java @@ -87,6 +87,7 @@ public ReadonlyVec3D getVector() { public static final ReadonlyVec3D NEG_MAX_VALUE = new Vec3D( -Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE); + /** * Creates a new vector from the given angle in the XY plane. The Z * component of the vector will be zero. @@ -209,6 +210,11 @@ public static final Vec3D randomVector(Random rnd) { public Vec3D() { } + public Vec3D(VecD3D v) { + this.x = (float)v.x; + this.y = (float)v.y; + this.z = (float)v.z; + } /** * Creates a new vector with the given coordinates. * diff --git a/src.core/toxi/geom/Vec4D.java b/src.core/toxi/geom/Vec4D.java index afa9b6ab..c1bc63ad 100644 --- a/src.core/toxi/geom/Vec4D.java +++ b/src.core/toxi/geom/Vec4D.java @@ -60,6 +60,12 @@ public Vec4D(float x, float y, float z, float w) { this.z = z; this.w = w; } + public Vec4D(VecD4D v) { + this.x = (float)v.x; + this.y = (float)v.y; + this.z = (float)v.z; + this.w = (float)v.w; + } public Vec4D(ReadonlyVec3D v, float w) { this.x = v.x(); diff --git a/src.core/toxi/geom/VecD2D.java b/src.core/toxi/geom/VecD2D.java new file mode 100644 index 00000000..79d3a9d4 --- /dev/null +++ b/src.core/toxi/geom/VecD2D.java @@ -0,0 +1,1778 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.Random; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; + +/** + * Comprehensive 2D vector class with additional basic intersection and + * collision detection features. + */ +public class VecD2D implements Comparable, ReadonlyVecD2D { + + public static enum AxisD { + + X(VecD2D.X_AXIS), + Y(VecD2D.Y_AXIS); + + private final ReadonlyVecD2D vector; + + private AxisD(ReadonlyVecD2D v) { + this.vector = v; + } + + public ReadonlyVecD2D getVector() { + return vector; + } + } + + public VecD2D VecD2D(Vec2D v) { + x=(double)v.x; + y=(double)v.y; + return this; + } + public VecD2D(Vec2D v) { + this.x = v.x; + this.y = v.y; + } + + /** + * Defines positive X axis + */ + public static final ReadonlyVecD2D X_AXIS = new VecD2D(1, 0); + + /** + * Defines positive Y axis + */ + public static final ReadonlyVecD2D Y_AXIS = new VecD2D(0, 1);; + + /** Defines the zero vector. */ + public static final ReadonlyVecD2D ZERO = new VecD2D(); + + /** + * Defines vector with both coords set to Float.MIN_VALUE. Useful for + * bounding box operations. + */ + public static final ReadonlyVecD2D MIN_VALUE = new VecD2D(Double.MIN_VALUE, + Double.MIN_VALUE); + + /** + * Defines vector with both coords set to Float.MAX_VALUE. Useful for + * bounding box operations. + */ + public static final ReadonlyVecD2D MAX_VALUE = new VecD2D(Double.MAX_VALUE, + Double.MAX_VALUE); + + public static final ReadonlyVecD2D NEG_MAX_VALUE = new VecD2D( + -Double.MAX_VALUE, -Double.MAX_VALUE); + + /** + * Creates a new vector from the given angle in the XY plane. + * + * The resulting vector for theta=0 is equal to the positive X axis. + * + * @param theta + * @return new vector pointing into the direction of the passed in angle + */ + public static final VecD2D fromTheta(float theta) { + return new VecD2D(Math.cos(theta), Math.sin(theta)); + } + public static final VecD2D fromTheta(double theta) { + return new VecD2D(Math.cos(theta), Math.sin(theta)); + } + + /** + * Constructs a new vector consisting of the largest components of both + * vectors. + * + * @param b + * the b + * @param a + * the a + * + * @return result as new vector + */ + public static final VecD2D max(ReadonlyVec2D a, ReadonlyVec2D b) { + return new VecD2D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y())); + } + public static final VecD2D max(ReadonlyVecD2D a, ReadonlyVec2D b) { + return new VecD2D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y())); + } + public static final VecD2D max(ReadonlyVec2D a, ReadonlyVecD2D b) { + return new VecD2D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y())); + } + public static final VecD2D max(ReadonlyVecD2D a, ReadonlyVecD2D b) { + return new VecD2D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y())); + } + + /** + * Constructs a new vector consisting of the smallest components of both + * vectors. + * + * @param b + * comparing vector + * @param a + * the a + * + * @return result as new vector + */ + public static final VecD2D min(ReadonlyVec2D a, ReadonlyVec2D b) { + return new VecD2D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y())); + } + public static final VecD2D min(ReadonlyVecD2D a, ReadonlyVec2D b) { + return new VecD2D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y())); + } + public static final VecD2D min(ReadonlyVec2D a, ReadonlyVecD2D b) { + return new VecD2D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y())); + } + public static final VecD2D min(ReadonlyVecD2D a, ReadonlyVecD2D b) { + return new VecD2D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y())); + } + + /** + * Static factory method. Creates a new random unit vector using the Random + * implementation set as default for the {@link MathUtils} class. + * + * @return a new random normalized unit vector. + */ + public static final VecD2D randomVector() { + return randomVector(MathUtils.RND); + } + + /** + * Static factory method. Creates a new random unit vector using the given + * Random generator instance. I recommend to have a look at the + * https://uncommons-maths.dev.java.net library for a good choice of + * reliable and high quality random number generators. + * + * @return a new random normalized unit vector. + */ + public static final VecD2D randomVector(Random rnd) { + VecD2D v = new VecD2D(rnd.nextDouble() * 2 - 1, rnd.nextDouble() * 2 - 1); + return v.normalize(); + } + + /** + * X coordinate + */ + @XmlAttribute(required = true) + public double x; + + /** + * Y coordinate + */ + @XmlAttribute(required = true) + public double y; + + /** + * Creates a new zero vector + */ + public VecD2D() { + x = y = 0; + } + + /** + * Creates a new vector with the given coordinates + * + * @param x + * @param y + */ + public VecD2D(float x, float y) { + this.x = (double)x; + this.y = (double)y; + } + public VecD2D(double x, float y) { + this.x = x; + this.y = (double)y; + } + public VecD2D(float x, double y) { + this.x = (double)x; + this.y = y; + } + public VecD2D(double x, double y) { + this.x = x; + this.y = y; + } + + public VecD2D(float[] v) { + this.x = (double)v[0]; + this.y = (double)v[1]; + } + public VecD2D(double[] v) { + this.x = v[0]; + this.y = v[1]; + } + + /** + * Creates a new vector with the coordinates of the given vector + * + * @param v + * vector to be copied + */ + public VecD2D(ReadonlyVec2D v) { + this.x = (double)v.x(); + this.y = (double)v.y(); + } + public VecD2D(ReadonlyVecD2D v) { + this.x = v.x(); + this.y = v.y(); + } + + public final VecD2D abs() { + x = MathUtils.abs(x); + y = MathUtils.abs(y); + return this; + } + + public final VecD2D add(float a, float b) { + return new VecD2D(x + a, y + b); + } + public final VecD2D add(double a, float b) { + return new VecD2D(x + a, y + b); + } + public final VecD2D add(float a, double b) { + return new VecD2D(x + a, y + b); + } + public final VecD2D add(double a, double b) { + return new VecD2D(x + a, y + b); + } + + public VecD2D add(ReadonlyVec2D v) { + return new VecD2D(x + (double)v.x(), y + (double)v.y()); + } + public VecD2D add(ReadonlyVecD2D v) { + return new VecD2D(x + v.x(), y + v.y()); + } + + public final VecD2D add(Vec2D v) { + return new VecD2D(x + v.x, y + v.y); + } + public final VecD2D add(VecD2D v) { + return new VecD2D(x + v.x, y + v.y); + } + + /** + * Adds vector {a,b,c} and overrides coordinates with result. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @return itself + */ + public final VecD2D addSelf(float a, float b) { + x += (double)a; + y += (double)b; + return this; + } + public final VecD2D addSelf(double a, float b) { + x += a; + y += (double)b; + return this; + } + public final VecD2D addSelf(float a, double b) { + x += (double)a; + y += b; + return this; + } + public final VecD2D addSelf(double a, double b) { + x += a; + y += b; + return this; + } + + /** + * Adds vector v and overrides coordinates with result. + * + * @param v + * vector to add + * @return itself + */ + public final VecD2D addSelf(Vec2D v) { + x +=(double) v.x; + y +=(double) v.y; + return this; + } + public final VecD2D addSelf(VecD2D v) { + x += v.x; + y += v.y; + return this; + } + + public final double angleBetween(ReadonlyVec2D v) { + return Math.acos(dot(v)); + } + public final double angleBetween(ReadonlyVecD2D v) { + return Math.acos(dot(v)); + } + + public final double angleBetween(ReadonlyVec2D v, boolean forceNormalize) { + double theta; + if (forceNormalize) { + theta = getNormalized().dot(v.getNormalized()); + } else { + theta = dot(v); + } + return Math.acos(MathUtils.clipNormalized(theta)); + } + public final double angleBetween(ReadonlyVecD2D v, boolean forceNormalize) { + double theta; + if (forceNormalize) { + theta = getNormalized().dot(v.getNormalized()); + } else { + theta = dot(v); + } + return Math.acos(MathUtils.clipNormalized(theta)); + } + + public VecD3D bisect(Vec2D b) { + VecD2D diff = this.sub(b); + VecD2D sum = this.add(b); + double dot = diff.dot(sum); + return new VecD3D(diff.x, diff.y, -dot / 2); + } + public VecD3D bisect(VecD2D b) { + VecD2D diff = this.sub(b); + VecD2D sum = this.add(b); + double dot = diff.dot(sum); + return new VecD3D(diff.x, diff.y, -dot / 2); + } + + /** + * Sets all vector components to 0. + * + * @return itself + */ + public final VecD2D clear() { + x = y = 0; + return this; + } + + public int compareTo(ReadonlyVec2D v) { + if (x == (double) v.x() && y == (double) v.y()) { + return 0; + } + double a = magSquared(); + double b = v.magSquared(); + if (a < b) { + return -1; + } + return +1; + } + public int compareTo(ReadonlyVecD2D v) { + if (x == v.x() && y == v.y()) { + return 0; + } + double a = magSquared(); + double b = v.magSquared(); + if (a < b) { + return -1; + } + return +1; + } + + /** + * Constraints this vector to the perimeter of the given polygon. Unlike the + * {@link #constrain(Rect)} version of this method, this version DOES NOT + * check containment automatically. If you want to only constrain a point if + * its (for example) outside the polygon, then check containment with + * {@link Polygon2D#containsPoint(ReadonlyVec2D)} first before calling this + * method. + * + * @param poly + * @return itself + */ + public VecD2D constrain(PolygonD2D poly) { + double minD = Double.MAX_VALUE; + VecD2D q = null; + for (LineD2D l : poly.getEdges()) { + VecD2D c = l.closestPointTo(this); + double d = c.distanceToSquared(this); + if (d < minD) { + q = c; + minD = d; + } + } + x = q.x; + y = q.y; + return this; + } + + /** + * Forcefully fits the vector in the given rectangle. + * + * @param r + * @return itself + */ + public VecD2D constrain(Rect r) { + x = MathUtils.clip(x, r.x, r.x + r.width); + y = MathUtils.clip(y, r.y, r.y + r.height); + return this; + } + public VecD2D constrain(RectD r) { + x = MathUtils.clip(x, r.x, r.x + r.width); + y = MathUtils.clip(y, r.y, r.y + r.height); + return this; + } + + /** + * Forcefully fits the vector in the given rectangle defined by the points. + * + * @param min + * @param max + * @return itself + */ + public VecD2D constrain(Vec2D min, Vec2D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + return this; + } + public VecD2D constrain(VecD2D min, Vec2D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + return this; + } + public VecD2D constrain(Vec2D min, VecD2D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + return this; + } + public VecD2D constrain(VecD2D min, VecD2D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + return this; + } + + public final VecD2D copy() { + return new VecD2D(this); + } + + public double cross(ReadonlyVec2D v) { + return (x * v.y()) - (y * v.x()); + } + public double cross(ReadonlyVecD2D v) { + return (x * v.y()) - (y * v.x()); + } + + public final double distanceTo(ReadonlyVec2D v) { + if (v != null) { + double dx = x - v.x(); + double dy = y - v.y(); + return Math.sqrt(dx * dx + dy * dy); + } else { + return Double.NaN; + } + } + public final double distanceTo(ReadonlyVecD2D v) { + if (v != null) { + double dx = x - v.x(); + double dy = y - v.y(); + return Math.sqrt(dx * dx + dy * dy); + } else { + return Double.NaN; + } + } + + + public final double distanceToSquared(ReadonlyVec2D v) { + if (v != null) { + double dx = x - v.x(); + double dy = y - v.y(); + return dx * dx + dy * dy; + } else { + return Double.NaN; + } + } + public final double distanceToSquared(ReadonlyVecD2D v) { + if (v != null) { + double dx = x - v.x(); + double dy = y - v.y(); + return dx * dx + dy * dy; + } else { + return Double.NaN; + } + } + + public final double dot(ReadonlyVec2D v) { + return x * v.x() + y * v.y(); + } + public final double dot(ReadonlyVecD2D v) { + return x * v.x() + y * v.y(); + } + + /** + * Returns true if the Object v is of type ReadonlyVec2D and all of the data + * members of v are equal to the corresponding data members in this vector. + * + * @param v + * the object with which the comparison is made + * @return true or false + */ + public boolean equals(Object v) { + try { + ReadonlyVecD2D vv = (ReadonlyVecD2D) v; + return (x == vv.x() && y == vv.y()); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + + } + + /** + * Returns true if all of the data members of ReadonlyVec2D v are equal to + * the corresponding data members in this vector. + * + * @param v + * the vector with which the comparison is made + * @return true or false + */ + public boolean equals(ReadonlyVec2D v) { + try { + return (x ==(double) v.x() && y == (double)v.y()); + } catch (NullPointerException e) { + return false; + } + } + public boolean equals(ReadonlyVecD2D v) { + try { + return (x == v.x() && y == v.y()); + } catch (NullPointerException e) { + return false; + } + } + + public boolean equalsWithTolerance(ReadonlyVec2D v, float tolerance) { + try { + double diff = x - v.x(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = y - v.y(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + return true; + } catch (NullPointerException e) { + return false; + } + } + public boolean equalsWithTolerance(ReadonlyVecD2D v, float tolerance) { + try { + double diff = x - v.x(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = y - v.y(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + return true; + } catch (NullPointerException e) { + return false; + } + } + public boolean equalsWithTolerance(ReadonlyVec2D v, double tolerance) { + try { + double diff = x - v.x(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = y - v.y(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + return true; + } catch (NullPointerException e) { + return false; + } + } + public boolean equalsWithTolerance(ReadonlyVecD2D v, double tolerance) { + try { + double diff = x - v.x(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = y - v.y(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + return true; + } catch (NullPointerException e) { + return false; + } + } + + /** + * Replaces the vector components with integer values of their current + * values + * + * @return itself + */ + public final VecD2D floor() { + x = MathUtils.floor(x); + y = MathUtils.floor(y); + return this; + } + + /** + * Replaces the vector components with the fractional part of their current + * values + * + * @return itself + */ + public final VecD2D frac() { + x -= MathUtils.floor(x); + y -= MathUtils.floor(y); + return this; + } + + public final VecD2D getAbs() { + return new VecD2D(this).abs(); + } + + public VecD2D getCartesian() { + return copy().toCartesian(); + } + + public double getComponent(AxisD id) { + switch (id) { + case X: + return x; + case Y: + return y; + } + return 0; + } + + public final double getComponent(int id) { + switch (id) { + case 0: + return x; + case 1: + return y; + } + throw new IllegalArgumentException("index must be 0 or 1"); + } + + public final VecD2D getConstrained(PolygonD2D poly) { + return new VecD2D(this).constrain(poly); + } + + public final VecD2D getConstrained(Rect r) { + return new VecD2D(this).constrain(r); + } + public final VecD2D getConstrained(RectD r) { + return new VecD2D(this).constrain(r); + } + + public final VecD2D getFloored() { + return new VecD2D(this).floor(); + } + + public final VecD2D getFrac() { + return new VecD2D(this).frac(); + } + + public final VecD2D getInverted() { + return new VecD2D(-x, -y); + } + + public final VecD2D getLimited(float lim) { + if (magSquared() > lim * lim) { + return getNormalizedTo(lim); + } + return new VecD2D(this); + } + public final VecD2D getLimited(double lim) { + if (magSquared() > lim * lim) { + return getNormalizedTo(lim); + } + return new VecD2D(this); + } + + public VecD2D getMapped(ScaleMap map) { + return new VecD2D(map.getClippedValueFor(x),map.getClippedValueFor(y)); + } + + public final VecD2D getNormalized() { + return new VecD2D(this).normalize(); + } + + public final VecD2D getNormalizedTo(float len) { + return new VecD2D(this).normalizeTo(len); + } + public final VecD2D getNormalizedTo(double len) { + return new VecD2D(this).normalizeTo(len); + } + + public final VecD2D getPerpendicular() { + return new VecD2D(this).perpendicular(); + } + + public VecD2D getPolar() { + return copy().toPolar(); + } + + public final VecD2D getReciprocal() { + return copy().reciprocal(); + } + + public final VecD2D getReflected(ReadonlyVec2D normal) { + return copy().reflect(normal); + } + public final VecD2D getReflected(ReadonlyVecD2D normal) { + return copy().reflect(normal); + } + + public final VecD2D getRotated(float theta) { + return new VecD2D(this).rotate(theta); + } + public final VecD2D getRotated(double theta) { + return new VecD2D(this).rotate(theta); + } + + public VecD2D getRoundedTo(float prec) { + return copy().roundTo(prec); + } + public VecD2D getRoundedTo(double prec) { + return copy().roundTo(prec); + } + + public VecD2D getSignum() { + return new VecD2D(this).signum(); + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different VecD2D objects with identical data values (i.e., VecD2D.equals + * returns true) will return the same hash code value. Two objects with + * different data members may return the same hash value, although this is + * not likely. + * + * @return the hash code value of this vector. + */ + public int hashCode() { + return ((Double)x).hashCode()+((Double)y).hashCode(); + } + + public final double heading() { + return Math.atan2(y, x); + } + + public VecD2D interpolateTo(ReadonlyVec2D v, float f) { + return new VecD2D(x + (v.x() - x) * f, y + (v.y() - y) * f); + } + public VecD2D interpolateTo(ReadonlyVecD2D v, float f) { + return new VecD2D(x + (v.x() - x) * f, y + (v.y() - y) * f); + } + public VecD2D interpolateTo(ReadonlyVec2D v, double f) { + return new VecD2D(x + (v.x() - x) * f, y + (v.y() - y) * f); + } + public VecD2D interpolateTo(ReadonlyVecD2D v, double f) { + return new VecD2D(x + (v.x() - x) * f, y + (v.y() - y) * f); + } + + + public VecD2D interpolateTo(ReadonlyVec2D v, float f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f)); + } + public VecD2D interpolateTo(ReadonlyVecD2D v, float f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f)); + } + public VecD2D interpolateTo(ReadonlyVec2D v, double f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f)); + } + public VecD2D interpolateTo(ReadonlyVecD2D v, double f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f)); + } + + public final VecD2D interpolateTo(Vec2D v, float f) { + return new VecD2D(x + (v.x - x) * f, y + (v.y - y) * f); + } + public final VecD2D interpolateTo(VecD2D v, float f) { + return new VecD2D(x + (v.x - x) * f, y + (v.y - y) * f); + } + public final VecD2D interpolateTo(Vec2D v, double f) { + return new VecD2D(x + (v.x - x) * f, y + (v.y - y) * f); + } + public final VecD2D interpolateTo(VecD2D v, double f) { + return new VecD2D(x + (v.x - x) * f, y + (v.y - y) * f); + } + + public VecD2D interpolateTo(Vec2D v, float f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f)); + } + public VecD2D interpolateTo(VecD2D v, float f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f)); + } + public VecD2D interpolateTo(Vec2D v, double f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f)); + } + public VecD2D interpolateTo(VecD2D v, double f, InterpolateStrategy s) { + return new VecD2D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f)); + } + + /** + * Interpolates the vector towards the given target vector, using linear + * interpolation + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * @return itself, result overrides current vector + */ + public final VecD2D interpolateToSelf(ReadonlyVec2D v, float f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + return this; + } + public final VecD2D interpolateToSelf(ReadonlyVecD2D v, float f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + return this; + } + public final VecD2D interpolateToSelf(ReadonlyVec2D v, double f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + return this; + } + public final VecD2D interpolateToSelf(ReadonlyVecD2D v, double f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + return this; + } + + /** + * Interpolates the vector towards the given target vector, using the given + * {@link InterpolateStrategy} + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * @param s + * InterpolateStrategy instance + * @return itself, result overrides current vector + */ + public VecD2D interpolateToSelf(ReadonlyVec2D v, float f,InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + return this; + } + public VecD2D interpolateToSelf(ReadonlyVecD2D v, float f,InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + return this; + } + public VecD2D interpolateToSelf(ReadonlyVec2D v, double f,InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + return this; + } + public VecD2D interpolateToSelf(ReadonlyVecD2D v, double f,InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + return this; + } + + /** + * Scales vector uniformly by factor -1 ( v = -v ), overrides coordinates + * with result + * + * @return itself + */ + public final VecD2D invert() { + x *= -1; + y *= -1; + return this; + } + + public boolean isInCircleD(ReadonlyVec2D sO, float sR) { + double d = sub(sO).magSquared(); + return (d <= sR * sR); + } + public boolean isInCircleD(ReadonlyVecD2D sO, float sR) { + double d = sub(sO).magSquared(); + return (d <= sR * sR); + } + public boolean isInCircleD(ReadonlyVec2D sO, double sR) { + double d = sub(sO).magSquared(); + return (d <= sR * sR); + } + public boolean isInCircleD(ReadonlyVecD2D sO, double sR) { + double d = sub(sO).magSquared(); + return (d <= sR * sR); + } + + public boolean isInRectangle(Rect r) { + if (x < r.x || x > r.x + r.width) { + return false; + } + if (y < r.y || y > r.y + r.height) { + return false; + } + return true; + } + public boolean isInRectangle(RectD r) { + if (x < r.x || x > r.x + r.width) { + return false; + } + if (y < r.y || y > r.y + r.height) { + return false; + } + return true; + } + + public boolean isInTriangleD(Vec2D a, Vec2D b, Vec2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + public boolean isInTriangleD(VecD2D a, Vec2D b, Vec2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + public boolean isInTriangleD(Vec2D a, VecD2D b, Vec2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + public boolean isInTriangleD(Vec2D a, Vec2D b, VecD2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + public boolean isInTriangleD(VecD2D a, VecD2D b, Vec2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + public boolean isInTriangleD(VecD2D a, Vec2D b, VecD2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + public boolean isInTriangleD(Vec2D a, VecD2D b, VecD2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + public boolean isInTriangleD(VecD2D a, VecD2D b, VecD2D c) { + VecD2D v1 = sub(a).normalize(); + VecD2D v2 = sub(b).normalize(); + VecD2D v3 = sub(c).normalize(); + double total_angles = Math.acos(v1.dot(v2)); + total_angles += Math.acos(v2.dot(v3)); + total_angles += Math.acos(v3.dot(v1)); + return (MathUtils.abs(total_angles - MathUtils.TWO_PI) <= 0.005f); + } + + public final boolean isMajorAxis(float tol) { + double ax = MathUtils.abs(x); + double ay = MathUtils.abs(y); + double itol = 1 - tol; + if (ax > itol) { + return (ay < tol); + } else if (ay > itol) { + return (ax < tol); + } + return false; + } + public final boolean isMajorAxis(double tol) { + double ax = MathUtils.abs(x); + double ay = MathUtils.abs(y); + double itol = 1 - tol; + if (ax > itol) { + return (ay < tol); + } else if (ay > itol) { + return (ax < tol); + } + return false; + } + + public final boolean isZeroVector() { + return MathUtils.abs(x) < MathUtils.EPS + && MathUtils.abs(y) < MathUtils.EPS; + } + + public final VecD2D jitter(float j) { + return jitter(j, j); + } + + /** + * Adds random jitter to the vector in the range -j ... +j using the default + * {@link Random} generator of {@link MathUtils}. + * + * @param jx + * maximum x jitter + * @param jy + * maximum y jitter + * @return itself + */ + public final VecD2D jitter(float jx, float jy) { + x += MathUtils.normalizedRandom() * jx; + y += MathUtils.normalizedRandom() * jy; + return this; + } + public final VecD2D jitter(double jx, float jy) { + x += MathUtils.normalizedRandom() * jx; + y += MathUtils.normalizedRandom() * jy; + return this; + } + public final VecD2D jitter(float jx, double jy) { + x += MathUtils.normalizedRandom() * jx; + y += MathUtils.normalizedRandom() * jy; + return this; + } + public final VecD2D jitter(double jx, double jy) { + x += MathUtils.normalizedRandom() * jx; + y += MathUtils.normalizedRandom() * jy; + return this; + } + + public final VecD2D jitter(Random rnd, float j) { + return jitter(rnd, j, j); + } + public final VecD2D jitter(Random rnd, double j) { + return jitter(rnd, j, j); + } + + public final VecD2D jitter(Random rnd, float jx, float jy) { + x += MathUtils.normalizedRandom(rnd) * jx; + y += MathUtils.normalizedRandom(rnd) * jy; + return this; + } + public final VecD2D jitter(Random rnd, double jx, float jy) { + x += MathUtils.normalizedRandom(rnd) * jx; + y += MathUtils.normalizedRandom(rnd) * jy; + return this; + } + public final VecD2D jitter(Random rnd, float jx, double jy) { + x += MathUtils.normalizedRandom(rnd) * jx; + y += MathUtils.normalizedRandom(rnd) * jy; + return this; + } + public final VecD2D jitter(Random rnd, double jx, double jy) { + x += MathUtils.normalizedRandom(rnd) * jx; + y += MathUtils.normalizedRandom(rnd) * jy; + return this; + } + + public final VecD2D jitter(Random rnd, Vec2D jv) { + return jitter(rnd, jv.x, jv.y); + } + public final VecD2D jitter(Random rnd, VecD2D jv) { + return jitter(rnd, jv.x, jv.y); + } + + public final VecD2D jitter(Vec2D jv) { + return jitter(jv.x, jv.y); + } + public final VecD2D jitter(VecD2D jv) { + return jitter(jv.x, jv.y); + } + + /** + * Limits the vector's magnitude to the length given + * + * @param lim + * new maximum magnitude + * @return itself + */ + public final VecD2D limit(float lim) { + if (magSquared() > lim * lim) { + return normalize().scaleSelf(lim); + } + return this; + } + public final VecD2D limit(double lim) { + if (magSquared() > lim * lim) { + return normalize().scaleSelf(lim); + } + return this; + } + + public final double magnitude() { + return Math.sqrt(x * x + y * y); + } + + public final double magSquared() { + return x * x + y * y; + } + + public final VecD2D max(ReadonlyVec2D v) { + return new VecD2D(MathUtils.max(x, v.x()), MathUtils.max(y, v.y())); + } + public final VecD2D max(ReadonlyVecD2D v) { + return new VecD2D(MathUtils.max(x, v.x()), MathUtils.max(y, v.y())); + } + + /** + * Adjusts the vector components to the maximum values of both vectors + * + * @param v + * @return itself + */ + public final VecD2D maxSelf(ReadonlyVec2D v) { + x = MathUtils.max(x, v.x()); + y = MathUtils.max(y, v.y()); + return this; + } + public final VecD2D maxSelf(ReadonlyVecD2D v) { + x = MathUtils.max(x, v.x()); + y = MathUtils.max(y, v.y()); + return this; + } + + public final VecD2D min(ReadonlyVec2D v) { + return new VecD2D(MathUtils.min(x, v.x()), MathUtils.min(y, v.y())); + } + public final VecD2D min(ReadonlyVecD2D v) { + return new VecD2D(MathUtils.min(x, v.x()), MathUtils.min(y, v.y())); + } + + /** + * Adjusts the vector components to the minimum values of both vectors + * + * @param v + * @return itself + */ + public final VecD2D minSelf(ReadonlyVec2D v) { + x = MathUtils.min(x, v.x()); + y = MathUtils.min(y, v.y()); + return this; + } + public final VecD2D minSelf(ReadonlyVecD2D v) { + x = MathUtils.min(x, v.x()); + y = MathUtils.min(y, v.y()); + return this; + } + + /** + * Normalizes the vector so that its magnitude = 1 + * + * @return itself + */ + public final VecD2D normalize() { + double mag = x * x + y * y; + if (mag > 0) { + mag = 1f / Math.sqrt(mag); + x *= mag; + y *= mag; + } + return this; + } + + + /** + * Normalizes the vector to the given length. + * + * @param len + * desired length + * @return itself + */ + public final VecD2D normalizeTo(float len) { + double mag = Math.sqrt(x * x + y * y); + if (mag > 0) { + mag = len / mag; + x *= mag; + y *= mag; + } + return this; + } + public final VecD2D normalizeTo(double len) { + double mag = Math.sqrt(x * x + y * y); + if (mag > 0) { + mag = len / mag; + x *= mag; + y *= mag; + } + return this; + } + + + public final VecD2D perpendicular() { + double t = x; + x = -y; + y = t; + return this; + } + + public final double positiveHeading() { + double dist = Math.sqrt(x * x + y * y); + if (y >= 0) { + return Math.acos(x / dist); + } else { + return (Math.acos(-x / dist) + MathUtils.PI); + } + } + + public final VecD2D reciprocal() { + x = 1f / x; + y = 1f / y; + return this; + } + + public final VecD2D reflect(ReadonlyVec2D normal) { + return set(VecD2D(normal).scale(this.dot(VecD2D(normal)) * 2).subSelf(this)); + } + public final VecD2D reflect(ReadonlyVecD2D normal) { + return set(normal.scale(this.dot(normal) * 2).subSelf(this)); + } + + /** + * Rotates the vector by the given angle around the Z axis. + * + * @param theta + * @return itself + */ + public final VecD2D rotate(float theta) { + double co = Math.cos(theta); + double si = Math.sin(theta); + double xx = co * x - si * y; + y = si * x + co * y; + x = xx; + return this; + } + public final VecD2D rotate(double theta) { + double co = Math.cos(theta); + double si = Math.sin(theta); + double xx = co * x - si * y; + y = si * x + co * y; + x = xx; + return this; + } + + + public VecD2D roundTo(float prec) { + x = MathUtils.roundTo(x, prec); + y = MathUtils.roundTo(y, prec); + return this; + } + public VecD2D roundTo(double prec) { + x = MathUtils.roundTo(x, prec); + y = MathUtils.roundTo(y, prec); + return this; + } + + public final VecD2D scale(float s) { + return new VecD2D(x * s, y * s); + } + public final VecD2D scale(double s) { + return new VecD2D(x * s, y * s); + } + + public final VecD2D scale(float a, float b) { + return new VecD2D(x * a, y * b); + } + public final VecD2D scale(double a, float b) { + return new VecD2D(x * a, y * b); + } + public final VecD2D scale(float a, double b) { + return new VecD2D(x * a, y * b); + } + public final VecD2D scale(double a, double b) { + return new VecD2D(x * a, y * b); + } + + + public final VecD2D scale(ReadonlyVec2D s) { + return VecD2D(s).copy().scaleSelf(this); + } + public final VecD2D scale(ReadonlyVecD2D s) { + return s.copy().scaleSelf(this); + } + + + public final VecD2D scale(Vec2D s) { + return new VecD2D(x * s.x, y * s.y); + } + public final VecD2D scale(VecD2D s) { + return new VecD2D(x * s.x, y * s.y); + } + + /** + * Scales vector uniformly and overrides coordinates with result + * + * @param s + * scale factor + * @return itself + */ + public final VecD2D scaleSelf(float s) { + x *= s; + y *= s; + return this; + } + public final VecD2D scaleSelf(double s) { + x *= s; + y *= s; + return this; + } + + /** + * Scales vector non-uniformly by vector {a,b,c} and overrides coordinates + * with result + * + * @param a + * scale factor for X coordinate + * @param b + * scale factor for Y coordinate + * @return itself + */ + public final VecD2D scaleSelf(float a, float b) { + x *= a; + y *= b; + return this; + } + public final VecD2D scaleSelf(double a, float b) { + x *= a; + y *= b; + return this; + } + public final VecD2D scaleSelf(float a, double b) { + x *= a; + y *= b; + return this; + } + public final VecD2D scaleSelf(double a, double b) { + x *= a; + y *= b; + return this; + } + + + /** + * Scales vector non-uniformly by vector v and overrides coordinates with + * result + * + * @param s + * scale vector + * @return itself + */ + + public final VecD2D scaleSelf(Vec2D s) { + x *= s.x; + y *= s.y; + return this; + } + public final VecD2D scaleSelf(VecD2D s) { + x *= s.x; + y *= s.y; + return this; + } + + /** + * Overrides coordinates with the given values + * + * @param x + * @param y + * @return itself + */ + public final VecD2D set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + public final VecD2D set(double x, float y) { + this.x = x; + this.y = y; + return this; + } + public final VecD2D set(float x, double y) { + this.x = x; + this.y = y; + return this; + } + public final VecD2D set(double x, double y) { + this.x = x; + this.y = y; + return this; + } + + public final VecD2D set(ReadonlyVec2D v) { + x = v.x(); + y = v.y(); + return this; + } + public final VecD2D set(ReadonlyVecD2D v) { + x = v.x(); + y = v.y(); + return this; + } + + /** + * Overrides coordinates with the ones of the given vector + * + * @param v + * vector to be copied + * @return itself + */ + public final VecD2D set(Vec2D v) { + x = v.x; + y = v.y; + return this; + } + public final VecD2D set(VecD2D v) { + x = v.x; + y = v.y; + return this; + } + + public final VecD2D setComponent(AxisD id, float val) { + switch (id) { + case X: + x = val; + break; + case Y: + y = val; + break; + } + return this; + } + public final VecD2D setComponent(AxisD id, double val) { + switch (id) { + case X: + x = val; + break; + case Y: + y = val; + break; + } + return this; + } + + + public final VecD2D setComponent(int id, float val) { + switch (id) { + case 0: + x = val; + break; + case 1: + y = val; + break; + default: + throw new IllegalArgumentException( + "component id needs to be 0 or 1"); + } + return this; + } + public final VecD2D setComponent(int id, double val) { + switch (id) { + case 0: + x = val; + break; + case 1: + y = val; + break; + default: + throw new IllegalArgumentException( + "component id needs to be 0 or 1"); + } + return this; + } + + + public VecD2D setX(float x) { + this.x = x; + return this; + } + public VecD2D setX(double x) { + this.x = x; + return this; + } + + public VecD2D setY(float y) { + this.y = y; + return this; + } + public VecD2D setY(double y) { + this.y = y; + return this; + } + + /** + * Replaces all vector components with the signum of their original values. + * In other words if a components value was negative its new value will be + * -1, if zero => 0, if positive => +1 + * + * @return itself + */ + public final VecD2D signum() { + x = (x < 0 ? -1 : x == 0 ? 0 : 1); + y = (y < 0 ? -1 : y == 0 ? 0 : 1); + return this; + } + + /** + * Rounds the vector to the closest major axis. Assumes the vector is + * normalized. + * + * @return itself + */ + public final VecD2D snapToAxis() { + if (MathUtils.abs(x) < 0.5f) { + x = 0; + } else { + x = x < 0 ? -1 : 1; + y = 0; + } + if (MathUtils.abs(y) < 0.5f) { + y = 0; + } else { + y = y < 0 ? -1 : 1; + x = 0; + } + return this; + } + + public final VecD2D sub(float a, float b) { + return new VecD2D(x - a, y - b); + } + public final VecD2D sub(double a, float b) { + return new VecD2D(x - a, y - b); + } + public final VecD2D sub(float a, double b) { + return new VecD2D(x - a, y - b); + } + public final VecD2D sub(double a, double b) { + return new VecD2D(x - a, y - b); + } + + + public final VecD2D sub(ReadonlyVec2D v) { + return new VecD2D(x - v.x(), y - v.y()); + } + public final VecD2D sub(ReadonlyVecD2D v) { + return new VecD2D(x - v.x(), y - v.y()); + } + + public final VecD2D sub(Vec2D v) { + return new VecD2D(x - v.x, y - v.y); + } + public final VecD2D sub(VecD2D v) { + return new VecD2D(x - v.x, y - v.y); + } + + /** + * Subtracts vector {a,b,c} and overrides coordinates with result. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @return itself + */ + public final VecD2D subSelf(float a, float b) { + x -= a; + y -= b; + return this; + } + public final VecD2D subSelf(double a, float b) { + x -= a; + y -= b; + return this; + } + public final VecD2D subSelf(float a, double b) { + x -= a; + y -= b; + return this; + } + public final VecD2D subSelf(double a, double b) { + x -= a; + y -= b; + return this; + } + + /** + * Subtracts vector v and overrides coordinates with result. + * + * @param v + * vector to be subtracted + * @return itself + */ + public final VecD2D subSelf(Vec2D v) { + x -= v.x; + y -= v.y; + return this; + } + public final VecD2D subSelf(VecD2D v) { + x -= v.x; + y -= v.y; + return this; + } + + public final VecD2D tangentNormalOfEllipse(Vec2D eO, Vec2D eR) { + VecD2D p = this.sub(eO); + double xr2 = eR.x * eR.x; + double yr2 = eR.y * eR.y; + return new VecD2D(p.x / xr2, p.y / yr2).normalize(); + } + public final VecD2D tangentNormalOfEllipse(VecD2D eO, Vec2D eR) { + VecD2D p = this.sub(eO); + double xr2 = eR.x * eR.x; + double yr2 = eR.y * eR.y; + return new VecD2D(p.x / xr2, p.y / yr2).normalize(); + } + public final VecD2D tangentNormalOfEllipse(Vec2D eO, VecD2D eR) { + VecD2D p = this.sub(eO); + double xr2 = eR.x * eR.x; + double yr2 = eR.y * eR.y; + return new VecD2D(p.x / xr2, p.y / yr2).normalize(); + } + public final VecD2D tangentNormalOfEllipse(VecD2D eO, VecD2D eR) { + VecD2D p = this.sub(eO); + double xr2 = eR.x * eR.x; + double yr2 = eR.y * eR.y; + return new VecD2D(p.x / xr2, p.y / yr2).normalize(); + } + + public final VecD3D toD3DXY() { + return new VecD3D(x, y, 0); + } + + public final VecD3D toD3DXZ() { + return new VecD3D(x, 0, y); + } + + public final VecD3D toD3DYZ() { + return new VecD3D(0, x, y); + } + + public double[] toArray() { + return new double[] { + x, y + }; + } + + public final VecD2D toCartesian() { + double xx = x * Math.cos(y); + y = x * Math.sin(y); + x = xx; + return this; + } + + public final VecD2D toPolar() { + double r = Math.sqrt(x * x + y * y); + y = Math.atan2(y, x); + x = r; + return this; + } + + public String toString() { + StringBuffer sb = new StringBuffer(32); + sb.append("{x:").append(x).append(", y:").append(y).append("}"); + return sb.toString(); + } + + public final double x() { + return x; + } + + public final double y() { + return y; + } + @Override + public VecD2D VecD2D(ReadonlyVec2D v) { + x=(double) v.x(); + y=(double) v.y(); + return null; + } + + + +} \ No newline at end of file diff --git a/src.core/toxi/geom/VecD3D.java b/src.core/toxi/geom/VecD3D.java new file mode 100644 index 00000000..72f9bea4 --- /dev/null +++ b/src.core/toxi/geom/VecD3D.java @@ -0,0 +1,2432 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import java.util.Random; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; + +/** + * Comprehensive double 3D vector class with additional basic intersection and + * collision detection features. + */ +public class VecD3D implements Comparable, ReadonlyVecD3D { + + public static enum AxisD { + + X(VecD3D.X_AXIS), + Y(VecD3D.Y_AXIS), + Z(VecD3D.Z_AXIS); + + private final ReadonlyVecD3D vector; + + private AxisD(ReadonlyVecD3D v) { + this.vector = v; + } + + public ReadonlyVecD3D getVector() { + return vector; + } + } + + /** + * Convert from float to double versions of Vec3D + */ + public VecD3D VecD3D(Vec3D v) { + x = (double)v.x; + y = (double)v.y; + z = (double)v.z; + return this; + } + + /** Defines positive X axis. */ + public static final ReadonlyVecD3D X_AXIS = new VecD3D(1, 0, 0); + + /** Defines positive Y axis. */ + public static final ReadonlyVecD3D Y_AXIS = new VecD3D(0, 1, 0); + + /** Defines positive Z axis. */ + public static final ReadonlyVecD3D Z_AXIS = new VecD3D(0, 0, 1); + + /** Defines the zero vector. */ + public static final ReadonlyVecD3D ZERO = new VecD3D(); + + /** + * Defines vector with all coords set to Double.MIN_VALUE. Useful for + * bounding box operations. + */ + public static final ReadonlyVecD3D MIN_VALUE = new VecD3D(Double.MIN_VALUE, + Double.MIN_VALUE, Double.MIN_VALUE); + + /** + * Defines vector with all coords set to Double.MAX_VALUE. Useful for + * bounding box operations. + */ + public static final ReadonlyVecD3D MAX_VALUE = new VecD3D(Double.MAX_VALUE, + Double.MAX_VALUE, Double.MAX_VALUE); + + public static final ReadonlyVecD3D NEG_MAX_VALUE = new VecD3D( + -Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE); + + /** + * Creates a new vector from the given angle in the XY plane. The Z + * component of the vector will be zero. + * + * The resulting vector for theta=0 is equal to the positive X axis. + * + * @param theta + * the theta + * + * @return new vector in the XY plane + */ + public static final VecD3D fromXYTheta(float theta) { + return new VecD3D(Math.cos(theta), Math.sin(theta), 0.d); + } + public static final VecD3D fromXYTheta(double theta) { + return new VecD3D(Math.cos(theta), Math.sin(theta), 0.d); + } + + /** + * Creates a new vector from the given angle in the XZ plane. The Y + * component of the vector will be zero. + * + * The resulting vector for theta=0 is equal to the positive X axis. + * + * @param theta + * the theta + * + * @return new vector in the XZ plane + */ + public static final VecD3D fromXZTheta(float theta) { + return new VecD3D(Math.cos(theta), 0.d, Math.sin(theta)); + } + public static final VecD3D fromXZTheta(double theta) { + return new VecD3D(Math.cos(theta), 0.d, Math.sin(theta)); + } + + /** + * Creates a new vector from the given angle in the YZ plane. The X + * component of the vector will be zero. + * + * The resulting vector for theta=0 is equal to the positive Y axis. + * + * @param theta + * the theta + * + * @return new vector in the YZ plane + */ + public static final VecD3D fromYZTheta(float theta) { + return new VecD3D(0.d, Math.cos(theta), Math.sin(theta)); + } + public static final VecD3D fromYZTheta(double theta) { + return new VecD3D(0.d, Math.cos(theta), Math.sin(theta)); + } + + /** + * Constructs a new vector consisting of the largest components of both + * vectors. + * + * @param b + * the b + * @param a + * the a + * + * @return result as new vector + */ + public static final VecD3D max(ReadonlyVec3D a, ReadonlyVec3D b) { + return new VecD3D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y()), MathUtils.max(a.z(), b.z())); + } + public static final VecD3D max(ReadonlyVecD3D a, ReadonlyVec3D b) { + return new VecD3D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y()), MathUtils.max(a.z(), b.z())); + } + public static final VecD3D max(ReadonlyVec3D a, ReadonlyVecD3D b) { + return new VecD3D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y()), MathUtils.max(a.z(), b.z())); + } + public static final VecD3D max(ReadonlyVecD3D a, ReadonlyVecD3D b) { + return new VecD3D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), + b.y()), MathUtils.max(a.z(), b.z())); + } + + /** + * Constructs a new vector consisting of the smallest components of both + * vectors. + * + * @param b + * comparing vector + * @param a + * the a + * + * @return result as new vector + */ + public static final VecD3D min(ReadonlyVec3D a, ReadonlyVec3D b) { + return new VecD3D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y()), MathUtils.min(a.z(), b.z())); + } + public static final VecD3D min(ReadonlyVecD3D a, ReadonlyVec3D b) { + return new VecD3D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y()), MathUtils.min(a.z(), b.z())); + } + public static final VecD3D min(ReadonlyVec3D a, ReadonlyVecD3D b) { + return new VecD3D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y()), MathUtils.min(a.z(), b.z())); + } + public static final VecD3D min(ReadonlyVecD3D a, ReadonlyVecD3D b) { + return new VecD3D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), + b.y()), MathUtils.min(a.z(), b.z())); + } + + /** + * Static factory method. Creates a new random unit vector using the Random + * implementation set as default for the {@link MathUtils} class. + * + * @return a new random normalized unit vector. + */ + public static final VecD3D randomVector() { + return randomVector(MathUtils.RND); + } + + /** + * Static factory method. Creates a new random unit vector using the given + * Random generator instance. I recommend to have a look at the + * https://uncommons-maths.dev.java.net library for a good choice of + * reliable and high quality random number generators. + * + * @param rnd + * the rnd + * + * @return a new random normalized unit vector. + */ + public static final VecD3D randomVector(Random rnd) { + VecD3D v = new VecD3D(rnd.nextDouble() * 2 - 1, rnd.nextDouble() * 2 - 1, + rnd.nextDouble() * 2 - 1); + return v.normalize(); + } + + /** X coordinate. */ + @XmlAttribute(required = true) + public double x; + + /** Y coordinate. */ + @XmlAttribute(required = true) + public double y; + + /** Z coordinate. */ + @XmlAttribute(required = true) + public double z; + + /** + * Creates a new zero vector. + */ + public VecD3D() { + } + + public VecD3D(Vec3D v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + } + + + /** + * Creates a new vector with the given coordinates. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + */ + public VecD3D(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + public VecD3D(double x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + public VecD3D(float x, double y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + public VecD3D(float x, float y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + public VecD3D(double x, double y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + public VecD3D(double x, float y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + public VecD3D(float x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + public VecD3D(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public VecD3D(float[] v) { + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + } + public VecD3D(double[] v) { + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + } + + /** + * Creates a new vector with the coordinates of the given vector. + * + * @param v + * vector to be copied + */ + public VecD3D(ReadonlyVec3D v) { + this.x = v.x(); + this.y = v.y(); + this.z = v.z(); + } + public VecD3D(ReadonlyVecD3D v) { + this.x = v.x(); + this.y = v.y(); + this.z = v.z(); + } + + /** + * Abs. + * + * @return the vec3 d + */ + public final VecD3D abs() { + x = MathUtils.abs(x); + y = MathUtils.abs(y); + z = MathUtils.abs(z); + return this; + } + + public final VecD3D add(float a, float b, float c) { + return new VecD3D(x + a, y + b, z + c); + } + public final VecD3D add(double a, float b, float c) { + return new VecD3D(x + a, y + b, z + c); + } + public final VecD3D add(float a, double b, float c) { + return new VecD3D(x + a, y + b, z + c); + } + public final VecD3D add(float a, float b, double c) { + return new VecD3D(x + a, y + b, z + c); + } + public final VecD3D add(double a, double b, float c) { + return new VecD3D(x + a, y + b, z + c); + } + public final VecD3D add(double a, float b, double c) { + return new VecD3D(x + a, y + b, z + c); + } + public final VecD3D add(float a, double b, double c) { + return new VecD3D(x + a, y + b, z + c); + } + public final VecD3D add(double a, double b, double c) { + return new VecD3D(x + a, y + b, z + c); + } + + public VecD3D add(ReadonlyVec3D v) { + return new VecD3D(x + v.x(), y + v.y(), z + v.z()); + } + public VecD3D add(ReadonlyVecD3D v) { + return new VecD3D(x + v.x(), y + v.y(), z + v.z()); + } + + + public final VecD3D add(Vec3D v) { + return new VecD3D(x + v.x, y + v.y, z + v.z); + } + public final VecD3D add(VecD3D v) { + return new VecD3D(x + v.x, y + v.y, z + v.z); + } + + /** + * Adds vector {a,b,c} and overrides coordinates with result. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @param c + * Z coordinate + * + * @return itself + */ + public final VecD3D addSelf(float a, float b, float c) { + x += a; + y += b; + z += c; + return this; + } + public final VecD3D addSelf(double a, float b, float c) { + x += a; + y += b; + z += c; + return this; + } + public final VecD3D addSelf(float a, double b, float c) { + x += a; + y += b; + z += c; + return this; + } + public final VecD3D addSelf(float a, float b, double c) { + x += a; + y += b; + z += c; + return this; + } + public final VecD3D addSelf(double a, double b, float c) { + x += a; + y += b; + z += c; + return this; + } + public final VecD3D addSelf(double a, float b, double c) { + x += a; + y += b; + z += c; + return this; + } + public final VecD3D addSelf(float a, double b, double c) { + x += a; + y += b; + z += c; + return this; + } + public final VecD3D addSelf(double a, double b, double c) { + x += a; + y += b; + z += c; + return this; + } + + public final VecD3D addSelf(ReadonlyVec3D v) { + x += v.x(); + y += v.y(); + z += v.z(); + return this; + } + public final VecD3D addSelf(ReadonlyVecD3D v) { + x += v.x(); + y += v.y(); + z += v.z(); + return this; + } + + /** + * Adds vector v and overrides coordinates with result. + * + * @param v + * vector to add + * + * @return itself + */ + public final VecD3D addSelf(Vec3D v) { + x += v.x; + y += v.y; + z += v.z; + return this; + } + public final VecD3D addSelf(VecD3D v) { + x += v.x; + y += v.y; + z += v.z; + return this; + } + + public final double angleBetween(ReadonlyVec3D v) { + return Math.acos(dot(v)); + } + public final double angleBetween(ReadonlyVecD3D v) { + return Math.acos(dot(v)); + } + + public final double angleBetween(ReadonlyVec3D v, boolean forceNormalize) { + double theta; + if (forceNormalize) { + theta = getNormalized().dot(v.getNormalized()); + } else { + theta = dot(v); + } + return Math.acos(theta); + } + public final double angleBetween(ReadonlyVecD3D v, boolean forceNormalize) { + double theta; + if (forceNormalize) { + theta = getNormalized().dot(v.getNormalized()); + } else { + theta = dot(v); + } + return Math.acos(theta); + } + + /** + * Sets all vector components to 0. + * + * @return itself + */ + public final ReadonlyVecD3D clear() { + x = y = z = 0; + return this; + } + + public int compareTo(ReadonlyVec3D v) { + if (x == (double)v.x() && y == (double)v.y() && z == (double)v.z()) { + return 0; + } + double a = magSquared(); + double b = v.magSquared(); + if (a < b) { + return -1; + } + return +1; + } + public int compareTo(ReadonlyVecD3D v) { + if (x == v.x() && y == v.y() && z == v.z()) { + return 0; + } + double a = magSquared(); + double b = v.magSquared(); + if (a < b) { + return -1; + } + return +1; + } + + + /** + * Forcefully fits the vector in the given AABB. + * + * @param box + * the box + * + * @return itself + */ + public VecD3D constrain(AABB box) { + return constrain(box.getMin(), box.getMax()); + } + public VecD3D constrain(AABBD box) { + return constrain(box.getMin(), box.getMax()); + } + + /** + * Forcefully fits the vector in the given AABB specified by the 2 given + * points. + * + * @param min + * @param max + * @return itself + */ + public VecD3D constrain(Vec3D min, Vec3D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + z = MathUtils.clip(z, min.z, max.z); + return this; + } + public VecD3D constrain(VecD3D min, Vec3D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + z = MathUtils.clip(z, min.z, max.z); + return this; + } + public VecD3D constrain(Vec3D min, VecD3D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + z = MathUtils.clip(z, min.z, max.z); + return this; + } + public VecD3D constrain(VecD3D min, VecD3D max) { + x = MathUtils.clip(x, min.x, max.x); + y = MathUtils.clip(y, min.y, max.y); + z = MathUtils.clip(z, min.z, max.z); + return this; + } + + public VecD3D copy() { + return new VecD3D(this); + } + + public final VecD3D cross(ReadonlyVec3D v) { + return new VecD3D(y * v.z() - v.y() * z, z * v.x() - v.z() * x, x * v.y() - v.x() * y); + } + public final VecD3D cross(ReadonlyVecD3D v) { + return new VecD3D(y * v.z() - v.y() * z, z * v.x() - v.z() * x, x * v.y() - v.x() * y); + } + + public final VecD3D cross(Vec3D v) { + return new VecD3D(y * v.z - v.y * z, z * v.x - v.z * x, x * v.y - v.x * y); + } + public final VecD3D cross(VecD3D v) { + return new VecD3D(y * v.z - v.y * z, z * v.x - v.z * x, x * v.y - v.x * y); + } + + + public final VecD3D crossInto(ReadonlyVec3D v, VecD3D result) { + final double vx = v.x(); + final double vy = v.y(); + final double vz = v.z(); + result.x = y * vz - vy * z; + result.y = z * vx - vz * x; + result.z = x * vy - vx * y; + return result; + } + public final VecD3D crossInto(ReadonlyVecD3D v, VecD3D result) { + final double vx = v.x(); + final double vy = v.y(); + final double vz = v.z(); + result.x = y * vz - vy * z; + result.y = z * vx - vz * x; + result.z = x * vy - vx * y; + return result; + } + + /** + * Calculates cross-product with vector v. The resulting vector is + * perpendicular to both the current and supplied vector and overrides the + * current. + * + * @param v + * the v + * + * @return itself + */ + public final VecD3D crossSelf(Vec3D v) { + final double cx = y * v.z - v.y * z; + final double cy = z * v.x - v.z * x; + z = x * v.y - v.x * y; + y = cy; + x = cx; + return this; + } + public final VecD3D crossSelf(VecD3D v) { + final double cx = y * v.z - v.y * z; + final double cy = z * v.x - v.z * x; + z = x * v.y - v.x * y; + y = cy; + x = cx; + return this; + } + + public final double distanceTo(ReadonlyVec3D v) { + if (v != null) { + final double dx = x - v.x(); + final double dy = y - v.y(); + final double dz = z - v.z(); + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } else { + return Double.NaN; + } + } + public final double distanceTo(ReadonlyVecD3D v) { + if (v != null) { + final double dx = x - v.x(); + final double dy = y - v.y(); + final double dz = z - v.z(); + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } else { + return Double.NaN; + } + } + + public final double distanceToSquared(ReadonlyVec3D v) { + if (v != null) { + final double dx = x - v.x(); + final double dy = y - v.y(); + final double dz = z - v.z(); + return dx * dx + dy * dy + dz * dz; + } else { + return Double.NaN; + } + } + public final double distanceToSquared(ReadonlyVecD3D v) { + if (v != null) { + final double dx = x - v.x(); + final double dy = y - v.y(); + final double dz = z - v.z(); + return dx * dx + dy * dy + dz * dz; + } else { + return Double.NaN; + } + } + + public final double dot(ReadonlyVec3D v) { + return x * v.x() + y * v.y() + z * v.z(); + } + public final double dot(ReadonlyVecD3D v) { + return x * v.x() + y * v.y() + z * v.z(); + } + + public final double dot(Vec3D v) { + return x * v.x + y * v.y + z * v.z; + } + public final double dot(VecD3D v) { + return x * v.x + y * v.y + z * v.z; + } + + /** + * Returns true if the Object v is of type ReadonlyVec3D and all of the data + * members of v are equal to the corresponding data members in this vector. + * + * @param v + * the Object with which the comparison is made + * @return true or false + */ + public boolean equals(Object v) { + try { + ReadonlyVecD3D vv = (ReadonlyVecD3D) v; + return (x == vv.x() && y == vv.y() && z == vv.z()); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + /** + * Returns true if the Object v is of type ReadonlyVec3D and all of the data + * members of v are equal to the corresponding data members in this vector. + * + * @param v + * the vector with which the comparison is made + * @return true or false + */ + public boolean equals(ReadonlyVec3D v) { + try { + return (x == (double)v.x() && y == (double) v.y() && z ==(double) v.z()); + } catch (NullPointerException e) { + return false; + } + } + public boolean equals(ReadonlyVecD3D v) { + try { + return (x == v.x() && y == v.y() && z == v.z()); + } catch (NullPointerException e) { + return false; + } + } + + public boolean equalsWithTolerance(ReadonlyVec3D v, float tolerance) { + return equalsWithTolerance((ReadonlyVecD3D) v, (double) tolerance); + } + public boolean equalsWithTolerance(ReadonlyVecD3D v, float tolerance) { + return equalsWithTolerance(v, (double) tolerance); + } + public boolean equalsWithTolerance(ReadonlyVec3D v, double tolerance) { + return equalsWithTolerance((ReadonlyVecD3D) v, tolerance); + } + public boolean equalsWithTolerance(ReadonlyVecD3D v, double tolerance) { + try { + double diff = x - v.x(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = y - v.y(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = z - v.z(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + return true; + } catch (NullPointerException e) { + return false; + } + } + + /** + * Replaces the vector components with integer values of their current + * values. + * + * @return itself + */ + public final VecD3D floor() { + x = MathUtils.floor(x); + y = MathUtils.floor(y); + z = MathUtils.floor(z); + return this; + } + + /** + * Replaces the vector components with the fractional part of their current + * values. + * + * @return itself + */ + public final VecD3D frac() { + x -= MathUtils.floor(x); + y -= MathUtils.floor(y); + z -= MathUtils.floor(z); + return this; + } + + public final VecD3D getAbs() { + return new VecD3D(this).abs(); + } + + public VecD3D getCartesian() { + return copy().toCartesian(); + } + + /** + * Identifies the closest cartesian axis to this vector. If at leat two + * vector components are equal, no unique decision can be made and the + * method returns null. + * + * @return Axis enum or null + */ + public final AxisD getClosestAxis() { + double ax = MathUtils.abs(x); + double ay = MathUtils.abs(y); + double az = MathUtils.abs(z); + if (ax > ay && ax > az) { + return AxisD.X; + } + if (ay > ax && ay > az) { + return AxisD.Y; + } + if (az > ax && az > ay) { + return AxisD.Z; + } + return null; + } + + public final double getComponent(AxisD id) { + switch (id) { + case X: + return x; + case Y: + return y; + case Z: + return z; + } + throw new IllegalArgumentException(); + } + + public final double getComponent(int id) { + switch (id) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + } + throw new IllegalArgumentException("index must be 0, 1 or 2"); + } + public final double getComponent(Object id) { + try { + int iid = Integer.parseInt(id.toString()); + switch (iid) { + case 0: + return x; + case 1: + return y; + case 2: + return z; + } + } catch (NumberFormatException e) { + } + throw new IllegalArgumentException("index must be 0, 1 or 2"); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getConstrained(toxi.geom.AABB) + */ + public final VecD3D getConstrained(AABB box) { + return new VecD3D(this).constrain(box); + } + public final VecD3D getConstrained(AABBD box) { + return new VecD3D(this).constrain(box); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getFloored() + */ + public final VecD3D getFloored() { + return new VecD3D(this).floor(); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getFrac() + */ + public final VecD3D getFrac() { + return new VecD3D(this).frac(); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getInverted() + */ + public final VecD3D getInverted() { + return new VecD3D(-x, -y, -z); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getLimited(float) + */ + public final VecD3D getLimited(float lim) { + if (magSquared() > lim * lim) { + return getNormalizedTo(lim); + } + return new VecD3D(this); + } + public final VecD3D getLimited(double lim) { + if (magSquared() > lim * lim) { + return getNormalizedTo(lim); + } + return new VecD3D(this); + } + + public VecD3D getMapped(ScaleMap map) { + return new VecD3D(map.getClippedValueFor(x), + map.getClippedValueFor(y), + map.getClippedValueFor(z)); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getNormalized() + */ + public final VecD3D getNormalized() { + return new VecD3D(this).normalize(); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getNormalizedTo(float) + */ + public final VecD3D getNormalizedTo(float len) { + return new VecD3D(this).normalizeTo(len); + } + public final VecD3D getNormalizedTo(double len) { + return new VecD3D(this).normalizeTo(len); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getReciprocal() + */ + public final VecD3D getReciprocal() { + return copy().reciprocal(); + } + + public final VecD3D getReflected(ReadonlyVec3D normal) { + return copy().reflect(normal); + } + public final VecD3D getReflected(ReadonlyVecD3D normal) { + return copy().reflect(normal); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getRotatedAroundAxis(toxi.geom.Vec3D, float) + */ + public final VecD3D getRotatedAroundAxis(ReadonlyVec3D axis, float theta) { + return new VecD3D(this).rotateAroundAxis(axis, theta); + } + public final VecD3D getRotatedAroundAxis(ReadonlyVecD3D axis, float theta) { + return new VecD3D(this).rotateAroundAxis(axis, theta); + } + public final VecD3D getRotatedAroundAxis(ReadonlyVec3D axis, double theta) { + return new VecD3D(this).rotateAroundAxis(axis, theta); + } + public final VecD3D getRotatedAroundAxis(ReadonlyVecD3D axis, double theta) { + return new VecD3D(this).rotateAroundAxis(axis, theta); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getRotatedX(float) + */ + public final VecD3D getRotatedX(float theta) { + return new VecD3D(this).rotateX(theta); + } + public final VecD3D getRotatedX(double theta) { + return new VecD3D(this).rotateX(theta); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getRotatedY(float) + */ + public final VecD3D getRotatedY(float theta) { + return new VecD3D(this).rotateY(theta); + } + public final VecD3D getRotatedY(double theta) { + return new VecD3D(this).rotateY(theta); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getRotatedZ(float) + */ + public final VecD3D getRotatedZ(float theta) { + return new VecD3D(this).rotateZ(theta); + } + public final VecD3D getRotatedZ(double theta) { + return new VecD3D(this).rotateZ(theta); + } + + public VecD3D getRoundedTo(float prec) { + return copy().roundTo(prec); + } + public VecD3D getRoundedTo(double prec) { + return copy().roundTo(prec); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getSignum() + */ + public final VecD3D getSignum() { + return new VecD3D(this).signum(); + } + + public VecD3D getSpherical() { + return copy().toSpherical(); + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different Vec3D objects with identical data values (i.e., Vec3D.equals + * returns true) will return the same hash code value. Two objects with + * different data members may return the same hash value, although this is + * not likely. + * + * @return the integer hash code value + */ + public int hashCode() { + return ((Double)x).hashCode()+((Double)y).hashCode()+((Double)z).hashCode(); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#headingXY() + */ + public final double headingXY() { + return Math.atan2(y, x); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#headingXZ() + */ + public final double headingXZ() { + return Math.atan2(z, x); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#headingYZ() + */ + public final double headingYZ() { + return Math.atan2(y, z); + } + + public ReadonlyVecD3D immutable() { + return this; + } + + public final VecD3D interpolateTo(ReadonlyVec3D v, float f) { + return new VecD3D(x + (v.x() - x) * f, y + (v.y() - y) * f, z + (v.z() - z) * f); + } + public final VecD3D interpolateTo(ReadonlyVecD3D v, float f) { + return new VecD3D(x + (v.x() - x) * f, y + (v.y() - y) * f, z + (v.z() - z) * f); + } + public final VecD3D interpolateTo(ReadonlyVec3D v, double f) { + return new VecD3D(x + (v.x() - x) * f, y + (v.y() - y) * f, z + (v.z() - z) * f); + } + public final VecD3D interpolateTo(ReadonlyVecD3D v, double f) { + return new VecD3D(x + (v.x() - x) * f, y + (v.y() - y) * f, z + (v.z() - z) * f); + } + + public final VecD3D interpolateTo(ReadonlyVec3D v, float f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f), s.interpolate(z, v.z(), f)); + } + public final VecD3D interpolateTo(ReadonlyVecD3D v, float f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f), s.interpolate(z, v.z(), f)); + } + public final VecD3D interpolateTo(ReadonlyVec3D v, double f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f), s.interpolate(z, v.z(), f)); + } + public final VecD3D interpolateTo(ReadonlyVecD3D v, double f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f), s.interpolate(z, v.z(), f)); + } + + + public final VecD3D interpolateTo(Vec3D v, float f) { + return new VecD3D(x + (v.x - x) * f, y + (v.y - y) * f, z + (v.z - z) * f); + } + public final VecD3D interpolateTo(VecD3D v, float f) { + return new VecD3D(x + (v.x - x) * f, y + (v.y - y) * f, z + (v.z - z) * f); + } + public final VecD3D interpolateTo(Vec3D v, double f) { + return new VecD3D(x + (v.x - x) * f, y + (v.y - y) * f, z + (v.z - z) * f); + } + public final VecD3D interpolateTo(VecD3D v, double f) { + return new VecD3D(x + (v.x - x) * f, y + (v.y - y) * f, z + (v.z - z) * f); + } + + public final VecD3D interpolateTo(Vec3D v, float f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f), s.interpolate(z, v.z, f)); + } + public final VecD3D interpolateTo(VecD3D v, float f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f), s.interpolate(z, v.z, f)); + } + public final VecD3D interpolateTo(Vec3D v, double f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f), s.interpolate(z, v.z, f)); + } + public final VecD3D interpolateTo(VecD3D v, double f, InterpolateStrategy s) { + return new VecD3D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f), s.interpolate(z, v.z, f)); + } + + /** + * Interpolates the vector towards the given target vector, using linear + * interpolation. + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * + * @return itself, result overrides current vector + */ + public final VecD3D interpolateToSelf(ReadonlyVec3D v, float f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + z += (v.z() - z) * f; + return this; + } + public final VecD3D interpolateToSelf(ReadonlyVecD3D v, float f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + z += (v.z() - z) * f; + return this; + } + public final VecD3D interpolateToSelf(ReadonlyVec3D v, double f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + z += (v.z() - z) * f; + return this; + } + public final VecD3D interpolateToSelf(ReadonlyVecD3D v, double f) { + x += (v.x() - x) * f; + y += (v.y() - y) * f; + z += (v.z() - z) * f; + return this; + } + + /** + * Interpolates the vector towards the given target vector, using the given + * {@link InterpolateStrategy}. + * + * @param v + * target vector + * @param f + * interpolation factor (should be in the range 0..1) + * @param s + * InterpolateStrategy instance + * + * @return itself, result overrides current vector + */ + public final VecD3D interpolateToSelf(ReadonlyVec3D v, float f, InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + z = s.interpolate(z, v.z(), f); + return this; + } + public final VecD3D interpolateToSelf(ReadonlyVecD3D v, float f, InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + z = s.interpolate(z, v.z(), f); + return this; + } + public final VecD3D interpolateToSelf(ReadonlyVec3D v, double f, InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + z = s.interpolate(z, v.z(), f); + return this; + } + public final VecD3D interpolateToSelf(ReadonlyVecD3D v, double f, InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + z = s.interpolate(z, v.z(), f); + return this; + } + + /** + * Scales vector uniformly by factor -1 ( v = -v ), overrides coordinates + * with result. + * + * @return itself + */ + public final VecD3D invert() { + x *= -1; + y *= -1; + z *= -1; + return this; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#isInAABB(toxi.geom.AABB) + */ + public boolean isInAABBD(AABB box) { + final VecD3D min = VecD3D(box.getMin()); + final VecD3D max = VecD3D(box.getMax()); + if (x < min.x || x > max.x) { + return false; + } + if (y < min.y || y > max.y) { + return false; + } + if (z < min.z || z > max.z) { + return false; + } + return true; + } + public boolean isInAABBD(AABBD box) { + final VecD3D min = box.getMin(); + final VecD3D max = box.getMax(); + if (x < min.x || x > max.x) { + return false; + } + if (y < min.y || y > max.y) { + return false; + } + if (z < min.z || z > max.z) { + return false; + } + return true; + } + + public boolean isInAABBD(Vec3D boxOrigin, Vec3D boxExtent) { + return isInAABBD(VecD3D(boxOrigin), VecD3D(boxExtent)); + } + public boolean isInAABBD(VecD3D boxOrigin, Vec3D boxExtent) { + return isInAABBD(boxOrigin, VecD3D(boxExtent)); + } + public boolean isInAABBD(Vec3D boxOrigin, VecD3D boxExtent) { + return isInAABBD(VecD3D(boxOrigin), boxExtent); + } + public boolean isInAABBD(VecD3D boxOrigin, VecD3D boxExtent) { + double w = boxExtent.x; + if (x < boxOrigin.x - w || x > boxOrigin.x + w) { + return false; + } + w = boxExtent.y; + if (y < boxOrigin.y - w || y > boxOrigin.y + w) { + return false; + } + w = boxExtent.z; + if (z < boxOrigin.z - w || z > boxOrigin.z + w) { + return false; + } + return true; + } + + public final boolean isMajorAxisD(float tol) { + return isMajorAxisD((double)tol); + } + public final boolean isMajorAxisD(double tol) { + double ax = MathUtils.abs(x); + double ay = MathUtils.abs(y); + double az = MathUtils.abs(z); + double itol = 1 - tol; + if (ax > itol) { + if (ay < tol) { + return (az < tol); + } + } else if (ay > itol) { + if (ax < tol) { + return (az < tol); + } + } else if (az > itol) { + if (ax < tol) { + return (ay < tol); + } + } + return false; + } + + public final boolean isZeroVector() { + return MathUtils.abs(x) < MathUtils.EPS + && MathUtils.abs(y) < MathUtils.EPS + && MathUtils.abs(z) < MathUtils.EPS; + } + + /** + * Add random jitter to the vector in the range -j ... +j using the default + * {@link Random} generator of {@link MathUtils}. + * + * @param j + * the j + * + * @return the vec3 d + */ + public final VecD3D jitter(float j) { + return jitter(j, j, j); + } + + /** + * Adds random jitter to the vector in the range -j ... +j using the default + * {@link Random} generator of {@link MathUtils}. + * + * @param jx + * maximum x jitter + * @param jy + * maximum y jitter + * @param jz + * maximum z jitter + * + * @return itself + */ + public final VecD3D jitter(float jx, float jy, float jz) { + return jitter((double) jx, (double) jy, (double) jz); + } + public final VecD3D jitter(double jx, float jy, float jz) { + return jitter(jx, (double) jy, (double) jz); + } + public final VecD3D jitter(float jx, double jy, float jz) { + return jitter((double) jx, jy, (double) jz); + } + public final VecD3D jitter(float jx, float jy, double jz) { + return jitter((double) jx, (double) jy, jz); + } + public final VecD3D jitter(double jx, double jy, float jz) { + return jitter(jx, jy, (double) jz); + } + public final VecD3D jitter(double jx, float jy, double jz) { + return jitter(jx, (double) jy, jz); + } + public final VecD3D jitter(float jx, double jy, double jz) { + return jitter((double) jx, jy, jz); + } + public final VecD3D jitter(double jx, double jy, double jz) { + x += MathUtils.normalizedRandom() * jx; + y += MathUtils.normalizedRandom() * jy; + z += MathUtils.normalizedRandom() * jz; + return this; + } + + public final VecD3D jitter(Random rnd, float j) { + return jitter(rnd, j, j, j); + } + public final VecD3D jitter(Random rnd, double j) { + return jitter(rnd, j, j, j); + } + public final VecD3D jitter(Random rnd, float jx, float jy, float jz) { + return jitter(rnd, (double) jx, (double) jy, (double) jz); + } + public final VecD3D jitter(Random rnd, double jx, float jy, float jz) { + return jitter(rnd, jx, (double) jy, (double) jz); + } + public final VecD3D jitter(Random rnd, float jx, double jy, float jz) { + return jitter(rnd, (double) jx, jy, (double) jz); + } + public final VecD3D jitter(Random rnd, float jx, float jy, double jz) { + return jitter(rnd, (double) jx, (double) jy, jz); + } + public final VecD3D jitter(Random rnd, double jx, double jy, float jz) { + return jitter(rnd, jx, jy, (double) jz); + } + public final VecD3D jitter(Random rnd, double jx, float jy, double jz) { + return jitter(rnd, jx, (double) jy, jz); + } + public final VecD3D jitter(Random rnd, float jx, double jy, double jz) { + return jitter(rnd, (double) jx, jy, jz); + } + public final VecD3D jitter(Random rnd, double jx, double jy, double jz) { + x += MathUtils.normalizedRandom(rnd) * jx; + y += MathUtils.normalizedRandom(rnd) * jy; + z += MathUtils.normalizedRandom(rnd) * jz; + return this; + } + + public final VecD3D jitter(Random rnd, Vec3D jitterVec) { + return jitter(rnd, jitterVec.x, jitterVec.y, jitterVec.z); + } + public final VecD3D jitter(Random rnd, VecD3D jitterVec) { + return jitter(rnd, jitterVec.x, jitterVec.y, jitterVec.z); + } + + /** + * Adds random jitter to the vector in the range defined by the given vector + * components and using the default {@link Random} generator of + * {@link MathUtils}. + * + * @param jitterVec + * the jitter vec + * + * @return itself + */ + public final VecD3D jitter(Vec3D jitterVec) { + return jitter(jitterVec.x, jitterVec.y, jitterVec.z); + } + public final VecD3D jitter(VecD3D jitterVec) { + return jitter(jitterVec.x, jitterVec.y, jitterVec.z); + } + + /** + * Limits the vector's magnitude to the length given. + * + * @param lim + * new maximum magnitude + * + * @return itself + */ + public final VecD3D limit(float lim) { + if (magSquared() > lim * lim) { + return normalize().scaleSelf(lim); + } + return this; + } + public final VecD3D limit(double lim) { + if (magSquared() > lim * lim) { + return normalize().scaleSelf(lim); + } + return this; + } + + public final double magnitude() { + return Math.sqrt(x * x + y * y + z * z); + } + + public final double magSquared() { + return x * x + y * y + z * z; + } + + /** + * Max self. + * + * @param b + * the b + * + * @return the vec3 d + */ + public final VecD3D maxSelf(ReadonlyVec3D b) { + x = MathUtils.max(x, b.x()); + y = MathUtils.max(y, b.y()); + z = MathUtils.max(z, b.z()); + return this; + } + public final VecD3D maxSelf(ReadonlyVecD3D b) { + x = MathUtils.max(x, b.x()); + y = MathUtils.max(y, b.y()); + z = MathUtils.max(z, b.z()); + return this; + } + + /** + * Min self. + * + * @param b + * the b + * + * @return the vec3 d + */ + public final VecD3D minSelf(ReadonlyVec3D b) { + x = MathUtils.min(x, b.x()); + y = MathUtils.min(y, b.y()); + z = MathUtils.min(z, b.z()); + return this; + } + public final VecD3D minSelf(ReadonlyVecD3D b) { + x = MathUtils.min(x, b.x()); + y = MathUtils.min(y, b.y()); + z = MathUtils.min(z, b.z()); + return this; + } + + /** + * Applies a uniform modulo operation to the vector, using the same base for + * all components. + * + * @param base + * the base + * + * @return itself + */ + public final VecD3D modSelf(float base) { + x %= base; + y %= base; + z %= base; + return this; + } + public final VecD3D modSelf(double base) { + x %= base; + y %= base; + z %= base; + return this; + } + + /** + * Calculates modulo operation for each vector component separately. + * + * @param bx + * the bx + * @param by + * the by + * @param bz + * the bz + * + * @return itself + */ + + public final VecD3D modSelf(float bx, float by, float bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + public final VecD3D modSelf(double bx, float by, float bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + public final VecD3D modSelf(float bx, double by, float bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + public final VecD3D modSelf(float bx, float by, double bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + public final VecD3D modSelf(double bx, double by, float bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + public final VecD3D modSelf(double bx, float by, double bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + public final VecD3D modSelf(float bx, double by, double bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + public final VecD3D modSelf(double bx, double by, double bz) { + x %= bx; + y %= by; + z %= bz; + return this; + } + + /** + * Normalizes the vector so that its magnitude = 1. + * + * @return itself + */ + public final VecD3D normalize() { + double mag = Math.sqrt(x * x + y * y + z * z); + if (mag > 0) { + mag = 1f / mag; + x *= mag; + y *= mag; + z *= mag; + } + return this; + } + + /** + * Normalizes the vector to the given length. + * + * @param len + * desired length + * @return itself + */ + public final VecD3D normalizeTo(float len) { + double mag = Math.sqrt(x * x + y * y + z * z); + if (mag > 0) { + mag = len / mag; + x *= mag; + y *= mag; + z *= mag; + } + return this; + } + public final VecD3D normalizeTo(double len) { + double mag = Math.sqrt(x * x + y * y + z * z); + if (mag > 0) { + mag = len / mag; + x *= mag; + y *= mag; + z *= mag; + } + return this; + } + + /** + * Replaces the vector components with their multiplicative inverse. + * + * @return itself + */ + public final VecD3D reciprocal() { + x = 1f / x; + y = 1f / y; + z = 1f / z; + return this; + } + + public final VecD3D reflect(ReadonlyVec3D normal) { + return reflect((ReadonlyVecD3D) normal); + } + public final VecD3D reflect(ReadonlyVecD3D normal) { + return set(normal.scale(this.dot(normal) * 2).subSelf(this)); + } + + /** + * Rotates the vector around the giving axis. + * + * @param axis + * rotation axis vector + * @param theta + * rotation angle (in radians) + * + * @return itself + */ + public final VecD3D rotateAroundAxis(ReadonlyVec3D axis, float theta) { + return rotateAroundAxis((ReadonlyVecD3D) axis, (double) theta); + } + public final VecD3D rotateAroundAxis(ReadonlyVecD3D axis, float theta) { + return rotateAroundAxis(axis, (double) theta); + } + public final VecD3D rotateAroundAxis(ReadonlyVec3D axis, double theta) { + return rotateAroundAxis((ReadonlyVecD3D) axis,theta); + } + public final VecD3D rotateAroundAxis(ReadonlyVecD3D axis, double theta) { + final double ax = axis.x(); + final double ay = axis.y(); + final double az = axis.z(); + final double ux = ax * x; + final double uy = ax * y; + final double uz = ax * z; + final double vx = ay * x; + final double vy = ay * y; + final double vz = ay * z; + final double wx = az * x; + final double wy = az * y; + final double wz = az * z; + final double si = Math.sin(theta); + final double co = Math.cos(theta); + double xx = (ax * (ux + vy + wz) + + (x * (ay * ay + az * az) - ax * (vy + wz)) * co + (-wy + vz) + * si); + double yy = (ay * (ux + vy + wz) + + (y * (ax * ax + az * az) - ay * (ux + wz)) * co + (wx - uz) + * si); + double zz = (az * (ux + vy + wz) + + (z * (ax * ax + ay * ay) - az * (ux + vy)) * co + (-vx + uy) + * si); + x = xx; + y = yy; + z = zz; + return this; + } + + /** + * Rotates the vector by the given angle around the X axis. + * + * @param theta + * the theta + * + * @return itself + */ + public final VecD3D rotateX(float theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double zz = co * z - si * y; + y = si * z + co * y; + z = zz; + return this; + } + public final VecD3D rotateX(double theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double zz = co * z - si * y; + y = si * z + co * y; + z = zz; + return this; + } + + /** + * Rotates the vector by the given angle around the Y axis. + * + * @param theta + * the theta + * + * @return itself + */ + public final VecD3D rotateY(float theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double xx = co * x - si * z; + z = si * x + co * z; + x = xx; + return this; + } + public final VecD3D rotateY(double theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double xx = co * x - si * z; + z = si * x + co * z; + x = xx; + return this; + } + + /** + * Rotates the vector by the given angle around the Z axis. + * + * @param theta + * the theta + * + * @return itself + */ + public final VecD3D rotateZ(float theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double xx = co * x - si * y; + y = si * x + co * y; + x = xx; + return this; + } + public final VecD3D rotateZ(double theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double xx = co * x - si * y; + y = si * x + co * y; + x = xx; + return this; + } + + public VecD3D roundTo(float prec) { + x = MathUtils.roundTo(x, prec); + y = MathUtils.roundTo(y, prec); + z = MathUtils.roundTo(z, prec); + return this; + } + public VecD3D roundTo(double prec) { + x = MathUtils.roundTo(x, prec); + y = MathUtils.roundTo(y, prec); + z = MathUtils.roundTo(z, prec); + return this; + } + + public VecD3D scale(float s) { + return new VecD3D(x * s, y * s, z * s); + } + public VecD3D scale(double s) { + return new VecD3D(x * s, y * s, z * s); + } + + public VecD3D scale(float a, float b, float c) { + return new VecD3D(x * a, y * b, z * c); + } + public VecD3D scale(double a, float b, float c) { + return new VecD3D(x * a, y * b, z * c); + } + public VecD3D scale(float a, double b, float c) { + return new VecD3D(x * a, y * b, z * c); + } + public VecD3D scale(float a, float b, double c) { + return new VecD3D(x * a, y * b, z * c); + } + public VecD3D scale(double a, double b, float c) { + return new VecD3D(x * a, y * b, z * c); + } + public VecD3D scale(double a, float b, double c) { + return new VecD3D(x * a, y * b, z * c); + } + public VecD3D scale(float a, double b, double c) { + return new VecD3D(x * a, y * b, z * c); + } + public VecD3D scale(double a, double b, double c) { + return new VecD3D(x * a, y * b, z * c); + } + + public VecD3D scale(ReadonlyVec3D s) { + return new VecD3D(x * s.x(), y * s.y(), z * s.z()); + } + public VecD3D scale(ReadonlyVecD3D s) { + return new VecD3D(x * s.x(), y * s.y(), z * s.z()); + } + + public VecD3D scale(Vec3D s) { + return new VecD3D(x * s.x, y * s.y, z * s.z); + } + public VecD3D scale(VecD3D s) { + return new VecD3D(x * s.x, y * s.y, z * s.z); + } + + /** + * Scales vector uniformly and overrides coordinates with result. + * + * @param s + * scale factor + * + * @return itself + */ + public VecD3D scaleSelf(float s) { + x *= s; + y *= s; + z *= s; + return this; + } + public VecD3D scaleSelf(double s) { + x *= s; + y *= s; + z *= s; + return this; + } + + /** + * Scales vector non-uniformly by vector {a,b,c} and overrides coordinates + * with result. + * + * @param a + * scale factor for X coordinate + * @param b + * scale factor for Y coordinate + * @param c + * scale factor for Z coordinate + * + * @return itself + */ + public VecD3D scaleSelf(float a, float b, float c) { + x *= a; + y *= b; + z *= c; + return this; + } + public VecD3D scaleSelf(double a, float b, float c) { + x *= a; + y *= b; + z *= c; + return this; + } + public VecD3D scaleSelf(float a, double b, float c) { + x *= a; + y *= b; + z *= c; + return this; + } + public VecD3D scaleSelf(float a, float b, double c) { + x *= a; + y *= b; + z *= c; + return this; + } + public VecD3D scaleSelf(double a, double b, float c) { + x *= a; + y *= b; + z *= c; + return this; + } + public VecD3D scaleSelf(double a, float b, double c) { + x *= a; + y *= b; + z *= c; + return this; + } + public VecD3D scaleSelf(float a, double b, double c) { + x *= a; + y *= b; + z *= c; + return this; + } + public VecD3D scaleSelf(double a, double b, double c) { + x *= a; + y *= b; + z *= c; + return this; + } + + public VecD3D scaleSelf(ReadonlyVec3D s) { + x *= s.x(); + y *= s.y(); + z *= s.z(); + return this; + } + public VecD3D scaleSelf(ReadonlyVecD3D s) { + x *= s.x(); + y *= s.y(); + z *= s.z(); + return this; + } + + /** + * Scales vector non-uniformly by vector v and overrides coordinates with + * result. + * + * @param s + * scale vector + * + * @return itself + */ + + public VecD3D scaleSelf(Vec3D s) { + x *= s.x; + y *= s.y; + z *= s.z; + return this; + } + public VecD3D scaleSelf(VecD3D s) { + x *= s.x; + y *= s.y; + z *= s.z; + return this; + } + + /** + * Overrides coordinates with the given values. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + * + * @return itself + */ + public VecD3D set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + public VecD3D set(double x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + public VecD3D set(float x, double y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + public VecD3D set(float x, float y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + public VecD3D set(double x, double y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + public VecD3D set(double x, float y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + public VecD3D set(float x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + public VecD3D set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public VecD3D set(ReadonlyVec3D v) { + x = v.x(); + y = v.y(); + z = v.z(); + return this; + } + public VecD3D set(ReadonlyVecD3D v) { + x = v.x(); + y = v.y(); + z = v.z(); + return this; + } + + /** + * Overrides coordinates with the ones of the given vector. + * + * @param v + * vector to be copied + * + * @return itself + */ + public VecD3D set(Vec3D v) { + x = v.x; + y = v.y; + z = v.z; + return this; + } + public VecD3D set(VecD3D v) { + x = v.x; + y = v.y; + z = v.z; + return this; + } + + public final VecD3D setComponent(AxisD id, float val) { + return setComponent(id, (double) val); + } + public final VecD3D setComponent(AxisD id, double val) { + switch (id) { + case X: + x = val; + break; + case Y: + y = val; + break; + case Z: + z = val; + break; + } + return this; + } + + public final VecD3D setComponent(int id, float val) { + return setComponent(id, (double) val); + } + public final VecD3D setComponent(int id, double val) { + switch (id) { + case 0: + x = val; + break; + case 1: + y = val; + break; + case 2: + z = val; + break; + } + return this; + } + + public VecD3D setX(float x) { + this.x = x; + return this; + } + public VecD3D setX(double x) { + this.x = x; + return this; + } + + /** + * Overrides XY coordinates with the ones of the given 2D vector. + * + * @param v + * 2D vector + * + * @return itself + */ + public VecD3D setXY(Vec2D v) { + x = v.x; + y = v.y; + return this; + } + public VecD3D setXY(VecD2D v) { + x = v.x; + y = v.y; + return this; + } + + public VecD3D setY(float y) { + this.y = y; + return this; + } + public VecD3D setY(double y) { + this.y = y; + return this; + } + + public VecD3D setZ(float z) { + this.z = z; + return this; + } + public VecD3D setZ(double z) { + this.z = z; + return this; + } + + public VecD3D shuffle(int iterations) { + double t; + for (int i = 0; i < iterations; i++) { + switch (MathUtils.random(3)) { + case 0: + t = x; + x = y; + y = t; + break; + case 1: + t = x; + x = z; + z = t; + break; + case 2: + t = y; + y = z; + z = t; + break; + } + } + return this; + } + + /** + * Replaces all vector components with the signum of their original values. + * In other words if a components value was negative its new value will be + * -1, if zero => 0, if positive => +1 + * + * @return itself + */ + public VecD3D signum() { + x = (x < 0 ? -1 : x == 0 ? 0 : 1); + y = (y < 0 ? -1 : y == 0 ? 0 : 1); + z = (z < 0 ? -1 : z == 0 ? 0 : 1); + return this; + } + + /** + * Rounds the vector to the closest major axis. Assumes the vector is + * normalized. + * + * @return itself + */ + public final VecD3D snapToAxis() { + if (MathUtils.abs(x) < 0.5f) { + x = 0; + } else { + x = x < 0 ? -1 : 1; + y = z = 0; + } + if (MathUtils.abs(y) < 0.5f) { + y = 0; + } else { + y = y < 0 ? -1 : 1; + x = z = 0; + } + if (MathUtils.abs(z) < 0.5f) { + z = 0; + } else { + z = z < 0 ? -1 : 1; + x = y = 0; + } + return this; + } + + public final VecD3D sub(float a, float b, float c) { + return new VecD3D(x - a, y - b, z - c); + } + public final VecD3D sub(double a, float b, float c) { + return new VecD3D(x - a, y - b, z - c); + } + public final VecD3D sub(float a, double b, float c) { + return new VecD3D(x - a, y - b, z - c); + } + public final VecD3D sub(float a, float b, double c) { + return new VecD3D(x - a, y - b, z - c); + } + public final VecD3D sub(double a, double b, float c) { + return new VecD3D(x - a, y - b, z - c); + } + public final VecD3D sub(double a, float b, double c) { + return new VecD3D(x - a, y - b, z - c); + } + public final VecD3D sub(float a, double b, double c) { + return new VecD3D(x - a, y - b, z - c); + } + public final VecD3D sub(double a, double b, double c) { + return new VecD3D(x - a, y - b, z - c); + } + + + public final VecD3D sub(ReadonlyVec3D v) { + return new VecD3D(x - v.x(), y - v.y(), z - v.z()); + } + public final VecD3D sub(ReadonlyVecD3D v) { + return new VecD3D(x - v.x(), y - v.y(), z - v.z()); + } + + public final VecD3D sub(Vec3D v) { + return new VecD3D(x - v.x, y - v.y, z - v.z); + } + public final VecD3D sub(VecD3D v) { + return new VecD3D(x - v.x, y - v.y, z - v.z); + } + + /** + * Subtracts vector {a,b,c} and overrides coordinates with result. + * + * @param a + * X coordinate + * @param b + * Y coordinate + * @param c + * Z coordinate + * + * @return itself + */ + public final VecD3D subSelf(float a, float b, float c) { + x -= a; + y -= b; + z -= c; + return this; + } + public final VecD3D subSelf(double a, float b, float c) { + x -= a; + y -= b; + z -= c; + return this; + } + public final VecD3D subSelf(float a, double b, float c) { + x -= a; + y -= b; + z -= c; + return this; + } + public final VecD3D subSelf(float a, float b, double c) { + x -= a; + y -= b; + z -= c; + return this; + } + public final VecD3D subSelf(double a, double b, float c) { + x -= a; + y -= b; + z -= c; + return this; + } + public final VecD3D subSelf(double a, float b, double c) { + x -= a; + y -= b; + z -= c; + return this; + } + public final VecD3D subSelf(float a, double b, double c) { + x -= a; + y -= b; + z -= c; + return this; + } + public final VecD3D subSelf(double a, double b, double c) { + x -= a; + y -= b; + z -= c; + return this; + } + + + public final VecD3D subSelf(ReadonlyVec3D v) { + x -= v.x(); + y -= v.y(); + z -= v.z(); + return this; + } + public final VecD3D subSelf(ReadonlyVecD3D v) { + x -= v.x(); + y -= v.y(); + z -= v.z(); + return this; + } + + /** + * Subtracts vector v and overrides coordinates with result. + * + * @param v + * vector to be subtracted + * + * @return itself + */ + public final VecD3D subSelf(Vec3D v) { + x -= v.x; + y -= v.y; + z -= v.z; + return this; + } + public final VecD3D subSelf(VecD3D v) { + x -= v.x; + y -= v.y; + z -= v.z; + return this; + } + + public final VecD2D toD2DXY() { + return new VecD2D(x, y); + } + + public final VecD2D toD2DXZ() { + return new VecD2D(x, z); + } + + public final VecD2D toD2DYZ() { + return new VecD2D(y, z); + } + + public VecD4D toD4D() { + return new VecD4D(x, y, z, 1); + } + + public VecD4D toD4D(float w) { + return new VecD4D(x, y, z, w); + } + public VecD4D toD4D(double w) { + return new VecD4D(x, y, z, w); + } + + public double[] toArray() { + return new double[] { + x, y, z + }; + } + + public double[] toArray4(float w) { + return new double[] { + x, y, z,(double) w + }; + } + public double[] toArray4(double w) { + return new double[] { + x, y, z, w + }; + } + + public final VecD3D toCartesian() { + final double a = x * Math.cos(z); + final double xx = a * Math.cos(y); + final double yy = x * Math.sin(z); + final double zz = a * Math.sin(y); + x = xx; + y = yy; + z = zz; + return this; + } + + public final VecD3D toSpherical() { + final double xx = Math.abs(x) <= MathUtils.EPS ? MathUtils.EPS : x; + final double zz = z; + + final double radius = Math.sqrt((xx * xx) + (y * y) + (zz * zz)); + z = Math.asin(y / radius); + y = Math.atan(zz / xx) + (xx < 0.0 ? MathUtils.PI : 0); + x = radius; + return this; + } + + public String toString() { + final StringBuffer sb = new StringBuffer(48); + sb.append("{x:").append(x).append(", y:").append(y).append(", z:") + .append(z).append("}"); + return sb.toString(); + } + + public final double x() { + return x; + } + + public final double y() { + return y; + } + + public final double z() { + return z; + } + +} diff --git a/src.core/toxi/geom/VecD4D.java b/src.core/toxi/geom/VecD4D.java new file mode 100644 index 00000000..7d03363a --- /dev/null +++ b/src.core/toxi/geom/VecD4D.java @@ -0,0 +1,752 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; + +public class VecD4D implements ReadonlyVecD4D, Cloneable { + + /** X coordinate */ + @XmlAttribute(required = true) + public double x; + + /** Y coordinate */ + @XmlAttribute(required = true) + public double y; + + /** Z coordinate */ + @XmlAttribute(required = true) + public double z; + + /** W coordinate (weight) */ + @XmlAttribute(required = true) + public double w; + + public VecD4D() { + } + + public VecD4D(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + public VecD4D(Vec4D v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = v.w; + } + + public VecD4D(ReadonlyVecD3D v, double w) { + this.x = v.x(); + this.y = v.y(); + this.z = v.z(); + this.w = w; + } + + public VecD4D(ReadonlyVecD4D v) { + set(v); + } + + public VecD4D abs() { + x = MathUtils.abs(x); + y = MathUtils.abs(y); + z = MathUtils.abs(z); + w = MathUtils.abs(w); + return this; + } + + public final VecD4D add(ReadonlyVecD4D v) { + return new VecD4D(x + v.x(), y + v.y(), z + v.z(), w + v.w()); + } + + public final VecD4D addScaled(ReadonlyVecD4D t, double s) { + return new VecD4D(s * t.x(), s * t.y(), s * t.z(), s * t.w()); + } + + public final VecD4D addScaledSelf(ReadonlyVecD4D t, double s) { + x += s * t.x(); + y += s * t.y(); + z += s * t.z(); + w += s * t.w(); + return this; + } + + public final VecD4D addSelf(ReadonlyVecD4D v) { + this.x += v.x(); + this.y += v.y(); + this.z += v.z(); + this.w += v.w(); + return this; + } + + public final VecD4D addXYZ(double xx, double yy, double zz) { + return new VecD4D(x + xx, y + yy, z + zz, w); + } + + public final VecD4D addXYZ(ReadonlyVecD3D v) { + return new VecD4D(x + v.x(), y + v.y(), z + v.z(), w); + } + + public final VecD4D addXYZSelf(double xx, double yy, double zz) { + x += xx; + y += yy; + z += zz; + return this; + } + + public final VecD4D addXYZSelf(ReadonlyVecD3D v) { + this.x += v.x(); + this.y += v.y(); + this.z += v.z(); + return this; + } + + /** + * Returns the (4-space) angle in radians between this vector and the vector + * parameter; the return value is constrained to the range [0,PI]. + * + * @param v + * the other vector + * @return the angle in radians in the range [0,PI] + */ + public final double angleBetween(ReadonlyVecD4D v) { + double vDot = dot(v) / (magnitude() * v.magnitude()); + if (vDot < -1.0) { + vDot = -1.0; + } + if (vDot > 1.0) { + vDot = 1.0; + } + return Math.acos(vDot); + } + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + public int compareTo(ReadonlyVecD4D v) { + if (x == v.x() && y == v.y() && z == v.z() && w == v.w()) { + return 0; + } + double a = magSquared(); + double b = v.magSquared(); + if (a < b) { + return -1; + } + return +1; + } + + public final VecD4D copy() { + return new VecD4D(this); + } + + public final double distanceTo(ReadonlyVecD4D v) { + if (v != null) { + final double dx = x - v.x(); + final double dy = y - v.y(); + final double dz = z - v.z(); + final double dw = w - v.z(); + return Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw); + } else { + return Double.NaN; + } + } + + public final double distanceToSquared(ReadonlyVecD4D v) { + if (v != null) { + final double dx = x - v.x(); + final double dy = y - v.y(); + final double dz = z - v.z(); + final double dw = w - v.z(); + return dx * dx + dy * dy + dz * dz + dw * dw; + } else { + return Double.NaN; + } + } + + public final double dot(ReadonlyVecD4D v) { + return (x * v.x() + y * v.y() + z * v.z() + w * v.w()); + } + + /** + * Returns true if the Object v is of type ReadonlyVecD4D and all of the data + * members of v are equal to the corresponding data members in this vector. + * + * @param v + * the Object with which the comparison is made + * @return true or false + */ + public boolean equals(Object v) { + try { + ReadonlyVecD4D vv = (ReadonlyVecD4D) v; + return (x == vv.x() && y == vv.y() && z == vv.z() && w == vv.w()); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + /** + * Returns true if the Object v is of type VecD4D and all of the data members + * of v are equal to the corresponding data members in this vector. + * + * @param v + * the vector with which the comparison is made + * @return true or false + */ + public boolean equals(ReadonlyVecD4D v) { + try { + return (x == v.x() && y == v.y() && z == v.z() && w == v.w()); + } catch (NullPointerException e) { + return false; + } + } + + public boolean equalsWithTolerance(ReadonlyVecD4D v, double tolerance) { + try { + double diff = x - v.x(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = y - v.y(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = z - v.z(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + diff = w - v.w(); + if (Double.isNaN(diff)) { + return false; + } + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + return true; + } catch (NullPointerException e) { + return false; + } + } + + public VecD4D getAbs() { + return copy().abs(); + } + + public final VecD4D getInvertedXYZ() { + return copy().invertXYZ(); + } + + public VecD4D getMapped(ScaleMap map) { + return new VecD4D(map.getClippedValueFor(x), + map.getClippedValueFor(y), + map.getClippedValueFor(z), + map.getClippedValueFor(w)); + } + + public VecD4D getMappedXYZ(ScaleMap map) { + return new VecD4D(map.getClippedValueFor(x), + map.getClippedValueFor(y), + map.getClippedValueFor(z), w); + } + + public VecD4D getNormalized() { + return copy().normalize(); + } + + public VecD4D getNormalizedTo(double len) { + return copy().normalizeTo(len); + } + + public VecD4D getRotatedAroundAxis(ReadonlyVecD3D axis, double theta) { + return copy().rotateAroundAxis(axis, theta); + } + + public VecD4D getRotatedX(double theta) { + return copy().rotateX(theta); + } + + public VecD4D getRotatedY(double theta) { + return copy().rotateY(theta); + } + + public VecD4D getRotatedZ(double theta) { + return copy().rotateZ(theta); + } + + public VecD4D getRoundedXYZTo(double prec) { + return copy().roundXYZTo(prec); + } + + public VecD4D getUnweighted() { + return copy().unweight(); + } + + public VecD4D getWeighted() { + return copy().weight(); + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different VecD4D objects with identical data values (i.e., VecD4D.equals + * returns true) will return the same hash code value. Two objects with + * different data members may return the same hash value, although this is + * not likely. + * + * @return the integer hash code value + */ + public int hashCode() { + return ((Double)x).hashCode()+((Double)y).hashCode()+((Double)z).hashCode()+((Double)w).hashCode(); + } + + public final VecD4D interpolateTo(ReadonlyVecD4D v, double t) { + return copy().interpolateToSelf(v, t); + } + + public final VecD4D interpolateTo(ReadonlyVecD4D v, double f, + InterpolateStrategy s) { + return new VecD4D(s.interpolate(x, v.x(), f), + s.interpolate(y, v.y(), f), s.interpolate(z, v.z(), f), + s.interpolate(w, v.w(), f)); + } + + public final VecD4D interpolateToSelf(ReadonlyVecD4D v, double t) { + this.x += (v.x() - x) * t; + this.y += (v.y() - y) * t; + this.z += (v.z() - z) * t; + this.w += (v.w() - w) * t; + return this; + } + + public final VecD4D interpolateToSelf(ReadonlyVecD4D v, double f, + InterpolateStrategy s) { + x = s.interpolate(x, v.x(), f); + y = s.interpolate(y, v.y(), f); + z = s.interpolate(z, v.z(), f); + w = s.interpolate(w, v.w(), f); + return this; + } + + public final VecD4D invertXYZ() { + this.x *= -1; + this.y *= -1; + this.z *= -1; + return this; + } + + public final boolean isZeroVector() { + return MathUtils.abs(x) < MathUtils.EPS + && MathUtils.abs(y) < MathUtils.EPS + && MathUtils.abs(z) < MathUtils.EPS + && MathUtils.abs(w) < MathUtils.EPS; + } + + public final double magnitude() { + return Math.sqrt(x * x + y * y + z * z + w * w); + } + + public final double magSquared() { + return x * x + y * y + z * z + w * w; + } + + /** + * Normalizes the vector so that its magnitude = 1. + * + * @return itself + */ + public final VecD4D normalize() { + double mag = Math.sqrt(x * x + y * y + z * z); + if (mag > 0) { + mag = 1f / mag; + x *= mag; + y *= mag; + z *= mag; + w *= mag; + } + return this; + } + + /** + * Normalizes the vector to the given length. + * + * @param len + * desired length + * @return itself + */ + public final VecD4D normalizeTo(double len) { + double mag = Math.sqrt(x * x + y * y + z * z); + if (mag > 0) { + mag = len / mag; + x *= mag; + y *= mag; + z *= mag; + w *= mag; + } + return this; + } + + /** + * Rotates the vector around the giving axis. + * + * @param axis + * rotation axis vector + * @param theta + * rotation angle (in radians) + * + * @return itself + */ + public final VecD4D rotateAroundAxis(ReadonlyVecD3D axis, double theta) { + final double ax = axis.x(); + final double ay = axis.y(); + final double az = axis.z(); + final double ux = ax * x; + final double uy = ax * y; + final double uz = ax * z; + final double vx = ay * x; + final double vy = ay * y; + final double vz = ay * z; + final double wx = az * x; + final double wy = az * y; + final double wz = az * z; + final double si = Math.sin(theta); + final double co = Math.cos(theta); + double xx = (ax * (ux + vy + wz) + + (x * (ay * ay + az * az) - ax * (vy + wz)) * co + (-wy + vz) + * si); + double yy = (ay * (ux + vy + wz) + + (y * (ax * ax + az * az) - ay * (ux + wz)) * co + (wx - uz) + * si); + double zz = (az * (ux + vy + wz) + + (z * (ax * ax + ay * ay) - az * (ux + vy)) * co + (-vx + uy) + * si); + x = xx; + y = yy; + z = zz; + return this; + } + + /** + * Rotates the vector by the given angle around the X axis. + * + * @param theta + * the theta + * + * @return itself + */ + public final VecD4D rotateX(double theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double zz = co * z - si * y; + y = si * z + co * y; + z = zz; + return this; + } + + /** + * Rotates the vector by the given angle around the Y axis. + * + * @param theta + * the theta + * + * @return itself + */ + public final VecD4D rotateY(double theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double xx = co * x - si * z; + z = si * x + co * z; + x = xx; + return this; + } + + /** + * Rotates the vector by the given angle around the Z axis. + * + * @param theta + * the theta + * + * @return itself + */ + public final VecD4D rotateZ(double theta) { + final double co = Math.cos(theta); + final double si = Math.sin(theta); + final double xx = co * x - si * y; + y = si * x + co * y; + x = xx; + return this; + } + + public VecD4D roundXYZTo(double prec) { + x = MathUtils.roundTo(x, prec); + y = MathUtils.roundTo(y, prec); + z = MathUtils.roundTo(z, prec); + return this; + } + + public final VecD4D scale(double s) { + return new VecD4D(x * s, y * s, z * s, w * s); + } + + public final VecD4D scale(double xx, double yy, double zz, double ww) { + return new VecD4D(x * xx, y * yy, z * zz, w * ww); + } + + public VecD4D scale(ReadonlyVecD4D s) { + return copy().scaleSelf(s); + } + + public final VecD4D scaleSelf(double s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.w *= s; + return this; + } + + public VecD4D scaleSelf(ReadonlyVecD4D s) { + this.x *= s.x(); + this.y *= s.y(); + this.z *= s.z(); + this.w *= s.w(); + return this; + } + + public final VecD4D scaleXYZSelf(double s) { + this.x *= s; + this.y *= s; + this.z *= s; + return this; + } + + public final VecD4D scaleXYZSelf(double xscale, double yscale, double zscale) { + this.x *= xscale; + this.y *= yscale; + this.z *= zscale; + return this; + } + + /** + * Overrides coordinates with the given values. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + * @param w + * the w + * + * @return itself + */ + public VecD4D set(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + public final VecD4D set(ReadonlyVecD4D v) { + this.x = v.x(); + this.y = v.y(); + this.z = v.z(); + this.w = v.w(); + return this; + } + + public VecD4D setW(double w) { + this.w = w; + return this; + } + + public VecD4D setX(double x) { + this.x = x; + return this; + } + + public final VecD4D setXYZ(ReadonlyVecD3D v) { + this.x = v.x(); + this.y = v.y(); + this.z = v.z(); + return this; + } + + public VecD4D setY(double y) { + this.y = y; + return this; + } + + public VecD4D setZ(double z) { + this.z = z; + return this; + } + + public final VecD4D sub(ReadonlyVecD4D v) { + return new VecD4D(x - v.x(), y - v.y(), z - v.z(), w - v.w()); + } + + public final VecD4D subSelf(ReadonlyVecD4D v) { + this.x -= v.x(); + this.y -= v.y(); + this.z -= v.z(); + this.w -= v.w(); + return this; + } + + public final VecD4D subXYZ(double xx, double yy, double zz) { + return new VecD4D(x - xx, y - yy, z - zz, w); + } + + public final VecD4D subXYZ(ReadonlyVecD3D v) { + return new VecD4D(x - v.x(), y - v.y(), z - v.z(), w); + } + + public final VecD4D subXYZSelf(double xx, double yy, double zz) { + this.x -= xx; + this.y -= yy; + this.z -= zz; + return this; + } + + public final VecD4D subXYZSelf(ReadonlyVecD3D v) { + this.x -= v.x(); + this.y -= v.y(); + this.z -= v.z(); + return this; + } + + public final VecD3D to3D() { + return new VecD3D(x, y, z); + } + + public double[] toArray() { + return new double[] { + x, y, z, w + }; + } + + public String toString() { + return "[x=" + x + ", y=" + y + ", z=" + z + ", w=" + w + "]"; + } + + public final VecD4D translate(double xx, double yy, double zz) { + this.x += w * xx; + this.y += w * yy; + this.z += w * zz; + return this; + } + + /** + * Divides each coordinate bythe weight with and sets the coordinate to the + * newly calculatd ones. + */ + public final VecD4D unweight() { + double iw = MathUtils.abs(w) > MathUtils.EPS ? 1f / w : 0; + x *= iw; + y *= iw; + z *= iw; + return this; + } + + public final VecD3D unweightInto(VecD3D out) { + double iw = MathUtils.abs(w) > MathUtils.EPS ? 1f / w : 0; + out.x = x * iw; + out.y = y * iw; + out.z = z * iw; + return out; + } + + /** + * @return the w + */ + public final double w() { + return w; + } + + /** + * Multiplies the weight with each coordinate and sets the coordinate to the + * newly calculatd ones. + * + * @return itself + */ + public final VecD4D weight() { + x *= w; + y *= w; + z *= w; + return this; + } + + public final VecD3D weightInto(VecD3D out) { + out.x = x * w; + out.y = y * w; + out.z = z * w; + return out; + } + + /** + * @return the x + */ + public final double x() { + return x; + } + + /** + * @return the y + */ + public final double y() { + return y; + } + + /** + * @return the z + */ + public final double z() { + return z; + } +} diff --git a/src.core/toxi/geom/XAxisCylinderD.java b/src.core/toxi/geom/XAxisCylinderD.java new file mode 100644 index 00000000..0153fd39 --- /dev/null +++ b/src.core/toxi/geom/XAxisCylinderD.java @@ -0,0 +1,52 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +public class XAxisCylinderD extends AxisAlignedCylinderD { + + public XAxisCylinderD(ReadonlyVecD3D pos, double radius, double length) { + super(pos, radius, length); + } + + public boolean containsPoint(ReadonlyVecD3D p) { + if (MathUtils.abs(p.x() - pos.x) < length * 0.5) { + double dy = p.y() - pos.y; + double dz = p.z() - pos.z; + if (Math.abs(dz * dz + dy * dy) < radiusSquared) { + return true; + } + } + return false; + } + + public VecD3D.AxisD getMajorAxis() { + return VecD3D.AxisD.X; + } +} diff --git a/src.core/toxi/geom/YAxisCylinderD.java b/src.core/toxi/geom/YAxisCylinderD.java new file mode 100644 index 00000000..3ccd704a --- /dev/null +++ b/src.core/toxi/geom/YAxisCylinderD.java @@ -0,0 +1,53 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +public class YAxisCylinderD extends AxisAlignedCylinderD { + + public YAxisCylinderD(ReadonlyVecD3D pos, double radius, double length) { + super(pos, radius, length); + } + + public boolean containsPoint(ReadonlyVecD3D p) { + if (MathUtils.abs(p.y() - pos.y) < length * 0.5) { + double dx = p.x() - pos.x; + double dz = p.z() - pos.z; + if (Math.abs(dz * dz + dx * dx) < radiusSquared) { + return true; + } + } + return false; + } + + public VecD3D.AxisD getMajorAxis() { + return VecD3D.AxisD.Y; + } + +} diff --git a/src.core/toxi/geom/ZAxisCylinderD.java b/src.core/toxi/geom/ZAxisCylinderD.java new file mode 100644 index 00000000..31a987d8 --- /dev/null +++ b/src.core/toxi/geom/ZAxisCylinderD.java @@ -0,0 +1,52 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +public class ZAxisCylinderD extends AxisAlignedCylinderD { + + public ZAxisCylinderD(ReadonlyVecD3D pos, double radius, double length) { + super(pos, radius, length); + } + + public boolean containsPoint(ReadonlyVecD3D p) { + if (MathUtils.abs(p.z() - pos.z) < length * 0.5) { + double dx = p.x() - pos.x; + double dy = p.y() - pos.y; + if (Math.abs(dx * dx + dy * dy) < radiusSquared) { + return true; + } + } + return false; + } + + public VecD3D.AxisD getMajorAxis() { + return VecD3D.AxisD.Z; + } +} diff --git a/src.core/toxi/geom/mesh/BezierPatchD.java b/src.core/toxi/geom/mesh/BezierPatchD.java new file mode 100644 index 00000000..ed3b536b --- /dev/null +++ b/src.core/toxi/geom/mesh/BezierPatchD.java @@ -0,0 +1,166 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.VecD3D; + +/** + * 4x4 bezier patch implementation with tesselation support (dynamic resolution) + * for generating triangle mesh representations. + */ +public class BezierPatchD { + + /** + * Computes a single point on the bezier surface given by the 2d array of + * control points. The desired point's coordinates have to be specified in + * UV space (range 0.0 .. 1.0). The implementation does not check or enforce + * the correct range of these coords and will not return valid points if the + * range is exceeded. + * + * @param u + * positive normalized U coordinate on the bezier surface + * @param v + * positive normalized V coordinate on the bezier surface + * @param points + * 4x4 array defining the patch's control points + * @return point on surface + */ + public static VecD3D computePointAt(double u, double v, VecD3D[][] points) { + final double u1 = 1 - u; + final double u1squared = u1 * u1 * 3 * u; + final double u1cubed = u1 * u1 * u1; + final double usquared = u * u; + final double v1 = 1 - v; + final double vsquared = v * v * 3; + final double v1squared = v1 * v1 * 3; + final double v1cubed = v1 * v1 * v1; + final double vcubed = v * v * v; + + final double u1usq = u1 * usquared * 3; + final double usqu = u * usquared; + final double v1vsq = v1 * vsquared; + final double v1sqv = v1squared * v; + + final VecD3D[] p0 = points[0]; + final VecD3D[] p1 = points[1]; + final VecD3D[] p2 = points[2]; + final VecD3D[] p3 = points[3]; + + final double x = u1cubed + * (p0[0].x * v1cubed + p0[1].x * v1sqv + p0[2].x * v1vsq + p0[3].x + * vcubed) + + u1squared + * (p1[0].x * v1cubed + p1[1].x * v1sqv + p1[2].x * v1vsq + p1[3].x + * vcubed) + + u1usq + * (p2[0].x * v1cubed + p2[1].x * v1sqv + p2[2].x * v1vsq + p2[3].x + * vcubed) + + usqu + * (p3[0].x * v1cubed + p3[1].x * v1sqv + p3[2].x * v1vsq + p3[3].x + * vcubed); + + final double y = u1cubed + * (p0[0].y * v1cubed + p0[1].y * v1sqv + p0[2].y * v1vsq + p0[3].y + * vcubed) + + u1squared + * (p1[0].y * v1cubed + p1[1].y * v1sqv + p1[2].y * v1vsq + p1[3].y + * vcubed) + + u1usq + * (p2[0].y * v1cubed + p2[1].y * v1sqv + p2[2].y * v1vsq + p2[3].y + * vcubed) + + usqu + * (p3[0].y * v1cubed + p3[1].y * v1sqv + p3[2].y * v1vsq + p3[3].y + * vcubed); + + final double z = u1cubed + * (p0[0].z * v1cubed + p0[1].z * v1sqv + p0[2].z * v1vsq + p0[3].z + * vcubed) + + u1squared + * (p1[0].z * v1cubed + p1[1].z * v1sqv + p1[2].z * v1vsq + p1[3].z + * vcubed) + + u1usq + * (p2[0].z * v1cubed + p2[1].z * v1sqv + p2[2].z * v1vsq + p2[3].z + * vcubed) + + usqu + * (p3[0].z * v1cubed + p3[1].z * v1sqv + p3[2].z * v1vsq + p3[3].z + * vcubed); + + return new VecD3D(x, y, z); + } + + public VecD3D[][] points; + + public BezierPatchD() { + points = new VecD3D[4][4]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + points[i][j] = new VecD3D(); + } + } + } + + public BezierPatchD(VecD3D[][] points) { + this.points = points; + } + + public VecD3D computePointAt(double u, double v) { + return computePointAt(u, v, points); + } + + public BezierPatchD set(int x, int y, VecD3D p) { + points[y][x].set(p); + return this; + } + + public MeshD3D toMesh(int res) { + return toMesh(null, res); + } + + public MeshD3D toMesh(MeshD3D mesh, int res) { + if (mesh == null) { + mesh = new TriangleMeshD(); + } + VecD3D[] curr = new VecD3D[res + 1]; + VecD3D[] prev = new VecD3D[res + 1]; + double r1 = 1f / res; + for (int y = 0; y <= res; y++) { + for (int x = 0; x <= res; x++) { + VecD3D p = computePointAt(x * r1, y * r1, points); + if (x > 0 && y > 0) { + mesh.addFaceD(p, curr[x - 1], prev[x - 1]); + mesh.addFaceD(p, prev[x - 1], prev[x]); + } + curr[x] = p; + } + VecD3D[] tmp = prev; + prev = curr; + curr = tmp; + } + return mesh; + } +} diff --git a/src.core/toxi/geom/mesh/BoxSelectorD.java b/src.core/toxi/geom/mesh/BoxSelectorD.java new file mode 100644 index 00000000..86309ba1 --- /dev/null +++ b/src.core/toxi/geom/mesh/BoxSelectorD.java @@ -0,0 +1,56 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.AABBD; + +/** + * A {@link VertexSelector} implementation for selecting vertices within a given + * {@link AABB}. + */ +public class BoxSelectorD extends VertexDSelector { + + public final AABBD box; + + public BoxSelectorD(MeshD3D mesh, AABBD box) { + super(mesh); + this.box = box; + } + + @Override + public VertexDSelector selectVertices() { + clearSelection(); + for (VertexD v : mesh.getVertices()) { + if (box.containsPoint(v)) { + selection.add(v); + } + } + return this; + } + +} diff --git a/src.core/toxi/geom/mesh/DefaultSelectorD.java b/src.core/toxi/geom/mesh/DefaultSelectorD.java new file mode 100644 index 00000000..595868a1 --- /dev/null +++ b/src.core/toxi/geom/mesh/DefaultSelectorD.java @@ -0,0 +1,42 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +public class DefaultSelectorD extends VertexDSelector { + + public DefaultSelectorD(MeshD3D mesh) { + super(mesh); + } + + @Override + public VertexDSelector selectVertices() { + clearSelection(); + selection.addAll(mesh.getVertices()); + return this; + } +} diff --git a/src.core/toxi/geom/mesh/FaceD.java b/src.core/toxi/geom/mesh/FaceD.java new file mode 100644 index 00000000..4effebda --- /dev/null +++ b/src.core/toxi/geom/mesh/FaceD.java @@ -0,0 +1,97 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.TriangleD3D; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; + +public class FaceD { + + public VertexD a, b, c; + public VecD2D uvA, uvB, uvC; + public VecD3D normal; + + public FaceD(VertexD a, VertexD b, VertexD c) { + this.a = a; + this.b = b; + this.c = c; + normal = a.sub(c).crossSelf(a.sub(b)).normalize(); + a.addFaceDNormal(normal); + b.addFaceDNormal(normal); + c.addFaceDNormal(normal); + } + + public FaceD(VertexD a, VertexD b, VertexD c, VecD2D uvA, VecD2D uvB, VecD2D uvC) { + this(a, b, c); + this.uvA = uvA; + this.uvB = uvB; + this.uvC = uvC; + } + + public void computeNormal() { + normal = a.sub(c).crossSelf(a.sub(b)).normalize(); + } + + public void flipVertexOrder() { + VertexD t = a; + a = b; + b = t; + normal.invert(); + } + + public VecD3D getCentroid() { + return a.add(b).addSelf(c).scale(1f / 3); + } + + public final VertexD[] getVertices(VertexD[] verts) { + if (verts != null) { + verts[0] = a; + verts[1] = b; + verts[2] = c; + } else { + verts = new VertexD[] { a, b, c }; + } + return verts; + } + + public String toString() { + return getClass().getName() + " " + a + ", " + b + ", " + c; + } + + /** + * Creates a generic {@link Triangle3D} instance using this face's vertices. + * The new instance is made up of copies of the original vertices and + * manipulating them will not impact the originals. + * + * @return triangle copy of this mesh face + */ + public TriangleD3D toTriangleD() { + return new TriangleD3D(a.copy(), b.copy(), c.copy()); + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh/LaplacianSmoothD.java b/src.core/toxi/geom/mesh/LaplacianSmoothD.java new file mode 100644 index 00000000..21945d80 --- /dev/null +++ b/src.core/toxi/geom/mesh/LaplacianSmoothD.java @@ -0,0 +1,74 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +import toxi.geom.VecD3D; + +/** + * Applies a laplacian smooth function to all vertices in the mesh + * + */ +public class LaplacianSmoothD implements WEMeshFilterStrategyD { + + public void filter(VertexDSelector selector, int numIterations) { + final Collection selection = selector.getSelection(); + if (!(selector.getMesh() instanceof WETriangleMesh)) { + throw new IllegalArgumentException( + "This filter requires a WETriangleMesh"); + } + final WETriangleMeshD mesh = (WETriangleMeshD) selector.getMesh(); + final HashMap filtered = new HashMap( + selection.size()); + for (int i = 0; i < numIterations; i++) { + filtered.clear(); + for (VertexD v : selection) { + final VecD3D laplacian = new VecD3D(); + final List neighbours = ((WEVertexD) v).getNeighbors(); + for (WEVertexD n : neighbours) { + laplacian.addSelf(n); + } + laplacian.scaleSelf(1f / neighbours.size()); + filtered.put(v, laplacian); + } + for (VertexD v : filtered.keySet()) { + mesh.vertices.get(v).set(filtered.get(v)); + } + mesh.rebuildIndex(); + } + mesh.computeFaceDNormals(); + mesh.computeVertexDNormals(); + } + + public void filter(WETriangleMeshD mesh, int numIterations) { + filter(new DefaultSelectorD(mesh).selectVertices(), numIterations); + } +} diff --git a/src.core/toxi/geom/mesh/MeshD3D.java b/src.core/toxi/geom/mesh/MeshD3D.java new file mode 100644 index 00000000..0730c3e1 --- /dev/null +++ b/src.core/toxi/geom/mesh/MeshD3D.java @@ -0,0 +1,169 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.util.Collection; + +import toxi.geom.AABBD; +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.SphereD; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; + +/** + * Common interface for 3D (triangle) mesh containers. + */ +public interface MeshD3D { + + /** + * Adds the given 3 points as triangle face to the mesh. The assumed vertex + * order is anti-clockwise. + * + * @param a + * @param b + * @param c + */ + public MeshD3D addFaceD(VecD3D a, VecD3D b, VecD3D c); + + /** + * Adds the given 3 points as triangle face to the mesh and assigns the + * given texture coordinates to each vertex. The assumed vertex order is + * anti-clockwise. + * + * @param a + * @param b + * @param c + * @param uvA + * @param uvB + * @param uvC + * @return itself + */ + public MeshD3D addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD2D uvA, VecD2D uvB, VecD2D uvC); + + public MeshD3D addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD3D n); + + public MeshD3D addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD3D n, VecD2D uvA, VecD2D uvB, VecD2D uvC); + + public MeshD3D addMeshD(MeshD3D mesh); + + /** + * Centers the mesh around the given pivot point (the centroid of its AABB). + * Method also updates & returns the new bounding box. + * + * @param origin + * new centroid or null (defaults to {0,0,0}) + */ + public AABBD center(ReadonlyVecD3D origin); + + /** + * Clears all counters, and vertex & face buffers. + */ + public MeshD3D clear(); + + /** + * Computes the mesh centroid, the average position of all vertices. + * + * @return centre point + */ + public VecD3D computeCentroid(); + + /** + * Re-calculates all face normals. + */ + public MeshD3D computeFaceDNormals(); + + /** + * Computes the smooth vertex normals for the entire mesh. + * + * @return itself + */ + public MeshD3D computeVertexDNormals(); + + /** + * Changes the vertex order of faces such that their normal is facing away + * from the mesh centroid. + * + * @return itself + */ + public MeshD3D faceOutwards(); + + /** + * Flips the vertex ordering between clockwise and anti-clockwise. FaceD + * normals are updated automatically too. + * + * @return itself + */ + public MeshD3D flipVertexDOrder(); + + /** + * Flips all vertices along the Y axis and reverses the vertex ordering of + * all faces to compensate and keep the direction of normals intact. + * + * @return itself + */ + public MeshD3D flipYAxis(); + + /** + * Computes & returns the axis-aligned bounding box of the mesh. + * + * @return bounding box + */ + public AABBD getBoundingBox(); + + /** + * Computes & returns the bounding sphere of the mesh. The origin of the + * sphere is the mesh's centroid. + * + * @return bounding sphere + */ + public SphereD getBoundingSphereD(); + + public VertexD getClosestVertexDToPoint(ReadonlyVecD3D p); + + public Collection getFaceDs(); + + /** + * Returns the number of triangles used. + * + * @return face count + */ + public int getNumFaceDs(); + + /** + * Returns the number of actual vertices used (unique vertices). + * + * @return vertex count + */ + public int getNumVertices(); + + public Collection getVertices(); + + public MeshD3D init(String name, int numV, int numF); + + public MeshD3D setName(String name); +} diff --git a/src.core/toxi/geom/mesh/MeshIntersectorD.java b/src.core/toxi/geom/mesh/MeshIntersectorD.java new file mode 100644 index 00000000..631e5d53 --- /dev/null +++ b/src.core/toxi/geom/mesh/MeshIntersectorD.java @@ -0,0 +1,78 @@ +package toxi.geom.mesh; + +import toxi.geom.AABBD; +import toxi.geom.IntersectorD3D; +import toxi.geom.IsectDataD3D; +import toxi.geom.RayD3D; +import toxi.geom.VecD3D; + +public class MeshIntersectorD implements IntersectorD3D { + + private static final double EPS = 0.00001f; + + private TriangleMeshD mesh; + private AABBD bounds; + + private final IsectDataD3D isec; + + public MeshIntersectorD(TriangleMeshD mesh) { + setMesh(mesh); + this.isec = new IsectDataD3D(); + } + + public IsectDataD3D getIntersectionDataD() { + return isec; + } + + public boolean intersectsRayD(RayD3D ray) { + isec.isIntersection = false; + if (bounds.intersectsRay(ray, 0, Double.MAX_VALUE) != null) { + VecD3D dir = ray.getDirection(); + double minD = Double.MAX_VALUE; + for (FaceD f : mesh.getFaceDs()) { + double d = intersectTriangleD(f.a, f.b, f.c, ray, dir); + if (d >= 0.0 && d < minD) { + isec.isIntersection = true; + isec.normal = f.normal; + minD = d; + } + } + if (isec.isIntersection) { + isec.pos = ray.getPointAtDistance(minD); + isec.dist = minD; + isec.dir = dir.getInverted(); + } + } + return isec.isIntersection; + } + + private double intersectTriangleD(VecD3D a, VecD3D b, VecD3D c, VecD3D ro, VecD3D dir) { + VecD3D e1 = b.sub(a); + VecD3D e2 = c.sub(a); + VecD3D pvec = dir.cross(e2); + double det = e1.dot(pvec); + if (det > -EPS && det < EPS) { + return -1; + } + double invDet = 1f / det; + VecD3D tvec = ro.sub(a); + double u = tvec.dot(pvec) * invDet; + if (u < 0.0 || u > 1.0) { + return -1; + } + VecD3D qvec = tvec.cross(e1); + double v = dir.dot(qvec) * invDet; + if (v < 0.0 || u + v > 1.0) { + return -1; + } + double t = e2.dot(qvec) * invDet; + return t; + } + + public void setMesh(TriangleMeshD mesh) { + this.mesh = mesh; + this.bounds = mesh.getBoundingBox(); + } + + +} diff --git a/src.core/toxi/geom/mesh/OBJWriter.java b/src.core/toxi/geom/mesh/OBJWriter.java index 1191b672..71c5832b 100644 --- a/src.core/toxi/geom/mesh/OBJWriter.java +++ b/src.core/toxi/geom/mesh/OBJWriter.java @@ -33,6 +33,7 @@ import java.io.PrintWriter; import toxi.geom.Vec3D; +import toxi.geom.VecD3D; /** * Extremely bare bones Wavefront OBJ 3D format exporter. Purely handles the @@ -123,4 +124,13 @@ public void vertex(Vec3D v) { objWriter.println("v " + v.x + " " + v.y + " " + v.z); numVerticesWritten++; } + public void normal(VecD3D n) { + objWriter.println("vn " + n.x + " " + n.y + " " + n.z); + numNormalsWritten++; + } + + public void vertex(VecD3D v) { + objWriter.println("v " + v.x + " " + v.y + " " + v.z); + numVerticesWritten++; + } } diff --git a/src.core/toxi/geom/mesh/PLYWriterD.java b/src.core/toxi/geom/mesh/PLYWriterD.java new file mode 100644 index 00000000..db46641d --- /dev/null +++ b/src.core/toxi/geom/mesh/PLYWriterD.java @@ -0,0 +1,178 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +package toxi.geom.mesh; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import toxi.geom.VecD3D; +import toxi.util.FileUtils; + +/** + * Standard Polygon Format (PLY) mesh exporter for toxiclibs + * {@link TriangleMesh} instances. Generates PLY binary format with optional + * vertex normal export. + */ +public class PLYWriterD { + + protected static final Logger logger = Logger.getLogger(PLYWriterD.class + .getSimpleName()); + + private final byte[] buf = new byte[8]; + + private boolean doWriteNormals; + + /** + * Creates a little-endian version of the given double. + * + * @param f + * @return + */ + private final byte[] le(double f) { + return le(Double.doubleToRawLongBits(f)); + } + + /** + * Creates a little-endian version of the given int. + * + * @param i + * @return + */ + private final byte[] le(long i) { + buf[7] = (byte) (i >>> 56); + buf[6] = (byte) (i >> 48 & 0xff); + buf[5] = (byte) (i >> 40 & 0xff); + buf[4] = (byte) (i >> 32 & 0xff); + buf[3] = (byte) (i >> 24 & 0xff); + buf[2] = (byte) (i >> 16 & 0xff); + buf[1] = (byte) (i >> 8 & 0xff); + buf[0] = (byte) (i & 0xff); + return buf; + } + + /** + * Exports the given mesh to the specified {@link OutputStream}, including + * required header information. The mesh data itself is stored in binary. + * The output stream itself will be wrapped in a buffered version (128KB) in + * order to improve write performance. + * + * @param mesh + * mesh instance to export + * @param stream + * valid output stream + */ + public void saveMeshD(TriangleMeshD mesh, OutputStream stream) { + try { + BufferedOutputStream out = new BufferedOutputStream(stream, 0x20000); + out.write("ply\n".getBytes()); + out.write("format binary_little_endian 1.0\n".getBytes()); + out.write(("element vertex " + mesh.getNumVertices() + "\n") + .getBytes()); + out.write("property double x\n".getBytes()); + out.write("property double y\n".getBytes()); + out.write("property double z\n".getBytes()); + if (doWriteNormals) { + out.write("property double nx\n".getBytes()); + out.write("property double ny\n".getBytes()); + out.write("property double nz\n".getBytes()); + } + out.write(("element face " + mesh.getNumFaceDs() + "\n").getBytes()); + out.write("property list uchar uint vertex_indices\n".getBytes()); + out.write("end_header\n".getBytes()); + VecD3D[] verts = new VecD3D[mesh.getNumVertices()]; + double[] normals = mesh.getNormalsForUniqueVerticesAsArray(); + int i = 0, j = 0; + for (VecD3D v : mesh.getVertices()) { + verts[i++] = v; + } + try { + for (i = 0, j = 0; i < verts.length; i++, j += 3) { + VecD3D v = verts[i]; + out.write(le(v.x)); + out.write(le(v.z)); + out.write(le(v.y)); + if (doWriteNormals) { + out.write(le(normals[j])); + out.write(le(normals[j + 2])); + out.write(le(-normals[j + 1])); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + for (FaceD f : mesh.getFaceDs()) { + out.write((byte) 3); + out.write(le(f.a.id)); + out.write(le(f.b.id)); + out.write(le(f.c.id)); + } + out.flush(); + out.close(); + logger.info(mesh.getNumFaceDs() + " faces written"); + } catch (IOException e) { + logger.log(Level.SEVERE, "error exporting mesh", e); + } + } + + /** + * Exports the given mesh to the specified file path, including required + * header information. The mesh data itself is stored in binary. + * + * @param mesh + * mesh instance to export + * @param path + * file path + */ + public void saveMeshD(TriangleMeshD mesh, String path) { + try { + saveMeshD(mesh, FileUtils.createOutputStream(new File(path))); + } catch (IOException e) { + logger.log(Level.SEVERE, "error exporting mesh", e); + } + } + + /** + * @return true, if normal export is enabled. + */ + public boolean writeNormals() { + return doWriteNormals; + } + + /** + * Setter enable export of vertex normals (false by default). + * + * @param doWriteNormals + * true to enable + */ + public void writeNormals(boolean doWriteNormals) { + this.doWriteNormals = doWriteNormals; + } +} diff --git a/src.core/toxi/geom/mesh/PlaneSelectorD.java b/src.core/toxi/geom/mesh/PlaneSelectorD.java new file mode 100644 index 00000000..b982a2ee --- /dev/null +++ b/src.core/toxi/geom/mesh/PlaneSelectorD.java @@ -0,0 +1,68 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.PlaneD; +import toxi.geom.PlaneD.Classifier; + +/** + * A {@link VertexSelector} implementation for selecting vertices in relation to + * a given {@link Plane}. Using a plane {@link Classifier} vertices can be + * selected either: on the plane, in front or behind. A tolerance for this check + * can be given too. + * + */ +public class PlaneSelectorD extends VertexDSelector { + + public PlaneD plane; + public double tolerance; + public Classifier classifier; + + public PlaneSelectorD(MeshD3D mesh, PlaneD plane, PlaneD.Classifier classifier) { + this(mesh, plane, classifier, 0.0001f); + } + + public PlaneSelectorD(MeshD3D mesh, PlaneD plane, PlaneD.Classifier classifier, + float tolerance) { + super(mesh); + this.plane = plane; + this.classifier = classifier; + this.tolerance = tolerance; + } + + @Override + public VertexDSelector selectVertices() { + clearSelection(); + for (VertexD v : mesh.getVertices()) { + if (plane.classifyPoint(v, tolerance) == classifier) { + selection.add(v); + } + } + return this; + } +} diff --git a/src.core/toxi/geom/mesh/STLWriter.java b/src.core/toxi/geom/mesh/STLWriter.java index 470b96b4..c23b575a 100644 --- a/src.core/toxi/geom/mesh/STLWriter.java +++ b/src.core/toxi/geom/mesh/STLWriter.java @@ -36,6 +36,8 @@ import java.util.logging.Logger; import toxi.geom.Vec3D; +import toxi.geom.VecD3D; +import toxi.geom.mesh.FaceD; /** * A simple, but flexible and memory efficient exporter for binary STL files. @@ -63,10 +65,10 @@ public class STLWriter { public static final int DEFAULT_BUFFER = 0x10000; protected OutputStream ds; - protected byte[] buf = new byte[4]; + protected byte[] buf = new byte[8]; protected int bufferSize; - protected Vec3D scale = new Vec3D(1, 1, 1); + protected VecD3D scale = new VecD3D(1, 1, 1); protected boolean useInvertedNormals = false; protected STLColorModel colorModel; @@ -113,6 +115,9 @@ public void endSave() { public void face(Vec3D a, Vec3D b, Vec3D c) { face(a, b, c, DEFAULT_RGB); } + public void faceD(VecD3D a, VecD3D b, VecD3D c) { + faceD(a, b, c, DEFAULT_RGB); + } public void face(Vec3D a, Vec3D b, Vec3D c, int rgb) { Vec3D normal = b.sub(a).crossSelf(c.sub(a)).normalize(); @@ -121,6 +126,13 @@ public void face(Vec3D a, Vec3D b, Vec3D c, int rgb) { } face(a, b, c, normal, rgb); } + public void faceD(VecD3D a, VecD3D b, VecD3D c, int rgb) { + VecD3D normal = b.sub(a).crossSelf(c.sub(a)).normalize(); + if (useInvertedNormals) { + normal.invert(); + } + faceD(a, b, c, normal, rgb); + } public void face(Vec3D a, Vec3D b, Vec3D c, Vec3D normal, int rgb) { try { @@ -139,6 +151,23 @@ public void face(Vec3D a, Vec3D b, Vec3D c, Vec3D normal, int rgb) { e.printStackTrace(); } } + public void faceD(VecD3D a, VecD3D b, VecD3D c, VecD3D normal, int rgb) { + try { + writeVectorD(normal); + // vertices + writeScaledVectorD(a); + writeScaledVectorD(b); + writeScaledVectorD(c); + // vertex attrib (color) + if (rgb != DEFAULT_RGB) { + writeShort(colorModel.formatRGB(rgb)); + } else { + writeShort(colorModel.getDefaultRGB()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } private final void prepareBuffer(int a) { buf[3] = (byte) (a >>> 24); @@ -147,13 +176,20 @@ private final void prepareBuffer(int a) { buf[0] = (byte) (a & 0xff); } + public void setScale(float s) { scale.set(s, s, s); } + public void setScaleD(double s) { + scale.set(s, s, s); + } public void setScale(Vec3D s) { scale.set(s); } + public void setScaleD(VecD3D s) { + scale.set(s); + } public void useInvertedNormals(boolean state) { useInvertedNormals = state; @@ -163,7 +199,7 @@ protected void writeFloat(float a) throws IOException { prepareBuffer(Float.floatToRawIntBits(a)); ds.write(buf, 0, 4); } - + protected void writeHeader(int num) throws IOException { byte[] header = new byte[80]; colorModel.formatHeader(header); @@ -178,9 +214,18 @@ protected void writeInt(int a) throws IOException { protected void writeScaledVector(Vec3D v) { try { - writeFloat(v.x * scale.x); - writeFloat(v.y * scale.y); - writeFloat(v.z * scale.z); + writeFloat((float)(v.x * scale.x)); + writeFloat((float)(v.y * scale.y)); + writeFloat((float)(v.z * scale.z)); + } catch (IOException e) { + e.printStackTrace(); + } + } + protected void writeScaledVectorD(VecD3D v) { + try { + writeFloat((float)(v.x * scale.x)); + writeFloat((float)(v.y * scale.y)); + writeFloat((float)(v.z * scale.z)); } catch (IOException e) { e.printStackTrace(); } @@ -201,4 +246,13 @@ protected void writeVector(Vec3D v) { e.printStackTrace(); } } + protected void writeVectorD(VecD3D v) { + try { + writeFloat((float)(v.x)); + writeFloat((float)(v.y)); + writeFloat((float)(v.z)); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/src.core/toxi/geom/mesh/SphereDFunction.java b/src.core/toxi/geom/mesh/SphereDFunction.java new file mode 100644 index 00000000..c6c0ed6e --- /dev/null +++ b/src.core/toxi/geom/mesh/SphereDFunction.java @@ -0,0 +1,105 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.SphereD; +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +/** + * This implementation of a {@link SurfaceFunction} samples a given + * {@link Sphere} instance when called by the {@link SurfaceMeshBuilder}. + */ +public class SphereDFunction implements SurfaceFunctionD { + + public SphereD sphere; + + protected double phiRange = MathUtils.PI; + protected double thetaRange = MathUtils.TWO_PI; + + public SphereDFunction() { + this(1); + } + + /** + * Creates a new instance using a sphere of the given radius, located at the + * world origin. + * + * @param radius + */ + public SphereDFunction(double radius) { + this(new SphereD(new VecD3D(), radius)); + } + + /** + * Creates a new instance using the given sphere + * + * @param s + * sphere + */ + public SphereDFunction(SphereD s) { + this.sphere = s; + } + + public VecD3D computeVertexFor(VecD3D p, double phi, double theta) { + phi -= MathUtils.HALF_PI; + double cosPhi = MathUtils.cos(phi); + double cosTheta = MathUtils.cos(theta); + double sinPhi = MathUtils.sin(phi); + double sinTheta = MathUtils.sin(theta); + double t = MathUtils.sign(cosPhi) * MathUtils.abs(cosPhi); + p.x = t * MathUtils.sign(cosTheta) * MathUtils.abs(cosTheta); + p.y = MathUtils.sign(sinPhi) * MathUtils.abs(sinPhi); + p.z = t * MathUtils.sign(sinTheta) * MathUtils.abs(sinTheta); + return p.scaleSelf(sphere.radius).addSelf(sphere); + } + + public double getPhiRange() { + return phiRange; + } + + public int getPhiResolutionLimit(int res) { + return res; + } + + public double getThetaRange() { + return thetaRange; + } + + public int getThetaResolutionLimit(int res) { + return res; + } + + public void setMaxPhi(double max) { + phiRange = MathUtils.min(max / 2, MathUtils.PI); + } + + public void setMaxTheta(double max) { + thetaRange = MathUtils.min(max, MathUtils.TWO_PI); + } +} diff --git a/src.core/toxi/geom/mesh/SphericalHarmonicsD.java b/src.core/toxi/geom/mesh/SphericalHarmonicsD.java new file mode 100644 index 00000000..2595a410 --- /dev/null +++ b/src.core/toxi/geom/mesh/SphericalHarmonicsD.java @@ -0,0 +1,75 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +/** + * Spherical harmonics surface evaluator based on code by Paul Bourke: + * http://local.wasp.uwa.edu.au/~pbourke/geometry/sphericalh/ + */ +public class SphericalHarmonicsD implements SurfaceFunctionD { + + double[] m; + + public SphericalHarmonicsD(double[] m) { + this.m = m; + } + + // FIXME check where flipped vertex order is coming from sometimes + public VecD3D computeVertexFor(VecD3D p, double phi, double theta) { + double r = 0; + r += Math.pow(MathUtils.sin(m[0] * theta), m[1]); + r += Math.pow(MathUtils.cos(m[2] * theta), m[3]); + r += Math.pow(MathUtils.sin(m[4] * phi), m[5]); + r += Math.pow(MathUtils.cos(m[6] * phi), m[7]); + + double sinTheta = MathUtils.sin(theta); + p.x = r * sinTheta * MathUtils.cos(phi); + p.y = r * MathUtils.cos(theta); + p.z = r * sinTheta * MathUtils.sin(phi); + return p; + } + + public double getPhiRange() { + return MathUtils.TWO_PI; + } + + public int getPhiResolutionLimit(int res) { + return res; + } + + public double getThetaRange() { + return MathUtils.PI; + } + + public int getThetaResolutionLimit(int res) { + return res; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh/SuperEllipsoidD.java b/src.core/toxi/geom/mesh/SuperEllipsoidD.java new file mode 100644 index 00000000..74e0c55e --- /dev/null +++ b/src.core/toxi/geom/mesh/SuperEllipsoidD.java @@ -0,0 +1,80 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +/** + * Super ellipsoid surface evaluator based on code by Paul Bourke: + * http://local.wasp.uwa.edu.au/~pbourke/geometry/superellipse/ + */ +public class SuperEllipsoidD implements SurfaceFunctionD { + + private double p1; + private double p2; + + public SuperEllipsoidD(double n1, double n2) { + this.p1 = n1; + this.p2 = n2; + } + + public VecD3D computeVertexFor(VecD3D p, double phi, double theta) { + phi -= MathUtils.HALF_PI; + double cosPhi = MathUtils.cos(phi); + double cosTheta = MathUtils.cos(theta); + double sinPhi = MathUtils.sin(phi); + double sinTheta = MathUtils.sin(theta); + + double t = MathUtils.sign(cosPhi) + * (double) Math.pow(MathUtils.abs(cosPhi), p1); + p.x = t * MathUtils.sign(cosTheta) + * (double) Math.pow(MathUtils.abs(cosTheta), p2); + p.y = MathUtils.sign(sinPhi) + * (double) Math.pow(MathUtils.abs(sinPhi), p1); + p.z = t * MathUtils.sign(sinTheta) + * (double) Math.pow(MathUtils.abs(sinTheta), p2); + return p; + } + + public double getPhiRange() { + return MathUtils.TWO_PI; + } + + public int getPhiResolutionLimit(int res) { + return res / 2; + } + + public double getThetaRange() { + return MathUtils.TWO_PI; + } + + public int getThetaResolutionLimit(int res) { + return res; + } +} diff --git a/src.core/toxi/geom/mesh/SurfaceFunctionD.java b/src.core/toxi/geom/mesh/SurfaceFunctionD.java new file mode 100644 index 00000000..3cdcaa3d --- /dev/null +++ b/src.core/toxi/geom/mesh/SurfaceFunctionD.java @@ -0,0 +1,50 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.VecD3D; + +/** + * This interface defines a functor for evaluating the coordinates of a surface + * mesh used by {@link SurfaceMeshBuilder}. + * + * It is assumed the implementation creates vertices within the unit sphere + * (normalized). + */ +public interface SurfaceFunctionD { + + public VecD3D computeVertexFor(VecD3D p, double phi, double theta); + + public double getPhiRange(); + + public int getPhiResolutionLimit(int res); + + public double getThetaRange(); + + public int getThetaResolutionLimit(int res); +} diff --git a/src.core/toxi/geom/mesh/SurfaceMeshDBuilder.java b/src.core/toxi/geom/mesh/SurfaceMeshDBuilder.java new file mode 100644 index 00000000..3b8e2850 --- /dev/null +++ b/src.core/toxi/geom/mesh/SurfaceMeshDBuilder.java @@ -0,0 +1,122 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; + +/** + * An extensible builder class for {@link TriangleMesh}es based on 3D surface + * functions using spherical coordinates. In order to create a mesh, you'll need + * to supply a {@link SurfaceFunction} implementation to the builder. + */ +public class SurfaceMeshDBuilder { + + protected SurfaceFunctionD function; + + public SurfaceMeshDBuilder(SurfaceFunctionD function) { + this.function = function; + } + + public MeshD3D createMeshD(int res) { + return createMeshD(null, res, 1); + } + + public MeshD3D createMeshD(MeshD3D mesh, int res, double size) { + return createMeshD(mesh, res, size, true); + } + + public MeshD3D createMeshD(MeshD3D mesh, int res, double size, boolean isClosed) { + if (mesh == null) { + mesh = new TriangleMeshD(); + } + VecD3D a = new VecD3D(); + VecD3D b = new VecD3D(); + VecD3D pa = new VecD3D(), pb = new VecD3D(); + VecD3D a0 = new VecD3D(), b0 = new VecD3D(); + int phiRes = function.getPhiResolutionLimit(res); + double phiRange = function.getPhiRange(); + int thetaRes = function.getThetaResolutionLimit(res); + double thetaRange = function.getThetaRange(); + double pres = 1f / phiRes; + double tres = 1f / thetaRes; + double ires = 1f / res; + VecD2D pauv = new VecD2D(); + VecD2D pbuv = new VecD2D(); + VecD2D auv = new VecD2D(); + VecD2D buv = new VecD2D(); + for (int p = 0; p < phiRes; p++) { + double phi = p * phiRange * ires; + double phiNext = (p + 1) * phiRange * ires; + for (int t = 0; t <= thetaRes; t++) { + double theta; + theta = t * thetaRange * ires; + a = function.computeVertexFor(a, phiNext, theta) + .scaleSelf(size); + auv.set(t * tres, 1 - (p + 1) * pres); + b = function.computeVertexFor(b, phi, theta).scaleSelf(size); + buv.set(t * tres, 1 - p * pres); + if (b.equalsWithTolerance(a, 0.0001f)) { + b.set(a); + } + if (t > 0) { + if (t == thetaRes && isClosed) { + a.set(a0); + b.set(b0); + } + mesh.addFaceD(pa, pb, a, pauv.copy(), pbuv.copy(), + auv.copy()); + mesh.addFaceD(pb, b, a, pbuv.copy(), buv.copy(), auv.copy()); + } else { + a0.set(a); + b0.set(b); + } + pa.set(a); + pb.set(b); + pauv.set(auv); + pbuv.set(buv); + } + } + return mesh; + } + + /** + * @return the function + */ + public SurfaceFunctionD getFunction() { + return function; + } + + /** + * @param function + * the function to set + */ + public void setFunction(SurfaceFunctionD function) { + this.function = function; + } +} diff --git a/src.core/toxi/geom/mesh/TerrainD.java b/src.core/toxi/geom/mesh/TerrainD.java new file mode 100644 index 00000000..1c8e56c7 --- /dev/null +++ b/src.core/toxi/geom/mesh/TerrainD.java @@ -0,0 +1,378 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.IsectDataD3D; +import toxi.geom.RayD3D; +import toxi.geom.TriangleD3D; +import toxi.geom.TriangleDIntersector; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; +import toxi.math.InterpolationD2D; +import toxi.math.MathUtils; + +/** + * Implementation of a 2D grid based heightfield with basic intersection + * features and conversion to {@link TriangleMesh}. The terrain is always + * located in the XZ plane with the positive Y axis as up vector. + */ +public class TerrainD { + + protected double[] elevation; + protected VecD3D[] vertices; + + protected int width; + + protected int depth; + protected VecD2D scale; + + /** + * Constructs a new and initially flat terrain of the given size in the XZ + * plane, centred around the world origin. + * + * @param width + * @param depth + * @param scale + */ + public TerrainD(int width, int depth, double scale) { + this(width, depth, new VecD2D(scale, scale)); + } + + public TerrainD(int width, int depth, VecD2D scale) { + this.width = width; + this.depth = depth; + this.scale = scale; + this.elevation = new double[width * depth]; + this.vertices = new VecD3D[elevation.length]; + VecD3D offset = new VecD3D(width / 2, 0, depth / 2); + VecD3D scaleXZ = scale.toD3DXZ(); + for (int z = 0, i = 0; z < depth; z++) { + for (int x = 0; x < width; x++) { + vertices[i++] = new VecD3D(x, 0, z).subSelf(offset).scaleSelf( + scaleXZ); + } + } + } + + public TerrainD clear() { + for (int i = 0; i < elevation.length; i++) { + elevation[i] = 0; + } + return updateElevation(); + } + + /** + * @return number of grid cells along the Z axis. + */ + public int getDepth() { + return depth; + } + + public double[] getElevation() { + return elevation; + } + + /** + * @param x + * @param z + * @return the elevation at grid point + */ + public double getHeightAtCell(int x, int z) { + return elevation[getIndex(x, z)]; + } + + /** + * Computes the elevation of the terrain at the given 2D world coordinate + * (based on current terrain scale). + * + * @param x + * scaled world coord x + * @param z + * scaled world coord z + * @return interpolated elevation + */ + public double getHeightAtPoint(double x, double z) { + double xx = x / scale.x + width * 0.5f; + double zz = z / scale.y + depth * 0.5f; + double y = 0; + if (xx >= 0 && xx < width && zz >= 0 && zz < depth) { + int x2 = (int) MathUtils.min(xx + 1, width - 1); + int z2 = (int) MathUtils.min(zz + 1, depth - 1); + double a = getHeightAtCell((int) xx, (int) zz); + double b = getHeightAtCell(x2, (int) zz); + double c = getHeightAtCell((int) xx, z2); + double d = getHeightAtCell(x2, z2); + y = InterpolationD2D.bilinear(xx, zz, (int) xx, (int) zz, x2, z2, a, + b, c, d); + } + return y; + } + + /** + * Computes the array index for the given cell coords & checks if they're in + * bounds. If not an {@link IndexOutOfBoundsException} is thrown. + * + * @param x + * @param z + * @return array index + */ + protected final int getIndex(int x, int z) { + int idx = z * width + x; + if (idx < 0 || idx > elevation.length) { + throw new IndexOutOfBoundsException( + "the given terrain cell is invalid: " + x + ";" + z); + } + return idx; + } + + /** + * @return the scale + */ + public VecD2D getScale() { + return scale; + } + + protected VecD3D getVertexAtCell(int x, int z) { + return vertices[getIndex(x, z)]; + } + + /** + * @return number of grid cells along the X axis. + */ + public int getWidth() { + return width; + } + + /** + * Computes the 3D position (with elevation) and normal vector at the given + * 2D location in the terrain. The position is in scaled world coordinates + * based on the given terrain scale. The returned data is encapsulated in a + * {@link toxi.geom.IsectData3D} instance. + * + * @param x + * @param z + * @return intersection data parcel + */ + public IsectDataD3D intersectAtPoint(double x, double z) { + double xx = x / scale.x + width * 0.5f; + double zz = z / scale.y + depth * 0.5f; + IsectDataD3D isec = new IsectDataD3D(); + if (xx >= 0 && xx < width && zz >= 0 && zz < depth) { + int x2 = (int) MathUtils.min(xx + 1, width - 1); + int z2 = (int) MathUtils.min(zz + 1, depth - 1); + VecD3D a = getVertexAtCell((int) xx, (int) zz); + VecD3D b = getVertexAtCell(x2, (int) zz); + VecD3D c = getVertexAtCell(x2, z2); + VecD3D d = getVertexAtCell((int) xx, z2); + RayD3D r = new RayD3D(new VecD3D(x, 10000, z), new VecD3D(0, -1, 0)); + TriangleDIntersector i = new TriangleDIntersector(new TriangleD3D(a, + b, d)); + if (i.intersectsRayD(r)) { + isec = i.getIntersectionDataD(); + } else { + i.setTriangleD(new TriangleD3D(b, c, d)); + i.intersectsRayD(r); + isec = i.getIntersectionDataD(); + } + } + return isec; + } + + /** + * Sets the elevation of all cells to those of the given array values. + * + * @param elevation + * array of height values + * @return itself + */ + public TerrainD setElevation(double[] elevation) { + if (this.elevation.length == elevation.length) { + this.elevation = elevation; + updateElevation(); + } else { + throw new IllegalArgumentException( + "the given elevation array size does not match existing terrain size"); + } + return this; + } + + /** + * Sets the elevation for a single given grid cell. + * + * @param x + * @param z + * @param h + * new elevation value + * @return itself + */ + public TerrainD setHeightAtCell(int x, int z, double h) { + int index = getIndex(x, z); + elevation[index] = h; + vertices[index].y = h; + return this; + } + + public void setScale(double scale) { + setScale(new VecD2D(scale, scale)); + } + + /** + * @param scale + * the scale to set + */ + public void setScale(VecD2D scale) { + this.scale.set(scale); + VecD3D offset = new VecD3D(width / 2, 0, depth / 2); + for (int z = 0, i = 0; z < depth; z++) { + for (int x = 0; x < width; x++, i++) { + vertices[i].set((x - offset.x) * scale.x, vertices[i].y, + (z - offset.z) * scale.y); + } + } + } + + public MeshD3D toMesh() { + return toMesh(null); + } + + public MeshD3D toMesh(double groundLevel) { + return toMeshD(null, groundLevel); + } + + /** + * Creates a {@link TriangleMesh} instance of the terrain surface or adds + * its geometry to an existing mesh. + * + * @param mesh + * @return mesh instance + */ + public MeshD3D toMesh(MeshD3D mesh) { + return toMeshD(mesh, 0, 0, width, depth); + } + + /** + * Creates a {@link TriangleMesh} instance of the terrain and constructs + * side panels and a bottom plane to form a fully enclosed mesh volume, e.g. + * suitable for CNC fabrication or 3D printing. The bottom plane will be + * created at the given ground level (can also be negative) and the sides + * are extended downward to that level too. + * + * @param mesh + * existing mesh or null + * @param groundLevel + * @return mesh + */ + public MeshD3D toMeshD(MeshD3D mesh, double groundLevel) { + return toMeshD(mesh, 0, 0, width, depth, groundLevel); + } + + public MeshD3D toMeshD(MeshD3D mesh, int minX, int minZ, int maxX, int maxZ) { + if (mesh == null) { + mesh = new TriangleMeshD("terrain", vertices.length, + vertices.length * 2); + } + minX = MathUtils.clip(minX, 0, width - 1); + maxX = MathUtils.clip(maxX, 0, width); + minZ = MathUtils.clip(minZ, 0, depth - 1); + maxZ = MathUtils.clip(maxZ, 0, depth); + minX++; + minZ++; + for (int z = minZ, idx = minX * width; z < maxZ; z++, idx += width) { + for (int x = minX; x < maxX; x++) { + mesh.addFaceD(vertices[idx - width + x - 1], vertices[idx + - width + x], vertices[idx + x - 1]); + mesh.addFaceD(vertices[idx - width + x], vertices[idx + x], + vertices[idx + x - 1]); + } + } + return mesh; + } + + public MeshD3D toMeshD(MeshD3D mesh, int mix, int miz, int mxx, int mxz, + double groundLevel) { + mesh = toMeshD(mesh, mix, miz, mxx, mxz); + mix = MathUtils.clip(mix, 0, width - 1); + mxx = MathUtils.clip(mxx, 0, width); + miz = MathUtils.clip(miz, 0, depth - 1); + mxz = MathUtils.clip(mxz, 0, depth); + VecD3D offset = new VecD3D(width, 0, depth).scaleSelf(0.5f); + double minX = (mix - offset.x) * scale.x; + double minZ = (miz - offset.z) * scale.y; + double maxX = (mxx - offset.x) * scale.x; + double maxZ = (mxz - offset.z) * scale.y; + for (int z = miz + 1; z < mxz; z++) { + VecD3D a = new VecD3D(minX, groundLevel, (z - 1 - offset.z) * scale.y); + VecD3D b = new VecD3D(minX, groundLevel, (z - offset.z) * scale.y); + // left + mesh.addFaceD(getVertexAtCell(mix, z - 1), getVertexAtCell(mix, z), + a); + mesh.addFaceD(getVertexAtCell(mix, z), b, a); + // right + a.x = b.x = maxX - scale.x; + mesh.addFaceD(getVertexAtCell(mxx - 1, z), + getVertexAtCell(mxx - 1, z - 1), b); + mesh.addFaceD(getVertexAtCell(mxx - 1, z - 1), a, b); + } + for (int x = mix + 1; x < mxx; x++) { + VecD3D a = new VecD3D((x - 1 - offset.x) * scale.x, groundLevel, minZ); + VecD3D b = new VecD3D((x - offset.x) * scale.x, groundLevel, minZ); + // back + mesh.addFaceD(getVertexAtCell(x, miz), getVertexAtCell(x - 1, miz), + b); + mesh.addFaceD(getVertexAtCell(x - 1, miz), a, b); + // front + a.z = b.z = maxZ - scale.y; + mesh.addFaceD(getVertexAtCell(x - 1, mxz - 1), + getVertexAtCell(x, mxz - 1), a); + mesh.addFaceD(getVertexAtCell(x, mxz - 1), b, a); + } + // bottom plane + for (int z = miz + 1; z < mxz; z++) { + for (int x = mix + 1; x < mxx; x++) { + VecD3D a = getVertexAtCell(x - 1, z - 1).copy(); + VecD3D b = getVertexAtCell(x, z - 1).copy(); + VecD3D c = getVertexAtCell(x - 1, z).copy(); + VecD3D d = getVertexAtCell(x, z).copy(); + a.y = groundLevel; + b.y = groundLevel; + c.y = groundLevel; + d.y = groundLevel; + mesh.addFaceD(a, c, d); + mesh.addFaceD(a, d, b); + } + } + return mesh; + } + + public TerrainD updateElevation() { + for (int i = 0; i < elevation.length; i++) { + vertices[i].y = elevation[i]; + } + return this; + } +} diff --git a/src.core/toxi/geom/mesh/TriangleMeshD.java b/src.core/toxi/geom/mesh/TriangleMeshD.java new file mode 100644 index 00000000..0a80aef0 --- /dev/null +++ b/src.core/toxi/geom/mesh/TriangleMeshD.java @@ -0,0 +1,942 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import toxi.geom.AABBD; +import toxi.geom.IntersectorD3D; +import toxi.geom.IsectDataD3D; +import toxi.geom.Matrix4x4; +import toxi.geom.QuaternionD; +import toxi.geom.Ray3D; +import toxi.geom.RayD3D; +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.Sphere; +import toxi.geom.SphereD; +import toxi.geom.TriangleD3D; +import toxi.geom.TriangleDIntersector; +import toxi.geom.mesh.VertexD; +import toxi.geom.mesh.FaceD; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +/** + * An extensible class to dynamically build, manipulate & export triangle + * meshes. Meshes are built face by face. This implementation automatically + * re-uses existing vertices and can generate smooth vertex normals. Vertice and + * face lists are directly accessible for speed & convenience. + */ +public class TriangleMeshD implements MeshD3D, IntersectorD3D { + + /** + * Default size for vertex list + */ + public static final int DEFAULT_NUM_VERTICES = 1000; + + /** + * Default size for face list + */ + public static final int DEFAULT_NUM_FACES = 3000; + + /** + * Default stride setting used for serializing mesh properties into arrays. + */ + public static final int DEFAULT_STRIDE = 4; + + protected static final Logger logger = Logger.getLogger(TriangleMeshD.class + .getName()); + + /** + * Mesh name + */ + public String name; + + /** + * VertexD buffer & lookup index when adding new faces + */ + public LinkedHashMap vertices; + + /** + * FaceD list + */ + public ArrayList faces; + + protected AABBD bounds; + protected VecD3D centroid = new VecD3D(); + protected int numVertices; + protected int numFaceDs; + + protected Matrix4x4 matrix = new Matrix4x4(); + protected TriangleDIntersector intersector = new TriangleDIntersector(); + + protected int uniqueVertexID; + + public TriangleMeshD() { + this("untitled"); + } + + /** + * Creates a new mesh instance with initial default buffer sizes. + * + * @param name + * mesh name + */ + public TriangleMeshD(String name) { + this(name, DEFAULT_NUM_VERTICES, DEFAULT_NUM_FACES); + } + + /** + * Creates a new mesh instance with the given initial buffer sizes. These + * numbers are no limits and the mesh can be smaller or grow later on. + * They're only used to initialise the underlying collections. + * + * @param name + * mesh name + * @param numV + * initial vertex buffer size + * @param numF + * initial face list size + */ + public TriangleMeshD(String name, int numV, int numF) { + init(name, numV, numF); + } + + public TriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c) { + return addFaceD(a, b, c, null, null, null, null); + } + + public TriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD2D uvA, VecD2D uvB, VecD2D uvC) { + return addFaceD(a, b, c, null, uvA, uvB, uvC); + } + + public TriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD3D n) { + return addFaceD(a, b, c, n, null, null, null); + } + + public TriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD3D n, VecD2D uvA, VecD2D uvB, VecD2D uvC) { + VertexD va = checkVertexD(a); + VertexD vb = checkVertexD(b); + VertexD vc = checkVertexD(c); + if (va.id == vb.id || va.id == vc.id || vb.id == vc.id) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("ignorning invalid face: " + a + "," + b + "," + c); + } + } else { + if (n != null) { + VecD3D nc = va.sub(vc).crossSelf(va.sub(vb)); + if (n.dot(nc) < 0) { + VertexD t = va; + va = vb; + vb = t; + } + } + FaceD f = new FaceD(va, vb, vc, uvA, uvB, uvC); + faces.add(f); + numFaceDs++; + } + return this; + } + + /** + * Adds all faces from the given mesh to this one. + * + * @param m + * source mesh instance + */ + public TriangleMeshD addMeshD(MeshD3D m) { + for (FaceD f : m.getFaceDs()) { + addFaceD(f.a, f.b, f.c, f.uvA, f.uvB, f.uvC); + } + return this; + } + + public AABBD center(ReadonlyVecD3D origin) { + computeCentroid(); + VecD3D delta = origin != null ? origin.sub(centroid) : centroid + .getInverted(); + for (VertexD v : vertices.values()) { + v.addSelf(delta); + } + getBoundingBox(); + return bounds; + } + + private final VertexD checkVertexD(VecD3D v) { + VertexD vertex = vertices.get(v); + if (vertex == null) { + vertex = createVertexD(v, uniqueVertexID++); + vertices.put(vertex, vertex); + numVertices++; + } + return vertex; + } + + /** + * Clears all counters, and vertex & face buffers. + */ + public TriangleMeshD clear() { + vertices.clear(); + faces.clear(); + bounds = null; + numVertices = 0; + numFaceDs = 0; + uniqueVertexID = 0; + return this; + } + + public VecD3D computeCentroid() { + centroid.clear(); + for (VecD3D v : vertices.values()) { + centroid.addSelf(v); + } + return centroid.scaleSelf(1f / numVertices).copy(); + } + + /** + * Re-calculates all face normals. + */ + public TriangleMeshD computeFaceDNormals() { + for (FaceD f : faces) { + f.computeNormal(); + } + return this; + } + + /** + * Computes the smooth vertex normals for the entire mesh. + */ + public TriangleMeshD computeVertexDNormals() { + for (VertexD v : vertices.values()) { + v.clearNormal(); + } + for (FaceD f : faces) { + f.a.addFaceDNormal(f.normal); + f.b.addFaceDNormal(f.normal); + f.c.addFaceDNormal(f.normal); + } + for (VertexD v : vertices.values()) { + v.computeNormal(); + } + return this; + } + + /** + * Creates a deep clone of the mesh. The new mesh name will have "-copy" as + * suffix. + * + * @return new mesh instance + */ + public TriangleMeshD copy() { + TriangleMeshD m = new TriangleMeshD(name + "-copy", numVertices, numFaceDs); + for (FaceD f : faces) { + m.addFaceD(f.a, f.b, f.c, f.normal, f.uvA, f.uvB, f.uvC); + } + return m; + } + + protected VertexD createVertexD(VecD3D v, int id) { + return new VertexD(v, id); + } + + public TriangleMeshD faceOutwards() { + computeCentroid(); + for (FaceD f : faces) { + VecD3D n = f.getCentroid().sub(centroid); + double dot = n.dot(f.normal); + if (dot < 0) { + f.flipVertexOrder(); + } + } + return this; + } + + public TriangleMeshD flipVertexDOrder() { + for (FaceD f : faces) { + VertexD t = f.a; + f.a = f.b; + f.b = t; + VecD2D tuv = f.uvA; + f.uvA = f.uvB; + f.uvB = tuv; + f.normal.invert(); + } + return this; + } + + public TriangleMeshD flipYAxis() { + transform(new Matrix4x4().scaleSelf(1, -1, 1)); + flipVertexDOrder(); + return this; + } + + public AABBD getBoundingBox() { + final VecD3D minBounds = VecD3D.MAX_VALUE.copy(); + final VecD3D maxBounds = VecD3D.NEG_MAX_VALUE.copy(); + for (VertexD v : vertices.values()) { + minBounds.minSelf(v); + maxBounds.maxSelf(v); + } + bounds = AABBD.fromMinMax(minBounds, maxBounds); + return bounds; + } + + public SphereD getBoundingSphereD() { + double radius = 0; + computeCentroid(); + for (VertexD v : vertices.values()) { + radius = MathUtils.max(radius, v.distanceToSquared(centroid)); + } + return new SphereD(centroid, Math.sqrt(radius)); + } + + public VertexD getClosestVertexDToPoint(ReadonlyVecD3D p) { + VertexD closest = null; + double minDist = Double.MAX_VALUE; + for (VertexD v : vertices.values()) { + double d = v.distanceToSquared(p); + if (d < minDist) { + closest = v; + minDist = d; + } + } + return closest; + } + + /** + * Creates an array of unravelled normal coordinates. For each vertex the + * normal vector of its parent face is used. This is a convienence + * invocation of {@link #getFaceDNormalsAsArray(double[], int, int)} with a + * default stride = 4. + * + * @return array of xyz normal coords + */ + public double[] getFaceDNormalsAsArray() { + return getFaceDNormalsAsArray(null, 0, DEFAULT_STRIDE); + } + + /** + * Creates an array of unravelled normal coordinates. For each vertex the + * normal vector of its parent face is used. This method can be used to + * translate the internal mesh data structure into a format suitable for + * OpenGL VertexD Buffer Objects (by choosing stride=4). For more detail, + * please see {@link #getMeshAsVertexDArray(double[], int, int)} + * + * @see #getMeshAsVertexDArray(double[], int, int) + * + * @param normals + * existing double array or null to automatically create one + * @param offset + * start index in array to place normals + * @param stride + * stride/alignment setting for individual coordinates (min value + * = 3) + * @return array of xyz normal coords + */ + public double[] getFaceDNormalsAsArray(double[] normals, int offset, int stride) { + stride = MathUtils.max(stride, 3); + if (normals == null) { + normals = new double[faces.size() * 3 * stride]; + } + int i = offset; + for (FaceD f : faces) { + normals[i] = f.normal.x; + normals[i + 1] = f.normal.y; + normals[i + 2] = f.normal.z; + i += stride; + normals[i] = f.normal.x; + normals[i + 1] = f.normal.y; + normals[i + 2] = f.normal.z; + i += stride; + normals[i] = f.normal.x; + normals[i + 1] = f.normal.y; + normals[i + 2] = f.normal.z; + i += stride; + } + return normals; + } + + public List getFaceDs() { + return faces; + } + + /** + * Builds an array of vertex indices of all faces. Each vertex ID + * corresponds to its position in the {@link #vertices} HashMap. The + * resulting array will be 3 times the face count. + * + * @return array of vertex indices + */ + public int[] getFaceDsAsArray() { + int[] faceList = new int[faces.size() * 3]; + int i = 0; + for (FaceD f : faces) { + faceList[i++] = f.a.id; + faceList[i++] = f.b.id; + faceList[i++] = f.c.id; + } + return faceList; + } + + public IsectDataD3D getIntersectionDataD() { + return intersector.getIntersectionDataD(); + } + + /** + * Creates an array of unravelled vertex coordinates for all faces using a + * stride setting of 4, resulting in a serialized version of all mesh vertex + * coordinates suitable for VBOs. + * + * @see #getMeshAsVertexDArray(double[], int, int) + * @return double array of vertex coordinates + */ + public double[] getMeshAsVertexDArray() { + return getMeshAsVertexDArray(null, 0, DEFAULT_STRIDE); + } + + /** + * Creates an array of unravelled vertex coordinates for all faces. This + * method can be used to translate the internal mesh data structure into a + * format suitable for OpenGL VertexD Buffer Objects (by choosing stride=4). + * The order of the array will be as follows: + * + *
    + *
  • Face 1: + *
      + *
    • VertexD #1 + *
        + *
      • x
      • + *
      • y
      • + *
      • z
      • + *
      • [optional empty indices to match stride setting]
      • + *
      + *
    • + *
    • VertexD #2 + *
        + *
      • x
      • + *
      • y
      • + *
      • z
      • + *
      • [optional empty indices to match stride setting]
      • + *
      + *
    • + *
    • VertexD #3 + *
        + *
      • x
      • + *
      • y
      • + *
      • z
      • + *
      • [optional empty indices to match stride setting]
      • + *
      + *
    • + *
    + *
  • Face 2: + *
      + *
    • VertexD #1
    • + *
    • ...etc.
    • + *
    + *
+ * + * @param verts + * an existing target array or null to automatically create one + * @param offset + * start index in arrtay to place vertices + * @param stride + * stride/alignment setting for individual coordinates + * @return array of xyz vertex coords + */ + public double[] getMeshAsVertexDArray(double[] verts, int offset, int stride) { + stride = MathUtils.max(stride, 3); + if (verts == null) { + verts = new double[faces.size() * 3 * stride]; + } + int i = offset; + for (FaceD f : faces) { + verts[i] = f.a.x; + verts[i + 1] = f.a.y; + verts[i + 2] = f.a.z; + i += stride; + verts[i] = f.b.x; + verts[i + 1] = f.b.y; + verts[i + 2] = f.b.z; + i += stride; + verts[i] = f.c.x; + verts[i + 1] = f.c.y; + verts[i + 2] = f.c.z; + i += stride; + } + return verts; + } + + public double[] getNormalsForUniqueVerticesAsArray() { + double[] normals = new double[numVertices * 3]; + int i = 0; + for (VertexD v : vertices.values()) { + normals[i++] = v.normal.x; + normals[i++] = v.normal.y; + normals[i++] = v.normal.z; + } + return normals; + } + + public int getNumFaceDs() { + return numFaceDs; + } + + public int getNumVertices() { + return numVertices; + } + + public TriangleMeshD getRotatedAroundAxis(VecD3D axis, double theta) { + return copy().rotateAroundAxisD(axis, theta); + } + + public TriangleMeshD getRotatedX(double theta) { + return copy().rotateX(theta); + } + + public TriangleMeshD getRotatedY(double theta) { + return copy().rotateY(theta); + } + + public TriangleMeshD getRotatedZ(double theta) { + return copy().rotateZ(theta); + } + + public TriangleMeshD getScaled(double scale) { + return copy().scale(scale); + } + + public TriangleMeshD getScaled(VecD3D scale) { + return copy().scale(scale); + } + + public TriangleMeshD getTranslated(VecD3D trans) { + return copy().translate(trans); + } + + public double[] getUniqueVerticesAsArray() { + double[] verts = new double[numVertices * 3]; + int i = 0; + for (VertexD v : vertices.values()) { + verts[i++] = v.x; + verts[i++] = v.y; + verts[i++] = v.z; + } + return verts; + } + + public VertexD getVertexDAtPoint(VecD3D v) { + return vertices.get(v); + } + + public VertexD getVertexDForID(int id) { + VertexD vertex = null; + for (VertexD v : vertices.values()) { + if (v.id == id) { + vertex = v; + break; + } + } + return vertex; + } + + /** + * Creates an array of unravelled vertex normal coordinates for all faces. + * Uses default stride = 4. + * + * @see #getVertexDNormalsAsArray(double[], int, int) + * @return array of xyz normal coords + */ + public double[] getVertexDNormalsAsArray() { + return getVertexDNormalsAsArray(null, 0, DEFAULT_STRIDE); + } + + /** + * Creates an array of unravelled vertex normal coordinates for all faces. + * This method can be used to translate the internal mesh data structure + * into a format suitable for OpenGL VertexD Buffer Objects (by choosing + * stride=4). For more detail, please see + * {@link #getMeshAsVertexDArray(double[], int, int)} + * + * @see #getMeshAsVertexDArray(double[], int, int) + * + * @param normals + * existing double array or null to automatically create one + * @param offset + * start index in array to place normals + * @param stride + * stride/alignment setting for individual coordinates (min value + * = 3) + * @return array of xyz normal coords + */ + public double[] getVertexDNormalsAsArray(double[] normals, int offset, + int stride) { + stride = MathUtils.max(stride, 3); + if (normals == null) { + normals = new double[faces.size() * 3 * stride]; + } + int i = offset; + for (FaceD f : faces) { + normals[i] = f.a.normal.x; + normals[i + 1] = f.a.normal.y; + normals[i + 2] = f.a.normal.z; + i += stride; + normals[i] = f.b.normal.x; + normals[i + 1] = f.b.normal.y; + normals[i + 2] = f.b.normal.z; + i += stride; + normals[i] = f.c.normal.x; + normals[i + 1] = f.c.normal.y; + normals[i + 2] = f.c.normal.z; + i += stride; + } + return normals; + } + + public Collection getVertices() { + return vertices.values(); + } + + protected void handleSaveAsSTL(STLWriter stl, boolean useFlippedY) { + if (useFlippedY) { + stl.setScaleD(new VecD3D(1, -1, 1)); + for (FaceD f : faces) { + stl.faceD(f.a, f.b, f.c, f.normal, STLWriter.DEFAULT_RGB); + } + } else { + for (FaceD f : faces) { + stl.faceD(f.b, f.a, f.c, f.normal, STLWriter.DEFAULT_RGB); + } + } + stl.endSave(); + logger.info(numFaceDs + " faces written"); + } + + public TriangleMeshD init(String name, int numV, int numF) { + setName(name); + vertices = new LinkedHashMap(numV, 1.5f, false); + faces = new ArrayList(numF); + return this; + } + + public boolean intersectsRayD(RayD3D ray) { + TriangleD3D tri = intersector.getTriangleD(); + for (FaceD f : faces) { + tri.set(f.a, f.b, f.c); + if (intersector.intersectsRayD(ray)) { + return true; + } + } + return false; + } + + public TriangleD3D perforateFaceD(FaceD f, double size) { + VecD3D centroid = f.getCentroid(); + double d = 1 - size; + VecD3D a2 = f.a.interpolateTo(centroid, d); + VecD3D b2 = f.b.interpolateTo(centroid, d); + VecD3D c2 = f.c.interpolateTo(centroid, d); + removeFaceD(f); + addFaceD(f.a, b2, a2); + addFaceD(f.a, f.b, b2); + addFaceD(f.b, c2, b2); + addFaceD(f.b, f.c, c2); + addFaceD(f.c, a2, c2); + addFaceD(f.c, f.a, a2); + return new TriangleD3D(a2, b2, c2); + } + + /** + * Rotates the mesh in such a way so that its "forward" axis is aligned with + * the given direction. This version uses the positive Z-axis as default + * forward direction. + * + * @param dir + * new target direction to point in + * @return itself + */ + public TriangleMeshD pointTowards(ReadonlyVecD3D dir) { + return transform(QuaternionD.getAlignmentQuat(dir, VecD3D.Z_AXIS) + .toMatrix4x4(matrix), true); + } + + /** + * Rotates the mesh in such a way so that its "forward" axis is aligned with + * the given direction. This version allows to specify the forward + * direction. + * + * @param dir + * new target direction to point in + * @param forward + * current forward axis + * @return itself + */ + public TriangleMeshD pointTowards(ReadonlyVecD3D dir, ReadonlyVecD3D forward) { + return transform( + QuaternionD.getAlignmentQuat(dir, forward).toMatrix4x4(matrix), + true); + } + + public void removeFaceD(FaceD f) { + faces.remove(f); + } + + public TriangleMeshD rotateAroundAxisD(VecD3D axis, double theta) { + return transform(matrix.identity().rotateAroundAxis(axis, theta)); + } + + public TriangleMeshD rotateX(double theta) { + return transform(matrix.identity().rotateX(theta)); + } + + public TriangleMeshD rotateY(double theta) { + return transform(matrix.identity().rotateY(theta)); + } + + public TriangleMeshD rotateZ(double theta) { + return transform(matrix.identity().rotateZ(theta)); + } + + /** + * Saves the mesh as OBJ format by appending it to the given mesh + * {@link OBJWriter} instance. + * + * @param obj + */ + public void saveAsOBJ(OBJWriter obj) { + saveAsOBJ(obj, true); + } + + public void saveAsOBJ(OBJWriter obj, boolean saveNormals) { + int vOffset = obj.getCurrVertexOffset() + 1; + int nOffset = obj.getCurrNormalOffset() + 1; + logger.info("writing OBJMesh: " + this.toString()); + obj.newObject(name); + // vertices + for (VertexD v : vertices.values()) { + obj.vertex(v); + } + // faces + if (saveNormals) { + // normals + for (VertexD v : vertices.values()) { + obj.normal(v.normal); + } + for (FaceD f : faces) { + obj.faceWithNormals(f.b.id + vOffset, f.a.id + vOffset, f.c.id + + vOffset, f.b.id + nOffset, f.a.id + nOffset, f.c.id + + nOffset); + } + } else { + for (FaceD f : faces) { + obj.face(f.b.id + vOffset, f.a.id + vOffset, f.c.id + vOffset); + } + } + } + + /** + * Saves the mesh as OBJ format to the given {@link OutputStream}. Currently + * no texture coordinates are supported or written. + * + * @param stream + */ + public void saveAsOBJ(OutputStream stream) { + OBJWriter obj = new OBJWriter(); + obj.beginSave(stream); + saveAsOBJ(obj); + obj.endSave(); + } + + /** + * Saves the mesh as OBJ format to the given file path. Existing files will + * be overwritten. + * + * @param path + */ + public void saveAsOBJ(String path) { + saveAsOBJ(path, true); + } + + public void saveAsOBJ(String path, boolean saveNormals) { + OBJWriter obj = new OBJWriter(); + obj.beginSave(path); + saveAsOBJ(obj, saveNormals); + obj.endSave(); + } + + /** + * Saves the mesh as binary STL format to the given {@link OutputStream}. + * + * @param stream + * @see #saveAsSTL(OutputStream, boolean) + */ + public final void saveAsSTL(OutputStream stream) { + saveAsSTL(stream, false); + } + + /** + * Saves the mesh as binary STL format to the given {@link OutputStream}. + * The exported mesh can optionally have it's Y axis flipped by setting the + * useFlippedY flag to true. + * + * @param stream + * @param useFlippedY + */ + public final void saveAsSTL(OutputStream stream, boolean useFlippedY) { + STLWriter stl = new STLWriter(); + stl.beginSave(stream, numFaceDs); + handleSaveAsSTL(stl, useFlippedY); + } + + /** + * Saves the mesh as binary STL format to the given {@link OutputStream} and + * using the supplied {@link STLWriter} instance. Use this method to export + * data in a custom {@link STLColorModel}. The exported mesh can optionally + * have it's Y axis flipped by setting the useFlippedY flag to true. + * + * @param stream + * @param stl + * @param useFlippedY + */ + public final void saveAsSTL(OutputStream stream, STLWriter stl, + boolean useFlippedY) { + stl.beginSave(stream, numFaceDs); + handleSaveAsSTL(stl, useFlippedY); + } + + /** + * Saves the mesh as binary STL format to the given file path. Existing + * files will be overwritten. + * + * @param fileName + */ + public final void saveAsSTL(String fileName) { + saveAsSTL(fileName, false); + } + + /** + * Saves the mesh as binary STL format to the given file path. The exported + * mesh can optionally have it's Y axis flipped by setting the useFlippedY + * flag to true. Existing files will be overwritten. + * + * @param fileName + * @param useFlippedY + */ + public final void saveAsSTL(String fileName, boolean useFlippedY) { + saveAsSTL(fileName, new STLWriter(), useFlippedY); + } + + public final void saveAsSTL(String fileName, STLWriter stl, + boolean useFlippedY) { + stl.beginSave(fileName, numFaceDs); + handleSaveAsSTL(stl, useFlippedY); + } + + public TriangleMeshD scale(double scale) { + return transform(matrix.identity().scaleSelf(scale)); + } + + public TriangleMeshD scale(double x, double y, double z) { + return transform(matrix.identity().scaleSelf(x, y, z)); + } + + public TriangleMeshD scale(VecD3D scale) { + return transform(matrix.identity().scaleSelf(scale)); + } + + public TriangleMeshD setName(String name) { + this.name = name; + return this; + } + + @Override + public String toString() { + return "TriangleMesh: " + name + " vertices: " + getNumVertices() + + " faces: " + getNumFaceDs(); + } + + public WETriangleMeshD toWEMeshD() { + return new WETriangleMeshD(name, vertices.size(), faces.size()) + .addMesh(this); + } + + /** + * Applies the given matrix transform to all mesh vertices and updates all + * face normals. + * + * @param mat + * @return itself + */ + public TriangleMeshD transform(Matrix4x4 mat) { + return transform(mat, true); + } + + /** + * Applies the given matrix transform to all mesh vertices. If the + * updateNormals flag is true, all face normals are updated automatically, + * however vertex normals need a manual update. + * + * @param mat + * @param updateNormals + * @return itself + */ + public TriangleMeshD transform(Matrix4x4 mat, boolean updateNormals) { + for (VertexD v : vertices.values()) { + v.set(mat.applyTo(v)); + } + if (updateNormals) { + computeFaceDNormals(); + } + return this; + } + + public TriangleMeshD translate(double x, double y, double z) { + return transform(matrix.identity().translateSelf(x, y, z)); + } + + public TriangleMeshD translate(VecD3D trans) { + return transform(matrix.identity().translateSelf(trans)); + } + + public TriangleMeshD updateVertexD(VecD3D orig, VecD3D newPos) { + VertexD v = vertices.get(orig); + if (v != null) { + vertices.remove(v); + v.set(newPos); + vertices.put(v, v); + } + return this; + } + +} diff --git a/src.core/toxi/geom/mesh/VertexD.java b/src.core/toxi/geom/mesh/VertexD.java new file mode 100644 index 00000000..0a2dfaff --- /dev/null +++ b/src.core/toxi/geom/mesh/VertexD.java @@ -0,0 +1,58 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import toxi.geom.VecD3D; + +public class VertexD extends VecD3D { + + public final VecD3D normal = new VecD3D(); + + public final int id; + + public VertexD(VecD3D v, int id) { + super(v); + this.id = id; + } + + final void addFaceDNormal(VecD3D n) { + normal.addSelf(n); + } + + final void clearNormal() { + normal.clear(); + } + + final void computeNormal() { + normal.normalize(); + } + + public String toString() { + return id + ": p: " + super.toString() + " n:" + normal.toString(); + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh/VertexDSelector.java b/src.core/toxi/geom/mesh/VertexDSelector.java new file mode 100644 index 00000000..a0905ad7 --- /dev/null +++ b/src.core/toxi/geom/mesh/VertexDSelector.java @@ -0,0 +1,188 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +/** + * Abstract parent class for selecting mesh vertices and manipulating resulting + * selections using set theory operations. Implementations of this class should + * aim to work with all mesh types (e.g. {@link TriangleMesh}, + * {@link WETriangleMesh}). + */ +public abstract class VertexDSelector { + + protected MeshD3D mesh; + protected Set selection; + + /** + * Creates a new selector assigned to the given mesh + * + * @param mesh + */ + public VertexDSelector(MeshD3D mesh) { + this.mesh = mesh; + this.selection = new HashSet(); + } + + /** + * Adds all vertices selected by the given selector to the current + * selection. The other selector needs to be assigned to the same mesh + * instance. + * + * @param sel2 + * other selector + * @return itself + */ + public VertexDSelector addSelection(VertexDSelector sel2) { + checkMeshIdentity(sel2.getMesh()); + selection.addAll(sel2.getSelection()); + return this; + } + + /** + * Utility function to check if the given mesh is the same instance as ours. + * + * @param mesh2 + */ + protected void checkMeshIdentity(MeshD3D mesh2) { + if (mesh2 != mesh) { + throw new IllegalArgumentException( + "The given selector is not using the same mesh instance"); + } + } + + /** + * Clears the current selection. + * + * @return itself + */ + public VertexDSelector clearSelection() { + selection.clear(); + return this; + } + + /** + * Returns the associated mesh for this selector. + * + * @return itself + */ + public MeshD3D getMesh() { + return mesh; + } + + /** + * Returns the actual collection of selected vertices + * + * @return vertex collection + */ + public Collection getSelection() { + return selection; + } + + /** + * Creates a new selection of all vertices NOT currently selected. + * + * @return itself + */ + public VertexDSelector invertSelection() { + final int size = MathUtils.max(0, + mesh.getNumVertices() - selection.size()); + HashSet newSel = new HashSet(size); + for (VertexD v : mesh.getVertices()) { + if (!selection.contains(v)) { + newSel.add(v); + } + } + selection = newSel; + return this; + } + + /** + * Selects vertices identical or closest to the ones given in the list of + * points. + * + * @param points + * @return itself + */ + public VertexDSelector selectSimilar(Collection points) { + for (VecD3D v : points) { + selection.add(mesh.getClosestVertexDToPoint(v)); + } + return this; + } + + /** + * Selects vertices using an implementation specific method. This is the + * only method which needs to be implemented by any selector subclass. + * + * @return itself + */ + public abstract VertexDSelector selectVertices(); + + /** + * Assigns a new mesh instance to this selector and clears the current + * selection. + * + * @param mesh + * the mesh to set + */ + public void setMesh(MeshD3D mesh) { + this.mesh = mesh; + clearSelection(); + } + + /** + * Returns the current number of selected vertices. + * + * @return number of vertices + */ + public int size() { + return selection.size(); + } + + /** + * Removes all vertices selected by the given selector from the current + * selection. The other selector needs to be assigned to the same mesh + * instance. + * + * @param sel2 + * other selector + * @return itself + */ + public VertexDSelector subtractSelection(VertexDSelector sel2) { + checkMeshIdentity(sel2.getMesh()); + selection.removeAll(sel2.getSelection()); + return this; + } +} diff --git a/src.core/toxi/geom/mesh/WEFaceD.java b/src.core/toxi/geom/mesh/WEFaceD.java new file mode 100644 index 00000000..12f7ca1f --- /dev/null +++ b/src.core/toxi/geom/mesh/WEFaceD.java @@ -0,0 +1,68 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.VecD2D; + +public final class WEFaceD extends FaceD { + + public List edges = new ArrayList(3); + + public WEFaceD(WEVertexD a, WEVertexD b, WEVertexD c) { + super(a, b, c); + } + + public WEFaceD(WEVertexD a, WEVertexD b, WEVertexD c, VecD2D uvA, VecD2D uvB, VecD2D uvC) { + super(a, b, c, uvA, uvB, uvC); + } + + public void addEdge(WingedEdgeD e) { + edges.add(e); + } + + /** + * @return the edges + */ + public List getEdges() { + return edges; + } + + public final WEVertexD[] getVertices(WEVertexD[] verts) { + if (verts != null) { + verts[0] = (WEVertexD) a; + verts[1] = (WEVertexD) b; + verts[2] = (WEVertexD) c; + } else { + verts = new WEVertexD[] { (WEVertexD) a, (WEVertexD) b, (WEVertexD) c }; + } + return verts; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh/WEMeshFilterStrategyD.java b/src.core/toxi/geom/mesh/WEMeshFilterStrategyD.java new file mode 100644 index 00000000..5e4c18b3 --- /dev/null +++ b/src.core/toxi/geom/mesh/WEMeshFilterStrategyD.java @@ -0,0 +1,42 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +public interface WEMeshFilterStrategyD { + + public void filter(VertexDSelector selector, int numIterations); + + /** + * Applies the vertex filter to the given mesh + * + * @param mesh + * @param numIterations + */ + public void filter(WETriangleMeshD mesh, int numIterations); + +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh/WETriangleMeshD.java b/src.core/toxi/geom/mesh/WETriangleMeshD.java new file mode 100644 index 00000000..9c9aa706 --- /dev/null +++ b/src.core/toxi/geom/mesh/WETriangleMeshD.java @@ -0,0 +1,537 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.logging.Level; + +import toxi.geom.AABBD; +import toxi.geom.LineD3D; +import toxi.geom.Matrix4x4; +import toxi.geom.QuaternionD; +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; +import toxi.geom.mesh.WEVertexD; +import toxi.geom.mesh.WingedEdgeD; + +import toxi.geom.mesh.subdiv.MidpointSubdivisionD; +import toxi.geom.mesh.subdiv.SubdivisionStrategyD; + +/** + * A class to dynamically build, manipulate & export triangle meshes. Meshes are + * build face by face. The class automatically re-uses existing vertices and can + * create smooth vertex normals. Vertices and faces are directly accessible for + * speed & convenience. + */ +public class WETriangleMeshD extends TriangleMeshD { + + /** + * WEVertexD buffer & lookup index when adding new faces + */ + public LinkedHashMap edges; + + private final LineD3D edgeCheck = new LineD3D(new VecD3D(), new VecD3D()); + + private int uniqueEdgeID; + + public WETriangleMeshD() { + this("untitled"); + } + + /** + * Creates a new mesh instance with initial default buffer sizes. + * + * @param name + * mesh name + */ + public WETriangleMeshD(String name) { + this(name, DEFAULT_NUM_VERTICES, DEFAULT_NUM_FACES); + } + + /** + * Creates a new mesh instance with the given initial buffer sizes. These + * numbers are no limits and the mesh can be smaller or grow later on. + * They're only used to initialise the underlying collections. + * + * @param name + * mesh name + * @param numV + * initial vertex buffer size + * @param numF + * initial face list size + */ + public WETriangleMeshD(String name, int numV, int numF) { + super(name, numV, numF); + } + + public WETriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c) { + return addFaceD(a, b, c, null, null, null, null); + } + + public WETriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD2D uvA, + VecD2D uvB, VecD2D uvC) { + return addFaceD(a, b, c, null, uvA, uvB, uvC); + } + + public WETriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD3D n) { + return addFaceD(a, b, c, n, null, null, null); + } + + public WETriangleMeshD addFaceD(VecD3D a, VecD3D b, VecD3D c, VecD3D n, + VecD2D uvA, VecD2D uvB, VecD2D uvC) { + WEVertexD va = checkVertexD(a); + WEVertexD vb = checkVertexD(b); + WEVertexD vc = checkVertexD(c); + if (va.id == vb.id || va.id == vc.id || vb.id == vc.id) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("ignorning invalid face: " + a + "," + b + "," + c); + } + } else { + if (n != null) { + VecD3D nc = va.sub(vc).crossSelf(va.sub(vb)); + if (n.dot(nc) < 0) { + WEVertexD t = va; + va = vb; + vb = t; + } + } + WEFaceD f = new WEFaceD(va, vb, vc, uvA, uvB, uvC); + faces.add(f); + numFaceDs++; + updateEdge(va, vb, f); + updateEdge(vb, vc, f); + updateEdge(vc, va, f); + } + return this; + } + + /** + * Adds all faces from the given mesh to this one. + * + * @param m + * source mesh instance + */ + public WETriangleMeshD addMesh(MeshD3D m) { + super.addMeshD(m); + return this; + } + + @Override + public AABBD center(ReadonlyVecD3D origin) { + super.center(origin); + rebuildIndex(); + return bounds; + } + + private final WEVertexD checkVertexD(VecD3D v) { + WEVertexD vertex = (WEVertexD) vertices.get(v); + if (vertex == null) { + vertex = createVertexD(v, uniqueVertexID++); + vertices.put(vertex, vertex); + numVertices++; + } + return vertex; + } + + /** + * Clears all counters, and vertex & face buffers. + */ + public WETriangleMeshD clear() { + super.clear(); + edges.clear(); + return this; + } + + /** + * Creates a deep clone of the mesh. The new mesh name will have "-copy" as + * suffix. + * + * @return new mesh instance + */ + public WETriangleMeshD copy() { + WETriangleMeshD m = new WETriangleMeshD(name + "-copy", numVertices, + numFaceDs); + for (FaceD f : faces) { + m.addFaceD(f.a, f.b, f.c, f.normal, f.uvA, f.uvB, f.uvC); + } + return m; + } + + protected WEVertexD createVertexD(VecD3D v, int id) { + return new WEVertexD(v, id); + } + + /** + * Flips the vertex ordering between clockwise and anti-clockwise. WEFaceD + * normals are updated automatically too. + * + * @return itself + */ + public WETriangleMeshD flipVertexDOrder() { + super.flipVertexDOrder(); + return this; + } + + public WETriangleMeshD flipYAxis() { + super.flipYAxis(); + return this; + } + + public WEVertexD getClosestVertexDToPoint(ReadonlyVecD3D p) { + return (WEVertexD) super.getClosestVertexDToPoint(p); + } + + public Collection getEdges() { + return edges.values(); + } + + public int getNumEdges() { + return edges.size(); + } + + public WETriangleMeshD getRotatedAroundAxis(VecD3D axis, double theta) { + return copy().rotateAroundAxis(axis, theta); + } + + public WETriangleMeshD getRotatedX(double theta) { + return copy().rotateX(theta); + } + + public WETriangleMeshD getRotatedY(double theta) { + return copy().rotateY(theta); + } + + public WETriangleMeshD getRotatedZ(double theta) { + return copy().rotateZ(theta); + } + + public WETriangleMeshD getScaled(double scale) { + return copy().scale(scale); + } + + public WETriangleMeshD getScaled(VecD3D scale) { + return copy().scale(scale); + } + + public WETriangleMeshD getTranslated(VecD3D trans) { + return copy().translate(trans); + } + + public WEVertexD getVertexDAtPoint(VecD3D v) { + return (WEVertexD) vertices.get(v); + } + + public WEVertexD getVertexDForID(int id) { + return (WEVertexD) super.getVertexDForID(id); + } + + public WETriangleMeshD init(String name, int numV, int numF) { + super.init(name, numV, numF); + edges = new LinkedHashMap(numV, 1.5f, false); + return this; + } + + /** + * Rotates the mesh in such a way so that its "forward" axis is aligned with + * the given direction. This version uses the positive Z-axis as default + * forward direction. + * + * @param dir + * new target direction to point in + * @return itself + */ + public WETriangleMeshD pointTowards(ReadonlyVecD3D dir) { + return transform(QuaternionD.getAlignmentQuat(dir, VecD3D.Z_AXIS) + .toMatrix4x4(matrix), true); + } + + /** + * Rotates the mesh in such a way so that its "forward" axis is aligned with + * the given direction. This version allows to specify the forward + * direction. + * + * @param dir + * new target direction to point in + * @param forward + * current forward axis + * @return itself + */ + public WETriangleMeshD pointTowards(ReadonlyVecD3D dir, ReadonlyVecD3D forward) { + return transform( + QuaternionD.getAlignmentQuat(dir, forward).toMatrix4x4(matrix), + true); + } + + public void rebuildIndex() { + LinkedHashMap newV = new LinkedHashMap( + vertices.size()); + for (VertexD v : vertices.values()) { + newV.put(v, v); + } + vertices = newV; + LinkedHashMap newE = new LinkedHashMap( + edges.size()); + for (WingedEdgeD e : edges.values()) { + newE.put(e, e); + } + edges = newE; + } + + protected void removeEdge(WingedEdgeD e) { + e.remove(); + WEVertexD v = (WEVertexD) e.a; + if (v.edges.size() == 0) { + vertices.remove(v); + } + v = (WEVertexD) e.b; + if (v.edges.size() == 0) { + vertices.remove(v); + } + for (WEFaceD f : e.faces) { + removeFaceD(f); + } + WingedEdgeD removed = edges.remove(edgeCheck.set(e.a, e.b)); + if (removed != e) { + throw new IllegalStateException("can't remove edge"); + } + } + + @Override + public void removeFaceD(FaceD f) { + faces.remove(f); + for (WingedEdgeD e : ((WEFaceD) f).edges) { + e.faces.remove(f); + if (e.faces.size() == 0) { + removeEdge(e); + } + } + } + + // FIXME + public void removeUnusedVertices() { + for (Iterator i = vertices.values().iterator(); i.hasNext();) { + VertexD v = i.next(); + boolean isUsed = false; + for (FaceD f : faces) { + if (f.a == v || f.b == v || f.c == v) { + isUsed = true; + break; + } + } + if (!isUsed) { + logger.info("removing vertex: " + v); + i.remove(); + } + } + } + + public void removeVertices(Collection selection) { + for (VertexD v : selection) { + WEVertexD wv = (WEVertexD) v; + for (WingedEdgeD e : new ArrayList(wv.edges)) { + for (FaceD f : new ArrayList(e.faces)) { + removeFaceD(f); + } + } + } + // rebuildIndex(); + } + + public WETriangleMeshD rotateAroundAxis(VecD3D axis, double theta) { + return transform(matrix.identity().rotateAroundAxis(axis, theta)); + } + + public WETriangleMeshD rotateX(double theta) { + return transform(matrix.identity().rotateX(theta)); + } + + public WETriangleMeshD rotateY(double theta) { + return transform(matrix.identity().rotateY(theta)); + } + + public WETriangleMeshD rotateZ(double theta) { + return transform(matrix.identity().rotateZ(theta)); + } + + public WETriangleMeshD scale(double scale) { + return transform(matrix.identity().scaleSelf(scale)); + } + + public WETriangleMeshD scale(VecD3D scale) { + return transform(matrix.identity().scaleSelf(scale)); + } + + public void splitEdge(ReadonlyVecD3D a, ReadonlyVecD3D b, + SubdivisionStrategyD subDiv) { + WingedEdgeD e = edges.get(edgeCheck.set(a, b)); + if (e != null) { + splitEdge(e, subDiv); + } + } + + public void splitEdge(WingedEdgeD e, SubdivisionStrategyD subDiv) { + List mid = subDiv.computeSplitPointsD(e); + splitFaceD(e.faces.get(0), e, mid); + if (e.faces.size() > 1) { + splitFaceD(e.faces.get(1), e, mid); + } + removeEdge(e); + } + + protected void splitFaceD(WEFaceD f, WingedEdgeD e, List midPoints) { + VecD3D p = null; + for (int i = 0; i < 3; i++) { + WingedEdgeD ec = f.edges.get(i); + if (!ec.equals(e)) { + if (ec.a.equals(e.a) || ec.a.equals(e.b)) { + p = ec.b; + } else { + p = ec.a; + } + break; + } + } + VecD3D prev = null; + for (int i = 0, num = midPoints.size(); i < num; i++) { + VecD3D mid = midPoints.get(i); + if (i == 0) { + addFaceD(p, e.a, mid, f.normal); + } else { + addFaceD(p, prev, mid, f.normal); + } + if (i == num - 1) { + addFaceD(p, mid, e.b, f.normal); + } + prev = mid; + } + } + + public void subdivide() { + subdivide(0); + } + + public void subdivide(double minLength) { + subdivide(new MidpointSubdivisionD(), minLength); + } + + public void subdivide(SubdivisionStrategyD subDiv) { + subdivide(subDiv, 0); + } + + public void subdivide(SubdivisionStrategyD subDiv, double minLength) { + subdivideEdges(new ArrayList(edges.values()), subDiv, + minLength); + } + + protected void subdivideEdges(List origEdges, + SubdivisionStrategyD subDiv, double minLength) { + Collections.sort(origEdges, subDiv.getEdgeOrdering()); + minLength *= minLength; + for (WingedEdgeD e : origEdges) { + if (edges.containsKey(e)) { + if (e.getLengthSquared() >= minLength) { + splitEdge(e, subDiv); + } + } + } + } + + public void subdivideFaceDEdges(List faces, + SubdivisionStrategyD subDiv, double minLength) { + List fedges = new ArrayList(); + for (WEFaceD f : faces) { + for (WingedEdgeD e : f.edges) { + if (!fedges.contains(e)) { + fedges.add(e); + } + } + } + subdivideEdges(fedges, subDiv, minLength); + } + + @Override + public String toString() { + return "WETriangleMesh: " + name + " vertices: " + getNumVertices() + + " faces: " + getNumFaceDs() + " edges:" + getNumEdges(); + } + + /** + * Applies the given matrix transform to all mesh vertices and updates all + * face normals. + * + * @param mat + * @return itself + */ + public WETriangleMeshD transform(Matrix4x4 mat) { + return transform(mat, true); + } + + /** + * Applies the given matrix transform to all mesh vertices. If the + * updateNormals flag is true, all face normals are updated automatically, + * however vertex normals still need a manual update. + * + * @param mat + * @param updateNormals + * @return itself + */ + public WETriangleMeshD transform(Matrix4x4 mat, boolean updateNormals) { + for (VertexD v : vertices.values()) { + mat.applyToSelf(v); + } + rebuildIndex(); + if (updateNormals) { + computeFaceDNormals(); + } + return this; + } + + public WETriangleMeshD translate(VecD3D trans) { + return transform(matrix.identity().translateSelf(trans)); + } + + protected void updateEdge(WEVertexD va, WEVertexD vb, WEFaceD f) { + edgeCheck.set(va, vb); + WingedEdgeD e = edges.get(edgeCheck); + if (e != null) { + e.addFaceD(f); + } else { + e = new WingedEdgeD(va, vb, f, uniqueEdgeID++); + edges.put(e, e); + va.addEdge(e); + vb.addEdge(e); + } + f.addEdge(e); + } +} diff --git a/src.core/toxi/geom/mesh/WEVertexD.java b/src.core/toxi/geom/mesh/WEVertexD.java new file mode 100644 index 00000000..8d42324d --- /dev/null +++ b/src.core/toxi/geom/mesh/WEVertexD.java @@ -0,0 +1,95 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.VecD3D; + +public class WEVertexD extends VertexD { + + public List edges = new ArrayList(4); + + public WEVertexD(VecD3D v, int id) { + super(v, id); + } + + public void addEdge(WingedEdgeD e) { + edges.add(e); + } + + public List getEdges() { + return edges; + } + + public WEVertexD getNeighborInDirection(ReadonlyVecD3D dir, double tolerance) { + WEVertexD closest = null; + double delta = 1 - tolerance; + for (WEVertexD n : getNeighbors()) { + double d = n.sub(this).normalize().dot(dir); + if (d > delta) { + closest = n; + delta = d; + } + } + return closest; + } + + public List getNeighbors() { + List neighbors = new ArrayList(edges.size()); + for (WingedEdgeD e : edges) { + neighbors.add(e.getOtherEndFor(this)); + } + return neighbors; + } + + /** + * Returns a list of all faces this vertex belongs to. + * + * @return face list + */ + public List getRelatedFaces() { + Set faces = new HashSet(edges.size() * 2); + for (WingedEdgeD e : edges) { + faces.addAll(e.faces); + } + return new ArrayList(faces); + } + + public void removeEdge(WingedEdge e) { + edges.remove(e); + } + + public String toString() { + return id + " {" + x + "," + y + "," + z + "}"; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh/WingedEdgeD.java b/src.core/toxi/geom/mesh/WingedEdgeD.java new file mode 100644 index 00000000..1b31d370 --- /dev/null +++ b/src.core/toxi/geom/mesh/WingedEdgeD.java @@ -0,0 +1,79 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.LineD3D; + +public class WingedEdgeD extends LineD3D { + + public List faces = new ArrayList(2); + public final int id; + + public WingedEdgeD(WEVertexD a, WEVertexD b, WEFaceD f, int id) { + super(a, b); + this.id = id; + addFaceD(f); + } + + public WingedEdgeD addFaceD(WEFaceD f) { + faces.add(f); + return this; + } + + /** + * @return the faces + */ + public List getFaceDs() { + return faces; + } + + public WEVertexD getOtherEndFor(WEVertexD v) { + if (a == v) { + return (WEVertexD) b; + } + if (b == v) { + return (WEVertexD) a; + } + return null; + } + + public void remove() { + for (WEFaceD f : faces) { + f.edges.remove(this); + } + ((WEVertexD) a).edges.remove(this); + ((WEVertexD) b).edges.remove(this); + } + + public String toString() { + return "id: " + id + " " + super.toString() + " f: " + faces.size(); + } +} diff --git a/src.core/toxi/geom/mesh/subdiv/CentroidSubdivD.java b/src.core/toxi/geom/mesh/subdiv/CentroidSubdivD.java new file mode 100644 index 00000000..d945ad0c --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/CentroidSubdivD.java @@ -0,0 +1,24 @@ +package toxi.geom.mesh.subdiv; + +import java.util.List; + +import toxi.geom.VecD3D; + +public class CentroidSubdivD implements NewSubdivStrategyD { + + public List subdivideTriangle(VecD3D a, VecD3D b, VecD3D c, + List resultVertices) { + VecD3D centroid = a.add(b).addSelf(c).scaleSelf(1 / 3.0f); + resultVertices.add(new VecD3D[] { + a, b, centroid + }); + resultVertices.add(new VecD3D[] { + b, c, centroid + }); + resultVertices.add(new VecD3D[] { + c, a, centroid + }); + return resultVertices; + } + +} diff --git a/src.core/toxi/geom/mesh/subdiv/DisplacementSubdivisionD.java b/src.core/toxi/geom/mesh/subdiv/DisplacementSubdivisionD.java new file mode 100644 index 00000000..698158fb --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/DisplacementSubdivisionD.java @@ -0,0 +1,69 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +/** + * Abstract parent class for all displacement subdivision strategies. It adds + * common support for the displacement amplification value, which subclasses can + * utilize. + */ +public abstract class DisplacementSubdivisionD extends SubdivisionStrategyD { + + protected double amp; + + public DisplacementSubdivisionD(double amp) { + this.amp = amp; + } + + /** + * @return the amp + */ + public double getAmp() { + return amp; + } + + public DisplacementSubdivisionD invertAmp() { + this.amp *= -1; + return this; + } + + public DisplacementSubdivisionD scaleAmp(double scale) { + this.amp *= scale; + return this; + } + + /** + * @param amp + * the amp to set + * @return itself + */ + public DisplacementSubdivisionD setAmp(double amp) { + this.amp = amp; + return this; + } +} diff --git a/src.core/toxi/geom/mesh/subdiv/DualDisplacementSubdivisionD.java b/src.core/toxi/geom/mesh/subdiv/DualDisplacementSubdivisionD.java new file mode 100644 index 00000000..e81fd012 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/DualDisplacementSubdivisionD.java @@ -0,0 +1,75 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.VecD3D; +import toxi.geom.mesh.WingedEdgeD; + +/** + * This subdivision strategy splits an edge in three equal parts using two split + * points at 33% and 66% of the edge. The split points themselves are being + * displaced, however, in the direction of the vector going from the configured + * reference point (often the mesh centroid is used) towards each split point. + * The displacement amount is configurable as fraction of the original edge + * length. So given that: + * + *
+ * S = edge split point
+ * R = reference point
+ * l = edge length
+ * a = displacement amplification factor
+ * 
+ * S' = S + (S-R) * a * l
+ * 
+ */ +public class DualDisplacementSubdivisionD extends SubdivisionStrategyD { + + public VecD3D centroid; + public double ampA, ampB; + + public DualDisplacementSubdivisionD(VecD3D centroid, double ampA, double ampB) { + this.centroid = centroid; + this.ampA = ampA; + this.ampB = ampB; + } + + public List computeSplitPointsD(WingedEdgeD edge) { + List mid = new ArrayList(2); + double len = edge.getLength(); + VecD3D a = edge.a.interpolateTo(edge.b, 0.3333f); + a.addSelf(a.sub(centroid).normalizeTo(ampA * len)); + VecD3D b = edge.a.interpolateTo(edge.b, 0.6666f); + b.addSelf(b.sub(centroid).normalizeTo(ampB * len)); + mid.add(a); + mid.add(b); + return mid; + } +} diff --git a/src.core/toxi/geom/mesh/subdiv/DualSubdivisionD.java b/src.core/toxi/geom/mesh/subdiv/DualSubdivisionD.java new file mode 100644 index 00000000..84f96a28 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/DualSubdivisionD.java @@ -0,0 +1,48 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.VecD3D; +import toxi.geom.mesh.WingedEdgeD; + +/** + * This subdivision strategy splits an edge in three equal parts using two split + * points at 33% and 66% of the edge. + */ +public class DualSubdivisionD extends SubdivisionStrategyD { + + public List computeSplitPointsD(WingedEdgeD edge) { + List mid = new ArrayList(2); + mid.add(edge.a.interpolateTo(edge.b, 0.3333f)); + mid.add(edge.a.interpolateTo(edge.b, 0.6666f)); + return mid; + } +} diff --git a/src.core/toxi/geom/mesh/subdiv/EdgeLengthComparatorD.java b/src.core/toxi/geom/mesh/subdiv/EdgeLengthComparatorD.java new file mode 100644 index 00000000..16935114 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/EdgeLengthComparatorD.java @@ -0,0 +1,43 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.Comparator; + +import toxi.geom.mesh.WingedEdgeD; + +/** + * Comparator used by {@link SubdivisionStrategy#getEdgeOrdering()} to define + * the order of edges to be subdivided. This one prioritizes longer edges. + */ +public class EdgeLengthComparatorD implements Comparator { + + public int compare(WingedEdgeD e1, WingedEdgeD e2) { + return (int) (e2.getLengthSquared() - e1.getLengthSquared()); + } +} diff --git a/src.core/toxi/geom/mesh/subdiv/FaceCountComparatorD.java b/src.core/toxi/geom/mesh/subdiv/FaceCountComparatorD.java new file mode 100644 index 00000000..eb3bb269 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/FaceCountComparatorD.java @@ -0,0 +1,44 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.Comparator; + +import toxi.geom.mesh.WingedEdgeD; + +/** + * Comparator used by {@link SubdivisionStrategy#getEdgeOrdering()} to define + * the order of edges to be subdivided. This one prioritizes edges with the most + * faces associated. + */ +public class FaceCountComparatorD implements Comparator { + + public int compare(WingedEdgeD e1, WingedEdgeD e2) { + return (e2.faces.size() - e1.faces.size()); + } +} diff --git a/src.core/toxi/geom/mesh/subdiv/MidpointDisplacementSubdivisionD.java b/src.core/toxi/geom/mesh/subdiv/MidpointDisplacementSubdivisionD.java new file mode 100644 index 00000000..30a9baf9 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/MidpointDisplacementSubdivisionD.java @@ -0,0 +1,72 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.VecD3D; +import toxi.geom.mesh.WingedEdgeD; + +/** + * This subdivision strategy splits an edge in two equal halves at its mid + * point. The midpoint itself is being displaced, however, in the direction of + * the vector going from the configured reference point (often the mesh centroid + * is used) towards the edge midpoint. The displacement amount is configurable + * as fraction of the original edge length. So given that: + * + *
+ * M = edge midpoint
+ * R = reference point
+ * l = edge length
+ * a = displacement amplification factor
+ * 
+ * D  = normalized(M-R)
+ * M' = M + D * a * l
+ * 
+ */ +public class MidpointDisplacementSubdivisionD extends DisplacementSubdivisionD { + + public VecD3D centroid; + + public MidpointDisplacementSubdivisionD(VecD3D centroid, double amount) { + super(amount); + this.centroid = centroid; + } + + @Override + public List computeSplitPointsD(WingedEdgeD edge) { + VecD3D mid = edge.getMidPoint(); + // Vec3D mid = edge.a.interpolateTo(edge.b, 0.25f); + VecD3D n = mid.sub(centroid).normalizeTo(amp * edge.getLength()); + List points = new ArrayList(1); + points.add(mid.addSelf(n)); + return points; + } + +} diff --git a/src.core/toxi/geom/mesh/subdiv/MidpointSubdivD.java b/src.core/toxi/geom/mesh/subdiv/MidpointSubdivD.java new file mode 100644 index 00000000..1e1fa9b2 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/MidpointSubdivD.java @@ -0,0 +1,29 @@ +package toxi.geom.mesh.subdiv; + +import java.util.List; + +import toxi.geom.VecD3D; + +public class MidpointSubdivD implements NewSubdivStrategyD { + + public List subdivideTriangle(VecD3D a, VecD3D b, VecD3D c, + List resultVertices) { + VecD3D mab = a.interpolateTo(b, 0.5f); + VecD3D mbc = b.interpolateTo(c, 0.5f); + VecD3D mca = c.interpolateTo(a, 0.5f); + resultVertices.add(new VecD3D[] { + a, mab, mca + }); + resultVertices.add(new VecD3D[] { + mab, b, mbc + }); + resultVertices.add(new VecD3D[] { + mbc, c, mca + }); + resultVertices.add(new VecD3D[] { + mab, mbc, mca + }); + return resultVertices; + } + +} diff --git a/src.core/toxi/geom/mesh/subdiv/MidpointSubdivisionD.java b/src.core/toxi/geom/mesh/subdiv/MidpointSubdivisionD.java new file mode 100644 index 00000000..d7bacd1d --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/MidpointSubdivisionD.java @@ -0,0 +1,47 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.VecD3D; +import toxi.geom.mesh.WingedEdgeD; + +/** + * This subdivision strategy simply splits an edge in two equal halves. + */ +public class MidpointSubdivisionD extends SubdivisionStrategyD { + + public List computeSplitPointsD(WingedEdgeD edge) { + List mid = new ArrayList(1); + mid.add(edge.getMidPoint()); + return mid; + } + +} diff --git a/src.core/toxi/geom/mesh/subdiv/NewSubdivStrategyD.java b/src.core/toxi/geom/mesh/subdiv/NewSubdivStrategyD.java new file mode 100644 index 00000000..7c7c1feb --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/NewSubdivStrategyD.java @@ -0,0 +1,11 @@ +package toxi.geom.mesh.subdiv; + +import java.util.List; + +import toxi.geom.VecD3D; + +public interface NewSubdivStrategyD { + + public List subdivideTriangle(VecD3D a, VecD3D b, VecD3D c, + List resultVertices); +} diff --git a/src.core/toxi/geom/mesh/subdiv/NormalDisplacementSubdivisionD.java b/src.core/toxi/geom/mesh/subdiv/NormalDisplacementSubdivisionD.java new file mode 100644 index 00000000..e7adf254 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/NormalDisplacementSubdivisionD.java @@ -0,0 +1,70 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.VecD3D; +import toxi.geom.mesh.WingedEdgeD; + +/** + * This subdivision strategy splits an edge in two equal halves at its mid + * point. The midpoint itself is being displaced, however, in the average + * direction of face normals associated with this edge. The displacement amount + * is configurable as fraction of the original edge length. So given that: + * + *
+ * M = edge midpoint
+ * N = averaged normal vector
+ * l = edge length
+ * a = displacement amplification factor
+ * 
+ * M' = M + N * a * l
+ * 
+ */ +public class NormalDisplacementSubdivisionD extends DisplacementSubdivisionD { + + public NormalDisplacementSubdivisionD(double amp) { + super(amp); + } + + @Override + public List computeSplitPointsD(WingedEdgeD edge) { + VecD3D mid = edge.getMidPoint(); + VecD3D n = edge.faces.get(0).normal; + if (edge.faces.size() > 1) { + n.addSelf(edge.faces.get(1).normal); + } + n.normalizeTo(amp * edge.getLength()); + List points = new ArrayList(3); + points.add(mid.addSelf(n)); + return points; + } + +} diff --git a/src.core/toxi/geom/mesh/subdiv/SubdivisionStrategy.java b/src.core/toxi/geom/mesh/subdiv/SubdivisionStrategy.java index 5b8cb82f..8e861328 100644 --- a/src.core/toxi/geom/mesh/subdiv/SubdivisionStrategy.java +++ b/src.core/toxi/geom/mesh/subdiv/SubdivisionStrategy.java @@ -31,7 +31,7 @@ import java.util.List; import toxi.geom.Vec3D; -import toxi.geom.mesh.WETriangleMesh; +//import toxi.geom.mesh.WETriangleMesh; import toxi.geom.mesh.WingedEdge; /** diff --git a/src.core/toxi/geom/mesh/subdiv/SubdivisionStrategyD.java b/src.core/toxi/geom/mesh/subdiv/SubdivisionStrategyD.java new file mode 100644 index 00000000..28106189 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/SubdivisionStrategyD.java @@ -0,0 +1,77 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.Comparator; +import java.util.List; + +import toxi.geom.VecD3D; +import toxi.geom.mesh.WETriangleMeshD; +import toxi.geom.mesh.WingedEdgeD; + +/** + * This is the abstract parent class for all subdivision strategies. Each of its + * implementations defines a concrete solution to split a single edge of a + * {@link WETriangleMesh}. + */ +public abstract class SubdivisionStrategyD { + + public static final Comparator DEFAULT_ORDERING = new EdgeLengthComparatorD(); + + protected Comparator order = DEFAULT_ORDERING; + + /** + * Computes a number of points on (or near) the given edge which are used + * for splitting the edge in smaller segments. + * + * @param edge + * edge to split + * @return list of split points + */ + public abstract List computeSplitPointsD(WingedEdgeD edge); + + /** + * Returns the {@link Comparator} used to sort a mesh's edge list based on a + * certain criteria. By default the {@link EdgeLengthComparator} is used. + * + * @return edge comparator + */ + public Comparator getEdgeOrdering() { + return order; + } + + /** + * Sets the given edge list {@link Comparator} for a strategy + * implementation. + * + * @param order + */ + public void setEdgeOrderingD(Comparator order) { + this.order = order; + } +} diff --git a/src.core/toxi/geom/mesh/subdiv/TriSubdivisionD.java b/src.core/toxi/geom/mesh/subdiv/TriSubdivisionD.java new file mode 100644 index 00000000..7d819365 --- /dev/null +++ b/src.core/toxi/geom/mesh/subdiv/TriSubdivisionD.java @@ -0,0 +1,50 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh.subdiv; + +import java.util.ArrayList; +import java.util.List; + +import toxi.geom.VecD3D; +import toxi.geom.mesh.WingedEdgeD; + +/** + * This subdivision strategy splits an edge in four equal parts using three + * split points at 25%, 50% and 75% of the edge. + */ +public class TriSubdivisionD extends SubdivisionStrategyD { + + public List computeSplitPointsD(WingedEdgeD edge) { + List mid = new ArrayList(3); + mid.add(edge.a.interpolateTo(edge.b, 0.25f)); + mid.add(edge.a.interpolateTo(edge.b, 0.5f)); + mid.add(edge.a.interpolateTo(edge.b, 0.75f)); + return mid; + } + +} diff --git a/src.core/toxi/geom/mesh2d/DelaunayTriangleD.java b/src.core/toxi/geom/mesh2d/DelaunayTriangleD.java new file mode 100644 index 00000000..e32c83b1 --- /dev/null +++ b/src.core/toxi/geom/mesh2d/DelaunayTriangleD.java @@ -0,0 +1,192 @@ +package toxi.geom.mesh2d; + +/* + * Copyright (c) 2007 by L. Paul Chew. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * 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. + */ + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import toxi.util.datatypes.ArraySet; + +/** + * A DelaunayTriangleD is an immutable Set of exactly three Pnts. + * + * All Set operations are available. Individual vertices can be accessed via + * iterator() and also via triangle.get(index). + * + * Note that, even if two triangles have the same vertex set, they are + * *different* triangles. Methods equals() and hashCode() are consistent with + * this rule. + * + * @author Paul Chew + * + * Created December 2007. Replaced general simplices with geometric + * triangle. + * + */ +public class DelaunayTriangleD extends ArraySet { + + private int idNumber; // The id number + private DelaunayVertexD circumcenter = null; // The triangle's circumcenter + + private static int idGenerator = 0; // Used to create id numbers + public static boolean moreInfo = false; // True if more info in toString + + /** + * @param collection + * a Collection holding the Simplex vertices + * @throws IllegalArgumentException + * if there are not three distinct vertices + */ + public DelaunayTriangleD(Collection collection) { + super(collection); + idNumber = idGenerator++; + if (this.size() != 3) { + throw new IllegalArgumentException( + "DelaunayTriangleD must have 3 vertices"); + } + } + + /** + * @param vertices + * the vertices of the DelaunayTriangleD. + * @throws IllegalArgumentException + * if there are not three distinct vertices + */ + public DelaunayTriangleD(DelaunayVertexD... vertices) { + this(Arrays.asList(vertices)); + } + + @Override + public boolean add(DelaunayVertexD vertex) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + return (this == o); + } + + /** + * Report the facet opposite vertex. + * + * @param vertex + * a vertex of this DelaunayTriangleD + * @return the facet opposite vertex + * @throws IllegalArgumentException + * if the vertex is not in triangle + */ + public ArraySet facetOpposite(DelaunayVertexD vertex) { + ArraySet facet = new ArraySet(this); + if (!facet.remove(vertex)) { + throw new IllegalArgumentException("Vertex not in triangle"); + } + return facet; + } + + /** + * @return the triangle's circumcenter + */ + public DelaunayVertexD getCircumcenter() { + if (circumcenter == null) { + circumcenter = DelaunayVertexD.circumcenter(this + .toArray(new DelaunayVertexD[0])); + } + return circumcenter; + } + + /** + * Get arbitrary vertex of this triangle, but not any of the bad vertices. + * + * @param badVertices + * one or more bad vertices + * @return a vertex of this triangle, but not one of the bad vertices + * @throws NoSuchElementException + * if no vertex found + */ + public DelaunayVertexD getVertexButNot(DelaunayVertexD... badVertices) { + Collection bad = Arrays.asList(badVertices); + for (DelaunayVertexD v : this) { + if (!bad.contains(v)) { + return v; + } + } + throw new NoSuchElementException("No vertex found"); + } + + /* The following two methods ensure that a DelaunayTriangleD is immutable */ + + @Override + public int hashCode() { + return (idNumber ^ (idNumber >>> 32)); + } + + /** + * True iff triangles are neighbors. Two triangles are neighbors if they + * share a facet. + * + * @param triangle + * the other DelaunayTriangleD + * @return true iff this DelaunayTriangleD is a neighbor of triangle + */ + public boolean isNeighbor(DelaunayTriangleD triangle) { + int count = 0; + for (DelaunayVertexD vertex : this) { + if (!triangle.contains(vertex)) { + count++; + } + } + return count == 1; + } + + /* The following two methods ensure that all triangles are different. */ + + @Override + public Iterator iterator() { + return new Iterator() { + + private Iterator it = DelaunayTriangleD.super + .iterator(); + + public boolean hasNext() { + return it.hasNext(); + } + + public DelaunayVertexD next() { + return it.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public String toString() { + if (!moreInfo) { + return "DelaunayTriangleD" + idNumber; + } + return "DelaunayTriangleD" + idNumber + super.toString(); + } + +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh2d/DelaunayTriangulationD.java b/src.core/toxi/geom/mesh2d/DelaunayTriangulationD.java new file mode 100644 index 00000000..89e87a2b --- /dev/null +++ b/src.core/toxi/geom/mesh2d/DelaunayTriangulationD.java @@ -0,0 +1,328 @@ +package toxi.geom.mesh2d; + +/* + * Copyright (c) 2005, 2007 by L. Paul Chew. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * 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. + */ + +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import toxi.util.datatypes.UndirectedGraph; + +/** + * A 2D Delaunay DelaunayTriangulationD (DT) with incremental site insertion. + * + * This is not the fastest way to build a DT, but it's a reasonable way to build + * a DT incrementally and it makes a nice interactive display. There are several + * O(n log n) methods, but they require that the sites are all known initially. + * + * A DelaunayTriangulationD is a Set of Triangles. A DelaunayTriangulationD is + * unmodifiable as a Set; the only way to change it is to add sites (via + * delaunayPlace). + * + * @author Paul Chew + * + * Created July 2005. Derived from an earlier, messier version. + * + * Modified November 2007. Rewrote to use AbstractSet as parent class + * and to use the UndirectedGraph class internally. Tried to make the DT + * algorithm clearer by explicitly creating a cavity. Added code needed + * to find a Voronoi cell. + * + * @author Karsten Schmidt + * + * Ported to use toxiclibs classes (June 2010). + */ +public class DelaunayTriangulationD extends AbstractSet { + + private DelaunayTriangleD mostRecent = null; + + private UndirectedGraph triGraph; + + /** + * All sites must fall within the initial triangle. + * + * @param triangle + * the initial triangle + */ + public DelaunayTriangulationD(DelaunayTriangleD triangle) { + triGraph = new UndirectedGraph(); + triGraph.add(triangle); + mostRecent = triangle; + } + + /** + * True iff triangle is a member of this triangulation. This method isn't + * required by AbstractSet, but it improves efficiency. + * + * @param triangle + * the object to check for membership + */ + public boolean contains(Object triangle) { + return triGraph.getNodes().contains(triangle); + } + + /** + * Place a new site into the DT. Nothing happens if the site matches an + * existing DT vertex. + * + * @param site + * the new DelaunayVertexD + * @throws IllegalArgumentException + * if site does not lie in any triangle + */ + public void delaunayPlace(DelaunayVertexD site) { + // Uses straightforward scheme rather than best asymptotic time + // Locate containing triangle + DelaunayTriangleD triangle = locate(site); + // Give up if no containing triangle or if site is already in DT + if (triangle == null) { + throw new IllegalArgumentException("No containing triangle"); + } + if (triangle.contains(site)) { + return; + } + // Determine the cavity and update the triangulation + Set cavity = getCavity(site, triangle); + mostRecent = update(site, cavity); + } + + /** + * Determine the cavity caused by site. + * + * @param site + * the site causing the cavity + * @param triangle + * the triangle containing site + * @return set of all triangles that have site in their circumcircle + */ + private Set getCavity(DelaunayVertexD site, + DelaunayTriangleD triangle) { + Set encroached = new HashSet(); + Queue toBeChecked = new LinkedList(); + Set marked = new HashSet(); + toBeChecked.add(triangle); + marked.add(triangle); + while (!toBeChecked.isEmpty()) { + triangle = toBeChecked.remove(); + if (site.vsCircumcircle(triangle.toArray(new DelaunayVertexD[0])) == 1) { + // Site outside triangle => triangle not in cavity + continue; + } + encroached.add(triangle); + // Check the neighbors + for (DelaunayTriangleD neighbor : triGraph + .getConnectedNodesFor(triangle)) { + if (marked.contains(neighbor)) { + continue; + } + marked.add(neighbor); + toBeChecked.add(neighbor); + } + } + return encroached; + } + + @Override + public Iterator iterator() { + return triGraph.getNodes().iterator(); + } + + /** + * Locate the triangle with point inside it or on its boundary. + * + * @param point + * the point to locate + * @return the triangle that holds point; null if no such triangle + */ + public DelaunayTriangleD locate(DelaunayVertexD point) { + DelaunayTriangleD triangle = mostRecent; + if (!this.contains(triangle)) { + triangle = null; + } + + // Try a directed walk (this works fine in 2D, but can fail in 3D) + Set visited = new HashSet(); + while (triangle != null) { + if (visited.contains(triangle)) { // This should never happen + System.out.println("Warning: Caught in a locate loop"); + break; + } + visited.add(triangle); + // Corner opposite point + DelaunayVertexD corner = point.isOutside(triangle + .toArray(new DelaunayVertexD[0])); + if (corner == null) { + return triangle; + } + triangle = this.neighborOpposite(corner, triangle); + } + // No luck; try brute force + System.out.println("Warning: Checking all triangles for " + point); + for (DelaunayTriangleD tri : this) { + if (point.isOutside(tri.toArray(new DelaunayVertexD[0])) == null) { + return tri; + } + } + // No such triangle + System.out.println("Warning: No triangle holds " + point); + return null; + } + + /** + * Report neighbor opposite the given vertex of triangle. + * + * @param site + * a vertex of triangle + * @param triangle + * we want the neighbor of this triangle + * @return the neighbor opposite site in triangle; null if none + * @throws IllegalArgumentException + * if site is not in this triangle + */ + public DelaunayTriangleD neighborOpposite(DelaunayVertexD site, + DelaunayTriangleD triangle) { + if (!triangle.contains(site)) { + throw new IllegalArgumentException("Bad vertex; not in triangle"); + } + for (DelaunayTriangleD neighbor : triGraph + .getConnectedNodesFor(triangle)) { + if (!neighbor.contains(site)) { + return neighbor; + } + } + return null; + } + + /** + * Return the set of triangles adjacent to triangle. + * + * @param triangle + * the triangle to check + * @return the neighbors of triangle + */ + public Set neighbors(DelaunayTriangleD triangle) { + return triGraph.getConnectedNodesFor(triangle); + } + + @Override + public int size() { + return triGraph.getNodes().size(); + } + + /** + * Report triangles surrounding site in order (cw or ccw). + * + * @param site + * we want the surrounding triangles for this site + * @param triangle + * a "starting" triangle that has site as a vertex + * @return all triangles surrounding site in order (cw or ccw) + * @throws IllegalArgumentException + * if site is not in triangle + */ + public List surroundingTriangles(DelaunayVertexD site, + DelaunayTriangleD triangle) { + if (!triangle.contains(site)) { + throw new IllegalArgumentException("Site not in triangle"); + } + List list = new ArrayList(); + DelaunayTriangleD start = triangle; + DelaunayVertexD guide = triangle.getVertexButNot(site); // Affects cw or + // ccw + while (true) { + list.add(triangle); + DelaunayTriangleD previous = triangle; + triangle = this.neighborOpposite(guide, triangle); // Next triangle + guide = previous.getVertexButNot(site, guide); // Update guide + if (triangle == start) { + break; + } + } + return list; + } + + @Override + public String toString() { + return "DelaunayTriangulationD with " + size() + " triangles"; + } + + /** + * Update the triangulation by removing the cavity triangles and then + * filling the cavity with new triangles. + * + * @param site + * the site that created the cavity + * @param cavity + * the triangles with site in their circumcircle + * @return one of the new triangles + */ + private DelaunayTriangleD update(DelaunayVertexD site, + Set cavity) { + Set> boundary = new HashSet>(); + Set theTriangles = new HashSet(); + + // Find boundary facets and adjacent triangles + for (DelaunayTriangleD triangle : cavity) { + theTriangles.addAll(neighbors(triangle)); + for (DelaunayVertexD vertex : triangle) { + Set facet = triangle.facetOpposite(vertex); + if (boundary.contains(facet)) { + boundary.remove(facet); + } else { + boundary.add(facet); + } + } + } + theTriangles.removeAll(cavity); // Adj triangles only + + // Remove the cavity triangles from the triangulation + for (DelaunayTriangleD triangle : cavity) { + triGraph.remove(triangle); + } + + // Build each new triangle and add it to the triangulation + Set newTriangles = new HashSet(); + for (Set vertices : boundary) { + vertices.add(site); + DelaunayTriangleD tri = new DelaunayTriangleD(vertices); + triGraph.add(tri); + newTriangles.add(tri); + } + + // Update the graph links for each new triangle + theTriangles.addAll(newTriangles); // Adj triangle + new triangles + for (DelaunayTriangleD triangle : newTriangles) { + for (DelaunayTriangleD other : theTriangles) { + if (triangle.isNeighbor(other)) { + triGraph.connect(triangle, other); + } + } + } + + // Return one of the new triangles + return newTriangles.iterator().next(); + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh2d/DelaunayVertexD.java b/src.core/toxi/geom/mesh2d/DelaunayVertexD.java new file mode 100644 index 00000000..f453331a --- /dev/null +++ b/src.core/toxi/geom/mesh2d/DelaunayVertexD.java @@ -0,0 +1,546 @@ +package toxi.geom.mesh2d; + +import toxi.geom.VecD2D; + +/* + * Copyright (c) 2005, 2007 by L. Paul Chew. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * 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. + */ + +/** + * Points in Euclidean space, implemented as double[]. + * + * Includes simple geometric operations. Uses matrices; a matrix is represented + * as an array of Pnts. Uses simplices; a simplex is represented as an array of + * Pnts. + * + * @author Paul Chew Created July 2005. Derived from an earlier, messier + * version. Modified Novemeber 2007. Minor clean up. + */ +public class DelaunayVertexD { + + /** + * Circumcenter of a simplex. + * + * @param simplex + * the simplex (as an array of Pnts) + * @return the circumcenter (a DelaunayVertexD) of simplex + */ + public static DelaunayVertexD circumcenter(DelaunayVertexD[] simplex) { + int dim = simplex[0].dimension(); + if (simplex.length - 1 != dim) { + throw new IllegalArgumentException("Dimension mismatch"); + } + DelaunayVertexD[] matrix = new DelaunayVertexD[dim]; + for (int i = 0; i < dim; i++) { + matrix[i] = simplex[i].bisector(simplex[i + 1]); + } + DelaunayVertexD hCenter = cross(matrix); // Center in homogeneous + // coordinates + double last = hCenter.coordinates[dim]; + double[] result = new double[dim]; + for (int i = 0; i < dim; i++) { + result[i] = hCenter.coordinates[i] / last; + } + return new DelaunayVertexD(result); + } + + /** + * Determine the signed content (i.e., area or volume, etc.) of a simplex. + * + * @param simplex + * the simplex (as an array of Pnts) + * @return the signed content of the simplex + */ + public static double content(DelaunayVertexD[] simplex) { + DelaunayVertexD[] matrix = new DelaunayVertexD[simplex.length]; + for (int i = 0; i < matrix.length; i++) { + matrix[i] = simplex[i].extend(1); + } + int fact = 1; + for (int i = 1; i < matrix.length; i++) { + fact = fact * i; + } + return determinant(matrix) / fact; + } + + /** + * Compute generalized cross-product of the rows of a matrix. The result is + * a DelaunayVertexD perpendicular (as a vector) to each row of the matrix. + * This is not an efficient implementation, but should be adequate for low + * dimension. + * + * @param matrix + * the matrix of Pnts (one less row than the DelaunayVertexD + * dimension) + * @return a DelaunayVertexD perpendicular to each row DelaunayVertexD + * @throws IllegalArgumentException + * if matrix is wrong shape + */ + public static DelaunayVertexD cross(DelaunayVertexD[] matrix) { + int len = matrix.length + 1; + if (len != matrix[0].dimension()) { + throw new IllegalArgumentException("Dimension mismatch"); + } + boolean[] columns = new boolean[len]; + for (int i = 0; i < len; i++) { + columns[i] = true; + } + double[] result = new double[len]; + int sign = 1; + try { + for (int i = 0; i < len; i++) { + columns[i] = false; + result[i] = sign * determinant(matrix, 0, columns); + columns[i] = true; + sign = -sign; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Matrix is wrong shape"); + } + return new DelaunayVertexD(result); + } + + /** + * Compute the determinant of a matrix (array of Pnts). This is not an + * efficient implementation, but should be adequate for low dimension. + * + * @param matrix + * the matrix as an array of Pnts + * @return the determinnant of the input matrix + * @throws IllegalArgumentException + * if dimensions are wrong + */ + public static double determinant(DelaunayVertexD[] matrix) { + if (matrix.length != matrix[0].dimension()) { + throw new IllegalArgumentException("Matrix is not square"); + } + boolean[] columns = new boolean[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + columns[i] = true; + } + try { + return determinant(matrix, 0, columns); + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Matrix is wrong shape"); + } + } + + /** + * Compute the determinant of a submatrix specified by starting row and by + * "active" columns. + * + * @param matrix + * the matrix as an array of Pnts + * @param row + * the starting row + * @param columns + * a boolean array indicating the "active" columns + * @return the determinant of the specified submatrix + * @throws ArrayIndexOutOfBoundsException + * if dimensions are wrong + */ + private static double determinant(DelaunayVertexD[] matrix, int row, + boolean[] columns) { + if (row == matrix.length) { + return 1; + } + double sum = 0; + int sign = 1; + for (int col = 0; col < columns.length; col++) { + if (!columns[col]) { + continue; + } + columns[col] = false; + sum += sign * matrix[row].coordinates[col] + * determinant(matrix, row + 1, columns); + columns[col] = true; + sign = -sign; + } + return sum; + } + + /** + * Create a String for a matrix. + * + * @param matrix + * the matrix (an array of Pnts) + * @return a String represenation of the matrix + */ + public static String toString(DelaunayVertexD[] matrix) { + StringBuilder buf = new StringBuilder("{"); + for (DelaunayVertexD row : matrix) { + buf.append(" " + row); + } + buf.append(" }"); + return buf.toString(); + } + + private double[] coordinates; // The point's coordinates + + /** + * Constructor. + * + * @param coords + * the coordinates + */ + public DelaunayVertexD(double... coords) { + // Copying is done here to ensure that DelaunayVertexD's coords cannot be + // altered. + // This is necessary because the double... notation actually creates a + // constructor with double[] as its argument. + coordinates = new double[coords.length]; + System.arraycopy(coords, 0, coordinates, 0, coords.length); + } + + /** + * Add. + * + * @param p + * the other DelaunayVertexD + * @return a new DelaunayVertexD = this + p + */ + public DelaunayVertexD add(DelaunayVertexD p) { + int len = dimCheck(p); + double[] coords = new double[len]; + for (int i = 0; i < len; i++) { + coords[i] = this.coordinates[i] + p.coordinates[i]; + } + return new DelaunayVertexD(coords); + } + + /** + * Angle (in radians) between two Pnts (treated as vectors). + * + * @param p + * the other DelaunayVertexD + * @return the angle (in radians) between the two Pnts + */ + public double angle(DelaunayVertexD p) { + return Math.acos(this.dot(p) / (this.magnitude() * p.magnitude())); + } + + /** + * Perpendicular bisector of two Pnts. Works in any dimension. The + * coefficients are returned as a DelaunayVertexD of one higher dimension + * (e.g., (A,B,C,D) for an equation of the form Ax + By + Cz + D = 0). + * + * @param point + * the other point + * @return the coefficients of the perpendicular bisector + */ + public DelaunayVertexD bisector(DelaunayVertexD point) { + dimCheck(point); + DelaunayVertexD diff = this.subtract(point); + DelaunayVertexD sum = this.add(point); + double dot = diff.dot(sum); + return diff.extend(-dot / 2); + } + + /** + * @return the specified coordinate of this DelaunayVertexD + * @throws ArrayIndexOutOfBoundsException + * for bad coordinate + */ + public double coord(int i) { + return this.coordinates[i]; + } + + /** + * Check that dimensions match. + * + * @param p + * the DelaunayVertexD to check (against this DelaunayVertexD) + * @return the dimension of the Pnts + * @throws IllegalArgumentException + * if dimension fail to match + */ + public int dimCheck(DelaunayVertexD p) { + int len = this.coordinates.length; + if (len != p.coordinates.length) { + throw new IllegalArgumentException("Dimension mismatch"); + } + return len; + } + + /** + * @return this DelaunayVertexD's dimension. + */ + public int dimension() { + return coordinates.length; + } + + /* Pnts as matrices */ + + /** + * Dot product. + * + * @param p + * the other DelaunayVertexD + * @return dot product of this DelaunayVertexD and p + */ + public double dot(DelaunayVertexD p) { + int len = dimCheck(p); + double sum = 0; + for (int i = 0; i < len; i++) { + sum += this.coordinates[i] * p.coordinates[i]; + } + return sum; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof DelaunayVertexD)) { + return false; + } + DelaunayVertexD p = (DelaunayVertexD) other; + if (this.coordinates.length != p.coordinates.length) { + return false; + } + for (int i = 0; i < this.coordinates.length; i++) { + if (this.coordinates[i] != p.coordinates[i]) { + return false; + } + } + return true; + } + + /** + * Create a new DelaunayVertexD by adding additional coordinates to this + * DelaunayVertexD. + * + * @param coords + * the new coordinates (added on the right end) + * @return a new DelaunayVertexD with the additional coordinates + */ + public DelaunayVertexD extend(double... coords) { + double[] result = new double[coordinates.length + coords.length]; + System.arraycopy(coordinates, 0, result, 0, coordinates.length); + System.arraycopy(coords, 0, result, coordinates.length, coords.length); + return new DelaunayVertexD(result); + } + + @Override + public int hashCode() { + int hash = 0; + for (double c : this.coordinates) { + long bits = Double.doubleToLongBits(c); + hash = (31 * hash) ^ (int) (bits ^ (bits >> 32)); + } + return hash; + } + + /* Pnts as simplices */ + + /** + * Test if this DelaunayVertexD is inside a simplex. + * + * @param simplex + * the simplex (an arary of Pnts) + * @return true iff this DelaunayVertexD is inside simplex. + */ + public boolean isInside(DelaunayVertexD[] simplex) { + int[] result = this.relation(simplex); + for (int r : result) { + if (r >= 0) { + return false; + } + } + return true; + } + + /** + * Test if this DelaunayVertexD is on a simplex. + * + * @param simplex + * the simplex (an array of Pnts) + * @return the simplex DelaunayVertexD that "witnesses" on-ness (or null if + * not on) + */ + public DelaunayVertexD isOn(DelaunayVertexD[] simplex) { + int[] result = this.relation(simplex); + DelaunayVertexD witness = null; + for (int i = 0; i < result.length; i++) { + if (result[i] == 0) { + witness = simplex[i]; + } else if (result[i] > 0) { + return null; + } + } + return witness; + } + + /** + * Test if this DelaunayVertexD is outside of simplex. + * + * @param simplex + * the simplex (an array of Pnts) + * @return simplex DelaunayVertexD that "witnesses" outsideness (or null if + * not outside) + */ + public DelaunayVertexD isOutside(DelaunayVertexD[] simplex) { + int[] result = this.relation(simplex); + for (int i = 0; i < result.length; i++) { + if (result[i] > 0) { + return simplex[i]; + } + } + return null; + } + + /** + * Magnitude (as a vector). + * + * @return the Euclidean length of this vector + */ + public double magnitude() { + return Math.sqrt(this.dot(this)); + } + + /** + * Relation between this DelaunayVertexD and a simplex (represented as an + * array of Pnts). Result is an array of signs, one for each vertex of the + * simplex, indicating the relation between the vertex, the vertex's + * opposite facet, and this DelaunayVertexD. + * + *
+     *   -1 means DelaunayVertexD is on same side of facet
+     *    0 means DelaunayVertexD is on the facet
+     *   +1 means DelaunayVertexD is on opposite side of facet
+     * 
+ * + * @param simplex + * an array of Pnts representing a simplex + * @return an array of signs showing relation between this DelaunayVertexD + * and simplex + * @throws IllegalArgumentExcpetion + * if the simplex is degenerate + */ + public int[] relation(DelaunayVertexD[] simplex) { + /* + * In 2D, we compute the cross of this matrix: 1 1 1 1 p0 a0 b0 c0 p1 a1 + * b1 c1 where (a, b, c) is the simplex and p is this DelaunayVertexD. + * The result is a vector in which the first coordinate is the signed + * area (all signed areas are off by the same constant factor) of the + * simplex and the remaining coordinates are the *negated* signed areas + * for the simplices in which p is substituted for each of the vertices. + * Analogous results occur in higher dimensions. + */ + int dim = simplex.length - 1; + if (this.dimension() != dim) { + throw new IllegalArgumentException("Dimension mismatch"); + } + + /* Create and load the matrix */ + DelaunayVertexD[] matrix = new DelaunayVertexD[dim + 1]; + /* First row */ + double[] coords = new double[dim + 2]; + for (int j = 0; j < coords.length; j++) { + coords[j] = 1; + } + matrix[0] = new DelaunayVertexD(coords); + /* Other rows */ + for (int i = 0; i < dim; i++) { + coords[0] = this.coordinates[i]; + for (int j = 0; j < simplex.length; j++) { + coords[j + 1] = simplex[j].coordinates[i]; + } + matrix[i + 1] = new DelaunayVertexD(coords); + } + + /* Compute and analyze the vector of areas/volumes/contents */ + DelaunayVertexD vector = cross(matrix); + double content = vector.coordinates[0]; + int[] result = new int[dim + 1]; + for (int i = 0; i < result.length; i++) { + double value = vector.coordinates[i + 1]; + if (Math.abs(value) <= 1.0e-6 * Math.abs(content)) { + result[i] = 0; + } else if (value < 0) { + result[i] = -1; + } else { + result[i] = 1; + } + } + if (content < 0) { + for (int i = 0; i < result.length; i++) { + result[i] = -result[i]; + } + } + if (content == 0) { + for (int i = 0; i < result.length; i++) { + result[i] = Math.abs(result[i]); + } + } + return result; + } + + /** + * Subtract. + * + * @param p + * the other DelaunayVertexD + * @return a new DelaunayVertexD = this - p + */ + public DelaunayVertexD subtract(DelaunayVertexD p) { + int len = dimCheck(p); + double[] coords = new double[len]; + for (int i = 0; i < len; i++) { + coords[i] = this.coordinates[i] - p.coordinates[i]; + } + return new DelaunayVertexD(coords); + } + + @Override + public String toString() { + if (coordinates.length == 0) { + return "DelaunayVertexD()"; + } + String result = "DelaunayVertexD(" + coordinates[0]; + for (int i = 1; i < coordinates.length; i++) { + result = result + "," + coordinates[i]; + } + result = result + ")"; + return result; + } + + public VecD2D toVecD2D() { + return new VecD2D(coordinates[0], coordinates[1]); + } + + /** + * Test relation between this DelaunayVertexD and circumcircle of a simplex. + * + * @param simplex + * the simplex (as an array of Pnts) + * @return -1, 0, or +1 for inside, on, or outside of circumcircle + */ + public int vsCircumcircle(DelaunayVertexD[] simplex) { + DelaunayVertexD[] matrix = new DelaunayVertexD[simplex.length + 1]; + for (int i = 0; i < simplex.length; i++) { + matrix[i] = simplex[i].extend(1, simplex[i].dot(simplex[i])); + } + matrix[simplex.length] = this.extend(1, this.dot(this)); + double d = determinant(matrix); + int result = (d < 0) ? -1 : ((d > 0) ? +1 : 0); + if (content(simplex) < 0) { + result = -result; + } + return result; + } +} \ No newline at end of file diff --git a/src.core/toxi/geom/mesh2d/VoronoiD.java b/src.core/toxi/geom/mesh2d/VoronoiD.java new file mode 100644 index 00000000..646712d1 --- /dev/null +++ b/src.core/toxi/geom/mesh2d/VoronoiD.java @@ -0,0 +1,105 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * Copyright (c) 2006-2011 Karsten Schmidt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * http://creativecommons.org/licenses/LGPL/2.1/ + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +package toxi.geom.mesh2d; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +import toxi.geom.PolygonD2D; +import toxi.geom.TriangleD2D; +import toxi.geom.VecD2D; + +public class VoronoiD { + + public static double DEFAULT_SIZE = 10000; + + protected DelaunayTriangulationD delaunay; + protected DelaunayTriangleD initialTriangle; + protected List sites = new ArrayList(); + + public VoronoiD() { + this(DEFAULT_SIZE); + } + + public VoronoiD(double size) { + initialTriangle = new DelaunayTriangleD( + new DelaunayVertexD(-size, -size), new DelaunayVertexD(size, + -size), new DelaunayVertexD(0, size)); + this.delaunay = new DelaunayTriangulationD(initialTriangle); + } + + public void addPoint(VecD2D p) { + sites.add(p.copy()); + delaunay.delaunayPlace(new DelaunayVertexD(p.x, p.y)); + } + + public void addPoints(Collection points) { + for (VecD2D p : points) { + addPoint(p); + } + } + + public List getRegions() { + List regions = new LinkedList(); + HashSet done = new HashSet( + initialTriangle); + for (DelaunayTriangleD triangle : delaunay) { + for (DelaunayVertexD site : triangle) { + if (done.contains(site)) { + continue; + } + done.add(site); + List list = delaunay.surroundingTriangles( + site, triangle); + PolygonD2D poly = new PolygonD2D(); + for (DelaunayTriangleD tri : list) { + DelaunayVertexD circumeter = tri.getCircumcenter(); + poly.add(new VecD2D(circumeter.coord(0), circumeter.coord(1))); + } + regions.add(poly); + } + } + return regions; + } + + public List getSites() { + return sites; + } + + public List getTriangles() { + List tris = new ArrayList(); + for (DelaunayTriangleD t : delaunay) { + tris.add(new TriangleD2D(t.get(0).toVecD2D(), t.get(1).toVecD2D(), t + .get(2).toVecD2D())); + } + return tris; + } +} diff --git a/src.test/toxi/test/geom/AABBDTest.java b/src.test/toxi/test/geom/AABBDTest.java new file mode 100644 index 00000000..551da14f --- /dev/null +++ b/src.test/toxi/test/geom/AABBDTest.java @@ -0,0 +1,79 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.AABBD; +import toxi.geom.RayD3D; +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.SphereD; +import toxi.geom.TriangleD3D; +import toxi.geom.VecD3D; + +public class AABBDTest extends TestCase { + + public void testAABBD2AABBD() { + AABBD box = new AABBD(new VecD3D(100, 0, 0), new VecD3D(20, 20, 20)); + AABBD b2 = new AABBD(new VecD3D(100, 30.1f, 0), new VecD3D(10, 10, 10)); + assertEquals(box.intersectsBox(b2), false); + } + + public void testAABBDNormal() { + AABBD box = new AABBD(new VecD3D(100, 100, 100), new VecD3D(100, 100, 100)); + VecD3D p = new VecD3D(100, 300, 100); + assertEquals(VecD3D.Y_AXIS, box.getNormalForPoint(p)); + p.set(100, -300, 100); + assertEquals(VecD3D.Y_AXIS.getInverted(), box.getNormalForPoint(p)); + p.set(300, 100, 100); + assertEquals(VecD3D.X_AXIS, box.getNormalForPoint(p)); + p.set(-300, 100, 100); + assertEquals(VecD3D.X_AXIS.getInverted(), box.getNormalForPoint(p)); + p.set(100, 100, 300); + assertEquals(VecD3D.Z_AXIS, box.getNormalForPoint(p)); + p.set(100, 100, -300); + assertEquals(VecD3D.Z_AXIS.getInverted(), box.getNormalForPoint(p)); + } + + public void testAABBDRayIntersect() { + AABBD box = AABBD.fromMinMax(new VecD3D(), new VecD3D(100, 100, 100)); + RayD3D r = new RayD3D(new VecD3D(50, 10, 10), new VecD3D(0, 1, 0)); + System.out.println(box.intersectsRay(r, -1000, 1000)); + } + + public void testAABBDSphereD() { + AABBD box = new AABBD(new VecD3D(100, 0, 0), new VecD3D(20, 20, 20)); + SphereD s = new SphereD(new VecD3D(100, 0, 0), 50); + assertEquals(box.intersectsSphereD(s), true); + } + + public void testAABBDTri() { + AABBD box = new AABBD(new VecD3D(), new VecD3D(100, 100, 100)); + VecD3D a = new VecD3D(-90, 0, 0); + VecD3D b = new VecD3D(-110, -200, 0); + VecD3D c = new VecD3D(-110, 200, 0); + TriangleD3D tri = new TriangleD3D(a, b, c); + System.out.println(box.intersectsTriangle(tri)); + } + + public void testInclude() { + AABBD box = AABBD.fromMinMax(new VecD3D(), new VecD3D(100, 100, 100)); + System.out.println(box); + VecD3D p = new VecD3D(-150, -50, 110); + box.growToContainPoint(p); + System.out.println(box.getMin() + " " + box.getMax()); + assertTrue(box.containsPoint(p)); + } + + public void testIsec() { + AABBD box = AABBD.fromMinMax(new VecD3D(), new VecD3D(100, 100, 100)); + AABBD box2 = AABBD.fromMinMax(new VecD3D(10, 10, 10), + new VecD3D(80, 80, 80)); + assertTrue(box.intersectsBox(box2)); + assertTrue(box2.intersectsBox(box)); + } + + public void testIsInAABBD() { + AABBD box = new AABBD(new VecD3D(100, 0, 0), new VecD3D(20, 20, 20)); + ReadonlyVecD3D p = new VecD3D(80, -19.99f, 0); + assertEquals(p.isInAABBD(box), true); + assertEquals(new VecD3D(120.01f, 19.99f, 0).isInAABBD(box), false); + } +} diff --git a/src.test/toxi/test/geom/AllGeomDTests.java b/src.test/toxi/test/geom/AllGeomDTests.java new file mode 100644 index 00000000..c7f86652 --- /dev/null +++ b/src.test/toxi/test/geom/AllGeomDTests.java @@ -0,0 +1,32 @@ +package toxi.test.geom; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllGeomDTests { + + public static Test suite() { + TestSuite suite = new TestSuite(AllGeomDTests.class.getName()); + // $JUnit-BEGIN$ + suite.addTestSuite(AABBDTest.class); + suite.addTestSuite(CircleDTest.class); + suite.addTestSuite(LineD2DTest.class); + suite.addTestSuite(LineD3DTest.class); + suite.addTestSuite(MatrixDTest.class); + suite.addTestSuite(OriginD3DTest.class); + suite.addTestSuite(PlaneDTest.class); + suite.addTestSuite(PolygonDTest.class); + suite.addTestSuite(QuaternionDTest.class); + suite.addTestSuite(RectDTest.class); + suite.addTestSuite(SphereDTest.class); + suite.addTestSuite(TreeDTest.class); + suite.addTestSuite(TriangleD2DTest.class); + suite.addTestSuite(TriangleMeshDTest.class); + suite.addTestSuite(TriangleDTest.class); + suite.addTestSuite(VecD3DTest.class); + suite.addTestSuite(WEMeshDTest.class); + // $JUnit-END$ + return suite; + } + +} diff --git a/src.test/toxi/test/geom/CircleDTest.java b/src.test/toxi/test/geom/CircleDTest.java new file mode 100644 index 00000000..ba699fc3 --- /dev/null +++ b/src.test/toxi/test/geom/CircleDTest.java @@ -0,0 +1,37 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.CircleD; +import toxi.geom.VecD2D; + +public class CircleDTest extends TestCase { + + private void showPoints(VecD2D[] points) { + if (points != null) { + for (VecD2D p : points) { + System.out.println(p); + } + } else { + System.out.println(""); + } + } + + public void testCircleDCircleDIntersection() { + CircleD a = new CircleD(100); + CircleD b = new CircleD(new VecD2D(200, 100), 200); + VecD2D[] isec = a.intersectsCircleD(b); + assertTrue(isec != null); + assertTrue(isec[0].equals(new VecD2D(0, 100))); + showPoints(isec); + b.setRadius(100); + isec = a.intersectsCircleD(b); + assertTrue(isec == null); + b.setRadius(99).set(0, 0); + isec = a.intersectsCircleD(b); + assertTrue(isec == null); + b.x = 1; + isec = a.intersectsCircleD(b); + assertTrue(isec != null); + showPoints(isec); + } +} diff --git a/src.test/toxi/test/geom/F2DandD2FTest.java b/src.test/toxi/test/geom/F2DandD2FTest.java new file mode 100644 index 00000000..ddfcee07 --- /dev/null +++ b/src.test/toxi/test/geom/F2DandD2FTest.java @@ -0,0 +1,232 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.AABB; +import toxi.geom.AABBD; +import toxi.geom.Circle; +import toxi.geom.CircleD; +import toxi.geom.Quaternion; +import toxi.geom.QuaternionD; +import toxi.geom.Sphere; +import toxi.geom.SphereD; +import toxi.geom.Vec2D; +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; +import toxi.geom.VecD4D; + + +/* For those pairs of float and double classes which have + * 1) a self-class constructor, + * AND 2) a constructor from more primitive classes + * a constructor was added to each of the class pair to convert to the other resolution class. + * + * Thus Circle got a Circle(CircleD) constructor + * because it has a Circle(Circle) constructor and a Circle(Vec2D,float) constructor + * + * IsectData2D did not get a IsectData2D(IsectDataD2D) constructor because there is no + * constructor for IsectData2D from primitive classes + * + * This test class confirms those constructors. + */ +public class F2DandD2FTest extends TestCase { + double d0=0.123456789012345678901234567890; + double d1=Math.PI; + double d2=Math.E; + double d3=Math.atan(3); + double d4=Math.atan(4); + double d5=Math.atan(5); + float f0=(float)d0; + float f1=(float)d1; + float f2=(float)d2; + float f3=(float)d3; + float f4=(float)d4; + float f5=(float)d5; + double df0=f0; + double df1=f1; + double df2=f2; + double df3=f3; + double df4=f4; + double df5=f5; + + public String getMethodName() { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + return stackTrace[2].getMethodName(); + } + + public void testF2DVec2D() { + //for(int ii=0;ii<13;ii++) { + // System.out.println(String.format("ii=%2d %30.27f",ii,Math.atan(ii/10.))); + // System.out.println(String.format(" %20.17f",Math.atan(ii/10.))); + //} + VecD2D a = new VecD2D(0.12345679,3.1415927); + Vec2D b = new Vec2D((float)0.123456789012345678901234567890,(float)Math.PI); + VecD2D c = new VecD2D(a); + System.out.println("\n"+getMethodName()); + System.out.println("floats "+b); + System.out.println(String.format("pure doubles (%20.18f,%20.18f),",a.x,a.y)); + System.out.println(String.format("doubles from constructor (%20.18f,%20.18f),",c.x,c.y)); + assertEquals(a,c); + } + + public void testF2DVec3D() { + VecD3D a = new VecD3D(0.12345679,3.1415927,2.7182817); + Vec3D b = new Vec3D((float)0.123456789012345678901234567890,(float)Math.PI,(float)Math.E); + VecD3D c = new VecD3D(a); + System.out.println("\n"+getMethodName()); + System.out.println("floats "+b); + System.out.println(String.format("pure doubles (%20.18f,%20.18f,%20.18f),",a.x,a.y,a.z)); + System.out.println(String.format("doubles from constructor (%20.18f,%20.18f,%20.18f),",c.x,c.y,c.z)); + assertEquals(a,c); + } + + public void testF2DVec4D() { + VecD4D a = new VecD4D(0.12345679,3.1415927,2.7182817,0.4636476); + Vec4D b = new Vec4D((float)0.123456789012345678901234567890,(float)Math.PI,(float)Math.E,(float)Math.atan(.5)); + VecD4D c = new VecD4D(a); + System.out.println("\n"+getMethodName()); + System.out.println("floats "+b); + System.out.println(String.format("pure doubles (%20.18f,%20.18f,%20.18f,%20.18f),",a.x,a.y,a.z,a.w)); + System.out.println(String.format("doubles from constructor (%20.18f,%20.18f,%20.18f,%20.18f),",c.x,c.y,c.z,c.w)); + assertEquals(a,c); + } + + public void testD2FVecD2D() { + VecD2D a = new VecD2D(0.123456789012345678901234567890,Math.PI); + Vec2D b = new Vec2D((float)0.123456789012345678901234567890,(float)Math.PI); + Vec2D c = new Vec2D(a); + System.out.println("\n"+getMethodName()); + System.out.println("pure double "+a); + System.out.println("floats "+b); + System.out.println("floats from constructor "+c); + assertEquals(b,c); + } + + public void testD2FVecD3D() { + VecD3D a = new VecD3D(0.123456789012345678901234567890,Math.PI,Math.E); + Vec3D b = new Vec3D((float)0.123456789012345678901234567890,(float)Math.PI,(float)Math.E); + Vec3D c = new Vec3D(a); + System.out.println("\n"+getMethodName()); + System.out.println("pure double "+a); + System.out.println("floats "+b); + System.out.println("floats from constructor "+c); + assertEquals(b,c); + } + + public void testD2FVecD4D() { + VecD4D a = new VecD4D(0.123456789012345678901234567890,Math.PI,Math.E,Math.atan(.5)); + Vec4D b = new Vec4D((float)0.123456789012345678901234567890,(float)Math.PI,(float)Math.E,(float)Math.atan(.5)); + Vec4D c = new Vec4D(a); + System.out.println("\n"+getMethodName()); + System.out.println("pure double "+a); + System.out.println("floats "+b); + System.out.println("floats from constructor "+c); + assertEquals(b,c); + } + + public void testD2FAABBD() { + AABBD a = new AABBD(new VecD3D(d0,d1,d2),new VecD3D(d3,d4,d5)); + AABB b = new AABB(new Vec3D(f0,f1,f2),new Vec3D(f3,f4,f5)); + AABB c = new AABB(a); + System.out.println("\n"+getMethodName()); + System.out.println(a.toString()); + System.out.println(b.toString()); + System.out.println(c.toString()); + assertEquals(b,c); + } + public void testF2DAABB() { + AABBD a = new AABBD(new VecD3D(df0,df1,df2),new VecD3D(df3,df4,df5)); + AABB b = new AABB(new Vec3D(f0,f1,f2),new Vec3D(f3,f4,f5)); + AABBD c = new AABBD(b); + System.out.println("\n"+getMethodName()); + System.out.println(a.toString()); + System.out.println(b.toString()); + System.out.println(c.toString()); + assertEquals(a,c); + } + + public void testD2FCircleD() { + CircleD a = new CircleD(new VecD2D(d0,d1),d3); + Circle b = new Circle(new Vec2D(f0,f1),f3); + Circle c = new Circle(a); + System.out.println("\n"+getMethodName()); + System.out.println(a.toString()); + System.out.println(b.toString()); + System.out.println(c.toString()); + assertEquals(b,c); + } + public void testF2DCircle() { + CircleD a = new CircleD(new VecD2D(df0,df1),df3); + Circle b = new Circle(new Vec2D(f0,f1),f3); + CircleD c = new CircleD(b); + System.out.println("\n"+getMethodName()); + System.out.println(a.toString()); + System.out.println(b.toString()); + System.out.println(c.toString()); + assertEquals(a,c); + } + + public void testD2FQuaternionD() { + QuaternionD a = new QuaternionD(d0,d1,d2,d3); + Quaternion b = new Quaternion(f0,f1,f2,f3); + Quaternion c = new Quaternion(a); + System.out.println("\n"+getMethodName()); + System.out.println(a.toString()); + System.out.println(b.toString()); + System.out.println(c.toString()); + //assertEquals(b,c); /* this fails */ + float dx=b.x-c.x; + float dy=b.y-c.y; + float dz=b.z-c.z; + float dw=b.w-c.w; + assertTrue( (dx map = new HashMap(); + map.put(l1, new WingedEdgeD(new WEVertexD(l1.a, 0), + new WEVertexD(l1.b, 1), null, 0)); + WingedEdgeD e = map.get(l1); + assertEquals(l1, e); + } +} diff --git a/src.test/toxi/test/geom/MatrixDTest.java b/src.test/toxi/test/geom/MatrixDTest.java new file mode 100644 index 00000000..f51fb59c --- /dev/null +++ b/src.test/toxi/test/geom/MatrixDTest.java @@ -0,0 +1,50 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.Matrix4x4; +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +public class MatrixDTest extends TestCase { + + public void testInverse() { + Matrix4x4 m = new Matrix4x4(); + m.translateSelf(100, 100, 0); + m.rotateX(MathUtils.HALF_PI); + m.scaleSelf(10, 10, 10); + System.out.println(m); + VecD3D v = new VecD3D(0, 1, 0); + VecD3D w = m.applyTo(v); + m = m.getInverted(); + ReadonlyVecD3D v2 = m.applyTo(w); + System.out.println(w); + System.out.println(v2); + assertTrue(v2.equalsWithTolerance(v, 0.0001f)); + } + + public void testRotate() { + Matrix4x4 m = new Matrix4x4(); + m.rotateX(MathUtils.HALF_PI); + VecD3D v = m.applyTo(new VecD3D(0, 1, 0)); + assertTrue(new VecD3D(0, 0, 1).equalsWithTolerance(v, 0.00001f)); + m.identity(); + m.rotateY(MathUtils.HALF_PI); + v = m.applyTo(new VecD3D(1, 0, 0)); + assertTrue(new VecD3D(0, 0, -1).equalsWithTolerance(v, 0.00001f)); + m.identity(); + m.rotateZ(MathUtils.HALF_PI); + v = m.applyTo(new VecD3D(1, 0, 0)); + assertTrue(new VecD3D(0, 1, 0).equalsWithTolerance(v, 0.00001f)); + m.identity(); + m.rotateAroundAxis(new VecD3D(0, 1, 0), MathUtils.HALF_PI); + v = m.applyTo(new VecD3D(1, 0, 0)); + assertTrue(new VecD3D(0, 0, 1).equalsWithTolerance(v, 0.00001f)); + } + + public void testTranslate() { + Matrix4x4 m = new Matrix4x4(); + m.translateSelf(100, 100, 100); + assertEquals(new VecD3D(100, 100, 100), m.applyTo(new VecD3D())); + } +} \ No newline at end of file diff --git a/src.test/toxi/test/geom/OriginD3DTest.java b/src.test/toxi/test/geom/OriginD3DTest.java new file mode 100644 index 00000000..37e2e938 --- /dev/null +++ b/src.test/toxi/test/geom/OriginD3DTest.java @@ -0,0 +1,16 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.OriginD3D; +import toxi.geom.VecD3D; + +public class OriginD3DTest extends TestCase { + + public void testViewConstruct() { + OriginD3D o = new OriginD3D(new VecD3D(0, -100, 0), new VecD3D(0, 1, 0)); + System.out.println(o.xAxis); + System.out.println(o.yAxis); + System.out.println(o.zAxis); + System.out.println(o.xAxis.angleBetween(o.zAxis)); + } +} diff --git a/src.test/toxi/test/geom/PlaneDTest.java b/src.test/toxi/test/geom/PlaneDTest.java new file mode 100644 index 00000000..f549dddf --- /dev/null +++ b/src.test/toxi/test/geom/PlaneDTest.java @@ -0,0 +1,27 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.PlaneD; +import toxi.geom.TriangleD3D; +import toxi.geom.VecD3D; + +public class PlaneDTest extends TestCase { + + public void testContainment() { + TriangleD3D t = new TriangleD3D(new VecD3D(-100, 0, 0), new VecD3D(0, 0, + -100), new VecD3D(0, 0, 100)); + PlaneD pl = new PlaneD(t.computeCentroid(), t.computeNormal()); + } + + public void testProjection() { + VecD3D origin = new VecD3D(0, 100, 0); + PlaneD plane = new PlaneD(origin, new VecD3D(0, 1, 0)); + VecD3D proj; + proj = plane.getProjectedPoint(new VecD3D()); + assertEquals(origin, proj); + proj = plane.getProjectedPoint(new VecD3D(0, 200, 0)); + assertEquals(origin, proj); + proj = plane.getProjectedPoint(origin); + assertEquals(origin, proj); + } +} diff --git a/src.test/toxi/test/geom/PolygonDTest.java b/src.test/toxi/test/geom/PolygonDTest.java new file mode 100644 index 00000000..ef48c1d3 --- /dev/null +++ b/src.test/toxi/test/geom/PolygonDTest.java @@ -0,0 +1,63 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.CircleD; +import toxi.geom.PolygonD2D; +import toxi.geom.ReadonlyVecD2D; +import toxi.geom.VecD2D; + +public class PolygonDTest extends TestCase { + + public void testAreaAndCentroid() { + PolygonD2D p = new PolygonD2D(); + p.add(new VecD2D()); + p.add(new VecD2D(1, 0)); + p.add(new VecD2D(1, 1)); + p.add(new VecD2D(0, 1)); + p.add(new VecD2D()); + assertEquals(4, p.getNumVertices()); + double area = p.getArea(); + assertEquals(1f, area); + ReadonlyVecD2D centroid = p.getCentroid(); + assertEquals(new VecD2D(0.5f, 0.5f), centroid); + } + + public void testCircleDArea() { + double radius = 1; + int subdiv = 36; + PolygonD2D p = new CircleD(radius).toPolygonD2D(subdiv); + double area = p.getArea(); + double area2 = new CircleD(radius).getArea(); + double ratio = area / area2; + assertTrue((1 - ratio) < 0.01); + } + + public void testClockwise() { + PolygonD2D p = new CircleD(50).toPolygonD2D(8); + assertTrue(p.isClockwise()); + } + + public void testContainment() { + final VecD2D origin = new VecD2D(100, 100); + PolygonD2D p = new CircleD(origin, 50).toPolygonD2D(8); + assertTrue(p.containsPoint(origin)); + assertTrue(p.containsPoint(p.vertices.get(0))); + assertFalse(p.containsPoint(p.vertices.get(3).scale(1.01f))); + } + + public void testIncreaseVertcount() { + final VecD2D origin = new VecD2D(100, 100); + PolygonD2D p = new CircleD(origin, 50).toPolygonD2D(3); + p.increaseVertexCount(6); + assertEquals(6, p.getNumVertices()); + } + + public void testReduce() { + PolygonD2D p = new CircleD(100).toPolygonD2D(30); + double len = p.vertices.get(0).distanceTo(p.vertices.get(1)); + p.reduceVertices(len * 0.99f); + assertEquals(30, p.getNumVertices()); + p.reduceVertices(len * 1.5f); + assertEquals(15, p.getNumVertices()); + } +} diff --git a/src.test/toxi/test/geom/QuaternionDTest.java b/src.test/toxi/test/geom/QuaternionDTest.java new file mode 100644 index 00000000..604dcdb4 --- /dev/null +++ b/src.test/toxi/test/geom/QuaternionDTest.java @@ -0,0 +1,56 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.Matrix4x4; +import toxi.geom.QuaternionD; +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +public class QuaternionDTest extends TestCase { + + public void testCreateFromAxisAngle() { + ReadonlyVecD3D axis = new VecD3D(100, 100, 100); + double angle = MathUtils.PI * 1.5f; + QuaternionD a = QuaternionD.createFromAxisAngle(axis, angle); + assertEquals(MathUtils.sin(-MathUtils.QUARTER_PI), a.w); + double[] reverse = a.toAxisAngle(); + VecD3D revAxis = new VecD3D(reverse[1], reverse[2], reverse[3]); + assertTrue(axis.getNormalized().equalsWithTolerance(revAxis, 0.01f)); + assertTrue(MathUtils.abs(angle - reverse[0]) < 0.01); + } + + public void testEuler() { + QuaternionD q = QuaternionD.createFromEuler(MathUtils.QUARTER_PI, + MathUtils.QUARTER_PI, 0); + System.out.println(q); + double[] reverse = q.toAxisAngle(); + System.out.println("toAxisAngle():"); + for (double f : reverse) { + System.out.println(f); + } + } + + public void testMatrixRoundtrip() { + for (int i = 0; i < 1000; i++) { + QuaternionD q = QuaternionD.createFromAxisAngle(VecD3D.randomVector(), + MathUtils.random(MathUtils.TWO_PI)).normalize(); + Matrix4x4 m = q.toMatrix4x4(); + QuaternionD q2 = QuaternionD.createFromMatrix(m); + VecD3D p = VecD3D.randomVector(); + VecD3D p2 = p.copy(); + q.applyTo(p); + q2.applyTo(p2); + // doubles are not very kind to round tripping + // hence quite large epsilon + assertTrue(p.equalsWithTolerance(p2, 0.0001f)); + } + } + + public void testSlerp() { + QuaternionD a = new QuaternionD(0, new VecD3D(0, 0, -1)); + QuaternionD b = new QuaternionD(0, new VecD3D(0, 0, 1)); + QuaternionD c = a.interpolateTo(b, 0.05f); + System.out.println(c); + } +} diff --git a/src.test/toxi/test/geom/RectDTest.java b/src.test/toxi/test/geom/RectDTest.java new file mode 100644 index 00000000..96ebbe20 --- /dev/null +++ b/src.test/toxi/test/geom/RectDTest.java @@ -0,0 +1,44 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.RectD; +import toxi.geom.VecD2D; + +public class RectDTest extends TestCase { + + public void testIntersectionRectD() { + RectD a = new RectD(100, 100, 100, 100); + RectD b = new RectD(80, 80, 100, 100); + RectD i = a.intersectionRectDWith(b); + assertEquals(new RectD(100, 100, 80, 80), i); + b = new RectD(80, 80, 20, 20); + i = a.intersectionRectDWith(b); + assertEquals(new VecD2D(), i.getDimensions()); + b.width = 10; + i = a.intersectionRectDWith(b); + assertNull(i); + b = new RectD(180, 180, 30, 50); + i = a.intersectionRectDWith(b); + assertEquals(new RectD(180, 180, 20, 20), i); + } + + public void testIsec() { + RectD a = new RectD(100, 100, 100, 100); + RectD b = new RectD(110, 110, 10, 10); + assertTrue(a.intersectsRectD(b)); + assertTrue(b.intersectsRectD(a)); + b = new RectD(80, 80, 30, 200); + assertTrue(a.intersectsRectD(b)); + } + + public void testRectDMerge() { + RectD r = new RectD(-10, 2, 3, 3); + RectD s = new RectD(-8, 4, 5, 3); + r = r.unionRectDWith(s); + assertEquals(new RectD(-10, 2, 7, 5), r); + r = new RectD(0, 0, 3, 3); + s = new RectD(-1, 2, 1, 1); + r = r.unionRectDWith(s); + assertEquals(new RectD(-1, 0, 4, 3), r); + } +} diff --git a/src.test/toxi/test/geom/SphereDTest.java b/src.test/toxi/test/geom/SphereDTest.java new file mode 100644 index 00000000..f9a57c8b --- /dev/null +++ b/src.test/toxi/test/geom/SphereDTest.java @@ -0,0 +1,38 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.RayD3D; +import toxi.geom.SphereD; +import toxi.geom.SphereDIntersectorReflector; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; +import toxi.math.MathUtils; + +public class SphereDTest extends TestCase { + + public void testIsInSphereD() { + VecD3D p = new VecD3D(0, -10, 0); + SphereD s = new SphereD(new VecD3D(), 10); + assertEquals(s.containsPoint(p), true); + p.set(0, 10.1f, 0); + assertEquals(s.containsPoint(p), false); + } + + public void testReflectRayD() { + SphereDIntersectorReflector si = new SphereDIntersectorReflector( + new VecD3D(0, 0, 0), 10); + RayD3D r = si.reflectRayD(new RayD3D(new VecD3D(100, 100, 0), new VecD3D(-1, + -1, 0))); + double absDiff = r.getDirection().angleBetween(new VecD3D(1, 1, 0), true); + System.out.println(r + " diff: " + absDiff); + assertEquals(absDiff < 0.002, true); + } + + public void testSurfaceDistance() { + VecD2D p = new VecD2D(90, 60).scale(MathUtils.DEG2RAD); + VecD2D q = new VecD2D(90, 61).scale(MathUtils.DEG2RAD); + SphereD e = new SphereD(SphereD.EARTH_RADIUS); + double dist = e.surfaceDistanceBetween(p, q); + assertTrue(MathUtils.abs(dist - 111.1952) < 0.1); + } +} diff --git a/src.test/toxi/test/geom/TreeDTest.java b/src.test/toxi/test/geom/TreeDTest.java new file mode 100644 index 00000000..c975a313 --- /dev/null +++ b/src.test/toxi/test/geom/TreeDTest.java @@ -0,0 +1,46 @@ +package toxi.test.geom; + +import java.util.List; + +import junit.framework.TestCase; +import toxi.geom.AABBD; +import toxi.geom.PointOctreeD; +import toxi.geom.PointQuadtreeD; +import toxi.geom.RectD; +import toxi.geom.SphereD; +import toxi.geom.VecD2D; +import toxi.geom.VecD3D; + +public class TreeDTest extends TestCase { + + public void testOctree() { + PointOctreeD t = new PointOctreeD(new VecD3D(), 100); + t.setMinNodeSize(0.5f); + assertEquals(t.addPoint(new VecD3D(0, 0, 0)), true); + assertEquals(t.addPoint(new VecD3D(1, 0, 0)), true); + PointOctreeD leaf1 = t.getLeafForPoint(new VecD3D(0, 0, 0)); + PointOctreeD leaf2 = t.getLeafForPoint(new VecD3D(1, 0, 0)); + assertNotSame(leaf1, leaf2); + assertEquals(t.addPoint(new VecD3D(0, 100, 0)), true); + assertEquals(t.addPoint(new VecD3D(101, 0, 0)), false); + List points = t.getPointsWithinSphereD(new SphereD(new VecD3D(50, + 0, 0), 50)); + assertEquals(points.size() == 2, true); + points = t.getPointsWithinBox(new AABBD(new VecD3D(50, 50, 50), + new VecD3D(50, 50, 50))); + assertEquals(points.size() == 3, true); + } + + public void testQuadtree() { + PointQuadtreeD t = new PointQuadtreeD(null, 0, 0, 100, 100); + assertEquals(t.index(new VecD2D(0, 0)), true); + assertEquals(t.index(new VecD2D(1, 1)), true); + assertEquals(t.index(new VecD2D(4, 0)), true); + PointQuadtreeD leaf1 = t.findNode(new VecD2D(0, 0)); + PointQuadtreeD leaf2 = t.findNode(new VecD2D(4, 0)); + assertNotSame(leaf1, leaf2); + List points = t.itemsWithinRectD(new RectD(0, 0, 2, 2), null); + assertEquals(2, points.size()); + } + +} diff --git a/src.test/toxi/test/geom/TriangleD2DTest.java b/src.test/toxi/test/geom/TriangleD2DTest.java new file mode 100644 index 00000000..940ca761 --- /dev/null +++ b/src.test/toxi/test/geom/TriangleD2DTest.java @@ -0,0 +1,99 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.ReadonlyVec2D; +import toxi.geom.Triangle2D; +import toxi.geom.Vec2D; +import toxi.geom.Vec3D; + +public class TriangleD2DTest extends TestCase { + + public void testBarycentric() { + Vec2D a = new Vec2D(-100, 0); + Vec2D b = new Vec2D(0, -100); + Vec2D c = new Vec2D(100, 0); + Triangle2D t = new Triangle2D(a, b, c); + assertEquals(new Vec3D(1, 0, 0), t.toBarycentric(a)); + assertEquals(new Vec3D(0, 1, 0), t.toBarycentric(b)); + assertEquals(new Vec3D(0, 0, 1), t.toBarycentric(c)); + // test roundtrip + assertEquals(a, t.fromBarycentric(t.toBarycentric(a))); + assertEquals(b, t.fromBarycentric(t.toBarycentric(b))); + assertEquals(c, t.fromBarycentric(t.toBarycentric(c))); + Vec2D p = new Vec2D(0, 0); + assertEquals(p, t.fromBarycentric(t.toBarycentric(p))); + // test point outside + Vec3D bp = t.toBarycentric(new Vec2D(0, -150)); + assertTrue(bp.magnitude() > 1); + } + + public void testCentroid() { + Vec2D a = new Vec2D(-100, 0); + Vec2D b = new Vec2D(0, 100); + Vec2D c = new Vec2D(100, 0); + Triangle2D t = new Triangle2D(a, b, c); + ReadonlyVec2D centroid = t.computeCentroid(); + assertTrue("incorrect centroid", + centroid.equals(new Vec2D(0, 100).scaleSelf(1f / 3))); + } + + public void testClockwise() { + Vec2D a = new Vec2D(-100, 0); + Vec2D b = new Vec2D(0, -100); + Vec2D c = new Vec2D(100, 0); + Vec2D d = new Vec2D(50, 50); + // clockwise + assertTrue(Triangle2D.isClockwise(a, b, c)); + assertTrue(Triangle2D.isClockwise(b, c, d)); + assertTrue(Triangle2D.isClockwise(c, d, a)); + assertTrue(Triangle2D.isClockwise(a, c, d)); + // anticlockwise + assertFalse(Triangle2D.isClockwise(a, c, b)); + assertFalse(Triangle2D.isClockwise(d, c, b)); + assertFalse(Triangle2D.isClockwise(a, d, c)); + } + + public void testContainment() { + Vec2D a = new Vec2D(-100, 0); + Vec2D b = new Vec2D(0, -100); + Vec2D c = new Vec2D(100, 0); + Triangle2D t = new Triangle2D(a, b, c); + assertTrue(t.containsPoint(new Vec2D(0, -50))); + assertTrue(t.containsPoint(a)); + assertTrue(t.containsPoint(b)); + assertTrue(t.containsPoint(c)); + assertFalse(t.containsPoint(new Vec2D(0, -101))); + // check anti-clockwise + t.flipVertexOrder(); + assertTrue(t.containsPoint(new Vec2D(0, -50))); + assertTrue(t.containsPoint(a)); + assertTrue(t.containsPoint(b)); + assertTrue(t.containsPoint(c)); + assertFalse(t.containsPoint(new Vec2D(0, -101))); + } + + public void testEquilateral() { + Triangle2D t = Triangle2D.createEquilateralFrom(new Vec2D(-100, 0), + new Vec2D(100, 0)); + assertEquals(new Vec2D(0, -57.735027f), t.computeCentroid()); + } + + public void testIntersection() { + Vec2D a = new Vec2D(-100, 0); + Vec2D b = new Vec2D(0, -100); + Vec2D c = new Vec2D(100, 0); + Vec2D d = new Vec2D(-200, -50); + Vec2D e = new Vec2D(0, 100); + Vec2D f = new Vec2D(0, -30); + Triangle2D t = new Triangle2D(a, b, c); + Triangle2D t2 = new Triangle2D(d, e, f); + assertTrue(t.intersectsTriangle(t2)); + f.x = 100; + assertTrue(t.intersectsTriangle(t2)); + assertTrue(t.intersectsTriangle(new Triangle2D(a, c, e))); + assertFalse(t.intersectsTriangle(new Triangle2D(a.add(0, 0.01f), c.add( + 0, 0.01f), e))); + assertTrue(t.intersectsTriangle(new Triangle2D(a.add(0, 0.01f), c.add( + 0, 0.01f), f))); + } +} diff --git a/src.test/toxi/test/geom/TriangleDTest.java b/src.test/toxi/test/geom/TriangleDTest.java new file mode 100644 index 00000000..9d0ee192 --- /dev/null +++ b/src.test/toxi/test/geom/TriangleDTest.java @@ -0,0 +1,70 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.ReadonlyVecD3D; +import toxi.geom.TriangleD3D; +import toxi.geom.VecD3D; + +public class TriangleDTest extends TestCase { + + public void testBarycentric() { + VecD3D a = new VecD3D(-100, -100, 0); + VecD3D c = new VecD3D(100, 0, 0); + VecD3D b = new VecD3D(-100, 100, 0); + TriangleD3D t = new TriangleD3D(a, b, c); + assertTrue(a.equalsWithTolerance(t.fromBarycentric(t.toBarycentric(a)), + 0.01f)); + assertTrue(b.equalsWithTolerance(t.fromBarycentric(t.toBarycentric(b)), + 0.01f)); + assertTrue(c.equalsWithTolerance(t.fromBarycentric(t.toBarycentric(c)), + 0.01f)); + } + + public void testCentroid() { + VecD3D a = new VecD3D(100, 0, 0); + VecD3D b = new VecD3D(0, 100, 0); + VecD3D c = new VecD3D(0, 0, 100); + TriangleD3D t = new TriangleD3D(a, b, c); + ReadonlyVecD3D centroid = t.computeCentroid(); + assertTrue("incorrect centroid", + centroid.equals(new VecD3D(100, 100, 100).scaleSelf(1f / 3))); + } + + public void testClockwise() { + VecD3D a = new VecD3D(0, 100, 0); + VecD3D b = new VecD3D(100, 0, -50); + VecD3D c = new VecD3D(-100, -100, 100); + assertTrue("not clockwiseXY", TriangleD3D.isClockwiseInXY(a, b, c)); + assertTrue("not clockwiseXZ", TriangleD3D.isClockwiseInXY(a, b, c)); + assertTrue("not clockwiseYZ", TriangleD3D.isClockwiseInXY(a, b, c)); + } + + public void testContainment() { + VecD3D a = new VecD3D(100, 0, 0); + VecD3D b = new VecD3D(0, 100, 0); + VecD3D c = new VecD3D(0, 0, 100); + TriangleD3D t = new TriangleD3D(a, b, c); + assertTrue(t.containsPoint(a)); + assertTrue(t.containsPoint(b)); + assertTrue(t.containsPoint(c)); + assertTrue(t.containsPoint(t.computeCentroid())); + assertFalse(t.containsPoint(a.add(0.1f, 0, 0))); + t.flipVertexOrder(); + assertTrue(t.containsPoint(t.computeCentroid())); + } + + public void testEquilateral() { + TriangleD3D t = TriangleD3D.createEquilateralFrom(new VecD3D(-100, 0, 0), + new VecD3D(100, 0, 0)); + + } + + public void testNormal() { + VecD3D a = new VecD3D(0, 100, 0); + VecD3D b = new VecD3D(100, 0, 0); + VecD3D c = new VecD3D(-100, -100, 0); + TriangleD3D t = new TriangleD3D(a, b, c); + ReadonlyVecD3D n = t.computeNormal(); + assertTrue("normal wrong", n.equals(new VecD3D(0, 0, 1))); + } +} diff --git a/src.test/toxi/test/geom/TriangleMeshDTest.java b/src.test/toxi/test/geom/TriangleMeshDTest.java new file mode 100644 index 00000000..0dd9f2b2 --- /dev/null +++ b/src.test/toxi/test/geom/TriangleMeshDTest.java @@ -0,0 +1,68 @@ +package toxi.test.geom; + +import java.util.ArrayList; + +import junit.framework.TestCase; +import toxi.geom.VecD3D; +import toxi.geom.mesh.FaceD; +import toxi.geom.mesh.STLReader; +import toxi.geom.mesh.TriangleMeshD; +import toxi.geom.mesh.VertexD; + +public class TriangleMeshDTest extends TestCase { + + TriangleMeshD mesh; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mesh = new TriangleMeshD("foo"); + mesh.addFaceD(new VecD3D(), new VecD3D(100, 100, 0), new VecD3D(100, 0, 0)); + mesh.addFaceD(new VecD3D(100, 100, 0), new VecD3D(100, 0, -100), + new VecD3D(100, 0, 0)); + mesh.addFaceD(new VecD3D(100f, 0, -100), new VecD3D(0, 100, -100), + new VecD3D(0, 0, -100)); + } + + public void testFaceDNormals() { + assertEquals(new VecD3D(0, 0, 1), mesh.faces.get(0).normal); + assertEquals(new VecD3D(1, 0, 0), mesh.faces.get(1).normal); + assertEquals(new VecD3D(0, 0, -1), mesh.faces.get(2).normal); + } + + public void testSTLImport() { + double total = 0; + int numIter = 100; + for (int i = 0; i < numIter; i++) { + long t = System.nanoTime(); + mesh = (TriangleMeshD) new STLReader().loadBinary("test/test.stl", + STLReader.TRIANGLEMESH); + total += (System.nanoTime() - t); + } + System.out.println("avg. mesh construction time: " + total * 1e-6 + / numIter); + assertNotNull(mesh); + assertEquals(714, mesh.getNumVertices()); + assertEquals(1424, mesh.getNumFaceDs()); + System.out.println(mesh); + } + + public void testUniqueVertices() { + ArrayList verts = new ArrayList(mesh.vertices.values()); + assertEquals(6, mesh.vertices.size()); + for (FaceD f : mesh.faces) { + assertEquals(verts.get(f.a.id), f.a); + assertEquals(verts.get(f.b.id), f.b); + assertEquals(verts.get(f.c.id), f.c); + } + } + + public void testVertexDNormals() { + mesh.computeVertexDNormals(); + VertexD[] verts = null; + for (FaceD f : mesh.faces) { + verts = f.getVertices(verts); + System.out.println(f); + } + } +} diff --git a/src.test/toxi/test/geom/VecD3DTest.java b/src.test/toxi/test/geom/VecD3DTest.java new file mode 100644 index 00000000..3911d5c1 --- /dev/null +++ b/src.test/toxi/test/geom/VecD3DTest.java @@ -0,0 +1,43 @@ +package toxi.test.geom; + +import java.util.List; + +import junit.framework.TestCase; +import toxi.geom.LineD3D; +import toxi.geom.VecD3D; + +public class VecD3DTest extends TestCase { + + public void testClosestAxis() { + assertEquals(VecD3D.AxisD.X, new VecD3D(-1, 0.9f, 0.8f).getClosestAxis()); + assertEquals(null, new VecD3D(1, -1, 0).getClosestAxis()); + assertEquals(null, new VecD3D(1, 0, -1).getClosestAxis()); + assertEquals(VecD3D.AxisD.Y, + new VecD3D(0.8f, -1, -0.99999f).getClosestAxis()); + assertEquals(null, new VecD3D(0.8f, -1, 1).getClosestAxis()); + assertEquals(VecD3D.AxisD.Z, new VecD3D(0.8f, -1, 1.1f).getClosestAxis()); + assertEquals(VecD3D.AxisD.X, new VecD3D(1, 0, 0).getClosestAxis()); + assertEquals(VecD3D.AxisD.Y, new VecD3D(0, -1, 0).getClosestAxis()); + assertEquals(VecD3D.AxisD.Z, new VecD3D(0, 0, 1).getClosestAxis()); + } + + public void testSphericalInstance() { + VecD3D v = new VecD3D(-1, 1, 1); + VecD3D w = v.copy(); + v.toSpherical(); + v.toCartesian(); + System.out.println(v); + assertTrue(v.equalsWithTolerance(w, 0.0001f)); + } + + public void testSplitSegments() { + VecD3D a = new VecD3D(0, 0, 0); + VecD3D b = new VecD3D(100, 0, 0); + List list = LineD3D.splitIntoSegments(a, b, 8, null, true); + assertEquals(14, list.size()); + // testing adding to existing list and skipping start point + LineD3D.splitIntoSegments(b, a, 10, list, false); + assertFalse(b.equals(list.get(14))); + assertEquals(24, list.size()); + } +} diff --git a/src.test/toxi/test/geom/WEMeshDTest.java b/src.test/toxi/test/geom/WEMeshDTest.java new file mode 100644 index 00000000..8fdff37d --- /dev/null +++ b/src.test/toxi/test/geom/WEMeshDTest.java @@ -0,0 +1,86 @@ +package toxi.test.geom; + +import junit.framework.TestCase; +import toxi.geom.TriangleD3D; +import toxi.geom.VecD3D; +import toxi.geom.mesh.FaceD; +import toxi.geom.mesh.WEFaceD; +import toxi.geom.mesh.WETriangleMeshD; +import toxi.geom.mesh.WEVertexD; +import toxi.geom.mesh.WingedEdgeD; +import toxi.geom.mesh.subdiv.MidpointSubdivisionD; + +public class WEMeshDTest extends TestCase { + + private WETriangleMeshD m; + + /* + * (non-Javadoc) + * + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + m = new WETriangleMeshD("plane", 4, 2); + m.addFaceD(new VecD3D(), new VecD3D(100, 0, 0), new VecD3D(100, 100, 0)); + m.addFaceD(new VecD3D(), new VecD3D(100, 100, 0), new VecD3D(0, 100, 0)); + super.setUp(); + } + + public void testAddFaceD() { + assertEquals(5, m.edges.size()); + System.out.println("mesh edges:"); + for (WingedEdgeD e : m.edges.values()) { + System.out.println(e); + } + WEVertexD v = (WEVertexD) m.vertices.get(new VecD3D()); + assertEquals(3, v.edges.size()); + assertEquals(1, v.edges.get(0).faces.size()); + assertEquals(2, v.edges.get(1).faces.size()); + System.out.println("vertex edges:"); + for (WingedEdgeD e : v.edges) { + System.out.println(e); + } + } + + public void testFaceDEdgeCount() { + for (FaceD f : m.faces) { + assertEquals(3, ((WEFaceD) f).edges.size()); + } + } + + public void testPerforate() { + m.removeFaceD(m.getFaceDs().get(0)); + WEFaceD f = (WEFaceD) m.getFaceDs().get(0); + m.perforateFaceD(f, 0.5f); + System.out.println(m.edges.size() + " edges"); + } + + public void testRemoveFaceD() { + assertEquals(5, m.edges.size()); + WEFaceD f = (WEFaceD) m.getFaceDs().get(0); + m.removeFaceD(f); + assertEquals(3, m.edges.size()); + assertEquals(3, m.vertices.size()); + } + + public void testSplitEdge() { + WingedEdgeD e = ((WEVertexD) m.vertices.get(new VecD3D())).edges.get(1); + m.splitEdge(e, new MidpointSubdivisionD()); + assertEquals(4, m.faces.size()); + assertEquals(8, m.edges.size()); + m.computeVertexDNormals(); + for (FaceD f : m.faces) { + System.out.println(TriangleD3D.isClockwiseInXY(f.a, f.b, f.c) + " " + + f); + } + assertEquals(3, ((WEVertexD) m.faces.get(0).a).edges.size()); + assertEquals(3, ((WEVertexD) m.faces.get(0).b).edges.size()); + assertEquals(4, ((WEVertexD) m.faces.get(0).c).edges.size()); + } + + public void testSubdivide() { + m.subdivide(); + assertEquals(8, m.faces.size()); + } +} \ No newline at end of file