From c6b8619810c40e17545fb6efec7ac60d1cfff8c1 Mon Sep 17 00:00:00 2001 From: SeH <1s1e1h1@gmail.com> Date: Sat, 13 Jun 2015 23:42:56 -0400 Subject: [PATCH] geometry lib stabilized --- .../com/syncleus/spangraph/geom/Axis.java | 21 - .../spangraph/spacetime/AbstractOctree.java | 467 +++ .../spangraph => toxi}/geom/AABB.java | 66 +- .../spangraph => toxi}/geom/Axis3D.java | 10 +- .../java/toxi/geom/AxisAlignedCylinder.java | 129 + .../java/toxi/geom/BernsteinPolynomial.java | 63 + src/main/java/toxi/geom/BezierCurve2D.java | 117 + src/main/java/toxi/geom/BezierCurve3D.java | 111 + .../java/toxi/geom/BooleanShapeBuilder.java | 135 + .../geom/BoxIntersector.java | 2 +- .../spangraph => toxi}/geom/Circle.java | 6 +- .../geom/CircleIntersector.java | 2 +- src/main/java/toxi/geom/Cone.java | 111 + .../java/toxi/geom/ConvexPolygonClipper.java | 108 + .../java/toxi/geom/CoordinateExtractor.java | 6 + .../spangraph => toxi}/geom/Ellipse.java | 19 +- src/main/java/toxi/geom/GMatrix.java | 2644 +++++++++++++ src/main/java/toxi/geom/GVector.java | 879 +++++ .../java/toxi/geom/GlobalGridTesselator.java | 43 + src/main/java/toxi/geom/GridTesselator.java | 93 + .../geom/Intersector2D.java | 2 +- .../geom/Intersector3D.java | 2 +- .../spangraph => toxi}/geom/IsectData2D.java | 2 +- .../spangraph => toxi}/geom/IsectData3D.java | 8 +- .../spangraph => toxi}/geom/Line2D.java | 12 +- .../spangraph => toxi}/geom/Line3D.java | 41 +- src/main/java/toxi/geom/LineStrip2D.java | 319 ++ src/main/java/toxi/geom/LineStrip3D.java | 182 + .../java/toxi/geom/LocalGridTesselator.java | 46 + src/main/java/toxi/geom/Matrix3d.java | 3063 +++++++++++++++ src/main/java/toxi/geom/Matrix4f.java | 3419 +++++++++++++++++ src/main/java/toxi/geom/Matrix4x4.java | 815 ++++ .../java/toxi/geom/MatrixSizeException.java | 58 + .../geom/OctreeVisitor.java | 2 +- .../spangraph => toxi}/geom/Origin3D.java | 20 +- .../spangraph => toxi}/geom/Plane.java | 76 +- src/main/java/toxi/geom/PlaneIntersector.java | 47 + .../spangraph => toxi}/geom/PointCloud3D.java | 42 +- .../spangraph => toxi}/geom/PointOctree.java | 69 +- .../geom/PointQuadtree.java | 2 +- .../spangraph => toxi}/geom/Polygon2D.java | 46 +- src/main/java/toxi/geom/PolygonClipper2D.java | 45 + .../java/toxi/geom/PolygonTesselator.java | 16 + .../geom/QuadtreeVisitor.java | 2 +- src/main/java/toxi/geom/Quaternion.java | 511 +++ .../spangraph => toxi}/geom/Ray2D.java | 2 +- .../spangraph => toxi}/geom/Ray3D.java | 10 +- .../geom/Ray3DIntersector.java | 2 +- .../geom/ReadonlyVec2D.java | 51 +- .../geom/ReadonlyVec4D.java | 6 +- .../spangraph => toxi}/geom/Rect.java | 10 +- src/main/java/toxi/geom/Reflector3D.java | 58 + .../spangraph => toxi}/geom/Shape2D.java | 2 +- .../spangraph => toxi}/geom/Shape3D.java | 4 +- .../toxi/geom/SingularMatrixException.java | 57 + src/main/java/toxi/geom/SpatialBins.java | 123 + .../spangraph => toxi}/geom/SpatialIndex.java | 2 +- .../spangraph => toxi}/geom/Sphere.java | 33 +- .../toxi/geom/SphereIntersectorReflector.java | 158 + .../spangraph => toxi}/geom/Spline2D.java | 2 +- .../spangraph => toxi}/geom/Spline3D.java | 61 +- .../toxi/geom/SutherlandHodgemanClipper.java | 142 + .../spangraph => toxi}/geom/Triangle2D.java | 24 +- .../spangraph => toxi}/geom/Triangle3D.java | 32 +- .../geom/TriangleIntersector.java | 2 +- .../spangraph => toxi}/geom/Vec2D.java | 84 +- .../spangraph => toxi}/geom/Vec3D.java | 325 +- .../spangraph => toxi}/geom/Vec4D.java | 20 +- .../spangraph => toxi}/geom/VecMathUtil.java | 2 +- src/main/java/toxi/geom/XAxisCylinder.java | 52 + src/main/java/toxi/geom/XYZ.java | 89 + src/main/java/toxi/geom/YAxisCylinder.java | 53 + src/main/java/toxi/geom/ZAxisCylinder.java | 52 + .../toxi/geom/mesh2d/DelaunayTriangle.java | 192 + .../geom/mesh2d/DelaunayTriangulation.java | 321 ++ .../java/toxi/geom/mesh2d/DelaunayVertex.java | 546 +++ src/main/java/toxi/geom/mesh2d/Voronoi.java | 106 + .../java/toxi/geom/nurbs/BasicNurbsCurve.java | 179 + .../toxi/geom/nurbs/BasicNurbsSurface.java | 211 + src/main/java/toxi/geom/nurbs/ControlNet.java | 140 + .../java/toxi/geom/nurbs/CurveCreator.java | 112 + src/main/java/toxi/geom/nurbs/CurveUtils.java | 240 ++ .../geom/nurbs/InterpolationException.java | 49 + src/main/java/toxi/geom/nurbs/KnotVector.java | 321 ++ .../java/toxi/geom/nurbs/NurbsCreator.java | 815 ++++ src/main/java/toxi/geom/nurbs/NurbsCurve.java | 98 + .../toxi/geom/nurbs/NurbsMeshCreator.java | 103 + .../java/toxi/geom/nurbs/NurbsSurface.java | 133 + .../geom/roVec3D.java} | 148 +- .../java/toxi/math/BezierInterpolation.java | 83 + .../java/toxi/math/CircularInterpolation.java | 76 + .../java/toxi/math/CosineInterpolation.java | 50 + .../toxi/math/DecimatedInterpolation.java | 60 + .../toxi/math/ExponentialInterpolation.java | 66 + .../java/toxi/math/InterpolateStrategy.java | 60 + src/main/java/toxi/math/Interpolation2D.java | 93 + .../java/toxi/math/LinearInterpolation.java | 44 + .../geom => toxi/math}/MathUtils.java | 2 +- .../java/toxi/math/NonLinearScaleMap.java | 60 + src/main/java/toxi/math/ScaleMap.java | 167 + .../java/toxi/math/SigmoidInterpolation.java | 68 + src/main/java/toxi/math/SinCosLUT.java | 118 + .../toxi/math/ThresholdInterpolation.java | 49 + .../java/toxi/math/ZoomLensInterpolation.java | 91 + .../toxi/math/conversion/UnitTranslator.java | 157 + .../java/toxi/math/noise/PerlinNoise.java | 216 ++ .../java/toxi/math/noise/SimplexNoise.java | 542 +++ .../java/toxi/math/waves/AMFMSineWave.java | 123 + .../java/toxi/math/waves/AbstractWave.java | 202 + .../java/toxi/math/waves/ConstantWave.java | 43 + .../toxi/math/waves/FMHarmonicSquareWave.java | 125 + .../java/toxi/math/waves/FMSawtoothWave.java | 114 + src/main/java/toxi/math/waves/FMSineWave.java | 105 + .../java/toxi/math/waves/FMSquareWave.java | 113 + .../java/toxi/math/waves/FMTriangleWave.java | 92 + src/main/java/toxi/math/waves/SineWave.java | 77 + src/main/java/toxi/math/waves/Wave2D.java | 47 + src/main/java/toxi/math/waves/WaveState.java | 43 + src/main/java/toxi/util/DateUtils.java | 133 + .../toxi/util/FileSequenceDescriptor.java | 157 + src/main/java/toxi/util/FileUtils.java | 480 +++ .../java/toxi/util/datatypes/ArraySet.java | 121 + .../java/toxi/util/datatypes/ArrayUtil.java | 377 ++ .../util/datatypes/BiasedDoubleRange.java | 115 + .../toxi/util/datatypes/BiasedFloatRange.java | 115 + .../util/datatypes/BiasedIntegerRange.java | 115 + .../java/toxi/util/datatypes/DoubleRange.java | 160 + .../java/toxi/util/datatypes/FloatRange.java | 160 + .../java/toxi/util/datatypes/GenericSet.java | 133 + .../toxi/util/datatypes/IntegerRange.java | 157 + .../java/toxi/util/datatypes/IntegerSet.java | 100 + .../java/toxi/util/datatypes/ItemIndex.java | 25 + .../util/datatypes/SingletonRegistry.java | 87 + .../toxi/util/datatypes/TypedProperties.java | 269 ++ .../toxi/util/datatypes/UndirectedGraph.java | 126 + .../toxi/util/datatypes/UniqueItemIndex.java | 140 + .../util/datatypes/WeightedRandomEntry.java | 53 + .../util/datatypes/WeightedRandomSet.java | 118 + .../toxi/util/events/EventDispatcher.java | 58 + 139 files changed, 24646 insertions(+), 690 deletions(-) delete mode 100644 src/main/java/com/syncleus/spangraph/geom/Axis.java create mode 100644 src/main/java/com/syncleus/spangraph/spacetime/AbstractOctree.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/AABB.java (92%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Axis3D.java (92%) create mode 100644 src/main/java/toxi/geom/AxisAlignedCylinder.java create mode 100644 src/main/java/toxi/geom/BernsteinPolynomial.java create mode 100644 src/main/java/toxi/geom/BezierCurve2D.java create mode 100644 src/main/java/toxi/geom/BezierCurve3D.java create mode 100644 src/main/java/toxi/geom/BooleanShapeBuilder.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/BoxIntersector.java (96%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Circle.java (99%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/CircleIntersector.java (98%) create mode 100644 src/main/java/toxi/geom/Cone.java create mode 100644 src/main/java/toxi/geom/ConvexPolygonClipper.java create mode 100644 src/main/java/toxi/geom/CoordinateExtractor.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Ellipse.java (92%) create mode 100644 src/main/java/toxi/geom/GMatrix.java create mode 100644 src/main/java/toxi/geom/GVector.java create mode 100644 src/main/java/toxi/geom/GlobalGridTesselator.java create mode 100644 src/main/java/toxi/geom/GridTesselator.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Intersector2D.java (97%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Intersector3D.java (97%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/IsectData2D.java (98%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/IsectData3D.java (93%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Line2D.java (95%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Line3D.java (91%) create mode 100644 src/main/java/toxi/geom/LineStrip2D.java create mode 100644 src/main/java/toxi/geom/LineStrip3D.java create mode 100644 src/main/java/toxi/geom/LocalGridTesselator.java create mode 100644 src/main/java/toxi/geom/Matrix3d.java create mode 100644 src/main/java/toxi/geom/Matrix4f.java create mode 100644 src/main/java/toxi/geom/Matrix4x4.java create mode 100644 src/main/java/toxi/geom/MatrixSizeException.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/OctreeVisitor.java (97%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Origin3D.java (89%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Plane.java (79%) create mode 100644 src/main/java/toxi/geom/PlaneIntersector.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/PointCloud3D.java (87%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/PointOctree.java (87%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/PointQuadtree.java (99%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Polygon2D.java (95%) create mode 100644 src/main/java/toxi/geom/PolygonClipper2D.java create mode 100644 src/main/java/toxi/geom/PolygonTesselator.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/QuadtreeVisitor.java (97%) create mode 100644 src/main/java/toxi/geom/Quaternion.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Ray2D.java (98%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Ray3D.java (92%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Ray3DIntersector.java (98%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/ReadonlyVec2D.java (94%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/ReadonlyVec4D.java (98%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Rect.java (99%) create mode 100644 src/main/java/toxi/geom/Reflector3D.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Shape2D.java (98%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Shape3D.java (94%) create mode 100644 src/main/java/toxi/geom/SingularMatrixException.java create mode 100644 src/main/java/toxi/geom/SpatialBins.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/SpatialIndex.java (89%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Sphere.java (90%) create mode 100644 src/main/java/toxi/geom/SphereIntersectorReflector.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Spline2D.java (99%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Spline3D.java (82%) create mode 100644 src/main/java/toxi/geom/SutherlandHodgemanClipper.java rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Triangle2D.java (93%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Triangle3D.java (93%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/TriangleIntersector.java (98%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Vec2D.java (94%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Vec3D.java (81%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/Vec4D.java (97%) rename src/main/java/{com/syncleus/spangraph => toxi}/geom/VecMathUtil.java (99%) create mode 100644 src/main/java/toxi/geom/XAxisCylinder.java create mode 100644 src/main/java/toxi/geom/XYZ.java create mode 100644 src/main/java/toxi/geom/YAxisCylinder.java create mode 100644 src/main/java/toxi/geom/ZAxisCylinder.java create mode 100644 src/main/java/toxi/geom/mesh2d/DelaunayTriangle.java create mode 100644 src/main/java/toxi/geom/mesh2d/DelaunayTriangulation.java create mode 100644 src/main/java/toxi/geom/mesh2d/DelaunayVertex.java create mode 100644 src/main/java/toxi/geom/mesh2d/Voronoi.java create mode 100644 src/main/java/toxi/geom/nurbs/BasicNurbsCurve.java create mode 100644 src/main/java/toxi/geom/nurbs/BasicNurbsSurface.java create mode 100644 src/main/java/toxi/geom/nurbs/ControlNet.java create mode 100644 src/main/java/toxi/geom/nurbs/CurveCreator.java create mode 100644 src/main/java/toxi/geom/nurbs/CurveUtils.java create mode 100644 src/main/java/toxi/geom/nurbs/InterpolationException.java create mode 100644 src/main/java/toxi/geom/nurbs/KnotVector.java create mode 100644 src/main/java/toxi/geom/nurbs/NurbsCreator.java create mode 100644 src/main/java/toxi/geom/nurbs/NurbsCurve.java create mode 100644 src/main/java/toxi/geom/nurbs/NurbsMeshCreator.java create mode 100644 src/main/java/toxi/geom/nurbs/NurbsSurface.java rename src/main/java/{com/syncleus/spangraph/geom/ReadonlyVec3D.java => toxi/geom/roVec3D.java} (76%) create mode 100644 src/main/java/toxi/math/BezierInterpolation.java create mode 100644 src/main/java/toxi/math/CircularInterpolation.java create mode 100644 src/main/java/toxi/math/CosineInterpolation.java create mode 100644 src/main/java/toxi/math/DecimatedInterpolation.java create mode 100644 src/main/java/toxi/math/ExponentialInterpolation.java create mode 100644 src/main/java/toxi/math/InterpolateStrategy.java create mode 100644 src/main/java/toxi/math/Interpolation2D.java create mode 100644 src/main/java/toxi/math/LinearInterpolation.java rename src/main/java/{com/syncleus/spangraph/geom => toxi/math}/MathUtils.java (99%) create mode 100644 src/main/java/toxi/math/NonLinearScaleMap.java create mode 100644 src/main/java/toxi/math/ScaleMap.java create mode 100644 src/main/java/toxi/math/SigmoidInterpolation.java create mode 100644 src/main/java/toxi/math/SinCosLUT.java create mode 100644 src/main/java/toxi/math/ThresholdInterpolation.java create mode 100644 src/main/java/toxi/math/ZoomLensInterpolation.java create mode 100644 src/main/java/toxi/math/conversion/UnitTranslator.java create mode 100644 src/main/java/toxi/math/noise/PerlinNoise.java create mode 100644 src/main/java/toxi/math/noise/SimplexNoise.java create mode 100644 src/main/java/toxi/math/waves/AMFMSineWave.java create mode 100644 src/main/java/toxi/math/waves/AbstractWave.java create mode 100644 src/main/java/toxi/math/waves/ConstantWave.java create mode 100644 src/main/java/toxi/math/waves/FMHarmonicSquareWave.java create mode 100644 src/main/java/toxi/math/waves/FMSawtoothWave.java create mode 100644 src/main/java/toxi/math/waves/FMSineWave.java create mode 100644 src/main/java/toxi/math/waves/FMSquareWave.java create mode 100644 src/main/java/toxi/math/waves/FMTriangleWave.java create mode 100644 src/main/java/toxi/math/waves/SineWave.java create mode 100644 src/main/java/toxi/math/waves/Wave2D.java create mode 100644 src/main/java/toxi/math/waves/WaveState.java create mode 100644 src/main/java/toxi/util/DateUtils.java create mode 100644 src/main/java/toxi/util/FileSequenceDescriptor.java create mode 100644 src/main/java/toxi/util/FileUtils.java create mode 100644 src/main/java/toxi/util/datatypes/ArraySet.java create mode 100644 src/main/java/toxi/util/datatypes/ArrayUtil.java create mode 100644 src/main/java/toxi/util/datatypes/BiasedDoubleRange.java create mode 100644 src/main/java/toxi/util/datatypes/BiasedFloatRange.java create mode 100644 src/main/java/toxi/util/datatypes/BiasedIntegerRange.java create mode 100644 src/main/java/toxi/util/datatypes/DoubleRange.java create mode 100644 src/main/java/toxi/util/datatypes/FloatRange.java create mode 100644 src/main/java/toxi/util/datatypes/GenericSet.java create mode 100644 src/main/java/toxi/util/datatypes/IntegerRange.java create mode 100644 src/main/java/toxi/util/datatypes/IntegerSet.java create mode 100644 src/main/java/toxi/util/datatypes/ItemIndex.java create mode 100644 src/main/java/toxi/util/datatypes/SingletonRegistry.java create mode 100644 src/main/java/toxi/util/datatypes/TypedProperties.java create mode 100644 src/main/java/toxi/util/datatypes/UndirectedGraph.java create mode 100644 src/main/java/toxi/util/datatypes/UniqueItemIndex.java create mode 100644 src/main/java/toxi/util/datatypes/WeightedRandomEntry.java create mode 100644 src/main/java/toxi/util/datatypes/WeightedRandomSet.java create mode 100644 src/main/java/toxi/util/events/EventDispatcher.java diff --git a/src/main/java/com/syncleus/spangraph/geom/Axis.java b/src/main/java/com/syncleus/spangraph/geom/Axis.java deleted file mode 100644 index 0418bd9..0000000 --- a/src/main/java/com/syncleus/spangraph/geom/Axis.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.syncleus.spangraph.geom; - -/** - * Created by me on 6/13/15. - */ -public enum Axis { - - X(Vec3D.X_AXIS), - Y(Vec3D.Y_AXIS), - Z(Vec3D.Z_AXIS); - - private final ReadonlyVec3D vector; - - private Axis(ReadonlyVec3D v) { - this.vector = v; - } - - public ReadonlyVec3D getVector() { - return vector; - } -} diff --git a/src/main/java/com/syncleus/spangraph/spacetime/AbstractOctree.java b/src/main/java/com/syncleus/spangraph/spacetime/AbstractOctree.java new file mode 100644 index 0000000..7650bc3 --- /dev/null +++ b/src/main/java/com/syncleus/spangraph/spacetime/AbstractOctree.java @@ -0,0 +1,467 @@ +package com.syncleus.spangraph.spacetime; + +import toxi.geom.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + + +public class AbstractOctree<L> extends AABB implements Shape3D { + + /** + * alternative tree recursion limit, number of world units when cells are + * not subdivided any further + */ + protected float minNodeSize = 4; + + /** + * + */ + public final AbstractOctree parent; + + protected AbstractOctree[] children; + + protected byte numChildren; + + protected Collection<XYZ> points; + + protected float size, halfSize; + + protected Vec3D center; + + private int depth = 0; + + private boolean isAutoReducing = true; + + /** + * Constructs a new AbstractOctree node within the AABB 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 AbstractOctree(AbstractOctree p, Vec3D o, float halfSize) { + super(o.add(halfSize, halfSize, halfSize), new Vec3D(halfSize, + halfSize, halfSize)); + this.parent = p; + this.halfSize = halfSize; + this.size = halfSize * 2; + this.center = o; + this.numChildren = 0; + if (parent != null) { + depth = parent.depth + 1; + minNodeSize = parent.minNodeSize; + } + } + + /** + * Constructs a new AbstractOctree node within the AABB 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 AbstractOctree(Vec3D o, float size) { + this(null, o, size / 2); + } + + /** + * Adds all points of the collection to the octree. IMPORTANT: Points need + * be of type Vec3D or have subclassed it. + * + * @param points + * point collection + * @return true, if all points have been added successfully. + */ + public boolean addAll(Collection<XYZ> points) { + boolean addedAll = true; + for (XYZ 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(final XYZ p) { + final float halfSize = this.halfSize; + + AbstractOctree[] children = this.children; + + // check if point is inside cube + if (containsPoint(p)) { + // only add points to leaves for now + if (halfSize <= minNodeSize) { + if (points == null) { + points = newPointsCollection(); + } + points.add(p); + return true; + } else { + if (children == null) { + children = new AbstractOctree[8]; + } + int octant = getOctantID(p, center); + if (children[octant] == null) { + Vec3D off = center.add(new Vec3D( + (octant & 1) != 0 ? halfSize : 0, + (octant & 2) != 0 ? halfSize : 0, + (octant & 4) != 0 ? halfSize : 0)); + children[octant] = new AbstractOctree(this, off, + halfSize * 0.5f); + numChildren++; + } + return children[octant].addPoint(p); + } + } + return false; + } + + protected Collection<XYZ> newPointsCollection() { + return new ArrayList(); + } + + + /** + * Applies the given {@link OctreeVisitor} implementation to this node and + * all of its children. + */ + public void forEach(Consumer<AbstractOctree> visitor) { + visitor.accept(this); + if (numChildren > 0) { + for (AbstractOctree c : children) { + if (c != null) { + c.forEach(visitor); + } + } + } + } + + public boolean containsPoint(XYZ p) { + return p.isInAABB(this); + } + + public AbstractOctree clear() { + numChildren = 0; + children = null; + points = null; + return this; + } + + /** + * @return a copy of the child nodes array + */ + public AbstractOctree[] getChildrenCopy() { + if (children != null) { + AbstractOctree[] clones = new AbstractOctree[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 + * + * @return leaf node or null if point is outside the tree dimensions + */ + public AbstractOctree getLeafForPoint(XYZ p) { + // if not a leaf node... + if (p.isInAABB(this)) { + if (numChildren > 0) { + int octant = getOctantID(p, center); + 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 float getMinNodeSize() { + return minNodeSize; + } + + public float 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(final Vec3D plocal) { + float halfSize = this.halfSize; + + return (plocal.x >= halfSize ? 1 : 0) + (plocal.y >= halfSize ? 2 : 0) + + (plocal.z >= halfSize ? 4 : 0); + } + + /** computes getOctantID for the point subtracted by another point, + * without needing to allocate a temporary object + + */ + private int getOctantID(final XYZ p, final Vec3D s) { + return ((p.x() - s.x) >= halfSize ? 1 : 0) + ((p.y() - s.y) >= halfSize ? 2 : 0) + + ((p.z() - s.z) >= halfSize ? 4 : 0); + } + + /** + * @return the offset + */ + public roVec3D getCenter() { + return center; + } + + /** + * @return the parent + */ + public AbstractOctree getParent() { + return parent; + } + + public List<XYZ> getPoints() { + return getPoints(new ArrayList()); + } + + /** + * @return the points + */ + public List<XYZ> getPoints(List<XYZ> results) { + if (points != null) { + } else if (numChildren > 0) { + for (int i = 0; i < 8; i++) { + if (children[i] != null) { + List<XYZ> childPoints = children[i].getPoints(); + if (childPoints != null) { + results.addAll(childPoints); + } + } + } + } + return results; + } + + /** + * Selects all stored points within the given axis-aligned bounding box. + * + * @param b + * AABB + * @return all points with the box volume + */ + public List<XYZ> getPointsWithinBox(AABB b) { + ArrayList<XYZ> results = null; + if (this.intersectsBox(b)) { + if (points != null) { + for (XYZ q : points) { + if (q.isInAABB(b)) { + if (results == null) { + results = new ArrayList<XYZ>(); + } + results.add(q); + } + } + } else if (numChildren > 0) { + for (int i = 0; i < 8; i++) { + if (children[i] != null) { + List<XYZ> 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 + */ + @Deprecated public List<XYZ> getPointsWithinSphere(Sphere s) { + ArrayList<XYZ> results = null; + if (this.intersectsSphere(s)) { + if (points != null) { + for (XYZ 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<XYZ> points = children[i].getPointsWithinSphere(s); + if (points != null) { + if (results == null) { + results = new ArrayList(); + } + results.addAll(points); + } + } + } + } + } + return results; + } + + public void forEachInSphere(Sphere s, Consumer<XYZ> c) { + + if (this.intersectsSphere(s)) { + if (points != null) { + for (XYZ q : points) { + if (s.containsPoint(q)) { + c.accept(q); + } + } + } else if (numChildren > 0) { + for (int i = 0; i < 8; i++) { + AbstractOctree cc = children[i]; + if (cc != null) { + cc.forEachInSphere(s, c); + } + } + } + } + } + + /** + * Selects all stored points within the given sphere volume + * + * @param sphereOrigin + * @param clipRadius + * @return selected points + */ + public void forEachInSphere(Vec3D sphereOrigin, float clipRadius, Consumer<XYZ> c) { + forEachInSphere(new Sphere(sphereOrigin, clipRadius), c); + } + + /** + * @return the size + */ + public float 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(XYZ p) { + boolean found = false; + AbstractOctree 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<XYZ> points) { + for (XYZ p : points) { + remove(p); + } + } + + /** + * @param minNodeSize + */ + public void setMinNodeSize(float 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.AABB#toString() + */ + public String toString() { + return "<octree> offset: " + super.toString() + " size: " + size; + } +} diff --git a/src/main/java/com/syncleus/spangraph/geom/AABB.java b/src/main/java/toxi/geom/AABB.java similarity index 92% rename from src/main/java/com/syncleus/spangraph/geom/AABB.java rename to src/main/java/toxi/geom/AABB.java index 88ea269..d0a0017 100644 --- a/src/main/java/com/syncleus/spangraph/geom/AABB.java +++ b/src/main/java/toxi/geom/AABB.java @@ -25,7 +25,9 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; + +import toxi.math.MathUtils; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -33,7 +35,6 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; import java.util.List; - /** * Axis-aligned bounding box with basic intersection features for Ray, AABB and * Sphere classes. @@ -65,16 +66,20 @@ public class AABB extends Vec3D implements Shape3D { * @param points * @return bounding rect */ - public static final AABB getBoundingBox(List<? extends Vec3D> points) { + public static final AABB getBoundingBox(List<? extends XYZ> points) { if (points == null || points.size() == 0) { return null; } - Vec3D first = points.get(0); - Vec3D min = first.copy(); - Vec3D max = first.copy(); - for (Vec3D p : points) { - min.minSelf(p); - max.maxSelf(p); + XYZ first = points.get(0); + Vec3D min = new Vec3D(first); + Vec3D max = new Vec3D(first); + int n = points.size(); + if (n > 1) { + for (int i = 1; i < n; i++) { + XYZ p = points.get(i); + min.minSelf(p); + max.maxSelf(p); + } } return fromMinMax(min, max); } @@ -115,7 +120,7 @@ public class AABB extends Vec3D implements Shape3D { * @param pos * @param extent */ - public AABB(ReadonlyVec3D pos, float extent) { + public AABB(roVec3D pos, float extent) { super(pos); setExtent(new Vec3D(extent, extent, extent)); } @@ -128,12 +133,12 @@ public class AABB extends Vec3D implements Shape3D { * box dimensions (the box will be double the size in each * direction) */ - public AABB(ReadonlyVec3D pos, ReadonlyVec3D extent) { + public AABB(roVec3D pos, roVec3D extent) { super(pos); setExtent(extent); } - public boolean containsPoint(ReadonlyVec3D p) { + public boolean containsPoint(XYZ p) { return p.isInAABB(this); } @@ -147,7 +152,7 @@ public class AABB extends Vec3D implements Shape3D { /** * Returns the current box size as new Vec3D instance (updating this vector - * will NOT update the box size! Use {@link #setExtent(ReadonlyVec3D)} for + * will NOT update the box size! Use {@link #setExtent(roVec3D)} for * those purposes) * * @return box size @@ -165,11 +170,11 @@ public class AABB extends Vec3D implements Shape3D { return min.copy(); } - public Vec3D getNormalForPoint(ReadonlyVec3D p) { + public XYZ getNormalForPoint(roVec3D p) { p = p.sub(this); Vec3D pabs = extent.sub(p.getAbs()); Vec3D psign = p.getSignum(); - Vec3D normal = Vec3D.X_AXIS.scale(psign.x); + XYZ normal = Vec3D.X_AXIS.scale(psign.x); float minDist = pabs.x; if (pabs.y < minDist) { minDist = pabs.y; @@ -188,7 +193,7 @@ public class AABB extends Vec3D implements Shape3D { * point to include * @return itself */ - public AABB growToContainPoint(ReadonlyVec3D p) { + public AABB growToContainPoint(roVec3D p) { min.minSelf(p); max.maxSelf(p); set(min.interpolateTo(max, 0.5f)); @@ -448,7 +453,7 @@ public class AABB extends Vec3D implements Shape3D { public AABB set(AABB box) { extent.set(box.extent); - return set((ReadonlyVec3D) box); + return set((XYZ) box); } /** @@ -469,9 +474,8 @@ public class AABB extends Vec3D implements Shape3D { * Updates the position of the box in space and calls * {@link #updateBounds()} immediately * - * @see toxi.geom.Vec3D#set(toxi.geom.Vec3D) */ - public AABB set(ReadonlyVec3D v) { + public AABB set(XYZ v) { x = v.x(); y = v.y(); z = v.z(); @@ -486,7 +490,7 @@ public class AABB extends Vec3D implements Shape3D { * new box size * @return itself, for method chaining */ - public AABB setExtent(ReadonlyVec3D extent) { + public AABB setExtent(roVec3D extent) { this.extent = extent.copy(); return updateBounds(); } @@ -582,4 +586,26 @@ public class AABB extends Vec3D implements Shape3D { } return this; } + + public boolean contains(final XYZ v) { + final Vec3D min = this.min; + final Vec3D max = this.max; + + final float x = v.x(); + if (x < min.x || x > max.x) { + return false; + } + + final float y = v.y(); + if (y < min.y || y > max.y) { + return false; + } + + final float z = v.z(); + if (z < min.z || z > max.z) { + return false; + } + + return true; + } } \ No newline at end of file diff --git a/src/main/java/com/syncleus/spangraph/geom/Axis3D.java b/src/main/java/toxi/geom/Axis3D.java similarity index 92% rename from src/main/java/com/syncleus/spangraph/geom/Axis3D.java rename to src/main/java/toxi/geom/Axis3D.java index 00fe4b4..aa899ab 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Axis3D.java +++ b/src/main/java/toxi/geom/Axis3D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * An immutable origin + axis in 3D-Space. @@ -53,8 +53,8 @@ public class Axis3D { return new Axis3D(Vec3D.Z_AXIS); } - public final ReadonlyVec3D origin; - public final ReadonlyVec3D dir; + public final roVec3D origin; + public final roVec3D dir; /** * Creates a new z-Axis3D object from the world origin. @@ -77,7 +77,7 @@ public class Axis3D { * @param dir * direction vector */ - public Axis3D(ReadonlyVec3D dir) { + public Axis3D(roVec3D dir) { this(new Vec3D(), dir); } @@ -89,7 +89,7 @@ public class Axis3D { * @param dir * direction */ - public Axis3D(ReadonlyVec3D o, ReadonlyVec3D dir) { + public Axis3D(roVec3D o, roVec3D dir) { this.origin = o; this.dir = dir.getNormalized(); } diff --git a/src/main/java/toxi/geom/AxisAlignedCylinder.java b/src/main/java/toxi/geom/AxisAlignedCylinder.java new file mode 100644 index 0000000..3b82ca2 --- /dev/null +++ b/src/main/java/toxi/geom/AxisAlignedCylinder.java @@ -0,0 +1,129 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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 abstract class AxisAlignedCylinder implements Shape3D { + + protected Vec3D pos; + protected float radius; + protected float radiusSquared; + protected float length; + + protected AxisAlignedCylinder(roVec3D pos, float radius, float 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(XYZ p); + + /** + * @return the length + */ + public float getLength() { + return length; + } + + /** + * @return the cylinder's orientation axis + */ + public abstract Vec3D.Axis getMajorAxis(); + + /** + * Returns the cylinder's position (centroid). + * + * @return the pos + */ + public XYZ getPosition() { + return pos.copy(); + } + + /** + * @return the cylinder radius + */ + public float getRadius() { + return radius; + } + + /** + * @param length + * the length to set + */ + public void setLength(float length) { + this.length = length; + } + + /** + * @param pos + * the pos to set + */ + public void setPosition(Vec3D pos) { + this.pos.set(pos); + } + + /** + * @param radius + */ + public void setRadius(float 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 Mesh3D toMesh() { +// return toMesh(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 Mesh3D toMesh(int steps, float thetaOffset) { +// return toMesh(null, steps, thetaOffset); +// } +// +// public Mesh3D toMesh(Mesh3D mesh, int steps, float thetaOffset) { +// return new Cone(pos, getMajorAxis().getVector(), radius, radius, length) +// .toMesh(mesh, steps, thetaOffset, true, true); +// } +} \ No newline at end of file diff --git a/src/main/java/toxi/geom/BernsteinPolynomial.java b/src/main/java/toxi/geom/BernsteinPolynomial.java new file mode 100644 index 0000000..33ff8e7 --- /dev/null +++ b/src/main/java/toxi/geom/BernsteinPolynomial.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 BernsteinPolynomial { + + public float[] b0, b1, b2, b3; + public int resolution; + + /** + * @param res + * number of subdivision steps between each control point of the + * spline3d + */ + public BernsteinPolynomial(int res) { + resolution = res; + b0 = new float[res]; + b1 = new float[res]; + b2 = new float[res]; + b3 = new float[res]; + float t = 0; + float dt = 1.0f / (resolution - 1); + for (int i = 0; i < resolution; i++) { + float t1 = 1 - t; + float t12 = t1 * t1; + float 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/main/java/toxi/geom/BezierCurve2D.java b/src/main/java/toxi/geom/BezierCurve2D.java new file mode 100644 index 0000000..23690ee --- /dev/null +++ b/src/main/java/toxi/geom/BezierCurve2D.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 Polygon2D} instances. Also provides curve + * tangent calculation feature. Usage of this class is very similar to + * {@link Spline2D}. + */ +public class BezierCurve2D { + + public static Vec2D computePointInSegment(Vec2D a, Vec2D b, Vec2D c, + Vec2D d, float t) { + float it = 1.0f - t; + float it2 = it * it; + float 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 Vec2D computeTangentInSegment(Vec2D a, Vec2D b, Vec2D c, + Vec2D d, float t) { + float t2 = t * t; + float 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)); + float 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 Vec2D(x, y).normalize(); + } + + private List<Vec2D> points; + + public BezierCurve2D() { + points = new ArrayList<Vec2D>(); + } + + public BezierCurve2D(List<Vec2D> points) { + this.points = points; + } + + public BezierCurve2D add(Vec2D 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) { + Vec2D c; + if (id == 0 && isClosed()) { + c = points.get(points.size() - 2); + } else { + c = points.get(id - 1); + } + Vec2D d = points.get(id); + Vec2D e = points.get(id + 1); + Vec2D cd = d.sub(c); + Vec2D de = e.sub(d); + Vec2D 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 Vec2Ds + */ + public LineStrip2D toLineStrip2D(int res) { + LineStrip2D strip = new LineStrip2D(); + int i = 0; + int maxRes = res; + for (int num = points.size(); i < num - 3; i += 3) { + Vec2D a = points.get(i); + Vec2D b = points.get(i + 1); + Vec2D c = points.get(i + 2); + Vec2D 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, (float) t / res)); + } + } + return strip; + } + + public Polygon2D toPolygon2D(int res) { + Polygon2D poly = new Polygon2D(toLineStrip2D(res).getVertices()); + if (isClosed()) { + poly.vertices.remove(poly.vertices.get(poly.vertices.size() - 1)); + } + return poly; + } +} diff --git a/src/main/java/toxi/geom/BezierCurve3D.java b/src/main/java/toxi/geom/BezierCurve3D.java new file mode 100644 index 0000000..409ab64 --- /dev/null +++ b/src/main/java/toxi/geom/BezierCurve3D.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 BezierCurve3D { + + public static XYZ computePointInSegment(Vec3D a, Vec3D b, Vec3D c, + Vec3D d, float t) { + float it = 1.0f - t; + float it2 = it * it; + float 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 XYZ computeTangentInSegment(Vec3D a, Vec3D b, Vec3D c, + Vec3D d, float t) { + float t2 = t * t; + float 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)); + float 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)); + float 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 Vec3D(x, y, z).normalize(); + } + + private List<Vec3D> points; + + public BezierCurve3D() { + points = new ArrayList(); + } + + public BezierCurve3D(List<Vec3D> points) { + this.points = points; + } + + public BezierCurve3D add(Vec3D 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) { + Vec3D c; + if (id == 0 && isClosed()) { + c = points.get(points.size() - 2); + } else { + c = points.get(id - 1); + } + Vec3D d = points.get(id); + Vec3D e = points.get(id + 1); + Vec3D cd = d.sub(c); + Vec3D de = e.sub(d); + Vec3D 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 Vec3Ds + */ + public LineStrip3D toLineStrip3D(int res) { + LineStrip3D strip = new LineStrip3D(); + int i = 0; + int maxRes = res; + for (int num = points.size(); i < num - 3; i += 3) { + Vec3D a = points.get(i); + Vec3D b = points.get(i + 1); + Vec3D c = points.get(i + 2); + Vec3D 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, (float) t / res)); + } + } + return strip; + } + +} diff --git a/src/main/java/toxi/geom/BooleanShapeBuilder.java b/src/main/java/toxi/geom/BooleanShapeBuilder.java new file mode 100644 index 0000000..fdb66cb --- /dev/null +++ b/src/main/java/toxi/geom/BooleanShapeBuilder.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 BooleanShapeBuilder { + + public enum Type { + UNION, + INTERSECTION, + DIFFERENCE, + XOR; + } + + private int bezierRes; + + private final Area area; + private final Type type; + + public BooleanShapeBuilder(Type type) { + this(type, 8); + } + + public BooleanShapeBuilder(Type type, int bezierRes) { + this.type = type; + this.bezierRes = bezierRes; + area = new Area(); + } + + public BooleanShapeBuilder addShape(Shape2D s) { + return combineWithArea(new Area(convertToAWTShape(s))); + } + + public BooleanShapeBuilder 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<Polygon2D> computeShapes() { + List<Polygon2D> shapes = new ArrayList<Polygon2D>(); + PathIterator i = area.getPathIterator(null); + float[] buf = new float[6]; + Vec2D prev = new Vec2D(); + Polygon2D s = null; + while (!i.isDone()) { + int id = i.currentSegment(buf); + switch (id) { + case PathIterator.SEG_MOVETO: + s = new Polygon2D(); + 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: + Vec2D pa = new Vec2D(buf[0], buf[1]); + Vec2D pb = new Vec2D(buf[2], buf[3]); + Vec2D pc = new Vec2D(buf[4], buf[5]); + for (int t = 0; t <= bezierRes; t++) { + s.add(BezierCurve2D.computePointInSegment(prev, pa, pb, + pc, (float) 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(Shape2D s) { + if (s instanceof Rect) { + Rect r = (Rect) s; + return new Rectangle2D.Float(r.x, r.y, r.width, r.height); + } + if (s instanceof Triangle2D) { + Triangle2D t = (Triangle2D) 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 Ellipse) { + Ellipse e = (Ellipse) s; + Vec2D r = e.getRadii(); + return new Ellipse2D.Float(e.x - r.x, e.y - r.y, r.x * 2, r.y * 2); + } + if (!(s instanceof Polygon2D)) { + s = s.toPolygon2D(); + } + Polygon2D poly = (Polygon2D) s; + Path2D path = new Path2D.Float(); + Vec2D 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/main/java/com/syncleus/spangraph/geom/BoxIntersector.java b/src/main/java/toxi/geom/BoxIntersector.java similarity index 96% rename from src/main/java/com/syncleus/spangraph/geom/BoxIntersector.java rename to src/main/java/toxi/geom/BoxIntersector.java index 3c172a9..3b02acd 100644 --- a/src/main/java/com/syncleus/spangraph/geom/BoxIntersector.java +++ b/src/main/java/toxi/geom/BoxIntersector.java @@ -1,4 +1,4 @@ -package com.syncleus.spangraph.geom; +package toxi.geom; public class BoxIntersector implements Intersector3D { diff --git a/src/main/java/com/syncleus/spangraph/geom/Circle.java b/src/main/java/toxi/geom/Circle.java similarity index 99% rename from src/main/java/com/syncleus/spangraph/geom/Circle.java rename to src/main/java/toxi/geom/Circle.java index 9762474..2127f17 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Circle.java +++ b/src/main/java/toxi/geom/Circle.java @@ -25,12 +25,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; - - +package toxi.geom; import java.util.List; +import toxi.math.MathUtils; /** * This class overrides {@link Ellipse} to define a 2D circle and provides @@ -39,7 +38,6 @@ import java.util.List; */ public class Circle extends Ellipse { - /** * 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 diff --git a/src/main/java/com/syncleus/spangraph/geom/CircleIntersector.java b/src/main/java/toxi/geom/CircleIntersector.java similarity index 98% rename from src/main/java/com/syncleus/spangraph/geom/CircleIntersector.java rename to src/main/java/toxi/geom/CircleIntersector.java index ba66378..70b470d 100644 --- a/src/main/java/com/syncleus/spangraph/geom/CircleIntersector.java +++ b/src/main/java/toxi/geom/CircleIntersector.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * This class handles Circle-Ray2D intersections by implementing the diff --git a/src/main/java/toxi/geom/Cone.java b/src/main/java/toxi/geom/Cone.java new file mode 100644 index 0000000..c769b72 --- /dev/null +++ b/src/main/java/toxi/geom/Cone.java @@ -0,0 +1,111 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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; + +/** + * 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 Cone extends Vec3D { + + public Vec3D dir; + public float radiusSouth; + public float radiusNorth; + public float 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 Cone(roVec3D pos, roVec3D dir, float rNorth, + float rSouth, float len) { + super(pos); + this.dir = dir.getNormalized(); + this.radiusNorth = rNorth; + this.radiusSouth = rSouth; + this.length = len; + } + +// public Mesh3D toMesh(int steps) { +// return toMesh(steps, 0); +// } +// +// public Mesh3D toMesh(int steps, float thetaOffset) { +// return toMesh(null, steps, thetaOffset, true, true); +// } +// +// public Mesh3D toMesh(Mesh3D mesh, int steps, float thetaOffset, +// boolean topClosed, boolean bottomClosed) { +// roVec3D c = this.add(0.01f, 0.01f, 0.01f); +// roVec3D n = c.cross(dir.getNormalized()).normalize(); +// Vec3D halfAxis = dir.scale(length * 0.5f); +// Vec3D p = sub(halfAxis); +// Vec3D q = add(halfAxis); +// Vec3D[] south = new Vec3D[steps]; +// Vec3D[] north = new Vec3D[steps]; +// float phi = MathUtils.TWO_PI / steps; +// for (int i = 0; i < steps; i++) { +// float theta = i * phi + thetaOffset; +// XYZ 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 TriangleMesh("cone", numV, numF); +// } +// for (int i = 0, j = 1; i < steps; i++, j++) { +// if (j == steps) { +// j = 0; +// } +// mesh.addFace(south[i], north[i], south[j], null, null, null, null); +// mesh.addFace(south[j], north[i], north[j], null, null, null, null); +// if (bottomClosed) { +// mesh.addFace(p, south[i], south[j], null, null, null, null); +// } +// if (topClosed) { +// mesh.addFace(north[i], q, north[j], null, null, null, null); +// } +// } +// return mesh; +// } +} \ No newline at end of file diff --git a/src/main/java/toxi/geom/ConvexPolygonClipper.java b/src/main/java/toxi/geom/ConvexPolygonClipper.java new file mode 100644 index 0000000..7aa0f9c --- /dev/null +++ b/src/main/java/toxi/geom/ConvexPolygonClipper.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(ReadonlyVec2D)} 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 ConvexPolygonClipper implements PolygonClipper2D { + + protected Polygon2D bounds; + protected Vec2D boundsCentroid; + + public ConvexPolygonClipper(Polygon2D bounds) { + setBounds(bounds); + } + + public Polygon2D clipPolygon(Polygon2D poly) { + List<Vec2D> points = new ArrayList<Vec2D>(poly.vertices); + List<Vec2D> clipped = new ArrayList<Vec2D>(); + points.add(points.get(0)); + for (Line2D clipEdge : bounds.getEdges()) { + clipped.clear(); + float sign = clipEdge.classifyPoint(boundsCentroid); + for (int i = 0, num = points.size() - 1; i < num; i++) { + Vec2D p = points.get(i); + Vec2D 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<Vec2D> t = points; + points = clipped; + clipped = t; + } + return new Polygon2D(points).removeDuplicates(0.001f); + } + + public Polygon2D getBounds() { + return bounds; + } + + protected Vec2D getClippedPosOnEdge(Line2D clipEdge, Vec2D p, Vec2D q) { + return clipEdge.intersectLine(new Line2D(p, q)).getPos(); + } + + protected boolean isKnownVertex(List<Vec2D> list, Vec2D q) { + for (Vec2D p : list) { + if (p.equalsWithTolerance(q, 0.001f)) { + return true; + } + } + return false; + } + + public void setBounds(Polygon2D bounds) { + this.bounds = bounds; + this.boundsCentroid = bounds.getCentroid(); + } +} diff --git a/src/main/java/toxi/geom/CoordinateExtractor.java b/src/main/java/toxi/geom/CoordinateExtractor.java new file mode 100644 index 0000000..6cb3bfc --- /dev/null +++ b/src/main/java/toxi/geom/CoordinateExtractor.java @@ -0,0 +1,6 @@ +package toxi.geom; + +public interface CoordinateExtractor<T> { + + public float coordinate(T obj); +} diff --git a/src/main/java/com/syncleus/spangraph/geom/Ellipse.java b/src/main/java/toxi/geom/Ellipse.java similarity index 92% rename from src/main/java/com/syncleus/spangraph/geom/Ellipse.java rename to src/main/java/toxi/geom/Ellipse.java index bd817cc..cb3b17a 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Ellipse.java +++ b/src/main/java/toxi/geom/Ellipse.java @@ -25,7 +25,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; + +import toxi.math.MathUtils; +import toxi.util.datatypes.BiasedFloatRange; import java.util.List; @@ -89,6 +92,7 @@ public class Ellipse extends Vec2D implements Shape2D { * Returns the ellipse's bounding rect. * * @return bounding rect + * @see toxi.geom.Shape2D#getBounds() */ public Rect getBounds() { return new Rect(sub(radius), add(radius)); @@ -141,15 +145,14 @@ public class Ellipse extends Vec2D implements Shape2D { /** * Creates a random point within the ellipse using a * {@link BiasedFloatRange} to create a more uniform distribution. - * + * * @return Vec2D */ public Vec2D getRandomPoint() { - throw new UnsupportedOperationException(); -// float theta = MathUtils.random(MathUtils.TWO_PI); -// BiasedFloatRange rnd = new BiasedFloatRange(0f, 1f, 1f, MathUtils.SQRT2); -// return Vec2D.fromTheta(theta).scaleSelf(radius.scale(rnd.pickRandom())) -// .addSelf(this); + float theta = MathUtils.random(MathUtils.TWO_PI); + BiasedFloatRange rnd = new BiasedFloatRange(0f, 1f, 1f, MathUtils.SQRT2); + return Vec2D.fromTheta(theta).scaleSelf(radius.scale(rnd.pickRandom())) + .addSelf(this); } /** @@ -171,7 +174,7 @@ public class Ellipse extends Vec2D implements Shape2D { * @param r * @return itself */ - public Ellipse setRadii(ReadonlyVec3D r) { + public Ellipse setRadii(roVec3D r) { return setRadii(r.x(), r.y()); } diff --git a/src/main/java/toxi/geom/GMatrix.java b/src/main/java/toxi/geom/GMatrix.java new file mode 100644 index 0000000..ff4998e --- /dev/null +++ b/src/main/java/toxi/geom/GMatrix.java @@ -0,0 +1,2644 @@ +/* + * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +/** + * A double precision, row major, general and dynamically-resizable, + * two-dimensional matrix class. Row and column numbering begins with zero. + */ +public class GMatrix implements java.io.Serializable, Cloneable { + + static final long serialVersionUID = 1L; + + /** + * Solves a set of linear equations. The input parameters "matrix1", and + * "row_perm" come from luDecompostion and do not change here. The parameter + * "matrix2" is a set of column vectors assembled into a nxn matrix of + * floating-point values. The procedure takes each column of "matrix2" in + * turn and treats it as the right-hand side of the matrix equation Ax = LUx + * = b. The solution vector replaces the original column of the matrix. + * + * If "matrix2" is the identity matrix, the procedure replaces its contents + * with the inverse of the matrix from which "matrix1" was originally + * derived. + */ + // + // Reference: Press, Flannery, Teukolsky, Vetterling, + // _Numerical_Recipes_in_C_, Cambridge University Press, + // 1988, pp 44-45. + // + public static void backSubstituteLU(int dim, double[] matrix1, + int[] row_perm, double[] matrix2) { + + int i, ii, ip, j, k; + int rp; + int cv, rv, ri; + double tt; + + // rp = row_perm; + rp = 0; + + // For each column vector of matrix2 ... + for (k = 0; k < dim; k++) { + // cv = &(matrix2[0][k]); + cv = k; + ii = -1; + + // Forward substitution + for (i = 0; i < dim; i++) { + double sum; + + ip = row_perm[rp + i]; + sum = matrix2[cv + dim * ip]; + matrix2[cv + dim * ip] = matrix2[cv + dim * i]; + if (ii >= 0) { + // rv = &(matrix1[i][0]); + rv = i * dim; + for (j = ii; j <= i - 1; j++) { + sum -= matrix1[rv + j] * matrix2[cv + dim * j]; + } + } else if (sum != 0.0) { + ii = i; + } + matrix2[cv + dim * i] = sum; + } + + // Backsubstitution + for (i = 0; i < dim; i++) { + ri = (dim - 1 - i); + rv = dim * (ri); + tt = 0.0; + for (j = 1; j <= i; j++) { + tt += matrix1[rv + dim - j] * matrix2[cv + dim * (dim - j)]; + } + matrix2[cv + dim * ri] = (matrix2[cv + dim * ri] - tt) + / matrix1[rv + ri]; + } + } + } + + private static void chase_across(double[] s, double[] e, int k, GMatrix u) { + double f, g, r; + double[] cosl = new double[1]; + double[] sinl = new double[1]; + int i; + GMatrix t = new GMatrix(u.nRow, u.nCol); + GMatrix m = new GMatrix(u.nRow, u.nCol); + + g = e[k]; + f = s[k + 1]; + + for (i = k; i < u.nCol - 2; i++) { + r = compute_rot(f, g, sinl, cosl); + g = -e[i + 1] * sinl[0]; + f = s[i + 2]; + s[i + 1] = r; + e[i + 1] = e[i + 1] * cosl[0]; + update_u_split(k, i + 1, u, cosl, sinl, t, m); + } + + s[i + 1] = compute_rot(f, g, sinl, cosl); + update_u_split(k, i + 1, u, cosl, sinl, t, m); + } + + private static void chase_up(double[] s, double[] e, int k, GMatrix v) { + double f, g, r; + double[] cosr = new double[1]; + double[] sinr = new double[1]; + int i; + GMatrix t = new GMatrix(v.nRow, v.nCol); + GMatrix m = new GMatrix(v.nRow, v.nCol); + + f = e[k]; + g = s[k]; + + for (i = k; i > 0; i--) { + r = compute_rot(f, g, sinr, cosr); + f = -e[i - 1] * sinr[0]; + g = s[i - 1]; + s[i] = r; + e[i - 1] = e[i - 1] * cosr[0]; + update_v_split(i, k + 1, v, cosr, sinr, t, m); + } + + s[i + 1] = compute_rot(f, g, sinr, cosr); + update_v_split(i, k + 1, v, cosr, sinr, t, m); + } + + private static void checkMatrix(GMatrix m) { + int i, j; + + for (i = 0; i < m.nRow; i++) { + for (j = 0; j < m.nCol; j++) { + if (MathUtils.abs(m.values[i][j]) < 0.0000000001) { + System.out.print(" 0.0 "); + } else { + System.out.print(" " + m.values[i][j]); + } + } + System.out.print("\n"); + } + } + + private static int compute_2X2(double f, double g, double h, + double[] single_values, double[] snl, double[] csl, double[] snr, + double[] csr, int index) { + + double c_b3 = 2.0; + double c_b4 = 1.0; + + double d__1; + int pmax; + double temp; + boolean swap; + double a, d, l, m, r, s, t, tsign, fa, ga, ha; + double ft, gt, ht, mm; + boolean gasmal; + double tt, clt, crt, slt, srt; + double ssmin, ssmax; + + ssmax = single_values[0]; + ssmin = single_values[1]; + clt = 0.0; + crt = 0.0; + slt = 0.0; + srt = 0.0; + tsign = 0.0; + + ft = f; + fa = MathUtils.abs(ft); + ht = h; + ha = MathUtils.abs(h); + + pmax = 1; + if (ha > fa) { + swap = true; + } else { + swap = false; + } + + if (swap) { + pmax = 3; + temp = ft; + ft = ht; + ht = temp; + temp = fa; + fa = ha; + ha = temp; + + } + + gt = g; + ga = MathUtils.abs(gt); + if (ga == 0.0) { + single_values[1] = ha; + single_values[0] = fa; + clt = 1.0; + crt = 1.0; + slt = 0.0; + srt = 0.0; + } else { + gasmal = true; + if (ga > fa) { + pmax = 2; + if (fa / ga < EPS) { + gasmal = false; + ssmax = ga; + + if (ha > 1.0) { + ssmin = fa / (ga / ha); + } else { + ssmin = fa / ga * ha; + } + clt = 1.0; + slt = ht / gt; + srt = 1.0; + crt = ft / gt; + } + } + if (gasmal) { + d = fa - ha; + if (d == fa) { + + l = 1.0; + } else { + l = d / fa; + } + + m = gt / ft; + t = 2.0 - l; + mm = m * m; + tt = t * t; + s = Math.sqrt(tt + mm); + + if (l == 0.0) { + r = MathUtils.abs(m); + } else { + r = Math.sqrt(l * l + mm); + } + + a = (s + r) * 0.5; + if (ga > fa) { + pmax = 2; + if (fa / ga < EPS) { + gasmal = false; + ssmax = ga; + if (ha > 1.0) { + ssmin = fa / (ga / ha); + } else { + ssmin = fa / ga * ha; + } + clt = 1.0; + slt = ht / gt; + srt = 1.0; + crt = ft / gt; + } + } + if (gasmal) { + d = fa - ha; + if (d == fa) { + l = 1.0; + } else { + l = d / fa; + } + + m = gt / ft; + t = 2.0 - l; + + mm = m * m; + tt = t * t; + s = Math.sqrt(tt + mm); + + if (l == 0.) { + r = MathUtils.abs(m); + } else { + r = Math.sqrt(l * l + mm); + } + + a = (s + r) * 0.5; + ssmin = ha / a; + ssmax = fa * a; + + if (mm == 0.0) { + if (l == 0.0) { + t = MathUtils.dualSign(c_b3, ft) + * MathUtils.dualSign(c_b4, gt); + } else { + t = gt / MathUtils.dualSign(d, ft) + m / t; + } + } else { + t = (m / (s + t) + m / (r + l)) * (a + 1.0); + } + + l = Math.sqrt(t * t + 4.0); + crt = 2.0 / l; + srt = t / l; + clt = (crt + srt * m) / a; + slt = ht / ft * srt / a; + } + } + if (swap) { + csl[0] = srt; + snl[0] = crt; + csr[0] = slt; + snr[0] = clt; + } else { + csl[0] = clt; + snl[0] = slt; + csr[0] = crt; + snr[0] = srt; + } + + if (pmax == 1) { + tsign = MathUtils.dualSign(c_b4, csr[0]) + * MathUtils.dualSign(c_b4, csl[0]) + * MathUtils.dualSign(c_b4, f); + } + if (pmax == 2) { + tsign = MathUtils.dualSign(c_b4, snr[0]) + * MathUtils.dualSign(c_b4, csl[0]) + * MathUtils.dualSign(c_b4, g); + } + if (pmax == 3) { + tsign = MathUtils.dualSign(c_b4, snr[0]) + * MathUtils.dualSign(c_b4, snl[0]) + * MathUtils.dualSign(c_b4, h); + } + + single_values[index] = MathUtils.dualSign(ssmax, tsign); + d__1 = tsign * MathUtils.dualSign(c_b4, f) + * MathUtils.dualSign(c_b4, h); + single_values[index + 1] = MathUtils.dualSign(ssmin, d__1); + } + + return 0; + } + + private static double compute_rot(double f, double g, double[] sin, + double[] cos) { + double cs, sn; + int i; + double scale; + int count; + double f1, g1; + double r; + final double safmn2 = 2.002083095183101E-146; + final double safmx2 = 4.994797680505588E+145; + + if (g == 0.0) { + cs = 1.0; + sn = 0.0; + r = f; + } else if (f == 0.0) { + cs = 0.0; + sn = 1.0; + r = g; + } else { + f1 = f; + g1 = g; + scale = MathUtils.max(MathUtils.abs(f1), MathUtils.abs(g1)); + if (scale >= safmx2) { + count = 0; + while (scale >= safmx2) { + ++count; + f1 *= safmn2; + g1 *= safmn2; + scale = MathUtils.max(MathUtils.abs(f1), MathUtils.abs(g1)); + } + r = Math.sqrt(f1 * f1 + g1 * g1); + cs = f1 / r; + sn = g1 / r; + for (i = 1; i <= count; ++i) { + r *= safmx2; + } + } else if (scale <= safmn2) { + count = 0; + while (scale <= safmn2) { + ++count; + f1 *= safmx2; + g1 *= safmx2; + scale = MathUtils.max(MathUtils.abs(f1), MathUtils.abs(g1)); + } + r = Math.sqrt(f1 * f1 + g1 * g1); + cs = f1 / r; + sn = g1 / r; + for (i = 1; i <= count; ++i) { + r *= safmn2; + } + } else { + r = Math.sqrt(f1 * f1 + g1 * g1); + cs = f1 / r; + sn = g1 / r; + } + if (MathUtils.abs(f) > MathUtils.abs(g) && cs < 0.0) { + cs = -cs; + sn = -sn; + r = -r; + } + } + sin[0] = sn; + cos[0] = cs; + return r; + } + + private static double compute_shift(double f, double g, double h) { + double d__1, d__2; + double fhmn, fhmx, c, fa, ga, ha, as, at, au; + double ssmin; + + fa = MathUtils.abs(f); + ga = MathUtils.abs(g); + ha = MathUtils.abs(h); + fhmn = MathUtils.min(fa, ha); + fhmx = MathUtils.max(fa, ha); + + if (fhmn == 0.0) { + ssmin = 0.0; + if (fhmx == 0.0) { + } else { + d__1 = MathUtils.min(fhmx, ga) / MathUtils.max(fhmx, ga); + } + } else { + if (ga < fhmx) { + as = fhmn / fhmx + 1.0; + at = (fhmx - fhmn) / fhmx; + d__1 = ga / fhmx; + au = d__1 * d__1; + c = 2.0 / (Math.sqrt(as * as + au) + Math.sqrt(at * at + au)); + ssmin = fhmn * c; + } else { + au = fhmx / ga; + if (au == 0.0) { + ssmin = fhmn * fhmx / ga; + } else { + as = fhmn / fhmx + 1.0; + at = (fhmx - fhmn) / fhmx; + d__1 = as * au; + d__2 = at * au; + c = 1.0 / (Math.sqrt(d__1 * d__1 + 1.0) + Math.sqrt(d__2 + * d__2 + 1.0)); + ssmin = fhmn * c * au; + ssmin += ssmin; + } + } + } + + return ssmin; + } + + public static void computeQR(int start, int end, double[] s, double[] e, + GMatrix u, GMatrix v) { + + int i, k, n, sl; + double shift, r, f, g; + double[] cosl = new double[1]; + double[] cosr = new double[1]; + double[] sinl = new double[1]; + double[] sinr = new double[1]; + + final int MAX_INTERATIONS = 2; + final double CONVERGE_TOL = 4.89E-15; + + boolean converged = false; + + f = 0.0; + g = 0.0; + + for (k = 0; k < MAX_INTERATIONS && !converged; k++) { + for (i = start; i <= end; i++) { + + // if at start of iterfaction compute shift + if (i == start) { + if (e.length == s.length) { + sl = end; + } else { + sl = end + 1; + } + + shift = compute_shift(s[sl - 1], e[end], s[sl]); + + f = (MathUtils.abs(s[i]) - shift) + * (MathUtils.dualSign(1.0, s[i]) + shift / s[i]); + g = e[i]; + } + + r = compute_rot(f, g, sinr, cosr); + if (i != start) { + e[i - 1] = r; + } + + f = cosr[0] * s[i] + sinr[0] * e[i]; + e[i] = cosr[0] * e[i] - sinr[0] * s[i]; + g = sinr[0] * s[i + 1]; + s[i + 1] = cosr[0] * s[i + 1]; + + update_v(i, v, cosr, sinr); + + r = compute_rot(f, g, sinl, cosl); + s[i] = r; + f = cosl[0] * e[i] + sinl[0] * s[i + 1]; + s[i + 1] = cosl[0] * s[i + 1] - sinl[0] * e[i]; + + if (i < end) { + // if not last + g = sinl[0] * e[i + 1]; + e[i + 1] = cosl[0] * e[i + 1]; + } + update_u(i, u, cosl, sinl); + } + + // if extra off diagonal perform one more right side rotation + if (s.length == e.length) { + r = compute_rot(f, g, sinr, cosr); + f = cosr[0] * s[i] + sinr[0] * e[i]; + e[i] = cosr[0] * e[i] - sinr[0] * s[i]; + s[i + 1] = cosr[0] * s[i + 1]; + + update_v(i, v, cosr, sinr); + } + + // check for convergence on off diagonals and reduce + while ((end - start > 1) && (MathUtils.abs(e[end]) < CONVERGE_TOL)) { + end--; + } + + // check if need to split + for (n = end - 2; n > start; n--) { + if (MathUtils.abs(e[n]) < CONVERGE_TOL) { // split + computeQR(n + 1, end, s, e, u, v); // do lower matrix + end = n - 1; // do upper matrix + + // check for convergence on off diagonals and reduce + while ((end - start > 1) + && (MathUtils.abs(e[end]) < CONVERGE_TOL)) { + end--; + } + } + } + + if ((end - start <= 1) + && (MathUtils.abs(e[start + 1]) < CONVERGE_TOL)) { + converged = true; + } else { + // check if zero on the diagonal + } + + } + + if (MathUtils.abs(e[1]) < CONVERGE_TOL) { + compute_2X2(s[start], e[start], s[start + 1], s, sinl, cosl, sinr, + cosr, 0); + e[start] = 0.0; + e[start + 1] = 0.0; + } + + i = start; + update_u(i, u, cosl, sinl); + update_v(i, v, cosr, sinr); + } + + public static int computeSVD(GMatrix mat, GMatrix U, GMatrix W, GMatrix V) { + int i, j, k; + int nr, nc, si; + + int rank; + double mag, scale, t; + int eLength, sLength, vecLength; + + GMatrix tmp = new GMatrix(mat.nRow, mat.nCol); + GMatrix u = new GMatrix(mat.nRow, mat.nCol); + GMatrix v = new GMatrix(mat.nRow, mat.nCol); + GMatrix m = new GMatrix(mat); + + // compute the number of singular values + if (m.nRow >= m.nCol) { + sLength = m.nCol; + eLength = m.nCol - 1; + } else { + sLength = m.nRow; + eLength = m.nRow; + } + + if (m.nRow > m.nCol) { + vecLength = m.nRow; + } else { + vecLength = m.nCol; + } + + double[] vec = new double[vecLength]; + double[] single_values = new double[sLength]; + double[] e = new double[eLength]; + + rank = 0; + + U.identity(); + V.identity(); + + nr = m.nRow; + nc = m.nCol; + + // householder reduction + for (si = 0; si < sLength; si++) { + // for each singular value + + if (nr > 1) { + // compute reflector + mag = 0.0; + for (i = 0; i < nr; i++) { + mag += m.values[i + si][si] * m.values[i + si][si]; + } + + mag = Math.sqrt(mag); + if (m.values[si][si] == 0.0) { + vec[0] = mag; + } else { + vec[0] = m.values[si][si] + + MathUtils.dualSign(mag, m.values[si][si]); + } + + for (i = 1; i < nr; i++) { + vec[i] = m.values[si + i][si]; + } + + scale = 0.0; + for (i = 0; i < nr; i++) { + scale += vec[i] * vec[i]; + } + + scale = 2.0 / scale; + for (j = si; j < m.nRow; j++) { + for (k = si; k < m.nRow; k++) { + u.values[j][k] = -scale * vec[j - si] * vec[k - si]; + } + } + + for (i = si; i < m.nRow; i++) { + u.values[i][i] += 1.0; + } + + // compute s + t = 0.0; + for (i = si; i < m.nRow; i++) { + t += u.values[si][i] * m.values[i][si]; + } + m.values[si][si] = t; + + // apply reflector + for (j = si; j < m.nRow; j++) { + for (k = si + 1; k < m.nCol; k++) { + tmp.values[j][k] = 0.0; + for (i = si; i < m.nCol; i++) { + tmp.values[j][k] += u.values[j][i] * m.values[i][k]; + } + } + } + + for (j = si; j < m.nRow; j++) { + for (k = si + 1; k < m.nCol; k++) { + m.values[j][k] = tmp.values[j][k]; + } + } + + // update U matrix + for (j = si; j < m.nRow; j++) { + for (k = 0; k < m.nCol; k++) { + tmp.values[j][k] = 0.0; + for (i = si; i < m.nCol; i++) { + tmp.values[j][k] += u.values[j][i] * U.values[i][k]; + } + } + } + + for (j = si; j < m.nRow; j++) { + for (k = 0; k < m.nCol; k++) { + U.values[j][k] = tmp.values[j][k]; + } + } + nr--; + } + + if (nc > 2) { + mag = 0.0; + for (i = 1; i < nc; i++) { + mag += m.values[si][si + i] * m.values[si][si + i]; + } + // generate the reflection vector, compute the first entry and + // copy the rest from the row to be zeroed + mag = Math.sqrt(mag); + if (m.values[si][si + 1] == 0.0) { + vec[0] = mag; + } else { + vec[0] = m.values[si][si + 1] + + MathUtils.dualSign(mag, m.values[si][si + 1]); + } + + for (i = 1; i < nc - 1; i++) { + vec[i] = m.values[si][si + i + 1]; + } + + // use reflection vector to compute v matrix + scale = 0.0; + for (i = 0; i < nc - 1; i++) { + scale += vec[i] * vec[i]; + } + + scale = 2.0 / scale; + for (j = si + 1; j < nc; j++) { + for (k = si + 1; k < m.nCol; k++) { + v.values[j][k] = -scale * vec[j - si - 1] + * vec[k - si - 1]; + } + } + + for (i = si + 1; i < m.nCol; i++) { + v.values[i][i] += 1.0; + } + + t = 0.0; + for (i = si; i < m.nCol; i++) { + t += v.values[i][si + 1] * m.values[si][i]; + } + m.values[si][si + 1] = t; + + // apply reflector + for (j = si + 1; j < m.nRow; j++) { + for (k = si + 1; k < m.nCol; k++) { + tmp.values[j][k] = 0.0; + for (i = si + 1; i < m.nCol; i++) { + tmp.values[j][k] += v.values[i][k] * m.values[j][i]; + } + } + } + + for (j = si + 1; j < m.nRow; j++) { + for (k = si + 1; k < m.nCol; k++) { + m.values[j][k] = tmp.values[j][k]; + } + } + + // update V matrix + for (j = 0; j < m.nRow; j++) { + for (k = si + 1; k < m.nCol; k++) { + tmp.values[j][k] = 0.0; + for (i = si + 1; i < m.nCol; i++) { + tmp.values[j][k] += v.values[i][k] * V.values[j][i]; + } + } + } + + for (j = 0; j < m.nRow; j++) { + for (k = si + 1; k < m.nCol; k++) { + V.values[j][k] = tmp.values[j][k]; + } + } + nc--; + } + } + + for (i = 0; i < sLength; i++) { + single_values[i] = m.values[i][i]; + } + + for (i = 0; i < eLength; i++) { + e[i] = m.values[i][i + 1]; + } + + // Fix ArrayIndexOutOfBounds for 2x2 matrices, which partially + // addresses bug 4348562 for J3D 1.2.1. + // + // Does *not* fix the following problems reported in 4348562, + // which will wait for J3D 1.3: + // + // 1) no output of W + // 2) wrong transposition of U + // 3) wrong results for 4x4 matrices + // 4) slow performance + if (m.nRow == 2 && m.nCol == 2) { + double[] cosl = new double[1]; + double[] cosr = new double[1]; + double[] sinl = new double[1]; + double[] sinr = new double[1]; + + compute_2X2(single_values[0], e[0], single_values[1], + single_values, sinl, cosl, sinr, cosr, 0); + + update_u(0, U, cosl, sinl); + update_v(0, V, cosr, sinr); + return 2; + } + + // compute_qr causes ArrayIndexOutOfBounds for 2x2 matrices + computeQR(0, e.length - 1, single_values, e, U, V); + + // compute rank = number of non zero singular values + rank = single_values.length; + + // sort by order of size of single values + // and check for zero's + return rank; + } + + /** + * Given a nxn array "matrix0", this function replaces it with the LU + * decomposition of a row-wise permutation of itself. The input parameters + * are "matrix0" and "dim". The array "matrix0" is also an output parameter. + * The vector "row_perm[]" is an output parameter that contains the row + * permutations resulting from partial pivoting. The output parameter + * "even_row_xchg" is 1 when the number of row exchanges is even, or -1 + * otherwise. Assumes data type is always double. + * + * @return true if the matrix is nonsingular, or false otherwise. + */ + // + // Reference: Press, Flannery, Teukolsky, Vetterling, + // _Numerical_Recipes_in_C_, Cambridge University Press, + // 1988, pp 40-45. + // + public static boolean decomposeLU(int dim, double[] matrix0, + int[] row_perm, int[] even_row_xchg) { + + double row_scale[] = new double[dim]; + + // Determine implicit scaling information by looping over rows + int i, j; + int ptr, rs, mtx; + double big, temp; + + ptr = 0; + rs = 0; + even_row_xchg[0] = 1; + + // For each row ... + i = dim; + while (i-- != 0) { + big = 0.0; + + // For each column, find the largest element in the row + j = dim; + while (j-- != 0) { + temp = matrix0[ptr++]; + temp = MathUtils.abs(temp); + if (temp > big) { + big = temp; + } + } + + // Is the matrix singular? + if (big == 0.0) { + return false; + } + row_scale[rs++] = 1.0 / big; + } + + // For all columns, execute Crout's method + mtx = 0; + for (j = 0; j < dim; j++) { + int imax, k; + int target, p1, p2; + double sum; + + // Determine elements of upper diagonal matrix U + for (i = 0; i < j; i++) { + target = mtx + (dim * i) + j; + sum = matrix0[target]; + k = i; + p1 = mtx + (dim * i); + p2 = mtx + j; + while (k-- != 0) { + sum -= matrix0[p1] * matrix0[p2]; + p1++; + p2 += dim; + } + matrix0[target] = sum; + } + + // Search for largest pivot element and calculate + // intermediate elements of lower diagonal matrix L. + big = 0.0; + imax = -1; + for (i = j; i < dim; i++) { + target = mtx + (dim * i) + j; + sum = matrix0[target]; + k = j; + p1 = mtx + (dim * i); + p2 = mtx + j; + while (k-- != 0) { + sum -= matrix0[p1] * matrix0[p2]; + p1++; + p2 += dim; + } + matrix0[target] = sum; + + // Is this the best pivot so far? + if ((temp = row_scale[i] * MathUtils.abs(sum)) >= big) { + big = temp; + imax = i; + } + } + + if (imax < 0) { + throw new RuntimeException(); + } + + // Is a row exchange necessary? + if (j != imax) { + // Yes: exchange rows + k = dim; + p1 = mtx + (dim * imax); + p2 = mtx + (dim * j); + while (k-- != 0) { + temp = matrix0[p1]; + matrix0[p1++] = matrix0[p2]; + matrix0[p2++] = temp; + } + + // Record change in scale factor + row_scale[imax] = row_scale[j]; + even_row_xchg[0] = -even_row_xchg[0]; // change exchange parity + } + + // Record row permutation + row_perm[j] = imax; + + // Is the matrix singular + if (matrix0[(mtx + (dim * j) + j)] == 0.0) { + return false; + } + + // Divide elements of lower diagonal matrix L by pivot + if (j != (dim - 1)) { + temp = 1.0 / (matrix0[(mtx + (dim * j) + j)]); + target = mtx + (dim * (j + 1)) + j; + i = (dim - 1) - j; + while (i-- != 0) { + matrix0[target] *= temp; + target += dim; + } + } + + } + + return true; + } + + private static void print_m(GMatrix m, GMatrix u, GMatrix v) { + GMatrix mtmp = new GMatrix(m.nCol, m.nRow); + + mtmp.mul(u, mtmp); + mtmp.mul(mtmp, v); + System.out.println("\n m = \n" + mtmp.toString(mtmp)); + + } + + private static void print_se(double[] s, double[] e) { + System.out.println("\ns =" + s[0] + " " + s[1] + " " + s[2]); + System.out.println("e =" + e[0] + " " + e[1]); + } + + private static void print_svd(double[] s, double[] e, GMatrix u, GMatrix v) { + int i; + GMatrix mtmp = new GMatrix(u.nCol, v.nRow); + + System.out.println(" \ns = "); + for (i = 0; i < s.length; i++) { + System.out.println(" " + s[i]); + } + + System.out.println(" \ne = "); + for (i = 0; i < e.length; i++) { + System.out.println(" " + e[i]); + } + + System.out.println(" \nu = \n" + u.toString()); + System.out.println(" \nv = \n" + v.toString()); + + mtmp.identity(); + for (i = 0; i < s.length; i++) { + mtmp.values[i][i] = s[i]; + } + for (i = 0; i < e.length; i++) { + mtmp.values[i][i + 1] = e[i]; + } + System.out.println(" \nm = \n" + mtmp.toString()); + + mtmp.mulTransposeLeft(u, mtmp); + mtmp.mulTransposeRight(mtmp, v); + + System.out.println(" \n u.transpose*m*v.transpose = \n" + + mtmp.toString()); + } + + private static String toString(GMatrix m) { + StringBuffer buffer = new StringBuffer(m.nRow * m.nCol * 8); + int i, j; + + for (i = 0; i < m.nRow; i++) { + for (j = 0; j < m.nCol; j++) { + if (MathUtils.abs(m.values[i][j]) < MathUtils.EPS) { + buffer.append("0.0000 "); + } else { + buffer.append(m.values[i][j]).append(" "); + } + } + buffer.append("\n"); + } + return buffer.toString(); + } + + private static void update_u(int index, GMatrix u, double[] cosl, + double[] sinl) { + int j; + double utemp; + + for (j = 0; j < u.nCol; j++) { + utemp = u.values[index][j]; + u.values[index][j] = cosl[0] * utemp + sinl[0] + * u.values[index + 1][j]; + u.values[index + 1][j] = -sinl[0] * utemp + cosl[0] + * u.values[index + 1][j]; + } + } + + private static void update_u_split(int topr, int bottomr, GMatrix u, + double[] cosl, double[] sinl, GMatrix t, GMatrix m) { + int j; + double utemp; + + for (j = 0; j < u.nCol; j++) { + utemp = u.values[topr][j]; + u.values[topr][j] = cosl[0] * utemp - sinl[0] + * u.values[bottomr][j]; + u.values[bottomr][j] = sinl[0] * utemp + cosl[0] + * u.values[bottomr][j]; + } + + checkMatrix(m); + checkMatrix(t); + m.mul(t, m); + checkMatrix(m); + } + + private static void update_v(int index, GMatrix v, double[] cosr, + double[] sinr) { + int j; + double vtemp; + + for (j = 0; j < v.nRow; j++) { + vtemp = v.values[j][index]; + v.values[j][index] = cosr[0] * vtemp + sinr[0] + * v.values[j][index + 1]; + v.values[j][index + 1] = -sinr[0] * vtemp + cosr[0] + * v.values[j][index + 1]; + } + } + + private static void update_v_split(int topr, int bottomr, GMatrix v, + double[] cosr, double[] sinr, GMatrix t, GMatrix m) { + int j; + double vtemp; + + for (j = 0; j < v.nRow; j++) { + vtemp = v.values[j][topr]; + v.values[j][topr] = cosr[0] * vtemp - sinr[0] + * v.values[j][bottomr]; + v.values[j][bottomr] = sinr[0] * vtemp + cosr[0] + * v.values[j][bottomr]; + } + + checkMatrix(m); + checkMatrix(t); + m.mul(m, t); + checkMatrix(m); + } + + int nRow; + + int nCol; + + // double dereference is slow + double[][] values; + + private static final double EPS = 1.0E-10; + + /** + * Constructs a new GMatrix and copies the initial values from the parameter + * matrix. + * + * @param matrix + * the source of the initial values of the new GMatrix + */ + public GMatrix(GMatrix matrix) { + nRow = matrix.nRow; + nCol = matrix.nCol; + values = new double[nRow][nCol]; + + int i, j; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = matrix.values[i][j]; + } + } + } + + /** + * Constructs an nRow by NCol identity matrix. Note that because row and + * column numbering begins with zero, nRow and nCol will be one larger than + * the maximum possible matrix index values. + * + * @param nRow + * number of rows in this matrix. + * @param nCol + * number of columns in this matrix. + */ + public GMatrix(int nRow, int nCol) { + values = new double[nRow][nCol]; + this.nRow = nRow; + this.nCol = nCol; + + int i, j; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = 0.0; + } + } + + int l; + if (nRow < nCol) { + l = nRow; + } else { + l = nCol; + } + + for (i = 0; i < l; i++) { + values[i][i] = 1.0; + } + } + + /** + * Constructs an nRow by nCol matrix initialized to the values in the matrix + * array. The array values are copied in one row at a time in row major + * fashion. The array should be at least nRow*nCol in length. Note that + * because row and column numbering begins with zero, nRow and nCol will be + * one larger than the maximum possible matrix index values. + * + * @param nRow + * number of rows in this matrix. + * @param nCol + * number of columns in this matrix. + * @param matrix + * a 1D array that specifies a matrix in row major fashion + */ + public GMatrix(int nRow, int nCol, double[] matrix) { + values = new double[nRow][nCol]; + this.nRow = nRow; + this.nCol = nCol; + + int i, j; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = matrix[i * nCol + j]; + } + } + } + + /** + * Sets the value of this matrix to sum of itself and matrix m1. + * + * @param m1 + * the other matrix + */ + public final void add(GMatrix m1) { + int i, j; + + if (nRow != m1.nRow) { + throw new MatrixSizeException(); + } + + if (nCol != m1.nCol) { + throw new MatrixSizeException(); + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = values[i][j] + m1.values[i][j]; + } + } + } + + /** + * Sets the value of this matrix to the matrix sum of matrices m1 and m2. + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void add(GMatrix m1, GMatrix m2) { + int i, j; + + if (m2.nRow != m1.nRow) { + throw new MatrixSizeException(); + } + + if (m2.nCol != m1.nCol) { + throw new MatrixSizeException(); + } + + if (nCol != m1.nCol || nRow != m1.nRow) { + throw new MatrixSizeException(); + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = m1.values[i][j] + m2.values[i][j]; + } + } + } + + /** + * Creates a new object of the same class as this object. + * + * @return a clone of this instance. + * @exception OutOfMemoryError + * if there is not enough memory. + * @see Cloneable + * @since vecmath 1.3 + */ + public Object clone() { + GMatrix m1 = null; + try { + m1 = (GMatrix) super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + + // Also need to clone array of values + m1.values = new double[nRow][nCol]; + for (int i = 0; i < nRow; i++) { + for (int j = 0; j < nCol; j++) { + m1.values[i][j] = values[i][j]; + } + } + + return m1; + } + + /** + * LU Decomposition: this matrix must be a square matrix and the LU GMatrix + * parameter must be the same size as this matrix. The matrix LU will be + * overwritten as the combination of a lower diagonal and upper diagonal + * matrix decompostion of this matrix; the diagonal elements of L (unity) + * are not stored. The GVector parameter records the row permutation + * effected by the partial pivoting, and is used as a parameter to the + * GVector method LUDBackSolve to solve sets of linear equations. This + * method returns +/- 1 depending on whether the number of row interchanges + * was even or odd, respectively. + * + * @param LU + * The matrix into which the lower and upper decompositions will + * be placed. + * @param permutation + * The row permutation effected by the partial pivoting + * @return +-1 depending on whether the number of row interchanges was even + * or odd respectively + */ + public final int computeLUD(GMatrix LU, GVector permutation) { + int size = LU.nRow * LU.nCol; + double[] temp = new double[size]; + int[] even_row_exchange = new int[1]; + int[] row_perm = new int[LU.nRow]; + int i, j; + + if (nRow != nCol) { + throw new MatrixSizeException(); + } + + if (nRow != LU.nRow) { + throw new MatrixSizeException(); + } + + if (nCol != LU.nCol) { + throw new MatrixSizeException(); + } + + if (LU.nRow != permutation.size()) { + throw new MatrixSizeException(); + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + temp[i * nCol + j] = values[i][j]; + } + } + + // Calculate LU decomposition: Is the matrix singular? + if (!decomposeLU(LU.nRow, temp, row_perm, even_row_exchange)) { + // Matrix has no inverse + throw new SingularMatrixException(); + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + LU.values[i][j] = temp[i * nCol + j]; + } + } + + for (i = 0; i < LU.nRow; i++) { + permutation.values[i] = row_perm[i]; + } + + return even_row_exchange[0]; + } + + /** + * Finds the singular value decomposition (SVD) of this matrix such that + * this = U*W*transpose(V); and returns the rank of this matrix; the values + * of U,W,V are all overwritten. Note that the matrix V is output as V, and + * not transpose(V). If this matrix is mxn, then U is mxm, W is a diagonal + * matrix that is mxn, and V is nxn. Using the notation W = diag(w), then + * the inverse of this matrix is: inverse(this) = V*diag(1/w)*tranpose(U), + * where diag(1/w) is the same matrix as W except that the reciprocal of + * each of the diagonal components is used. + * + * @param U + * The computed U matrix in the equation this = U*W*transpose(V) + * @param W + * The computed W matrix in the equation this = U*W*transpose(V) + * @param V + * The computed V matrix in the equation this = U*W*transpose(V) + * @return The rank of this matrix. + */ + public final int computeSVD(GMatrix U, GMatrix W, GMatrix V) { + // check for consistancy in dimensions + if (nCol != V.nCol || nCol != V.nRow) { + throw new MatrixSizeException(); + } + + if (nRow != U.nRow || nRow != U.nCol) { + throw new MatrixSizeException(); + } + + if (nRow != W.nRow || nCol != W.nCol) { + throw new MatrixSizeException(); + } + + // Fix ArrayIndexOutOfBounds for 2x2 matrices, which partially + // addresses bug 4348562 for J3D 1.2.1. + // + // Does *not* fix the following problems reported in 4348562, + // which will wait for J3D 1.3: + // + // 1) no output of W + // 2) wrong transposition of U + // 3) wrong results for 4x4 matrices + // 4) slow performance + if (nRow == 2 && nCol == 2) { + if (values[1][0] == 0.0) { + U.identity(); + V.identity(); + + if (values[0][1] == 0.0) { + return 2; + } + + double[] sinl = new double[1]; + double[] sinr = new double[1]; + double[] cosl = new double[1]; + double[] cosr = new double[1]; + double[] single_values = new double[2]; + + single_values[0] = values[0][0]; + single_values[1] = values[1][1]; + + compute_2X2(values[0][0], values[0][1], values[1][1], + single_values, sinl, cosl, sinr, cosr, 0); + + update_u(0, U, cosl, sinl); + update_v(0, V, cosr, sinr); + + return 2; + } + // else call computeSVD() and check for 2x2 there + } + + return computeSVD(this, U, W, V); + } + + /** + * Copies a sub-matrix derived from this matrix into the target matrix. The + * upper left of the sub-matrix is located at (rowSource, colSource); the + * lower right of the sub-matrix is located at + * (lastRowSource,lastColSource). The sub-matrix is copied into the the + * target matrix starting at (rowDest, colDest). + * + * @param rowSource + * the top-most row of the sub-matrix + * @param colSource + * the left-most column of the sub-matrix + * @param numRow + * the number of rows in the sub-matrix + * @param numCol + * the number of columns in the sub-matrix + * @param rowDest + * the top-most row of the position of the copied sub-matrix + * within the target matrix + * @param colDest + * the left-most column of the position of the copied sub-matrix + * within the target matrix + * @param target + * the matrix into which the sub-matrix will be copied + */ + public final void copySubMatrix(int rowSource, int colSource, int numRow, + int numCol, int rowDest, int colDest, GMatrix target) { + int i, j; + + if (this != target) { + for (i = 0; i < numRow; i++) { + for (j = 0; j < numCol; j++) { + target.values[rowDest + i][colDest + j] = values[rowSource + + i][colSource + j]; + } + } + } else { + double[][] tmp = new double[numRow][numCol]; + for (i = 0; i < numRow; i++) { + for (j = 0; j < numCol; j++) { + tmp[i][j] = values[rowSource + i][colSource + j]; + } + } + for (i = 0; i < numRow; i++) { + for (j = 0; j < numCol; j++) { + target.values[rowDest + i][colDest + j] = tmp[i][j]; + } + } + } + } + + /** + * Returns true if the L-infinite distance between this matrix and matrix m1 + * is less than or equal to the epsilon parameter, otherwise returns false. + * The L-infinite distance is equal to MAX[i=0,1,2, . . .n ; j=0,1,2, . . .n + * ; abs(this.m(i,j) - m1.m(i,j)] + * + * @param m1 + * The matrix to be compared to this matrix + * @param epsilon + * the threshold value + */ + public boolean epsilonEquals(GMatrix m1, double epsilon) { + int i, j; + double diff; + if (nRow != m1.nRow || nCol != m1.nCol) { + return false; + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + diff = values[i][j] - m1.values[i][j]; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + } + } + return true; + } + + /** + * @deprecated Use epsilonEquals(GMatrix, double) instead + */ + @Deprecated + public boolean epsilonEquals(GMatrix m1, float epsilon) { + return epsilonEquals(m1, (double) epsilon); + } + + /** + * Returns true if all of the data members of GMatrix m1 are equal to the + * corresponding data members in this GMatrix. + * + * @param m1 + * The matrix with which the comparison is made. + * @return true or false + */ + public boolean equals(GMatrix m1) { + try { + int i, j; + + if (nRow != m1.nRow || nCol != m1.nCol) { + return false; + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + if (values[i][j] != m1.values[i][j]) { + return false; + } + } + } + return true; + } catch (NullPointerException e2) { + return false; + } + } + + /** + * Returns true if the Object o1 is of type GMatrix and all of the data + * members of o1 are equal to the corresponding data members in this + * GMatrix. + * + * @param o1 + * The object with which the comparison is made. + * @return true or false + */ + public boolean equals(Object o1) { + try { + GMatrix m2 = (GMatrix) o1; + int i, j; + if (nRow != m2.nRow || nCol != m2.nCol) { + return false; + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + if (values[i][j] != m2.values[i][j]) { + return false; + } + } + } + return true; + } catch (ClassCastException e1) { + return false; + } catch (NullPointerException e2) { + return false; + } + } + + /** + * Places the values in the this GMatrix into the matrix m1; m1 should be at + * least as large as this GMatrix. + * + * @param m1 + * The matrix that will hold the new values + */ + public final void get(GMatrix m1) { + int i, j, nc, nr; + + if (nCol < m1.nCol) { + nc = nCol; + } else { + nc = m1.nCol; + } + + if (nRow < m1.nRow) { + nr = nRow; + } else { + nr = m1.nRow; + } + + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + m1.values[i][j] = values[i][j]; + } + } + for (i = nr; i < m1.nRow; i++) { + for (j = 0; j < m1.nCol; j++) { + m1.values[i][j] = 0.0; + } + } + for (j = nc; j < m1.nCol; j++) { + for (i = 0; i < nr; i++) { + m1.values[i][j] = 0.0; + } + } + } + + /** + * Places the values in the upper 3x3 of this GMatrix into the matrix m1. + * + * @param m1 + * The matrix that will hold the new values + */ + public final void get(Matrix3d m1) { + if (nRow < 3 || nCol < 3) { + m1.setZero(); + if (nCol > 0) { + if (nRow > 0) { + m1.m00 = values[0][0]; + if (nRow > 1) { + m1.m10 = values[1][0]; + if (nRow > 2) { + m1.m20 = values[2][0]; + } + } + } + if (nCol > 1) { + if (nRow > 0) { + m1.m01 = values[0][1]; + if (nRow > 1) { + m1.m11 = values[1][1]; + if (nRow > 2) { + m1.m21 = values[2][1]; + } + } + } + if (nCol > 2) { + if (nRow > 0) { + m1.m02 = values[0][2]; + if (nRow > 1) { + m1.m12 = values[1][2]; + if (nRow > 2) { + m1.m22 = values[2][2]; + } + } + } + } + } + } + } else { + m1.m00 = values[0][0]; + m1.m01 = values[0][1]; + m1.m02 = values[0][2]; + + m1.m10 = values[1][0]; + m1.m11 = values[1][1]; + m1.m12 = values[1][2]; + + m1.m20 = values[2][0]; + m1.m21 = values[2][1]; + m1.m22 = values[2][2]; + } + } + + /** + * Places the values in the upper 4x4 of this GMatrix into the matrix m1. + * + * @param m1 + * The matrix that will hold the new values + */ + public final void get(Matrix4f m1) { + + if (nRow < 4 || nCol < 4) { + m1.setZero(); + if (nCol > 0) { + if (nRow > 0) { + m1.m00 = (float) values[0][0]; + if (nRow > 1) { + m1.m10 = (float) values[1][0]; + if (nRow > 2) { + m1.m20 = (float) values[2][0]; + if (nRow > 3) { + m1.m30 = (float) values[3][0]; + } + } + } + } + if (nCol > 1) { + if (nRow > 0) { + m1.m01 = (float) values[0][1]; + if (nRow > 1) { + m1.m11 = (float) values[1][1]; + if (nRow > 2) { + m1.m21 = (float) values[2][1]; + if (nRow > 3) { + m1.m31 = (float) values[3][1]; + } + } + } + } + if (nCol > 2) { + if (nRow > 0) { + m1.m02 = (float) values[0][2]; + if (nRow > 1) { + m1.m12 = (float) values[1][2]; + if (nRow > 2) { + m1.m22 = (float) values[2][2]; + if (nRow > 3) { + m1.m32 = (float) values[3][2]; + } + } + } + } + if (nCol > 3) { + if (nRow > 0) { + m1.m03 = (float) values[0][3]; + if (nRow > 1) { + m1.m13 = (float) values[1][3]; + if (nRow > 2) { + m1.m23 = (float) values[2][3]; + if (nRow > 3) { + m1.m33 = (float) values[3][3]; + } + } + } + } + } + } + } + } + } else { + m1.m00 = (float) values[0][0]; + m1.m01 = (float) values[0][1]; + m1.m02 = (float) values[0][2]; + m1.m03 = (float) values[0][3]; + + m1.m10 = (float) values[1][0]; + m1.m11 = (float) values[1][1]; + m1.m12 = (float) values[1][2]; + m1.m13 = (float) values[1][3]; + + m1.m20 = (float) values[2][0]; + m1.m21 = (float) values[2][1]; + m1.m22 = (float) values[2][2]; + m1.m23 = (float) values[2][3]; + + m1.m30 = (float) values[3][0]; + m1.m31 = (float) values[3][1]; + m1.m32 = (float) values[3][2]; + m1.m33 = (float) values[3][3]; + } + } + + /** + * Places the values of the specified column into the array parameter. + * + * @param col + * the target column number + * @param array + * the array into which the column values will be placed + */ + public final void getColumn(int col, double[] array) { + for (int i = 0; i < nRow; i++) { + array[i] = values[i][col]; + } + + } + + /** + * Places the values of the specified column into the vector parameter. + * + * @param col + * the target column number + * @param vector + * the vector into which the column values will be placed + */ + public final void getColumn(int col, GVector vector) { + if (vector.size() < nRow) { + vector.setSize(nRow); + } + + for (int i = 0; i < nRow; i++) { + vector.values[i] = values[i][col]; + } + } + + /** + * Retrieves the value at the specified row and column of this matrix. + * + * @param row + * the row number to be retrieved (zero indexed) + * @param column + * the column number to be retrieved (zero indexed) + * @return the value at the indexed element + */ + public final double getElement(int row, int column) { + return (values[row][column]); + } + + /** + * Returns the number of colmuns in this matrix. + * + * @return number of columns in this matrix + */ + public final int getNumCol() { + return (nCol); + } + + /** + * Returns the number of rows in this matrix. + * + * @return number of rows in this matrix + */ + public final int getNumRow() { + return (nRow); + } + + /** + * Places the values of the specified row into the array parameter. + * + * @param row + * the target row number + * @param array + * the array into which the row values will be placed + */ + public final void getRow(int row, double[] array) { + for (int i = 0; i < nCol; i++) { + array[i] = values[row][i]; + } + } + + /** + * Places the values of the specified row into the vector parameter. + * + * @param row + * the target row number + * @param vector + * the vector into which the row values will be placed + */ + public final void getRow(int row, GVector vector) { + if (vector.size() < nCol) { + vector.setSize(nCol); + } + + for (int i = 0; i < nCol; i++) { + vector.values[i] = values[row][i]; + } + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different GMatrix objects with identical data values (i.e., + * GMatrix.equals returns true) will return the same hash number. Two + * GMatrix 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() { + long bits = 1L; + + bits = 31L * bits + nRow; + bits = 31L * bits + nCol; + + for (int i = 0; i < nRow; i++) { + for (int j = 0; j < nCol; j++) { + bits = 31L * bits + VecMathUtil.doubleToLongBits(values[i][j]); + } + } + + return (int) (bits ^ (bits >> 32)); + } + + /** + * Sets this GMatrix to the identity matrix. + */ + public final void identity() { + int i, j; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = 0.0; + } + } + + int l; + if (nRow < nCol) { + l = nRow; + } else { + l = nCol; + } + + for (i = 0; i < l; i++) { + values[i][i] = 1.0; + } + } + + /** + * Subtracts this matrix from the identity matrix and puts the values back + * into this (this = I - this). + */ + public final void identityMinus() { + int i, j; + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = -values[i][j]; + } + } + + int l; + if (nRow < nCol) { + l = nRow; + } else { + l = nCol; + } + + for (i = 0; i < l; i++) { + values[i][i] += 1.0; + } + } + + /** + * Inverts this matrix in place. + */ + public final void invert() { + invertGeneral(this); + } + + /** + * Inverts matrix m1 and places the new values into this matrix. Matrix m1 + * is not modified. + * + * @param m1 + * the matrix to be inverted + */ + public final void invert(GMatrix m1) { + invertGeneral(m1); + } + + /** + * General invert routine. Inverts m1 and places the result in "this". Note + * that this routine handles both the "this" version and the non-"this" + * version. + * + * Also note that since this routine is slow anyway, we won't worry about + * allocating a little bit of garbage. + */ + final void invertGeneral(GMatrix m1) { + int size = m1.nRow * m1.nCol; + double temp[] = new double[size]; + double result[] = new double[size]; + int row_perm[] = new int[m1.nRow]; + int[] even_row_exchange = new int[1]; + int i, j; + + // Use LU decomposition and backsubstitution code specifically + // for floating-point nxn matrices. + if (m1.nRow != m1.nCol) { + // Matrix is either under or over determined + throw new MatrixSizeException(); + } + + // Copy source matrix to temp + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + temp[i * nCol + j] = m1.values[i][j]; + } + } + + // Calculate LU decomposition: Is the matrix singular? + if (!decomposeLU(m1.nRow, temp, row_perm, even_row_exchange)) { + // Matrix has no inverse + throw new SingularMatrixException(); + } + + // Perform back substitution on the identity matrix + for (i = 0; i < size; i++) { + result[i] = 0.0; + } + + for (i = 0; i < nCol; i++) { + result[i + i * nCol] = 1.0; + } + + backSubstituteLU(m1.nRow, temp, row_perm, result); + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = result[i * nCol + j]; + } + } + } + + /** + * Sets the value of this matrix to the result of multiplying itself with + * matrix m1 (this = this * m1). + * + * @param m1 + * the other matrix + */ + public final void mul(GMatrix m1) { + int i, j, k; + + if (nCol != m1.nRow || nCol != m1.nCol) { + throw new MatrixSizeException(); + } + + double[][] tmp = new double[nRow][nCol]; + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + tmp[i][j] = 0.0; + for (k = 0; k < nCol; k++) { + tmp[i][j] += values[i][k] * m1.values[k][j]; + } + } + } + + values = tmp; + } + + /** + * Sets the value of this matrix to the result of multiplying the two + * argument matrices together (this = m1 * m2). + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void mul(GMatrix m1, GMatrix m2) { + int i, j, k; + + if (m1.nCol != m2.nRow || nRow != m1.nRow || nCol != m2.nCol) { + throw new MatrixSizeException(); + } + + double[][] tmp = new double[nRow][nCol]; + + for (i = 0; i < m1.nRow; i++) { + for (j = 0; j < m2.nCol; j++) { + tmp[i][j] = 0.0; + for (k = 0; k < m1.nCol; k++) { + tmp[i][j] += m1.values[i][k] * m2.values[k][j]; + } + } + } + + values = tmp; + } + + /** + * Computes the outer product of the two vectors; multiplies the the first + * vector by the transpose of the second vector and places the matrix result + * into this matrix. This matrix must be be as big or bigger than + * getSize(v1)xgetSize(v2). + * + * @param v1 + * the first vector, treated as a row vector + * @param v2 + * the second vector, treated as a column vector + */ + public final void mul(GVector v1, GVector v2) { + int i, j; + + if (nRow < v1.size()) { + throw new MatrixSizeException(); + } + + if (nCol < v2.size()) { + throw new MatrixSizeException(); + } + + for (i = 0; i < v1.size(); i++) { + for (j = 0; j < v2.size(); j++) { + values[i][j] = v1.values[i] * v2.values[j]; + } + } + } + + /** + * Multiplies the transpose of matrix m1 times the transpose of matrix m2, + * and places the result into this. + * + * @param m1 + * The matrix on the left hand side of the multiplication + * @param m2 + * The matrix on the right hand side of the multiplication + */ + public final void mulTransposeBoth(GMatrix m1, GMatrix m2) { + int i, j, k; + + if (m1.nRow != m2.nCol || nRow != m1.nCol || nCol != m2.nRow) { + throw new MatrixSizeException(); + } + + if (m1 == this || m2 == this) { + double[][] tmp = new double[nRow][nCol]; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + tmp[i][j] = 0.0; + for (k = 0; k < m1.nRow; k++) { + tmp[i][j] += m1.values[k][i] * m2.values[j][k]; + } + } + } + values = tmp; + } else { + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = 0.0; + for (k = 0; k < m1.nRow; k++) { + values[i][j] += m1.values[k][i] * m2.values[j][k]; + } + } + } + } + } + + /** + * Multiplies the transpose of matrix m1 times matrix m2, and places the + * result into this. + * + * @param m1 + * The matrix on the left hand side of the multiplication + * @param m2 + * The matrix on the right hand side of the multiplication + */ + public final void mulTransposeLeft(GMatrix m1, GMatrix m2) { + int i, j, k; + + if (m1.nRow != m2.nRow || nCol != m2.nCol || nRow != m1.nCol) { + throw new MatrixSizeException(); + } + + if (m1 == this || m2 == this) { + double[][] tmp = new double[nRow][nCol]; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + tmp[i][j] = 0.0; + for (k = 0; k < m1.nRow; k++) { + tmp[i][j] += m1.values[k][i] * m2.values[k][j]; + } + } + } + values = tmp; + } else { + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = 0.0; + for (k = 0; k < m1.nRow; k++) { + values[i][j] += m1.values[k][i] * m2.values[k][j]; + } + } + } + } + } + + /** + * Multiplies matrix m1 times the transpose of matrix m2, and places the + * result into this. + * + * @param m1 + * The matrix on the left hand side of the multiplication + * @param m2 + * The matrix on the right hand side of the multiplication + */ + public final void mulTransposeRight(GMatrix m1, GMatrix m2) { + int i, j, k; + + if (m1.nCol != m2.nCol || nCol != m2.nRow || nRow != m1.nRow) { + throw new MatrixSizeException(); + } + + if (m1 == this || m2 == this) { + double[][] tmp = new double[nRow][nCol]; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + tmp[i][j] = 0.0; + for (k = 0; k < m1.nCol; k++) { + tmp[i][j] += m1.values[i][k] * m2.values[j][k]; + } + } + } + values = tmp; + } else { + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = 0.0; + for (k = 0; k < m1.nCol; k++) { + values[i][j] += m1.values[i][k] * m2.values[j][k]; + } + } + } + } + + } + + /** + * Negates the value of this matrix: this = -this. + */ + public final void negate() { + int i, j; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = -values[i][j]; + } + } + } + + /** + * Sets the value of this matrix equal to the negation of of the GMatrix + * parameter. + * + * @param m1 + * The source matrix + */ + public final void negate(GMatrix m1) { + int i, j; + if (nRow != m1.nRow || nCol != m1.nCol) { + throw new MatrixSizeException(); + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = -m1.values[i][j]; + } + } + } + + /** + * Sets the value of this matrix to the values found in the array parameter. + * The values are copied in one row at a time, in row major fashion. The + * array should be at least equal in length to the number of matrix rows + * times the number of matrix columns in this matrix. + * + * @param matrix + * the row major source array + */ + public final void set(double[] matrix) { + int i, j; + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = matrix[nCol * i + j]; + } + } + } + + /** + * Sets the value of this matrix to the values found in matrix m1. + * + * @param m1 + * the source matrix + */ + public final void set(GMatrix m1) { + int i, j; + + if (nRow < m1.nRow || nCol < m1.nCol) { + nRow = m1.nRow; + nCol = m1.nCol; + values = new double[nRow][nCol]; + } + + for (i = 0; i < Math.min(nRow, m1.nRow); i++) { + for (j = 0; j < Math.min(nCol, m1.nCol); j++) { + values[i][j] = m1.values[i][j]; + } + } + + for (i = m1.nRow; i < nRow; i++) { // pad rest or matrix with zeros + for (j = m1.nCol; j < nCol; j++) { + values[i][j] = 0.0; + } + } + } + + /** + * Sets the value of this matrix to that of the Matrix3d provided. + * + * @param m1 + * the matrix + */ + public final void set(Matrix3d m1) { + if (nRow < 3 || nCol < 3) { + values = new double[3][3]; + nRow = 3; + nCol = 3; + } + + values[0][0] = m1.m00; + values[0][1] = m1.m01; + values[0][2] = m1.m02; + + values[1][0] = m1.m10; + values[1][1] = m1.m11; + values[1][2] = m1.m12; + + values[2][0] = m1.m20; + values[2][1] = m1.m21; + values[2][2] = m1.m22; + + for (int i = 3; i < nRow; i++) { // pad rest or matrix with zeros + for (int j = 3; j < nCol; j++) { + values[i][j] = 0.0; + } + } + + } + + /** + * Sets the value of this matrix to that of the Matrix4f provided. + * + * @param m1 + * the matrix + */ + public final void set(Matrix4f m1) { + if (nRow < 4 || nCol < 4) { + values = new double[4][4]; + nRow = 4; + nCol = 4; + } + + values[0][0] = m1.m00; + values[0][1] = m1.m01; + values[0][2] = m1.m02; + values[0][3] = m1.m03; + + values[1][0] = m1.m10; + values[1][1] = m1.m11; + values[1][2] = m1.m12; + values[1][3] = m1.m13; + + values[2][0] = m1.m20; + values[2][1] = m1.m21; + values[2][2] = m1.m22; + values[2][3] = m1.m23; + + values[3][0] = m1.m30; + values[3][1] = m1.m31; + values[3][2] = m1.m32; + values[3][3] = m1.m33; + + for (int i = 4; i < nRow; i++) { // pad rest or matrix with zeros + for (int j = 4; j < nCol; j++) { + values[i][j] = 0.0; + } + } + } + + /** + * Copy the values from the array into the specified column of this matrix. + * + * @param col + * the column of this matrix into which the array values will be + * copied + * @param array + * the source array + */ + public final void setColumn(int col, double[] array) { + for (int i = 0; i < nRow; i++) { + values[i][col] = array[i]; + } + } + + /** + * Copy the values from the vector into the specified column of this matrix. + * + * @param col + * the column of this matrix into which the array values will be + * copied + * @param vector + * the source vector + */ + public final void setColumn(int col, GVector vector) { + for (int i = 0; i < nRow; i++) { + values[i][col] = vector.values[i]; + } + + } + + /** + * Modifies the value at the specified row and column of this matrix. + * + * @param row + * the row number to be modified (zero indexed) + * @param column + * the column number to be modified (zero indexed) + * @param value + * the new matrix element value + */ + public final void setElement(int row, int column, double value) { + values[row][column] = value; + } + + /** + * Copy the values from the array into the specified row of this matrix. + * + * @param row + * the row of this matrix into which the array values will be + * copied. + * @param array + * the source array + */ + public final void setRow(int row, double[] array) { + for (int i = 0; i < nCol; i++) { + values[row][i] = array[i]; + } + } + + /** + * Copy the values from the vector into the specified row of this matrix. + * + * @param row + * the row of this matrix into which the array values will be + * copied + * @param vector + * the source vector + */ + public final void setRow(int row, GVector vector) { + for (int i = 0; i < nCol; i++) { + values[row][i] = vector.values[i]; + } + } + + /** + * Sets this matrix to a uniform scale matrix; all of the values are reset. + * + * @param scale + * The new scale value + */ + public final void setScale(double scale) { + int i, j, l; + + if (nRow < nCol) { + l = nRow; + } else { + l = nCol; + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = 0.0; + } + } + + for (i = 0; i < l; i++) { + values[i][i] = scale; + } + } + + /** + * Changes the size of this matrix dynamically. If the size is increased no + * data values will be lost. If the size is decreased, only those data + * values whose matrix positions were eliminated will be lost. + * + * @param nRow + * number of desired rows in this matrix + * @param nCol + * number of desired columns in this matrix + */ + public final void setSize(int nRow, int nCol) { + double[][] tmp = new double[nRow][nCol]; + int i, j, maxRow, maxCol; + + if (this.nRow < nRow) { + maxRow = this.nRow; + } else { + maxRow = nRow; + } + + if (this.nCol < nCol) { + maxCol = this.nCol; + } else { + maxCol = nCol; + } + + for (i = 0; i < maxRow; i++) { + for (j = 0; j < maxCol; j++) { + tmp[i][j] = values[i][j]; + } + } + + this.nRow = nRow; + this.nCol = nCol; + + values = tmp; + } + + /** + * Sets all the values in this matrix to zero. + */ + public final void setZero() { + int i, j; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = 0.0; + } + } + } + + /** + * Sets the value of this matrix to the matrix difference of itself and + * matrix m1 (this = this - m1). + * + * @param m1 + * the other matrix + */ + public final void sub(GMatrix m1) { + int i, j; + if (nRow != m1.nRow) { + throw new MatrixSizeException(); + } + + if (nCol != m1.nCol) { + throw new MatrixSizeException(); + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = values[i][j] - m1.values[i][j]; + } + } + } + + /** + * Sets the value of this matrix to the matrix difference of matrices m1 and + * m2 (this = m1 - m2). + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void sub(GMatrix m1, GMatrix m2) { + int i, j; + if (m2.nRow != m1.nRow) { + throw new MatrixSizeException(); + } + + if (m2.nCol != m1.nCol) { + throw new MatrixSizeException(); + } + + if (nRow != m1.nRow || nCol != m1.nCol) { + throw new MatrixSizeException(); + } + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = m1.values[i][j] - m2.values[i][j]; + } + } + } + + /** + * Returns a string that contains the values of this GMatrix. + * + * @return the String representation + */ + public String toString() { + StringBuffer buffer = new StringBuffer(nRow * nCol * 8); + + int i, j; + + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + buffer.append(values[i][j]).append(" "); + } + buffer.append("\n"); + } + + return buffer.toString(); + } + + /** + * Returns the trace of this matrix. + * + * @return the trace of this matrix + */ + public final double trace() { + int i, l; + double t; + + if (nRow < nCol) { + l = nRow; + } else { + l = nCol; + } + + t = 0.0; + for (i = 0; i < l; i++) { + t += values[i][i]; + } + return t; + } + + /** + * Transposes this matrix in place. + */ + public final void transpose() { + int i, j; + + if (nRow != nCol) { + double[][] tmp; + i = nRow; + nRow = nCol; + nCol = i; + tmp = new double[nRow][nCol]; + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + tmp[i][j] = values[j][i]; + } + } + values = tmp; + } else { + double swap; + for (i = 0; i < nRow; i++) { + for (j = 0; j < i; j++) { + swap = values[i][j]; + values[i][j] = values[j][i]; + values[j][i] = swap; + } + } + } + } + + /** + * Places the matrix values of the transpose of matrix m1 into this matrix. + * + * @param m1 + * the matrix to be transposed (but not modified) + */ + public final void transpose(GMatrix m1) { + int i, j; + + if (nRow != m1.nCol || nCol != m1.nRow) { + throw new MatrixSizeException(); + } + + if (m1 != this) { + for (i = 0; i < nRow; i++) { + for (j = 0; j < nCol; j++) { + values[i][j] = m1.values[j][i]; + } + } + } else { + transpose(); + } + } + +} diff --git a/src/main/java/toxi/geom/GVector.java b/src/main/java/toxi/geom/GVector.java new file mode 100644 index 0000000..5e8844b --- /dev/null +++ b/src/main/java/toxi/geom/GVector.java @@ -0,0 +1,879 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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; + +/** + * A double precision, general, dynamically-resizable, one-dimensional vector + * class. + */ +public class GVector implements java.io.Serializable, Cloneable { + + private int length; + public double[] values; + + static final long serialVersionUID = 1L; + + /** + * Constructs a new GVector of the specified length and initializes it by + * copying the specified number of elements from the specified array. The + * array must contain at least <code>length</code> elements (i.e., + * <code>vector.length</code> >= <code>length</code>. The length of this new + * GVector is set to the specified length. + * + * @param vector + * The array from which the values will be copied. + * @param length + * The number of values copied from the array. + */ + public GVector(double vector[], int length) { + this.length = length; + values = new double[length]; + for (int i = 0; i < length; i++) { + values[i] = vector[i]; + } + } + + /** + * Constructs a new GVector from the specified array elements. The length of + * this GVector is set to the length of the specified array. The array + * elements are copied into this new GVector. + * + * @param vector + * the values for the new GVector. + */ + public GVector(double[] vector) { + length = vector.length; + values = new double[vector.length]; + for (int i = 0; i < length; i++) { + values[i] = vector[i]; + } + } + + /** + * Constructs a new GVector from the specified vector. The vector elements + * are copied into this new GVector. + * + * @param vector + * the source GVector for this new GVector. + */ + public GVector(GVector vector) { + values = new double[vector.length]; + length = vector.length; + for (int i = 0; i < length; i++) { + values[i] = vector.values[i]; + } + } + + /** + * Constructs a new GVector of the specified length with all vector elements + * initialized to 0. + * + * @param length + * the number of elements in this GVector. + */ + public GVector(int length) { + this.length = length; + values = new double[length]; + for (int i = 0; i < length; i++) { + values[i] = 0.0; + } + } + + /** + * Constructs a new GVector and copies the initial values from the specified + * tuple. + * + * @param v + * the source for the new GVector's initial values + */ + public GVector(ReadonlyVec2D v) { + values = new double[] { + v.x(), v.y() + }; + length = 2; + } + + /** + * Constructs a new GVector and copies the initial values from the specified + * tuple. + * + * @param v + * the source for the new GVector's initial values + */ + public GVector(roVec3D v) { + values = new double[] { + v.x(), v.y(), v.z() + }; + length = 3; + } + + /** + * Constructs a new GVector and copies the initial values from the specified + * tuple. + * + * @param v + * the source for the new GVector's initial values + */ + public GVector(ReadonlyVec4D 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 + * sized). Returns result as new vector. + * + * @param v + * @return new vector + */ + public final GVector add(GVector v) { + if (length != v.length) { + throw new MatrixSizeException(); + } + double[] tmp = new double[length]; + for (int i = 0; i < length; i++) { + tmp[i] = values[i] + v.values[i]; + } + return new GVector(tmp); + } + + /** + * Sets the value of this vector to sum of itself and the specified vector + * + * @param vector + * the second vector + * @return itself + */ + public final GVector addSelf(GVector vector) { + if (length != vector.length) { + throw new MatrixSizeException(); + } + for (int i = 0; i < length; i++) { + this.values[i] += vector.values[i]; + } + return this; + } + + /** + * Returns the (n-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(GVector v) { + return (Math.acos(this.dot(v) / (this.magnitude() * v.magnitude()))); + } + + /** + * LU Decomposition Back Solve; this method takes the LU matrix and the + * permutation vector produced by the GMatrix method LUD and solves the + * equation (LU)*x = b by placing the solution vector x into this vector. + * This vector should be the same length or longer than b. + * + * @param LU + * The matrix into which the lower and upper decompostions have + * been placed + * @param b + * The b vector in the equation (LU)*x = b + * @param permutation + * The row permuations that were necessary to produce the LU + * matrix parameter + */ + public final void backSolveLUD(GMatrix LU, GVector b, GVector permutation) { + int size = LU.nRow * LU.nCol; + + double[] temp = new double[size]; + double[] result = new double[size]; + int[] row_perm = new int[b.size()]; + int i, j; + + if (LU.nRow != b.size()) { + throw new MatrixSizeException(); + } + + if (LU.nRow != permutation.size()) { + throw new MatrixSizeException(); + } + + if (LU.nRow != LU.nCol) { + throw new MatrixSizeException(); + } + + for (i = 0; i < LU.nRow; i++) { + for (j = 0; j < LU.nCol; j++) { + temp[i * LU.nCol + j] = LU.values[i][j]; + } + } + + for (i = 0; i < LU.nRow; i++) { + result[i * LU.nCol] = b.values[i]; + } + for (i = 0; i < LU.nCol; i++) { + row_perm[i] = (int) permutation.values[i]; + } + + GMatrix.backSubstituteLU(LU.nRow, temp, row_perm, result); + + for (i = 0; i < LU.nRow; i++) { + this.values[i] = result[i * LU.nCol]; + } + } + + /** + * Solves for x in Ax = b, where x is this vector (nx1), A is mxn, b is mx1, + * and A = U*W*transpose(V); U,W,V must be precomputed and can be found by + * taking the singular value decomposition (SVD) of A using the method SVD + * found in the GMatrix class. + * + * @param U + * The U matrix produced by the GMatrix method SVD + * @param W + * The W matrix produced by the GMatrix method SVD + * @param V + * The V matrix produced by the GMatrix method SVD + * @param b + * The b vector in the linear equation Ax = b + */ + public final void backSolveSVD(GMatrix U, GMatrix W, GMatrix V, GVector b) { + if (!(U.nRow == b.size() && U.nRow == U.nCol && U.nRow == W.nRow)) { + throw new MatrixSizeException(); + } + if (!(W.nCol == values.length && W.nCol == V.nCol && W.nCol == V.nRow)) { + throw new MatrixSizeException(); + } + GMatrix tmp = new GMatrix(U.nRow, W.nCol); + tmp.mul(U, V); + tmp.mulTransposeRight(U, W); + tmp.invert(); + mul(tmp, b); + } + + /** + * Creates a new object of the same class as this object. + * + * @return a clone of this instance. + * @exception OutOfMemoryError + * if there is not enough memory. + * @see Cloneable + * @since vecmath 1.3 + */ + public Object clone() { + GVector v = null; + try { + v = (GVector) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + v.values = new double[length]; + for (int i = 0; i < length; i++) { + v.values[i] = values[i]; + } + return v; + } + + /** + * Returns the dot product of this vector and vector v. + * + * @param v + * the other vector + * @return the dot product of this and v + */ + public final double dot(GVector v) { + if (length != v.length) { + throw new MatrixSizeException(); + } + double result = 0.0; + for (int i = 0; i < length; i++) { + result += values[i] * v.values[i]; + } + return result; + } + + /** + * Returns true if all of the data members of GVector vector1 are equal to + * the corresponding data members in this GVector. + * + * @param vector1 + * The vector with which the comparison is made. + * @return true or false + */ + public boolean equals(GVector vector1) { + try { + if (length != vector1.length) { + return false; + } + for (int i = 0; i < length; i++) { + if (values[i] != vector1.values[i]) { + return false; + } + } + return true; + } catch (NullPointerException e) { + return false; + } + } + + /** + * Returns true if the Object o1 is of type GMatrix and all of the data + * members of o1 are equal to the corresponding data members in this + * GMatrix. + * + * @param o1 + * The object with which the comparison is made. + * @return true or false + */ + public boolean equals(Object o1) { + try { + GVector v2 = (GVector) o1; + if (length != v2.length) { + return false; + } + for (int i = 0; i < length; i++) { + if (values[i] != v2.values[i]) { + return false; + } + } + return true; + } catch (ClassCastException e) { + return false; + } catch (NullPointerException e) { + return false; + } + + } + + /** + * Returns true if the L-infinite distance between this vector and vector v + * is less than or equal to the tolerance parameter, otherwise returns + * false. The L-infinite distance is equal to MAX[abs(x1-x2), abs(y1-y2), . + * . . ]. + * + * @param v + * The vector to be compared to this vector + * @param tolerance + * the threshold value + */ + public boolean equalsWithTolerance(GVector v, double tolerance) { + try { + double diff; + if (length != v.length) { + return false; + } + for (int i = 0; i < length; i++) { + diff = values[i] - v.values[i]; + if ((diff < 0 ? -diff : diff) > tolerance) { + return false; + } + } + return true; + } catch (NullPointerException e) { + return false; + } + } + + /** + * Retrieves the value at the specified index value of this vector. + * + * @param index + * the index of the element to retrieve (zero indexed) + * @return the value at the indexed element + */ + public final double get(int index) { + return values[index]; + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different GVector objects with identical data values (i.e., + * GVector.equals returns true) will return the same hash number. Two + * GVector 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() { + long bits = 1L; + for (int i = 0; i < length; i++) { + bits = 31L * bits + VecMathUtil.doubleToLongBits(values[i]); + } + return (int) (bits ^ (bits >> 32)); + } + + /** + * Linearly interpolates this vector to the target vector and places the + * result into a new instance: result = this + (target-this)*alpha. The + * target vector needs to be equal sized. + * + * @param v + * the target vector + * @param alpha + * the alpha interpolation parameter + * @return result as new vector + */ + public final GVector interpolateTo(GVector v, double alpha) { + if (length != v.length) { + throw new MatrixSizeException(); + } + return new GVector(this).interpolateToSelf(v, alpha); + } + + /** + * Interpolates the vector towards the given target vector, using the given + * {@link InterpolateStrategy}. The target vector needs to be equal sized. + * + * @param v + * target vector + * @param alpha + * interpolation factor (should be in the range 0..1) + * @param strategy + * InterpolateStrategy instance + * + * @return result as new vector + */ + public final GVector interpolateTo(GVector v, double alpha, + InterpolateStrategy strategy) { + if (length != v.length) { + throw new MatrixSizeException(); + } + return new GVector(this).interpolateToSelf(v, alpha, strategy); + } + + /** + * Linearly interpolates this vector to the target vector and places result + * in this vector. result = this + (target-this)*alpha. The target vector + * needs to be equal sized. + * + * @param v + * the target vector + * @param alpha + * the alpha interpolation parameter + */ + public final GVector interpolateToSelf(GVector v, double alpha) { + if (v.length != length) { + throw new MatrixSizeException(); + } + for (int i = 0; i < length; i++) { + values[i] += (v.values[i] - values[i]) * alpha; + } + return this; + } + + /** + * Interpolates the vector towards the given target vector, using the given + * {@link InterpolateStrategy}. The target vector needs to be equal sized. + * + * @param v + * target vector + * @param alpha + * interpolation factor (should be in the range 0..1) + * @param strategy + * InterpolateStrategy instance + * + * @return itself, result overrides current vector + */ + public final GVector interpolateToSelf(GVector v, double alpha, + InterpolateStrategy strategy) { + if (v.length != length) { + throw new MatrixSizeException(); + } + for (int i = 0; i < length; i++) { + values[i] = strategy.interpolate(values[i], v.values[i], alpha); + } + return this; + } + + /** + * Negates the value of this vector: this = -this. + */ + public final void invert() { + for (int i = 0; i < length; i++) { + this.values[i] *= -1.0; + } + } + + /** + * Returns the square root of the sum of the squares of this vector (its + * length in n-dimensional space). + * + * @return length of this vector + */ + + public final double magnitude() { + double sq = 0.0; + for (int i = 0; i < length; i++) { + sq += values[i] * values[i]; + } + return Math.sqrt(sq); + } + + /** + * Returns the sum of the squares of this vector (its length squared in + * n-dimensional space). + * + * @return length squared of this vector + */ + public final double magSquared() { + double sq = 0.0; + for (int i = 0; i < length; i++) { + sq += values[i] * values[i]; + } + return sq; + } + + /** + * Multiplies matrix m1 times Vector v1 and places the result into this + * vector (this = m1*v1). + * + * @param m1 + * The matrix in the multiplication + * @param v1 + * The vector that is multiplied + */ + public final void mul(GMatrix m1, GVector v1) { + if (m1.getNumCol() != v1.length) { + throw new MatrixSizeException(); + } + + if (length != m1.getNumRow()) { + throw new MatrixSizeException(); + } + + double v[]; + if (v1 != this) { + v = v1.values; + } else { + v = values.clone(); + } + + for (int j = length - 1; j >= 0; j--) { + values[j] = 0.0; + for (int i = v1.length - 1; i >= 0; i--) { + values[j] += m1.values[j][i] * v[i]; + } + } + } + + /** + * Multiplies the transpose of vector v1 (ie, v1 becomes a row vector with + * respect to the multiplication) times matrix m1 and places the result into + * this vector (this = transpose(v1)*m1). The result is technically a row + * vector, but the GVector class only knows about column vectors, and so the + * result is stored as a column vector. + * + * @param m1 + * The matrix in the multiplication + * @param v1 + * The vector that is temporarily transposed + */ + public final void mul(GVector v1, GMatrix m1) { + if (m1.getNumRow() != v1.length) { + throw new MatrixSizeException(); + } + + if (length != m1.getNumCol()) { + throw new MatrixSizeException(); + } + + double v[]; + if (v1 != this) { + v = v1.values; + } else { + v = values.clone(); + } + + for (int j = length - 1; j >= 0; j--) { + values[j] = 0.0; + for (int i = v1.length - 1; i >= 0; i--) { + values[j] += m1.values[i][j] * v[i]; + } + } + } + + /** + * Normalizes this vector in place. + */ + public final void normalize() { + double mag = magnitude(); + if (mag > MathUtils.EPS) { + double invMag = 1.0 / mag; + for (int i = 0; i < length; i++) { + values[i] = values[i] * invMag; + } + } + } + + /** + * Scales this vector by the scale factor s and returns result as new + * vector. + * + * @param s + * the scalar value + * @return new vector + */ + public final GVector scale(double s) { + double[] tmp = new double[length]; + for (int i = 0; i < length; i++) { + tmp[i] = values[i] * s; + } + return new GVector(tmp); + } + + /** + * Scales the values of this vector with the values of the given vector + * vector (this = this * vector). Returns result as new vector. + * + * @param v + * scale vector + * @return new vector + */ + public final GVector scale(GVector v) { + if (length != v.length) { + throw new MatrixSizeException(); + } + double[] tmp = new double[length]; + for (int i = 0; i < length; i++) { + tmp[i] = values[i] * v.values[i]; + } + return new GVector(tmp); + } + + /** + * Scales this vector by the scale factor s. + * + * @param s + * the scalar value + * @return itself + */ + public final GVector scaleSelf(double s) { + for (int i = 0; i < length; i++) { + values[i] = values[i] * s; + } + return this; + } + + /** + * Scales the values of this vector with the values of the given vector + * vector (this = this * vector). + * + * @param v + * scale vector + * @return itself + */ + public final GVector scaleSelf(GVector v) { + if (length != v.length) { + throw new MatrixSizeException(); + } + for (int i = 0; i < length; i++) { + this.values[i] *= v.values[i]; + } + return this; + } + + /** + * Sets the values of this vector to the values found in the array + * parameter. If the array is shorter than the number of values in this + * vector the remaining values are zeroed. If the array is longer, only the + * first values up to to the vector length are copied. + * + * @param vector + * the source array + */ + public final GVector set(double[] vector) { + int i; + if (vector.length >= length) { + for (i = 0; i < length; i++) { + values[i] = vector[i]; + } + } else { + for (i = 0; i < vector.length; i++) { + values[i] = vector[i]; + } + for (i = vector.length; i < length; i++) { + values[i] = 0.0; + } + } + return this; + } + + /** + * Sets the value of this vector to the values found in vector vector. + * + * @param vector + * the source vector + */ + public final GVector set(GVector vector) { + return set(vector.values); + } + + /** + * Sets the value of this vector to the values in tuple + * + * @param tuple + * the source for the new GVector's new values + */ + public final GVector set(ReadonlyVec2D tuple) { + return set(new double[] { + tuple.x(), tuple.y() + }); + } + + /** + * Sets the value of this vector to the values in tuple + * + * @param tuple + * the source for the new GVector's new values + */ + public final GVector set(roVec3D tuple) { + return set(new double[] { + tuple.x(), tuple.y(), tuple.z() + }); + } + + /** + * Sets the value of this vector to the values in tuple + * + * @param tuple + * the source for the new GVector's new values + * @return itself + */ + public final GVector set(ReadonlyVec4D tuple) { + return set(new double[] { + tuple.x(), tuple.y(), tuple.w() + }); + } + + /** + * Modifies the value at the specified index of this vector. + * + * @param index + * the index if the element to modify (zero indexed) + * @param value + * the new vector element value + */ + public final GVector setElement(int index, double value) { + values[index] = value; + return this; + } + + /** + * Changes the size of this vector dynamically. If the size is increased no + * data values will be lost. If the size is decreased, only those data + * values whose vector positions were eliminated will be lost. + * + * @param length + * number of desired elements in this vector + */ + public final GVector setSize(int length) { + double[] tmp = new double[length]; + int max; + if (this.length < length) { + max = this.length; + } else { + max = length; + } + for (int i = 0; i < max; i++) { + tmp[i] = values[i]; + } + this.length = length; + values = tmp; + return this; + } + + /** + * Returns the number of elements in this vector. + * + * @return number of elements in this vector + */ + public final int size() { + return values.length; + } + + /** + * Creates the vector difference of this vector and the given one (must be + * equal sized). Returns result as new vector. + * + * @param v + * @return new vector + */ + public final GVector sub(GVector v) { + if (length != v.length) { + throw new MatrixSizeException(); + } + double[] tmp = new double[length]; + for (int i = 0; i < length; i++) { + tmp[i] = values[i] - v.values[i]; + } + return new GVector(tmp); + } + + /** + * Sets the value of this vector to the vector difference of itself and + * vector (this = this - vector). + * + * @param vector + * the other vector + */ + public final GVector subSelf(GVector vector) { + if (length != vector.length) { + throw new MatrixSizeException(); + } + for (int i = 0; i < length; i++) { + this.values[i] -= vector.values[i]; + } + return this; + } + + /** + * Returns a string that contains the values of this GVector. + * + * @return the String representation + */ + public String toString() { + StringBuilder buffer = new StringBuilder(length * 8); + for (int i = 0; i < length; i++) { + buffer.append(values[i]).append(" "); + } + return buffer.toString(); + } + + /** + * Sets all the values in this vector to zero. + */ + public final GVector zero() { + for (int i = 0; i < length; i++) { + this.values[i] = 0.0; + } + return this; + } +} diff --git a/src/main/java/toxi/geom/GlobalGridTesselator.java b/src/main/java/toxi/geom/GlobalGridTesselator.java new file mode 100644 index 0000000..799e581 --- /dev/null +++ b/src/main/java/toxi/geom/GlobalGridTesselator.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 GlobalGridTesselator extends GridTesselator { + + public GlobalGridTesselator(float res) { + super(res); + } + + protected List<Vec2D> createInsidePoints(Polygon2D poly, Rect bounds) { + List<Vec2D> points = new ArrayList<Vec2D>(); + for (float y = bounds.y; y < bounds.getBottom(); y += res) { + float yy = MathUtils.roundTo(y, res); + for (float x = bounds.x; x < bounds.getRight(); x += res) { + Vec2D p = new Vec2D(MathUtils.roundTo(x, res), yy); + if (poly.containsPoint(p)) { + points.add(p); + } + } + } + return points; + } +} diff --git a/src/main/java/toxi/geom/GridTesselator.java b/src/main/java/toxi/geom/GridTesselator.java new file mode 100644 index 0000000..7f027ca --- /dev/null +++ b/src/main/java/toxi/geom/GridTesselator.java @@ -0,0 +1,93 @@ +package toxi.geom; + +import toxi.geom.mesh2d.DelaunayTriangulation; +import toxi.geom.mesh2d.Voronoi; +import toxi.math.MathUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 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 GridTesselator implements PolygonTesselator { + + protected float res; + private float rootSize; + + /** + * Creates a new instance with the given grid resolution. + * + * @param res + * snap distance for grid points + */ + public GridTesselator(float res) { + this(res, Voronoi.DEFAULT_SIZE); + } + + /** + * Creates a new instance with the given grid resolution. + * + * @param res + * snap distance for grid points + */ + public GridTesselator(float res, float rootSize) { + this.res = res; + this.rootSize = rootSize; + } + + protected abstract List<Vec2D> createInsidePoints(Polygon2D poly, + Rect bounds); + + public float getResolution() { + return res; + } + + public void setResolution(float 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<Triangle2D> tesselatePolygon(Polygon2D poly) { + List<Triangle2D> triangles = new ArrayList<Triangle2D>(); + Rect bounds = poly.getBounds(); + // a Voronoi diagram relies on a Delaunay triangulation behind the + // scenes + Voronoi voronoi = new Voronoi(rootSize); + // add perimeter points + for (Vec2D v : poly.vertices) { + voronoi.addPoint(v); + } + // add random inliers + for (Vec2D 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 (Triangle2D t : voronoi.getTriangles()) { + if (MathUtils.abs(t.a.x) != Voronoi.DEFAULT_SIZE + && MathUtils.abs(t.a.y) != Voronoi.DEFAULT_SIZE) { + if (poly.containsPoint(t.computeCentroid())) { + triangles.add(t); + } + } + } + return triangles; + } + +} \ No newline at end of file diff --git a/src/main/java/com/syncleus/spangraph/geom/Intersector2D.java b/src/main/java/toxi/geom/Intersector2D.java similarity index 97% rename from src/main/java/com/syncleus/spangraph/geom/Intersector2D.java rename to src/main/java/toxi/geom/Intersector2D.java index 26e623d..acf253f 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Intersector2D.java +++ b/src/main/java/toxi/geom/Intersector2D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * Generic interface for ray intersection with 2D geometry diff --git a/src/main/java/com/syncleus/spangraph/geom/Intersector3D.java b/src/main/java/toxi/geom/Intersector3D.java similarity index 97% rename from src/main/java/com/syncleus/spangraph/geom/Intersector3D.java rename to src/main/java/toxi/geom/Intersector3D.java index c8caf73..ee7f122 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Intersector3D.java +++ b/src/main/java/toxi/geom/Intersector3D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * Generic interface for ray intersection with 3D geometry diff --git a/src/main/java/com/syncleus/spangraph/geom/IsectData2D.java b/src/main/java/toxi/geom/IsectData2D.java similarity index 98% rename from src/main/java/com/syncleus/spangraph/geom/IsectData2D.java rename to src/main/java/toxi/geom/IsectData2D.java index 7cec6cd..3fe3511 100644 --- a/src/main/java/com/syncleus/spangraph/geom/IsectData2D.java +++ b/src/main/java/toxi/geom/IsectData2D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; public class IsectData2D { diff --git a/src/main/java/com/syncleus/spangraph/geom/IsectData3D.java b/src/main/java/toxi/geom/IsectData3D.java similarity index 93% rename from src/main/java/com/syncleus/spangraph/geom/IsectData3D.java rename to src/main/java/toxi/geom/IsectData3D.java index 46d6b37..ef76813 100644 --- a/src/main/java/com/syncleus/spangraph/geom/IsectData3D.java +++ b/src/main/java/toxi/geom/IsectData3D.java @@ -25,15 +25,15 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; public class IsectData3D { public boolean isIntersection; public float dist; - public ReadonlyVec3D pos; - public ReadonlyVec3D dir; - public ReadonlyVec3D normal; + public Vec3D pos; + public Vec3D dir; + public XYZ normal; public IsectData3D() { diff --git a/src/main/java/com/syncleus/spangraph/geom/Line2D.java b/src/main/java/toxi/geom/Line2D.java similarity index 95% rename from src/main/java/com/syncleus/spangraph/geom/Line2D.java rename to src/main/java/toxi/geom/Line2D.java index 07e417c..17c9c9f 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Line2D.java +++ b/src/main/java/toxi/geom/Line2D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import java.util.ArrayList; import java.util.List; @@ -309,20 +309,20 @@ public class Line2D { float ub = nb / denom; final Vec2D i = a.interpolateTo(b, ua); if (ua >= 0.0f && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) { - isec = new LineIntersection(LineIntersection.Type.INTERSECTING, i, ua, ub); + isec = new LineIntersection(Type.INTERSECTING, i, ua, ub); } else { - isec = new LineIntersection(LineIntersection.Type.NON_INTERSECTING, i, ua, ub); + 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(LineIntersection.Type.COINCIDENT, null); + isec = new LineIntersection(Type.COINCIDENT, null); } else { - isec = new LineIntersection(LineIntersection.Type.COINCIDENT_NO_INTERSECT, + isec = new LineIntersection(Type.COINCIDENT_NO_INTERSECT, null); } } else { - isec = new LineIntersection(LineIntersection.Type.PARALLEL, null); + isec = new LineIntersection(Type.PARALLEL, null); } } return isec; diff --git a/src/main/java/com/syncleus/spangraph/geom/Line3D.java b/src/main/java/toxi/geom/Line3D.java similarity index 91% rename from src/main/java/com/syncleus/spangraph/geom/Line3D.java rename to src/main/java/toxi/geom/Line3D.java index 372a3cd..45af97a 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Line3D.java +++ b/src/main/java/toxi/geom/Line3D.java @@ -25,16 +25,15 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; - -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlElement; +package toxi.geom; import toxi.geom.Line3D.LineIntersection.Type; import toxi.math.MathUtils; +import javax.xml.bind.annotation.XmlElement; +import java.util.ArrayList; +import java.util.List; + public class Line3D { public static class LineIntersection { @@ -112,10 +111,10 @@ public class Line3D { * false, if A is NOT to be added to results * @return list of result vectors */ - public static final List<Vec3D> splitIntoSegments(Vec3D a, Vec3D b, - float stepLength, List<Vec3D> segments, boolean addFirst) { + public static final List<XYZ> splitIntoSegments(Vec3D a, Vec3D b, + float stepLength, List<XYZ> segments, boolean addFirst) { if (segments == null) { - segments = new ArrayList<Vec3D>(); + segments = new ArrayList(); } if (addFirst) { segments.add(a.copy()); @@ -142,9 +141,9 @@ public class Line3D { this.b = new Vec3D(x2, y2, z2); } - public Line3D(ReadonlyVec3D a, ReadonlyVec3D b) { - this.a = a.copy(); - this.b = b.copy(); + public Line3D(XYZ a, XYZ b) { + this.a = new Vec3D(a); + this.b = new Vec3D(b); } public Line3D(Vec3D a, Vec3D b) { @@ -168,11 +167,11 @@ public class Line3D { public LineIntersection closestLineTo(Line3D l) { Vec3D p43 = l.a.sub(l.b); if (p43.isZeroVector()) { - return new LineIntersection(LineIntersection.Type.NON_INTERSECTING); + return new LineIntersection(Type.NON_INTERSECTING); } Vec3D p21 = b.sub(a); if (p21.isZeroVector()) { - return new LineIntersection(LineIntersection.Type.NON_INTERSECTING); + return new LineIntersection(Type.NON_INTERSECTING); } Vec3D p13 = a.sub(l.a); @@ -184,7 +183,7 @@ public class Line3D { double denom = d2121 * d4343 - d4321 * d4321; if (MathUtils.abs(denom) < MathUtils.EPS) { - return new LineIntersection(LineIntersection.Type.NON_INTERSECTING); + return new LineIntersection(Type.NON_INTERSECTING); } double numer = d1343 * d4321 - d1321 * d4343; float mua = (float) (numer / denom); @@ -192,7 +191,7 @@ public class Line3D { Vec3D pa = a.add(p21.scaleSelf(mua)); Vec3D pb = l.a.add(p43.scaleSelf(mub)); - return new LineIntersection(LineIntersection.Type.INTERSECTING, new Line3D(pa, pb), mua, + return new LineIntersection(Type.INTERSECTING, new Line3D(pa, pb), mua, mub); } @@ -203,7 +202,7 @@ public class Line3D { * point to check against * @return closest point on the line */ - public Vec3D closestPointTo(ReadonlyVec3D p) { + public Vec3D closestPointTo(roVec3D p) { final Vec3D v = b.sub(a); final float t = p.sub(a).dot(v) / v.magSquared(); // Check to see if t is beyond the extents of the line segment @@ -240,7 +239,7 @@ public class Line3D { * Returns the line's axis-aligned bounding box. * * @return aabb - * @see toxi.geom.AABB + * @see AABB */ public AABB getBounds() { return AABB.fromMinMax(a, b); @@ -262,7 +261,7 @@ public class Line3D { return a.add(b).scaleSelf(0.5f); } - public Vec3D getNormal() { + public XYZ getNormal() { return b.cross(a); } @@ -323,7 +322,7 @@ public class Line3D { return this; } - public Line3D set(ReadonlyVec3D a, ReadonlyVec3D b) { + public Line3D set(roVec3D a, roVec3D b) { this.a = a.copy(); this.b = b.copy(); return this; @@ -335,7 +334,7 @@ public class Line3D { return this; } - public List<Vec3D> splitIntoSegments(List<Vec3D> segments, + public List<XYZ> splitIntoSegments(List<XYZ> segments, float stepLength, boolean addFirst) { return splitIntoSegments(a, b, stepLength, segments, addFirst); } diff --git a/src/main/java/toxi/geom/LineStrip2D.java b/src/main/java/toxi/geom/LineStrip2D.java new file mode 100644 index 0000000..af80183 --- /dev/null +++ b/src/main/java/toxi/geom/LineStrip2D.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.Line2D.LineIntersection; +import toxi.geom.Line2D.LineIntersection.Type; +import toxi.math.MathUtils; + +public class LineStrip2D implements Iterable<Vec2D> { + + @XmlElement(name = "v") + protected List<Vec2D> vertices = new ArrayList<Vec2D>(); + + protected float[] arcLenIndex; + + public LineStrip2D() { + } + + public LineStrip2D(Collection<? extends Vec2D> vertices) { + this.vertices = new ArrayList<Vec2D>(vertices); + } + + public LineStrip2D add(float x, float y) { + vertices.add(new Vec2D(x, y)); + return this; + } + + public LineStrip2D add(ReadonlyVec2D p) { + vertices.add(p.copy()); + return this; + } + + public LineStrip2D add(Vec2D 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 Vec2D get(int i) { + if (i < 0) { + i += vertices.size(); + } + return vertices.get(i); + } + + public Circle getBoundingCircle() { + return Circle.newBoundingCircle(vertices); + } + + public Rect getBounds() { + return Rect.getBoundingRect(vertices); + } + + public Vec2D getCentroid() { + int num = vertices.size(); + if (num > 0) { + Vec2D centroid = new Vec2D(); + for (Vec2D 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<Vec2D> getDecimatedVertices(float 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<Vec2D> getDecimatedVertices(float step, boolean doAddFinalVertex) { + ArrayList<Vec2D> uniform = new ArrayList<Vec2D>(); + if (vertices.size() < 3) { + if (vertices.size() == 2) { + new Line2D(vertices.get(0), vertices.get(1)).splitIntoSegments( + uniform, step, true); + if (!doAddFinalVertex) { + uniform.remove(uniform.size() - 1); + } + } else { + return null; + } + } + float 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++; + } + ReadonlyVec2D p = vertices.get(currIdx - 1); + ReadonlyVec2D q = vertices.get(currIdx); + float frac = (float) ((currT - arcLenIndex[currIdx - 1]) / (arcLenIndex[currIdx] - arcLenIndex[currIdx - 1])); + Vec2D 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<Line2D> getEdges() { + int num = vertices.size(); + List<Line2D> edges = new ArrayList<Line2D>(num - 1); + for (int i = 1; i < num; i++) { + edges.add(new Line2D(vertices.get(i - 1), vertices.get(i))); + } + return edges; + } + + public float getLength() { + if (arcLenIndex == null + || (arcLenIndex != null && arcLenIndex.length != vertices + .size())) { + arcLenIndex = new float[vertices.size()]; + } + float arcLen = 0; + for (int i = 1; i < arcLenIndex.length; i++) { + ReadonlyVec2D p = vertices.get(i - 1); + ReadonlyVec2D 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 Vec2D getPointAt(float 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); + } + float totalLength = this.getLength(); + double offp = 0, offq = 0; + for (int i = 1; i < num; i++) { + Vec2D p = vertices.get(i - 1); + Vec2D q = vertices.get(i); + offq += q.distanceTo(p) / totalLength; + if (offp <= t && offq >= t) { + return p.interpolateTo(q, (float) MathUtils.mapInterval(t, + offp, offq, 0.0, 1.0)); + } + offp = offq; + } + } + return null; + } + + public List<Line2D> getSegments() { + final int num = vertices.size(); + List<Line2D> segments = new ArrayList<Line2D>(num - 1); + for (int i = 1; i < num; i++) { + segments.add(new Line2D(vertices.get(i - 1), vertices.get(i))); + } + return segments; + } + + /** + * @return the vertices + */ + public List<Vec2D> getVertices() { + return vertices; + } + + public LineIntersection intersectLine(Line2D line) { + Line2D l = new Line2D(new Vec2D(), new Vec2D()); + 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<Vec2D> iterator() { + return vertices.iterator(); + } + + public LineStrip2D rotate(float theta) { + for (Vec2D v : vertices) { + v.rotate(theta); + } + return this; + } + + public LineStrip2D scale(float scale) { + return scale(scale, scale); + } + + public LineStrip2D scale(float x, float y) { + for (Vec2D v : vertices) { + v.scaleSelf(x, y); + } + return this; + } + + public LineStrip2D scale(ReadonlyVec2D scale) { + return scale(scale.x(), scale.y()); + } + + public LineStrip2D scaleSize(float scale) { + return scaleSize(scale, scale); + } + + public LineStrip2D scaleSize(float x, float y) { + Vec2D centroid = getCentroid(); + for (Vec2D v : vertices) { + v.subSelf(centroid).scaleSelf(x, y).addSelf(centroid); + } + return this; + } + + public LineStrip2D scaleSize(ReadonlyVec2D scale) { + return scaleSize(scale.x(), scale.y()); + } + + /** + * @param vertices + * the vertices to set + */ + public void setVertices(List<Vec2D> vertices) { + this.vertices = vertices; + } + + public LineStrip2D translate(float x, float y) { + for (Vec2D v : vertices) { + v.addSelf(x, y); + } + return this; + } + + public LineStrip2D translate(ReadonlyVec2D offset) { + return translate(offset.x(), offset.y()); + } +} diff --git a/src/main/java/toxi/geom/LineStrip3D.java b/src/main/java/toxi/geom/LineStrip3D.java new file mode 100644 index 0000000..d76121f --- /dev/null +++ b/src/main/java/toxi/geom/LineStrip3D.java @@ -0,0 +1,182 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.XmlElement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class LineStrip3D implements Iterable<XYZ> { + + @XmlElement(name = "v") + protected List<XYZ> vertices = new ArrayList<XYZ>(); + + protected float[] arcLenIndex; + + public LineStrip3D() { + } + + public LineStrip3D(Collection<? extends XYZ> vertices) { + this.vertices = new ArrayList(vertices); + } + + public LineStrip3D add(float x, float y, float z) { + vertices.add(new Vec3D(x, y, z)); + return this; + } + + public LineStrip3D add(roVec3D p) { + vertices.add(p.copy()); + return this; + } + + public LineStrip3D add(XYZ 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 XYZ 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<XYZ> getDecimatedVertices(float 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<XYZ> getDecimatedVertices(float step, boolean doAddFinalVertex) { + ArrayList<XYZ> uniform = new ArrayList(); + if (vertices.size() < 3) { + if (vertices.size() == 2) { + new Line3D(vertices.get(0), vertices.get(1)).splitIntoSegments( + uniform, step, true); + if (!doAddFinalVertex) { + uniform.remove(uniform.size() - 1); + } + } else { + return null; + } + } + float 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++; + } + XYZ p = vertices.get(currIdx - 1); + XYZ q = vertices.get(currIdx); + float frac = (float) ((currT - arcLenIndex[currIdx - 1]) / (arcLenIndex[currIdx] - arcLenIndex[currIdx - 1])); + XYZ i = p.interpolateTo(q, frac); + uniform.add(i); + } + if (doAddFinalVertex) { + uniform.add(vertices.get(vertices.size() - 1).copy()); + } + return uniform; + } + + public float getEstimatedArcLength() { + if (arcLenIndex == null + || (arcLenIndex != null && arcLenIndex.length != vertices + .size())) { + arcLenIndex = new float[vertices.size()]; + } + float arcLen = 0; + for (int i = 1; i < arcLenIndex.length; i++) { + XYZ p = vertices.get(i - 1); + XYZ q = vertices.get(i); + arcLen += p.distanceTo(q); + arcLenIndex[i] = arcLen; + } + return arcLen; + } + + public List<Line3D> getSegments() { + final int num = vertices.size(); + List<Line3D> segments = new ArrayList<Line3D>(num - 1); + for (int i = 1; i < num; i++) { + segments.add(new Line3D(vertices.get(i - 1), vertices.get(i))); + } + return segments; + } + + /** + * @return the vertices + */ + public List<XYZ> getVertices() { + return vertices; + } + + public Iterator<XYZ> iterator() { + return vertices.iterator(); + } + + /** + * @param vertices + * the vertices to set + */ + public void setVertices(List<XYZ> vertices) { + this.vertices = vertices; + } +} diff --git a/src/main/java/toxi/geom/LocalGridTesselator.java b/src/main/java/toxi/geom/LocalGridTesselator.java new file mode 100644 index 0000000..8c8e358 --- /dev/null +++ b/src/main/java/toxi/geom/LocalGridTesselator.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 LocalGridTesselator extends GridTesselator { + + public LocalGridTesselator(int res) { + super(res); + } + + protected List<Vec2D> createInsidePoints(Polygon2D poly, Rect bounds) { + List<Vec2D> points = new ArrayList<Vec2D>(); + 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++) { + Vec2D p = new Vec2D((float) xmap.getMappedValueFor(x), yy); + if (poly.containsPoint(p)) { + points.add(p); + } + } + } + return points; + } +} diff --git a/src/main/java/toxi/geom/Matrix3d.java b/src/main/java/toxi/geom/Matrix3d.java new file mode 100644 index 0000000..3e38371 --- /dev/null +++ b/src/main/java/toxi/geom/Matrix3d.java @@ -0,0 +1,3063 @@ +/* + * $RCSfile$ + * + * Copyright 1996-2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + * + * $Revision: 127 $ + * $Date: 2008-02-28 20:18:51 +0000 (Thu, 28 Feb 2008) $ + * $State$ + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +/** + * A double precision floating point 3 by 3 matrix. Primarily to support 3D + * rotations. + */ +public class Matrix3d implements java.io.Serializable, Cloneable { + + // Compatible with 1.1 + static final long serialVersionUID = 6837536777072402710L; + + private static final boolean almostEqual(double a, double b) { + if (a == b) { + return true; + } + + final double EPSILON_ABSOLUTE = 1.0e-6; + final double EPSILON_RELATIVE = 1.0e-4; + double diff = Math.abs(a - b); + double absA = Math.abs(a); + double absB = Math.abs(b); + double max = (absA >= absB) ? absA : absB; + + if (diff < EPSILON_ABSOLUTE) { + return true; + } + + if ((diff / max) < EPSILON_RELATIVE) { + return true; + } + + return false; + } + + static int compute_2X2(double f, double g, double h, + double[] single_values, double[] snl, double[] csl, double[] snr, + double[] csr, int index) { + + double c_b3 = 2.; + double c_b4 = 1.; + + double d__1; + int pmax; + double temp; + boolean swap; + double a, d, l, m, r, s, t, tsign, fa, ga, ha; + double ft, gt, ht, mm; + boolean gasmal; + double tt, clt, crt, slt, srt; + double ssmin, ssmax; + + ssmax = single_values[0]; + ssmin = single_values[1]; + clt = 0.0; + crt = 0.0; + slt = 0.0; + srt = 0.0; + tsign = 0.0; + + ft = f; + fa = Math.abs(ft); + ht = h; + ha = Math.abs(h); + + pmax = 1; + if (ha > fa) { + swap = true; + } else { + swap = false; + } + + if (swap) { + pmax = 3; + temp = ft; + ft = ht; + ht = temp; + temp = fa; + fa = ha; + ha = temp; + + } + gt = g; + ga = Math.abs(gt); + if (ga == 0.) { + + single_values[1] = ha; + single_values[0] = fa; + clt = 1.; + crt = 1.; + slt = 0.; + srt = 0.; + } else { + gasmal = true; + + if (ga > fa) { + pmax = 2; + if (fa / ga < EPS) { + + gasmal = false; + ssmax = ga; + if (ha > 1.) { + ssmin = fa / (ga / ha); + } else { + ssmin = fa / ga * ha; + } + clt = 1.; + slt = ht / gt; + srt = 1.; + crt = ft / gt; + } + } + if (gasmal) { + + d = fa - ha; + if (d == fa) { + + l = 1.; + } else { + l = d / fa; + } + + m = gt / ft; + + t = 2. - l; + + mm = m * m; + tt = t * t; + s = Math.sqrt(tt + mm); + + if (l == 0.) { + r = Math.abs(m); + } else { + r = Math.sqrt(l * l + mm); + } + + a = (s + r) * .5; + + if (ga > fa) { + pmax = 2; + if (fa / ga < EPS) { + + gasmal = false; + ssmax = ga; + if (ha > 1.) { + ssmin = fa / (ga / ha); + } else { + ssmin = fa / ga * ha; + } + clt = 1.; + slt = ht / gt; + srt = 1.; + crt = ft / gt; + } + } + if (gasmal) { + + d = fa - ha; + if (d == fa) { + + l = 1.; + } else { + l = d / fa; + } + + m = gt / ft; + + t = 2. - l; + + mm = m * m; + tt = t * t; + s = Math.sqrt(tt + mm); + + if (l == 0.) { + r = Math.abs(m); + } else { + r = Math.sqrt(l * l + mm); + } + + a = (s + r) * .5; + + ssmin = ha / a; + ssmax = fa * a; + if (mm == 0.) { + + if (l == 0.) { + t = MathUtils.dualSign(c_b3, ft) + * MathUtils.dualSign(c_b4, gt); + } else { + t = gt / MathUtils.dualSign(d, ft) + m / t; + } + } else { + t = (m / (s + t) + m / (r + l)) * (a + 1.); + } + l = Math.sqrt(t * t + 4.); + crt = 2. / l; + srt = t / l; + clt = (crt + srt * m) / a; + slt = ht / ft * srt / a; + } + } + if (swap) { + csl[0] = srt; + snl[0] = crt; + csr[0] = slt; + snr[0] = clt; + } else { + csl[0] = clt; + snl[0] = slt; + csr[0] = crt; + snr[0] = srt; + } + + if (pmax == 1) { + tsign = MathUtils.dualSign(c_b4, csr[0]) + * MathUtils.dualSign(c_b4, csl[0]) + * MathUtils.dualSign(c_b4, f); + } + if (pmax == 2) { + tsign = MathUtils.dualSign(c_b4, snr[0]) + * MathUtils.dualSign(c_b4, csl[0]) + * MathUtils.dualSign(c_b4, g); + } + if (pmax == 3) { + tsign = MathUtils.dualSign(c_b4, snr[0]) + * MathUtils.dualSign(c_b4, snl[0]) + * MathUtils.dualSign(c_b4, h); + } + single_values[index] = MathUtils.dualSign(ssmax, tsign); + d__1 = tsign * MathUtils.dualSign(c_b4, f) + * MathUtils.dualSign(c_b4, h); + single_values[index + 1] = MathUtils.dualSign(ssmin, d__1); + + } + return 0; + } + + static int compute_qr(double[] s, double[] e, double[] u, double[] v) { + int k; + boolean converged; + double shift, r; + double[] cosl = new double[2]; + double[] cosr = new double[2]; + double[] sinl = new double[2]; + double[] sinr = new double[2]; + double[] m = new double[9]; + + double utemp, vtemp; + double f, g; + + final int MAX_INTERATIONS = 10; + final double CONVERGE_TOL = 4.89E-15; + + double c_b48 = 1.; + int first; + converged = false; + + first = 1; + + if (Math.abs(e[1]) < CONVERGE_TOL || Math.abs(e[0]) < CONVERGE_TOL) { + converged = true; + } + + for (k = 0; k < MAX_INTERATIONS && !converged; k++) { + shift = compute_shift(s[1], e[1], s[2]); + f = (Math.abs(s[0]) - shift) + * (MathUtils.dualSign(c_b48, s[0]) + shift / s[0]); + g = e[0]; + r = compute_rot(f, g, sinr, cosr, 0, first); + f = cosr[0] * s[0] + sinr[0] * e[0]; + e[0] = cosr[0] * e[0] - sinr[0] * s[0]; + g = sinr[0] * s[1]; + s[1] = cosr[0] * s[1]; + + r = compute_rot(f, g, sinl, cosl, 0, first); + first = 0; + s[0] = r; + f = cosl[0] * e[0] + sinl[0] * s[1]; + s[1] = cosl[0] * s[1] - sinl[0] * e[0]; + g = sinl[0] * e[1]; + e[1] = cosl[0] * e[1]; + + r = compute_rot(f, g, sinr, cosr, 1, first); + e[0] = r; + f = cosr[1] * s[1] + sinr[1] * e[1]; + e[1] = cosr[1] * e[1] - sinr[1] * s[1]; + g = sinr[1] * s[2]; + s[2] = cosr[1] * s[2]; + + r = compute_rot(f, g, sinl, cosl, 1, first); + s[1] = r; + f = cosl[1] * e[1] + sinl[1] * s[2]; + s[2] = cosl[1] * s[2] - sinl[1] * e[1]; + e[1] = f; + + // update u matrices + utemp = u[0]; + u[0] = cosl[0] * utemp + sinl[0] * u[3]; + u[3] = -sinl[0] * utemp + cosl[0] * u[3]; + utemp = u[1]; + u[1] = cosl[0] * utemp + sinl[0] * u[4]; + u[4] = -sinl[0] * utemp + cosl[0] * u[4]; + utemp = u[2]; + u[2] = cosl[0] * utemp + sinl[0] * u[5]; + u[5] = -sinl[0] * utemp + cosl[0] * u[5]; + + utemp = u[3]; + u[3] = cosl[1] * utemp + sinl[1] * u[6]; + u[6] = -sinl[1] * utemp + cosl[1] * u[6]; + utemp = u[4]; + u[4] = cosl[1] * utemp + sinl[1] * u[7]; + u[7] = -sinl[1] * utemp + cosl[1] * u[7]; + utemp = u[5]; + u[5] = cosl[1] * utemp + sinl[1] * u[8]; + u[8] = -sinl[1] * utemp + cosl[1] * u[8]; + + // update v matrices + + vtemp = v[0]; + v[0] = cosr[0] * vtemp + sinr[0] * v[1]; + v[1] = -sinr[0] * vtemp + cosr[0] * v[1]; + vtemp = v[3]; + v[3] = cosr[0] * vtemp + sinr[0] * v[4]; + v[4] = -sinr[0] * vtemp + cosr[0] * v[4]; + vtemp = v[6]; + v[6] = cosr[0] * vtemp + sinr[0] * v[7]; + v[7] = -sinr[0] * vtemp + cosr[0] * v[7]; + + vtemp = v[1]; + v[1] = cosr[1] * vtemp + sinr[1] * v[2]; + v[2] = -sinr[1] * vtemp + cosr[1] * v[2]; + vtemp = v[4]; + v[4] = cosr[1] * vtemp + sinr[1] * v[5]; + v[5] = -sinr[1] * vtemp + cosr[1] * v[5]; + vtemp = v[7]; + v[7] = cosr[1] * vtemp + sinr[1] * v[8]; + v[8] = -sinr[1] * vtemp + cosr[1] * v[8]; + + m[0] = s[0]; + m[1] = e[0]; + m[2] = 0.0; + m[3] = 0.0; + m[4] = s[1]; + m[5] = e[1]; + m[6] = 0.0; + m[7] = 0.0; + m[8] = s[2]; + + if (Math.abs(e[1]) < CONVERGE_TOL || Math.abs(e[0]) < CONVERGE_TOL) { + converged = true; + } + } + + if (Math.abs(e[1]) < CONVERGE_TOL) { + compute_2X2(s[0], e[0], s[1], s, sinl, cosl, sinr, cosr, 0); + + utemp = u[0]; + u[0] = cosl[0] * utemp + sinl[0] * u[3]; + u[3] = -sinl[0] * utemp + cosl[0] * u[3]; + utemp = u[1]; + u[1] = cosl[0] * utemp + sinl[0] * u[4]; + u[4] = -sinl[0] * utemp + cosl[0] * u[4]; + utemp = u[2]; + u[2] = cosl[0] * utemp + sinl[0] * u[5]; + u[5] = -sinl[0] * utemp + cosl[0] * u[5]; + + // update v matrices + + vtemp = v[0]; + v[0] = cosr[0] * vtemp + sinr[0] * v[1]; + v[1] = -sinr[0] * vtemp + cosr[0] * v[1]; + vtemp = v[3]; + v[3] = cosr[0] * vtemp + sinr[0] * v[4]; + v[4] = -sinr[0] * vtemp + cosr[0] * v[4]; + vtemp = v[6]; + v[6] = cosr[0] * vtemp + sinr[0] * v[7]; + v[7] = -sinr[0] * vtemp + cosr[0] * v[7]; + } else { + compute_2X2(s[1], e[1], s[2], s, sinl, cosl, sinr, cosr, 1); + + utemp = u[3]; + u[3] = cosl[0] * utemp + sinl[0] * u[6]; + u[6] = -sinl[0] * utemp + cosl[0] * u[6]; + utemp = u[4]; + u[4] = cosl[0] * utemp + sinl[0] * u[7]; + u[7] = -sinl[0] * utemp + cosl[0] * u[7]; + utemp = u[5]; + u[5] = cosl[0] * utemp + sinl[0] * u[8]; + u[8] = -sinl[0] * utemp + cosl[0] * u[8]; + + // update v matrices + + vtemp = v[1]; + v[1] = cosr[0] * vtemp + sinr[0] * v[2]; + v[2] = -sinr[0] * vtemp + cosr[0] * v[2]; + vtemp = v[4]; + v[4] = cosr[0] * vtemp + sinr[0] * v[5]; + v[5] = -sinr[0] * vtemp + cosr[0] * v[5]; + vtemp = v[7]; + v[7] = cosr[0] * vtemp + sinr[0] * v[8]; + v[8] = -sinr[0] * vtemp + cosr[0] * v[8]; + } + + return (0); + } + + static double compute_rot(double f, double g, double[] sin, double[] cos, + int index, int first) { + double cs, sn; + int i; + double scale; + int count; + double f1, g1; + double r; + final double safmn2 = 2.002083095183101E-146; + final double safmx2 = 4.994797680505588E+145; + + if (g == 0.) { + cs = 1.; + sn = 0.; + r = f; + } else if (f == 0.) { + cs = 0.; + sn = 1.; + r = g; + } else { + f1 = f; + g1 = g; + scale = MathUtils.max(MathUtils.abs(f1), MathUtils.abs(g1)); + if (scale >= safmx2) { + count = 0; + while (scale >= safmx2) { + ++count; + f1 *= safmn2; + g1 *= safmn2; + scale = MathUtils.max(MathUtils.abs(f1), MathUtils.abs(g1)); + } + r = Math.sqrt(f1 * f1 + g1 * g1); + cs = f1 / r; + sn = g1 / r; + for (i = 1; i <= count; ++i) { + r *= safmx2; + } + } else if (scale <= safmn2) { + count = 0; + while (scale <= safmn2) { + ++count; + f1 *= safmx2; + g1 *= safmx2; + scale = MathUtils.max(MathUtils.abs(f1), MathUtils.abs(g1)); + } + r = Math.sqrt(f1 * f1 + g1 * g1); + cs = f1 / r; + sn = g1 / r; + for (i = 1; i <= count; ++i) { + r *= safmn2; + } + } else { + r = Math.sqrt(f1 * f1 + g1 * g1); + cs = f1 / r; + sn = g1 / r; + } + if (Math.abs(f) > Math.abs(g) && cs < 0.) { + cs = -cs; + sn = -sn; + r = -r; + } + } + sin[index] = sn; + cos[index] = cs; + return r; + + } + + static double compute_shift(double f, double g, double h) { + double d__1, d__2; + double fhmn, fhmx, c, fa, ga, ha, as, at, au; + double ssmin; + + fa = Math.abs(f); + ga = Math.abs(g); + ha = Math.abs(h); + fhmn = MathUtils.min(fa, ha); + fhmx = MathUtils.max(fa, ha); + if (fhmn == 0.) { + ssmin = 0.; + if (fhmx == 0.) { + } else { + d__1 = MathUtils.min(fhmx, ga) / MathUtils.max(fhmx, ga); + } + } else { + if (ga < fhmx) { + as = fhmn / fhmx + 1.; + at = (fhmx - fhmn) / fhmx; + d__1 = ga / fhmx; + au = d__1 * d__1; + c = 2. / (Math.sqrt(as * as + au) + Math.sqrt(at * at + au)); + ssmin = fhmn * c; + } else { + au = fhmx / ga; + if (au == 0.) { + ssmin = fhmn * fhmx / ga; + } else { + as = fhmn / fhmx + 1.; + at = (fhmx - fhmn) / fhmx; + d__1 = as * au; + d__2 = at * au; + c = 1. / (Math.sqrt(d__1 * d__1 + 1.) + Math.sqrt(d__2 + * d__2 + 1.)); + ssmin = fhmn * c * au; + ssmin += ssmin; + } + } + } + + return (ssmin); + } + + static void compute_svd(double[] m, double[] outScale, double[] outRot) { + int i; + double g; + double[] u1 = new double[9]; + double[] v1 = new double[9]; + double[] t1 = new double[9]; + double[] t2 = new double[9]; + + double[] tmp = t1; + double[] single_values = t2; + + double[] rot = new double[9]; + double[] e = new double[3]; + double[] scales = new double[3]; + + int negCnt = 0; + double c1, c2, c3, c4; + double s1, s2, s3, s4; + + for (i = 0; i < 9; i++) { + rot[i] = m[i]; + } + + // u1 + + if (m[3] * m[3] < EPS) { + u1[0] = 1.0; + u1[1] = 0.0; + u1[2] = 0.0; + u1[3] = 0.0; + u1[4] = 1.0; + u1[5] = 0.0; + u1[6] = 0.0; + u1[7] = 0.0; + u1[8] = 1.0; + } else if (m[0] * m[0] < EPS) { + tmp[0] = m[0]; + tmp[1] = m[1]; + tmp[2] = m[2]; + m[0] = m[3]; + m[1] = m[4]; + m[2] = m[5]; + + m[3] = -tmp[0]; // zero + m[4] = -tmp[1]; + m[5] = -tmp[2]; + + u1[0] = 0.0; + u1[1] = 1.0; + u1[2] = 0.0; + u1[3] = -1.0; + u1[4] = 0.0; + u1[5] = 0.0; + u1[6] = 0.0; + u1[7] = 0.0; + u1[8] = 1.0; + } else { + g = 1.0 / Math.sqrt(m[0] * m[0] + m[3] * m[3]); + c1 = m[0] * g; + s1 = m[3] * g; + tmp[0] = c1 * m[0] + s1 * m[3]; + tmp[1] = c1 * m[1] + s1 * m[4]; + tmp[2] = c1 * m[2] + s1 * m[5]; + + m[3] = -s1 * m[0] + c1 * m[3]; // zero + m[4] = -s1 * m[1] + c1 * m[4]; + m[5] = -s1 * m[2] + c1 * m[5]; + + m[0] = tmp[0]; + m[1] = tmp[1]; + m[2] = tmp[2]; + u1[0] = c1; + u1[1] = s1; + u1[2] = 0.0; + u1[3] = -s1; + u1[4] = c1; + u1[5] = 0.0; + u1[6] = 0.0; + u1[7] = 0.0; + u1[8] = 1.0; + } + + // u2 + + if (m[6] * m[6] < EPS) { + } else if (m[0] * m[0] < EPS) { + tmp[0] = m[0]; + tmp[1] = m[1]; + tmp[2] = m[2]; + m[0] = m[6]; + m[1] = m[7]; + m[2] = m[8]; + + m[6] = -tmp[0]; // zero + m[7] = -tmp[1]; + m[8] = -tmp[2]; + + tmp[0] = u1[0]; + tmp[1] = u1[1]; + tmp[2] = u1[2]; + u1[0] = u1[6]; + u1[1] = u1[7]; + u1[2] = u1[8]; + + u1[6] = -tmp[0]; // zero + u1[7] = -tmp[1]; + u1[8] = -tmp[2]; + } else { + g = 1.0 / Math.sqrt(m[0] * m[0] + m[6] * m[6]); + c2 = m[0] * g; + s2 = m[6] * g; + tmp[0] = c2 * m[0] + s2 * m[6]; + tmp[1] = c2 * m[1] + s2 * m[7]; + tmp[2] = c2 * m[2] + s2 * m[8]; + + m[6] = -s2 * m[0] + c2 * m[6]; + m[7] = -s2 * m[1] + c2 * m[7]; + m[8] = -s2 * m[2] + c2 * m[8]; + m[0] = tmp[0]; + m[1] = tmp[1]; + m[2] = tmp[2]; + + tmp[0] = c2 * u1[0]; + tmp[1] = c2 * u1[1]; + u1[2] = s2; + + tmp[6] = -u1[0] * s2; + tmp[7] = -u1[1] * s2; + u1[8] = c2; + u1[0] = tmp[0]; + u1[1] = tmp[1]; + u1[6] = tmp[6]; + u1[7] = tmp[7]; + } + + // v1 + + if (m[2] * m[2] < EPS) { + v1[0] = 1.0; + v1[1] = 0.0; + v1[2] = 0.0; + v1[3] = 0.0; + v1[4] = 1.0; + v1[5] = 0.0; + v1[6] = 0.0; + v1[7] = 0.0; + v1[8] = 1.0; + } else if (m[1] * m[1] < EPS) { + tmp[2] = m[2]; + tmp[5] = m[5]; + tmp[8] = m[8]; + m[2] = -m[1]; + m[5] = -m[4]; + m[8] = -m[7]; + + m[1] = tmp[2]; // zero + m[4] = tmp[5]; + m[7] = tmp[8]; + + v1[0] = 1.0; + v1[1] = 0.0; + v1[2] = 0.0; + v1[3] = 0.0; + v1[4] = 0.0; + v1[5] = -1.0; + v1[6] = 0.0; + v1[7] = 1.0; + v1[8] = 0.0; + } else { + g = 1.0 / Math.sqrt(m[1] * m[1] + m[2] * m[2]); + c3 = m[1] * g; + s3 = m[2] * g; + tmp[1] = c3 * m[1] + s3 * m[2]; // can assign to m[1]? + m[2] = -s3 * m[1] + c3 * m[2]; // zero + m[1] = tmp[1]; + + tmp[4] = c3 * m[4] + s3 * m[5]; + m[5] = -s3 * m[4] + c3 * m[5]; + m[4] = tmp[4]; + + tmp[7] = c3 * m[7] + s3 * m[8]; + m[8] = -s3 * m[7] + c3 * m[8]; + m[7] = tmp[7]; + + v1[0] = 1.0; + v1[1] = 0.0; + v1[2] = 0.0; + v1[3] = 0.0; + v1[4] = c3; + v1[5] = -s3; + v1[6] = 0.0; + v1[7] = s3; + v1[8] = c3; + } + + // u3 + + if (m[7] * m[7] < EPS) { + } else if (m[4] * m[4] < EPS) { + tmp[3] = m[3]; + tmp[4] = m[4]; + tmp[5] = m[5]; + m[3] = m[6]; // zero + m[4] = m[7]; + m[5] = m[8]; + + m[6] = -tmp[3]; // zero + m[7] = -tmp[4]; // zero + m[8] = -tmp[5]; + + tmp[3] = u1[3]; + tmp[4] = u1[4]; + tmp[5] = u1[5]; + u1[3] = u1[6]; + u1[4] = u1[7]; + u1[5] = u1[8]; + + u1[6] = -tmp[3]; // zero + u1[7] = -tmp[4]; + u1[8] = -tmp[5]; + + } else { + g = 1.0 / Math.sqrt(m[4] * m[4] + m[7] * m[7]); + c4 = m[4] * g; + s4 = m[7] * g; + tmp[3] = c4 * m[3] + s4 * m[6]; + m[6] = -s4 * m[3] + c4 * m[6]; // zero + m[3] = tmp[3]; + + tmp[4] = c4 * m[4] + s4 * m[7]; + m[7] = -s4 * m[4] + c4 * m[7]; + m[4] = tmp[4]; + + tmp[5] = c4 * m[5] + s4 * m[8]; + m[8] = -s4 * m[5] + c4 * m[8]; + m[5] = tmp[5]; + + tmp[3] = c4 * u1[3] + s4 * u1[6]; + u1[6] = -s4 * u1[3] + c4 * u1[6]; + u1[3] = tmp[3]; + + tmp[4] = c4 * u1[4] + s4 * u1[7]; + u1[7] = -s4 * u1[4] + c4 * u1[7]; + u1[4] = tmp[4]; + + tmp[5] = c4 * u1[5] + s4 * u1[8]; + u1[8] = -s4 * u1[5] + c4 * u1[8]; + u1[5] = tmp[5]; + } + + single_values[0] = m[0]; + single_values[1] = m[4]; + single_values[2] = m[8]; + e[0] = m[1]; + e[1] = m[5]; + + if (e[0] * e[0] < EPS && e[1] * e[1] < EPS) { + + } else { + compute_qr(single_values, e, u1, v1); + } + + scales[0] = single_values[0]; + scales[1] = single_values[1]; + scales[2] = single_values[2]; + + // Do some optimization here. If scale is unity, simply return the + // rotation matric. + if (almostEqual(Math.abs(scales[0]), 1.0) + && almostEqual(Math.abs(scales[1]), 1.0) + && almostEqual(Math.abs(scales[2]), 1.0)) { + for (i = 0; i < 3; i++) { + if (scales[i] < 0.0) { + negCnt++; + } + } + + if ((negCnt == 0) || (negCnt == 2)) { + outScale[0] = outScale[1] = outScale[2] = 1.0; + for (i = 0; i < 9; i++) { + outRot[i] = rot[i]; + } + + return; + } + } + + transpose_mat(u1, t1); + transpose_mat(v1, t2); + + svdReorder(m, t1, t2, scales, outRot, outScale); + } + + /** + * Solves a set of linear equations. The input parameters "matrix1", and + * "row_perm" come from luDecompostionD3x3 and do not change here. The + * parameter "matrix2" is a set of column vectors assembled into a 3x3 + * matrix of floating-point values. The procedure takes each column of + * "matrix2" in turn and treats it as the right-hand side of the matrix + * equation Ax = LUx = b. The solution vector replaces the original column + * of the matrix. + * + * If "matrix2" is the identity matrix, the procedure replaces its contents + * with the inverse of the matrix from which "matrix1" was originally + * derived. + */ + // + // Reference: Press, Flannery, Teukolsky, Vetterling, + // _Numerical_Recipes_in_C_, Cambridge University Press, + // 1988, pp 44-45. + // + static void luBacksubstitution(double[] matrix1, int[] row_perm, + double[] matrix2) { + + int i, ii, ip, j, k; + int rp; + int cv, rv; + + // rp = row_perm; + rp = 0; + + // For each column vector of matrix2 ... + for (k = 0; k < 3; k++) { + // cv = &(matrix2[0][k]); + cv = k; + ii = -1; + + // Forward substitution + for (i = 0; i < 3; i++) { + double sum; + + ip = row_perm[rp + i]; + sum = matrix2[cv + 3 * ip]; + matrix2[cv + 3 * ip] = matrix2[cv + 3 * i]; + if (ii >= 0) { + // rv = &(matrix1[i][0]); + rv = i * 3; + for (j = ii; j <= i - 1; j++) { + sum -= matrix1[rv + j] * matrix2[cv + 3 * j]; + } + } else if (sum != 0.0) { + ii = i; + } + matrix2[cv + 3 * i] = sum; + } + + // Backsubstitution + // rv = &(matrix1[3][0]); + rv = 2 * 3; + matrix2[cv + 3 * 2] /= matrix1[rv + 2]; + + rv -= 3; + matrix2[cv + 3 * 1] = (matrix2[cv + 3 * 1] - matrix1[rv + 2] + * matrix2[cv + 3 * 2]) + / matrix1[rv + 1]; + + rv -= 3; + matrix2[cv + 4 * 0] = (matrix2[cv + 3 * 0] - matrix1[rv + 1] + * matrix2[cv + 3 * 1] - matrix1[rv + 2] + * matrix2[cv + 3 * 2]) + / matrix1[rv + 0]; + } + } + + static void mat_mul(double[] m1, double[] m2, double[] m3) { + int i; + double[] tmp = new double[9]; + + tmp[0] = m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6]; + tmp[1] = m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7]; + tmp[2] = m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8]; + + tmp[3] = m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6]; + tmp[4] = m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7]; + tmp[5] = m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8]; + + tmp[6] = m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6]; + tmp[7] = m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7]; + tmp[8] = m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8]; + + for (i = 0; i < 9; i++) { + m3[i] = tmp[i]; + } + } + + static void print_det(double[] mat) { + double det; + + det = mat[0] * mat[4] * mat[8] + mat[1] * mat[5] * mat[6] + mat[2] + * mat[3] * mat[7] - mat[2] * mat[4] * mat[6] - mat[0] * mat[5] + * mat[7] - mat[1] * mat[3] * mat[8]; + System.out.println("det= " + det); + } + + static void print_mat(double[] mat) { + int i; + for (i = 0; i < 3; i++) { + System.out.println(mat[i * 3 + 0] + " " + mat[i * 3 + 1] + " " + + mat[i * 3 + 2] + "\n"); + } + + } + + static void svdReorder(double[] m, double[] t1, double[] t2, + double[] scales, double[] outRot, double[] outScale) { + + int[] out = new int[3]; + int[] in = new int[3]; + int in0, in1, in2, index, i; + double[] mag = new double[3]; + double[] rot = new double[9]; + + // check for rotation information in the scales + if (scales[0] < 0.0) { // move the rotation info to rotation matrix + scales[0] = -scales[0]; + t2[0] = -t2[0]; + t2[1] = -t2[1]; + t2[2] = -t2[2]; + } + if (scales[1] < 0.0) { // move the rotation info to rotation matrix + scales[1] = -scales[1]; + t2[3] = -t2[3]; + t2[4] = -t2[4]; + t2[5] = -t2[5]; + } + if (scales[2] < 0.0) { // move the rotation info to rotation matrix + scales[2] = -scales[2]; + t2[6] = -t2[6]; + t2[7] = -t2[7]; + t2[8] = -t2[8]; + } + + mat_mul(t1, t2, rot); + + // check for equal scales case and do not reorder + if (almostEqual(Math.abs(scales[0]), Math.abs(scales[1])) + && almostEqual(Math.abs(scales[1]), Math.abs(scales[2]))) { + for (i = 0; i < 9; i++) { + outRot[i] = rot[i]; + } + for (i = 0; i < 3; i++) { + outScale[i] = scales[i]; + } + + } else { + + // sort the order of the results of SVD + if (scales[0] > scales[1]) { + if (scales[0] > scales[2]) { + if (scales[2] > scales[1]) { + out[0] = 0; + out[1] = 2; + out[2] = 1; // xzy + } else { + out[0] = 0; + out[1] = 1; + out[2] = 2; // xyz + } + } else { + out[0] = 2; + out[1] = 0; + out[2] = 1; // zxy + } + } else { // y > x + if (scales[1] > scales[2]) { + if (scales[2] > scales[0]) { + out[0] = 1; + out[1] = 2; + out[2] = 0; // yzx + } else { + out[0] = 1; + out[1] = 0; + out[2] = 2; // yxz + } + } else { + out[0] = 2; + out[1] = 1; + out[2] = 0; // zyx + } + } + + /* + * System.out.println("\nscales="+scales[0]+" "+scales[1]+" "+scales[ + * 2]); System.out.println("\nrot="+rot[0]+" "+rot[1]+" "+rot[2]); + * System.out.println("rot="+rot[3]+" "+rot[4]+" "+rot[5]); + * System.out.println("rot="+rot[6]+" "+rot[7]+" "+rot[8]); + */ + + // sort the order of the input matrix + mag[0] = (m[0] * m[0] + m[1] * m[1] + m[2] * m[2]); + mag[1] = (m[3] * m[3] + m[4] * m[4] + m[5] * m[5]); + mag[2] = (m[6] * m[6] + m[7] * m[7] + m[8] * m[8]); + + if (mag[0] > mag[1]) { + if (mag[0] > mag[2]) { + if (mag[2] > mag[1]) { + // 0 - 2 - 1 + in0 = 0; + in2 = 1; + in1 = 2;// xzy + } else { + // 0 - 1 - 2 + in0 = 0; + in1 = 1; + in2 = 2; // xyz + } + } else { + // 2 - 0 - 1 + in2 = 0; + in0 = 1; + in1 = 2; // zxy + } + } else { // y > x 1>0 + if (mag[1] > mag[2]) { + if (mag[2] > mag[0]) { + // 1 - 2 - 0 + in1 = 0; + in2 = 1; + in0 = 2; // yzx + } else { + // 1 - 0 - 2 + in1 = 0; + in0 = 1; + in2 = 2; // yxz + } + } else { + // 2 - 1 - 0 + in2 = 0; + in1 = 1; + in0 = 2; // zyx + } + } + + index = out[in0]; + outScale[0] = scales[index]; + + index = out[in1]; + outScale[1] = scales[index]; + + index = out[in2]; + outScale[2] = scales[index]; + + index = out[in0]; + outRot[0] = rot[index]; + + index = out[in0] + 3; + outRot[0 + 3] = rot[index]; + + index = out[in0] + 6; + outRot[0 + 6] = rot[index]; + + index = out[in1]; + outRot[1] = rot[index]; + + index = out[in1] + 3; + outRot[1 + 3] = rot[index]; + + index = out[in1] + 6; + outRot[1 + 6] = rot[index]; + + index = out[in2]; + outRot[2] = rot[index]; + + index = out[in2] + 3; + outRot[2 + 3] = rot[index]; + + index = out[in2] + 6; + outRot[2 + 6] = rot[index]; + } + } + + static void transpose_mat(double[] in, double[] out) { + out[0] = in[0]; + out[1] = in[3]; + out[2] = in[6]; + + out[3] = in[1]; + out[4] = in[4]; + out[5] = in[7]; + + out[6] = in[2]; + out[7] = in[5]; + out[8] = in[8]; + } + + /** + * The first matrix element in the first row. + */ + public double m00; + + /** + * The second matrix element in the first row. + */ + public double m01; + + /** + * The third matrix element in the first row. + */ + public double m02; + + /** + * The first matrix element in the second row. + */ + public double m10; + + /** + * The second matrix element in the second row. + */ + public double m11; + + /** + * The third matrix element in the second row. + */ + public double m12; + + /** + * The first matrix element in the third row. + */ + public double m20; + + /** + * The second matrix element in the third row. + */ + public double m21; + + /** + * The third matrix element in the third row. + */ + public double m22; + + // double[] tmp = new double[9]; // scratch matrix + // double[] tmp_rot = new double[9]; // scratch matrix + // double[] tmp_scale = new double[3]; // scratch matrix + private static final double EPS = 1.110223024E-16; + + private static final double ERR_EPS = 1.0E-8; + + private static double xin, yin, zin, xout, yout, zout; + + /** + * Constructs and initializes a Matrix3d to all zeros. + */ + public Matrix3d() { + this.m00 = 0.0; + this.m01 = 0.0; + this.m02 = 0.0; + + this.m10 = 0.0; + this.m11 = 0.0; + this.m12 = 0.0; + + this.m20 = 0.0; + this.m21 = 0.0; + this.m22 = 0.0; + + } + + /** + * Constructs and initializes a Matrix3d from the specified nine values. + * + * @param m00 + * the [0][0] element + * @param m01 + * the [0][1] element + * @param m02 + * the [0][2] element + * @param m10 + * the [1][0] element + * @param m11 + * the [1][1] element + * @param m12 + * the [1][2] element + * @param m20 + * the [2][0] element + * @param m21 + * the [2][1] element + * @param m22 + * the [2][2] element + */ + public Matrix3d(double m00, double m01, double m02, double m10, double m11, + double m12, double m20, double m21, double m22) { + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + + } + + /** + * Constructs and initializes a Matrix3d from the specified nine- element + * array. + * + * @param v + * the array of length 9 containing in order + */ + public Matrix3d(double[] v) { + this.m00 = v[0]; + this.m01 = v[1]; + this.m02 = v[2]; + + this.m10 = v[3]; + this.m11 = v[4]; + this.m12 = v[5]; + + this.m20 = v[6]; + this.m21 = v[7]; + this.m22 = v[8]; + + } + + /** + * Constructs a new matrix with the same values as the Matrix3d parameter. + * + * @param m1 + * the source matrix + */ + public Matrix3d(Matrix3d m1) { + this.m00 = m1.m00; + this.m01 = m1.m01; + this.m02 = m1.m02; + + this.m10 = m1.m10; + this.m11 = m1.m11; + this.m12 = m1.m12; + + this.m20 = m1.m20; + this.m21 = m1.m21; + this.m22 = m1.m22; + + } + + /** + * Adds a scalar to each component of this matrix. + * + * @param scalar + * the scalar adder + */ + public final void add(double scalar) { + m00 += scalar; + m01 += scalar; + m02 += scalar; + + m10 += scalar; + m11 += scalar; + m12 += scalar; + + m20 += scalar; + m21 += scalar; + m22 += scalar; + + } + + /** + * Adds a scalar to each component of the matrix m1 and places the result + * into this. Matrix m1 is not modified. + * + * @param scalar + * the scalar adder + * @param m1 + * the original matrix values + */ + public final void add(double scalar, Matrix3d m1) { + this.m00 = m1.m00 + scalar; + this.m01 = m1.m01 + scalar; + this.m02 = m1.m02 + scalar; + + this.m10 = m1.m10 + scalar; + this.m11 = m1.m11 + scalar; + this.m12 = m1.m12 + scalar; + + this.m20 = m1.m20 + scalar; + this.m21 = m1.m21 + scalar; + this.m22 = m1.m22 + scalar; + } + + /** + * Sets the value of this matrix to the sum of itself and matrix m1. + * + * @param m1 + * the other matrix + */ + public final void add(Matrix3d m1) { + this.m00 += m1.m00; + this.m01 += m1.m01; + this.m02 += m1.m02; + + this.m10 += m1.m10; + this.m11 += m1.m11; + this.m12 += m1.m12; + + this.m20 += m1.m20; + this.m21 += m1.m21; + this.m22 += m1.m22; + } + + /** + * Sets the value of this matrix to the matrix sum of matrices m1 and m2. + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void add(Matrix3d m1, Matrix3d m2) { + this.m00 = m1.m00 + m2.m00; + this.m01 = m1.m01 + m2.m01; + this.m02 = m1.m02 + m2.m02; + + this.m10 = m1.m10 + m2.m10; + this.m11 = m1.m11 + m2.m11; + this.m12 = m1.m12 + m2.m12; + + this.m20 = m1.m20 + m2.m20; + this.m21 = m1.m21 + m2.m21; + this.m22 = m1.m22 + m2.m22; + } + + /** + * Creates a new object of the same class as this object. + * + * @return a clone of this instance. + * @exception OutOfMemoryError + * if there is not enough memory. + * @see Cloneable + * @since vecmath 1.3 + */ + public Object clone() { + Matrix3d m1 = null; + try { + m1 = (Matrix3d) super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + + // Also need to create new tmp arrays (no need to actually clone them) + return m1; + } + + /** + * Computes the determinant of this matrix. + * + * @return the determinant of the matrix + */ + public final double determinant() { + double total; + + total = this.m00 * (this.m11 * this.m22 - this.m12 * this.m21) + + this.m01 * (this.m12 * this.m20 - this.m10 * this.m22) + + this.m02 * (this.m10 * this.m21 - this.m11 * this.m20); + return total; + } + + /** + * Returns true if the L-infinite distance between this matrix and matrix m1 + * is less than or equal to the epsilon parameter, otherwise returns false. + * The L-infinite distance is equal to MAX[i=0,1,2 ; j=0,1,2 ; + * abs(this.m(i,j) - m1.m(i,j)] + * + * @param m1 + * the matrix to be compared to this matrix + * @param epsilon + * the threshold value + */ + public boolean epsilonEquals(Matrix3d m1, double epsilon) { + double diff; + + diff = m00 - m1.m00; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m01 - m1.m01; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m02 - m1.m02; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m10 - m1.m10; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m11 - m1.m11; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m12 - m1.m12; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m20 - m1.m20; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m21 - m1.m21; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + diff = m22 - m1.m22; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + + return true; + } + + /** + * Returns true if all of the data members of Matrix3d m1 are equal to the + * corresponding data members in this Matrix3d. + * + * @param m1 + * the matrix with which the comparison is made + * @return true or false + */ + public boolean equals(Matrix3d m1) { + try { + return (this.m00 == m1.m00 && this.m01 == m1.m01 + && this.m02 == m1.m02 && this.m10 == m1.m10 + && this.m11 == m1.m11 && this.m12 == m1.m12 + && this.m20 == m1.m20 && this.m21 == m1.m21 && this.m22 == m1.m22); + } catch (NullPointerException e2) { + return false; + } + + } + + /** + * Returns true if the Object t1 is of type Matrix3d and all of the data + * members of t1 are equal to the corresponding data members in this + * Matrix3d. + * + * @param t1 + * the matrix with which the comparison is made + * @return true or false + */ + public boolean equals(Object t1) { + try { + Matrix3d m2 = (Matrix3d) t1; + return (this.m00 == m2.m00 && this.m01 == m2.m01 + && this.m02 == m2.m02 && this.m10 == m2.m10 + && this.m11 == m2.m11 && this.m12 == m2.m12 + && this.m20 == m2.m20 && this.m21 == m2.m21 && this.m22 == m2.m22); + } catch (ClassCastException e1) { + return false; + } catch (NullPointerException e2) { + return false; + } + + } + + /** + * Retrieves the value at the specified row and column of the specified + * matrix. + * + * @param row + * the row number to be retrieved (zero indexed) + * @param column + * the column number to be retrieved (zero indexed) + * @return the value at the indexed element. + */ + public final double get(int row, int column) { + switch (row) { + case 0: + switch (column) { + case 0: + return (this.m00); + case 1: + return (this.m01); + case 2: + return (this.m02); + default: + break; + } + break; + case 1: + switch (column) { + case 0: + return (this.m10); + case 1: + return (this.m11); + case 2: + return (this.m12); + default: + break; + } + break; + + case 2: + switch (column) { + case 0: + return (this.m20); + case 1: + return (this.m21); + case 2: + return (this.m22); + default: + break; + } + break; + + default: + break; + } + throw new ArrayIndexOutOfBoundsException(); + } + + /** + * Copies the matrix values in the specified column into the array + * parameter. + * + * @param column + * the matrix column + * @param v + * the array into which the matrix row values will be copied + */ + public final void getColumn(int column, double v[]) { + if (column == 0) { + v[0] = m00; + v[1] = m10; + v[2] = m20; + } else if (column == 1) { + v[0] = m01; + v[1] = m11; + v[2] = m21; + } else if (column == 2) { + v[0] = m02; + v[1] = m12; + v[2] = m22; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + + } + + /** + * Copies the matrix values in the specified column into the vector + * parameter. + * + * @param column + * the matrix column + * @param v + * the vector into which the matrix row values will be copied + */ + public final void getColumn(int column, Vec3D v) { + if (column == 0) { + v.x = (float) m00; + v.y = (float) m10; + v.z = (float) m20; + } else if (column == 1) { + v.x = (float) m01; + v.y = (float) m11; + v.z = (float) m21; + } else if (column == 2) { + v.x = (float) m02; + v.y = (float) m12; + v.z = (float) m22; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + + } + + /** + * Copies the matrix values in the specified row into the array parameter. + * + * @param row + * the matrix row + * @param v + * the array into which the matrix row values will be copied + */ + public final void getRow(int row, double v[]) { + if (row == 0) { + v[0] = m00; + v[1] = m01; + v[2] = m02; + } else if (row == 1) { + v[0] = m10; + v[1] = m11; + v[2] = m12; + } else if (row == 2) { + v[0] = m20; + v[1] = m21; + v[2] = m22; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Copies the matrix values in the specified row into the vector parameter. + * + * @param row + * the matrix row + * @param v + * the vector into which the matrix row values will be copied + */ + public final void getRow(int row, Vec3D v) { + if (row == 0) { + v.x = (float) m00; + v.y = (float) m01; + v.z = (float) m02; + } else if (row == 1) { + v.x = (float) m10; + v.y = (float) m11; + v.z = (float) m12; + } else if (row == 2) { + v.x = (float) m20; + v.y = (float) m21; + v.z = (float) m22; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + + } + + /** + * Performs an SVD normalization of this matrix to calculate and return the + * uniform scale factor. If the matrix has non-uniform scale factors, the + * largest of the x, y, and z scale factors will be returned. This matrix is + * not modified. + * + * @return the scale factor of this matrix + */ + public final double getScale() { + double[] tmp_scale = new double[3]; + double[] tmp_rot = new double[9]; + getScaleRotate(tmp_scale, tmp_rot); + return (MathUtils.max(tmp_scale)); + } + + /** + * perform SVD (if necessary to get rotational component + */ + final void getScaleRotate(double scales[], double rots[]) { + double[] tmp = new double[9]; + tmp[0] = m00; + tmp[1] = m01; + tmp[2] = m02; + + tmp[3] = m10; + tmp[4] = m11; + tmp[5] = m12; + + tmp[6] = m20; + tmp[7] = m21; + tmp[8] = m22; + compute_svd(tmp, scales, rots); + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different Matrix3d objects with identical data values (i.e., + * Matrix3d.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() { + long bits = 1L; + bits = 31L * bits + VecMathUtil.doubleToLongBits(m00); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m01); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m02); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m10); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m11); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m12); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m20); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m21); + bits = 31L * bits + VecMathUtil.doubleToLongBits(m22); + return (int) (bits ^ (bits >> 32)); + } + + /** + * Inverts this matrix in place. + */ + public final void invert() { + invertGeneral(this); + } + + /** + * Sets the value of this matrix to the matrix inverse of the passed matrix + * m1. + * + * @param m1 + * the matrix to be inverted + */ + public final void invert(Matrix3d m1) { + invertGeneral(m1); + } + + /** + * General invert routine. Inverts m1 and places the result in "this". Note + * that this routine handles both the "this" version and the non-"this" + * version. + * + * Also note that since this routine is slow anyway, we won't worry about + * allocating a little bit of garbage. + */ + private final void invertGeneral(Matrix3d m1) { + double result[] = new double[9]; + int row_perm[] = new int[3]; + int i, r, c; + double[] tmp = new double[9]; // scratch matrix + + // Use LU decomposition and backsubstitution code specifically + // for floating-point 3x3 matrices. + + // Copy source matrix to t1tmp + tmp[0] = m1.m00; + tmp[1] = m1.m01; + tmp[2] = m1.m02; + + tmp[3] = m1.m10; + tmp[4] = m1.m11; + tmp[5] = m1.m12; + + tmp[6] = m1.m20; + tmp[7] = m1.m21; + tmp[8] = m1.m22; + + // Calculate LU decomposition: Is the matrix singular? + if (!Matrix4x4.LUDecomposition(tmp, row_perm, 3)) { + // Matrix has no inverse + throw new SingularMatrixException(); + } + + // Perform back substitution on the identity matrix + for (i = 0; i < 9; i++) { + result[i] = 0.0; + } + result[0] = 1.0; + result[4] = 1.0; + result[8] = 1.0; + luBacksubstitution(tmp, row_perm, result); + + this.m00 = result[0]; + this.m01 = result[1]; + this.m02 = result[2]; + + this.m10 = result[3]; + this.m11 = result[4]; + this.m12 = result[5]; + + this.m20 = result[6]; + this.m21 = result[7]; + this.m22 = result[8]; + + } + + /** + * Multiplies each element of this matrix by a scalar. + * + * @param scalar + * The scalar multiplier. + */ + public final void mul(double scalar) { + m00 *= scalar; + m01 *= scalar; + m02 *= scalar; + + m10 *= scalar; + m11 *= scalar; + m12 *= scalar; + + m20 *= scalar; + m21 *= scalar; + m22 *= scalar; + + } + + /** + * Multiplies each element of matrix m1 by a scalar and places the result + * into this. Matrix m1 is not modified. + * + * @param scalar + * the scalar multiplier + * @param m1 + * the original matrix + */ + public final void mul(double scalar, Matrix3d m1) { + this.m00 = scalar * m1.m00; + this.m01 = scalar * m1.m01; + this.m02 = scalar * m1.m02; + + this.m10 = scalar * m1.m10; + this.m11 = scalar * m1.m11; + this.m12 = scalar * m1.m12; + + this.m20 = scalar * m1.m20; + this.m21 = scalar * m1.m21; + this.m22 = scalar * m1.m22; + + } + + /** + * Sets the value of this matrix to the result of multiplying itself with + * matrix m1. + * + * @param m1 + * the other matrix + */ + public final void mul(Matrix3d m1) { + double m00, m01, m02, m10, m11, m12, m20, m21, m22; + + m00 = this.m00 * m1.m00 + this.m01 * m1.m10 + this.m02 * m1.m20; + m01 = this.m00 * m1.m01 + this.m01 * m1.m11 + this.m02 * m1.m21; + m02 = this.m00 * m1.m02 + this.m01 * m1.m12 + this.m02 * m1.m22; + + m10 = this.m10 * m1.m00 + this.m11 * m1.m10 + this.m12 * m1.m20; + m11 = this.m10 * m1.m01 + this.m11 * m1.m11 + this.m12 * m1.m21; + m12 = this.m10 * m1.m02 + this.m11 * m1.m12 + this.m12 * m1.m22; + + m20 = this.m20 * m1.m00 + this.m21 * m1.m10 + this.m22 * m1.m20; + m21 = this.m20 * m1.m01 + this.m21 * m1.m11 + this.m22 * m1.m21; + m22 = this.m20 * m1.m02 + this.m21 * m1.m12 + this.m22 * m1.m22; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + + /** + * Sets the value of this matrix to the result of multiplying the two + * argument matrices together. + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void mul(Matrix3d m1, Matrix3d m2) { + if (this != m1 && this != m2) { + this.m00 = m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20; + this.m01 = m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21; + this.m02 = m1.m00 * m2.m02 + m1.m01 * m2.m12 + m1.m02 * m2.m22; + + this.m10 = m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20; + this.m11 = m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21; + this.m12 = m1.m10 * m2.m02 + m1.m11 * m2.m12 + m1.m12 * m2.m22; + + this.m20 = m1.m20 * m2.m00 + m1.m21 * m2.m10 + m1.m22 * m2.m20; + this.m21 = m1.m20 * m2.m01 + m1.m21 * m2.m11 + m1.m22 * m2.m21; + this.m22 = m1.m20 * m2.m02 + m1.m21 * m2.m12 + m1.m22 * m2.m22; + } else { + double m00, m01, m02, m10, m11, m12, m20, m21, m22; // vars for temp + // result matrix + + m00 = m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20; + m01 = m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21; + m02 = m1.m00 * m2.m02 + m1.m01 * m2.m12 + m1.m02 * m2.m22; + + m10 = m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20; + m11 = m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21; + m12 = m1.m10 * m2.m02 + m1.m11 * m2.m12 + m1.m12 * m2.m22; + + m20 = m1.m20 * m2.m00 + m1.m21 * m2.m10 + m1.m22 * m2.m20; + m21 = m1.m20 * m2.m01 + m1.m21 * m2.m11 + m1.m22 * m2.m21; + m22 = m1.m20 * m2.m02 + m1.m21 * m2.m12 + m1.m22 * m2.m22; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + } + + /** + * Multiplies this matrix by matrix m1, does an SVD normalization of the + * result, and places the result back into this matrix this = + * SVDnorm(this*m1). + * + * @param m1 + * the matrix on the right hand side of the multiplication + */ + public final void mulNormalize(Matrix3d m1) { + + double[] tmp = new double[9]; // scratch matrix + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + + tmp[0] = this.m00 * m1.m00 + this.m01 * m1.m10 + this.m02 * m1.m20; + tmp[1] = this.m00 * m1.m01 + this.m01 * m1.m11 + this.m02 * m1.m21; + tmp[2] = this.m00 * m1.m02 + this.m01 * m1.m12 + this.m02 * m1.m22; + + tmp[3] = this.m10 * m1.m00 + this.m11 * m1.m10 + this.m12 * m1.m20; + tmp[4] = this.m10 * m1.m01 + this.m11 * m1.m11 + this.m12 * m1.m21; + tmp[5] = this.m10 * m1.m02 + this.m11 * m1.m12 + this.m12 * m1.m22; + + tmp[6] = this.m20 * m1.m00 + this.m21 * m1.m10 + this.m22 * m1.m20; + tmp[7] = this.m20 * m1.m01 + this.m21 * m1.m11 + this.m22 * m1.m21; + tmp[8] = this.m20 * m1.m02 + this.m21 * m1.m12 + this.m22 * m1.m22; + + compute_svd(tmp, tmp_scale, tmp_rot); + + this.m00 = tmp_rot[0]; + this.m01 = tmp_rot[1]; + this.m02 = tmp_rot[2]; + + this.m10 = tmp_rot[3]; + this.m11 = tmp_rot[4]; + this.m12 = tmp_rot[5]; + + this.m20 = tmp_rot[6]; + this.m21 = tmp_rot[7]; + this.m22 = tmp_rot[8]; + + } + + /** + * Multiplies matrix m1 by matrix m2, does an SVD normalization of the + * result, and places the result into this matrix this = SVDnorm(m1*m2). + * + * @param m1 + * the matrix on the left hand side of the multiplication + * @param m2 + * the matrix on the right hand side of the multiplication + */ + public final void mulNormalize(Matrix3d m1, Matrix3d m2) { + + double[] tmp = new double[9]; // scratch matrix + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + + tmp[0] = m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20; + tmp[1] = m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21; + tmp[2] = m1.m00 * m2.m02 + m1.m01 * m2.m12 + m1.m02 * m2.m22; + + tmp[3] = m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20; + tmp[4] = m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21; + tmp[5] = m1.m10 * m2.m02 + m1.m11 * m2.m12 + m1.m12 * m2.m22; + + tmp[6] = m1.m20 * m2.m00 + m1.m21 * m2.m10 + m1.m22 * m2.m20; + tmp[7] = m1.m20 * m2.m01 + m1.m21 * m2.m11 + m1.m22 * m2.m21; + tmp[8] = m1.m20 * m2.m02 + m1.m21 * m2.m12 + m1.m22 * m2.m22; + + compute_svd(tmp, tmp_scale, tmp_rot); + + this.m00 = tmp_rot[0]; + this.m01 = tmp_rot[1]; + this.m02 = tmp_rot[2]; + + this.m10 = tmp_rot[3]; + this.m11 = tmp_rot[4]; + this.m12 = tmp_rot[5]; + + this.m20 = tmp_rot[6]; + this.m21 = tmp_rot[7]; + this.m22 = tmp_rot[8]; + + } + + /** + * Multiplies the transpose of matrix m1 times the transpose of matrix m2, + * and places the result into this. + * + * @param m1 + * the matrix on the left hand side of the multiplication + * @param m2 + * the matrix on the right hand side of the multiplication + */ + public final void mulTransposeBoth(Matrix3d m1, Matrix3d m2) { + if (this != m1 && this != m2) { + this.m00 = m1.m00 * m2.m00 + m1.m10 * m2.m01 + m1.m20 * m2.m02; + this.m01 = m1.m00 * m2.m10 + m1.m10 * m2.m11 + m1.m20 * m2.m12; + this.m02 = m1.m00 * m2.m20 + m1.m10 * m2.m21 + m1.m20 * m2.m22; + + this.m10 = m1.m01 * m2.m00 + m1.m11 * m2.m01 + m1.m21 * m2.m02; + this.m11 = m1.m01 * m2.m10 + m1.m11 * m2.m11 + m1.m21 * m2.m12; + this.m12 = m1.m01 * m2.m20 + m1.m11 * m2.m21 + m1.m21 * m2.m22; + + this.m20 = m1.m02 * m2.m00 + m1.m12 * m2.m01 + m1.m22 * m2.m02; + this.m21 = m1.m02 * m2.m10 + m1.m12 * m2.m11 + m1.m22 * m2.m12; + this.m22 = m1.m02 * m2.m20 + m1.m12 * m2.m21 + m1.m22 * m2.m22; + } else { + double m00, m01, m02, m10, m11, m12, m20, m21, m22; // vars for temp + // result matrix + + m00 = m1.m00 * m2.m00 + m1.m10 * m2.m01 + m1.m20 * m2.m02; + m01 = m1.m00 * m2.m10 + m1.m10 * m2.m11 + m1.m20 * m2.m12; + m02 = m1.m00 * m2.m20 + m1.m10 * m2.m21 + m1.m20 * m2.m22; + + m10 = m1.m01 * m2.m00 + m1.m11 * m2.m01 + m1.m21 * m2.m02; + m11 = m1.m01 * m2.m10 + m1.m11 * m2.m11 + m1.m21 * m2.m12; + m12 = m1.m01 * m2.m20 + m1.m11 * m2.m21 + m1.m21 * m2.m22; + + m20 = m1.m02 * m2.m00 + m1.m12 * m2.m01 + m1.m22 * m2.m02; + m21 = m1.m02 * m2.m10 + m1.m12 * m2.m11 + m1.m22 * m2.m12; + m22 = m1.m02 * m2.m20 + m1.m12 * m2.m21 + m1.m22 * m2.m22; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + + } + + /** + * Multiplies the transpose of matrix m1 times matrix m2, and places the + * result into this. + * + * @param m1 + * the matrix on the left hand side of the multiplication + * @param m2 + * the matrix on the right hand side of the multiplication + */ + public final void mulTransposeLeft(Matrix3d m1, Matrix3d m2) { + if (this != m1 && this != m2) { + this.m00 = m1.m00 * m2.m00 + m1.m10 * m2.m10 + m1.m20 * m2.m20; + this.m01 = m1.m00 * m2.m01 + m1.m10 * m2.m11 + m1.m20 * m2.m21; + this.m02 = m1.m00 * m2.m02 + m1.m10 * m2.m12 + m1.m20 * m2.m22; + + this.m10 = m1.m01 * m2.m00 + m1.m11 * m2.m10 + m1.m21 * m2.m20; + this.m11 = m1.m01 * m2.m01 + m1.m11 * m2.m11 + m1.m21 * m2.m21; + this.m12 = m1.m01 * m2.m02 + m1.m11 * m2.m12 + m1.m21 * m2.m22; + + this.m20 = m1.m02 * m2.m00 + m1.m12 * m2.m10 + m1.m22 * m2.m20; + this.m21 = m1.m02 * m2.m01 + m1.m12 * m2.m11 + m1.m22 * m2.m21; + this.m22 = m1.m02 * m2.m02 + m1.m12 * m2.m12 + m1.m22 * m2.m22; + } else { + double m00, m01, m02, m10, m11, m12, m20, m21, m22; // vars for temp + // result matrix + + m00 = m1.m00 * m2.m00 + m1.m10 * m2.m10 + m1.m20 * m2.m20; + m01 = m1.m00 * m2.m01 + m1.m10 * m2.m11 + m1.m20 * m2.m21; + m02 = m1.m00 * m2.m02 + m1.m10 * m2.m12 + m1.m20 * m2.m22; + + m10 = m1.m01 * m2.m00 + m1.m11 * m2.m10 + m1.m21 * m2.m20; + m11 = m1.m01 * m2.m01 + m1.m11 * m2.m11 + m1.m21 * m2.m21; + m12 = m1.m01 * m2.m02 + m1.m11 * m2.m12 + m1.m21 * m2.m22; + + m20 = m1.m02 * m2.m00 + m1.m12 * m2.m10 + m1.m22 * m2.m20; + m21 = m1.m02 * m2.m01 + m1.m12 * m2.m11 + m1.m22 * m2.m21; + m22 = m1.m02 * m2.m02 + m1.m12 * m2.m12 + m1.m22 * m2.m22; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + } + + /** + * Multiplies matrix m1 times the transpose of matrix m2, and places the + * result into this. + * + * @param m1 + * the matrix on the left hand side of the multiplication + * @param m2 + * the matrix on the right hand side of the multiplication + */ + public final void mulTransposeRight(Matrix3d m1, Matrix3d m2) { + if (this != m1 && this != m2) { + this.m00 = m1.m00 * m2.m00 + m1.m01 * m2.m01 + m1.m02 * m2.m02; + this.m01 = m1.m00 * m2.m10 + m1.m01 * m2.m11 + m1.m02 * m2.m12; + this.m02 = m1.m00 * m2.m20 + m1.m01 * m2.m21 + m1.m02 * m2.m22; + + this.m10 = m1.m10 * m2.m00 + m1.m11 * m2.m01 + m1.m12 * m2.m02; + this.m11 = m1.m10 * m2.m10 + m1.m11 * m2.m11 + m1.m12 * m2.m12; + this.m12 = m1.m10 * m2.m20 + m1.m11 * m2.m21 + m1.m12 * m2.m22; + + this.m20 = m1.m20 * m2.m00 + m1.m21 * m2.m01 + m1.m22 * m2.m02; + this.m21 = m1.m20 * m2.m10 + m1.m21 * m2.m11 + m1.m22 * m2.m12; + this.m22 = m1.m20 * m2.m20 + m1.m21 * m2.m21 + m1.m22 * m2.m22; + } else { + double m00, m01, m02, m10, m11, m12, m20, m21, m22; // vars for temp + // result matrix + + m00 = m1.m00 * m2.m00 + m1.m01 * m2.m01 + m1.m02 * m2.m02; + m01 = m1.m00 * m2.m10 + m1.m01 * m2.m11 + m1.m02 * m2.m12; + m02 = m1.m00 * m2.m20 + m1.m01 * m2.m21 + m1.m02 * m2.m22; + + m10 = m1.m10 * m2.m00 + m1.m11 * m2.m01 + m1.m12 * m2.m02; + m11 = m1.m10 * m2.m10 + m1.m11 * m2.m11 + m1.m12 * m2.m12; + m12 = m1.m10 * m2.m20 + m1.m11 * m2.m21 + m1.m12 * m2.m22; + + m20 = m1.m20 * m2.m00 + m1.m21 * m2.m01 + m1.m22 * m2.m02; + m21 = m1.m20 * m2.m10 + m1.m21 * m2.m11 + m1.m22 * m2.m12; + m22 = m1.m20 * m2.m20 + m1.m21 * m2.m21 + m1.m22 * m2.m22; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + } + } + + /** + * Negates the value of this matrix: this = -this. + */ + public final void negate() { + this.m00 = -this.m00; + this.m01 = -this.m01; + this.m02 = -this.m02; + + this.m10 = -this.m10; + this.m11 = -this.m11; + this.m12 = -this.m12; + + this.m20 = -this.m20; + this.m21 = -this.m21; + this.m22 = -this.m22; + + } + + /** + * Sets the value of this matrix equal to the negation of of the Matrix3d + * parameter. + * + * @param m1 + * the source matrix + */ + public final void negate(Matrix3d m1) { + this.m00 = -m1.m00; + this.m01 = -m1.m01; + this.m02 = -m1.m02; + + this.m10 = -m1.m10; + this.m11 = -m1.m11; + this.m12 = -m1.m12; + + this.m20 = -m1.m20; + this.m21 = -m1.m21; + this.m22 = -m1.m22; + + } + + /** + * Performs singular value decomposition normalization of this matrix. + */ + public final void normalize() { + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + + getScaleRotate(tmp_scale, tmp_rot); + + this.m00 = tmp_rot[0]; + this.m01 = tmp_rot[1]; + this.m02 = tmp_rot[2]; + + this.m10 = tmp_rot[3]; + this.m11 = tmp_rot[4]; + this.m12 = tmp_rot[5]; + + this.m20 = tmp_rot[6]; + this.m21 = tmp_rot[7]; + this.m22 = tmp_rot[8]; + + } + + /** + * Perform singular value decomposition normalization of matrix m1 and place + * the normalized values into this. + * + * @param m1 + * Provides the matrix values to be normalized + */ + public final void normalize(Matrix3d m1) { + + double[] tmp = new double[9]; // scratch matrix + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + + tmp[0] = m1.m00; + tmp[1] = m1.m01; + tmp[2] = m1.m02; + + tmp[3] = m1.m10; + tmp[4] = m1.m11; + tmp[5] = m1.m12; + + tmp[6] = m1.m20; + tmp[7] = m1.m21; + tmp[8] = m1.m22; + + compute_svd(tmp, tmp_scale, tmp_rot); + + this.m00 = tmp_rot[0]; + this.m01 = tmp_rot[1]; + this.m02 = tmp_rot[2]; + + this.m10 = tmp_rot[3]; + this.m11 = tmp_rot[4]; + this.m12 = tmp_rot[5]; + + this.m20 = tmp_rot[6]; + this.m21 = tmp_rot[7]; + this.m22 = tmp_rot[8]; + } + + /** + * Perform cross product normalization of this matrix. + */ + + public final void normalizeCP() { + double mag = 1.0 / Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); + m00 = m00 * mag; + m10 = m10 * mag; + m20 = m20 * mag; + + mag = 1.0 / Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); + m01 = m01 * mag; + m11 = m11 * mag; + m21 = m21 * mag; + + m02 = m10 * m21 - m11 * m20; + m12 = m01 * m20 - m00 * m21; + m22 = m00 * m11 - m01 * m10; + } + + /** + * Perform cross product normalization of matrix m1 and place the normalized + * values into this. + * + * @param m1 + * Provides the matrix values to be normalized + */ + public final void normalizeCP(Matrix3d m1) { + double mag = 1.0 / Math.sqrt(m1.m00 * m1.m00 + m1.m10 * m1.m10 + m1.m20 + * m1.m20); + m00 = m1.m00 * mag; + m10 = m1.m10 * mag; + m20 = m1.m20 * mag; + + mag = 1.0 / Math.sqrt(m1.m01 * m1.m01 + m1.m11 * m1.m11 + m1.m21 + * m1.m21); + m01 = m1.m01 * mag; + m11 = m1.m11 * mag; + m21 = m1.m21 * mag; + + m02 = m10 * m21 - m11 * m20; + m12 = m01 * m20 - m00 * m21; + m22 = m00 * m11 - m01 * m10; + } + + /** + * Sets the value of this matrix to a counter clockwise rotation about the x + * axis. + * + * @param angle + * the angle to rotate about the X axis in radians + */ + public final void rotX(double angle) { + double sinAngle, cosAngle; + + sinAngle = Math.sin(angle); + cosAngle = Math.cos(angle); + + this.m00 = 1.0; + this.m01 = 0.0; + this.m02 = 0.0; + + this.m10 = 0.0; + this.m11 = cosAngle; + this.m12 = -sinAngle; + + this.m20 = 0.0; + this.m21 = sinAngle; + this.m22 = cosAngle; + } + + /** + * Sets the value of this matrix to a counter clockwise rotation about the y + * axis. + * + * @param angle + * the angle to rotate about the Y axis in radians + */ + public final void rotY(double angle) { + double sinAngle, cosAngle; + + sinAngle = Math.sin(angle); + cosAngle = Math.cos(angle); + + this.m00 = cosAngle; + this.m01 = 0.0; + this.m02 = sinAngle; + + this.m10 = 0.0; + this.m11 = 1.0; + this.m12 = 0.0; + + this.m20 = -sinAngle; + this.m21 = 0.0; + this.m22 = cosAngle; + } + + /** + * Sets the value of this matrix to a counter clockwise rotation about the z + * axis. + * + * @param angle + * the angle to rotate about the Z axis in radians + */ + public final void rotZ(double angle) { + double sinAngle, cosAngle; + + sinAngle = Math.sin(angle); + cosAngle = Math.cos(angle); + + this.m00 = cosAngle; + this.m01 = -sinAngle; + this.m02 = 0.0; + + this.m10 = sinAngle; + this.m11 = cosAngle; + this.m12 = 0.0; + + this.m20 = 0.0; + this.m21 = 0.0; + this.m22 = 1.0; + } + + /** + * Sets the value of this matrix to a scale matrix with the passed scale + * amount. + * + * @param scale + * the scale factor for the matrix + */ + public final void set(double scale) { + this.m00 = scale; + this.m01 = 0.0; + this.m02 = 0.0; + + this.m10 = 0.0; + this.m11 = scale; + this.m12 = 0.0; + + this.m20 = 0.0; + this.m21 = 0.0; + this.m22 = scale; + } + + /** + * Sets the values in this Matrix3d equal to the row-major array parameter + * (ie, the first three elements of the array will be copied into the first + * row of this matrix, etc.). + * + * @param m + * the double precision array of length 9 + */ + public final void set(double[] m) { + m00 = m[0]; + m01 = m[1]; + m02 = m[2]; + + m10 = m[3]; + m11 = m[4]; + m12 = m[5]; + + m20 = m[6]; + m21 = m[7]; + m22 = m[8]; + + } + + /** + * Sets the value of this matrix to the value of the Matrix3d argument. + * + * @param m1 + * the source matrix3d + */ + public final void set(Matrix3d m1) { + this.m00 = m1.m00; + this.m01 = m1.m01; + this.m02 = m1.m02; + + this.m10 = m1.m10; + this.m11 = m1.m11; + this.m12 = m1.m12; + + this.m20 = m1.m20; + this.m21 = m1.m21; + this.m22 = m1.m22; + } + + /** + * Sets the value of this matrix to the matrix conversion of the single + * precision quaternion argument. + * + * @param q1 + * the quaternion to be converted + */ + public final void set(Quaternion q1) { + this.m00 = (1.0 - 2.0 * q1.y * q1.y - 2.0 * q1.z * q1.z); + this.m10 = (2.0 * (q1.x * q1.y + q1.w * q1.z)); + this.m20 = (2.0 * (q1.x * q1.z - q1.w * q1.y)); + + this.m01 = (2.0 * (q1.x * q1.y - q1.w * q1.z)); + this.m11 = (1.0 - 2.0 * q1.x * q1.x - 2.0 * q1.z * q1.z); + this.m21 = (2.0 * (q1.y * q1.z + q1.w * q1.x)); + + this.m02 = (2.0 * (q1.x * q1.z + q1.w * q1.y)); + this.m12 = (2.0 * (q1.y * q1.z - q1.w * q1.x)); + this.m22 = (1.0 - 2.0 * q1.x * q1.x - 2.0 * q1.y * q1.y); + } + + /** + * Sets the specified column of this matrix3d to the three values provided. + * + * @param column + * the column number to be modified (zero indexed) + * @param v + * the replacement column + */ + public final void setColumn(int column, double v[]) { + switch (column) { + case 0: + this.m00 = v[0]; + this.m10 = v[1]; + this.m20 = v[2]; + break; + + case 1: + this.m01 = v[0]; + this.m11 = v[1]; + this.m21 = v[2]; + break; + + case 2: + this.m02 = v[0]; + this.m12 = v[1]; + this.m22 = v[2]; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified column of this matrix3d to the three values provided. + * + * @param column + * the column number to be modified (zero indexed) + * @param x + * the first row element + * @param y + * the second row element + * @param z + * the third row element + */ + public final void setColumn(int column, double x, double y, double z) { + switch (column) { + case 0: + this.m00 = x; + this.m10 = y; + this.m20 = z; + break; + + case 1: + this.m01 = x; + this.m11 = y; + this.m21 = z; + break; + + case 2: + this.m02 = x; + this.m12 = y; + this.m22 = z; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified column of this matrix3d to the vector provided. + * + * @param column + * the column number to be modified (zero indexed) + * @param v + * the replacement column + */ + public final void setColumn(int column, Vec3D v) { + switch (column) { + case 0: + this.m00 = v.x; + this.m10 = v.y; + this.m20 = v.z; + break; + + case 1: + this.m01 = v.x; + this.m11 = v.y; + this.m21 = v.z; + break; + + case 2: + this.m02 = v.x; + this.m12 = v.y; + this.m22 = v.z; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified element of this matrix3f to the value provided. + * + * @param row + * the row number to be modified (zero indexed) + * @param column + * the column number to be modified (zero indexed) + * @param value + * the new value + */ + public final void setElement(int row, int column, double value) { + switch (row) { + case 0: + switch (column) { + case 0: + this.m00 = value; + break; + case 1: + this.m01 = value; + break; + case 2: + this.m02 = value; + break; + default: + throw new ArrayIndexOutOfBoundsException(); + } + break; + + case 1: + switch (column) { + case 0: + this.m10 = value; + break; + case 1: + this.m11 = value; + break; + case 2: + this.m12 = value; + break; + default: + throw new ArrayIndexOutOfBoundsException(); + } + break; + + case 2: + switch (column) { + case 0: + this.m20 = value; + break; + case 1: + this.m21 = value; + break; + case 2: + this.m22 = value; + break; + default: + throw new ArrayIndexOutOfBoundsException(); + } + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets this Matrix3d to identity. + */ + public final void setIdentity() { + this.m00 = 1.0; + this.m01 = 0.0; + this.m02 = 0.0; + + this.m10 = 0.0; + this.m11 = 1.0; + this.m12 = 0.0; + + this.m20 = 0.0; + this.m21 = 0.0; + this.m22 = 1.0; + } + + /** + * Set the first matrix element in the first row. + * + * @param m00 + * The m00 to set. + * + * @since vecmath 1.5 + */ + public final void setM00(double m00) { + this.m00 = m00; + } + + /** + * Set the second matrix element in the first row. + * + * @param m01 + * The m01 to set. + * + * @since vecmath 1.5 + */ + public final void setM01(double m01) { + this.m01 = m01; + } + + /** + * Set the third matrix element in the first row. + * + * @param m02 + * The m02 to set. + * + * @since vecmath 1.5 + */ + public final void setM02(double m02) { + this.m02 = m02; + } + + /** + * Set first matrix element in the second row. + * + * @param m10 + * The m10 to set. + * + * @since vecmath 1.5 + */ + public final void setM10(double m10) { + this.m10 = m10; + } + + /** + * Set the second matrix element in the second row. + * + * @param m11 + * The m11 to set. + * + * @since vecmath 1.5 + */ + public final void setM11(double m11) { + this.m11 = m11; + } + + /** + * Set the third matrix element in the second row. + * + * @param m12 + * The m12 to set. + * + * @since vecmath 1.5 + */ + public final void setM12(double m12) { + this.m12 = m12; + } + + /** + * Set the first matrix element in the third row. + * + * @param m20 + * The m20 to set. + * + * @since vecmath 1.5 + */ + public final void setM20(double m20) { + this.m20 = m20; + } + + /** + * Set the second matrix element in the third row. + * + * @param m21 + * The m21 to set. + * + * @since vecmath 1.5 + */ + public final void setM21(double m21) { + this.m21 = m21; + } + + /** + * Set the third matrix element in the third row. + * + * @param m22 + * The m22 to set. + * + * @since vecmath 1.5 + */ + public final void setM22(double m22) { + this.m22 = m22; + } + + /** + * Sets the specified row of this matrix3d to the three values provided. + * + * @param row + * the row number to be modified (zero indexed) + * @param v + * the replacement row + */ + public final void setRow(int row, double v[]) { + switch (row) { + case 0: + this.m00 = v[0]; + this.m01 = v[1]; + this.m02 = v[2]; + break; + + case 1: + this.m10 = v[0]; + this.m11 = v[1]; + this.m12 = v[2]; + break; + + case 2: + this.m20 = v[0]; + this.m21 = v[1]; + this.m22 = v[2]; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified row of this matrix3d to the 4 values provided. + * + * @param row + * the row number to be modified (zero indexed) + * @param x + * the first column element + * @param y + * the second column element + * @param z + * the third column element + */ + public final void setRow(int row, double x, double y, double z) { + switch (row) { + case 0: + this.m00 = x; + this.m01 = y; + this.m02 = z; + break; + + case 1: + this.m10 = x; + this.m11 = y; + this.m12 = z; + break; + + case 2: + this.m20 = x; + this.m21 = y; + this.m22 = z; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified row of this matrix3d to the Vector provided. + * + * @param row + * the row number to be modified (zero indexed) + * @param v + * the replacement row + */ + public final void setRow(int row, Vec3D v) { + switch (row) { + case 0: + this.m00 = v.x; + this.m01 = v.y; + this.m02 = v.z; + break; + + case 1: + this.m10 = v.x; + this.m11 = v.y; + this.m12 = v.z; + break; + + case 2: + this.m20 = v.x; + this.m21 = v.y; + this.m22 = v.z; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the scale component of the current matrix by factoring out the + * current scale (by doing an SVD) and multiplying by the new scale. + * + * @param scale + * the new scale amount + */ + public final void setScale(double scale) { + + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + + getScaleRotate(tmp_scale, tmp_rot); + + this.m00 = tmp_rot[0] * scale; + this.m01 = tmp_rot[1] * scale; + this.m02 = tmp_rot[2] * scale; + + this.m10 = tmp_rot[3] * scale; + this.m11 = tmp_rot[4] * scale; + this.m12 = tmp_rot[5] * scale; + + this.m20 = tmp_rot[6] * scale; + this.m21 = tmp_rot[7] * scale; + this.m22 = tmp_rot[8] * scale; + } + + /** + * Sets this matrix to all zeros. + */ + public final void setZero() { + m00 = 0.0; + m01 = 0.0; + m02 = 0.0; + + m10 = 0.0; + m11 = 0.0; + m12 = 0.0; + + m20 = 0.0; + m21 = 0.0; + m22 = 0.0; + + } + + /** + * Sets the value of this matrix to the matrix difference of itself and + * matrix m1 (this = this - m1). + * + * @param m1 + * the other matrix + */ + public final void sub(Matrix3d m1) { + this.m00 -= m1.m00; + this.m01 -= m1.m01; + this.m02 -= m1.m02; + + this.m10 -= m1.m10; + this.m11 -= m1.m11; + this.m12 -= m1.m12; + + this.m20 -= m1.m20; + this.m21 -= m1.m21; + this.m22 -= m1.m22; + } + + /** + * Sets the value of this matrix to the matrix difference of matrices m1 and + * m2. + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void sub(Matrix3d m1, Matrix3d m2) { + this.m00 = m1.m00 - m2.m00; + this.m01 = m1.m01 - m2.m01; + this.m02 = m1.m02 - m2.m02; + + this.m10 = m1.m10 - m2.m10; + this.m11 = m1.m11 - m2.m11; + this.m12 = m1.m12 - m2.m12; + + this.m20 = m1.m20 - m2.m20; + this.m21 = m1.m21 - m2.m21; + this.m22 = m1.m22 - m2.m22; + } + + /** + * Returns a string that contains the values of this Matrix3d. + * + * @return the String representation + */ + public String toString() { + return this.m00 + ", " + this.m01 + ", " + this.m02 + "\n" + this.m10 + + ", " + this.m11 + ", " + this.m12 + "\n" + this.m20 + ", " + + this.m21 + ", " + this.m22 + "\n"; + } + + /** + * Multiply this matrix by the tuple t and place the result back into the + * tuple (t = this*t). + * + * @param t + * the tuple to be multiplied by this matrix and then replaced + */ + public final void transform(Vec3D t) { + float x, y, z; + x = (float) (m00 * t.x + m01 * t.y + m02 * t.z); + y = (float) (m10 * t.x + m11 * t.y + m12 * t.z); + z = (float) (m20 * t.x + m21 * t.y + m22 * t.z); + t.set(x, y, z); + } + + /** + * Multiply this matrix by the tuple t and and place the result into the + * tuple "result" (result = this*t). + * + * @param t + * the tuple to be multiplied by this matrix + * @param result + * the tuple into which the product is placed + */ + public final void transform(Vec3D t, Vec3D result) { + float x, y, z; + x = (float) (m00 * t.x + m01 * t.y + m02 * t.z); + y = (float) (m10 * t.x + m11 * t.y + m12 * t.z); + result.z = (float) (m20 * t.x + m21 * t.y + m22 * t.z); + result.x = x; + result.y = y; + } + + /** + * Sets the value of this matrix to its transpose. + */ + public final void transpose() { + double temp; + + temp = this.m10; + this.m10 = this.m01; + this.m01 = temp; + + temp = this.m20; + this.m20 = this.m02; + this.m02 = temp; + + temp = this.m21; + this.m21 = this.m12; + this.m12 = temp; + } + + /** + * Sets the value of this matrix to the transpose of the argument matrix. + * + * @param m1 + * the matrix to be transposed + */ + public final void transpose(Matrix3d m1) { + if (this != m1) { + this.m00 = m1.m00; + this.m01 = m1.m10; + this.m02 = m1.m20; + + this.m10 = m1.m01; + this.m11 = m1.m11; + this.m12 = m1.m21; + + this.m20 = m1.m02; + this.m21 = m1.m12; + this.m22 = m1.m22; + } else { + this.transpose(); + } + } + +} diff --git a/src/main/java/toxi/geom/Matrix4f.java b/src/main/java/toxi/geom/Matrix4f.java new file mode 100644 index 0000000..bc054e0 --- /dev/null +++ b/src/main/java/toxi/geom/Matrix4f.java @@ -0,0 +1,3419 @@ +/* + * $RCSfile$ + * + * Copyright 1996-2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + * + * $Revision: 127 $ + * $Date: 2008-02-28 20:18:51 +0000 (Thu, 28 Feb 2008) $ + * $State$ + */ + +package toxi.geom; + +import toxi.math.MathUtils; + +/** + * A single precision floating point 4 by 4 matrix. Primarily to support 3D + * rotations. + * + */ +public class Matrix4f implements java.io.Serializable, Cloneable { + + // Compatible with 1.1 + static final long serialVersionUID = -8405036035410109353L; + + /** + * Solves a set of linear equations. The input parameters "matrix1", and + * "row_perm" come from luDecompostionD4x4 and do not change here. The + * parameter "matrix2" is a set of column vectors assembled into a 4x4 + * matrix of floating-point values. The procedure takes each column of + * "matrix2" in turn and treats it as the right-hand side of the matrix + * equation Ax = LUx = b. The solution vector replaces the original column + * of the matrix. + * + * If "matrix2" is the identity matrix, the procedure replaces its contents + * with the inverse of the matrix from which "matrix1" was originally + * derived. + */ + // + // Reference: Press, Flannery, Teukolsky, Vetterling, + // _Numerical_Recipes_in_C_, Cambridge University Press, + // 1988, pp 44-45. + // + static void luBacksubstitution(double[] matrix1, int[] row_perm, + double[] matrix2) { + + int i, ii, ip, j, k; + int rp; + int cv, rv; + + // rp = row_perm; + rp = 0; + + // For each column vector of matrix2 ... + for (k = 0; k < 4; k++) { + // cv = &(matrix2[0][k]); + cv = k; + ii = -1; + + // Forward substitution + for (i = 0; i < 4; i++) { + double sum; + + ip = row_perm[rp + i]; + sum = matrix2[cv + 4 * ip]; + matrix2[cv + 4 * ip] = matrix2[cv + 4 * i]; + if (ii >= 0) { + // rv = &(matrix1[i][0]); + rv = i * 4; + for (j = ii; j <= i - 1; j++) { + sum -= matrix1[rv + j] * matrix2[cv + 4 * j]; + } + } else if (sum != 0.0) { + ii = i; + } + matrix2[cv + 4 * i] = sum; + } + + // Backsubstitution + // rv = &(matrix1[3][0]); + rv = 3 * 4; + matrix2[cv + 4 * 3] /= matrix1[rv + 3]; + + rv -= 4; + matrix2[cv + 4 * 2] = (matrix2[cv + 4 * 2] - matrix1[rv + 3] + * matrix2[cv + 4 * 3]) + / matrix1[rv + 2]; + + rv -= 4; + matrix2[cv + 4 * 1] = (matrix2[cv + 4 * 1] - matrix1[rv + 2] + * matrix2[cv + 4 * 2] - matrix1[rv + 3] + * matrix2[cv + 4 * 3]) + / matrix1[rv + 1]; + + rv -= 4; + matrix2[cv + 4 * 0] = (matrix2[cv + 4 * 0] - matrix1[rv + 1] + * matrix2[cv + 4 * 1] - matrix1[rv + 2] + * matrix2[cv + 4 * 2] - matrix1[rv + 3] + * matrix2[cv + 4 * 3]) + / matrix1[rv + 0]; + } + } + + /** + * The first element of the first row. + */ + public float m00; + + /** + * The second element of the first row. + */ + public float m01; + + /** + * The third element of the first row. + */ + public float m02; + + /** + * The fourth element of the first row. + */ + public float m03; + + /** + * The first element of the second row. + */ + public float m10; + + /** + * The second element of the second row. + */ + public float m11; + + /** + * The third element of the second row. + */ + public float m12; + + /** + * The fourth element of the second row. + */ + public float m13; + + /** + * The first element of the third row. + */ + public float m20; + + /** + * The second element of the third row. + */ + public float m21; + + /** + * The third element of the third row. + */ + public float m22; + + /** + * The fourth element of the third row. + */ + public float m23; + + /** + * The first element of the fourth row. + */ + public float m30; + + /** + * The second element of the fourth row. + */ + public float m31; + /** + * The third element of the fourth row. + */ + public float m32; + + /** + * The fourth element of the fourth row. + */ + public float m33; + + /* + * double[] tmp = new double[9]; double[] tmp_scale = new double[3]; + * double[] tmp_rot = new double[9]; + */ + private static final double EPS = 1.0E-8; + + /** + * Constructs and initializes a Matrix4f to all zeros. + */ + public Matrix4f() { + this.m00 = (float) 0.0; + this.m01 = (float) 0.0; + this.m02 = (float) 0.0; + this.m03 = (float) 0.0; + + this.m10 = (float) 0.0; + this.m11 = (float) 0.0; + this.m12 = (float) 0.0; + this.m13 = (float) 0.0; + + this.m20 = (float) 0.0; + this.m21 = (float) 0.0; + this.m22 = (float) 0.0; + this.m23 = (float) 0.0; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 0.0; + + } + + /** + * Constructs and initializes a Matrix4f from the specified 16 values. + * + * @param m00 + * the [0][0] element + * @param m01 + * the [0][1] element + * @param m02 + * the [0][2] element + * @param m03 + * the [0][3] element + * @param m10 + * the [1][0] element + * @param m11 + * the [1][1] element + * @param m12 + * the [1][2] element + * @param m13 + * the [1][3] element + * @param m20 + * the [2][0] element + * @param m21 + * the [2][1] element + * @param m22 + * the [2][2] element + * @param m23 + * the [2][3] element + * @param m30 + * the [3][0] element + * @param m31 + * the [3][1] element + * @param m32 + * the [3][2] element + * @param m33 + * the [3][3] element + */ + public Matrix4f(float m00, float m01, float m02, float m03, float m10, + float m11, float m12, float m13, float m20, float m21, float m22, + float m23, float m30, float m31, float m32, float m33) { + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + + } + + /** + * Constructs and initializes a Matrix4f from the specified 16 element + * array. this.m00 =v[0], this.m01=v[1], etc. + * + * @param v + * the array of length 16 containing in order + */ + public Matrix4f(float[] v) { + this.m00 = v[0]; + this.m01 = v[1]; + this.m02 = v[2]; + this.m03 = v[3]; + + this.m10 = v[4]; + this.m11 = v[5]; + this.m12 = v[6]; + this.m13 = v[7]; + + this.m20 = v[8]; + this.m21 = v[9]; + this.m22 = v[10]; + this.m23 = v[11]; + + this.m30 = v[12]; + this.m31 = v[13]; + this.m32 = v[14]; + this.m33 = v[15]; + + } + + /** + * Constructs and initializes a Matrix4f from the rotation matrix, + * translation, and scale values; the scale is applied only to the + * rotational components of the matrix (upper 3x3) and not to the + * translational components of the matrix. + * + * @param m1 + * the rotation matrix representing the rotational components + * @param t1 + * the translational components of the matrix + * @param s + * the scale value applied to the rotational components + */ + public Matrix4f(Matrix3d m1, Vec3D t1, float s) { + this.m00 = (float) (m1.m00 * s); + this.m01 = (float) (m1.m01 * s); + this.m02 = (float) (m1.m02 * s); + this.m03 = t1.x; + + this.m10 = (float) (m1.m10 * s); + this.m11 = (float) (m1.m11 * s); + this.m12 = (float) (m1.m12 * s); + this.m13 = t1.y; + + this.m20 = (float) (m1.m20 * s); + this.m21 = (float) (m1.m21 * s); + this.m22 = (float) (m1.m22 * s); + this.m23 = t1.z; + + this.m30 = 0.0f; + this.m31 = 0.0f; + this.m32 = 0.0f; + this.m33 = 1.0f; + + } + + /** + * Constructs a new matrix with the same values as the Matrix4f parameter. + * + * @param m1 + * the source matrix + */ + public Matrix4f(Matrix4f m1) { + this.m00 = m1.m00; + this.m01 = m1.m01; + this.m02 = m1.m02; + this.m03 = m1.m03; + + this.m10 = m1.m10; + this.m11 = m1.m11; + this.m12 = m1.m12; + this.m13 = m1.m13; + + this.m20 = m1.m20; + this.m21 = m1.m21; + this.m22 = m1.m22; + this.m23 = m1.m23; + + this.m30 = m1.m30; + this.m31 = m1.m31; + this.m32 = m1.m32; + this.m33 = m1.m33; + + } + + /** + * Constructs and initializes a Matrix4f from the quaternion, translation, + * and scale values; the scale is applied only to the rotational components + * of the matrix (upper 3x3) and not to the translational components. + * + * @param q1 + * the quaternion value representing the rotational component + * @param t1 + * the translational component of the matrix + * @param s + * the scale value applied to the rotational components + */ + public Matrix4f(Quaternion q1, Vec3D t1, float s) { + m00 = (float) (s * (1.0 - 2.0 * q1.y * q1.y - 2.0 * q1.z * q1.z)); + m10 = (float) (s * (2.0 * (q1.x * q1.y + q1.w * q1.z))); + m20 = (float) (s * (2.0 * (q1.x * q1.z - q1.w * q1.y))); + + m01 = (float) (s * (2.0 * (q1.x * q1.y - q1.w * q1.z))); + m11 = (float) (s * (1.0 - 2.0 * q1.x * q1.x - 2.0 * q1.z * q1.z)); + m21 = (float) (s * (2.0 * (q1.y * q1.z + q1.w * q1.x))); + + m02 = (float) (s * (2.0 * (q1.x * q1.z + q1.w * q1.y))); + m12 = (float) (s * (2.0 * (q1.y * q1.z - q1.w * q1.x))); + m22 = (float) (s * (1.0 - 2.0 * q1.x * q1.x - 2.0 * q1.y * q1.y)); + + m03 = t1.x; + m13 = t1.y; + m23 = t1.z; + + m30 = 0.0f; + m31 = 0.0f; + m32 = 0.0f; + m33 = 1.0f; + + } + + /** + * Adds a scalar to each component of this matrix. + * + * @param scalar + * the scalar adder + */ + public final void add(float scalar) { + m00 += scalar; + m01 += scalar; + m02 += scalar; + m03 += scalar; + m10 += scalar; + m11 += scalar; + m12 += scalar; + m13 += scalar; + m20 += scalar; + m21 += scalar; + m22 += scalar; + m23 += scalar; + m30 += scalar; + m31 += scalar; + m32 += scalar; + m33 += scalar; + } + + /** + * Adds a scalar to each component of the matrix m1 and places the result + * into this. Matrix m1 is not modified. + * + * @param scalar + * the scalar adder + * @param m1 + * the original matrix values + */ + public final void add(float scalar, Matrix4f m1) { + this.m00 = m1.m00 + scalar; + this.m01 = m1.m01 + scalar; + this.m02 = m1.m02 + scalar; + this.m03 = m1.m03 + scalar; + this.m10 = m1.m10 + scalar; + this.m11 = m1.m11 + scalar; + this.m12 = m1.m12 + scalar; + this.m13 = m1.m13 + scalar; + this.m20 = m1.m20 + scalar; + this.m21 = m1.m21 + scalar; + this.m22 = m1.m22 + scalar; + this.m23 = m1.m23 + scalar; + this.m30 = m1.m30 + scalar; + this.m31 = m1.m31 + scalar; + this.m32 = m1.m32 + scalar; + this.m33 = m1.m33 + scalar; + } + + /** + * Sets the value of this matrix to the sum of itself and matrix m1. + * + * @param m1 + * the other matrix + */ + public final void add(Matrix4f m1) { + this.m00 += m1.m00; + this.m01 += m1.m01; + this.m02 += m1.m02; + this.m03 += m1.m03; + + this.m10 += m1.m10; + this.m11 += m1.m11; + this.m12 += m1.m12; + this.m13 += m1.m13; + + this.m20 += m1.m20; + this.m21 += m1.m21; + this.m22 += m1.m22; + this.m23 += m1.m23; + + this.m30 += m1.m30; + this.m31 += m1.m31; + this.m32 += m1.m32; + this.m33 += m1.m33; + } + + /** + * Sets the value of this matrix to the matrix sum of matrices m1 and m2. + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void add(Matrix4f m1, Matrix4f m2) { + this.m00 = m1.m00 + m2.m00; + this.m01 = m1.m01 + m2.m01; + this.m02 = m1.m02 + m2.m02; + this.m03 = m1.m03 + m2.m03; + + this.m10 = m1.m10 + m2.m10; + this.m11 = m1.m11 + m2.m11; + this.m12 = m1.m12 + m2.m12; + this.m13 = m1.m13 + m2.m13; + + this.m20 = m1.m20 + m2.m20; + this.m21 = m1.m21 + m2.m21; + this.m22 = m1.m22 + m2.m22; + this.m23 = m1.m23 + m2.m23; + + this.m30 = m1.m30 + m2.m30; + this.m31 = m1.m31 + m2.m31; + this.m32 = m1.m32 + m2.m32; + this.m33 = m1.m33 + m2.m33; + } + + /** + * Creates a new object of the same class as this object. + * + * @return a clone of this instance. + * @exception OutOfMemoryError + * if there is not enough memory. + * @see Cloneable + * @since vecmath 1.3 + */ + public Object clone() { + Matrix4f m1 = null; + try { + m1 = (Matrix4f) super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + + return m1; + } + + /** + * Computes the determinate of this matrix. + * + * @return the determinate of the matrix + */ + public final float determinant() { + float det; + + // cofactor exapainsion along first row + + det = m00 + * (m11 * m22 * m33 + m12 * m23 * m31 + m13 * m21 * m32 - m13 + * m22 * m31 - m11 * m23 * m32 - m12 * m21 * m33); + det -= m01 + * (m10 * m22 * m33 + m12 * m23 * m30 + m13 * m20 * m32 - m13 + * m22 * m30 - m10 * m23 * m32 - m12 * m20 * m33); + det += m02 + * (m10 * m21 * m33 + m11 * m23 * m30 + m13 * m20 * m31 - m13 + * m21 * m30 - m10 * m23 * m31 - m11 * m20 * m33); + det -= m03 + * (m10 * m21 * m32 + m11 * m22 * m30 + m12 * m20 * m31 - m12 + * m21 * m30 - m10 * m22 * m31 - m11 * m20 * m32); + + return (det); + } + + /** + * Returns true if the L-infinite distance between this matrix and matrix m1 + * is less than or equal to the epsilon parameter, otherwise returns false. + * The L-infinite distance is equal to MAX[i=0,1,2,3 ; j=0,1,2,3 ; + * abs(this.m(i,j) - m1.m(i,j)] + * + * @param m1 + * the matrix to be compared to this matrix + * @param epsilon + * the threshold value + */ + public boolean epsilonEquals(Matrix4f m1, float epsilon) { + + boolean status = true; + + if (Math.abs(this.m00 - m1.m00) > epsilon) { + status = false; + } + if (Math.abs(this.m01 - m1.m01) > epsilon) { + status = false; + } + if (Math.abs(this.m02 - m1.m02) > epsilon) { + status = false; + } + if (Math.abs(this.m03 - m1.m03) > epsilon) { + status = false; + } + + if (Math.abs(this.m10 - m1.m10) > epsilon) { + status = false; + } + if (Math.abs(this.m11 - m1.m11) > epsilon) { + status = false; + } + if (Math.abs(this.m12 - m1.m12) > epsilon) { + status = false; + } + if (Math.abs(this.m13 - m1.m13) > epsilon) { + status = false; + } + + if (Math.abs(this.m20 - m1.m20) > epsilon) { + status = false; + } + if (Math.abs(this.m21 - m1.m21) > epsilon) { + status = false; + } + if (Math.abs(this.m22 - m1.m22) > epsilon) { + status = false; + } + if (Math.abs(this.m23 - m1.m23) > epsilon) { + status = false; + } + + if (Math.abs(this.m30 - m1.m30) > epsilon) { + status = false; + } + if (Math.abs(this.m31 - m1.m31) > epsilon) { + status = false; + } + if (Math.abs(this.m32 - m1.m32) > epsilon) { + status = false; + } + if (Math.abs(this.m33 - m1.m33) > epsilon) { + status = false; + } + + return (status); + + } + + /** + * Returns true if all of the data members of Matrix4f m1 are equal to the + * corresponding data members in this Matrix4f. + * + * @param m1 + * the matrix with which the comparison is made. + * @return true or false + */ + public boolean equals(Matrix4f m1) { + try { + return (this.m00 == m1.m00 && this.m01 == m1.m01 + && this.m02 == m1.m02 && this.m03 == m1.m03 + && this.m10 == m1.m10 && this.m11 == m1.m11 + && this.m12 == m1.m12 && this.m13 == m1.m13 + && this.m20 == m1.m20 && this.m21 == m1.m21 + && this.m22 == m1.m22 && this.m23 == m1.m23 + && this.m30 == m1.m30 && this.m31 == m1.m31 + && this.m32 == m1.m32 && this.m33 == m1.m33); + } catch (NullPointerException e2) { + return false; + } + + } + + /** + * Returns true if the Object t1 is of type Matrix4f and all of the data + * members of t1 are equal to the corresponding data members in this + * Matrix4f. + * + * @param t1 + * the matrix with which the comparison is made. + * @return true or false + */ + public boolean equals(Object t1) { + try { + Matrix4f m2 = (Matrix4f) t1; + return (this.m00 == m2.m00 && this.m01 == m2.m01 + && this.m02 == m2.m02 && this.m03 == m2.m03 + && this.m10 == m2.m10 && this.m11 == m2.m11 + && this.m12 == m2.m12 && this.m13 == m2.m13 + && this.m20 == m2.m20 && this.m21 == m2.m21 + && this.m22 == m2.m22 && this.m23 == m2.m23 + && this.m30 == m2.m30 && this.m31 == m2.m31 + && this.m32 == m2.m32 && this.m33 == m2.m33); + } catch (ClassCastException e1) { + return false; + } catch (NullPointerException e2) { + return false; + } + } + + /** + * Performs an SVD normalization of this matrix in order to acquire the + * normalized rotational component; the values are placed into the Matrix3d + * parameter. + * + * @param m1 + * matrix into which the rotational component is placed + */ + public final void get(Matrix3d m1) { + + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + + getScaleRotate(tmp_scale, tmp_rot); + + m1.m00 = tmp_rot[0]; + m1.m01 = tmp_rot[1]; + m1.m02 = tmp_rot[2]; + + m1.m10 = tmp_rot[3]; + m1.m11 = tmp_rot[4]; + m1.m12 = tmp_rot[5]; + + m1.m20 = tmp_rot[6]; + m1.m21 = tmp_rot[7]; + m1.m22 = tmp_rot[8]; + + } + + /** + * Performs an SVD normalization of this matrix to calculate the rotation as + * a 3x3 matrix, the translation, and the scale. None of the matrix values + * are modified. + * + * @param m1 + * the normalized matrix representing the rotation + * @param t1 + * the translation component + * @return the scale component of this transform + */ + public final double get(Matrix3d m1, Vec3D t1) { + double[] tmp_rot = new double[9]; + double[] tmp_scale = new double[3]; + getScaleRotate(tmp_scale, tmp_rot); + + m1.m00 = tmp_rot[0]; + m1.m01 = tmp_rot[1]; + m1.m02 = tmp_rot[2]; + + m1.m10 = tmp_rot[3]; + m1.m11 = tmp_rot[4]; + m1.m12 = tmp_rot[5]; + + m1.m20 = tmp_rot[6]; + m1.m21 = tmp_rot[7]; + m1.m22 = tmp_rot[8]; + + t1.x = m03; + t1.y = m13; + t1.z = m23; + + return MathUtils.max(tmp_scale); + + } + + /** + * Performs an SVD normalization of this matrix in order to acquire the + * normalized rotational component; the values are placed into the Quat4f + * parameter. + * + * @param q1 + * quaternion into which the rotation component is placed + */ + public final void get(Quaternion q1) { + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + getScaleRotate(tmp_scale, tmp_rot); + + double ww; + + ww = 0.25 * (1.0 + tmp_rot[0] + tmp_rot[4] + tmp_rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-30)) { + q1.w = (float) Math.sqrt(ww); + ww = 0.25 / q1.w; + q1.x = (float) ((tmp_rot[7] - tmp_rot[5]) * ww); + q1.y = (float) ((tmp_rot[2] - tmp_rot[6]) * ww); + q1.z = (float) ((tmp_rot[3] - tmp_rot[1]) * ww); + return; + } + + q1.w = 0.0f; + ww = -0.5 * (tmp_rot[4] + tmp_rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-30)) { + q1.x = (float) Math.sqrt(ww); + ww = 0.5 / q1.x; + q1.y = (float) (tmp_rot[3] * ww); + q1.z = (float) (tmp_rot[6] * ww); + return; + } + + q1.x = 0.0f; + ww = 0.5 * (1.0 - tmp_rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-30)) { + q1.y = (float) (Math.sqrt(ww)); + q1.z = (float) (tmp_rot[7] / (2.0 * q1.y)); + return; + } + + q1.y = 0.0f; + q1.z = 1.0f; + + } + + /** + * Retrieves the translational components of this matrix. + * + * @param trans + * the vector that will receive the translational component + */ + public final void get(Vec3D trans) { + trans.x = m03; + trans.y = m13; + trans.z = m23; + } + + /** + * Copies the matrix values in the specified column into the array + * parameter. + * + * @param column + * the matrix column + * @param v + * the array into which the matrix row values will be copied + */ + public final void getColumn(int column, float v[]) { + if (column == 0) { + v[0] = m00; + v[1] = m10; + v[2] = m20; + v[3] = m30; + } else if (column == 1) { + v[0] = m01; + v[1] = m11; + v[2] = m21; + v[3] = m31; + } else if (column == 2) { + v[0] = m02; + v[1] = m12; + v[2] = m22; + v[3] = m32; + } else if (column == 3) { + v[0] = m03; + v[1] = m13; + v[2] = m23; + v[3] = m33; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + + } + + /** + * Copies the matrix values in the specified column into the vector + * parameter. + * + * @param column + * the matrix column + * @param v + * the vector into which the matrix row values will be copied + */ + public final void getColumn(int column, Vec4D v) { + if (column == 0) { + v.x = m00; + v.y = m10; + v.z = m20; + v.w = m30; + } else if (column == 1) { + v.x = m01; + v.y = m11; + v.z = m21; + v.w = m31; + } else if (column == 2) { + v.x = m02; + v.y = m12; + v.z = m22; + v.w = m32; + } else if (column == 3) { + v.x = m03; + v.y = m13; + v.z = m23; + v.w = m33; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + + } + + /** + * Retrieves the value at the specified row and column of this matrix. + * + * @param row + * the row number to be retrieved (zero indexed) + * @param column + * the column number to be retrieved (zero indexed) + * @return the value at the indexed element + */ + public final float getElement(int row, int column) { + switch (row) { + case 0: + switch (column) { + case 0: + return (this.m00); + case 1: + return (this.m01); + case 2: + return (this.m02); + case 3: + return (this.m03); + default: + break; + } + break; + case 1: + switch (column) { + case 0: + return (this.m10); + case 1: + return (this.m11); + case 2: + return (this.m12); + case 3: + return (this.m13); + default: + break; + } + break; + + case 2: + switch (column) { + case 0: + return (this.m20); + case 1: + return (this.m21); + case 2: + return (this.m22); + case 3: + return (this.m23); + default: + break; + } + break; + + case 3: + switch (column) { + case 0: + return (this.m30); + case 1: + return (this.m31); + case 2: + return (this.m32); + case 3: + return (this.m33); + default: + break; + } + break; + + default: + break; + } + throw new ArrayIndexOutOfBoundsException(); + } + + /** + * Get the first matrix element in the first row. + * + * @return Returns the m00. + * + * @since vecmath 1.5 + */ + public final float getM00() { + return m00; + } + + /** + * Get the second matrix element in the first row. + * + * @return Returns the m01. + * + * @since vecmath 1.5 + */ + public final float getM01() { + return m01; + } + + /** + * Get the third matrix element in the first row. + * + * @return Returns the m02. + * + * @since vecmath 1.5 + */ + public final float getM02() { + return m02; + } + + /** + * Get the fourth element of the first row. + * + * @return Returns the m03. + * + * @since vecmath 1.5 + */ + public final float getM03() { + return m03; + } + + /** + * Get first matrix element in the second row. + * + * @return Returns the m10. + * + * @since vecmath 1.5 + */ + public final float getM10() { + return m10; + } + + /** + * Get second matrix element in the second row. + * + * @return Returns the m11. + * + * @since vecmath 1.5 + */ + public final float getM11() { + return m11; + } + + /** + * Get the third matrix element in the second row. + * + * @return Returns the m12. + * + * @since vecmath 1.5 + */ + public final float getM12() { + return m12; + } + + /** + * Get the fourth element of the second row. + * + * @return Returns the m13. + * + * @since vecmath 1.5 + */ + public final float getM13() { + return m13; + } + + /** + * Get the first matrix element in the third row. + * + * @return Returns the m20. + * + * @since vecmath 1.5 + */ + public final float getM20() { + return m20; + } + + /** + * Get the second matrix element in the third row. + * + * @return Returns the m21. + * + * @since vecmath 1.5 + */ + public final float getM21() { + return m21; + } + + /** + * Get the third matrix element in the third row. + * + * @return Returns the m22. + * + * @since vecmath 1.5 + */ + public final float getM22() { + return m22; + } + + /** + * Get the fourth element of the third row. + * + * @return Returns the m23. + * + * @since vecmath 1.5 + */ + public final float getM23() { + return m23; + } + + /** + * Get the first element of the fourth row. + * + * @return Returns the m30. + * + * @since vecmath 1.5 + */ + public final float getM30() { + return m30; + } + + /** + * Get the second element of the fourth row. + * + * @return Returns the m31. + * + * @since vecmath 1.5 + */ + public final float getM31() { + return m31; + } + + /** + * Get the third element of the fourth row. + * + * @return Returns the m32. + * + * @since vecmath 1.5 + */ + public final float getM32() { + return m32; + } + + /** + * Get the fourth element of the fourth row. + * + * @return Returns the m33. + * + * @since vecmath 1.5 + */ + public final float getM33() { + return m33; + } + + /** + * Gets the upper 3x3 values of this matrix and places them into the matrix + * m1. + * + * @param m1 + * the matrix that will hold the values + */ + public final void getRotationScale(Matrix3d m1) { + m1.m00 = m00; + m1.m01 = m01; + m1.m02 = m02; + m1.m10 = m10; + m1.m11 = m11; + m1.m12 = m12; + m1.m20 = m20; + m1.m21 = m21; + m1.m22 = m22; + } + + /** + * Copies the matrix values in the specified row into the array parameter. + * + * @param row + * the matrix row + * @param v + * the array into which the matrix row values will be copied + */ + public final void getRow(int row, float v[]) { + if (row == 0) { + v[0] = m00; + v[1] = m01; + v[2] = m02; + v[3] = m03; + } else if (row == 1) { + v[0] = m10; + v[1] = m11; + v[2] = m12; + v[3] = m13; + } else if (row == 2) { + v[0] = m20; + v[1] = m21; + v[2] = m22; + v[3] = m23; + } else if (row == 3) { + v[0] = m30; + v[1] = m31; + v[2] = m32; + v[3] = m33; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + + } + + /** + * Copies the matrix values in the specified row into the vector parameter. + * + * @param row + * the matrix row + * @param v + * the vector into which the matrix row values will be copied + */ + public final void getRow(int row, Vec4D v) { + if (row == 0) { + v.x = m00; + v.y = m01; + v.z = m02; + v.w = m03; + } else if (row == 1) { + v.x = m10; + v.y = m11; + v.z = m12; + v.w = m13; + } else if (row == 2) { + v.x = m20; + v.y = m21; + v.z = m22; + v.w = m23; + } else if (row == 3) { + v.x = m30; + v.y = m31; + v.z = m32; + v.w = m33; + } else { + throw new ArrayIndexOutOfBoundsException(); + } + + } + + /** + * Performs an SVD normalization of this matrix to calculate and return the + * uniform scale factor. If the matrix has non-uniform scale factors, the + * largest of the x, y, and z scale factors will be returned. This matrix is + * not modified. + * + * @return the scale factor of this matrix + */ + public final float getScale() { + double[] tmp_scale = new double[3]; + getScaleRotate(tmp_scale, new double[9]); + return ((float) MathUtils.max(tmp_scale)); + } + + private final void getScaleRotate(double scales[], double rots[]) { + + double[] tmp = new double[9]; // scratch matrix + tmp[0] = m00; + tmp[1] = m01; + tmp[2] = m02; + + tmp[3] = m10; + tmp[4] = m11; + tmp[5] = m12; + + tmp[6] = m20; + tmp[7] = m21; + tmp[8] = m22; + + Matrix3d.compute_svd(tmp, scales, rots); + } + + /** + * Returns a hash code value based on the data values in this object. Two + * different Matrix4f objects with identical data values (i.e., + * Matrix4f.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() { + long bits = 1L; + bits = 31L * bits + VecMathUtil.floatToIntBits(m00); + bits = 31L * bits + VecMathUtil.floatToIntBits(m01); + bits = 31L * bits + VecMathUtil.floatToIntBits(m02); + bits = 31L * bits + VecMathUtil.floatToIntBits(m03); + bits = 31L * bits + VecMathUtil.floatToIntBits(m10); + bits = 31L * bits + VecMathUtil.floatToIntBits(m11); + bits = 31L * bits + VecMathUtil.floatToIntBits(m12); + bits = 31L * bits + VecMathUtil.floatToIntBits(m13); + bits = 31L * bits + VecMathUtil.floatToIntBits(m20); + bits = 31L * bits + VecMathUtil.floatToIntBits(m21); + bits = 31L * bits + VecMathUtil.floatToIntBits(m22); + bits = 31L * bits + VecMathUtil.floatToIntBits(m23); + bits = 31L * bits + VecMathUtil.floatToIntBits(m30); + bits = 31L * bits + VecMathUtil.floatToIntBits(m31); + bits = 31L * bits + VecMathUtil.floatToIntBits(m32); + bits = 31L * bits + VecMathUtil.floatToIntBits(m33); + return (int) (bits ^ (bits >> 32)); + } + + /** + * Inverts this matrix in place. + */ + public final void invert() { + invertGeneral(this); + } + + /** + * Sets the value of this matrix to the matrix inverse of the passed (user + * declared) matrix m1. + * + * @param m1 + * the matrix to be inverted + */ + public final void invert(Matrix4f m1) { + + invertGeneral(m1); + } + + /** + * General invert routine. Inverts m1 and places the result in "this". Note + * that this routine handles both the "this" version and the non-"this" + * version. + * + * Also note that since this routine is slow anyway, we won't worry about + * allocating a little bit of garbage. + */ + final void invertGeneral(Matrix4f m1) { + double temp[] = new double[16]; + double result[] = new double[16]; + int row_perm[] = new int[4]; + int i, r, c; + + // Use LU decomposition and backsubstitution code specifically + // for floating-point 4x4 matrices. + + // Copy source matrix to t1tmp + temp[0] = m1.m00; + temp[1] = m1.m01; + temp[2] = m1.m02; + temp[3] = m1.m03; + + temp[4] = m1.m10; + temp[5] = m1.m11; + temp[6] = m1.m12; + temp[7] = m1.m13; + + temp[8] = m1.m20; + temp[9] = m1.m21; + temp[10] = m1.m22; + temp[11] = m1.m23; + + temp[12] = m1.m30; + temp[13] = m1.m31; + temp[14] = m1.m32; + temp[15] = m1.m33; + + // Calculate LU decomposition: Is the matrix singular? + if (!Matrix4x4.LUDecomposition(temp, row_perm, 4)) { + // Matrix has no inverse + throw new SingularMatrixException(); + } + + // Perform back substitution on the identity matrix + for (i = 0; i < 16; i++) { + result[i] = 0.0; + } + result[0] = 1.0; + result[5] = 1.0; + result[10] = 1.0; + result[15] = 1.0; + luBacksubstitution(temp, row_perm, result); + + this.m00 = (float) result[0]; + this.m01 = (float) result[1]; + this.m02 = (float) result[2]; + this.m03 = (float) result[3]; + + this.m10 = (float) result[4]; + this.m11 = (float) result[5]; + this.m12 = (float) result[6]; + this.m13 = (float) result[7]; + + this.m20 = (float) result[8]; + this.m21 = (float) result[9]; + this.m22 = (float) result[10]; + this.m23 = (float) result[11]; + + this.m30 = (float) result[12]; + this.m31 = (float) result[13]; + this.m32 = (float) result[14]; + this.m33 = (float) result[15]; + + } + + /** + * Multiplies each element of this matrix by a scalar. + * + * @param scalar + * the scalar multiplier. + */ + public final void mul(float scalar) { + m00 *= scalar; + m01 *= scalar; + m02 *= scalar; + m03 *= scalar; + m10 *= scalar; + m11 *= scalar; + m12 *= scalar; + m13 *= scalar; + m20 *= scalar; + m21 *= scalar; + m22 *= scalar; + m23 *= scalar; + m30 *= scalar; + m31 *= scalar; + m32 *= scalar; + m33 *= scalar; + } + + /** + * Multiplies each element of matrix m1 by a scalar and places the result + * into this. Matrix m1 is not modified. + * + * @param scalar + * the scalar multiplier. + * @param m1 + * the original matrix. + */ + public final void mul(float scalar, Matrix4f m1) { + this.m00 = m1.m00 * scalar; + this.m01 = m1.m01 * scalar; + this.m02 = m1.m02 * scalar; + this.m03 = m1.m03 * scalar; + this.m10 = m1.m10 * scalar; + this.m11 = m1.m11 * scalar; + this.m12 = m1.m12 * scalar; + this.m13 = m1.m13 * scalar; + this.m20 = m1.m20 * scalar; + this.m21 = m1.m21 * scalar; + this.m22 = m1.m22 * scalar; + this.m23 = m1.m23 * scalar; + this.m30 = m1.m30 * scalar; + this.m31 = m1.m31 * scalar; + this.m32 = m1.m32 * scalar; + this.m33 = m1.m33 * scalar; + } + + /** + * Sets the value of this matrix to the result of multiplying itself with + * matrix m1. + * + * @param m1 + * the other matrix + */ + public final void mul(Matrix4f m1) { + float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; // vars + // for + // temp + // result + // matrix + + m00 = this.m00 * m1.m00 + this.m01 * m1.m10 + this.m02 * m1.m20 + + this.m03 * m1.m30; + m01 = this.m00 * m1.m01 + this.m01 * m1.m11 + this.m02 * m1.m21 + + this.m03 * m1.m31; + m02 = this.m00 * m1.m02 + this.m01 * m1.m12 + this.m02 * m1.m22 + + this.m03 * m1.m32; + m03 = this.m00 * m1.m03 + this.m01 * m1.m13 + this.m02 * m1.m23 + + this.m03 * m1.m33; + + m10 = this.m10 * m1.m00 + this.m11 * m1.m10 + this.m12 * m1.m20 + + this.m13 * m1.m30; + m11 = this.m10 * m1.m01 + this.m11 * m1.m11 + this.m12 * m1.m21 + + this.m13 * m1.m31; + m12 = this.m10 * m1.m02 + this.m11 * m1.m12 + this.m12 * m1.m22 + + this.m13 * m1.m32; + m13 = this.m10 * m1.m03 + this.m11 * m1.m13 + this.m12 * m1.m23 + + this.m13 * m1.m33; + + m20 = this.m20 * m1.m00 + this.m21 * m1.m10 + this.m22 * m1.m20 + + this.m23 * m1.m30; + m21 = this.m20 * m1.m01 + this.m21 * m1.m11 + this.m22 * m1.m21 + + this.m23 * m1.m31; + m22 = this.m20 * m1.m02 + this.m21 * m1.m12 + this.m22 * m1.m22 + + this.m23 * m1.m32; + m23 = this.m20 * m1.m03 + this.m21 * m1.m13 + this.m22 * m1.m23 + + this.m23 * m1.m33; + + m30 = this.m30 * m1.m00 + this.m31 * m1.m10 + this.m32 * m1.m20 + + this.m33 * m1.m30; + m31 = this.m30 * m1.m01 + this.m31 * m1.m11 + this.m32 * m1.m21 + + this.m33 * m1.m31; + m32 = this.m30 * m1.m02 + this.m31 * m1.m12 + this.m32 * m1.m22 + + this.m33 * m1.m32; + m33 = this.m30 * m1.m03 + this.m31 * m1.m13 + this.m32 * m1.m23 + + this.m33 * m1.m33; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + /** + * Sets the value of this matrix to the result of multiplying the two + * argument matrices together. + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void mul(Matrix4f m1, Matrix4f m2) { + if (this != m1 && this != m2) { + + this.m00 = m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20 + + m1.m03 * m2.m30; + this.m01 = m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21 + + m1.m03 * m2.m31; + this.m02 = m1.m00 * m2.m02 + m1.m01 * m2.m12 + m1.m02 * m2.m22 + + m1.m03 * m2.m32; + this.m03 = m1.m00 * m2.m03 + m1.m01 * m2.m13 + m1.m02 * m2.m23 + + m1.m03 * m2.m33; + + this.m10 = m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20 + + m1.m13 * m2.m30; + this.m11 = m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21 + + m1.m13 * m2.m31; + this.m12 = m1.m10 * m2.m02 + m1.m11 * m2.m12 + m1.m12 * m2.m22 + + m1.m13 * m2.m32; + this.m13 = m1.m10 * m2.m03 + m1.m11 * m2.m13 + m1.m12 * m2.m23 + + m1.m13 * m2.m33; + + this.m20 = m1.m20 * m2.m00 + m1.m21 * m2.m10 + m1.m22 * m2.m20 + + m1.m23 * m2.m30; + this.m21 = m1.m20 * m2.m01 + m1.m21 * m2.m11 + m1.m22 * m2.m21 + + m1.m23 * m2.m31; + this.m22 = m1.m20 * m2.m02 + m1.m21 * m2.m12 + m1.m22 * m2.m22 + + m1.m23 * m2.m32; + this.m23 = m1.m20 * m2.m03 + m1.m21 * m2.m13 + m1.m22 * m2.m23 + + m1.m23 * m2.m33; + + this.m30 = m1.m30 * m2.m00 + m1.m31 * m2.m10 + m1.m32 * m2.m20 + + m1.m33 * m2.m30; + this.m31 = m1.m30 * m2.m01 + m1.m31 * m2.m11 + m1.m32 * m2.m21 + + m1.m33 * m2.m31; + this.m32 = m1.m30 * m2.m02 + m1.m31 * m2.m12 + m1.m32 * m2.m22 + + m1.m33 * m2.m32; + this.m33 = m1.m30 * m2.m03 + m1.m31 * m2.m13 + m1.m32 * m2.m23 + + m1.m33 * m2.m33; + } else { + float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; // vars + // for + // temp + // result + // matrix + m00 = m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20 + m1.m03 + * m2.m30; + m01 = m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21 + m1.m03 + * m2.m31; + m02 = m1.m00 * m2.m02 + m1.m01 * m2.m12 + m1.m02 * m2.m22 + m1.m03 + * m2.m32; + m03 = m1.m00 * m2.m03 + m1.m01 * m2.m13 + m1.m02 * m2.m23 + m1.m03 + * m2.m33; + + m10 = m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20 + m1.m13 + * m2.m30; + m11 = m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21 + m1.m13 + * m2.m31; + m12 = m1.m10 * m2.m02 + m1.m11 * m2.m12 + m1.m12 * m2.m22 + m1.m13 + * m2.m32; + m13 = m1.m10 * m2.m03 + m1.m11 * m2.m13 + m1.m12 * m2.m23 + m1.m13 + * m2.m33; + + m20 = m1.m20 * m2.m00 + m1.m21 * m2.m10 + m1.m22 * m2.m20 + m1.m23 + * m2.m30; + m21 = m1.m20 * m2.m01 + m1.m21 * m2.m11 + m1.m22 * m2.m21 + m1.m23 + * m2.m31; + m22 = m1.m20 * m2.m02 + m1.m21 * m2.m12 + m1.m22 * m2.m22 + m1.m23 + * m2.m32; + m23 = m1.m20 * m2.m03 + m1.m21 * m2.m13 + m1.m22 * m2.m23 + m1.m23 + * m2.m33; + + m30 = m1.m30 * m2.m00 + m1.m31 * m2.m10 + m1.m32 * m2.m20 + m1.m33 + * m2.m30; + m31 = m1.m30 * m2.m01 + m1.m31 * m2.m11 + m1.m32 * m2.m21 + m1.m33 + * m2.m31; + m32 = m1.m30 * m2.m02 + m1.m31 * m2.m12 + m1.m32 * m2.m22 + m1.m33 + * m2.m32; + m33 = m1.m30 * m2.m03 + m1.m31 * m2.m13 + m1.m32 * m2.m23 + m1.m33 + * m2.m33; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + } + + /** + * Multiplies the transpose of matrix m1 times the transpose of matrix m2, + * and places the result into this. + * + * @param m1 + * the matrix on the left hand side of the multiplication + * @param m2 + * the matrix on the right hand side of the multiplication + */ + public final void mulTransposeBoth(Matrix4f m1, Matrix4f m2) { + if (this != m1 && this != m2) { + this.m00 = m1.m00 * m2.m00 + m1.m10 * m2.m01 + m1.m20 * m2.m02 + + m1.m30 * m2.m03; + this.m01 = m1.m00 * m2.m10 + m1.m10 * m2.m11 + m1.m20 * m2.m12 + + m1.m30 * m2.m13; + this.m02 = m1.m00 * m2.m20 + m1.m10 * m2.m21 + m1.m20 * m2.m22 + + m1.m30 * m2.m23; + this.m03 = m1.m00 * m2.m30 + m1.m10 * m2.m31 + m1.m20 * m2.m32 + + m1.m30 * m2.m33; + + this.m10 = m1.m01 * m2.m00 + m1.m11 * m2.m01 + m1.m21 * m2.m02 + + m1.m31 * m2.m03; + this.m11 = m1.m01 * m2.m10 + m1.m11 * m2.m11 + m1.m21 * m2.m12 + + m1.m31 * m2.m13; + this.m12 = m1.m01 * m2.m20 + m1.m11 * m2.m21 + m1.m21 * m2.m22 + + m1.m31 * m2.m23; + this.m13 = m1.m01 * m2.m30 + m1.m11 * m2.m31 + m1.m21 * m2.m32 + + m1.m31 * m2.m33; + + this.m20 = m1.m02 * m2.m00 + m1.m12 * m2.m01 + m1.m22 * m2.m02 + + m1.m32 * m2.m03; + this.m21 = m1.m02 * m2.m10 + m1.m12 * m2.m11 + m1.m22 * m2.m12 + + m1.m32 * m2.m13; + this.m22 = m1.m02 * m2.m20 + m1.m12 * m2.m21 + m1.m22 * m2.m22 + + m1.m32 * m2.m23; + this.m23 = m1.m02 * m2.m30 + m1.m12 * m2.m31 + m1.m22 * m2.m32 + + m1.m32 * m2.m33; + + this.m30 = m1.m03 * m2.m00 + m1.m13 * m2.m01 + m1.m23 * m2.m02 + + m1.m33 * m2.m03; + this.m31 = m1.m03 * m2.m10 + m1.m13 * m2.m11 + m1.m23 * m2.m12 + + m1.m33 * m2.m13; + this.m32 = m1.m03 * m2.m20 + m1.m13 * m2.m21 + m1.m23 * m2.m22 + + m1.m33 * m2.m23; + this.m33 = m1.m03 * m2.m30 + m1.m13 * m2.m31 + m1.m23 * m2.m32 + + m1.m33 * m2.m33; + } else { + float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, // vars + // for + // temp + // result + // matrix + m30, m31, m32, m33; + + m00 = m1.m00 * m2.m00 + m1.m10 * m2.m01 + m1.m20 * m2.m02 + m1.m30 + * m2.m03; + m01 = m1.m00 * m2.m10 + m1.m10 * m2.m11 + m1.m20 * m2.m12 + m1.m30 + * m2.m13; + m02 = m1.m00 * m2.m20 + m1.m10 * m2.m21 + m1.m20 * m2.m22 + m1.m30 + * m2.m23; + m03 = m1.m00 * m2.m30 + m1.m10 * m2.m31 + m1.m20 * m2.m32 + m1.m30 + * m2.m33; + + m10 = m1.m01 * m2.m00 + m1.m11 * m2.m01 + m1.m21 * m2.m02 + m1.m31 + * m2.m03; + m11 = m1.m01 * m2.m10 + m1.m11 * m2.m11 + m1.m21 * m2.m12 + m1.m31 + * m2.m13; + m12 = m1.m01 * m2.m20 + m1.m11 * m2.m21 + m1.m21 * m2.m22 + m1.m31 + * m2.m23; + m13 = m1.m01 * m2.m30 + m1.m11 * m2.m31 + m1.m21 * m2.m32 + m1.m31 + * m2.m33; + + m20 = m1.m02 * m2.m00 + m1.m12 * m2.m01 + m1.m22 * m2.m02 + m1.m32 + * m2.m03; + m21 = m1.m02 * m2.m10 + m1.m12 * m2.m11 + m1.m22 * m2.m12 + m1.m32 + * m2.m13; + m22 = m1.m02 * m2.m20 + m1.m12 * m2.m21 + m1.m22 * m2.m22 + m1.m32 + * m2.m23; + m23 = m1.m02 * m2.m30 + m1.m12 * m2.m31 + m1.m22 * m2.m32 + m1.m32 + * m2.m33; + + m30 = m1.m03 * m2.m00 + m1.m13 * m2.m01 + m1.m23 * m2.m02 + m1.m33 + * m2.m03; + m31 = m1.m03 * m2.m10 + m1.m13 * m2.m11 + m1.m23 * m2.m12 + m1.m33 + * m2.m13; + m32 = m1.m03 * m2.m20 + m1.m13 * m2.m21 + m1.m23 * m2.m22 + m1.m33 + * m2.m23; + m33 = m1.m03 * m2.m30 + m1.m13 * m2.m31 + m1.m23 * m2.m32 + m1.m33 + * m2.m33; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + } + + /** + * Multiplies the transpose of matrix m1 times matrix m2, and places the + * result into this. + * + * @param m1 + * the matrix on the left hand side of the multiplication + * @param m2 + * the matrix on the right hand side of the multiplication + */ + public final void mulTransposeLeft(Matrix4f m1, Matrix4f m2) { + if (this != m1 && this != m2) { + this.m00 = m1.m00 * m2.m00 + m1.m10 * m2.m10 + m1.m20 * m2.m20 + + m1.m30 * m2.m30; + this.m01 = m1.m00 * m2.m01 + m1.m10 * m2.m11 + m1.m20 * m2.m21 + + m1.m30 * m2.m31; + this.m02 = m1.m00 * m2.m02 + m1.m10 * m2.m12 + m1.m20 * m2.m22 + + m1.m30 * m2.m32; + this.m03 = m1.m00 * m2.m03 + m1.m10 * m2.m13 + m1.m20 * m2.m23 + + m1.m30 * m2.m33; + + this.m10 = m1.m01 * m2.m00 + m1.m11 * m2.m10 + m1.m21 * m2.m20 + + m1.m31 * m2.m30; + this.m11 = m1.m01 * m2.m01 + m1.m11 * m2.m11 + m1.m21 * m2.m21 + + m1.m31 * m2.m31; + this.m12 = m1.m01 * m2.m02 + m1.m11 * m2.m12 + m1.m21 * m2.m22 + + m1.m31 * m2.m32; + this.m13 = m1.m01 * m2.m03 + m1.m11 * m2.m13 + m1.m21 * m2.m23 + + m1.m31 * m2.m33; + + this.m20 = m1.m02 * m2.m00 + m1.m12 * m2.m10 + m1.m22 * m2.m20 + + m1.m32 * m2.m30; + this.m21 = m1.m02 * m2.m01 + m1.m12 * m2.m11 + m1.m22 * m2.m21 + + m1.m32 * m2.m31; + this.m22 = m1.m02 * m2.m02 + m1.m12 * m2.m12 + m1.m22 * m2.m22 + + m1.m32 * m2.m32; + this.m23 = m1.m02 * m2.m03 + m1.m12 * m2.m13 + m1.m22 * m2.m23 + + m1.m32 * m2.m33; + + this.m30 = m1.m03 * m2.m00 + m1.m13 * m2.m10 + m1.m23 * m2.m20 + + m1.m33 * m2.m30; + this.m31 = m1.m03 * m2.m01 + m1.m13 * m2.m11 + m1.m23 * m2.m21 + + m1.m33 * m2.m31; + this.m32 = m1.m03 * m2.m02 + m1.m13 * m2.m12 + m1.m23 * m2.m22 + + m1.m33 * m2.m32; + this.m33 = m1.m03 * m2.m03 + m1.m13 * m2.m13 + m1.m23 * m2.m23 + + m1.m33 * m2.m33; + } else { + float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, // vars + // for + // temp + // result + // matrix + m30, m31, m32, m33; + + m00 = m1.m00 * m2.m00 + m1.m10 * m2.m10 + m1.m20 * m2.m20 + m1.m30 + * m2.m30; + m01 = m1.m00 * m2.m01 + m1.m10 * m2.m11 + m1.m20 * m2.m21 + m1.m30 + * m2.m31; + m02 = m1.m00 * m2.m02 + m1.m10 * m2.m12 + m1.m20 * m2.m22 + m1.m30 + * m2.m32; + m03 = m1.m00 * m2.m03 + m1.m10 * m2.m13 + m1.m20 * m2.m23 + m1.m30 + * m2.m33; + + m10 = m1.m01 * m2.m00 + m1.m11 * m2.m10 + m1.m21 * m2.m20 + m1.m31 + * m2.m30; + m11 = m1.m01 * m2.m01 + m1.m11 * m2.m11 + m1.m21 * m2.m21 + m1.m31 + * m2.m31; + m12 = m1.m01 * m2.m02 + m1.m11 * m2.m12 + m1.m21 * m2.m22 + m1.m31 + * m2.m32; + m13 = m1.m01 * m2.m03 + m1.m11 * m2.m13 + m1.m21 * m2.m23 + m1.m31 + * m2.m33; + + m20 = m1.m02 * m2.m00 + m1.m12 * m2.m10 + m1.m22 * m2.m20 + m1.m32 + * m2.m30; + m21 = m1.m02 * m2.m01 + m1.m12 * m2.m11 + m1.m22 * m2.m21 + m1.m32 + * m2.m31; + m22 = m1.m02 * m2.m02 + m1.m12 * m2.m12 + m1.m22 * m2.m22 + m1.m32 + * m2.m32; + m23 = m1.m02 * m2.m03 + m1.m12 * m2.m13 + m1.m22 * m2.m23 + m1.m32 + * m2.m33; + + m30 = m1.m03 * m2.m00 + m1.m13 * m2.m10 + m1.m23 * m2.m20 + m1.m33 + * m2.m30; + m31 = m1.m03 * m2.m01 + m1.m13 * m2.m11 + m1.m23 * m2.m21 + m1.m33 + * m2.m31; + m32 = m1.m03 * m2.m02 + m1.m13 * m2.m12 + m1.m23 * m2.m22 + m1.m33 + * m2.m32; + m33 = m1.m03 * m2.m03 + m1.m13 * m2.m13 + m1.m23 * m2.m23 + m1.m33 + * m2.m33; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + } + + /** + * Multiplies matrix m1 times the transpose of matrix m2, and places the + * result into this. + * + * @param m1 + * the matrix on the left hand side of the multiplication + * @param m2 + * the matrix on the right hand side of the multiplication + */ + public final void mulTransposeRight(Matrix4f m1, Matrix4f m2) { + if (this != m1 && this != m2) { + this.m00 = m1.m00 * m2.m00 + m1.m01 * m2.m01 + m1.m02 * m2.m02 + + m1.m03 * m2.m03; + this.m01 = m1.m00 * m2.m10 + m1.m01 * m2.m11 + m1.m02 * m2.m12 + + m1.m03 * m2.m13; + this.m02 = m1.m00 * m2.m20 + m1.m01 * m2.m21 + m1.m02 * m2.m22 + + m1.m03 * m2.m23; + this.m03 = m1.m00 * m2.m30 + m1.m01 * m2.m31 + m1.m02 * m2.m32 + + m1.m03 * m2.m33; + + this.m10 = m1.m10 * m2.m00 + m1.m11 * m2.m01 + m1.m12 * m2.m02 + + m1.m13 * m2.m03; + this.m11 = m1.m10 * m2.m10 + m1.m11 * m2.m11 + m1.m12 * m2.m12 + + m1.m13 * m2.m13; + this.m12 = m1.m10 * m2.m20 + m1.m11 * m2.m21 + m1.m12 * m2.m22 + + m1.m13 * m2.m23; + this.m13 = m1.m10 * m2.m30 + m1.m11 * m2.m31 + m1.m12 * m2.m32 + + m1.m13 * m2.m33; + + this.m20 = m1.m20 * m2.m00 + m1.m21 * m2.m01 + m1.m22 * m2.m02 + + m1.m23 * m2.m03; + this.m21 = m1.m20 * m2.m10 + m1.m21 * m2.m11 + m1.m22 * m2.m12 + + m1.m23 * m2.m13; + this.m22 = m1.m20 * m2.m20 + m1.m21 * m2.m21 + m1.m22 * m2.m22 + + m1.m23 * m2.m23; + this.m23 = m1.m20 * m2.m30 + m1.m21 * m2.m31 + m1.m22 * m2.m32 + + m1.m23 * m2.m33; + + this.m30 = m1.m30 * m2.m00 + m1.m31 * m2.m01 + m1.m32 * m2.m02 + + m1.m33 * m2.m03; + this.m31 = m1.m30 * m2.m10 + m1.m31 * m2.m11 + m1.m32 * m2.m12 + + m1.m33 * m2.m13; + this.m32 = m1.m30 * m2.m20 + m1.m31 * m2.m21 + m1.m32 * m2.m22 + + m1.m33 * m2.m23; + this.m33 = m1.m30 * m2.m30 + m1.m31 * m2.m31 + m1.m32 * m2.m32 + + m1.m33 * m2.m33; + } else { + float m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, // vars + // for + // temp + // result + // matrix + m30, m31, m32, m33; + + m00 = m1.m00 * m2.m00 + m1.m01 * m2.m01 + m1.m02 * m2.m02 + m1.m03 + * m2.m03; + m01 = m1.m00 * m2.m10 + m1.m01 * m2.m11 + m1.m02 * m2.m12 + m1.m03 + * m2.m13; + m02 = m1.m00 * m2.m20 + m1.m01 * m2.m21 + m1.m02 * m2.m22 + m1.m03 + * m2.m23; + m03 = m1.m00 * m2.m30 + m1.m01 * m2.m31 + m1.m02 * m2.m32 + m1.m03 + * m2.m33; + + m10 = m1.m10 * m2.m00 + m1.m11 * m2.m01 + m1.m12 * m2.m02 + m1.m13 + * m2.m03; + m11 = m1.m10 * m2.m10 + m1.m11 * m2.m11 + m1.m12 * m2.m12 + m1.m13 + * m2.m13; + m12 = m1.m10 * m2.m20 + m1.m11 * m2.m21 + m1.m12 * m2.m22 + m1.m13 + * m2.m23; + m13 = m1.m10 * m2.m30 + m1.m11 * m2.m31 + m1.m12 * m2.m32 + m1.m13 + * m2.m33; + + m20 = m1.m20 * m2.m00 + m1.m21 * m2.m01 + m1.m22 * m2.m02 + m1.m23 + * m2.m03; + m21 = m1.m20 * m2.m10 + m1.m21 * m2.m11 + m1.m22 * m2.m12 + m1.m23 + * m2.m13; + m22 = m1.m20 * m2.m20 + m1.m21 * m2.m21 + m1.m22 * m2.m22 + m1.m23 + * m2.m23; + m23 = m1.m20 * m2.m30 + m1.m21 * m2.m31 + m1.m22 * m2.m32 + m1.m23 + * m2.m33; + + m30 = m1.m30 * m2.m00 + m1.m31 * m2.m01 + m1.m32 * m2.m02 + m1.m33 + * m2.m03; + m31 = m1.m30 * m2.m10 + m1.m31 * m2.m11 + m1.m32 * m2.m12 + m1.m33 + * m2.m13; + m32 = m1.m30 * m2.m20 + m1.m31 * m2.m21 + m1.m32 * m2.m22 + m1.m33 + * m2.m23; + m33 = m1.m30 * m2.m30 + m1.m31 * m2.m31 + m1.m32 * m2.m32 + m1.m33 + * m2.m33; + + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m03 = m03; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m20 = m20; + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m30 = m30; + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + } + + } + + /** + * Negates the value of this matrix: this = -this. + */ + public final void negate() { + m00 = -m00; + m01 = -m01; + m02 = -m02; + m03 = -m03; + m10 = -m10; + m11 = -m11; + m12 = -m12; + m13 = -m13; + m20 = -m20; + m21 = -m21; + m22 = -m22; + m23 = -m23; + m30 = -m30; + m31 = -m31; + m32 = -m32; + m33 = -m33; + } + + /** + * Sets the value of this matrix equal to the negation of of the Matrix4f + * parameter. + * + * @param m1 + * the source matrix + */ + public final void negate(Matrix4f m1) { + this.m00 = -m1.m00; + this.m01 = -m1.m01; + this.m02 = -m1.m02; + this.m03 = -m1.m03; + this.m10 = -m1.m10; + this.m11 = -m1.m11; + this.m12 = -m1.m12; + this.m13 = -m1.m13; + this.m20 = -m1.m20; + this.m21 = -m1.m21; + this.m22 = -m1.m22; + this.m23 = -m1.m23; + this.m30 = -m1.m30; + this.m31 = -m1.m31; + this.m32 = -m1.m32; + this.m33 = -m1.m33; + } + + /** + * Sets the value of this matrix to a counter clockwise rotation about the x + * axis. + * + * @param angle + * the angle to rotate about the X axis in radians + */ + public final void rotX(float angle) { + float sinAngle, cosAngle; + + sinAngle = (float) Math.sin(angle); + cosAngle = (float) Math.cos(angle); + + this.m00 = (float) 1.0; + this.m01 = (float) 0.0; + this.m02 = (float) 0.0; + this.m03 = (float) 0.0; + + this.m10 = (float) 0.0; + this.m11 = cosAngle; + this.m12 = -sinAngle; + this.m13 = (float) 0.0; + + this.m20 = (float) 0.0; + this.m21 = sinAngle; + this.m22 = cosAngle; + this.m23 = (float) 0.0; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this matrix to a counter clockwise rotation about the y + * axis. + * + * @param angle + * the angle to rotate about the Y axis in radians + */ + public final void rotY(float angle) { + float sinAngle, cosAngle; + + sinAngle = (float) Math.sin(angle); + cosAngle = (float) Math.cos(angle); + + this.m00 = cosAngle; + this.m01 = (float) 0.0; + this.m02 = sinAngle; + this.m03 = (float) 0.0; + + this.m10 = (float) 0.0; + this.m11 = (float) 1.0; + this.m12 = (float) 0.0; + this.m13 = (float) 0.0; + + this.m20 = -sinAngle; + this.m21 = (float) 0.0; + this.m22 = cosAngle; + this.m23 = (float) 0.0; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this matrix to a counter clockwise rotation about the z + * axis. + * + * @param angle + * the angle to rotate about the Z axis in radians + */ + public final void rotZ(float angle) { + float sinAngle, cosAngle; + + sinAngle = (float) Math.sin(angle); + cosAngle = (float) Math.cos(angle); + + this.m00 = cosAngle; + this.m01 = -sinAngle; + this.m02 = (float) 0.0; + this.m03 = (float) 0.0; + + this.m10 = sinAngle; + this.m11 = cosAngle; + this.m12 = (float) 0.0; + this.m13 = (float) 0.0; + + this.m20 = (float) 0.0; + this.m21 = (float) 0.0; + this.m22 = (float) 1.0; + this.m23 = (float) 0.0; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this matrix to a scale matrix with the the passed scale + * amount. + * + * @param scale + * the scale factor for the matrix + */ + public final void set(float scale) { + this.m00 = scale; + this.m01 = (float) 0.0; + this.m02 = (float) 0.0; + this.m03 = (float) 0.0; + + this.m10 = (float) 0.0; + this.m11 = scale; + this.m12 = (float) 0.0; + this.m13 = (float) 0.0; + + this.m20 = (float) 0.0; + this.m21 = (float) 0.0; + this.m22 = scale; + this.m23 = (float) 0.0; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this transform to a scale and translation matrix; the + * scale is not applied to the translation and all of the matrix values are + * modified. + * + * @param scale + * the scale factor for the matrix + * @param t1 + * the translation amount + */ + public final void set(float scale, Vec3D t1) { + this.m00 = scale; + this.m01 = (float) 0.0; + this.m02 = (float) 0.0; + this.m03 = t1.x; + + this.m10 = (float) 0.0; + this.m11 = scale; + this.m12 = (float) 0.0; + this.m13 = t1.y; + + this.m20 = (float) 0.0; + this.m21 = (float) 0.0; + this.m22 = scale; + this.m23 = t1.z; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the values in this Matrix4f equal to the row-major array parameter + * (ie, the first four elements of the array will be copied into the first + * row of this matrix, etc.). + * + * @param m + * the single precision array of length 16 + */ + public final void set(float[] m) { + m00 = m[0]; + m01 = m[1]; + m02 = m[2]; + m03 = m[3]; + m10 = m[4]; + m11 = m[5]; + m12 = m[6]; + m13 = m[7]; + m20 = m[8]; + m21 = m[9]; + m22 = m[10]; + m23 = m[11]; + m30 = m[12]; + m31 = m[13]; + m32 = m[14]; + m33 = m[15]; + } + + /** + * Sets the rotational component (upper 3x3) of this matrix to the matrix + * values in the double precision Matrix3d argument; the other elements of + * this matrix are initialized as if this were an identity matrix (i.e., + * affine matrix with no translational component). + * + * @param m1 + * the double-precision 3x3 matrix + */ + public final void set(Matrix3d m1) { + m00 = (float) m1.m00; + m01 = (float) m1.m01; + m02 = (float) m1.m02; + m03 = 0.0f; + m10 = (float) m1.m10; + m11 = (float) m1.m11; + m12 = (float) m1.m12; + m13 = 0.0f; + m20 = (float) m1.m20; + m21 = (float) m1.m21; + m22 = (float) m1.m22; + m23 = 0.0f; + m30 = 0.0f; + m31 = 0.0f; + m32 = 0.0f; + m33 = 1.0f; + } + + /** + * Sets the value of this matrix from the rotation expressed by the rotation + * matrix m1, the translation t1, and the scale factor. The translation is + * not modified by the scale. + * + * @param m1 + * the rotation component + * @param t1 + * the translation component + * @param scale + * the scale factor + */ + public final void set(Matrix3d m1, Vec3D t1, double scale) { + this.m00 = (float) (m1.m00 * scale); + this.m01 = (float) (m1.m01 * scale); + this.m02 = (float) (m1.m02 * scale); + this.m03 = t1.x; + + this.m10 = (float) (m1.m10 * scale); + this.m11 = (float) (m1.m11 * scale); + this.m12 = (float) (m1.m12 * scale); + this.m13 = t1.y; + + this.m20 = (float) (m1.m20 * scale); + this.m21 = (float) (m1.m21 * scale); + this.m22 = (float) (m1.m22 * scale); + this.m23 = t1.z; + + this.m30 = 0.0f; + this.m31 = 0.0f; + this.m32 = 0.0f; + this.m33 = 1.0f; + } + + /** + * Sets the value of this matrix to a copy of the passed matrix m1. + * + * @param m1 + * the matrix to be copied + */ + public final void set(Matrix4f m1) { + this.m00 = m1.m00; + this.m01 = m1.m01; + this.m02 = m1.m02; + this.m03 = m1.m03; + + this.m10 = m1.m10; + this.m11 = m1.m11; + this.m12 = m1.m12; + this.m13 = m1.m13; + + this.m20 = m1.m20; + this.m21 = m1.m21; + this.m22 = m1.m22; + this.m23 = m1.m23; + + this.m30 = m1.m30; + this.m31 = m1.m31; + this.m32 = m1.m32; + this.m33 = m1.m33; + } + + /** + * Sets the value of this matrix to the matrix conversion of the single + * precision quaternion argument. + * + * @param q1 + * the quaternion to be converted + */ + public final void set(Quaternion q1) { + this.m00 = (1.0f - 2.0f * q1.y * q1.y - 2.0f * q1.z * q1.z); + this.m10 = (2.0f * (q1.x * q1.y + q1.w * q1.z)); + this.m20 = (2.0f * (q1.x * q1.z - q1.w * q1.y)); + + this.m01 = (2.0f * (q1.x * q1.y - q1.w * q1.z)); + this.m11 = (1.0f - 2.0f * q1.x * q1.x - 2.0f * q1.z * q1.z); + this.m21 = (2.0f * (q1.y * q1.z + q1.w * q1.x)); + + this.m02 = (2.0f * (q1.x * q1.z + q1.w * q1.y)); + this.m12 = (2.0f * (q1.y * q1.z - q1.w * q1.x)); + this.m22 = (1.0f - 2.0f * q1.x * q1.x - 2.0f * q1.y * q1.y); + + this.m03 = (float) 0.0; + this.m13 = (float) 0.0; + this.m23 = (float) 0.0; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this matrix from the rotation expressed by the + * quaternion q1, the translation t1, and the scale s. + * + * @param q1 + * the rotation expressed as a quaternion + * @param t1 + * the translation + * @param s + * the scale value + */ + public final void set(Quaternion q1, Vec3D t1, double s) { + this.m00 = (float) (s * (1.0 - 2.0 * q1.y * q1.y - 2.0 * q1.z * q1.z)); + this.m10 = (float) (s * (2.0 * (q1.x * q1.y + q1.w * q1.z))); + this.m20 = (float) (s * (2.0 * (q1.x * q1.z - q1.w * q1.y))); + + this.m01 = (float) (s * (2.0 * (q1.x * q1.y - q1.w * q1.z))); + this.m11 = (float) (s * (1.0 - 2.0 * q1.x * q1.x - 2.0 * q1.z * q1.z)); + this.m21 = (float) (s * (2.0 * (q1.y * q1.z + q1.w * q1.x))); + + this.m02 = (float) (s * (2.0 * (q1.x * q1.z + q1.w * q1.y))); + this.m12 = (float) (s * (2.0 * (q1.y * q1.z - q1.w * q1.x))); + this.m22 = (float) (s * (1.0 - 2.0 * q1.x * q1.x - 2.0 * q1.y * q1.y)); + + this.m03 = t1.x; + this.m13 = t1.y; + this.m23 = t1.z; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this matrix from the rotation expressed by the + * quaternion q1, the translation t1, and the scale s. + * + * @param q1 + * the rotation expressed as a quaternion + * @param t1 + * the translation + * @param s + * the scale value + */ + public final void set(Quaternion q1, Vec3D t1, float s) { + this.m00 = (s * (1.0f - 2.0f * q1.y * q1.y - 2.0f * q1.z * q1.z)); + this.m10 = (s * (2.0f * (q1.x * q1.y + q1.w * q1.z))); + this.m20 = (s * (2.0f * (q1.x * q1.z - q1.w * q1.y))); + + this.m01 = (s * (2.0f * (q1.x * q1.y - q1.w * q1.z))); + this.m11 = (s * (1.0f - 2.0f * q1.x * q1.x - 2.0f * q1.z * q1.z)); + this.m21 = (s * (2.0f * (q1.y * q1.z + q1.w * q1.x))); + + this.m02 = (s * (2.0f * (q1.x * q1.z + q1.w * q1.y))); + this.m12 = (s * (2.0f * (q1.y * q1.z - q1.w * q1.x))); + this.m22 = (s * (1.0f - 2.0f * q1.x * q1.x - 2.0f * q1.y * q1.y)); + + this.m03 = t1.x; + this.m13 = t1.y; + this.m23 = t1.z; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this matrix to a translate matrix with the passed + * translation value. + * + * @param v1 + * the translation amount + */ + public final void set(Vec3D v1) { + this.m00 = (float) 1.0; + this.m01 = (float) 0.0; + this.m02 = (float) 0.0; + this.m03 = v1.x; + + this.m10 = (float) 0.0; + this.m11 = (float) 1.0; + this.m12 = (float) 0.0; + this.m13 = v1.y; + + this.m20 = (float) 0.0; + this.m21 = (float) 0.0; + this.m22 = (float) 1.0; + this.m23 = v1.z; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the value of this transform to a scale and translation matrix; the + * translation is scaled by the scale factor and all of the matrix values + * are modified. + * + * @param t1 + * the translation amount + * @param scale + * the scale factor for the matrix + */ + public final void set(Vec3D t1, float scale) { + this.m00 = scale; + this.m01 = (float) 0.0; + this.m02 = (float) 0.0; + this.m03 = scale * t1.x; + + this.m10 = (float) 0.0; + this.m11 = scale; + this.m12 = (float) 0.0; + this.m13 = scale * t1.y; + + this.m20 = (float) 0.0; + this.m21 = (float) 0.0; + this.m22 = scale; + this.m23 = scale * t1.z; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Sets the specified column of this matrix4f to the four values provided. + * + * @param column + * the column number to be modified (zero indexed) + * @param v + * the replacement column + */ + public final void setColumn(int column, float v[]) { + switch (column) { + case 0: + this.m00 = v[0]; + this.m10 = v[1]; + this.m20 = v[2]; + this.m30 = v[3]; + break; + + case 1: + this.m01 = v[0]; + this.m11 = v[1]; + this.m21 = v[2]; + this.m31 = v[3]; + break; + + case 2: + this.m02 = v[0]; + this.m12 = v[1]; + this.m22 = v[2]; + this.m32 = v[3]; + break; + + case 3: + this.m03 = v[0]; + this.m13 = v[1]; + this.m23 = v[2]; + this.m33 = v[3]; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified column of this matrix4f to the four values provided. + * + * @param column + * the column number to be modified (zero indexed) + * @param x + * the first row element + * @param y + * the second row element + * @param z + * the third row element + * @param w + * the fourth row element + */ + public final void setColumn(int column, float x, float y, float z, float w) { + switch (column) { + case 0: + this.m00 = x; + this.m10 = y; + this.m20 = z; + this.m30 = w; + break; + + case 1: + this.m01 = x; + this.m11 = y; + this.m21 = z; + this.m31 = w; + break; + + case 2: + this.m02 = x; + this.m12 = y; + this.m22 = z; + this.m32 = w; + break; + + case 3: + this.m03 = x; + this.m13 = y; + this.m23 = z; + this.m33 = w; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified column of this matrix4f to the vector provided. + * + * @param column + * the column number to be modified (zero indexed) + * @param v + * the replacement column + */ + public final void setColumn(int column, Vec4D v) { + switch (column) { + case 0: + this.m00 = v.x; + this.m10 = v.y; + this.m20 = v.z; + this.m30 = v.w; + break; + + case 1: + this.m01 = v.x; + this.m11 = v.y; + this.m21 = v.z; + this.m31 = v.w; + break; + + case 2: + this.m02 = v.x; + this.m12 = v.y; + this.m22 = v.z; + this.m32 = v.w; + break; + + case 3: + this.m03 = v.x; + this.m13 = v.y; + this.m23 = v.z; + this.m33 = v.w; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified element of this matrix4f to the value provided. + * + * @param row + * the row number to be modified (zero indexed) + * @param column + * the column number to be modified (zero indexed) + * @param value + * the new value + */ + public final void setElement(int row, int column, float value) { + switch (row) { + case 0: + switch (column) { + case 0: + this.m00 = value; + break; + case 1: + this.m01 = value; + break; + case 2: + this.m02 = value; + break; + case 3: + this.m03 = value; + break; + default: + throw new ArrayIndexOutOfBoundsException(); + } + break; + + case 1: + switch (column) { + case 0: + this.m10 = value; + break; + case 1: + this.m11 = value; + break; + case 2: + this.m12 = value; + break; + case 3: + this.m13 = value; + break; + default: + throw new ArrayIndexOutOfBoundsException(); + } + break; + + case 2: + switch (column) { + case 0: + this.m20 = value; + break; + case 1: + this.m21 = value; + break; + case 2: + this.m22 = value; + break; + case 3: + this.m23 = value; + break; + default: + throw new ArrayIndexOutOfBoundsException(); + } + break; + + case 3: + switch (column) { + case 0: + this.m30 = value; + break; + case 1: + this.m31 = value; + break; + case 2: + this.m32 = value; + break; + case 3: + this.m33 = value; + break; + default: + throw new ArrayIndexOutOfBoundsException(); + } + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets this Matrix4f to identity. + */ + public final void setIdentity() { + this.m00 = (float) 1.0; + this.m01 = (float) 0.0; + this.m02 = (float) 0.0; + this.m03 = (float) 0.0; + + this.m10 = (float) 0.0; + this.m11 = (float) 1.0; + this.m12 = (float) 0.0; + this.m13 = (float) 0.0; + + this.m20 = (float) 0.0; + this.m21 = (float) 0.0; + this.m22 = (float) 1.0; + this.m23 = (float) 0.0; + + this.m30 = (float) 0.0; + this.m31 = (float) 0.0; + this.m32 = (float) 0.0; + this.m33 = (float) 1.0; + } + + /** + * Set the first matrix element in the first row. + * + * @param m00 + * The m00 to set. + * + * @since vecmath 1.5 + */ + public final void setM00(float m00) { + this.m00 = m00; + } + + /** + * Set the second matrix element in the first row. + * + * @param m01 + * The m01 to set. + * + * @since vecmath 1.5 + */ + public final void setM01(float m01) { + this.m01 = m01; + } + + /** + * Set the third matrix element in the first row. + * + * @param m02 + * The m02 to set. + * + * @since vecmath 1.5 + */ + public final void setM02(float m02) { + this.m02 = m02; + } + + /** + * Set the fourth element of the first row. + * + * @param m03 + * The m03 to set. + * + * @since vecmath 1.5 + */ + public final void setM03(float m03) { + this.m03 = m03; + } + + /** + * Set first matrix element in the second row. + * + * @param m10 + * The m10 to set. + * + * @since vecmath 1.5 + */ + public final void setM10(float m10) { + this.m10 = m10; + } + + /** + * Set the second matrix element in the second row. + * + * @param m11 + * The m11 to set. + * + * @since vecmath 1.5 + */ + public final void setM11(float m11) { + this.m11 = m11; + } + + /** + * Set the third matrix element in the second row. + * + * @param m12 + * The m12 to set. + * + * @since vecmath 1.5 + */ + public final void setM12(float m12) { + this.m12 = m12; + } + + /** + * Set the fourth element of the second row. + * + * @param m13 + * The m13 to set. + * + * @since vecmath 1.5 + */ + public final void setM13(float m13) { + this.m13 = m13; + } + + /** + * Set the first matrix element in the third row. + * + * @param m20 + * The m20 to set. + * + * @since vecmath 1.5 + */ + public final void setM20(float m20) { + this.m20 = m20; + } + + /** + * Set the second matrix element in the third row. + * + * @param m21 + * The m21 to set. + * + * @since vecmath 1.5 + */ + public final void setM21(float m21) { + this.m21 = m21; + } + + /** + * Set the third matrix element in the third row. + * + * @param m22 + * The m22 to set. + * + * @since vecmath 1.5 + */ + public final void setM22(float m22) { + this.m22 = m22; + } + + /** + * Set the fourth element of the third row. + * + * @param m23 + * The m23 to set. + * + * @since vecmath 1.5 + */ + public final void setM23(float m23) { + this.m23 = m23; + } + + /** + * Set the first element of the fourth row. + * + * @param m30 + * The m30 to set. + * + * + * @since vecmath 1.5 + */ + public final void setM30(float m30) { + this.m30 = m30; + } + + /** + * Set the second element of the fourth row. + * + * @param m31 + * The m31 to set. + * + * @since vecmath 1.5 + */ + public final void setM31(float m31) { + this.m31 = m31; + } + + /** + * Set the third element of the fourth row. + * + * @param m32 + * The m32 to set. + * + * + * @since vecmath 1.5 + */ + public final void setM32(float m32) { + this.m32 = m32; + } + + /** + * Set the fourth element of the fourth row. + * + * @param m33 + * The m33 to set. + * + * @since vecmath 1.5 + */ + public final void setM33(float m33) { + this.m33 = m33; + } + + /** + * Sets the rotational component (upper 3x3) of this matrix to the matrix + * values in the double precision Matrix3d argument; the other elements of + * this matrix are unchanged; a singular value decomposition is performed on + * this object's upper 3x3 matrix to factor out the scale, then this + * object's upper 3x3 matrix components are replaced by the passed rotation + * components, and then the scale is reapplied to the rotational components. + * + * @param m1 + * double precision 3x3 matrix + */ + public final void setRotation(Matrix3d m1) { + double[] tmp_rot = new double[9]; + double[] tmp_scale = new double[3]; + + getScaleRotate(tmp_scale, tmp_rot); + + m00 = (float) (m1.m00 * tmp_scale[0]); + m01 = (float) (m1.m01 * tmp_scale[1]); + m02 = (float) (m1.m02 * tmp_scale[2]); + + m10 = (float) (m1.m10 * tmp_scale[0]); + m11 = (float) (m1.m11 * tmp_scale[1]); + m12 = (float) (m1.m12 * tmp_scale[2]); + + m20 = (float) (m1.m20 * tmp_scale[0]); + m21 = (float) (m1.m21 * tmp_scale[1]); + m22 = (float) (m1.m22 * tmp_scale[2]); + + } + + /** + * Sets the rotational component (upper 3x3) of this matrix to the matrix + * equivalent values of the quaternion argument; the other elements of this + * matrix are unchanged; a singular value decomposition is performed on this + * object's upper 3x3 matrix to factor out the scale, then this object's + * upper 3x3 matrix components are replaced by the matrix equivalent of the + * quaternion, and then the scale is reapplied to the rotational components. + * + * @param q1 + * the quaternion that specifies the rotation + */ + public final void setRotation(Quaternion q1) { + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + getScaleRotate(tmp_scale, tmp_rot); + + m00 = (float) ((1.0f - 2.0f * q1.y * q1.y - 2.0f * q1.z * q1.z) * tmp_scale[0]); + m10 = (float) ((2.0f * (q1.x * q1.y + q1.w * q1.z)) * tmp_scale[0]); + m20 = (float) ((2.0f * (q1.x * q1.z - q1.w * q1.y)) * tmp_scale[0]); + + m01 = (float) ((2.0f * (q1.x * q1.y - q1.w * q1.z)) * tmp_scale[1]); + m11 = (float) ((1.0f - 2.0f * q1.x * q1.x - 2.0f * q1.z * q1.z) * tmp_scale[1]); + m21 = (float) ((2.0f * (q1.y * q1.z + q1.w * q1.x)) * tmp_scale[1]); + + m02 = (float) ((2.0f * (q1.x * q1.z + q1.w * q1.y)) * tmp_scale[2]); + m12 = (float) ((2.0f * (q1.y * q1.z - q1.w * q1.x)) * tmp_scale[2]); + m22 = (float) ((1.0f - 2.0f * q1.x * q1.x - 2.0f * q1.y * q1.y) * tmp_scale[2]); + + } + + /** + * Replaces the upper 3x3 matrix values of this matrix with the values in + * the matrix m1. + * + * @param m1 + * the matrix that will be the new upper 3x3 + */ + public final void setRotationScale(Matrix3d m1) { + m00 = (float) m1.m00; + m01 = (float) m1.m01; + m02 = (float) m1.m02; + m10 = (float) m1.m10; + m11 = (float) m1.m11; + m12 = (float) m1.m12; + m20 = (float) m1.m20; + m21 = (float) m1.m21; + m22 = (float) m1.m22; + } + + /** + * Sets the specified row of this matrix4f to the four values provided in + * the passed array. + * + * @param row + * the row number to be modified (zero indexed) + * @param v + * the replacement row + */ + public final void setRow(int row, float v[]) { + switch (row) { + case 0: + this.m00 = v[0]; + this.m01 = v[1]; + this.m02 = v[2]; + this.m03 = v[3]; + break; + + case 1: + this.m10 = v[0]; + this.m11 = v[1]; + this.m12 = v[2]; + this.m13 = v[3]; + break; + + case 2: + this.m20 = v[0]; + this.m21 = v[1]; + this.m22 = v[2]; + this.m23 = v[3]; + break; + + case 3: + this.m30 = v[0]; + this.m31 = v[1]; + this.m32 = v[2]; + this.m33 = v[3]; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified row of this matrix4f to the four values provided. + * + * @param row + * the row number to be modified (zero indexed) + * @param x + * the first column element + * @param y + * the second column element + * @param z + * the third column element + * @param w + * the fourth column element + */ + public final void setRow(int row, float x, float y, float z, float w) { + switch (row) { + case 0: + this.m00 = x; + this.m01 = y; + this.m02 = z; + this.m03 = w; + break; + + case 1: + this.m10 = x; + this.m11 = y; + this.m12 = z; + this.m13 = w; + break; + + case 2: + this.m20 = x; + this.m21 = y; + this.m22 = z; + this.m23 = w; + break; + + case 3: + this.m30 = x; + this.m31 = y; + this.m32 = z; + this.m33 = w; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the specified row of this matrix4f to the Vector provided. + * + * @param row + * the row number to be modified (zero indexed) + * @param v + * the replacement row + */ + public final void setRow(int row, Vec4D v) { + switch (row) { + case 0: + this.m00 = v.x; + this.m01 = v.y; + this.m02 = v.z; + this.m03 = v.w; + break; + + case 1: + this.m10 = v.x; + this.m11 = v.y; + this.m12 = v.z; + this.m13 = v.w; + break; + + case 2: + this.m20 = v.x; + this.m21 = v.y; + this.m22 = v.z; + this.m23 = v.w; + break; + + case 3: + this.m30 = v.x; + this.m31 = v.y; + this.m32 = v.z; + this.m33 = v.w; + break; + + default: + throw new ArrayIndexOutOfBoundsException(); + } + } + + /** + * Sets the scale component of the current matrix by factoring out the + * current scale (by doing an SVD) from the rotational component and + * multiplying by the new scale. + * + * @param scale + * the new scale amount + */ + public final void setScale(float scale) { + + double[] tmp_rot = new double[9]; // scratch matrix + double[] tmp_scale = new double[3]; // scratch matrix + getScaleRotate(tmp_scale, tmp_rot); + + m00 = (float) (tmp_rot[0] * scale); + m01 = (float) (tmp_rot[1] * scale); + m02 = (float) (tmp_rot[2] * scale); + + m10 = (float) (tmp_rot[3] * scale); + m11 = (float) (tmp_rot[4] * scale); + m12 = (float) (tmp_rot[5] * scale); + + m20 = (float) (tmp_rot[6] * scale); + m21 = (float) (tmp_rot[7] * scale); + m22 = (float) (tmp_rot[8] * scale); + + } + + /** + * Modifies the translational components of this matrix to the values of the + * Vector3f argument; the other values of this matrix are not modified. + * + * @param trans + * the translational component + */ + public final void setTranslation(Vec3D trans) { + m03 = trans.x; + m13 = trans.y; + m23 = trans.z; + } + + /** + * Sets this matrix to all zeros. + */ + public final void setZero() { + m00 = 0.0f; + m01 = 0.0f; + m02 = 0.0f; + m03 = 0.0f; + m10 = 0.0f; + m11 = 0.0f; + m12 = 0.0f; + m13 = 0.0f; + m20 = 0.0f; + m21 = 0.0f; + m22 = 0.0f; + m23 = 0.0f; + m30 = 0.0f; + m31 = 0.0f; + m32 = 0.0f; + m33 = 0.0f; + } + + /** + * Sets this matrix to the matrix difference of itself and matrix m1 (this = + * this - m1). + * + * @param m1 + * the other matrix + */ + public final void sub(Matrix4f m1) { + this.m00 -= m1.m00; + this.m01 -= m1.m01; + this.m02 -= m1.m02; + this.m03 -= m1.m03; + + this.m10 -= m1.m10; + this.m11 -= m1.m11; + this.m12 -= m1.m12; + this.m13 -= m1.m13; + + this.m20 -= m1.m20; + this.m21 -= m1.m21; + this.m22 -= m1.m22; + this.m23 -= m1.m23; + + this.m30 -= m1.m30; + this.m31 -= m1.m31; + this.m32 -= m1.m32; + this.m33 -= m1.m33; + } + + /** + * Performs an element-by-element subtraction of matrix m2 from matrix m1 + * and places the result into matrix this (this = m2 - m1). + * + * @param m1 + * the first matrix + * @param m2 + * the second matrix + */ + public final void sub(Matrix4f m1, Matrix4f m2) { + this.m00 = m1.m00 - m2.m00; + this.m01 = m1.m01 - m2.m01; + this.m02 = m1.m02 - m2.m02; + this.m03 = m1.m03 - m2.m03; + + this.m10 = m1.m10 - m2.m10; + this.m11 = m1.m11 - m2.m11; + this.m12 = m1.m12 - m2.m12; + this.m13 = m1.m13 - m2.m13; + + this.m20 = m1.m20 - m2.m20; + this.m21 = m1.m21 - m2.m21; + this.m22 = m1.m22 - m2.m22; + this.m23 = m1.m23 - m2.m23; + + this.m30 = m1.m30 - m2.m30; + this.m31 = m1.m31 - m2.m31; + this.m32 = m1.m32 - m2.m32; + this.m33 = m1.m33 - m2.m33; + } + + /** + * Returns a string that contains the values of this Matrix4f. + * + * @return the String representation + */ + public String toString() { + return this.m00 + ", " + this.m01 + ", " + this.m02 + ", " + this.m03 + + "\n" + this.m10 + ", " + this.m11 + ", " + this.m12 + ", " + + this.m13 + "\n" + this.m20 + ", " + this.m21 + ", " + + this.m22 + ", " + this.m23 + "\n" + this.m30 + ", " + + this.m31 + ", " + this.m32 + ", " + this.m33 + "\n"; + } + + /** + * Transform the vector vec using this Transform and place the result back + * into vec. + * + * @param vec + * the single precision vector to be transformed + */ + public final void transform(Vec4D vec) { + float x, y, z; + + x = m00 * vec.x + m01 * vec.y + m02 * vec.z + m03 * vec.w; + y = m10 * vec.x + m11 * vec.y + m12 * vec.z + m13 * vec.w; + z = m20 * vec.x + m21 * vec.y + m22 * vec.z + m23 * vec.w; + vec.w = m30 * vec.x + m31 * vec.y + m32 * vec.z + m33 * vec.w; + vec.x = x; + vec.y = y; + vec.z = z; + } + + /** + * Transform the vector vec using this Matrix4f and place the result into + * vecOut. + * + * @param vec + * the single precision vector to be transformed + * @param vecOut + * the vector into which the transformed values are placed + */ + public final void transform(Vec4D vec, Vec4D vecOut) { + float x, y, z; + x = m00 * vec.x + m01 * vec.y + m02 * vec.z + m03 * vec.w; + y = m10 * vec.x + m11 * vec.y + m12 * vec.z + m13 * vec.w; + z = m20 * vec.x + m21 * vec.y + m22 * vec.z + m23 * vec.w; + vecOut.w = m30 * vec.x + m31 * vec.y + m32 * vec.z + m33 * vec.w; + vecOut.x = x; + vecOut.y = y; + vecOut.z = z; + } + + /** + * Transforms the point parameter with this Matrix4f and places the result + * back into point. The fourth element of the point input paramter is + * assumed to be one. + * + * @param point + * the input point to be transformed. + */ + public final void transformOne(Vec3D point) { + float x, y; + x = m00 * point.x + m01 * point.y + m02 * point.z + m03; + y = m10 * point.x + m11 * point.y + m12 * point.z + m13; + point.z = m20 * point.x + m21 * point.y + m22 * point.z + m23; + point.x = x; + point.y = y; + } + + /** + * Transforms the point parameter with this Matrix4f and places the result + * into pointOut. The fourth element of the point input paramter is assumed + * to be one. + * + * @param point + * the input point to be transformed. + * @param pointOut + * the transformed point + */ + public final void transformOne(Vec3D point, Vec3D pointOut) { + float x, y; + x = m00 * point.x + m01 * point.y + m02 * point.z + m03; + y = m10 * point.x + m11 * point.y + m12 * point.z + m13; + pointOut.z = m20 * point.x + m21 * point.y + m22 * point.z + m23; + pointOut.x = x; + pointOut.y = y; + } + + /** + * Transforms the normal parameter by this transform and places the value + * back into normal. The fourth element of the normal is assumed to be zero. + * + * @param normal + * the input normal to be transformed. + */ + public final void transformZero(Vec3D normal) { + float x, y; + + x = m00 * normal.x + m01 * normal.y + m02 * normal.z; + y = m10 * normal.x + m11 * normal.y + m12 * normal.z; + normal.z = m20 * normal.x + m21 * normal.y + m22 * normal.z; + normal.x = x; + normal.y = y; + } + + /** + * Transforms the normal parameter by this Matrix4f and places the value + * into normalOut. The fourth element of the normal is assumed to be zero. + * + * @param normal + * the input normal to be transformed. + * @param normalOut + * the transformed normal + */ + public final void transformZero(Vec3D normal, Vec3D normalOut) { + float x, y; + x = m00 * normal.x + m01 * normal.y + m02 * normal.z; + y = m10 * normal.x + m11 * normal.y + m12 * normal.z; + normalOut.z = m20 * normal.x + m21 * normal.y + m22 * normal.z; + normalOut.x = x; + normalOut.y = y; + } + + /** + * Sets the value of this matrix to its transpose in place. + */ + public final void transpose() { + float temp; + + temp = this.m10; + this.m10 = this.m01; + this.m01 = temp; + + temp = this.m20; + this.m20 = this.m02; + this.m02 = temp; + + temp = this.m30; + this.m30 = this.m03; + this.m03 = temp; + + temp = this.m21; + this.m21 = this.m12; + this.m12 = temp; + + temp = this.m31; + this.m31 = this.m13; + this.m13 = temp; + + temp = this.m32; + this.m32 = this.m23; + this.m23 = temp; + } + + /** + * Sets the value of this matrix to the transpose of the argument matrix. + * + * @param m1 + * the matrix to be transposed + */ + public final void transpose(Matrix4f m1) { + if (this != m1) { + this.m00 = m1.m00; + this.m01 = m1.m10; + this.m02 = m1.m20; + this.m03 = m1.m30; + + this.m10 = m1.m01; + this.m11 = m1.m11; + this.m12 = m1.m21; + this.m13 = m1.m31; + + this.m20 = m1.m02; + this.m21 = m1.m12; + this.m22 = m1.m22; + this.m23 = m1.m32; + + this.m30 = m1.m03; + this.m31 = m1.m13; + this.m32 = m1.m23; + this.m33 = m1.m33; + } else { + this.transpose(); + } + } +} diff --git a/src/main/java/toxi/geom/Matrix4x4.java b/src/main/java/toxi/geom/Matrix4x4.java new file mode 100644 index 0000000..e847b73 --- /dev/null +++ b/src/main/java/toxi/geom/Matrix4x4.java @@ -0,0 +1,815 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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; + +/** + * Implements a simple row-major 4x4 matrix class, all matrix operations are + * applied to new instances. Use {@link #transpose()} to convert from + * column-major formats... + */ +public class Matrix4x4 { + + private static final Matrix4x4 TEMP = new Matrix4x4(); + + /** + * Given a MxM array "matrix0", this function replaces it with the LU + * decomposition of a row-wise permutation of itself. The input parameters + * are "matrix0" and "dimen". The array "matrix0" is also an output + * parameter. The vector "row_perm[4]" is an output parameter that contains + * the row permutations resulting from partial pivoting. The output + * parameter "even_row_xchg" is 1 when the number of row exchanges is even, + * or -1 otherwise. Assumes data type is always double. + * + * This function is similar to luDecomposition, except that it is tuned + * specifically for 4x4 matrices. + * + * Reference: Press, Flannery, Teukolsky, Vetterling, + * _Numerical_Recipes_in_C_, Cambridge University Press, 1988, pp 40-45. + * + * @param matrix0 + * @param row_perm + * @param width + * @return true if the matrix is nonsingular, or false otherwise. + */ + static boolean LUDecomposition(double[] matrix0, int[] row_perm, int width) { + double row_scale[] = new double[width]; + // Determine implicit scaling information by looping over rows + { + int i, j; + int ptr, rs; + double big, temp; + + ptr = 0; + rs = 0; + + // For each row ... + i = width; + while (i-- != 0) { + big = 0.0; + + // For each column, find the largest element in the row + j = width; + while (j-- != 0) { + temp = matrix0[ptr++]; + temp = Math.abs(temp); + if (temp > big) { + big = temp; + } + } + // Is the matrix singular? + if (big == 0.0) { + return false; + } + row_scale[rs++] = 1.0 / big; + } + } + + { + int j; + int mtx = 0; + + // For all columns, execute Crout's method + for (j = 0; j < width; j++) { + int i, imax, k; + int target, p1, p2; + double sum, big, temp; + + // Determine elements of upper diagonal matrix U + for (i = 0; i < j; i++) { + target = mtx + (width * i) + j; + sum = matrix0[target]; + k = i; + p1 = mtx + (width * i); + p2 = mtx + j; + while (k-- != 0) { + sum -= matrix0[p1] * matrix0[p2]; + p1++; + p2 += width; + } + matrix0[target] = sum; + } + + // Search for largest pivot element and calculate + // intermediate elements of lower diagonal matrix L. + big = 0.0; + imax = -1; + for (i = j; i < width; i++) { + target = mtx + (width * i) + j; + sum = matrix0[target]; + k = j; + p1 = mtx + (width * i); + p2 = mtx + j; + while (k-- != 0) { + sum -= matrix0[p1] * matrix0[p2]; + p1++; + p2 += width; + } + matrix0[target] = sum; + + // Is this the best pivot so far? + if ((temp = row_scale[i] * Math.abs(sum)) >= big) { + big = temp; + imax = i; + } + } + + if (imax < 0) { + throw new RuntimeException(); + } + + // Is a row exchange necessary? + if (j != imax) { + // Yes: exchange rows + k = width; + p1 = mtx + (width * imax); + p2 = mtx + (width * j); + while (k-- != 0) { + temp = matrix0[p1]; + matrix0[p1++] = matrix0[p2]; + matrix0[p2++] = temp; + } + + // Record change in scale factor + row_scale[imax] = row_scale[j]; + } + + // Record row permutation + row_perm[j] = imax; + + // Is the matrix singular + if (matrix0[(mtx + (width * j) + j)] == 0.0) { + return false; + } + + // Divide elements of lower diagonal matrix L by pivot + if (j != width - 1) { + temp = 1.0 / (matrix0[(mtx + (width * j) + j)]); + target = mtx + (width * (j + 1)) + j; + i = (width - 1) - j; + while (i-- != 0) { + matrix0[target] *= temp; + target += width; + } + } + } + } + return true; + } + + public double[][] matrix; + protected double[] temp = new double[4]; + + public Matrix4x4() { + init(); + matrix[0][0] = 1; + matrix[1][1] = 1; + matrix[2][2] = 1; + matrix[3][3] = 1; + } + + public Matrix4x4(double v11, double v12, double v13, double v14, + double v21, double v22, double v23, double v24, double v31, + double v32, double v33, double v34, double v41, double v42, + double v43, double v44) { + init(); + double[] m = matrix[0]; + m[0] = v11; + m[1] = v12; + m[2] = v13; + m[3] = v14; + m = matrix[1]; + m[0] = v21; + m[1] = v22; + m[2] = v23; + m[3] = v24; + m = matrix[2]; + m[0] = v31; + m[1] = v32; + m[2] = v33; + m[3] = v34; + m = matrix[3]; + m[0] = v41; + m[1] = v42; + m[2] = v43; + m[3] = v44; + } + + /** + * Initialising constructor from a 1d array. Assumes row-major ordering + * (column index increases faster). + * + * @param array + */ + public Matrix4x4(double[] array) { + if (array.length != 9 && array.length != 16) { + throw new RuntimeException("Array.length must == 9 or 16"); + } + init(); + if (array.length == 16) { + matrix[0][0] = array[0]; + matrix[0][1] = array[1]; + matrix[0][2] = array[2]; + matrix[0][3] = array[3]; + + matrix[1][0] = array[4]; + matrix[1][1] = array[5]; + matrix[1][2] = array[6]; + matrix[1][3] = array[7]; + + matrix[2][0] = array[8]; + matrix[2][1] = array[9]; + matrix[2][2] = array[10]; + matrix[2][3] = array[11]; + + matrix[3][0] = array[12]; + matrix[3][1] = array[13]; + matrix[3][2] = array[14]; + matrix[3][3] = array[15]; + } else { + matrix[0][0] = array[0]; + matrix[0][1] = array[1]; + matrix[0][2] = array[2]; + + matrix[1][0] = array[3]; + matrix[1][1] = array[4]; + matrix[1][2] = array[5]; + + matrix[2][0] = array[6]; + matrix[2][1] = array[7]; + matrix[2][2] = array[8]; + + matrix[3][0] = array[9]; + matrix[3][1] = array[10]; + matrix[3][2] = array[11]; + matrix[3][3] = 1; + } + } + + public Matrix4x4(Matrix4x4 m) { + init(); + for (int i = 0; i < 4; i++) { + double[] mi = matrix[i]; + double[] mmi = m.matrix[i]; + mi[0] = mmi[0]; + mi[1] = mmi[1]; + mi[2] = mmi[2]; + mi[3] = mmi[3]; + } + } + + public Matrix4x4 add(Matrix4x4 rhs) { + Matrix4x4 result = new Matrix4x4(this); + return result.addSelf(rhs); + } + + public Matrix4x4 addSelf(Matrix4x4 m) { + for (int i = 0; i < 4; i++) { + double[] mi = matrix[i]; + double[] rhsm = m.matrix[i]; + mi[0] += rhsm[0]; + mi[1] += rhsm[1]; + mi[2] += rhsm[2]; + mi[3] += rhsm[3]; + } + return this; + } + + /** + * Creates a copy of the given vector, transformed by this matrix. + * + * @param v + * @return transformed vector + */ + public Vec3D applyTo(XYZ v) { + return applyToSelf(new Vec3D(v)); + } + + public Vec3D applyToSelf(Vec3D 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((float) temp[0], (float) temp[1], (float) temp[2]).scaleSelf( + (float) (1.0 / temp[3])); + return v; + } + + public Matrix4x4 copy() { + return new Matrix4x4(this); + } + + public Matrix4x4 getInverted() { + return new Matrix4x4(this).invert(); + } + + public Matrix4x4 getRotatedAroundAxis(roVec3D axis, double theta) { + return new Matrix4x4(this).rotateAroundAxis(axis, theta); + } + + public Matrix4x4 getRotatedX(double theta) { + return new Matrix4x4(this).rotateX(theta); + } + + public Matrix4x4 getRotatedY(double theta) { + return new Matrix4x4(this).rotateY(theta); + } + + public Matrix4x4 getRotatedZ(double theta) { + return new Matrix4x4(this).rotateZ(theta); + } + + public Matrix4x4 getTransposed() { + return new Matrix4x4(this).transpose(); + } + + public Matrix4x4 identity() { + double[] m; + m = matrix[0]; + m[1] = m[2] = m[3] = 0; + m = matrix[1]; + m[0] = m[2] = m[3] = 0; + m = matrix[2]; + m[0] = m[1] = m[3] = 0; + m = matrix[3]; + m[0] = m[1] = m[2] = 0; + matrix[0][0] = 1; + matrix[1][1] = 1; + matrix[2][2] = 1; + matrix[3][3] = 1; + return this; + } + + private final void init() { + matrix = new double[][] { + new double[4], new double[4], new double[4], new double[4] + }; + } + + /** + * Matrix Inversion using Cramer's Method Computes Adjoint matrix divided by + * determinant Code modified from + * http://www.intel.com/design/pentiumiii/sml/24504301.pdf + * + * @return itself + */ + public Matrix4x4 invert() { + final double[] tmp = new double[12]; + final double[] src = new double[16]; + final double[] dst = new double[16]; + final double[] mat = toArray(null); + + for (int i = 0; i < 4; i++) { + int i4 = i << 2; + src[i] = mat[i4]; + src[i + 4] = mat[i4 + 1]; + src[i + 8] = mat[i4 + 2]; + src[i + 12] = mat[i4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + double src0 = src[0]; + double src1 = src[1]; + double src2 = src[2]; + double src3 = src[3]; + double src4 = src[4]; + double src5 = src[5]; + double src6 = src[6]; + double src7 = src[7]; + dst[0] = tmp[0] * src5 + tmp[3] * src6 + tmp[4] * src7; + dst[0] -= tmp[1] * src5 + tmp[2] * src6 + tmp[5] * src7; + dst[1] = tmp[1] * src4 + tmp[6] * src6 + tmp[9] * src7; + dst[1] -= tmp[0] * src4 + tmp[7] * src6 + tmp[8] * src7; + dst[2] = tmp[2] * src4 + tmp[7] * src5 + tmp[10] * src7; + dst[2] -= tmp[3] * src4 + tmp[6] * src5 + tmp[11] * src7; + dst[3] = tmp[5] * src4 + tmp[8] * src5 + tmp[11] * src6; + dst[3] -= tmp[4] * src4 + tmp[9] * src5 + tmp[10] * src6; + dst[4] = tmp[1] * src1 + tmp[2] * src2 + tmp[5] * src3; + dst[4] -= tmp[0] * src1 + tmp[3] * src2 + tmp[4] * src3; + dst[5] = tmp[0] * src0 + tmp[7] * src2 + tmp[8] * src3; + dst[5] -= tmp[1] * src0 + tmp[6] * src2 + tmp[9] * src3; + dst[6] = tmp[3] * src0 + tmp[6] * src1 + tmp[11] * src3; + dst[6] -= tmp[2] * src0 + tmp[7] * src1 + tmp[10] * src3; + dst[7] = tmp[4] * src0 + tmp[9] * src1 + tmp[10] * src2; + dst[7] -= tmp[5] * src0 + tmp[8] * src1 + tmp[11] * src2; + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src2 * src7; + tmp[1] = src3 * src6; + tmp[2] = src1 * src7; + tmp[3] = src3 * src5; + tmp[4] = src1 * src6; + tmp[5] = src2 * src5; + tmp[6] = src0 * src7; + tmp[7] = src3 * src4; + tmp[8] = src0 * src6; + tmp[9] = src2 * src4; + tmp[10] = src0 * src5; + tmp[11] = src1 * src4; + + // calculate second 8 elements (cofactors) + src0 = src[8]; + src1 = src[9]; + src2 = src[10]; + src3 = src[11]; + src4 = src[12]; + src5 = src[13]; + src6 = src[14]; + src7 = src[15]; + dst[8] = tmp[0] * src5 + tmp[3] * src6 + tmp[4] * src7; + dst[8] -= tmp[1] * src5 + tmp[2] * src6 + tmp[5] * src7; + dst[9] = tmp[1] * src4 + tmp[6] * src6 + tmp[9] * src7; + dst[9] -= tmp[0] * src4 + tmp[7] * src6 + tmp[8] * src7; + dst[10] = tmp[2] * src4 + tmp[7] * src5 + tmp[10] * src7; + dst[10] -= tmp[3] * src4 + tmp[6] * src5 + tmp[11] * src7; + dst[11] = tmp[5] * src4 + tmp[8] * src5 + tmp[11] * src6; + dst[11] -= tmp[4] * src4 + tmp[9] * src5 + tmp[10] * src6; + dst[12] = tmp[2] * src2 + tmp[5] * src3 + tmp[1] * src1; + dst[12] -= tmp[4] * src3 + tmp[0] * src1 + tmp[3] * src2; + dst[13] = tmp[8] * src3 + tmp[0] * src0 + tmp[7] * src2; + dst[13] -= tmp[6] * src2 + tmp[9] * src3 + tmp[1] * src0; + dst[14] = tmp[6] * src1 + tmp[11] * src3 + tmp[3] * src0; + dst[14] -= tmp[10] * src3 + tmp[2] * src0 + tmp[7] * src1; + dst[15] = tmp[10] * src2 + tmp[4] * src0 + tmp[9] * src1; + dst[15] -= tmp[8] * src1 + tmp[11] * src2 + tmp[5] * src0; + + double det = 1.0 / (src[0] * dst[0] + src[1] * dst[1] + src[2] * dst[2] + src[3] + * dst[3]); + + for (int i = 0, k = 0; i < 4; i++) { + double[] m = matrix[i]; + for (int j = 0; j < 4; j++) { + m[j] = dst[k++] * det; + } + } + return this; + } + + public Matrix4x4 lookAt(roVec3D eye, roVec3D target, + roVec3D 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 multiply(double factor) { + return new Matrix4x4(this).multiply(factor); + } + + /** + * Matrix-Matrix Right-multiplication. + * + * @param mat + * @return product as new matrix + */ + public Matrix4x4 multiply(Matrix4x4 mat) { + return new Matrix4x4(this).multiplySelf(mat); + } + + /** + * In-place matrix-scalar multiplication. + * + * @param factor + * @return product applied to this matrix. + */ + public Matrix4x4 multiplySelf(double factor) { + for (int i = 0; i < 4; i++) { + double[] m = matrix[i]; + m[0] *= factor; + m[1] *= factor; + m[2] *= factor; + m[3] *= factor; + } + return this; + } + + public Matrix4x4 multiplySelf(Matrix4x4 mat) { + double[] mm0 = mat.matrix[0]; + double[] mm1 = mat.matrix[1]; + double[] mm2 = mat.matrix[2]; + double[] mm3 = mat.matrix[3]; + for (int i = 0; i < 4; i++) { + double[] m = matrix[i]; + for (int j = 0; j < 4; j++) { + temp[j] = m[0] * mm0[j] + m[1] * mm1[j] + m[2] * mm2[j] + m[3] + * mm3[j]; + } + m[0] = temp[0]; + m[1] = temp[1]; + m[2] = temp[2]; + m[3] = temp[3]; + } + return this; + } + + /** + * Applies rotation about arbitrary axis to matrix + * + * @param axis + * @param theta + * @return rotation applied to this matrix + */ + public Matrix4x4 rotateAroundAxis(roVec3D 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. + * + * @param theta + * rotation angle in radians + * @return itself + */ + public Matrix4x4 rotateX(double theta) { + TEMP.identity(); + TEMP.matrix[1][1] = TEMP.matrix[2][2] = Math.cos(theta); + TEMP.matrix[2][1] = Math.sin(theta); + TEMP.matrix[1][2] = -TEMP.matrix[2][1]; + return this.multiplySelf(TEMP); + } + + /** + * Applies rotation about Y to this matrix. + * + * @param theta + * rotation angle in radians + * @return itself + */ + public Matrix4x4 rotateY(double theta) { + TEMP.identity(); + TEMP.matrix[0][0] = TEMP.matrix[2][2] = Math.cos(theta); + TEMP.matrix[0][2] = Math.sin(theta); + TEMP.matrix[2][0] = -TEMP.matrix[0][2]; + return this.multiplySelf(TEMP); + } + + // Apply Rotation about Z to Matrix + public Matrix4x4 rotateZ(double theta) { + TEMP.identity(); + TEMP.matrix[0][0] = TEMP.matrix[1][1] = Math.cos(theta); + TEMP.matrix[1][0] = Math.sin(theta); + TEMP.matrix[0][1] = -TEMP.matrix[1][0]; + return this.multiplySelf(TEMP); + } + + public Matrix4x4 scale(double scale) { + return new Matrix4x4(this).scaleSelf(scale); + } + + public Matrix4x4 scale(double scaleX, double scaleY, double scaleZ) { + return new Matrix4x4(this).scaleSelf(scaleX, scaleY, scaleZ); + } + + public Matrix4x4 scale(roVec3D scale) { + return new Matrix4x4(this).scaleSelf(scale.x(), scale.y(), scale.z()); + } + + public Matrix4x4 scaleSelf(double scale) { + return scaleSelf(scale, scale, scale); + } + + public Matrix4x4 scaleSelf(double scaleX, double scaleY, double scaleZ) { + TEMP.identity(); + TEMP.setScale(scaleX, scaleY, scaleZ); + return this.multiplySelf(TEMP); + } + + public Matrix4x4 scaleSelf(roVec3D 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, + double l, double m, double n, double o, double p) { + double[] mat = matrix[0]; + mat[0] = a; + mat[1] = b; + mat[2] = c; + mat[3] = d; + mat = matrix[1]; + mat[0] = e; + mat[1] = f; + mat[2] = g; + mat[3] = h; + mat = matrix[2]; + mat[0] = i; + mat[1] = j; + mat[2] = k; + mat[3] = l; + mat = matrix[3]; + mat[0] = m; + mat[1] = n; + mat[2] = o; + mat[3] = p; + return this; + } + + public Matrix4x4 set(Matrix4x4 mat) { + for (int i = 0; i < 4; i++) { + double[] m = matrix[i]; + double[] n = mat.matrix[i]; + m[0] = n[0]; + m[1] = n[1]; + m[2] = n[2]; + m[3] = n[3]; + } + return this; + } + + public Matrix4x4 setFrustum(double left, double right, double top, + double bottom, double near, double far) { + return set((2.0 * near) / (right - left), 0, (left + right) + / (right - left), 0, 0, (2.0 * near) / (top - bottom), + (top + bottom) / (top - bottom), 0, 0, 0, -(near + far) + / (far - near), (-2 * near * far) / (far - near), 0, 0, + -1, 0); + } + + public Matrix4x4 setOrtho(double left, double right, double top, + double bottom, double near, double far) { + return set(2.0 / (right - left), 0, 0, (left + right) / (right - left), + 0, 2.0 / (top - bottom), 0, (top + bottom) / (top - bottom), 0, + 0, -2.0 / (far - near), (far + near) / (far - near), 0, 0, 0, 1); + } + + public Matrix4x4 setPerspective(double fov, double aspect, double near, + double far) { + double y = near * Math.tan(0.5 * MathUtils.radians(fov)); + double x = aspect * y; + return setFrustum(-x, x, y, -y, near, far); + } + + public Matrix4x4 setPosition(double x, double y, double z) { + matrix[0][3] = x; + matrix[1][3] = y; + matrix[2][3] = z; + return this; + } + + public Matrix4x4 setScale(double scaleX, double scaleY, double scaleZ) { + matrix[0][0] = scaleX; + matrix[1][1] = scaleY; + matrix[2][2] = scaleZ; + return this; + } + + public Matrix4x4 sub(Matrix4x4 m) { + return new Matrix4x4(this).subSelf(m); + } + + public Matrix4x4 subSelf(Matrix4x4 mat) { + for (int i = 0; i < 4; i++) { + double[] m = matrix[i]; + double[] n = mat.matrix[i]; + m[0] -= n[0]; + m[1] -= n[1]; + m[2] -= n[2]; + m[3] -= n[3]; + } + return this; + } + + /** + * Copies all matrix elements into an linear array. + * + * @param result + * array (or null to create a new one) + * @return matrix as 16 element array + */ + public double[] toArray(double[] result) { + if (result == null) { + result = new double[16]; + } + for (int i = 0, k = 0; i < 4; i++) { + double[] m = matrix[i]; + for (int j = 0; j < 4; j++) { + result[k++] = m[j]; + } + } + return result; + } + + public float[] toFloatArray(float[] result) { + if (result == null) { + result = new float[16]; + } + double[] tmp = toArray(null); + for (int i = 0; i < 16; i++) { + result[i] = (float) tmp[i]; + } + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + return "| " + matrix[0][0] + " " + matrix[0][1] + " " + matrix[0][2] + + " " + matrix[0][3] + " |\n" + "| " + matrix[1][0] + " " + + matrix[1][1] + " " + matrix[1][2] + " " + matrix[1][3] + + " |\n" + "| " + matrix[2][0] + " " + matrix[2][1] + " " + + matrix[2][2] + " " + matrix[2][3] + " |\n" + "| " + + matrix[3][0] + " " + matrix[3][1] + " " + matrix[3][2] + " " + + matrix[3][3] + " |"; + } + + public float[] toTransposedFloatArray(float[] result) { + if (result == null) { + result = new float[16]; + } + for (int i = 0, k = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + result[k++] = (float) matrix[j][i]; + } + } + return result; + } + + public Matrix4x4 translate(double dx, double dy, double dz) { + return new Matrix4x4(this).translateSelf(dx, dy, dz); + } + + public Matrix4x4 translate(roVec3D trans) { + return new Matrix4x4(this).translateSelf(trans.x(), trans.y(), + trans.z()); + } + + public Matrix4x4 translateSelf(double dx, double dy, double dz) { + TEMP.identity(); + TEMP.setPosition(dx, dy, dz); + return this.multiplySelf(TEMP); + } + + public Matrix4x4 translateSelf(roVec3D trans) { + return translateSelf(trans.x(), trans.y(), trans.z()); + } + + /** + * Converts the matrix (in-place) between column-major to row-major order + * (and vice versa). + * + * @return itself + */ + public Matrix4x4 transpose() { + return set(matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0], + matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1], + matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2], + matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]); + } +} \ No newline at end of file diff --git a/src/main/java/toxi/geom/MatrixSizeException.java b/src/main/java/toxi/geom/MatrixSizeException.java new file mode 100644 index 0000000..105ca7a --- /dev/null +++ b/src/main/java/toxi/geom/MatrixSizeException.java @@ -0,0 +1,58 @@ +/* + * $RCSfile$ + * + * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + * + * $Revision: 127 $ + * $Date: 2008-02-28 20:18:51 +0000 (Thu, 28 Feb 2008) $ + * $State$ + */ + +package toxi.geom; + +/** + * Indicates that an operation cannot be completed properly because of a + * mismatch in the sizes of object attributes. + */ +public class MatrixSizeException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Create the exception object with default values. + */ + public MatrixSizeException() { + } + + /** + * Create the exception object that outputs a message. + * + * @param str + * the message string to be output. + */ + public MatrixSizeException(String str) { + super(str); + } + +} diff --git a/src/main/java/com/syncleus/spangraph/geom/OctreeVisitor.java b/src/main/java/toxi/geom/OctreeVisitor.java similarity index 97% rename from src/main/java/com/syncleus/spangraph/geom/OctreeVisitor.java rename to src/main/java/toxi/geom/OctreeVisitor.java index 9b9f28d..cc9becb 100644 --- a/src/main/java/com/syncleus/spangraph/geom/OctreeVisitor.java +++ b/src/main/java/toxi/geom/OctreeVisitor.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * This interface is the core part of the visitor pattern application for diff --git a/src/main/java/com/syncleus/spangraph/geom/Origin3D.java b/src/main/java/toxi/geom/Origin3D.java similarity index 89% rename from src/main/java/com/syncleus/spangraph/geom/Origin3D.java rename to src/main/java/toxi/geom/Origin3D.java index 5e5598c..622bacf 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Origin3D.java +++ b/src/main/java/toxi/geom/Origin3D.java @@ -25,7 +25,9 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; + +import toxi.geom.Vec3D.Axis; /** * This class defines an origin and set of axis vectors for a 3D cartesian @@ -33,8 +35,8 @@ package com.syncleus.spangraph.geom; */ public class Origin3D { - public ReadonlyVec3D origin; - public ReadonlyVec3D xAxis, yAxis, zAxis; + public roVec3D origin; + public roVec3D xAxis, yAxis, zAxis; /** * Creates a new origin at the world origin using the standard XYZ axes @@ -47,12 +49,12 @@ public class Origin3D { this(new Vec3D(x, y, z)); } -// public Origin3D(Matrix4x4 mat) { -// this.origin = mat.applyToSelf(new Vec3D()); -// this.xAxis = mat.applyTo(Vec3D.X_AXIS).subSelf(origin).normalize(); -// this.yAxis = mat.applyTo(Vec3D.Y_AXIS).subSelf(origin).normalize(); -// zAxis = xAxis.crossInto(yAxis, new Vec3D()); -// } + public Origin3D(Matrix4x4 mat) { + this.origin = mat.applyToSelf(new Vec3D()); + this.xAxis = mat.applyTo(Vec3D.X_AXIS).subSelf(origin).normalize(); + this.yAxis = mat.applyTo(Vec3D.Y_AXIS).subSelf(origin).normalize(); + zAxis = xAxis.crossInto(yAxis, new Vec3D()); + } /** * Creates a new origin at the given origin using the standard XYZ axes diff --git a/src/main/java/com/syncleus/spangraph/geom/Plane.java b/src/main/java/toxi/geom/Plane.java similarity index 79% rename from src/main/java/com/syncleus/spangraph/geom/Plane.java rename to src/main/java/toxi/geom/Plane.java index 2cdb7eb..dae1260 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Plane.java +++ b/src/main/java/toxi/geom/Plane.java @@ -25,14 +25,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +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.Mesh3D; -import toxi.geom.mesh.TriangleMesh; import toxi.math.MathUtils; /** @@ -43,7 +41,7 @@ import toxi.math.MathUtils; public class Plane extends Vec3D implements Shape3D { /** - * Classifier constant for {@link Plane#classifyPoint(ReadonlyVec3D, float)} + * Classifier constant for {@link Plane#classifyPoint(roVec3D, float)} */ public enum Classifier { FRONT, @@ -63,7 +61,7 @@ public class Plane extends Vec3D implements Shape3D { normal = Vec3D.Y_AXIS.copy(); } - public Plane(ReadonlyVec3D origin, ReadonlyVec3D norm) { + public Plane(roVec3D origin, roVec3D norm) { super(origin); normal = norm.getNormalized(); } @@ -78,7 +76,7 @@ public class Plane extends Vec3D implements Shape3D { * * @return One of the 3 classification types: FRONT, BACK, ON_PLANE */ - public Classifier classifyPoint(ReadonlyVec3D p, float tolerance) { + public Classifier classifyPoint(XYZ p, float tolerance) { float d = this.sub(p).normalize().dot(normal); if (d < -tolerance) { return Classifier.FRONT; @@ -88,7 +86,7 @@ public class Plane extends Vec3D implements Shape3D { return Classifier.ON_PLANE; } - public boolean containsPoint(ReadonlyVec3D p) { + public boolean containsPoint(XYZ p) { return classifyPoint(p, MathUtils.EPS) == Classifier.ON_PLANE; } @@ -115,7 +113,7 @@ public class Plane extends Vec3D implements Shape3D { * @param r * @return intersection point or null if ray doesn't intersect plane */ - public ReadonlyVec3D getIntersectionWithRay(Ray3D r) { + public roVec3D getIntersectionWithRay(Ray3D r) { float denom = normal.dot(r.getDirection()); if (denom > MathUtils.EPS) { float u = normal.dot(this.sub(r)) / denom; @@ -125,14 +123,14 @@ public class Plane extends Vec3D implements Shape3D { } } - public Vec3D getProjectedPoint(Vec3D p) { + public XYZ getProjectedPoint(Vec3D p) { Vec3D dir; if (normal.dot(sub(p)) < 0) { dir = normal.getInverted(); } else { dir = normal; } - Vec3D proj = new Ray3D(p, dir) + XYZ proj = new Ray3D(p, dir) .getPointAtDistance(getDistanceToPoint(p)); return proj; } @@ -193,35 +191,35 @@ public class Plane extends Vec3D implements Shape3D { return new Ray3D(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 Mesh3D toMesh(float size) { - return toMesh(null, size); - } - - public Mesh3D toMesh(Mesh3D mesh, float size) { - if (mesh == null) { - mesh = new TriangleMesh("plane", 4, 2); - } - ReadonlyVec3D p = equalsWithTolerance(Vec3D.ZERO, 0.01f) ? add(0.01f, - 0.01f, 0.01f) : this; - size *= 0.5f; - Vec3D n = p.cross(normal).normalizeTo(size); - Vec3D m = n.cross(normal).normalizeTo(size); - Vec3D a = this.add(n).addSelf(m); - Vec3D b = this.add(n).subSelf(m); - Vec3D c = this.sub(n).subSelf(m); - Vec3D d = this.sub(n).addSelf(m); - mesh.addFace(a, d, b, null, null, null, null); - mesh.addFace(b, d, c, null, null, null, null); - return mesh; - } +// /** +// * 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 Mesh3D toMesh(float size) { +// return toMesh(null, size); +// } +// +// public Mesh3D toMesh(Mesh3D mesh, float size) { +// if (mesh == null) { +// mesh = new TriangleMesh("plane", 4, 2); +// } +// roVec3D p = equalsWithTolerance(Vec3D.ZERO, 0.01f) ? add(0.01f, +// 0.01f, 0.01f) : this; +// size *= 0.5f; +// Vec3D n = p.cross(normal).normalizeTo(size); +// Vec3D m = n.cross(normal).normalizeTo(size); +// Vec3D a = this.add(n).addSelf(m); +// Vec3D b = this.add(n).subSelf(m); +// Vec3D c = this.sub(n).subSelf(m); +// Vec3D d = this.sub(n).addSelf(m); +// mesh.addFace(a, d, b, null, null, null, null); +// mesh.addFace(b, d, c, null, null, null, null); +// return mesh; +// } public String toString() { StringBuffer sb = new StringBuffer(); diff --git a/src/main/java/toxi/geom/PlaneIntersector.java b/src/main/java/toxi/geom/PlaneIntersector.java new file mode 100644 index 0000000..bcfff06 --- /dev/null +++ b/src/main/java/toxi/geom/PlaneIntersector.java @@ -0,0 +1,47 @@ +package toxi.geom; + +import toxi.math.MathUtils; + +public class PlaneIntersector implements Intersector3D { + + private Plane plane; + private final IsectData3D isec; + + public PlaneIntersector(Plane p) { + this.plane = p; + this.isec = new IsectData3D(); + } + + public IsectData3D getIntersectionData() { + return isec; + } + + /** + * @return the box + */ + public Plane getPlane() { + return plane; + } + + public boolean intersectsRay(Ray3D ray) { + float d = -plane.normal.dot(plane); + float numer = plane.normal.dot(ray) + d; + float 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 setPlane(Plane p) { + this.plane = p; + } +} diff --git a/src/main/java/com/syncleus/spangraph/geom/PointCloud3D.java b/src/main/java/toxi/geom/PointCloud3D.java similarity index 87% rename from src/main/java/com/syncleus/spangraph/geom/PointCloud3D.java rename to src/main/java/toxi/geom/PointCloud3D.java index 59d180a..dbe8a46 100644 --- a/src/main/java/com/syncleus/spangraph/geom/PointCloud3D.java +++ b/src/main/java/toxi/geom/PointCloud3D.java @@ -25,7 +25,9 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; + +import toxi.math.MathUtils; import java.util.ArrayList; import java.util.Iterator; @@ -45,7 +47,7 @@ public class PointCloud3D implements Iterable<Vec3D> { } public PointCloud3D(int numPoints) { - points = new ArrayList<Vec3D>(numPoints); + points = new ArrayList(numPoints); clear(); } @@ -66,20 +68,20 @@ public class PointCloud3D implements Iterable<Vec3D> { return this; } -// /** -// * Applies the given transformation matrix to all points in the cloud. -// * -// * @param m -// * transformation matrix -// * @return itself -// */ -// public PointCloud3D applyMatrix(Matrix4x4 m) { -// for (Vec3D p : points) { -// p.set(m.applyTo(p)); -// } -// updateBounds(); -// return this; -// } + /** + * Applies the given transformation matrix to all points in the cloud. + * + * @param m + * transformation matrix + * @return itself + */ + public PointCloud3D applyMatrix(Matrix4x4 m) { + for (Vec3D p : points) { + p.set(m.applyTo(p)); + } + updateBounds(); + return this; + } /** * Updates all points in the cloud so that their new centroid is at the @@ -99,7 +101,7 @@ public class PointCloud3D implements Iterable<Vec3D> { * new centroid * @return itself */ - public PointCloud3D center(ReadonlyVec3D origin) { + public PointCloud3D center(roVec3D origin) { getCentroid(); Vec3D delta = origin != null ? origin.sub(centroid) : centroid .getInverted(); @@ -132,7 +134,7 @@ public class PointCloud3D implements Iterable<Vec3D> { */ public PointCloud3D copy() { PointCloud3D c = new PointCloud3D(points.size()); - for (ReadonlyVec3D p : points) { + for (roVec3D p : points) { c.addPoint(p.copy()); } return c; @@ -169,7 +171,7 @@ public class PointCloud3D implements Iterable<Vec3D> { * @param p * @return true, if point has been removed. */ - public boolean removePoint(ReadonlyVec3D p) { + public boolean removePoint(roVec3D p) { return points.remove(p); } @@ -194,7 +196,7 @@ public class PointCloud3D implements Iterable<Vec3D> { } centroid.set(min.add(max).scaleSelf(0.5f)); radiusSquared = 0; - for (ReadonlyVec3D p : points) { + for (roVec3D p : points) { radiusSquared = MathUtils.max(radiusSquared, p.distanceToSquared(centroid)); } diff --git a/src/main/java/com/syncleus/spangraph/geom/PointOctree.java b/src/main/java/toxi/geom/PointOctree.java similarity index 87% rename from src/main/java/com/syncleus/spangraph/geom/PointOctree.java rename to src/main/java/toxi/geom/PointOctree.java index c646e09..c5de07f 100644 --- a/src/main/java/com/syncleus/spangraph/geom/PointOctree.java +++ b/src/main/java/toxi/geom/PointOctree.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import java.util.ArrayList; import java.util.Collection; @@ -45,24 +45,24 @@ public class PointOctree extends AABB implements Shape3D { * alternative tree recursion limit, number of world units when cells are * not subdivided any further */ - protected float minNodeSize = 4; + public float minNodeSize = 4; /** * */ - protected PointOctree parent; + public final PointOctree parent; - protected PointOctree[] children; + public PointOctree[] children; protected byte numChildren; - protected ArrayList<Vec3D> points; + protected ArrayList<XYZ> points; protected float size, halfSize; protected Vec3D offset; - private int depth = 0; + public int depth = 0; private boolean isAutoReducing = false; @@ -80,6 +80,7 @@ public class PointOctree extends AABB implements Shape3D { private PointOctree(PointOctree p, Vec3D o, float halfSize) { super(o.add(halfSize, halfSize, halfSize), new Vec3D(halfSize, halfSize, halfSize)); + this.parent = p; this.halfSize = halfSize; this.size = halfSize * 2; @@ -112,9 +113,9 @@ public class PointOctree extends AABB implements Shape3D { * point collection * @return true, if all points have been added successfully. */ - public boolean addAll(Collection<Vec3D> points) { + public boolean addAll(Collection<XYZ> points) { boolean addedAll = true; - for (Vec3D p : points) { + for (XYZ p : points) { addedAll &= addPoint(p); } return addedAll; @@ -128,13 +129,13 @@ public class PointOctree extends AABB implements Shape3D { * @param p * @return true, if point has been added successfully */ - public boolean addPoint(Vec3D p) { + public boolean addPoint(XYZ 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<Vec3D>(); + points = new ArrayList(); } points.add(p); return true; @@ -174,7 +175,7 @@ public class PointOctree extends AABB implements Shape3D { } } - public boolean containsPoint(ReadonlyVec3D p) { + public boolean containsPoint(roVec3D p) { return p.isInAABB(this); } @@ -210,7 +211,7 @@ public class PointOctree extends AABB implements Shape3D { * point to check * @return leaf node or null if point is outside the tree dimensions */ - public PointOctree getLeafForPoint(ReadonlyVec3D p) { + public PointOctree getLeafForPoint(XYZ p) { // if not a leaf node... if (p.isInAABB(this)) { if (numChildren > 0) { @@ -262,7 +263,7 @@ public class PointOctree extends AABB implements Shape3D { /** * @return the offset */ - public ReadonlyVec3D getOffset() { + public roVec3D getOffset() { return offset; } @@ -276,17 +277,17 @@ public class PointOctree extends AABB implements Shape3D { /** * @return the points */ - public List<Vec3D> getPoints() { - List<Vec3D> results = null; + public List<XYZ> getPoints() { + List<XYZ> results = null; if (points != null) { - results = new ArrayList<Vec3D>(points); + results = new ArrayList(points); } else if (numChildren > 0) { for (int i = 0; i < 8; i++) { if (children[i] != null) { - List<Vec3D> childPoints = children[i].getPoints(); + List<XYZ> childPoints = children[i].getPoints(); if (childPoints != null) { if (results == null) { - results = new ArrayList<Vec3D>(); + results = new ArrayList(); } results.addAll(childPoints); } @@ -303,14 +304,14 @@ public class PointOctree extends AABB implements Shape3D { * AABB * @return all points with the box volume */ - public List<Vec3D> getPointsWithinBox(AABB b) { - ArrayList<Vec3D> results = null; + public List<XYZ> getPointsWithinBox(AABB b) { + ArrayList<XYZ> results = null; if (this.intersectsBox(b)) { if (points != null) { - for (Vec3D q : points) { + for (XYZ q : points) { if (q.isInAABB(b)) { if (results == null) { - results = new ArrayList<Vec3D>(); + results = new ArrayList(); } results.add(q); } @@ -318,10 +319,10 @@ public class PointOctree extends AABB implements Shape3D { } else if (numChildren > 0) { for (int i = 0; i < 8; i++) { if (children[i] != null) { - List<Vec3D> points = children[i].getPointsWithinBox(b); + List<XYZ> points = children[i].getPointsWithinBox(b); if (points != null) { if (results == null) { - results = new ArrayList<Vec3D>(); + results = new ArrayList(); } results.addAll(points); } @@ -339,14 +340,14 @@ public class PointOctree extends AABB implements Shape3D { * sphere * @return selected points */ - public List<Vec3D> getPointsWithinSphere(Sphere s) { - ArrayList<Vec3D> results = null; + public List<XYZ> getPointsWithinSphere(Sphere s) { + ArrayList<XYZ> results = null; if (this.intersectsSphere(s)) { if (points != null) { - for (Vec3D q : points) { + for (XYZ q : points) { if (s.containsPoint(q)) { if (results == null) { - results = new ArrayList<Vec3D>(); + results = new ArrayList(); } results.add(q); } @@ -354,11 +355,11 @@ public class PointOctree extends AABB implements Shape3D { } else if (numChildren > 0) { for (int i = 0; i < 8; i++) { if (children[i] != null) { - List<Vec3D> points = children[i] + List<XYZ> points = children[i] .getPointsWithinSphere(s); if (points != null) { if (results == null) { - results = new ArrayList<Vec3D>(); + results = new ArrayList(); } results.addAll(points); } @@ -376,7 +377,7 @@ public class PointOctree extends AABB implements Shape3D { * @param clipRadius * @return selected points */ - public List<Vec3D> getPointsWithinSphere(Vec3D sphereOrigin, + public List<XYZ> getPointsWithinSphere(Vec3D sphereOrigin, float clipRadius) { return getPointsWithinSphere(new Sphere(sphereOrigin, clipRadius)); } @@ -412,7 +413,7 @@ public class PointOctree extends AABB implements Shape3D { * point to delete * @return true, if the point was found & removed */ - public boolean remove(ReadonlyVec3D p) { + public boolean remove(XYZ p) { boolean found = false; PointOctree leaf = getLeafForPoint(p); if (leaf != null) { @@ -426,8 +427,8 @@ public class PointOctree extends AABB implements Shape3D { return found; } - public void removeAll(Collection<Vec3D> points) { - for (ReadonlyVec3D p : points) { + public void removeAll(Collection<XYZ> points) { + for (XYZ p : points) { remove(p); } } diff --git a/src/main/java/com/syncleus/spangraph/geom/PointQuadtree.java b/src/main/java/toxi/geom/PointQuadtree.java similarity index 99% rename from src/main/java/com/syncleus/spangraph/geom/PointQuadtree.java rename to src/main/java/toxi/geom/PointQuadtree.java index 048ce79..05db672 100644 --- a/src/main/java/com/syncleus/spangraph/geom/PointQuadtree.java +++ b/src/main/java/toxi/geom/PointQuadtree.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/syncleus/spangraph/geom/Polygon2D.java b/src/main/java/toxi/geom/Polygon2D.java similarity index 95% rename from src/main/java/com/syncleus/spangraph/geom/Polygon2D.java rename to src/main/java/toxi/geom/Polygon2D.java index 58977cf..d0bf866 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Polygon2D.java +++ b/src/main/java/toxi/geom/Polygon2D.java @@ -25,21 +25,19 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; +import javafx.scene.shape.TriangleMesh; +import toxi.geom.Line2D.LineIntersection; +import toxi.geom.Line2D.LineIntersection.Type; +import toxi.math.MathUtils; + +import javax.xml.bind.annotation.XmlElement; 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.Line2D.LineIntersection; -import toxi.geom.Line2D.LineIntersection.Type; -import toxi.geom.mesh.Mesh3D; -import toxi.geom.mesh.TriangleMesh; -import toxi.math.MathUtils; - /** * Container type for convex polygons. Implements {@link Shape2D}. */ @@ -267,7 +265,7 @@ public class Polygon2D implements Shape2D, Iterable<Vec2D> { * Returns the polygon's bounding rect. * * @return bounding rect - * @see toxi.geom.Shape2D#getBounds() + * @see Shape2D#getBounds() */ public Rect getBounds() { return Rect.getBoundingRect(vertices); @@ -296,7 +294,7 @@ public class Polygon2D implements Shape2D, Iterable<Vec2D> { * * @return perimiter length * - * @see toxi.geom.Shape2D#getCircumference() + * @see Shape2D#getCircumference() */ public float getCircumference() { float circ = 0; @@ -728,32 +726,6 @@ public class Polygon2D implements Shape2D, Iterable<Vec2D> { return this; } - public Mesh3D toMesh(Mesh3D mesh) { - return toMesh(mesh, null, 0); - } - - public Mesh3D toMesh(Mesh3D mesh, Vec2D centroid2D, float extrude) { - if (mesh == null) { - mesh = new TriangleMesh(); - } - final int num = vertices.size(); - if (centroid2D == null) { - centroid2D = getCentroid(); - } - Vec3D centroid = centroid2D.to3DXY(); - centroid.z = extrude; - Rect bounds = getBounds(); - Vec2D boundScale = new Vec2D(1f / bounds.width, 1f / bounds.height); - Vec2D uvC = centroid2D.sub(bounds.getTopLeft()).scaleSelf(boundScale); - for (int i = 1; i <= num; i++) { - Vec2D a = vertices.get(i % num); - Vec2D b = vertices.get(i - 1); - Vec2D uvA = a.sub(bounds.getTopLeft()).scaleSelf(boundScale); - Vec2D uvB = b.sub(bounds.getTopLeft()).scaleSelf(boundScale); - mesh.addFace(centroid, a.to3DXY(), b.to3DXY(), uvC, uvA, uvB); - } - return mesh; - } /** * Attempts to remove all internal self-intersections and creates a new diff --git a/src/main/java/toxi/geom/PolygonClipper2D.java b/src/main/java/toxi/geom/PolygonClipper2D.java new file mode 100644 index 0000000..99a9722 --- /dev/null +++ b/src/main/java/toxi/geom/PolygonClipper2D.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 PolygonClipper2D { + + /** + * Creates a clipped version of the polygon to the boundary shape set. + * + * @param poly + * polygon to be clipped + * @return clipped poly + */ + public Polygon2D clipPolygon(Polygon2D poly); + +} \ No newline at end of file diff --git a/src/main/java/toxi/geom/PolygonTesselator.java b/src/main/java/toxi/geom/PolygonTesselator.java new file mode 100644 index 0000000..299c894 --- /dev/null +++ b/src/main/java/toxi/geom/PolygonTesselator.java @@ -0,0 +1,16 @@ +package toxi.geom; + +import java.util.List; + +public interface PolygonTesselator { + + /** + * Tesselates the given polygon into a set of triangles. + * + * @param poly + * polygon + * @return list of triangles + */ + public List<Triangle2D> tesselatePolygon(Polygon2D poly); + +} \ No newline at end of file diff --git a/src/main/java/com/syncleus/spangraph/geom/QuadtreeVisitor.java b/src/main/java/toxi/geom/QuadtreeVisitor.java similarity index 97% rename from src/main/java/com/syncleus/spangraph/geom/QuadtreeVisitor.java rename to src/main/java/toxi/geom/QuadtreeVisitor.java index 6b3b09c..98cde8c 100644 --- a/src/main/java/com/syncleus/spangraph/geom/QuadtreeVisitor.java +++ b/src/main/java/toxi/geom/QuadtreeVisitor.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * This interface is the core part of the visitor pattern application for diff --git a/src/main/java/toxi/geom/Quaternion.java b/src/main/java/toxi/geom/Quaternion.java new file mode 100644 index 0000000..b49511a --- /dev/null +++ b/src/main/java/toxi/geom/Quaternion.java @@ -0,0 +1,511 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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 javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +/** + * Quaternion implementation with SLERP based on http://is.gd/2n9s + */ +@XmlAccessorType(XmlAccessType.FIELD) +public class Quaternion { + + public static final float 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 Quaternion createFromAxisAngle(roVec3D axis, float angle) { + angle *= 0.5; + float sin = MathUtils.sin(angle); + float cos = MathUtils.cos(angle); + Quaternion q = new Quaternion(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 Quaternion createFromEuler(float pitch, float yaw, float roll) { + pitch *= 0.5; + yaw *= 0.5; + roll *= 0.5; + float sinPitch = MathUtils.sin(pitch); + float cosPitch = MathUtils.cos(pitch); + float sinYaw = MathUtils.sin(yaw); + float cosYaw = MathUtils.cos(yaw); + float sinRoll = MathUtils.sin(roll); + float cosRoll = MathUtils.cos(roll); + float cosPitchCosYaw = cosPitch * cosYaw; + float sinPitchSinYaw = sinPitch * sinYaw; + + Quaternion q = new Quaternion(); + + 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 Quaternion 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 Quaternion((float) q[3], (float) q[0], (float) q[1], + (float) 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 Quaternion getAlignmentQuat(roVec3D dir, + roVec3D forward) { + Vec3D target = dir.getNormalized(); + roVec3D axis = forward.cross(target); + float length = axis.magnitude() + 0.0001f; + float angle = (float) Math.atan2(length, forward.dot(target)); + return createFromAxisAngle(axis, angle); + } + + @XmlAttribute(required = true) + public float x, y, z, w; + + public Quaternion() { + identity(); + } + + public Quaternion(float w, float x, float y, float z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + } + + public Quaternion(float w, roVec3D v) { + this.x = v.x(); + this.y = v.y(); + this.z = v.z(); + this.w = w; + } + + public Quaternion(Quaternion q) { + this.w = q.w; + this.x = q.x; + this.y = q.y; + this.z = q.z; + } + + public Quaternion add(Quaternion q) { + return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w); + } + + public Quaternion addSelf(Quaternion q) { + x += q.x; + y += q.y; + z += q.z; + w += q.w; + return this; + } + + public XYZ applyTo(Vec3D v) { + float ix = w * v.x + y * v.z - z * v.y; + float iy = w * v.y + z * v.x - x * v.z; + float iz = w * v.z + x * v.y - y * v.x; + float iw = -x * v.x - y * v.y - z * v.z; + float xx = ix * w - iw * x - iy * z + iz * y; + float yy = iy * w - iw * y - iz * x + ix * z; + float zz = iz * w - iw * z - ix * y + iy * x; + v.set(xx, yy, zz); + return v; + } + + public Quaternion copy() { + return new Quaternion(w, x, y, z); + } + + /** + * Computes the dot product with the given quaternion. + * + * @param q + * @return dot product + */ + public float dot(Quaternion 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 Quaternion getConjugate() { + Quaternion q = new Quaternion(); + 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 Quaternion getNormalized() { + return new Quaternion(this).normalize(); + } + + public Quaternion identity() { + w = 1.0f; + x = 0.0f; + y = 0.0f; + z = 0.0f; + return this; + } + + /** + * Spherical interpolation to target quaternion (code ported from <a href= + * "http://www.gamasutra.com/view/feature/3278/rotating_objects_using_quaternions.php" + * >GamaSutra</a>) + * + * @param target + * quaternion + * @param t + * interpolation factor (0..1) + * @return new interpolated quat + */ + public Quaternion interpolateTo(Quaternion target, float t) { + return copy().interpolateToSelf(target, t); + } + + /** + * @param target + * @param t + * @param is + * @return interpolated quaternion as new instance + */ + public Quaternion interpolateTo(Quaternion target, float t, + InterpolateStrategy is) { + return copy().interpolateToSelf(target, is.interpolate(0, 1, t)); + } + + /** + * Spherical interpolation to target quaternion (code ported from <a href= + * "http://www.gamasutra.com/view/feature/3278/rotating_objects_using_quaternions.php" + * >GamaSutra</a>) + * + * @param target + * quaternion + * @param t + * interpolation factor (0..1) + * @return new interpolated quat + */ + public Quaternion interpolateToSelf(Quaternion target, double t) { + double scale; + double invscale; + float 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 = (float) (scale * w - invscale * target.w); + x = (float) (scale * x - invscale * target.x); + y = (float) (scale * y - invscale * target.y); + z = (float) (scale * z - invscale * target.z); + } else { + w = (float) (scale * w + invscale * target.w); + x = (float) (scale * x + invscale * target.x); + y = (float) (scale * y + invscale * target.y); + z = (float) (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 Quaternion interpolateToSelf(Quaternion target, float t, + InterpolateStrategy is) { + return interpolateToSelf(target, is.interpolate(0, 1, t)); + } + + public float magnitude() { + return (float) Math.sqrt(x * x + y * y + z * z + w * w); + } + + public Quaternion multiply(Quaternion q2) { + Quaternion res = new Quaternion(); + 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 Quaternion 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 Quaternion scale(float t) { + return new Quaternion(x * t, y * t, z * t, w * t); + } + + public Quaternion scaleSelf(float t) { + x *= t; + y *= t; + z *= t; + w *= t; + return this; + } + + public Quaternion set(float w, float x, float y, float z) { + this.w = w; + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public Quaternion set(float w, Vec3D v) { + this.w = w; + x = v.x; + y = v.y; + z = v.z; + return this; + } + + public Quaternion set(Quaternion q) { + w = q.w; + x = q.x; + y = q.y; + z = q.z; + return this; + } + + public Quaternion sub(Quaternion q) { + return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w); + } + + public Quaternion subSelf(Quaternion q) { + x -= q.x; + y -= q.y; + z -= q.z; + w -= q.w; + return this; + } + + public float[] toArray() { + return new float[] { + w, x, y, z + }; + } + + /** + * Converts the quaternion into a float array consisting of: rotation angle + * in radians, rotation axis x,y,z + * + * @return 4-element float array + */ + public float[] toAxisAngle() { + float[] res = new float[4]; + float sa = (float) Math.sqrt(1.0f - w * w); + if (sa < MathUtils.EPS) { + sa = 1.0f; + } else { + sa = 1.0f / sa; + } + res[0] = (float) 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 | + + float x2 = x + x; + float y2 = y + y; + float z2 = z + z; + float xx = x * x2; + float xy = x * y2; + float xz = x * z2; + float yy = y * y2; + float yz = y * z2; + float zz = z * z2; + float wx = w * x2; + float wy = w * y2; + float 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/main/java/com/syncleus/spangraph/geom/Ray2D.java b/src/main/java/toxi/geom/Ray2D.java similarity index 98% rename from src/main/java/com/syncleus/spangraph/geom/Ray2D.java rename to src/main/java/toxi/geom/Ray2D.java index 1e430a1..1902a53 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Ray2D.java +++ b/src/main/java/toxi/geom/Ray2D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; diff --git a/src/main/java/com/syncleus/spangraph/geom/Ray3D.java b/src/main/java/toxi/geom/Ray3D.java similarity index 92% rename from src/main/java/com/syncleus/spangraph/geom/Ray3D.java rename to src/main/java/toxi/geom/Ray3D.java index 3fa0d99..fadef96 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Ray3D.java +++ b/src/main/java/toxi/geom/Ray3D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -45,12 +45,12 @@ public class Ray3D extends Vec3D { dir = Vec3D.Y_AXIS.copy(); } - public Ray3D(float x, float y, float z, ReadonlyVec3D d) { + public Ray3D(float x, float y, float z, XYZ d) { super(x, y, z); dir = d.getNormalized(); } - public Ray3D(ReadonlyVec3D o, ReadonlyVec3D d) { + public Ray3D(XYZ o, XYZ d) { this(o.x(), o.y(), o.z(), d); } @@ -93,12 +93,12 @@ public class Ray3D extends Vec3D { * new direction * @return itself */ - public Ray3D setDirection(ReadonlyVec3D d) { + public Ray3D setDirection(roVec3D d) { dir.set(d).normalize(); return this; } - public Ray3D setNormalizedDirection(ReadonlyVec3D d) { + public Ray3D setNormalizedDirection(roVec3D d) { dir.set(d); return this; } diff --git a/src/main/java/com/syncleus/spangraph/geom/Ray3DIntersector.java b/src/main/java/toxi/geom/Ray3DIntersector.java similarity index 98% rename from src/main/java/com/syncleus/spangraph/geom/Ray3DIntersector.java rename to src/main/java/toxi/geom/Ray3DIntersector.java index dcf1208..3b72cc5 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Ray3DIntersector.java +++ b/src/main/java/toxi/geom/Ray3DIntersector.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import toxi.math.MathUtils; diff --git a/src/main/java/com/syncleus/spangraph/geom/ReadonlyVec2D.java b/src/main/java/toxi/geom/ReadonlyVec2D.java similarity index 94% rename from src/main/java/com/syncleus/spangraph/geom/ReadonlyVec2D.java rename to src/main/java/toxi/geom/ReadonlyVec2D.java index 9684d10..26d1375 100644 --- a/src/main/java/com/syncleus/spangraph/geom/ReadonlyVec2D.java +++ b/src/main/java/toxi/geom/ReadonlyVec2D.java @@ -25,7 +25,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; + +import toxi.geom.Vec2D.Axis; +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; /** * Readonly, immutable interface wrapper for Vec2D instances. Used throughout @@ -218,14 +223,14 @@ public interface ReadonlyVec2D { */ public Vec2D getLimited(float lim); -// /** -// * Produces a new vector with its coordinates passed through the given -// * {@link ScaleMap}. -// * -// * @param map -// * @return mapped vector -// */ -// public Vec2D getMapped(ScaleMap map); + /** + * Produces a new vector with its coordinates passed through the given + * {@link ScaleMap}. + * + * @param map + * @return mapped vector + */ + public Vec2D getMapped(ScaleMap map); /** * Produces the normalized version as a new vector @@ -305,19 +310,19 @@ public interface ReadonlyVec2D { */ public Vec2D interpolateTo(ReadonlyVec2D v, float 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 Vec2D interpolateTo(ReadonlyVec2D v, float f, InterpolateStrategy s); + /** + * 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 Vec2D interpolateTo(ReadonlyVec2D v, float f, InterpolateStrategy s); /** * Checks if the point is inside the given sphere. @@ -495,7 +500,7 @@ public interface ReadonlyVec2D { * * @return 3D vector */ - public Vec3D to3DYZ(); + public XYZ to3DYZ(); /* * (non-Javadoc) diff --git a/src/main/java/com/syncleus/spangraph/geom/ReadonlyVec4D.java b/src/main/java/toxi/geom/ReadonlyVec4D.java similarity index 98% rename from src/main/java/com/syncleus/spangraph/geom/ReadonlyVec4D.java rename to src/main/java/toxi/geom/ReadonlyVec4D.java index dd9ad4a..cdc7aea 100644 --- a/src/main/java/com/syncleus/spangraph/geom/ReadonlyVec4D.java +++ b/src/main/java/toxi/geom/ReadonlyVec4D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import toxi.math.InterpolateStrategy; import toxi.math.MathUtils; @@ -207,7 +207,7 @@ public interface ReadonlyVec4D { * * @return new result vector */ - public Vec4D getRotatedAroundAxis(ReadonlyVec3D axis, float theta); + public Vec4D getRotatedAroundAxis(roVec3D axis, float theta); /** * Creates a new vector rotated by the given angle around the X axis. @@ -368,7 +368,7 @@ public interface ReadonlyVec4D { public float w(); - public Vec3D weightInto(Vec3D out); + public XYZ weightInto(Vec3D out); public float x(); diff --git a/src/main/java/com/syncleus/spangraph/geom/Rect.java b/src/main/java/toxi/geom/Rect.java similarity index 99% rename from src/main/java/com/syncleus/spangraph/geom/Rect.java rename to src/main/java/toxi/geom/Rect.java index af5263a..64e229e 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Rect.java +++ b/src/main/java/toxi/geom/Rect.java @@ -25,14 +25,16 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; -import javax.xml.bind.annotation.XmlAccessType; +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 java.util.ArrayList; -import java.util.List; + +import toxi.math.MathUtils; @XmlAccessorType(XmlAccessType.FIELD) public class Rect implements Shape2D { diff --git a/src/main/java/toxi/geom/Reflector3D.java b/src/main/java/toxi/geom/Reflector3D.java new file mode 100644 index 0000000..c7532d8 --- /dev/null +++ b/src/main/java/toxi/geom/Reflector3D.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 Reflector3D extends Intersector3D { + + /** + * 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 roVec3D getReflectedRayPointAtDistance(float dist); + + /** + * @return angle between incident ray and surface normal + */ + public float getReflectionAngle(); + + /** + * Reflects given ray on the entity's surface + * + * @param ray + * incident ray + * @return reflected ray starting from intersection point + */ + public Ray3D reflectRay(Ray3D ray); +} \ No newline at end of file diff --git a/src/main/java/com/syncleus/spangraph/geom/Shape2D.java b/src/main/java/toxi/geom/Shape2D.java similarity index 98% rename from src/main/java/com/syncleus/spangraph/geom/Shape2D.java rename to src/main/java/toxi/geom/Shape2D.java index e304ead..869b548 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Shape2D.java +++ b/src/main/java/toxi/geom/Shape2D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import java.util.List; diff --git a/src/main/java/com/syncleus/spangraph/geom/Shape3D.java b/src/main/java/toxi/geom/Shape3D.java similarity index 94% rename from src/main/java/com/syncleus/spangraph/geom/Shape3D.java rename to src/main/java/toxi/geom/Shape3D.java index b2b5ce7..f044756 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Shape3D.java +++ b/src/main/java/toxi/geom/Shape3D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * Interface description of common operations supported by 3D geometry types. @@ -37,5 +37,5 @@ public interface Shape3D { * * @return true, if inside */ - boolean containsPoint(ReadonlyVec3D p); + boolean containsPoint(XYZ p); } diff --git a/src/main/java/toxi/geom/SingularMatrixException.java b/src/main/java/toxi/geom/SingularMatrixException.java new file mode 100644 index 0000000..1aa8ad9 --- /dev/null +++ b/src/main/java/toxi/geom/SingularMatrixException.java @@ -0,0 +1,57 @@ +/* + * $RCSfile$ + * + * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + * + * $Revision: 127 $ + * $Date: 2008-02-28 20:18:51 +0000 (Thu, 28 Feb 2008) $ + * $State$ + */ + +package toxi.geom; + +/** + * Indicates that inverse of a matrix can not be computed. + */ +public class SingularMatrixException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Create the exception object with default values. + */ + public SingularMatrixException() { + } + + /** + * Create the exception object that outputs message. + * + * @param str + * the message string to be output. + */ + public SingularMatrixException(String str) { + super(str); + } + +} diff --git a/src/main/java/toxi/geom/SpatialBins.java b/src/main/java/toxi/geom/SpatialBins.java new file mode 100644 index 0000000..b4bcfe9 --- /dev/null +++ b/src/main/java/toxi/geom/SpatialBins.java @@ -0,0 +1,123 @@ +package toxi.geom; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import toxi.math.MathUtils; + +public class SpatialBins<T> implements SpatialIndex<T> { + + private final float invBinWidth; + private final float minOffset; + private final int numBins; + private int numItems; + + private final List<HashSet<T>> bins; + private final CoordinateExtractor<T> extractor; + + public SpatialBins(float min, float max, int numBins, + CoordinateExtractor<T> extractor) { + this.extractor = extractor; + this.bins = new ArrayList<HashSet<T>>(); + for (int i = 0; i < numBins; i++) { + bins.add(new HashSet<T>()); + } + this.minOffset = min; + this.numBins = numBins; + this.invBinWidth = numBins / (max - min); + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.SpatialIndex#clear() + */ + public void clear() { + for (HashSet<T> bin : bins) { + bin.clear(); + } + numItems = 0; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.SpatialIndex#index(T) + */ + public boolean index(T p) { + int id = (int) MathUtils.clip((extractor.coordinate(p) - minOffset) + * invBinWidth, 0, numBins - 1); + if (bins.get(id).add(p)) { + numItems++; + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.SpatialIndex#isIndexed(T) + */ + public boolean isIndexed(T item) { + // TODO Auto-generated method stub + return false; + } + + public List<T> itemsWithinRadius(T p, float radius, List<T> 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<T>(); + } + results.addAll(bins.get(i)); + } + return results; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.SpatialIndex#reindex(float, T) + */ + public boolean reindex(T p, T q) { + int id1 = (int) MathUtils.clip((extractor.coordinate(p) - minOffset) + * invBinWidth, 0, numBins); + int id2 = (int) MathUtils.clip((extractor.coordinate(q) - minOffset) + * invBinWidth, 0, numBins); + if (id1 != id2) { + if (bins.get(id1).remove(p)) { + numItems--; + } + if (bins.get(id2).add(q)) { + numItems++; + } + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.SpatialIndex#size() + */ + public int size() { + return numItems; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.SpatialIndex#unindex(T) + */ + public boolean unindex(T p) { + int id = (int) MathUtils.clip((extractor.coordinate(p) - minOffset) + * invBinWidth, 0, numBins); + return bins.get(id).remove(p); + } +} diff --git a/src/main/java/com/syncleus/spangraph/geom/SpatialIndex.java b/src/main/java/toxi/geom/SpatialIndex.java similarity index 89% rename from src/main/java/com/syncleus/spangraph/geom/SpatialIndex.java rename to src/main/java/toxi/geom/SpatialIndex.java index f0a30db..36dbdb7 100644 --- a/src/main/java/com/syncleus/spangraph/geom/SpatialIndex.java +++ b/src/main/java/toxi/geom/SpatialIndex.java @@ -1,4 +1,4 @@ -package com.syncleus.spangraph.geom; +package toxi.geom; import java.util.List; diff --git a/src/main/java/com/syncleus/spangraph/geom/Sphere.java b/src/main/java/toxi/geom/Sphere.java similarity index 90% rename from src/main/java/com/syncleus/spangraph/geom/Sphere.java rename to src/main/java/toxi/geom/Sphere.java index 2aa2ae0..3e7d658 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Sphere.java +++ b/src/main/java/toxi/geom/Sphere.java @@ -25,16 +25,14 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; - - +package toxi.geom; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; @XmlAccessorType(XmlAccessType.FIELD) -public class Sphere extends Vec3D { +public class Sphere extends Vec3D implements Shape3D { /** * Earth's mean radius in km @@ -53,7 +51,7 @@ public class Sphere extends Vec3D { this(new Vec3D(), radius); } - public Sphere(ReadonlyVec3D v, float r) { + public Sphere(roVec3D v, float r) { super(v); radius = r; } @@ -62,9 +60,22 @@ public class Sphere extends Vec3D { this(s, s.radius); } - public boolean containsPoint(ReadonlyVec3D p) { - float d = this.sub(p).magSquared(); - return (d <= radius * radius); + public boolean containsPoint(final XYZ p) { + float d = 0; + float rsquare = radius*radius; + + float dx = x() - p.x(); dx*=dx; + if (dx > rsquare) return false; + + float dy = y() - p.y(); dy*=dy; + d = dx + dy; + if (d > rsquare) return false; + + float dz = z() - p.z(); dz*=dz; + d += dz; + if (d > rsquare) return false; + + return true; } /** @@ -84,7 +95,7 @@ public class Sphere extends Vec3D { */ public float[] intersectRay(Ray3D ray) { float[] result = null; - ReadonlyVec3D q = ray.sub(this); + roVec3D q = ray.sub(this); float distSquared = q.magSquared(); float v = -q.dot(ray.getDirection()); float d = radius * radius - (distSquared - v * v); @@ -132,7 +143,7 @@ public class Sphere extends Vec3D { // Sphere and triangle intersect if the (squared) distance from sphere // center to Vec3D p is less than the (squared) sphere radius - ReadonlyVec3D v = result.sub(this); + roVec3D v = result.sub(this); return v.magSquared() <= radius * radius; } @@ -171,7 +182,7 @@ public class Sphere extends Vec3D { * @return a unit normal vector to the tangent plane of the ellipsoid in the * point. */ - public Vec3D tangentPlaneNormalAt(ReadonlyVec3D q) { + public Vec3D tangentPlaneNormalAt(XYZ q) { return q.sub(this).normalize(); } diff --git a/src/main/java/toxi/geom/SphereIntersectorReflector.java b/src/main/java/toxi/geom/SphereIntersectorReflector.java new file mode 100644 index 0000000..caad360 --- /dev/null +++ b/src/main/java/toxi/geom/SphereIntersectorReflector.java @@ -0,0 +1,158 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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 SphereIntersectorReflector implements Intersector3D, Reflector3D { + + protected Sphere sphere; + protected IsectData3D isectData; + + protected XYZ reflectedDir, reflectedPos; + protected float reflectTheta; + + public SphereIntersectorReflector(Sphere s) { + sphere = s; + isectData = new IsectData3D(); + } + + public SphereIntersectorReflector(Vec3D o, float r) { + this(new Sphere(o, r)); + } + + public IsectData3D getIntersectionData() { + return isectData; + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.Reflector3D#getReflectedRayPointAtDistance(float) + */ + public roVec3D getReflectedRayPointAtDistance(float dist) { + if (reflectedDir != null) { + return isectData.pos.add(reflectedDir.scale(dist)); + } else { + return null; + } + } + + /* + * (non-Javadoc) + * + * @see toxi.geom.Reflector3D#getReflectionAngle() + */ + public float getReflectionAngle() { + return reflectTheta; + } + + /** + * @return the sphere + */ + public Sphere getSphere() { + 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 float intersectRayDistance(Ray3D ray) { + roVec3D q = sphere.sub(ray); + float distSquared = q.magSquared(); + float v = q.dot(ray.dir); + float 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 - (float) Math.sqrt(d); + } + + public boolean intersectsRay(Ray3D ray) { + isectData.dist = intersectRayDistance(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.Vec3D, toxi.geom.Vec3D) + */ + public Ray3D reflectRay(Ray3D ray) { + if (intersectsRay(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 + Vec3D 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 Ray3D(isectData.pos, reflectedDir); + } else { + return null; + } + } + + /** + * @param sphere + * the sphere to set + */ + public void setSphere(Sphere sphere) { + this.sphere = sphere; + } +} diff --git a/src/main/java/com/syncleus/spangraph/geom/Spline2D.java b/src/main/java/toxi/geom/Spline2D.java similarity index 99% rename from src/main/java/com/syncleus/spangraph/geom/Spline2D.java rename to src/main/java/toxi/geom/Spline2D.java index 9417348..dbbd978 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Spline2D.java +++ b/src/main/java/toxi/geom/Spline2D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/com/syncleus/spangraph/geom/Spline3D.java b/src/main/java/toxi/geom/Spline3D.java similarity index 82% rename from src/main/java/com/syncleus/spangraph/geom/Spline3D.java rename to src/main/java/toxi/geom/Spline3D.java index 98e61af..0c2b298 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Spline3D.java +++ b/src/main/java/toxi/geom/Spline3D.java @@ -25,18 +25,13 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; +import javax.xml.bind.annotation.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlTransient; - /** * <p> * This is a generic 3D B-Spline class for curves of arbitrary length, control @@ -68,10 +63,10 @@ public class Spline3D { public static final float DEFAULT_TIGHTNESS = 0.25f; @XmlTransient - protected Vec3D[] points; + protected XYZ[] points; @XmlElement(name = "p") - public List<Vec3D> pointList = new ArrayList<Vec3D>(); + public List<XYZ> pointList = new ArrayList(); @XmlTransient public BernsteinPolynomial bernstein; @@ -93,7 +88,7 @@ public class Spline3D { /** * Constructs an empty spline container with default curve tightness. You - * need to populate the spline manually by using {@link #add(ReadonlyVec3D)} + * need to populate the spline manually by using {@link #add(roVec3D)} * . */ public Spline3D() { @@ -104,7 +99,7 @@ public class Spline3D { * @param rawPoints * list of control point vectors */ - public Spline3D(List<Vec3D> rawPoints) { + public Spline3D(List<XYZ> rawPoints) { this(rawPoints, null, DEFAULT_TIGHTNESS); } @@ -117,7 +112,7 @@ public class Spline3D { * default curve tightness used for the interpolated vertices * {@linkplain #setTightness(float)} */ - public Spline3D(List<Vec3D> rawPoints, BernsteinPolynomial b, + public Spline3D(List<XYZ> rawPoints, BernsteinPolynomial b, float tightness) { pointList.addAll(rawPoints); bernstein = b; @@ -128,7 +123,7 @@ public class Spline3D { * @param pointArray * array of control point vectors */ - public Spline3D(Vec3D[] pointArray) { + public Spline3D(XYZ[] pointArray) { this(pointArray, null, DEFAULT_TIGHTNESS); } @@ -141,7 +136,7 @@ public class Spline3D { * default curve tightness used for the interpolated vertices * {@linkplain #setTightness(float)} */ - public Spline3D(Vec3D[] pointArray, BernsteinPolynomial b, float tightness) { + public Spline3D(XYZ[] pointArray, BernsteinPolynomial b, float tightness) { this(Arrays.asList(pointArray), b, tightness); } @@ -155,25 +150,25 @@ public class Spline3D { * @param p * @return itself */ - public Spline3D add(ReadonlyVec3D p) { + public Spline3D add(roVec3D p) { pointList.add(p.copy()); return this; } protected void findCPoints() { bi[1] = -tightness; - coeffA[1].set((points[2].x - points[0].x - delta[0].x) * tightness, - (points[2].y - points[0].y - delta[0].y) * tightness, - (points[2].z - points[0].z - delta[0].z) * tightness); + coeffA[1].set((points[2].x() - points[0].x() - delta[0].x) * tightness, + (points[2].y() - points[0].y() - delta[0].y) * tightness, + (points[2].z() - points[0].z() - delta[0].z) * tightness); final int numP = getNumPoints(); for (int i = 2; i < numP - 1; i++) { bi[i] = -1 / (invTightness + bi[i - 1]); coeffA[i].set( - -(points[i + 1].x - points[i - 1].x - coeffA[i - 1].x) + -(points[i + 1].x() - points[i - 1].x() - coeffA[i - 1].x) * bi[i], - -(points[i + 1].y - points[i - 1].y - coeffA[i - 1].y) + -(points[i + 1].y() - points[i - 1].y() - coeffA[i - 1].y) * bi[i], - -(points[i + 1].z - points[i - 1].z - coeffA[i - 1].z) + -(points[i + 1].z() - points[i - 1].z() - coeffA[i - 1].z) * bi[i]); } for (int i = numP - 2; i > 0; i--) { @@ -195,7 +190,7 @@ public class Spline3D { /** * @return the pointList */ - public List<Vec3D> getPointList() { + public List<XYZ> getPointList() { return pointList; } @@ -215,9 +210,9 @@ public class Spline3D { * the pointList to set * @return itself */ - public Spline3D setPointList(List<Vec3D> plist) { + public Spline3D setPointList(List<XYZ> plist) { pointList.clear(); - for (ReadonlyVec3D p : plist) { + for (XYZ p : plist) { pointList.add(p.copy()); } return this; @@ -276,17 +271,17 @@ public class Spline3D { Vec3D deltaQ = new Vec3D(); res--; for (int i = 0, numP = getNumPoints(); i < numP - 1; i++) { - Vec3D p = points[i]; - Vec3D q = points[i + 1]; + XYZ p = points[i]; + XYZ q = points[i + 1]; deltaP.set(delta[i]).addSelf(p); deltaQ.set(q).subSelf(delta[i + 1]); for (int k = 0; k < res; k++) { - float x = p.x * bernstein.b0[k] + deltaP.x * bernstein.b1[k] - + deltaQ.x * bernstein.b2[k] + q.x * bernstein.b3[k]; - float y = p.y * bernstein.b0[k] + deltaP.y * bernstein.b1[k] - + deltaQ.y * bernstein.b2[k] + q.y * bernstein.b3[k]; - float z = p.z * bernstein.b0[k] + deltaP.z * bernstein.b1[k] - + deltaQ.z * bernstein.b2[k] + q.z * bernstein.b3[k]; + float x = p.x() * bernstein.b0[k] + deltaP.x * bernstein.b1[k] + + deltaQ.x * bernstein.b2[k] + q.x() * bernstein.b3[k]; + float y = p.y() * bernstein.b0[k] + deltaP.y * bernstein.b1[k] + + deltaQ.y * bernstein.b2[k] + q.y() * bernstein.b3[k]; + float z = p.z() * bernstein.b0[k] + deltaP.z * bernstein.b1[k] + + deltaQ.z * bernstein.b2[k] + q.z() * bernstein.b3[k]; strip.add(new Vec3D(x, y, z)); } } @@ -306,6 +301,6 @@ public class Spline3D { } } setTightness(tightness); - points = pointList.toArray(new Vec3D[numP]); + points = pointList.toArray(new XYZ[numP]); } } \ No newline at end of file diff --git a/src/main/java/toxi/geom/SutherlandHodgemanClipper.java b/src/main/java/toxi/geom/SutherlandHodgemanClipper.java new file mode 100644 index 0000000..03eadc3 --- /dev/null +++ b/src/main/java/toxi/geom/SutherlandHodgemanClipper.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 SutherlandHodgemanClipper implements PolygonClipper2D { + + protected Rect bounds; + + public SutherlandHodgemanClipper(Rect bounds) { + this.bounds = bounds; + } + + public Polygon2D clipPolygon(Polygon2D poly) { + List<Vec2D> points = new ArrayList<Vec2D>(poly.vertices); + List<Vec2D> clipped = new ArrayList<Vec2D>(); + 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++) { + Vec2D p = points.get(i); + Vec2D 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<Vec2D> t = points; + points = clipped; + clipped = t; + } + return new Polygon2D(points).removeDuplicates(0.001f); + } + + /** + * @return the bounding rect + */ + public Rect getBounds() { + return bounds; + } + + private final Vec2D getClippedPosOnEdge(int edgeID, Vec2D p1, Vec2D p2) { + switch (edgeID) { + case 0: + return new Vec2D(p1.x + ((bounds.y - p1.y) * (p2.x - p1.x)) + / (p2.y - p1.y), bounds.y); + case 2: + float by = bounds.y + bounds.height; + return new Vec2D(p1.x + ((by - p1.y) * (p2.x - p1.x)) + / (p2.y - p1.y), by); + case 1: + float bx = bounds.x + bounds.width; + return new Vec2D(bx, p1.y + ((bx - p1.x) * (p2.y - p1.y)) + / (p2.x - p1.x)); + + case 3: + return new Vec2D(bounds.x, p1.y + + ((bounds.x - p1.x) * (p2.y - p1.y)) / (p2.x - p1.x)); + default: + return null; + } + } + + private final boolean isInsideEdge(Vec2D 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<Vec2D> list, Vec2D q) { + for (Vec2D p : list) { + if (p.equalsWithTolerance(q, 0.0001f)) { + return true; + } + } + return false; + } + + /** + * @param bounds + * the bounding rect to set + */ + public void setBounds(Rect bounds) { + this.bounds = bounds; + } +} diff --git a/src/main/java/com/syncleus/spangraph/geom/Triangle2D.java b/src/main/java/toxi/geom/Triangle2D.java similarity index 93% rename from src/main/java/com/syncleus/spangraph/geom/Triangle2D.java rename to src/main/java/toxi/geom/Triangle2D.java index a0e6682..7cb17ba 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Triangle2D.java +++ b/src/main/java/toxi/geom/Triangle2D.java @@ -25,14 +25,21 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; - -import javax.xml.bind.annotation.XmlTransient; 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.Line2D.LineIntersection.Type; +import toxi.math.MathUtils; + +@XmlAccessorType(XmlAccessType.FIELD) public class Triangle2D implements Shape2D { public static Triangle2D createEquilateralFrom(ReadonlyVec2D a, @@ -49,6 +56,7 @@ public class Triangle2D implements Shape2D { return (determ > 0.0); } + @XmlElement(required = true) public Vec2D a, b, c; @XmlTransient @@ -120,7 +128,7 @@ public class Triangle2D implements Shape2D { return this; } - public Vec2D fromBarycentric(ReadonlyVec3D p) { + public Vec2D fromBarycentric(roVec3D p) { return new Vec2D(a.x * p.x() + b.x * p.y() + c.x * p.z(), a.y * p.x() + b.y * p.y() + c.y * p.z()); } @@ -246,8 +254,8 @@ public class Triangle2D implements Shape2D { }; for (Line2D la : ea) { for (Line2D lb : eb) { - Line2D.LineIntersection.Type type = la.intersectLine(lb).getType(); - if (type != Line2D.LineIntersection.Type.NON_INTERSECTING && type != Line2D.LineIntersection.Type.PARALLEL) { + Type type = la.intersectLine(lb).getType(); + if (type != Type.NON_INTERSECTING && type != Type.PARALLEL) { return true; } } @@ -268,14 +276,14 @@ public class Triangle2D implements Shape2D { /** * 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(ReadonlyVec3D)} + * a different triangle using its {@link #fromBarycentric(roVec3D)} * method. * * @param p * point in world space * @return barycentric coords as {@link Vec3D} */ - public Vec3D toBarycentric(ReadonlyVec2D p) { + public XYZ toBarycentric(ReadonlyVec2D p) { return new Triangle3D(a.to3DXY(), b.to3DXY(), c.to3DXY()) .toBarycentric(p.to3DXY()); } diff --git a/src/main/java/com/syncleus/spangraph/geom/Triangle3D.java b/src/main/java/toxi/geom/Triangle3D.java similarity index 93% rename from src/main/java/com/syncleus/spangraph/geom/Triangle3D.java rename to src/main/java/toxi/geom/Triangle3D.java index b50475d..44c787b 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Triangle3D.java +++ b/src/main/java/toxi/geom/Triangle3D.java @@ -25,15 +25,15 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; + +import toxi.math.MathUtils; 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.math.MathUtils; - @XmlAccessorType(XmlAccessType.FIELD) public class Triangle3D implements Shape3D { @@ -92,9 +92,9 @@ public class Triangle3D implements Shape3D { Vec3D ac = c.sub(a); Vec3D bc = c.sub(b); - ReadonlyVec3D pa = p.sub(a); - ReadonlyVec3D pb = p.sub(b); - ReadonlyVec3D pc = p.sub(c); + roVec3D pa = p.sub(a); + roVec3D pb = p.sub(b); + roVec3D pc = p.sub(c); Vec3D ap = a.sub(p); Vec3D bp = b.sub(p); @@ -128,7 +128,7 @@ public class Triangle3D implements Shape3D { } // P is outside (or on) AB if the triple scalar product [N PA PB] <= 0 - ReadonlyVec3D n = ab.cross(ac); + roVec3D n = ab.cross(ac); float vc = n.dot(ap.crossSelf(bp)); // If P outside AB and within feature region of AB, @@ -183,7 +183,7 @@ public class Triangle3D implements Shape3D { * * @return true, if point is in triangle. */ - public boolean containsPoint(ReadonlyVec3D p) { + public boolean containsPoint(XYZ p) { Vec3D v0 = c.sub(a); Vec3D v1 = b.sub(a); Vec3D v2 = p.sub(a); @@ -211,7 +211,7 @@ public class Triangle3D implements Shape3D { return this; } - public Vec3D fromBarycentric(ReadonlyVec3D p) { + public XYZ fromBarycentric(roVec3D p) { return new Vec3D(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()); @@ -232,7 +232,7 @@ public class Triangle3D implements Shape3D { * @return closest point */ - public Vec3D getClosestPointTo(ReadonlyVec3D p) { + public XYZ getClosestPointTo(roVec3D p) { Line3D edge = new Line3D(a, b); final Vec3D Rab = edge.closestPointTo(p); final Vec3D Rbc = edge.set(b, c).closestPointTo(p); @@ -243,7 +243,7 @@ public class Triangle3D implements Shape3D { final float dCA = p.sub(Rca).magSquared(); float min = dAB; - Vec3D result = Rab; + XYZ result = Rab; if (dBC < min) { min = dBC; @@ -256,13 +256,13 @@ public class Triangle3D implements Shape3D { return result; } - public Vec3D[] getVertexArray() { + public XYZ[] getVertexArray() { return getVertexArray(null, 0); } - public Vec3D[] getVertexArray(Vec3D[] array, int offset) { + public XYZ[] getVertexArray(XYZ[] array, int offset) { if (array == null) { - array = new Vec3D[3]; + array = new XYZ[3]; } array[offset++] = a; array[offset++] = b; @@ -282,7 +282,7 @@ public class Triangle3D implements Shape3D { return Triangle3D.isClockwiseInXY(a, b, c); } - private boolean isSameClockDir(Vec3D a, Vec3D b, ReadonlyVec3D p, Vec3D norm) { + private boolean isSameClockDir(Vec3D a, Vec3D b, roVec3D p, Vec3D norm) { float bax = b.x - a.x; float bay = b.y - a.y; float baz = b.z - a.z; @@ -302,7 +302,7 @@ public class Triangle3D implements Shape3D { c = c2; } - public Vec3D toBarycentric(ReadonlyVec3D p) { + public XYZ toBarycentric(roVec3D p) { Vec3D e = b.sub(a).cross(c.sub(a)); Vec3D n = e.getNormalized(); diff --git a/src/main/java/com/syncleus/spangraph/geom/TriangleIntersector.java b/src/main/java/toxi/geom/TriangleIntersector.java similarity index 98% rename from src/main/java/com/syncleus/spangraph/geom/TriangleIntersector.java rename to src/main/java/toxi/geom/TriangleIntersector.java index 3a5c6be..fbd8b09 100644 --- a/src/main/java/com/syncleus/spangraph/geom/TriangleIntersector.java +++ b/src/main/java/toxi/geom/TriangleIntersector.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import toxi.math.MathUtils; diff --git a/src/main/java/com/syncleus/spangraph/geom/Vec2D.java b/src/main/java/toxi/geom/Vec2D.java similarity index 94% rename from src/main/java/com/syncleus/spangraph/geom/Vec2D.java rename to src/main/java/toxi/geom/Vec2D.java index 0370221..469bbe7 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Vec2D.java +++ b/src/main/java/toxi/geom/Vec2D.java @@ -25,17 +25,37 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; -import javax.xml.bind.annotation.XmlAttribute; 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 Vec2D implements Comparable<ReadonlyVec2D>, ReadonlyVec2D { + public static enum Axis { + + X(Vec2D.X_AXIS), + Y(Vec2D.Y_AXIS); + + private final ReadonlyVec2D vector; + + private Axis(ReadonlyVec2D v) { + this.vector = v; + } + + public ReadonlyVec2D getVector() { + return vector; + } + } /** * Defines positive X axis @@ -490,11 +510,11 @@ public class Vec2D implements Comparable<ReadonlyVec2D>, ReadonlyVec2D { } return new Vec2D(this); } -// -// public Vec2D getMapped(ScaleMap map) { -// return new Vec2D((float) map.getClippedValueFor(x), -// (float) map.getClippedValueFor(y)); -// } + + public Vec2D getMapped(ScaleMap map) { + return new Vec2D((float) map.getClippedValueFor(x), + (float) map.getClippedValueFor(y)); + } public final Vec2D getNormalized() { return new Vec2D(this).normalize(); @@ -556,17 +576,17 @@ public class Vec2D implements Comparable<ReadonlyVec2D>, ReadonlyVec2D { return new Vec2D(x + (v.x() - x) * f, y + (v.y() - y) * f); } -// public Vec2D interpolateTo(ReadonlyVec2D v, float f, InterpolateStrategy s) { -// return new Vec2D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f)); -// } + public Vec2D interpolateTo(ReadonlyVec2D v, float f, InterpolateStrategy s) { + return new Vec2D(s.interpolate(x, v.x(), f), s.interpolate(y, v.y(), f)); + } public final Vec2D interpolateTo(Vec2D v, float f) { return new Vec2D(x + (v.x - x) * f, y + (v.y - y) * f); } -// public Vec2D interpolateTo(Vec2D v, float f, InterpolateStrategy s) { -// return new Vec2D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f)); -// } + public Vec2D interpolateTo(Vec2D v, float f, InterpolateStrategy s) { + return new Vec2D(s.interpolate(x, v.x, f), s.interpolate(y, v.y, f)); + } /** * Interpolates the vector towards the given target vector, using linear @@ -584,24 +604,24 @@ public class Vec2D implements Comparable<ReadonlyVec2D>, ReadonlyVec2D { 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 Vec2D interpolateToSelf(ReadonlyVec2D v, float f, -// InterpolateStrategy s) { -// x = s.interpolate(x, v.x(), f); -// y = s.interpolate(y, v.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 Vec2D interpolateToSelf(ReadonlyVec2D v, float 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 @@ -1051,7 +1071,7 @@ public class Vec2D implements Comparable<ReadonlyVec2D>, ReadonlyVec2D { return new Vec3D(x, 0, y); } - public final Vec3D to3DYZ() { + public final XYZ to3DYZ() { return new Vec3D(0, x, y); } diff --git a/src/main/java/com/syncleus/spangraph/geom/Vec3D.java b/src/main/java/toxi/geom/Vec3D.java similarity index 81% rename from src/main/java/com/syncleus/spangraph/geom/Vec3D.java rename to src/main/java/toxi/geom/Vec3D.java index bb4018a..6d650ef 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Vec3D.java +++ b/src/main/java/toxi/geom/Vec3D.java @@ -25,44 +25,67 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; -import java.util.Random; +import toxi.math.InterpolateStrategy; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; +import javax.xml.bind.annotation.XmlAttribute; +import java.util.Random; /** * Comprehensive 3D vector class with additional basic intersection and * collision detection features. */ -public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { +public class Vec3D implements Comparable<roVec3D>, roVec3D { + + + + public static enum Axis { + + X(Vec3D.X_AXIS), + Y(Vec3D.Y_AXIS), + Z(Vec3D.Z_AXIS); + + private final roVec3D vector; + + private Axis(roVec3D v) { + this.vector = v; + } + + public roVec3D getVector() { + return vector; + } + } /** Defines positive X axis. */ - public static final ReadonlyVec3D X_AXIS = new Vec3D(1, 0, 0); + public static final roVec3D X_AXIS = new Vec3D(1, 0, 0); /** Defines positive Y axis. */ - public static final ReadonlyVec3D Y_AXIS = new Vec3D(0, 1, 0); + public static final roVec3D Y_AXIS = new Vec3D(0, 1, 0); /** Defines positive Z axis. */ - public static final ReadonlyVec3D Z_AXIS = new Vec3D(0, 0, 1); + public static final roVec3D Z_AXIS = new Vec3D(0, 0, 1); /** Defines the zero vector. */ - public static final ReadonlyVec3D ZERO = new Vec3D();; + public static final roVec3D ZERO = new Vec3D();; /** * Defines vector with all coords set to Float.MIN_VALUE. Useful for * bounding box operations. */ - public static final ReadonlyVec3D MIN_VALUE = new Vec3D(Float.MIN_VALUE, + public static final roVec3D MIN_VALUE = new Vec3D(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE); /** * Defines vector with all coords set to Float.MAX_VALUE. Useful for * bounding box operations. */ - public static final ReadonlyVec3D MAX_VALUE = new Vec3D(Float.MAX_VALUE, + public static final roVec3D MAX_VALUE = new Vec3D(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); - public static final ReadonlyVec3D NEG_MAX_VALUE = new Vec3D( + public static final roVec3D NEG_MAX_VALUE = new Vec3D( -Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE); /** @@ -76,7 +99,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return new vector in the XY plane */ - public static final Vec3D fromXYTheta(float theta) { + public static final XYZ fromXYTheta(float theta) { return new Vec3D((float) Math.cos(theta), (float) Math.sin(theta), 0); } @@ -91,7 +114,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return new vector in the XZ plane */ - public static final Vec3D fromXZTheta(float theta) { + public static final XYZ fromXZTheta(float theta) { return new Vec3D((float) Math.cos(theta), 0, (float) Math.sin(theta)); } @@ -106,7 +129,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return new vector in the YZ plane */ - public static final Vec3D fromYZTheta(float theta) { + public static final XYZ fromYZTheta(float theta) { return new Vec3D(0, (float) Math.cos(theta), (float) Math.sin(theta)); } @@ -121,7 +144,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return result as new vector */ - public static final Vec3D max(ReadonlyVec3D a, ReadonlyVec3D b) { + public static final Vec3D max(roVec3D a, roVec3D b) { return new Vec3D(MathUtils.max(a.x(), b.x()), MathUtils.max(a.y(), b.y()), MathUtils.max(a.z(), b.z())); } @@ -137,7 +160,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return result as new vector */ - public static final Vec3D min(ReadonlyVec3D a, ReadonlyVec3D b) { + public static final Vec3D min(roVec3D a, roVec3D b) { return new Vec3D(MathUtils.min(a.x(), b.x()), MathUtils.min(a.y(), b.y()), MathUtils.min(a.z(), b.z())); } @@ -148,7 +171,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return a new random normalized unit vector. */ - public static final Vec3D randomVector() { + public static final XYZ randomVector() { return randomVector(MathUtils.RND); } @@ -163,19 +186,22 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return a new random normalized unit vector. */ - public static final Vec3D randomVector(Random rnd) { + public static final XYZ randomVector(Random rnd) { Vec3D v = new Vec3D(rnd.nextFloat() * 2 - 1, rnd.nextFloat() * 2 - 1, rnd.nextFloat() * 2 - 1); return v.normalize(); } /** X coordinate. */ + @XmlAttribute(required = true) public float x; /** Y coordinate. */ + @XmlAttribute(required = true) public float y; /** Z coordinate. */ + @XmlAttribute(required = true) public float z; /** @@ -208,11 +234,11 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { /** * Creates a new vector with the coordinates of the given vector. - * + * * @param v * vector to be copied */ - public Vec3D(ReadonlyVec3D v) { + public Vec3D(XYZ v) { this.x = v.x(); this.y = v.y(); this.z = v.z(); @@ -234,7 +260,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return new Vec3D(x + a, y + b, z + c); } - public Vec3D add(ReadonlyVec3D v) { + public Vec3D add(roVec3D v) { return new Vec3D(x + v.x(), y + v.y(), z + v.z()); } @@ -254,14 +280,14 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D addSelf(float a, float b, float c) { + public final XYZ addSelf(float a, float b, float c) { x += a; y += b; z += c; return this; } - public final Vec3D addSelf(ReadonlyVec3D v) { + public final Vec3D addSelf(XYZ v) { x += v.x(); y += v.y(); z += v.z(); @@ -283,11 +309,11 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public final float angleBetween(ReadonlyVec3D v) { + public final float angleBetween(XYZ v) { return (float) Math.acos(dot(v)); } - public final float angleBetween(ReadonlyVec3D v, boolean forceNormalize) { + public final float angleBetween(XYZ v, boolean forceNormalize) { float theta; if (forceNormalize) { theta = getNormalized().dot(v.getNormalized()); @@ -302,12 +328,12 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final ReadonlyVec3D clear() { + public roVec3D clear() { x = y = z = 0; return this; } - public int compareTo(ReadonlyVec3D v) { + public int compareTo(roVec3D v) { if (x == v.x() && y == v.y() && z == v.z()) { return 0; } @@ -327,7 +353,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public Vec3D constrain(AABB box) { + public XYZ constrain(AABB box) { return constrain(box.getMin(), box.getMax()); } @@ -339,7 +365,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * @param max * @return itself */ - public Vec3D constrain(Vec3D min, Vec3D max) { + public XYZ 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); @@ -350,7 +376,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return new Vec3D(this); } - public final Vec3D cross(ReadonlyVec3D v) { + public final Vec3D cross(XYZ v) { return new Vec3D(y * v.z() - v.y() * z, z * v.x() - v.z() * x, x * v.y() - v.x() * y); } @@ -360,7 +386,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * y); } - public final Vec3D crossInto(ReadonlyVec3D v, Vec3D result) { + public final Vec3D crossInto(XYZ v, Vec3D result) { final float vx = v.x(); final float vy = v.y(); final float vz = v.z(); @@ -389,29 +415,9 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public final float distanceTo(ReadonlyVec3D v) { - if (v != null) { - final float dx = x - v.x(); - final float dy = y - v.y(); - final float dz = z - v.z(); - return (float) Math.sqrt(dx * dx + dy * dy + dz * dz); - } else { - return Float.NaN; - } - } - public final float distanceToSquared(ReadonlyVec3D v) { - if (v != null) { - final float dx = x - v.x(); - final float dy = y - v.y(); - final float dz = z - v.z(); - return dx * dx + dy * dy + dz * dz; - } else { - return Float.NaN; - } - } - public final float dot(ReadonlyVec3D v) { + public final float dot(XYZ v) { return x * v.x() + y * v.y() + z * v.z(); } @@ -429,7 +435,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { */ public boolean equals(Object v) { try { - ReadonlyVec3D vv = (ReadonlyVec3D) v; + roVec3D vv = (roVec3D) v; return (x == vv.x() && y == vv.y() && z == vv.z()); } catch (NullPointerException e) { return false; @@ -446,7 +452,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * the vector with which the comparison is made * @return true or false */ - public boolean equals(ReadonlyVec3D v) { + public boolean equals(roVec3D v) { try { return (x == v.x() && y == v.y() && z == v.z()); } catch (NullPointerException e) { @@ -454,7 +460,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { } } - public boolean equalsWithTolerance(ReadonlyVec3D v, float tolerance) { + public boolean equalsWithTolerance(roVec3D v, float tolerance) { try { float diff = x - v.x(); if (Float.isNaN(diff)) { @@ -489,7 +495,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D floor() { + public final XYZ floor() { x = MathUtils.floor(x); y = MathUtils.floor(y); z = MathUtils.floor(z); @@ -502,7 +508,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D frac() { + public final XYZ frac() { x -= MathUtils.floor(x); y -= MathUtils.floor(y); z -= MathUtils.floor(z); @@ -513,7 +519,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return new Vec3D(this).abs(); } - public Vec3D getCartesian() { + public XYZ getCartesian() { return copy().toCartesian(); } @@ -569,7 +575,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getConstrained(toxi.geom.AABB) */ - public final Vec3D getConstrained(AABB box) { + public final XYZ getConstrained(AABB box) { return new Vec3D(this).constrain(box); } @@ -578,7 +584,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getFloored() */ - public final Vec3D getFloored() { + public final XYZ getFloored() { return new Vec3D(this).floor(); } @@ -587,7 +593,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getFrac() */ - public final Vec3D getFrac() { + public final XYZ getFrac() { return new Vec3D(this).frac(); } @@ -605,26 +611,17 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getLimited(float) */ - public final Vec3D getLimited(float lim) { + public final XYZ getLimited(float lim) { if (magSquared() > lim * lim) { return getNormalizedTo(lim); } return new Vec3D(this); } - /*public Vec3D getMapped(ScaleMap map) { + public XYZ getMapped(ScaleMap map) { return new Vec3D((float) map.getClippedValueFor(x), (float) map.getClippedValueFor(y), (float) map.getClippedValueFor(z)); - }*/ - - /* - * (non-Javadoc) - * - * @see toxi.geom.ReadonlyVec3D#getNormalized() - */ - public final Vec3D getNormalized() { - return new Vec3D(this).normalize(); } /* @@ -641,11 +638,11 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getReciprocal() */ - public final Vec3D getReciprocal() { + public final XYZ getReciprocal() { return copy().reciprocal(); } - public final Vec3D getReflected(ReadonlyVec3D normal) { + public final XYZ getReflected(roVec3D normal) { return copy().reflect(normal); } @@ -654,7 +651,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getRotatedAroundAxis(toxi.geom.Vec3D, float) */ - public final Vec3D getRotatedAroundAxis(ReadonlyVec3D axis, float theta) { + public final XYZ getRotatedAroundAxis(roVec3D axis, float theta) { return new Vec3D(this).rotateAroundAxis(axis, theta); } @@ -663,7 +660,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getRotatedX(float) */ - public final Vec3D getRotatedX(float theta) { + public final XYZ getRotatedX(float theta) { return new Vec3D(this).rotateX(theta); } @@ -672,7 +669,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getRotatedY(float) */ - public final Vec3D getRotatedY(float theta) { + public final XYZ getRotatedY(float theta) { return new Vec3D(this).rotateY(theta); } @@ -681,11 +678,11 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @see toxi.geom.ReadonlyVec3D#getRotatedZ(float) */ - public final Vec3D getRotatedZ(float theta) { + public final XYZ getRotatedZ(float theta) { return new Vec3D(this).rotateZ(theta); } - public Vec3D getRoundedTo(float prec) { + public XYZ getRoundedTo(float prec) { return copy().roundTo(prec); } @@ -698,7 +695,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return new Vec3D(this).signum(); } - public Vec3D getSpherical() { + public XYZ getSpherical() { return copy().toSpherical(); } @@ -746,69 +743,47 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return (float) Math.atan2(y, z); } - public ReadonlyVec3D immutable() { + public roVec3D immutable() { return this; } - public final Vec3D interpolateTo(ReadonlyVec3D v, float f) { - return new Vec3D(x + (v.x() - x) * f, y + (v.y() - y) * f, z - + (v.z() - z) * f); - } - -// public final Vec3D interpolateTo(ReadonlyVec3D v, float f, -// InterpolateStrategy s) { -// return new Vec3D(s.interpolate(x, v.x(), f), -// s.interpolate(y, v.y(), f), s.interpolate(z, v.z(), f)); -// } -// - public final Vec3D interpolateTo(Vec3D v, float f) { - return new Vec3D(x + (v.x - x) * f, y + (v.y - y) * f, z + (v.z - z) - * f); - } -// -// public final Vec3D interpolateTo(Vec3D v, float f, InterpolateStrategy s) { -// return new Vec3D(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 Vec3D interpolateToSelf(ReadonlyVec3D v, float f) { + public final Vec3D interpolateToSelf(roVec3D v, float 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 Vec3D 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; -// } + /** + * 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 XYZ interpolateToSelf(roVec3D 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; + } /** * Scales vector uniformly by factor -1 ( v = -v ), overrides coordinates @@ -816,32 +791,14 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D invert() { + public final XYZ invert() { x *= -1; y *= -1; z *= -1; return this; } - /* - * (non-Javadoc) - * - * @see toxi.geom.ReadonlyVec3D#isInAABB(toxi.geom.AABB) - */ - public boolean isInAABB(AABB box) { - final Vec3D min = box.getMin(); - final Vec3D 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 isInAABB(Vec3D boxOrigin, Vec3D boxExtent) { float w = boxExtent.x; @@ -895,7 +852,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return the vec3 d */ - public final Vec3D jitter(float j) { + public final XYZ jitter(float j) { return jitter(j, j, j); } @@ -912,25 +869,25 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D jitter(float jx, float jy, float jz) { + public final XYZ jitter(float jx, float jy, float jz) { x += MathUtils.normalizedRandom() * jx; y += MathUtils.normalizedRandom() * jy; z += MathUtils.normalizedRandom() * jz; return this; } - public final Vec3D jitter(Random rnd, float j) { + public final XYZ jitter(Random rnd, float j) { return jitter(rnd, j, j, j); } - public final Vec3D jitter(Random rnd, float jx, float jy, float jz) { + public final XYZ jitter(Random rnd, float jx, float jy, float jz) { x += MathUtils.normalizedRandom(rnd) * jx; y += MathUtils.normalizedRandom(rnd) * jy; z += MathUtils.normalizedRandom(rnd) * jz; return this; } - public final Vec3D jitter(Random rnd, Vec3D jitterVec) { + public final XYZ jitter(Random rnd, Vec3D jitterVec) { return jitter(rnd, jitterVec.x, jitterVec.y, jitterVec.z); } @@ -944,7 +901,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D jitter(Vec3D jitterVec) { + public final XYZ jitter(Vec3D jitterVec) { return jitter(jitterVec.x, jitterVec.y, jitterVec.z); } @@ -979,7 +936,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return the vec3 d */ - public final Vec3D maxSelf(ReadonlyVec3D b) { + public final XYZ maxSelf(XYZ b) { x = MathUtils.max(x, b.x()); y = MathUtils.max(y, b.y()); z = MathUtils.max(z, b.z()); @@ -994,7 +951,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return the vec3 d */ - public final Vec3D minSelf(ReadonlyVec3D b) { + public final XYZ minSelf(XYZ b) { x = MathUtils.min(x, b.x()); y = MathUtils.min(y, b.y()); z = MathUtils.min(z, b.z()); @@ -1010,7 +967,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D modSelf(float base) { + public final XYZ modSelf(float base) { x %= base; y %= base; z %= base; @@ -1030,7 +987,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * @return itself */ - public final Vec3D modSelf(float bx, float by, float bz) { + public final XYZ modSelf(float bx, float by, float bz) { x %= bx; y %= by; z %= bz; @@ -1083,7 +1040,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public final Vec3D reflect(ReadonlyVec3D normal) { + public final XYZ reflect(roVec3D normal) { return set(normal.scale(this.dot(normal) * 2).subSelf(this)); } @@ -1097,7 +1054,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D rotateAroundAxis(ReadonlyVec3D axis, float theta) { + public final XYZ rotateAroundAxis(XYZ axis, float theta) { final float ax = axis.x(); final float ay = axis.y(); final float az = axis.z(); @@ -1135,7 +1092,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D rotateX(float theta) { + public final XYZ rotateX(float theta) { final float co = (float) Math.cos(theta); final float si = (float) Math.sin(theta); final float zz = co * z - si * y; @@ -1152,7 +1109,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D rotateY(float theta) { + public final XYZ rotateY(float theta) { final float co = (float) Math.cos(theta); final float si = (float) Math.sin(theta); final float xx = co * x - si * z; @@ -1169,7 +1126,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D rotateZ(float theta) { + public final XYZ rotateZ(float theta) { final float co = (float) Math.cos(theta); final float si = (float) Math.sin(theta); final float xx = co * x - si * y; @@ -1178,26 +1135,23 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public Vec3D roundTo(float prec) { + public XYZ roundTo(float prec) { x = MathUtils.roundTo(x, prec); y = MathUtils.roundTo(y, prec); z = MathUtils.roundTo(z, prec); return this; } - public Vec3D scale(float s) { - return new Vec3D(x * s, y * s, z * s); - } - public Vec3D scale(float a, float b, float c) { + public XYZ scale(float a, float b, float c) { return new Vec3D(x * a, y * b, z * c); } - public Vec3D scale(ReadonlyVec3D s) { + public XYZ scale(roVec3D s) { return new Vec3D(x * s.x(), y * s.y(), z * s.z()); } - public Vec3D scale(Vec3D s) { + public XYZ scale(Vec3D s) { return new Vec3D(x * s.x, y * s.y, z * s.z); } @@ -1229,14 +1183,14 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public Vec3D scaleSelf(float a, float b, float c) { + public XYZ scaleSelf(float a, float b, float c) { x *= a; y *= b; z *= c; return this; } - public Vec3D scaleSelf(ReadonlyVec3D s) { + public XYZ scaleSelf(roVec3D s) { x *= s.x(); y *= s.y(); z *= s.z(); @@ -1279,7 +1233,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public Vec3D set(ReadonlyVec3D v) { + public Vec3D set(XYZ v) { x = v.x(); y = v.y(); z = v.z(); @@ -1301,7 +1255,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public final Vec3D setComponent(Axis id, float val) { + public final XYZ setComponent(Axis id, float val) { switch (id) { case X: x = val; @@ -1316,7 +1270,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public final Vec3D setComponent(int id, float val) { + public final XYZ setComponent(int id, float val) { switch (id) { case 0: x = val; @@ -1331,7 +1285,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public Vec3D setX(float x) { + public XYZ setX(float x) { this.x = x; return this; } @@ -1344,23 +1298,23 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public Vec3D setXY(Vec2D v) { + public XYZ setXY(Vec2D v) { x = v.x; y = v.y; return this; } - public Vec3D setY(float y) { + public XYZ setY(float y) { this.y = y; return this; } - public Vec3D setZ(float z) { + public XYZ setZ(float z) { this.z = z; return this; } - public Vec3D shuffle(int iterations) { + public XYZ shuffle(int iterations) { float t; for (int i = 0; i < iterations; i++) { switch (MathUtils.random(3)) { @@ -1404,7 +1358,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D snapToAxis() { + public final XYZ snapToAxis() { if (MathUtils.abs(x) < 0.5f) { x = 0; } else { @@ -1426,17 +1380,14 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public final Vec3D sub(float a, float b, float c) { + public final XYZ sub(float a, float b, float c) { return new Vec3D(x - a, y - b, z - c); } - public final Vec3D sub(ReadonlyVec3D v) { + public final Vec3D sub(roVec3D v) { return new Vec3D(x - v.x(), y - v.y(), z - v.z()); } - public final Vec3D sub(Vec3D v) { - return new Vec3D(x - v.x, y - v.y, z - v.z); - } /** * Subtracts vector {a,b,c} and overrides coordinates with result. @@ -1450,14 +1401,14 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { * * @return itself */ - public final Vec3D subSelf(float a, float b, float c) { + public final XYZ subSelf(float a, float b, float c) { x -= a; y -= b; z -= c; return this; } - public final Vec3D subSelf(ReadonlyVec3D v) { + public final Vec3D subSelf(roVec3D v) { x -= v.x(); y -= v.y(); z -= v.z(); @@ -1511,7 +1462,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { }; } - public final Vec3D toCartesian() { + public final XYZ toCartesian() { final float a = (float) (x * Math.cos(z)); final float xx = (float) (a * Math.cos(y)); final float yy = (float) (x * Math.sin(z)); @@ -1522,7 +1473,7 @@ public class Vec3D implements Comparable<ReadonlyVec3D>, ReadonlyVec3D { return this; } - public final Vec3D toSpherical() { + public final XYZ toSpherical() { final float xx = Math.abs(x) <= MathUtils.EPS ? MathUtils.EPS : x; final float zz = z; diff --git a/src/main/java/com/syncleus/spangraph/geom/Vec4D.java b/src/main/java/toxi/geom/Vec4D.java similarity index 97% rename from src/main/java/com/syncleus/spangraph/geom/Vec4D.java rename to src/main/java/toxi/geom/Vec4D.java index 4712c7d..c673691 100644 --- a/src/main/java/com/syncleus/spangraph/geom/Vec4D.java +++ b/src/main/java/toxi/geom/Vec4D.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; import javax.xml.bind.annotation.XmlAttribute; @@ -61,7 +61,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { this.w = w; } - public Vec4D(ReadonlyVec3D v, float w) { + public Vec4D(roVec3D v, float w) { this.x = v.x(); this.y = v.y(); this.z = v.z(); @@ -108,7 +108,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { return new Vec4D(x + xx, y + yy, z + zz, w); } - public final Vec4D addXYZ(ReadonlyVec3D v) { + public final Vec4D addXYZ(roVec3D v) { return new Vec4D(x + v.x(), y + v.y(), z + v.z(), w); } @@ -119,7 +119,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { return this; } - public final Vec4D addXYZSelf(ReadonlyVec3D v) { + public final Vec4D addXYZSelf(roVec3D v) { this.x += v.x(); this.y += v.y(); this.z += v.z(); @@ -297,7 +297,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { return copy().normalizeTo(len); } - public Vec4D getRotatedAroundAxis(ReadonlyVec3D axis, float theta) { + public Vec4D getRotatedAroundAxis(roVec3D axis, float theta) { return copy().rotateAroundAxis(axis, theta); } @@ -439,7 +439,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { * * @return itself */ - public final Vec4D rotateAroundAxis(ReadonlyVec3D axis, float theta) { + public final Vec4D rotateAroundAxis(roVec3D axis, float theta) { final float ax = axis.x(); final float ay = axis.y(); final float az = axis.z(); @@ -609,7 +609,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { return this; } - public final Vec4D setXYZ(ReadonlyVec3D v) { + public final Vec4D setXYZ(roVec3D v) { this.x = v.x(); this.y = v.y(); this.z = v.z(); @@ -642,7 +642,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { return new Vec4D(x - xx, y - yy, z - zz, w); } - public final Vec4D subXYZ(ReadonlyVec3D v) { + public final Vec4D subXYZ(roVec3D v) { return new Vec4D(x - v.x(), y - v.y(), z - v.z(), w); } @@ -653,7 +653,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { return this; } - public final Vec4D subXYZSelf(ReadonlyVec3D v) { + public final Vec4D subXYZSelf(roVec3D v) { this.x -= v.x(); this.y -= v.y(); this.z -= v.z(); @@ -721,7 +721,7 @@ public class Vec4D implements ReadonlyVec4D, Cloneable { return this; } - public final Vec3D weightInto(Vec3D out) { + public final XYZ weightInto(Vec3D out) { out.x = x * w; out.y = y * w; out.z = z * w; diff --git a/src/main/java/com/syncleus/spangraph/geom/VecMathUtil.java b/src/main/java/toxi/geom/VecMathUtil.java similarity index 99% rename from src/main/java/com/syncleus/spangraph/geom/VecMathUtil.java rename to src/main/java/toxi/geom/VecMathUtil.java index 77a13ae..2038bc4 100644 --- a/src/main/java/com/syncleus/spangraph/geom/VecMathUtil.java +++ b/src/main/java/toxi/geom/VecMathUtil.java @@ -29,7 +29,7 @@ * $State$ */ -package com.syncleus.spangraph.geom; +package toxi.geom; /** * Utility vecmath class used when computing the hash code for vecmath objects diff --git a/src/main/java/toxi/geom/XAxisCylinder.java b/src/main/java/toxi/geom/XAxisCylinder.java new file mode 100644 index 0000000..a19a721 --- /dev/null +++ b/src/main/java/toxi/geom/XAxisCylinder.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 XAxisCylinder extends AxisAlignedCylinder { + + public XAxisCylinder(roVec3D pos, float radius, float length) { + super(pos, radius, length); + } + + public boolean containsPoint(XYZ p) { + if (MathUtils.abs(p.x() - pos.x) < length * 0.5) { + float dy = p.y() - pos.y; + float dz = p.z() - pos.z; + if (Math.abs(dz * dz + dy * dy) < radiusSquared) { + return true; + } + } + return false; + } + + public Vec3D.Axis getMajorAxis() { + return Vec3D.Axis.X; + } +} diff --git a/src/main/java/toxi/geom/XYZ.java b/src/main/java/toxi/geom/XYZ.java new file mode 100644 index 0000000..91c52c8 --- /dev/null +++ b/src/main/java/toxi/geom/XYZ.java @@ -0,0 +1,89 @@ +package toxi.geom; + +import toxi.math.InterpolateStrategy; + +/** + * Created by me on 6/13/15. + */ +public interface XYZ { + + public float x(); + + public float y(); + + public float z(); + + default public XYZ interpolateTo(XYZ v, float f) { + return new Vec3D(x() + (v.x() - x()) * f, y() + (v.y() - y()) * f, z() + + (v.z() - z()) * f); + } + + default public XYZ interpolateTo(XYZ v, float f, + InterpolateStrategy s) { + return new Vec3D(s.interpolate(x(), v.x(), f), + s.interpolate(y(), v.y(), f), s.interpolate(z(), v.z(), f)); + } + + default public Vec3D interpolateTo(Vec3D v, float f) { + return new Vec3D(x() + (v.x - x()) * f, y() + (v.y - y()) * f, z() + (v.z - z()) + * f); + } + + default public XYZ interpolateTo(Vec3D v, float f, InterpolateStrategy s) { + return new Vec3D(s.interpolate(x(), v.x, f), s.interpolate(y(), v.y, f), + s.interpolate(z(), v.z, f)); + } + + + default public Vec3D scale(float s) { + return new Vec3D(x() * s, y() * s, z() * s); + } + + default public Vec3D sub(XYZ v) { + return new Vec3D(x() - v.x(), y() - v.y(), z() - v.z()); + } + + default public float distanceTo(XYZ v) { + if (v != null) { + final float dx = x() - v.x(); + final float dy = y() - v.y(); + final float dz = z() - v.z(); + return (float) Math.sqrt(dx * dx + dy * dy + dz * dz); + } else { + return Float.NaN; + } + } + + default public float distanceToSquared(roVec3D v) { + if (v != null) { + final float dx = x() - v.x(); + final float dy = y() - v.y(); + final float dz = z() - v.z(); + return dx * dx + dy * dy + dz * dz; + } else { + return Float.NaN; + } + } + + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#getNormalized() + */ + default public Vec3D getNormalized() { + return new Vec3D(this).normalize(); + } + + + /* + * (non-Javadoc) + * + * @see toxi.geom.ReadonlyVec3D#isInAABB(toxi.geom.AABB) + */ + default public boolean isInAABB(final AABB box) { + return box.contains(this); + } + + default XYZ copy() { return new Vec3D(this); } +} diff --git a/src/main/java/toxi/geom/YAxisCylinder.java b/src/main/java/toxi/geom/YAxisCylinder.java new file mode 100644 index 0000000..092bd08 --- /dev/null +++ b/src/main/java/toxi/geom/YAxisCylinder.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 YAxisCylinder extends AxisAlignedCylinder { + + public YAxisCylinder(roVec3D pos, float radius, float length) { + super(pos, radius, length); + } + + public boolean containsPoint(XYZ p) { + if (MathUtils.abs(p.y() - pos.y) < length * 0.5) { + float dx = p.x() - pos.x; + float dz = p.z() - pos.z; + if (Math.abs(dz * dz + dx * dx) < radiusSquared) { + return true; + } + } + return false; + } + + public Vec3D.Axis getMajorAxis() { + return Vec3D.Axis.Y; + } + +} diff --git a/src/main/java/toxi/geom/ZAxisCylinder.java b/src/main/java/toxi/geom/ZAxisCylinder.java new file mode 100644 index 0000000..6f47bc1 --- /dev/null +++ b/src/main/java/toxi/geom/ZAxisCylinder.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 ZAxisCylinder extends AxisAlignedCylinder { + + public ZAxisCylinder(roVec3D pos, float radius, float length) { + super(pos, radius, length); + } + + public boolean containsPoint(XYZ p) { + if (MathUtils.abs(p.z() - pos.z) < length * 0.5) { + float dx = p.x() - pos.x; + float dy = p.y() - pos.y; + if (Math.abs(dx * dx + dy * dy) < radiusSquared) { + return true; + } + } + return false; + } + + public Vec3D.Axis getMajorAxis() { + return Vec3D.Axis.Z; + } +} diff --git a/src/main/java/toxi/geom/mesh2d/DelaunayTriangle.java b/src/main/java/toxi/geom/mesh2d/DelaunayTriangle.java new file mode 100644 index 0000000..cf43526 --- /dev/null +++ b/src/main/java/toxi/geom/mesh2d/DelaunayTriangle.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 DelaunayTriangle 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 DelaunayTriangle extends ArraySet<DelaunayVertex> { + + private int idNumber; // The id number + private DelaunayVertex 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 DelaunayTriangle(Collection<? extends DelaunayVertex> collection) { + super(collection); + idNumber = idGenerator++; + if (this.size() != 3) { + throw new IllegalArgumentException( + "DelaunayTriangle must have 3 vertices"); + } + } + + /** + * @param vertices + * the vertices of the DelaunayTriangle. + * @throws IllegalArgumentException + * if there are not three distinct vertices + */ + public DelaunayTriangle(DelaunayVertex... vertices) { + this(Arrays.asList(vertices)); + } + + @Override + public boolean add(DelaunayVertex vertex) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + return (this == o); + } + + /** + * Report the facet opposite vertex. + * + * @param vertex + * a vertex of this DelaunayTriangle + * @return the facet opposite vertex + * @throws IllegalArgumentException + * if the vertex is not in triangle + */ + public ArraySet<DelaunayVertex> facetOpposite(DelaunayVertex vertex) { + ArraySet<DelaunayVertex> facet = new ArraySet<DelaunayVertex>(this); + if (!facet.remove(vertex)) { + throw new IllegalArgumentException("Vertex not in triangle"); + } + return facet; + } + + /** + * @return the triangle's circumcenter + */ + public DelaunayVertex getCircumcenter() { + if (circumcenter == null) { + circumcenter = DelaunayVertex.circumcenter(this + .toArray(new DelaunayVertex[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 DelaunayVertex getVertexButNot(DelaunayVertex... badVertices) { + Collection<DelaunayVertex> bad = Arrays.asList(badVertices); + for (DelaunayVertex v : this) { + if (!bad.contains(v)) { + return v; + } + } + throw new NoSuchElementException("No vertex found"); + } + + /* The following two methods ensure that a DelaunayTriangle 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 DelaunayTriangle + * @return true iff this DelaunayTriangle is a neighbor of triangle + */ + public boolean isNeighbor(DelaunayTriangle triangle) { + int count = 0; + for (DelaunayVertex vertex : this) { + if (!triangle.contains(vertex)) { + count++; + } + } + return count == 1; + } + + /* The following two methods ensure that all triangles are different. */ + + @Override + public Iterator<DelaunayVertex> iterator() { + return new Iterator<DelaunayVertex>() { + + private Iterator<DelaunayVertex> it = DelaunayTriangle.super + .iterator(); + + public boolean hasNext() { + return it.hasNext(); + } + + public DelaunayVertex next() { + return it.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public String toString() { + if (!moreInfo) { + return "DelaunayTriangle" + idNumber; + } + return "DelaunayTriangle" + idNumber + super.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/toxi/geom/mesh2d/DelaunayTriangulation.java b/src/main/java/toxi/geom/mesh2d/DelaunayTriangulation.java new file mode 100644 index 0000000..9fcb2b4 --- /dev/null +++ b/src/main/java/toxi/geom/mesh2d/DelaunayTriangulation.java @@ -0,0 +1,321 @@ +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 toxi.util.datatypes.UndirectedGraph; + +import java.util.*; + +/** + * A 2D Delaunay DelaunayTriangulation (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 DelaunayTriangulation is a Set of Triangles. A DelaunayTriangulation 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 DelaunayTriangulation extends AbstractSet<DelaunayTriangle> { + + private DelaunayTriangle mostRecent = null; + + private UndirectedGraph<DelaunayTriangle> triGraph; + + /** + * All sites must fall within the initial triangle. + * + * @param triangle + * the initial triangle + */ + public DelaunayTriangulation(DelaunayTriangle triangle) { + triGraph = new UndirectedGraph<DelaunayTriangle>(); + 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 DelaunayVertex + * @throws IllegalArgumentException + * if site does not lie in any triangle + */ + public void delaunayPlace(DelaunayVertex site) { + // Uses straightforward scheme rather than best asymptotic time + // Locate containing triangle + DelaunayTriangle 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<DelaunayTriangle> 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<DelaunayTriangle> getCavity(DelaunayVertex site, + DelaunayTriangle triangle) { + Set<DelaunayTriangle> encroached = new HashSet<DelaunayTriangle>(); + Queue<DelaunayTriangle> toBeChecked = new LinkedList<DelaunayTriangle>(); + Set<DelaunayTriangle> marked = new HashSet<DelaunayTriangle>(); + toBeChecked.add(triangle); + marked.add(triangle); + while (!toBeChecked.isEmpty()) { + triangle = toBeChecked.remove(); + if (site.vsCircumcircle(triangle.toArray(new DelaunayVertex[0])) == 1) { + // Site outside triangle => triangle not in cavity + continue; + } + encroached.add(triangle); + // Check the neighbors + for (DelaunayTriangle neighbor : triGraph + .getConnectedNodesFor(triangle)) { + if (marked.contains(neighbor)) { + continue; + } + marked.add(neighbor); + toBeChecked.add(neighbor); + } + } + return encroached; + } + + @Override + public Iterator<DelaunayTriangle> 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 DelaunayTriangle locate(DelaunayVertex point) { + DelaunayTriangle triangle = mostRecent; + if (!this.contains(triangle)) { + triangle = null; + } + + // Try a directed walk (this works fine in 2D, but can fail in 3D) + Set<DelaunayTriangle> visited = new HashSet<DelaunayTriangle>(); + 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 + DelaunayVertex corner = point.isOutside(triangle + .toArray(new DelaunayVertex[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 (DelaunayTriangle tri : this) { + if (point.isOutside(tri.toArray(new DelaunayVertex[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 DelaunayTriangle neighborOpposite(DelaunayVertex site, + DelaunayTriangle triangle) { + if (!triangle.contains(site)) { + throw new IllegalArgumentException("Bad vertex; not in triangle"); + } + for (DelaunayTriangle 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<DelaunayTriangle> neighbors(DelaunayTriangle 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<DelaunayTriangle> surroundingTriangles(DelaunayVertex site, + DelaunayTriangle triangle) { + if (!triangle.contains(site)) { + throw new IllegalArgumentException("Site not in triangle"); + } + List<DelaunayTriangle> list = new ArrayList<DelaunayTriangle>(); + DelaunayTriangle start = triangle; + DelaunayVertex guide = triangle.getVertexButNot(site); // Affects cw or + // ccw + while (true) { + list.add(triangle); + DelaunayTriangle 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 "DelaunayTriangulation 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 DelaunayTriangle update(DelaunayVertex site, + Set<DelaunayTriangle> cavity) { + Set<Set<DelaunayVertex>> boundary = new HashSet<Set<DelaunayVertex>>(); + Set<DelaunayTriangle> theTriangles = new HashSet<DelaunayTriangle>(); + + // Find boundary facets and adjacent triangles + for (DelaunayTriangle triangle : cavity) { + theTriangles.addAll(neighbors(triangle)); + for (DelaunayVertex vertex : triangle) { + Set<DelaunayVertex> 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 (DelaunayTriangle triangle : cavity) { + triGraph.remove(triangle); + } + + // Build each new triangle and add it to the triangulation + Set<DelaunayTriangle> newTriangles = new HashSet<DelaunayTriangle>(); + for (Set<DelaunayVertex> vertices : boundary) { + vertices.add(site); + DelaunayTriangle tri = new DelaunayTriangle(vertices); + triGraph.add(tri); + newTriangles.add(tri); + } + + // Update the graph links for each new triangle + theTriangles.addAll(newTriangles); // Adj triangle + new triangles + for (DelaunayTriangle triangle : newTriangles) { + for (DelaunayTriangle 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/main/java/toxi/geom/mesh2d/DelaunayVertex.java b/src/main/java/toxi/geom/mesh2d/DelaunayVertex.java new file mode 100644 index 0000000..be95cdf --- /dev/null +++ b/src/main/java/toxi/geom/mesh2d/DelaunayVertex.java @@ -0,0 +1,546 @@ +package toxi.geom.mesh2d; + +import toxi.geom.Vec2D; + +/* + * 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 DelaunayVertex { + + /** + * Circumcenter of a simplex. + * + * @param simplex + * the simplex (as an array of Pnts) + * @return the circumcenter (a DelaunayVertex) of simplex + */ + public static DelaunayVertex circumcenter(DelaunayVertex[] simplex) { + int dim = simplex[0].dimension(); + if (simplex.length - 1 != dim) { + throw new IllegalArgumentException("Dimension mismatch"); + } + DelaunayVertex[] matrix = new DelaunayVertex[dim]; + for (int i = 0; i < dim; i++) { + matrix[i] = simplex[i].bisector(simplex[i + 1]); + } + DelaunayVertex 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 DelaunayVertex(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(DelaunayVertex[] simplex) { + DelaunayVertex[] matrix = new DelaunayVertex[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 DelaunayVertex 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 DelaunayVertex + * dimension) + * @return a DelaunayVertex perpendicular to each row DelaunayVertex + * @throws IllegalArgumentException + * if matrix is wrong shape + */ + public static DelaunayVertex cross(DelaunayVertex[] 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 DelaunayVertex(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(DelaunayVertex[] 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(DelaunayVertex[] 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(DelaunayVertex[] matrix) { + StringBuilder buf = new StringBuilder("{"); + for (DelaunayVertex row : matrix) { + buf.append(" " + row); + } + buf.append(" }"); + return buf.toString(); + } + + private double[] coordinates; // The point's coordinates + + /** + * Constructor. + * + * @param coords + * the coordinates + */ + public DelaunayVertex(double... coords) { + // Copying is done here to ensure that DelaunayVertex'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 DelaunayVertex + * @return a new DelaunayVertex = this + p + */ + public DelaunayVertex add(DelaunayVertex 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 DelaunayVertex(coords); + } + + /** + * Angle (in radians) between two Pnts (treated as vectors). + * + * @param p + * the other DelaunayVertex + * @return the angle (in radians) between the two Pnts + */ + public double angle(DelaunayVertex 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 DelaunayVertex 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 DelaunayVertex bisector(DelaunayVertex point) { + dimCheck(point); + DelaunayVertex diff = this.subtract(point); + DelaunayVertex sum = this.add(point); + double dot = diff.dot(sum); + return diff.extend(-dot / 2); + } + + /** + * @return the specified coordinate of this DelaunayVertex + * @throws ArrayIndexOutOfBoundsException + * for bad coordinate + */ + public double coord(int i) { + return this.coordinates[i]; + } + + /** + * Check that dimensions match. + * + * @param p + * the DelaunayVertex to check (against this DelaunayVertex) + * @return the dimension of the Pnts + * @throws IllegalArgumentException + * if dimension fail to match + */ + public int dimCheck(DelaunayVertex p) { + int len = this.coordinates.length; + if (len != p.coordinates.length) { + throw new IllegalArgumentException("Dimension mismatch"); + } + return len; + } + + /** + * @return this DelaunayVertex's dimension. + */ + public int dimension() { + return coordinates.length; + } + + /* Pnts as matrices */ + + /** + * Dot product. + * + * @param p + * the other DelaunayVertex + * @return dot product of this DelaunayVertex and p + */ + public double dot(DelaunayVertex 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 DelaunayVertex)) { + return false; + } + DelaunayVertex p = (DelaunayVertex) 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 DelaunayVertex by adding additional coordinates to this + * DelaunayVertex. + * + * @param coords + * the new coordinates (added on the right end) + * @return a new DelaunayVertex with the additional coordinates + */ + public DelaunayVertex 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 DelaunayVertex(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 DelaunayVertex is inside a simplex. + * + * @param simplex + * the simplex (an arary of Pnts) + * @return true iff this DelaunayVertex is inside simplex. + */ + public boolean isInside(DelaunayVertex[] simplex) { + int[] result = this.relation(simplex); + for (int r : result) { + if (r >= 0) { + return false; + } + } + return true; + } + + /** + * Test if this DelaunayVertex is on a simplex. + * + * @param simplex + * the simplex (an array of Pnts) + * @return the simplex DelaunayVertex that "witnesses" on-ness (or null if + * not on) + */ + public DelaunayVertex isOn(DelaunayVertex[] simplex) { + int[] result = this.relation(simplex); + DelaunayVertex 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 DelaunayVertex is outside of simplex. + * + * @param simplex + * the simplex (an array of Pnts) + * @return simplex DelaunayVertex that "witnesses" outsideness (or null if + * not outside) + */ + public DelaunayVertex isOutside(DelaunayVertex[] 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 DelaunayVertex 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 DelaunayVertex. + * + * <pre> + * -1 means DelaunayVertex is on same side of facet + * 0 means DelaunayVertex is on the facet + * +1 means DelaunayVertex is on opposite side of facet + * </pre> + * + * @param simplex + * an array of Pnts representing a simplex + * @return an array of signs showing relation between this DelaunayVertex + * and simplex + * @throws IllegalArgumentExcpetion + * if the simplex is degenerate + */ + public int[] relation(DelaunayVertex[] 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 DelaunayVertex. + * 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 */ + DelaunayVertex[] matrix = new DelaunayVertex[dim + 1]; + /* First row */ + double[] coords = new double[dim + 2]; + for (int j = 0; j < coords.length; j++) { + coords[j] = 1; + } + matrix[0] = new DelaunayVertex(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 DelaunayVertex(coords); + } + + /* Compute and analyze the vector of areas/volumes/contents */ + DelaunayVertex 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 DelaunayVertex + * @return a new DelaunayVertex = this - p + */ + public DelaunayVertex subtract(DelaunayVertex 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 DelaunayVertex(coords); + } + + @Override + public String toString() { + if (coordinates.length == 0) { + return "DelaunayVertex()"; + } + String result = "DelaunayVertex(" + coordinates[0]; + for (int i = 1; i < coordinates.length; i++) { + result = result + "," + coordinates[i]; + } + result = result + ")"; + return result; + } + + public Vec2D toVec2D() { + return new Vec2D((float) coordinates[0], (float) coordinates[1]); + } + + /** + * Test relation between this DelaunayVertex 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(DelaunayVertex[] simplex) { + DelaunayVertex[] matrix = new DelaunayVertex[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/main/java/toxi/geom/mesh2d/Voronoi.java b/src/main/java/toxi/geom/mesh2d/Voronoi.java new file mode 100644 index 0000000..6f5f641 --- /dev/null +++ b/src/main/java/toxi/geom/mesh2d/Voronoi.java @@ -0,0 +1,106 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.Polygon2D; +import toxi.geom.Triangle2D; +import toxi.geom.Vec2D; + +public class Voronoi { + + public static float DEFAULT_SIZE = 10000; + + protected DelaunayTriangulation delaunay; + protected DelaunayTriangle initialTriangle; + protected List<Vec2D> sites = new ArrayList<Vec2D>(); + + public Voronoi() { + this(DEFAULT_SIZE); + } + + public Voronoi(float size) { + initialTriangle = new DelaunayTriangle( + new DelaunayVertex(-size, -size), new DelaunayVertex(size, + -size), new DelaunayVertex(0, size)); + this.delaunay = new DelaunayTriangulation(initialTriangle); + } + + public void addPoint(Vec2D p) { + sites.add(p.copy()); + delaunay.delaunayPlace(new DelaunayVertex(p.x, p.y)); + } + + public void addPoints(Collection<? extends Vec2D> points) { + for (Vec2D p : points) { + addPoint(p); + } + } + + public List<Polygon2D> getRegions() { + List<Polygon2D> regions = new LinkedList<Polygon2D>(); + HashSet<DelaunayVertex> done = new HashSet<DelaunayVertex>( + initialTriangle); + for (DelaunayTriangle triangle : delaunay) { + for (DelaunayVertex site : triangle) { + if (done.contains(site)) { + continue; + } + done.add(site); + List<DelaunayTriangle> list = delaunay.surroundingTriangles( + site, triangle); + Polygon2D poly = new Polygon2D(); + for (DelaunayTriangle tri : list) { + DelaunayVertex circumeter = tri.getCircumcenter(); + poly.add(new Vec2D((float) circumeter.coord(0), + (float) circumeter.coord(1))); + } + regions.add(poly); + } + } + return regions; + } + + public List<Vec2D> getSites() { + return sites; + } + + public List<Triangle2D> getTriangles() { + List<Triangle2D> tris = new ArrayList<Triangle2D>(); + for (DelaunayTriangle t : delaunay) { + tris.add(new Triangle2D(t.get(0).toVec2D(), t.get(1).toVec2D(), t + .get(2).toVec2D())); + } + return tris; + } +} diff --git a/src/main/java/toxi/geom/nurbs/BasicNurbsCurve.java b/src/main/java/toxi/geom/nurbs/BasicNurbsCurve.java new file mode 100644 index 0000000..1884e12 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/BasicNurbsCurve.java @@ -0,0 +1,179 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +import toxi.geom.Polygon2D; +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; +import toxi.geom.XYZ; + +/** + * A Basic implementation of a NurbsCurve. + * + * @author sg + * @version 1.0 + */ +public class BasicNurbsCurve implements NurbsCurve, Cloneable { + + private Vec4D[] cpoly; + private KnotVector uKnots; + + /** + * Create a Nurbs Curve from the given Controlpoints, Knots and degree.<br> + * [TODO Validate Input, part of it is done by creating the KnotVector] + * + * @param cps + * Array of Controlpoints + * @param uK + * Knot values + * @param degree + * Degree of the Nurbs Curve + */ + public BasicNurbsCurve(Vec4D[] cps, float[] uK, int degree) { + this(cps, new KnotVector(uK, degree)); + } + + /** + * Generate a Nurbs from the given Controlpoints and the given Knotvector.<br> + * [TODO validate input] + * + * @param cps + * Array of Controlpoints + * @param uKnots + * Knotvector of the Nurbs + */ + public BasicNurbsCurve(Vec4D[] cps, KnotVector uKnots) { + cpoly = cps; + this.uKnots = uKnots; + if (uKnots.length() != uKnots.getDegree() + cpoly.length + 1) { + throw new IllegalArgumentException( + "Nurbs Curve has wrong knot number"); + } + } + + public Vec4D[][] curveDerivCpts(int d, int r1, int r2) { + + Vec4D[][] result = new Vec4D[d + 1][r2 - r1 + 1]; + + int degree = uKnots.getDegree(); + + // k=0 => control points + int r = r2 - r1; + for (int i = 0; i <= r; i++) { + result[0][i] = cpoly[i]; + } + + // k=1 => 1st derivative, k=2 => 2nd derivative, etc... + for (int k = 1; k <= d; k++) { + int tmp = degree - k + 1; + for (int i = 0; i <= (r - k); i++) { + Vec4D cw = new Vec4D(result[k - 1][i + 1]); + cw.subSelf(result[k - 1][i]); + cw.scaleSelf(tmp); + cw.scaleSelf(1 / (uKnots.get(r1 + i + degree + 1) - uKnots + .get(r1 + i + k))); + result[k][i] = cw; + } + } + return result; + } + + public XYZ[] derivativesOnCurve(float u, int grade) { + return derivativesOnCurve(u, grade, new XYZ[grade + 1]); + } + + public XYZ[] derivativesOnCurve(float u, int grade, XYZ[] derivs) { + + int span = uKnots.findSpan(u); + int degree = uKnots.getDegree(); + + // TODO: compute derivatives also for NURBS + // currently supports only non-rational B-Splines + float derivVals[][] = uKnots.derivBasisFunctions(span, u, grade); + + // Zero values + for (int k = (degree + 1); k <= grade; k++) { + derivs[k] = new Vec3D(); + } + + for (int k = 0; k <= grade; k++) { + Vec3D d = new Vec3D(); + for (int j = 0; j <= degree; j++) { + Vec4D v = cpoly[(span - degree) + j]; + float s = derivVals[k][j]; + d.addSelf(v.x * s, v.y * s, v.z * s); + } + derivs[k] = d; + } + return derivs; + } + + public Vec4D[] getControlPoints() { + return cpoly; + } + + public int getDegree() { + return uKnots.getDegree(); + } + + public float[] getKnots() { + return uKnots.getArray(); + } + + public KnotVector getKnotVector() { + return uKnots; + } + + public Vec3D pointOnCurve(float u) { + return pointOnCurve(u, new Vec3D()); + } + + public Vec3D pointOnCurve(float u, Vec3D out) { + int span = uKnots.findSpan(u); + int degree = uKnots.getDegree(); + + // for periodic knot vectors the usable parameter range is + // span >= degree and span <= no control points (n+1) + if (span < degree) { + return out; + } + + if (span > uKnots.getN()) { + return out; + } + + double[] bf = uKnots.basisFunctions(span, u); + Vec4D cw = new Vec4D(); + for (int i = 0; i <= degree; i++) { + cw.addSelf(cpoly[(span - degree) + i].getWeighted().scaleSelf( + (float) bf[i])); + } + return cw.unweightInto(out); + } + + public Polygon2D toPolygon2D(int res) { + float delta = 1f / (res - 1); + Polygon2D poly = new Polygon2D(); + for (int i = 0; i < res; i++) { + poly.add(pointOnCurve(i * delta).to2DXY()); + } + return poly; + } +} diff --git a/src/main/java/toxi/geom/nurbs/BasicNurbsSurface.java b/src/main/java/toxi/geom/nurbs/BasicNurbsSurface.java new file mode 100644 index 0000000..cb6ec24 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/BasicNurbsSurface.java @@ -0,0 +1,211 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; +import toxi.geom.XYZ; + +/** + * A Basic NurbsSurface implementation. + * + * @author sg + * @version 1.2 + */ +public class BasicNurbsSurface implements NurbsSurface { + + private KnotVector uKnots; + private KnotVector vKnots; + private ControlNet cpnet; + + // private List<TrimCurve> trimms = new LinkedList<TrimCurve>(); + + /** + * Create a Nurbs Surface from the given {@link ControlNet} and the knot + * values of degree p in u direction and of degree q in v direction. + * + * @param cps + * ControlNet of the Nurbs + * @param uK + * Knot values in u direction + * @param vK + * Knot values in v direction + * @param p + * degree in u direction + * @param q + * degree in v direction + */ + public BasicNurbsSurface(ControlNet cps, float[] uK, float[] vK, int p, + int q) throws IllegalArgumentException { + this(cps, new KnotVector(uK, p), new KnotVector(vK, q)); + } + + /** + * Create a Nurbs form the Controlnet and the two Knot vectors. + * + * @param net + * Contorl net of Nurbs + * @param u + * KnotVector in u direction + * @param v + * KnotVector in v direction + */ + public BasicNurbsSurface(ControlNet net, KnotVector u, KnotVector v) + throws IllegalArgumentException { + cpnet = net; + uKnots = u; + vKnots = v; + validate(); + } + + // public void addTrimCurve(TrimCurve tc) { + // trimms.add(tc); + // } + + public ControlNet getControlNet() { + return cpnet; + } + + // public List<TrimCurve> getTrimCurves() { + // return trimms; + // } + + public int getUDegree() { + return uKnots.getDegree(); + } + + public float[] getUKnots() { + return uKnots.getArray(); + } + + public KnotVector getUKnotVector() { + return uKnots; + } + + public int getVDegree() { + return vKnots.getDegree(); + } + + public float[] getVKnots() { + return vKnots.getArray(); + } + + public KnotVector getVKnotVector() { + return vKnots; + } + + public Vec3D pointOnSurface(double u, double v) { + return pointOnSurface((float) u, (float) v, new Vec3D()); + } + + public XYZ pointOnSurface(float u, float v) { + return pointOnSurface(u, v, new Vec3D()); + } + + public Vec3D pointOnSurface(float u, float v, Vec3D out) { + + // Piegl -> Algorithm A4.3 -> page 134 + + int uspan = uKnots.findSpan(u); + double[] bfu = uKnots.basisFunctions(uspan, u); + + int vspan = vKnots.findSpan(v); + double[] bfv = vKnots.basisFunctions(vspan, v); + + int p = uKnots.getDegree(); + int q = vKnots.getDegree(); + Vec4D[] tmp = new Vec4D[q + 1]; + for (int l = 0; l <= q; l++) { + Vec4D pw = new Vec4D(); + for (int k = 0; k <= p; k++) { + pw.addSelf(cpnet.get(uspan - p + k, vspan - q + l) + .getWeighted().scaleSelf((float) bfu[k])); + } + tmp[l] = pw; + } + Vec4D sw = new Vec4D(); + for (int l = 0; l <= q; l++) { + sw.addSelf(tmp[l].scaleSelf((float) bfv[l])); + } + return sw.unweightInto(out); + } + + public Vec4D[][][][] surfaceDerivCpts(int d, int r1, int r2, int s1, int s2) { + + Vec4D[][][][] result = new Vec4D[d + 1][d + 1][r2 - r1 + 1][s2 - s1 + 1]; + int degreeU = uKnots.getDegree(); + int degreeV = vKnots.getDegree(); + + int du = d < degreeU ? d : degreeU; + int dv = d < degreeV ? d : degreeV; + int r = r2 - r1; + int s = s2 - s1; + Vec4D[][] cps = cpnet.getControlPoints(); + Vec4D[] ucps = new Vec4D[cpnet.uLength()]; + for (int j = s1; j <= s2; j++) { + for (int idxu = 0; idxu < cpnet.uLength(); idxu++) { + ucps[idxu] = cps[idxu][j]; + } + Vec4D[][] tmp = new BasicNurbsCurve(ucps, uKnots).curveDerivCpts( + du, r1, r2); + for (int k = 0; k <= du; k++) { + final Vec4D[][] resk0 = result[k][0]; + for (int i = 0; i <= (r - k); i++) { + resk0[i][j - s1] = tmp[k][i]; + } + } + } + + for (int k = 0; k <= du; k++) { + final Vec4D[][] resk0 = result[k][0]; + for (int i = 0; i <= (r - k); i++) { + final int length = resk0[i].length; + final Vec4D[] resk0i = resk0[i]; + Vec4D[] vcps = new Vec4D[length]; + for (int idx = 0; idx < length; idx++) { + vcps[idx] = resk0i[idx]; + } + final int dd = (d - k) < dv ? (d - k) : dv; + Vec4D[][] tmp = new BasicNurbsCurve(vcps, vKnots) + .curveDerivCpts(dd, 0, s); + for (int l = 1; l <= dd; l++) { + final Vec4D[] reskli = result[k][l][i]; + final Vec4D[] tmpL = tmp[l]; + for (int j = 0; j <= (s - 1); j++) { + reskli[j] = tmpL[j]; + } + } + } + } + return result; + } + + private void validate() { + if (uKnots.length() != uKnots.getDegree() + cpnet.uLength() + 1) { + throw new IllegalArgumentException( + "Nurbs Surface has wrong Knot number in u Direction"); + } + if (vKnots.length() != vKnots.getDegree() + cpnet.vLength() + 1) { + throw new IllegalArgumentException( + "Nurbs Surface has wrong Knot number in v Direction"); + } + + } +} diff --git a/src/main/java/toxi/geom/nurbs/ControlNet.java b/src/main/java/toxi/geom/nurbs/ControlNet.java new file mode 100644 index 0000000..8141781 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/ControlNet.java @@ -0,0 +1,140 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +import toxi.geom.roVec3D; +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; + +/** + * A ControlNet for a NurbsSurface + * + * @author sg + * @version 1.0 + */ +public class ControlNet { + + private int nU, nV; + private Vec4D[][] cps; + + /** + * Create a ControlNet from the given points the two dimensional array must + * be a Matrix else an IllegalArgumentException is thrown. + * + * @param cpnet + * "Matrix" of ControlPoints + */ + public ControlNet(Vec4D[][] cpnet) throws IllegalArgumentException { + if (cpnet.length < 1) { + throw new IllegalArgumentException( + "Nurbs is not a Surface, to few ControlPoints in u Direction"); + } + if (cpnet[0].length < 1) { + throw new IllegalArgumentException( + "Nurbs is not a Surface, to few ControlPoints in v Direction"); + } + for (int i = 1; i < cpnet.length; i++) { + if (cpnet[i].length != cpnet[i - 1].length) { + throw new IllegalArgumentException( + "ControlPoint net is not a Matrix"); + } + } + + nU = cpnet.length; + nV = cpnet[0].length; + cps = cpnet; + } + + public void center(roVec3D origin) { + Vec3D centroid = computeCentroid(); + Vec3D delta = origin != null ? origin.sub(centroid) : centroid + .getInverted(); + for (int i = 0; i < nU; i++) { + for (int j = 0; j < nV; j++) { + cps[i][j].addXYZSelf(delta); + } + } + } + + public Vec3D computeCentroid() { + Vec4D centroid = new Vec4D(); + for (int i = 0; i < nU; i++) { + for (int j = 0; j < nV; j++) { + centroid.addSelf(cps[i][j]); + } + } + return centroid.scaleSelf(1f / (nU * nV)).to3D(); + } + + /** + * Get the ControlPoint at the position u,v + * + * @param u + * index in u direction + * @param v + * index in v direction + * @return The by u and v indexed ControlPoint + */ + public Vec4D get(int u, int v) { + return cps[u][v]; + } + + /** + * Get all the control points + * + * @return 2D array + */ + public Vec4D[][] getControlPoints() { + return cps; + } + + /** + * Set the ControlPoint at the position u,v + * + * @param u + * index in u direction + * @param v + * index in v direction + * @param cp + * ControlPoint to set at the indexed position + */ + public void set(int u, int v, Vec4D cp) { + cps[u][v].set(cp); + } + + /** + * Get number of ControlPoints in u direction + * + * @return number of ControlPoints in u direction + */ + public int uLength() { + return nU; + } + + /** + * Get number of ControlPoints in v direction + * + * @return number of ControlPoints in v direction + */ + public int vLength() { + return nV; + } + +} diff --git a/src/main/java/toxi/geom/nurbs/CurveCreator.java b/src/main/java/toxi/geom/nurbs/CurveCreator.java new file mode 100644 index 0000000..95f9b85 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/CurveCreator.java @@ -0,0 +1,112 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +import java.util.LinkedList; +import java.util.List; + +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; + +/** + * Convenience class to create dynamically NurbsCurves. + * + * @author sg + * @version 1.0 + */ +public class CurveCreator { + + private List<Vec4D> cps = new LinkedList<Vec4D>(); + private NurbsCurve curve = null; + + private int degree; + private int incp = 0; + + /** + * Create a new CurveCretor object which can create Curves from the given + * degree + * + * @param degree + * Degree the created NurbsCurve will have. + */ + public CurveCreator(int degree) { + this.degree = degree; + } + + /** + * Add a normal Point as ControlPoint to the CurveCreator. The newly added + * Point has automatically the weight one. + * + * @param cp + * Controlpoint to add with weight one. + * @return the actual NurbsCurve if any exists, or null otherwise (to less + * control for the given degree). + */ + public NurbsCurve addControlPoint(Vec3D cp) { + return addControlPoint(new Vec4D(cp, 1)); + } + + /** + * Add a new Controlpoint to the current CurveCreator. + * + * @param cp + * ControlPoint to add. + * @return the actual NurbsCurve if any exists, or null otherwise (to less + * control for the given degree). + */ + public NurbsCurve addControlPoint(Vec4D cp) { + cps.add(cp); + int np = cps.size(); + int tmp = degree; + if (np <= degree) { + if (incp == 0) { + incp++; + return null; + } + tmp = incp++; + } + + float[] u = new float[np + tmp + 1]; + for (int i = 0; i <= tmp; i++) { + u[u.length - 1 - i] = 1; + } + if (np > degree + 1) { + float val = 1.0f / (np - degree); + float step = val; + for (int i = degree + 1; i < u.length - 1 - degree; i++) { + u[i] = val; + val += step; + } + } + curve = new BasicNurbsCurve(cps.toArray(new Vec4D[cps.size()]), u, tmp); + return curve; + } + + /** + * Get the curve NurbsCurve of the CurveCreator + * + * @return the actual NurbsCurve if any exists, or null otherwise (to less + * control for the given degree). + */ + public NurbsCurve getCurve() { + return curve; + } + +} diff --git a/src/main/java/toxi/geom/nurbs/CurveUtils.java b/src/main/java/toxi/geom/nurbs/CurveUtils.java new file mode 100644 index 0000000..98e32f0 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/CurveUtils.java @@ -0,0 +1,240 @@ +package toxi.geom.nurbs; + +import java.util.LinkedList; + +import toxi.geom.Vec4D; +import toxi.math.MathUtils; + +/** + * @author sg + */ +public final class CurveUtils { + + private static int[] binomials(int x, int y) { + int[] bin = new int[y + 1]; + bin[0] = 1; + for (int i = 1; i <= y; i++) { + bin[i] = (bin[i - 1] * x) / i; + x--; + } + return bin; + }; + + public static NurbsCurve connectCurves(NurbsCurve curve1, NurbsCurve curve2) { + return connectCurves(new NurbsCurve[] { + curve1, curve2 + }); + } + + public static NurbsCurve connectCurves(NurbsCurve[] curves) { + if (curves.length < 2) { + throw new IllegalArgumentException("Must be at least 2 curves"); + } + int degree = curves[0].getDegree(); + LinkedList<Float> knots = new LinkedList<Float>(); + for (int i = 0; i <= degree; i++) { + knots.add(0f); + } + LinkedList<Vec4D> cps = new LinkedList<Vec4D>(); + cps.add(curves[0].getControlPoints()[0]); + for (int i = 0; i < curves.length; i++) { + float[] u = curves[i].getKnots(); + if (degree != curves[i].getDegree()) { + throw new IllegalArgumentException( + "Curves must have equal degrees"); + } + float start = knots.getLast() - u[0]; + for (int j = degree + 1; j < u.length - degree - 1; j++) { + knots.addLast(start + u[j]); + } + final float lastU = start + u[u.length - 1]; + for (int j = 0; j < degree; j++) { + knots.addLast(lastU); + } + + Vec4D[] pts = curves[i].getControlPoints(); + for (int j = 1; j < pts.length; j++) { + cps.addLast(pts[j]); + } + // TODO check start and end point equality + } + knots.addLast(knots.getLast()); + + float[] u = new float[knots.size()]; + for (int i = 0; i < u.length; i++) { + u[i] = knots.get(i); + } + return new BasicNurbsCurve(cps.toArray(new Vec4D[cps.size()]), u, + degree); + } + + public static NurbsCurve increaseDegree(NurbsCurve curve, int t) { + if (t <= 0) { + throw new IllegalArgumentException( + "New degree smaller or equal degree of curve"); + } + Vec4D[] cp = curve.getControlPoints(); + float[] u = curve.getKnots(); + int p = curve.getDegree(); + int seg = curve.getKnotVector().getNumberOfSegments() + 1; + Vec4D[] cph = new Vec4D[cp.length + t * seg]; + float[] uh = new float[u.length + t * seg]; + + float[][] bezalfs = new float[p + t + 1][p + 1]; + Vec4D[] bpts = new Vec4D[p + 1]; + Vec4D[] ebpts = new Vec4D[p + t + 1]; + Vec4D[] nextbpts = new Vec4D[p - 1]; + float[] alfs = new float[p - 1]; + + double m = u.length - 1; + int ph = p + t; + int ph2 = ph / 2; + bezalfs[0][0] = bezalfs[p + t][p] = 1; + int[] binph = binomials(ph, ph2); + int[] binp = binomials(p, p); + int[] bint = binomials(t, t); + for (int i = 1; i <= ph2; i++) { + float inv = 1f / binph[i]; + int mpi = MathUtils.min(p, i); + for (int j = MathUtils.max(0, i - t); j <= mpi; j++) { + bezalfs[i][j] = inv * binp[j] * bint[i - j]; + } + } + for (int i = ph2 + 1; i < ph; i++) { + double mpi = MathUtils.min(p, i); + for (int j = MathUtils.max(0, i - t); j <= mpi; j++) { + bezalfs[i][j] = bezalfs[ph - i][p - j]; + } + } + int mh = ph; + int kind = ph + 1; + int r = -1; + int a = p; + int b = p + 1; + int cind = 1; + float ua = u[0]; + cph[0] = new Vec4D(cp[0]); + for (int i = 0; i <= ph; i++) { + uh[i] = ua; + } + for (int i = 0; i <= p; i++) { + bpts[i] = new Vec4D(cp[i]); + } + + while (b < m) { + int i = b; + while (b < m && u[b] == u[b + 1]) { + b++; + } + int mul = b - i + 1; + mh += mul + t; + float ub = u[b]; + int oldr = r; + r = p - mul; + int lbz = 1; + if (oldr > 0) { + lbz = (oldr + 2) / 2; + } + int rbz = ph; + if (r > 0) { + rbz = ph - (r + 1) / 2; + float numer = ub - ua; + for (int k = p; k > mul; k--) { + alfs[k - mul - 1] = numer / (u[a + k] - ua); + } + for (int j = 1; j <= r; j++) { + int save = r - j; + int s = mul + j; + for (int k = p; k >= s; k--) { + bpts[k].interpolateToSelf(bpts[k - 1], 1 - alfs[k - s]); + } + nextbpts[save] = bpts[p]; + } + } + for (i = lbz; i <= ph; i++) { + ebpts[i] = new Vec4D(); + int mpi = MathUtils.min(p, i); + for (int j = MathUtils.max(0, i - t); j <= mpi; j++) { + ebpts[i].addScaledSelf(bpts[j], bezalfs[i][j]); + } + } + if (oldr > 1) { + int first = kind - 2; + int last = kind; + float den = ua - ub; + for (int tr = 1; tr < oldr; tr++) { + i = first; + int j = last; + int kj = j - kind + 1; + while (j - i > tr) { + if (i < cind) { + float alf = (ub - uh[i]) / (ua - uh[i]); + cph[i].interpolateToSelf(cph[i - 1], 1 - alf); + } + if (j >= lbz) { + if (j - tr <= kind - ph + oldr) { + float gam = (ub - uh[j - tr]) / den; + ebpts[kj].interpolateToSelf(ebpts[kj + 1], + 1 - gam); + } else { + float bet = (ub - uh[kind - 1]) / den; + ebpts[kj].interpolateToSelf(ebpts[kj + 1], + 1 - bet); + } + } + i++; + j--; + kj--; + } + first--; + last++; + } + } + if (a != p) { + for (i = 0; i < ph - oldr; i++) { + uh[kind] = ua; + kind++; + } + } + for (int j = lbz; j <= rbz; j++) { + cph[cind] = new Vec4D(ebpts[j]); + cind++; + } + if (b < m) { + for (int j = 0; j < r; j++) { + bpts[j].set(nextbpts[j]); + } + for (int j = r; j <= p; j++) { + bpts[j].set(cp[b - p + j]); + } + a = b; + b++; + ua = ub; + } else { + for (i = 0; i <= ph; i++) { + uh[kind + i] = ub; + } + } + } + int nh = mh - ph - 1; + float[] uNew = new float[mh + 1]; + for (int i = 0; i < uNew.length; i++) { + uNew[i] = uh[i]; + } + Vec4D[] cpNew = new Vec4D[nh + 1]; + for (int i = 0; i < cpNew.length; i++) { + cpNew[i] = new Vec4D(cph[i]); + } + return new BasicNurbsCurve(cpNew, uNew, p + t); + } + + private CurveUtils() { + } + + public NurbsCurve equalizeConnectCurves(NurbsCurve[] curves) { + // TODO equalize degrees and movw curves so that end of curves[i-1] is + // the same as start of curves[i]; + return connectCurves(curves); + } + +} diff --git a/src/main/java/toxi/geom/nurbs/InterpolationException.java b/src/main/java/toxi/geom/nurbs/InterpolationException.java new file mode 100644 index 0000000..12bc60d --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/InterpolationException.java @@ -0,0 +1,49 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +/** + * An InterpolationException is thrown if Nurbs could not be interpolated from + * the given points. + * + * @author sg + * @version 1.0 + */ +public class InterpolationException extends Exception { + + private static final long serialVersionUID = 1L; + + public InterpolationException() { + super(); + } + + public InterpolationException(String arg0) { + super(arg0); + } + + public InterpolationException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public InterpolationException(Throwable arg0) { + super(arg0); + } + +} diff --git a/src/main/java/toxi/geom/nurbs/KnotVector.java b/src/main/java/toxi/geom/nurbs/KnotVector.java new file mode 100644 index 0000000..de47d62 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/KnotVector.java @@ -0,0 +1,321 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +/** + * KnotVector, assembles the knots values of a NURBS and its degree. + * + * @author sg + * @version 1.0 + */ +public class KnotVector { + + private boolean isOpen; + private float knots[]; + private int degree; + private int n; + + /** + * Create a Knotvector from the given knot values of the desired degree. + * + * @param knots + * knot values + * @param degree + * degree of Nurbs + */ + public KnotVector(float knots[], int degree) + throws IllegalArgumentException { + this.knots = knots; + this.degree = degree; + n = knots.length - degree - 2; + for (int i = 1; i < knots.length; i++) { + if (knots[i - 1] > knots[i]) { + throw new IllegalArgumentException("Knots not valid knot[" + + (i - 1) + "] > knot[" + i + "]: knot[" + (i - 1) + + "]=" + knots[i - 1] + " > knot[" + i + "]=" + + knots[i]); + } + } + + int m = knots.length - 1; + + // Check if it is an open knot vector + isOpen = true; + for (int k = 0; k < degree && isOpen; k++) { + if (knots[k] != knots[k + 1]) { + isOpen = false; + } + } + for (int k = m; k > m - degree && isOpen; k--) { + if (knots[k] != knots[k - 1]) { + isOpen = false; + } + } + + } + + /** + * Gets the basis function values for the given u value. This function + * calculates firstly the span which is needed in order to calculate the + * basis functions values. + * + * @param u + * Value to calculate basis functions for. + * @return basis function values + */ + public double[] basisFunctions(float u) { + return basisFunctions(findSpan(u), u); + } + + /** + * Calculates the basis function values for the given u value, when it's + * already known in which span u lies. + * + * @param span + * The span u lies in + * @param u + * Value to calculate basis functions for. + * @return basis function values + */ + public double[] basisFunctions(int span, float u) { + final int d1 = degree + 1; + double res[] = new double[d1]; + double left[] = new double[d1]; + double right[] = new double[d1]; + res[0] = 1; + for (int j = 1; j < d1; j++) { + left[j] = u - knots[span + 1 - j]; + right[j] = knots[span + j] - u; + double saved = 0; + for (int r = 0; r < j; r++) { + double tmp = res[r] / (right[r + 1] + left[j - r]); + res[r] = saved + right[r + 1] * tmp; + saved = left[j - r] * tmp; + } + res[j] = saved; + } + return res; + } + + /** + * Calculates the basis functions and its derivatives up to the given grade. + * + * @param u + * Value to calculate basis functions and derivatives for. + * @param grade + * grade to calculate derivations for. + * @return an array of basis function values or derivated basis functions + * values. The first array is the degree of dderivation in the + * second array rhe values are stored. <br> + * Example: <br> + * <code> + * float[]][] f=dersBasisFuns(0.1f, 3); + * float value=f[0][1]; //In value is know the second value of the basis function derived 0 times stored. + * </code> + * + */ + public float[][] derivBasisFunctions(float u, int grade) { + int span = findSpan(u); + return derivBasisFunctions(span, u, grade); + } + + /** + * Calculates the basis functions and its derivatives up to the given grade. + * + * @param span + * Span the given value lies in. + * @param u + * Value to calculate basis functions and derivatives for. + * @param grade + * grade to calculate derivations for. + * @return an array of basis function values or derivated basis functions + * values + * @see KnotVector#derivBasisFunctions(float, int) + */ + public float[][] derivBasisFunctions(int span, float u, int grade) { + float[][] ders = new float[grade + 1][degree + 1]; + float[][] ndu = new float[degree + 1][degree + 1]; + ndu[0][0] = 1.0f; + float[] left = new float[degree + 1]; + float[] right = new float[degree + 1]; + int j1, j2; + for (int j = 1; j <= degree; j++) { + left[j] = u - knots[span + 1 - j]; + right[j] = knots[span + j] - u; + float saved = 0.0f; + for (int r = 0; r < j; r++) { + ndu[j][r] = right[r + 1] + left[j - r]; + float temp = ndu[r][j - 1] / ndu[j][r]; + ndu[r][j] = saved + right[r + 1] * temp; + saved = left[j - r] * temp; + } + ndu[j][j] = saved; + } + for (int j = 0; j <= degree; j++) { + ders[0][j] = ndu[j][degree]; + } + for (int r = 0; r <= degree; r++) { + int s1 = 0; + int s2 = 1; + float[][] a = new float[2][degree + 1]; + a[0][0] = 1.0f; + for (int k = 1; k <= grade; k++) { + float d = 0.0f; + final int rk = r - k; + final int pk = degree - k; + final float[] as1 = a[s1]; + final float[] as2 = a[s2]; + if (r >= k) { + as2[0] = d = as1[0] / ndu[pk + 1][rk]; + d *= ndu[rk][pk]; + } + if (rk >= -1) { + j1 = 1; + } else { + j1 = -rk; + } + if (r - 1 <= pk) { + j2 = k - 1; + } else { + j2 = degree - r; + } + for (int j = j1; j <= j2; j++) { + as2[j] = (as1[j] - as1[j - 1]) / ndu[pk + 1][rk + j]; + d += as2[j] * ndu[rk + j][pk]; + } + if (r <= pk) { + as2[k] = -as1[k - 1] / ndu[pk + 1][r]; + d += as2[k] * ndu[r][pk]; + } + ders[k][r] = d; + int j = s1; + s1 = s2; + s2 = j; + } + } + int r = degree; + for (int k = 1; k <= grade; k++) { + for (int j = 0; j <= degree; j++) { + ders[k][j] *= r; + } + r *= (degree - k); + } + return ders; + } + + /** + * Finds the span (Position of corresponding knot values in knot vector) a + * given value belongs to. + * + * @param u + * value to find span for + * @return Position of span. + */ + public int findSpan(float u) { + if (u >= knots[n + 1]) { + return n; + } + int low = degree; + int high = n + 1; + int mid = (low + high) / 2; + while ((u < knots[mid] || u >= knots[mid + 1]) && low < high) { + if (u < knots[mid]) { + high = mid; + } else { + low = mid; + } + mid = (low + high) / 2; + } + return mid; + } + + /** + * Get the knot value at a specific index. + * + * @param i + * Index to get knot value for + * @return the knot value + */ + public float get(int i) { + return knots[i]; + } + + /** + * get the knot values as float array + * + * @return the knot values + */ + public float[] getArray() { + return knots; + } + + /** + * Get the degree of the KnotVector + * + * @return Degree of the Knotvector + */ + public int getDegree() { + return degree; + } + + /** + * Return the nu + * + * @return Length of the KnotVector + */ + public int getN() { + return n; + } + + public int getNumberOfSegments() { + int seg = 0; + float u = knots[0]; + for (int i = 1; i < knots.length; i++) { + if (u != knots[i]) { + seg++; + u = knots[i]; + } + } + return seg; + } + + public synchronized boolean isOpen() { + return isOpen; + } + + public int length() { + return knots.length; + } + + /** + * Set the knot value at a specific index. After this operation a call to + * isValid may be needed if one is not sure if the KnotVector with the + * changed value is valid for a Nurbs. + * + * @param i + * Index to set knot value + * @param val + * value to set the knot too + */ + public void set(int i, float val) { + knots[i] = val; + } + +} diff --git a/src/main/java/toxi/geom/nurbs/NurbsCreator.java b/src/main/java/toxi/geom/nurbs/NurbsCreator.java new file mode 100644 index 0000000..c44f092 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/NurbsCreator.java @@ -0,0 +1,815 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +import toxi.geom.Axis3D; +import toxi.geom.GMatrix; +import toxi.geom.GVector; +import toxi.geom.Origin3D; +import toxi.geom.roVec3D; +import toxi.geom.SingularMatrixException; +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; +import toxi.math.MathUtils; + +/** + * This class offers some static methods to create NurbsSurfaces and NurbsCurves + * from diffrent inputs. + * + * @author sg + * @version 1.0 + */ +public final class NurbsCreator { + + private static KnotVector averaging(final float[] uk, final int p) { + int m = uk.length + p; + int n = uk.length - 1; + float ip = 1f / p; + float[] u = new float[m + 1]; + for (int i = 0; i <= p; i++) { + u[m - i] = 1; + } + for (int j = 1; j <= n - p; j++) { + float sum = 0; + for (int i = j; i <= j + p - 1; i++) { + sum += uk[i]; + } + u[j + p] = sum * ip; + } + return new KnotVector(u, p); + } + + private static float[] centripetal(Vec3D[] points) { + int n = points.length - 1; + float d = 0; + float[] uk = new float[n + 1]; + uk[n] = 1; + double[] tmp = new double[n]; + for (int k = 1; k <= n; k++) { + tmp[k - 1] = Math.sqrt(points[k].distanceTo(points[k - 1])); + d += tmp[k - 1]; + } + d = 1f / d; + for (int i = 1; i < n; i++) { + uk[i] = uk[i - 1] + (float) (tmp[i - 1] * d); + } + return uk; + } + + /** + * Create an Arc. + * + * @param o + * Origin to creat arc around + * @param r + * Radius of the arc. + * @param thetaStart + * Start angle of the arc in radians + * @param thetaEnd + * End angle of the arc in radians. If end angle is smaller than + * start angle, the end angle is increased by 2*PI. + * @return A NurbsCurve for the Arc. + */ + public static NurbsCurve createArc(Origin3D o, float r, float thetaStart, + float thetaEnd) { + Vec3D tmp = new Vec3D(); + + if (thetaEnd < thetaStart) { + thetaEnd += MathUtils.TWO_PI; + } + double theta = thetaEnd - thetaStart; + + int narcs = 4; + if (theta <= MathUtils.HALF_PI) { + narcs = 1; + } else if (theta <= MathUtils.PI) { + narcs = 2; + } else if (theta <= MathUtils.THREE_HALVES_PI) { + narcs = 3; + } + double dtheta = theta / narcs; + int n = 2 * narcs; + double w1 = Math.cos(dtheta / 2); + + final float sinStart = (float) Math.sin(thetaStart); + final float cosStart = (float) Math.cos(thetaStart); + tmp.set(o.xAxis).scaleSelf(r * cosStart); + Vec3D p0 = new Vec3D(o.origin).addSelf(tmp); + tmp.set(o.yAxis).scaleSelf(r * sinStart); + p0.addSelf(tmp); + + tmp.set(o.yAxis).scaleSelf(cosStart); + Vec3D t0 = new Vec3D(o.xAxis).scaleSelf(-sinStart).addSelf(tmp); + + Vec4D[] cps = new Vec4D[n + 1]; + cps[0] = new Vec4D(p0, 1); + int index = 0; + double angle = thetaStart; + + Vec3D p1 = new Vec3D(); + Vec3D p2 = new Vec3D(); + Vec3D t2 = new Vec3D(); + for (int i = 1; i <= narcs; i++) { + angle += dtheta; + final double sin = Math.sin(angle); + final double cos = Math.cos(angle); + + tmp.set(o.xAxis).scaleSelf((float) (r * cos)); + p2.set(o.origin).addSelf(tmp); + tmp.set(o.yAxis).scaleSelf((float) (r * sin)); + p2.addSelf(tmp); + + cps[index + 2] = new Vec4D(p2, 1); + + t2.set(o.xAxis).scaleSelf((float) -sin); + tmp.set(o.yAxis).scaleSelf((float) cos); + t2.addSelf(tmp); + + lineIntersect3D(p0, t0, p2, t2, p1, p1); + + cps[index + 1] = new Vec4D(p1, (float) w1); + index += 2; + if (i < narcs) { + p0.set(p2); + t0.set(t2); + } + } + int j = n + 1; + float[] uKnot = new float[j + 3]; + for (int i = 0; i < 3; i++) { + uKnot[i + j] = 1; + } + switch (narcs) { + case 2: + uKnot[3] = 0.5f; + uKnot[4] = 0.5f; + break; + case 3: + uKnot[3] = uKnot[4] = MathUtils.THIRD; + uKnot[5] = uKnot[6] = 2 * MathUtils.THIRD; + break; + case 4: + uKnot[3] = 0.25f; + uKnot[4] = 0.25f; + uKnot[5] = 0.5f; + uKnot[6] = 0.5f; + uKnot[7] = 0.75f; + uKnot[8] = 0.75f; + break; + } + + return new BasicNurbsCurve(cps, uKnot, 2); + } + + /** + * Create a full-circle NurbsCurve around the given Origin with radius r. + * The NurbsCurve has controlpolygon which has 7 controlpoints and the shape + * of quadrat. + * + * @param o + * Origin to create the full-circle around + * @param r + * Radius of the full-circle + * @return A NurbsCurve for a full-circle + */ + public static NurbsCurve createFullCircleQuad7(Origin3D o, float r) { + + Vec4D[] cp = new Vec4D[7]; + cp[0] = new Vec4D(o.xAxis.scale(r), 1); + cp[3] = cp[0].getInvertedXYZ(); + cp[6] = cp[0].copy(); + + cp[1] = new Vec4D(o.yAxis.add(o.xAxis).scaleSelf(r), 0.5f); + cp[4] = cp[1].getInvertedXYZ(); + + cp[2] = new Vec4D(o.xAxis.getInverted().addSelf(o.yAxis).scaleSelf(r), + 0.5f); + cp[5] = cp[2].getInvertedXYZ(); + + for (int i = 0; i < 7; i++) { + cp[i].addXYZSelf(o.origin); + } + float[] u = { + 0, 0, 0, 0.25f, 0.5f, 0.5f, 0.75f, 1, 1, 1 + }; + return new BasicNurbsCurve(cp, u, 2); + } + + /** + * Create a full-circle NurbsCurve around the given Origin with radius r. + * The NurbsCurve has controlpolygon which has 9 controlpoints and the shape + * of quadrat. + * + * @param o + * Origin to create the full-circle around + * @param r + * Radius of the full-circle + * @return A NurbsCurve for a full-circle + */ + public static NurbsCurve createFullCircleQuad9(Origin3D o, float r) { + final float w = MathUtils.SQRT2 / 2; + + Vec4D[] cp = new Vec4D[9]; + cp[0] = new Vec4D(o.xAxis.scale(r), 1); + cp[4] = cp[0].getInvertedXYZ(); + cp[8] = cp[0].copy(); + + cp[1] = new Vec4D(o.xAxis.add(o.yAxis).scaleSelf(r), w); + cp[5] = cp[1].getInvertedXYZ(); + + cp[2] = new Vec4D(o.yAxis.scale(r), 1); + cp[6] = cp[2].getInvertedXYZ(); + + cp[3] = new Vec4D(o.xAxis.getInverted().addSelf(o.yAxis).scaleSelf(r), + w); + cp[7] = cp[3].getInvertedXYZ(); + + for (int i = 0; i < 9; i++) { + cp[i].addXYZSelf(o.origin); + } + float[] u = { + 0, 0, 0, 0.25f, 0.25f, 0.5f, 0.5f, 0.75f, 0.75f, 1, 1, 1 + }; + return new BasicNurbsCurve(cp, u, 2); + } + + /** + * Create a revolved NurbsSurface from the given NurbsCurve around the given + * axis whith the angle theta. + * + * @param a + * Axis to revolve around. + * @param curve + * NurbsCurve to revolve + * @param theta + * Angle to revolve + * @return The revolved NurbsSurface + */ + // TODO:call createRevolvedSurface(Axis3D a, NurbsCurve curve, double + // thetaStart, double thetaEnd) as as it is tested + public static NurbsSurface createRevolvedSurface(Axis3D a, + NurbsCurve curve, double theta) { + int narcs = 4; + if (theta <= MathUtils.HALF_PI) { + narcs = 1; + } else if (theta <= MathUtils.PI) { + narcs = 2; + } else if (theta <= MathUtils.THREE_HALVES_PI) { + narcs = 3; + } + + int j = 3 + 2 * (narcs - 1); + final double dtheta = theta / narcs; + final float[] uKnot = new float[j + 3]; + for (int i = 0; i < 3; i++) { + uKnot[j + i] = 1; + } + switch (narcs) { + case 2: + uKnot[3] = 0.5f; + uKnot[4] = 0.5f; + break; + case 3: + uKnot[3] = uKnot[4] = MathUtils.THIRD; + uKnot[5] = uKnot[6] = 2 * MathUtils.THIRD; + break; + case 4: + uKnot[3] = 0.25f; + uKnot[4] = 0.25f; + uKnot[5] = 0.5f; + uKnot[6] = 0.5f; + uKnot[7] = 0.75f; + uKnot[8] = 0.75f; + break; + } + + double angle = 0; + final double[] cos = new double[narcs + 1]; + final double[] sin = new double[narcs + 1]; + for (int i = 0; i <= narcs; i++) { + cos[i] = Math.cos(angle); + sin[i] = Math.sin(angle); + angle += dtheta; + } + + Vec4D[] pj = curve.getControlPoints(); + Vec3D P0 = new Vec3D(); + final Vec3D P2 = new Vec3D(); + final Vec3D O = new Vec3D(); + final Vec3D T2 = new Vec3D(); + final Vec3D T0 = new Vec3D(); + final Vec3D tmp = new Vec3D(); + final Vec3D X = new Vec3D(); + final Vec3D Y = new Vec3D(); + final Vec4D[][] pij = new Vec4D[2 * narcs + 1][pj.length]; + final double wm = Math.cos(dtheta / 2); + for (j = 0; j < pj.length; j++) { + pointToLine3D(a.origin, a.dir, pj[j].to3D(), O); + X.set(pj[j].to3D().subSelf(O)); + final double r = X.magnitude(); + if (r == 0) { + X.set(O); + } + X.normalize(); + a.dir.crossInto(X, Y); + pij[0][j] = new Vec4D(pj[j]); + P0 = pj[j].to3D(); + T0.set(Y); + int index = 0; + for (int i = 1; i <= narcs; i++) { + tmp.set(X).scaleSelf((float) (r * cos[i])); + P2.set(O).addSelf(tmp); + tmp.set(Y).scaleSelf((float) (r * sin[i])); + P2.addSelf(tmp); + + pij[index + 2][j] = new Vec4D(P2, pj[j].w); + + tmp.set(Y).scaleSelf((float) cos[i]); + T2.set(X).scaleSelf((float) -sin[i]).addSelf(tmp); + + lineIntersect3D(P0, T0, P2, T2, tmp, tmp); + pij[index + 1][j] = new Vec4D(tmp, (float) (wm * pj[j].w)); + + index += 2; + if (i < narcs) { + P0.set(P2); + T0.set(T2); + } + + } + } + ControlNet cnet = new ControlNet(pij); + return new BasicNurbsSurface(cnet, uKnot, curve.getKnots(), 2, + curve.getDegree()); + } + + /** + * Create a revolved NurbsSurface from the given NurbsCurve around the given + * axis whith the angle theta. + * + * @param a + * Axis to revolve around. + * @param curve + * NurbsCurve to revolve + * @param thetaStart + * Angle to start revolution + * @param thetaEnd + * Angle to end revolution + * @return The revolved NurbsSurface + */ + public static NurbsSurface createRevolvedSurface(Axis3D a, + NurbsCurve curve, double thetaStart, double thetaEnd) { + int narcs = 4; + if (thetaStart > thetaEnd) { + double tmp = thetaEnd; + thetaEnd = thetaStart; + thetaStart = tmp; + } + double theta = thetaEnd - thetaStart; + if (theta <= MathUtils.HALF_PI) { + narcs = 1; + } else if (theta <= MathUtils.PI) { + narcs = 2; + } else if (theta <= MathUtils.THREE_HALVES_PI) { + narcs = 3; + } + + int j = 3 + 2 * (narcs - 1); + final double dtheta = theta / narcs; + final float[] uKnot = new float[j + 3]; + for (int i = 0; i < 3; i++) { + uKnot[i] = 0; + uKnot[j + i] = 1; + } + switch (narcs) { + case 2: + uKnot[3] = 0.5f; + uKnot[4] = 0.5f; + break; + case 3: + uKnot[3] = uKnot[4] = MathUtils.THIRD; + uKnot[5] = uKnot[6] = 2 * MathUtils.THIRD; + break; + case 4: + uKnot[3] = 0.25f; + uKnot[4] = 0.25f; + uKnot[5] = 0.5f; + uKnot[6] = 0.5f; + uKnot[7] = 0.75f; + uKnot[8] = 0.75f; + break; + } + + double angle = thetaStart; + final double[] cos = new double[narcs + 1]; + final double[] sin = new double[narcs + 1]; + for (int i = 0; i <= narcs; i++) { + cos[i] = Math.cos(angle); + sin[i] = Math.sin(angle); + angle += dtheta; + } + + final Vec4D[] pj = curve.getControlPoints(); + Vec3D P0 = new Vec3D(); + final Vec3D O = new Vec3D(); + final Vec3D P2 = new Vec3D(); + final Vec3D T2 = new Vec3D(); + final Vec3D T0 = new Vec3D(); + final Vec3D tmp = new Vec3D(); + final Vec3D X = new Vec3D(); + final Vec3D Y = new Vec3D(); + final Vec4D[][] pij = new Vec4D[2 * narcs + 1][pj.length]; + final double wm = Math.cos(dtheta / 2); + for (j = 0; j < pj.length; j++) { + pointToLine3D(a.origin, a.dir, pj[j].to3D(), O); + X.set(pj[j].to3D().subSelf(O)); + final double r = X.magnitude(); + if (r == 0) { + X.set(O); + } + X.normalize(); + a.dir.crossInto(X, Y); + pij[0][j] = new Vec4D(pj[j]); + P0 = pj[j].to3D(); + T0.set(Y); + int index = 0; + for (int i = 1; i <= narcs; i++) { + tmp.set(X).scaleSelf((float) (r * cos[i])); + P2.set(O).addSelf(tmp); + tmp.set(Y).scaleSelf((float) (r * sin[i])); + P2.addSelf(tmp); + + pij[index + 2][j] = new Vec4D(P2, pj[j].w); + + tmp.set(Y).scaleSelf((float) cos[i]); + T2.set(X).scaleSelf((float) -sin[i]).addSelf(tmp); + + lineIntersect3D(P0, T0, P2, T2, tmp, tmp); + pij[index + 1][j] = new Vec4D(tmp, (float) (wm * pj[j].w)); + + index += 2; + if (i < narcs) { + P0.set(P2); + T0.set(T2); + } + } + } + ControlNet cnet = new ControlNet(pij); + return new BasicNurbsSurface(cnet, uKnot, curve.getKnots(), 2, + curve.getDegree()); + } + + /** + * Create a semi-circle NurbsCurve around the given Origin with radius r. + * + * @param o + * Origin to create semi-circle around. + * @param r + * Radius of the semi-circle + * @return A NurbsCurve for a semi-circle + */ + public static NurbsCurve createSemiCircle(Origin3D o, float r) { + Vec4D[] cp = new Vec4D[4]; + cp[0] = new Vec4D(o.xAxis.scale(r), 1); + cp[3] = cp[0].getInvertedXYZ(); + cp[0].addXYZSelf(o.origin); + cp[3].addXYZSelf(o.origin); + cp[1] = new Vec4D(o.xAxis.add(o.yAxis).scaleSelf(r).addSelf(o.origin), + 0.5f); + cp[2] = new Vec4D(o.xAxis.getInverted().addSelf(o.yAxis).scaleSelf(r) + .addSelf(o.origin), 0.5f); + + float[] u = { + 0, 0, 0, 0.5f, 1, 1, 1 + }; + return new BasicNurbsCurve(cp, u, 2); + } + + /** + * Creates a {@link NurbsSurface} by swinging a profile {@link NurbsCurve} + * in the XZ plane around a trajectory curve in the XY plane. Both curves + * MUST be offset from the major axes (i.e. their control points should have + * non-zero coordinates for the Y coordinates of the profile curve and the Z + * coordinates of the trajectory). + * + * @param proj + * profile curve in XZ + * @param traj + * trajectory curve in XY + * @param alpha + * scale factor + * @return 3D NURBS surface + */ + public static NurbsSurface createSwungSurface(NurbsCurve proj, + NurbsCurve traj, float alpha) { + Vec4D[] cpProj = proj.getControlPoints(); + Vec4D[] cpTraj = traj.getControlPoints(); + + // The NURBS Book, Piegl, p.455,456 + // http://books.google.co.uk/books?id=7dqY5dyAwWkC&pg=PA455&lpg=PA455 + // fixed Z handling (was wrong in original jgeom version) + Vec4D[][] cps = new Vec4D[cpProj.length][cpTraj.length]; + for (int i = 0; i < cpProj.length; i++) { + for (int j = 0; j < cpTraj.length; j++) { + Vec4D cp = new Vec4D(); + cp.x = cpProj[i].x * cpTraj[j].x * alpha; + cp.y = cpProj[i].y * cpTraj[j].y * alpha; + cp.z = (cpProj[i].z + cpTraj[j].z) * alpha; + cp.w = cpProj[i].w * cpTraj[j].w; + cps[i][j] = cp; + } + } + return new BasicNurbsSurface(new ControlNet(cps), proj.getKnots(), + traj.getKnots(), proj.getDegree(), traj.getDegree()); + + } + + /** + * Perform a linear extrusion of the given {@link NurbsCurve} along the + * supplied vector to produce a new {@link NurbsSurface}. The extrusion + * length is the length of the vector given. + * + * @param curve + * NURBS curve instance + * @param extrude + * a extrusion vector + * @return a NurbsSurface. + */ + public static NurbsSurface extrudeCurve(NurbsCurve curve, Vec3D extrude) { + + // Curve and Surface Construction using Rational B-splines + // Piegl and Tiller CAD Vol 19 #9 November 1987 pp 485-498 + KnotVector vKnot = new KnotVector(new float[] { + 0f, 0f, 1f, 1f + }, 1); + + Vec4D[][] cpoints = new Vec4D[curve.getControlPoints().length][2]; + Vec4D[] curvePoints = curve.getControlPoints(); + for (int i = 0; i < cpoints.length; i++) { + for (int j = 0; j < 2; j++) { + /* + * Change added 11/02/90 Steve Larkin : Have multiplied the term + * wcoord to the extrusion vector before adding to the curve + * coordinates. Not really sure this is the correct fix, but it + * works ! + */ + Vec4D cp = new Vec4D(); + cp.x = curvePoints[i].x + j * extrude.x; + cp.y = curvePoints[i].y + j * extrude.y; + cp.z = curvePoints[i].z + j * extrude.z; + cp.w = curvePoints[i].w; + cpoints[i][j] = cp; + } + } + ControlNet cnet = new ControlNet(cpoints); + return new BasicNurbsSurface(cnet, curve.getKnots(), vKnot.getArray(), + curve.getDegree(), vKnot.getDegree()); + } + + /** + * Interpolates a NurbCurve form the given Points using a global + * interpolation technique. + * + * @param points + * Points to interpolate + * @param degree + * degree of the interpolated NurbsCurve + * @return A NurbsCurve interpolating the given Points + * @throws InterpolationException + * thrown if interpolation failed or is not possible. + */ + public static NurbsCurve globalCurveInterpolation(Vec3D[] points, int degree) + throws InterpolationException { + try { + final int n = points.length; + final double[] A = new double[n * n]; + + final float[] uk = centripetal(points); + KnotVector uKnots = averaging(uk, degree); + for (int i = 0; i < n; i++) { + int span = uKnots.findSpan(uk[i]); + double[] tmp = uKnots.basisFunctions(span, uk[i]); + System.arraycopy(tmp, 0, A, i * n + span - degree, tmp.length); + } + final GMatrix a = new GMatrix(n, n, A); + final GVector perm = new GVector(n); + final GMatrix lu = new GMatrix(n, n); + a.computeLUD(lu, perm); + + final Vec4D[] cps = new Vec4D[n]; + for (int i = 0; i < cps.length; i++) { + cps[i] = new Vec4D(0, 0, 0, 1); + } + + // x-ccordinate + final GVector b = new GVector(n); + for (int j = 0; j < n; j++) { + b.setElement(j, points[j].x); + } + final GVector sol = new GVector(n); + sol.backSolveLUD(lu, b, perm); + for (int j = 0; j < n; j++) { + cps[j].x = (float) sol.get(j); + } + + // y-ccordinate + for (int j = 0; j < n; j++) { + b.setElement(j, points[j].y); + } + sol.zero(); + sol.backSolveLUD(lu, b, perm); + for (int j = 0; j < n; j++) { + cps[j].y = (float) sol.get(j); + } + + // z-ccordinate + for (int j = 0; j < n; j++) { + b.setElement(j, points[j].z); + } + sol.zero(); + sol.backSolveLUD(lu, b, perm); + for (int j = 0; j < n; j++) { + cps[j].z = (float) sol.get(j); + } + return new BasicNurbsCurve(cps, uKnots); + } catch (SingularMatrixException ex) { + throw new InterpolationException(ex); + } + + } + + /** + * Interpolates a NurbsSurface from the given points using a gloabl + * interpolation technique. + * + * @param points + * Points arranged in a net (matrix) to interpolate + * @param uDegrees + * degree in u direction + * @param vDegrees + * degree in v direction + * @return A NurbsSurface interpolating the given points. + * @throws InterpolationException + * thrown if interpolation failed or is not possible. + */ + public static NurbsSurface globalSurfaceInterpolation(Vec3D[][] points, + int uDegrees, int vDegrees) throws InterpolationException { + final int n = points.length; + final int m = points[0].length; + float[][] uv = surfaceMeshParameters(points, n - 1, m - 1); + KnotVector u = averaging(uv[0], uDegrees); + KnotVector v = averaging(uv[1], vDegrees); + + Vec4D[][] r = new Vec4D[m][n]; + Vec3D[] tmp = new Vec3D[n]; + for (int l = 0; l < m; l++) { + for (int i = 0; i < n; i++) { + tmp[i] = points[i][l]; + } + try { + NurbsCurve curve = globalCurveInterpolation(tmp, uDegrees); + r[l] = curve.getControlPoints(); + } catch (InterpolationException ex) { + for (int i = 0; i < tmp.length; i++) { + r[l][i] = new Vec4D(tmp[i], 1); + } + } + + } + + Vec4D[][] cp = new Vec4D[n][m]; + tmp = new Vec3D[m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + tmp[j] = r[j][i].to3D(); + } + try { + NurbsCurve curve = globalCurveInterpolation(tmp, vDegrees); + cp[i] = curve.getControlPoints(); + } catch (InterpolationException ex) { + for (int j = 0; j < tmp.length; j++) { + cp[i][j] = new Vec4D(tmp[j], 1); + } + } + } + + return new BasicNurbsSurface(new ControlNet(cp), u, v); + } + + private static void lineIntersect3D(Vec3D p0, Vec3D t0, Vec3D p2, Vec3D t2, + Vec3D out0, Vec3D out2) { + Vec3D v02 = p0.sub(p2); + + double a = t0.dot(t0); + double b = t0.dot(t2); + double c = t2.dot(t2); + double d = t0.dot(v02); + double e = t2.dot(v02); + double denom = a * c - b * b; + + double mu0, mu2; + + if (denom < MathUtils.EPS) { + mu0 = 0; + mu2 = b > c ? d / b : e / c; + } else { + mu0 = (b * e - c * d) / denom; + mu2 = (a * e - b * d) / denom; + } + + out0.set(t0.scale((float) mu0).addSelf(p0)); + out2.set(t2.scale((float) mu2).addSelf(p2)); + } + + private static void pointToLine3D(roVec3D p, roVec3D t, + Vec3D top, Vec3D out) { + Vec3D dir = top.sub(p); + float hyp = dir.magnitude(); + out.set(p.add(t.scale(t.dot(dir.normalize()) * hyp))); + } + + private static float[][] surfaceMeshParameters(Vec3D points[][], int n, + int m) { + final float[][] res = new float[2][]; + int num = m + 1; + final float[] cds = new float[(n + 1) * (m + 1)]; + final float[] uk = new float[n + 1]; + uk[n] = 1; + for (int l = 0; l <= m; l++) { + float total = 0; + for (int k = 1; k <= n; k++) { + cds[k] = points[k][l].distanceTo(points[k - 1][l]); + total += cds[k]; + } + if (total == 0) { + num = num - 1; + } else { + float d = 0; + total = 1f / total; + for (int k = 1; k <= n; k++) { + d += cds[k]; + uk[k] += d * total; + } + } + } + if (num == 0) { + return null; + } + float inum = 1f / num; + for (int k = 1; k < n; k++) { + uk[k] *= inum; + } + + num = n + 1; + final float[] vk = new float[m + 1]; + vk[m] = 1; + for (int l = 0; l <= n; l++) { + float total = 0; + Vec3D[] pl = points[l]; + for (int k = 1; k <= m; k++) { + cds[k] = pl[k].distanceTo(pl[k - 1]); + total += cds[k]; + } + if (total == 0) { + num = num - 1; + } else { + float d = 0; + total = 1f / total; + for (int k = 1; k <= m; k++) { + d += cds[k]; + vk[k] += d * total; + } + } + } + if (num == 0) { + return null; + } + inum = 1f / num; + for (int k = 1; k < m; k++) { + vk[k] *= inum; + } + res[0] = uk; + res[1] = vk; + return res; + } + + private NurbsCreator() { + } +} diff --git a/src/main/java/toxi/geom/nurbs/NurbsCurve.java b/src/main/java/toxi/geom/nurbs/NurbsCurve.java new file mode 100644 index 0000000..72b6db3 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/NurbsCurve.java @@ -0,0 +1,98 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +import toxi.geom.Polygon2D; +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; +import toxi.geom.XYZ; + +/** + * Interface for Nurbs Curves + * + * @author sg + * @version 1.0 + */ +public interface NurbsCurve { + + /** + * Computes control points of dth derivative<br /> + * Piegel, L. The Nurbs Book<br /> + * Algorithm A3.3 -> Page 98<br /> + * <br /> + * + * @param d + * - dth derivative<br /> + * @param r1 + * - from control point (0 for all control points)<br /> + * @param r2 + * - to control point (n for all control points)<br /> + * @return Vec4D[k][i] kth derivative, ith control point + */ + public Vec4D[][] curveDerivCpts(int d, int r1, int r2); + + public abstract XYZ[] derivativesOnCurve(float u, int d); + + public abstract XYZ[] derivativesOnCurve(float u, int d, XYZ[] ders); + + /** + * Get the ControlPoints of this curve + * + * @return the ordered ControlPoints + */ + Vec4D[] getControlPoints(); + + /** + * Get the Degree of the curve + * + * @return degree of curve + */ + int getDegree(); + + /** + * Gets the Knot values of the Nurbs curve + * + * @return knot values + */ + float[] getKnots(); + + KnotVector getKnotVector(); + + /** + * Calculate point on surface for the given u value + * + * @param u + * value to calculate point of + * @return calculated Point + */ + Vec3D pointOnCurve(float u); + + /** + * Calculate point on surface for the given u value + * + * @param u + * value to calculate point of + * @param out + * Point to place result in + */ + Vec3D pointOnCurve(float u, Vec3D out); + + Polygon2D toPolygon2D(int res); +} diff --git a/src/main/java/toxi/geom/nurbs/NurbsMeshCreator.java b/src/main/java/toxi/geom/nurbs/NurbsMeshCreator.java new file mode 100644 index 0000000..9ec5c9d --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/NurbsMeshCreator.java @@ -0,0 +1,103 @@ +//package toxi.geom.nurbs; +// +//import toxi.geom.Vec2D; +//import toxi.geom.Vec3D; +//import toxi.geom.mesh.Mesh3D; +//import toxi.geom.mesh.TriangleMesh; +// +//public class NurbsMeshCreator { +// +// private NurbsSurface surf; +// private Vec2D maxUV; +// +// public NurbsMeshCreator(NurbsSurface surf) { +// this(surf, new Vec2D(1, 1)); +// } +// +// public NurbsMeshCreator(NurbsSurface surf, Vec2D maxUV) { +// this.surf = surf; +// this.maxUV = maxUV; +// } +// +// public Mesh3D createControlMesh(Mesh3D mesh) { +// Vec3D[] prev = null; +// if (mesh == null) { +// mesh = new TriangleMesh(); +// } +// int resU = surf.getControlNet().uLength(); +// int resV = surf.getControlNet().vLength(); +// Vec2D dUV = maxUV.scale(1f / resU, 1f / resV); +// for (int u = 0; u < resU; u++) { +// Vec3D[] curr = new Vec3D[resV + 1]; +// for (int v = 0; v < resV; v++) { +// Vec3D vert = surf.getControlNet().get(u, v).to3D(); +// if (v > 0 && u > 0) { +// mesh.addFace(curr[v - 1], vert, prev[v - 1], +// dUV.scale(u, v - 1), dUV.scale(u, v), +// dUV.scale(u - 1, v - 1)); +// mesh.addFace(vert, prev[v], prev[v - 1], dUV.scale(u, v), +// dUV.scale(u - 1, v), dUV.scale(u - 1, v - 1)); +// } +// curr[v] = vert; +// } +// prev = curr; +// } +// mesh.computeVertexNormals(); +// return mesh; +// } +// +// public Mesh3D createMesh(Mesh3D mesh, int resU, int resV, boolean isClosed) { +// final KnotVector knotU = surf.getUKnotVector(); +// final KnotVector knotV = surf.getVKnotVector(); +// double iresU = knotU.get(knotU.length() - 1) / resU; +// double iresV = knotV.get(knotV.length() - 1) / resV; +// Vec3D[] prev = null; +// Vec3D[] first = null; +// if (mesh == null) { +// mesh = new TriangleMesh(); +// } +// Vec2D dUV = maxUV.scale(1f / resU, 1f / resV); +// for (int u = 0; u <= resU; u++) { +// Vec3D[] curr = new Vec3D[resV + 1]; +// for (int v = 0; v <= resV; v++) { +// Vec3D vert = null; +// if (isClosed) { +// vert = u < resU ? surf.pointOnSurface(u * iresU, v * iresV) +// : first[v]; +// } else { +// vert = surf.pointOnSurface(u * iresU, v * iresV); +// } +// if (v > 0 && u > 0) { +// mesh.addFace(curr[v - 1], vert, prev[v - 1], +// dUV.scale(u, v - 1), dUV.scale(u, v), +// dUV.scale(u - 1, v - 1)); +// mesh.addFace(vert, prev[v], prev[v - 1], dUV.scale(u, v), +// dUV.scale(u - 1, v), dUV.scale(u - 1, v - 1)); +// } +// curr[v] = vert; +// } +// prev = curr; +// if (u == 0) { +// first = curr; +// } +// } +// mesh.computeVertexNormals(); +// return mesh; +// } +// +// public NurbsSurface getSurface() { +// return surf; +// } +// +// public Vec2D getUVScale() { +// return maxUV; +// } +// +// public void setSurface(NurbsSurface surf) { +// this.surf = surf; +// } +// +// public void setUVScale(Vec2D maxUV) { +// this.maxUV = maxUV; +// } +//} diff --git a/src/main/java/toxi/geom/nurbs/NurbsSurface.java b/src/main/java/toxi/geom/nurbs/NurbsSurface.java new file mode 100644 index 0000000..54ac235 --- /dev/null +++ b/src/main/java/toxi/geom/nurbs/NurbsSurface.java @@ -0,0 +1,133 @@ +/* + * jgeom: Geometry Library fo Java + * + * Copyright (C) 2005 Samuel Gerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package toxi.geom.nurbs; + +import toxi.geom.Vec3D; +import toxi.geom.Vec4D; +import toxi.geom.XYZ; + +/** + * Interface for Nurbs Surfaces. + * + * @author sg + * @version 1.2 + */ +public interface NurbsSurface { + + /** + * Get the Contol points of the NurbsSurface + * + * @return ControlNet of the Nurbs + */ + ControlNet getControlNet(); + + /** + * Get a List of all TrimCurves asociated with this Nurbs Surface + * + * @return List of TrimCurves + */ + // List<TrimCurve> getTrimCurves(); + + /** + * Get the degree in u direction + * + * @return degree in u direction + */ + int getUDegree(); + + /** + * Get the knot values in u direction + * + * @return knot values in u direction + */ + float[] getUKnots(); + + public abstract KnotVector getUKnotVector(); + + /** + * Get the degree in v direction + * + * @return degree in v direction + */ + int getVDegree(); + + /** + * Get the knot values in v direction + * + * @return knot values in v direction + */ + float[] getVKnots(); + + public abstract KnotVector getVKnotVector(); + + public abstract Vec3D pointOnSurface(double u, double v); + + /** + * Calculate point on surface for the given u and v values + * + * @param u + * u value to caculate point from + * @param v + * v value to caculate point from + * @return calculated point + */ + XYZ pointOnSurface(float u, float v); + + /** + * Add a TrimCurve to this Nurbs Surface + * + * @param tc + * TrimCurve to add. + */ + // void addTrimCurve(TrimCurve tc); + + /** + * Calculate point on surface for the given u and v values + * + * @param u + * u value to caculate point from + * @param v + * v value to caculate point from + * @param out + * point to place result in. + */ + Vec3D pointOnSurface(float u, float v, Vec3D out); + + /** + * Computes control points of dth derivative<br /> + * Piegel, L. The Nurbs Book, Algorithm A3.7 -> Page 114<br /> + * + * @param d + * - dth derivative (0<=k+l<=d)<br /> + * @param r1 + * - from control point (u direction; 0 for all control points)<br /> + * @param r2 + * - to control point (u direction; n for all control points)<br /> + * @param s1 + * - from control point (v direction; 0 for all control points)<br /> + * @param s2 + * - to control point (v direction; n for all control points)<br /> + * @return ControlPoint4f[k][l][i][j] i,jth control point, differentiated k + * times<br /> + * with respect to u and l times with respect to v + */ + public Vec4D[][][][] surfaceDerivCpts(int d, int r1, int r2, int s1, int s2); + +} diff --git a/src/main/java/com/syncleus/spangraph/geom/ReadonlyVec3D.java b/src/main/java/toxi/geom/roVec3D.java similarity index 76% rename from src/main/java/com/syncleus/spangraph/geom/ReadonlyVec3D.java rename to src/main/java/toxi/geom/roVec3D.java index 60c46bb..ebe2f31 100644 --- a/src/main/java/com/syncleus/spangraph/geom/ReadonlyVec3D.java +++ b/src/main/java/toxi/geom/roVec3D.java @@ -25,14 +25,17 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.geom; +import toxi.geom.Vec3D.Axis; +import toxi.math.MathUtils; +import toxi.math.ScaleMap; /** * Readonly, immutable interface wrapper for Vec3D instances. Used throughout * the library for safety purposes. */ -public interface ReadonlyVec3D { +public interface roVec3D extends XYZ { /** * Adds vector {a,b,c} and returns result as new vector. @@ -48,7 +51,7 @@ public interface ReadonlyVec3D { */ public Vec3D add(float a, float b, float c); - public Vec3D add(ReadonlyVec3D v); + public Vec3D add(roVec3D v); /** * Add vector v and returns result as new vector. @@ -63,14 +66,14 @@ public interface ReadonlyVec3D { /** * 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)} + * alternative implementation {@link #angleBetween(roVec3D, boolean)} * * @param v * vector * * @return angle in radians, or NaN if vectors are parallel */ - public float angleBetween(ReadonlyVec3D v); + public float angleBetween(XYZ v); /** * Computes the angle between this vector and vector V. @@ -84,7 +87,7 @@ public interface ReadonlyVec3D { * * @return angle in radians, or NaN if vectors are parallel */ - public float angleBetween(ReadonlyVec3D v, boolean forceNormalize); + public float angleBetween(XYZ v, boolean forceNormalize); /** * Compares the length of the vector with another one. @@ -94,7 +97,7 @@ public interface ReadonlyVec3D { * * @return -1 if other vector is longer, 0 if both are equal or else +1 */ - public int compareTo(ReadonlyVec3D v); + public int compareTo(roVec3D v); /** * Copy. @@ -106,13 +109,13 @@ public interface ReadonlyVec3D { /** * 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 Vec3D cross(ReadonlyVec3D v); + public Vec3D cross(XYZ v); /** * Calculates cross-product with vector v. The resulting vector is @@ -126,29 +129,8 @@ public interface ReadonlyVec3D { * * @return result vector */ - public Vec3D crossInto(ReadonlyVec3D v, Vec3D result); + public Vec3D crossInto(XYZ v, Vec3D result); - /** - * Calculates distance to another vector. - * - * @param v - * non-null vector - * - * @return distance or Float.NaN if v=null - */ - public float distanceTo(ReadonlyVec3D v); - - /** - * Calculates the squared distance to another vector. - * - * @param v - * non-null vector - * - * @return distance or NaN if v=null - * - * @see #magSquared() - */ - public float distanceToSquared(ReadonlyVec3D v); /** * Computes the scalar product (dot product) with the given vector. @@ -161,7 +143,7 @@ public interface ReadonlyVec3D { * @see <a href="http://en.wikipedia.org/wiki/Dot_product">Wikipedia * entry</a> */ - public float dot(ReadonlyVec3D v); + public float dot(XYZ v); /* * (non-Javadoc) @@ -182,7 +164,7 @@ public interface ReadonlyVec3D { * * @return true, if equal */ - public boolean equalsWithTolerance(ReadonlyVec3D v, float tolerance); + public boolean equalsWithTolerance(roVec3D v, float tolerance); /** * Gets the abs. @@ -196,7 +178,7 @@ public interface ReadonlyVec3D { * * @return new vector */ - public Vec3D getCartesian(); + public XYZ getCartesian(); public Axis getClosestAxis(); @@ -212,7 +194,7 @@ public interface ReadonlyVec3D { * * @return fitted vector */ - public Vec3D getConstrained(AABB box); + public XYZ getConstrained(AABB box); /** * Creates a new vector whose components are the integer value of their @@ -220,7 +202,7 @@ public interface ReadonlyVec3D { * * @return result as new vector */ - public Vec3D getFloored(); + public XYZ getFloored(); /** * Creates a new vector whose components are the fractional part of their @@ -228,7 +210,7 @@ public interface ReadonlyVec3D { * * @return result as new vector */ - public Vec3D getFrac(); + public XYZ getFrac(); /** * Scales vector uniformly by factor -1 ( v = -v ). @@ -246,7 +228,7 @@ public interface ReadonlyVec3D { * * @return result as new vector */ - public Vec3D getLimited(float lim); + public XYZ getLimited(float lim); /** * Produces a new vector with its coordinates passed through the given @@ -255,14 +237,8 @@ public interface ReadonlyVec3D { * @param map * @return mapped vector */ - //public Vec3D getMapped(ScaleMap map); + public XYZ getMapped(ScaleMap map); - /** - * Produces the normalized version as a new vector. - * - * @return new vector - */ - public Vec3D getNormalized(); /** * Produces a new vector normalized to the given length. @@ -279,9 +255,9 @@ public interface ReadonlyVec3D { * * @return new vector */ - public Vec3D getReciprocal(); + public XYZ getReciprocal(); - public Vec3D getReflected(ReadonlyVec3D normal); + public XYZ getReflected(roVec3D normal); /** * Gets the rotated around axis. @@ -293,7 +269,7 @@ public interface ReadonlyVec3D { * * @return new result vector */ - public Vec3D getRotatedAroundAxis(ReadonlyVec3D axis, float theta); + public XYZ getRotatedAroundAxis(roVec3D axis, float theta); /** * Creates a new vector rotated by the given angle around the X axis. @@ -303,7 +279,7 @@ public interface ReadonlyVec3D { * * @return rotated vector */ - public Vec3D getRotatedX(float theta); + public XYZ getRotatedX(float theta); /** * Creates a new vector rotated by the given angle around the Y axis. @@ -313,7 +289,7 @@ public interface ReadonlyVec3D { * * @return rotated vector */ - public Vec3D getRotatedY(float theta); + public XYZ getRotatedY(float theta); /** * Creates a new vector rotated by the given angle around the Z axis. @@ -323,7 +299,7 @@ public interface ReadonlyVec3D { * * @return rotated vector */ - public Vec3D getRotatedZ(float theta); + public XYZ getRotatedZ(float theta); /** * Creates a new vector with its coordinates rounded to the given precision @@ -332,7 +308,7 @@ public interface ReadonlyVec3D { * @param prec * @return grid aligned vector */ - public Vec3D getRoundedTo(float prec); + public XYZ getRoundedTo(float prec); /** * Creates a new vector in which all components are replaced with the signum @@ -354,7 +330,7 @@ public interface ReadonlyVec3D { * * @return new vector */ - public Vec3D getSpherical(); + public XYZ getSpherical(); /** * Computes the vector's direction in the XY plane (for example for 2D @@ -380,33 +356,6 @@ public interface ReadonlyVec3D { */ public float 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 Vec3D interpolateTo(ReadonlyVec3D v, float 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 Vec3D interpolateTo(ReadonlyVec3D v, float f, InterpolateStrategy s); /** * Checks if the point is inside the given AABB. @@ -416,7 +365,7 @@ public interface ReadonlyVec3D { * * @return true, if point is inside */ - public boolean isInAABB(AABB box); + /** * Checks if the point is inside the given axis-aligned bounding box. @@ -464,29 +413,9 @@ public interface ReadonlyVec3D { */ public float magSquared(); - /** - * Scales vector uniformly and returns result as new vector. - * - * @param s - * scale factor - * - * @return new vector - */ - public Vec3D scale(float 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 Vec3D scale(float a, float b, float c); + + /** * Scales vector non-uniformly by vector v and returns result as new vector. @@ -496,7 +425,7 @@ public interface ReadonlyVec3D { * * @return new vector */ - public Vec3D scale(ReadonlyVec3D s); + public XYZ scale(roVec3D s); /** * Subtracts vector {a,b,c} and returns result as new vector. @@ -510,7 +439,7 @@ public interface ReadonlyVec3D { * * @return result as new vector */ - public Vec3D sub(float a, float b, float c); + public XYZ sub(float a, float b, float c); /** * Subtracts vector v and returns result as new vector. @@ -520,7 +449,7 @@ public interface ReadonlyVec3D { * * @return result as new vector */ - public Vec3D sub(ReadonlyVec3D v); + public Vec3D sub(roVec3D v); /** * Creates a new 2D vector of the XY components. @@ -563,9 +492,4 @@ public interface ReadonlyVec3D { public float[] toArray4(float w); - public float x(); - - public float y(); - - public float z(); } \ No newline at end of file diff --git a/src/main/java/toxi/math/BezierInterpolation.java b/src/main/java/toxi/math/BezierInterpolation.java new file mode 100644 index 0000000..c133c39 --- /dev/null +++ b/src/main/java/toxi/math/BezierInterpolation.java @@ -0,0 +1,83 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +/** + * Bezier curve interpolation with configurable coefficients. The curve + * parameters need to be normalized offsets relative to the start and end values + * passed to the {@link #interpolate(float, float, float)} method, but can + * exceed the normal 0 .. 1.0 interval. Use symmetrical offsets to create a + * symmetrical curve, e.g. this will create a curve with 2 dips reaching the + * minimum and maximum values at 25% and 75% of the interval... + * + * <p> + * <code>BezierInterpolation b=new BezierInterpolation(3,-3);</code> + * </p> + * + * The curve will be a straight line with this configuration: + * + * <p> + * <code>BezierInterpolation b=new BezierInterpolation(1f/3,-1f/3);</code> + * </p> + */ +public class BezierInterpolation implements InterpolateStrategy { + + public float c1; + public float c2; + + public BezierInterpolation(float h1, float h2) { + this.c1 = h1; + this.c2 = h2; + } + + public double interpolate(double a, double b, double t) { + double tSquared = t * t; + double invT = 1.0f - t; + double invTSquared = invT * invT; + return (a * invTSquared * invT) + + (3 * (c1 * (b - a) + a) * t * invTSquared) + + (3 * (c2 * (b - a) + b) * tSquared * invT) + + (b * tSquared * t); + } + + public float interpolate(float a, float b, float t) { + float tSquared = t * t; + float invT = 1.0f - t; + float invTSquared = invT * invT; + return (a * invTSquared * invT) + + (3 * (c1 * (b - a) + a) * t * invTSquared) + + (3 * (c2 * (b - a) + b) * tSquared * invT) + + (b * tSquared * t); + } + + public void setCoefficients(float a, float b) { + c1 = a; + c2 = b; + } + +} diff --git a/src/main/java/toxi/math/CircularInterpolation.java b/src/main/java/toxi/math/CircularInterpolation.java new file mode 100644 index 0000000..b4cf6b2 --- /dev/null +++ b/src/main/java/toxi/math/CircularInterpolation.java @@ -0,0 +1,76 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +/** + * Implementation of the circular interpolation function. + * + * i = a-(b-a) * (sqrt(1 - (1 - f) * (1 - f) )) + */ +public class CircularInterpolation implements InterpolateStrategy { + + protected boolean isFlipped; + + public CircularInterpolation() { + this(false); + } + + /** + * The interpolation slope can be flipped to have its steepest ascent + * towards the end value, rather than at the beginning in the default + * configuration. + * + * @param isFlipped + * true, if slope is inverted + */ + public CircularInterpolation(boolean isFlipped) { + this.isFlipped = isFlipped; + } + + public double interpolate(double a, double b, double f) { + if (isFlipped) { + return a - (b - a) * (Math.sqrt(1 - f * f) - 1); + } else { + f = 1 - f; + return a + (b - a) * (Math.sqrt(1 - f * f)); + } + } + + public float interpolate(float a, float b, float f) { + if (isFlipped) { + return a - (b - a) * ((float) Math.sqrt(1 - f * f) - 1); + } else { + f = 1 - f; + return a + (b - a) * ((float) Math.sqrt(1 - f * f)); + } + } + + public void setFlipped(boolean isFlipped) { + this.isFlipped = isFlipped; + } +} diff --git a/src/main/java/toxi/math/CosineInterpolation.java b/src/main/java/toxi/math/CosineInterpolation.java new file mode 100644 index 0000000..a071fd1 --- /dev/null +++ b/src/main/java/toxi/math/CosineInterpolation.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.math; + +/** + * Implementation of the cosine interpolation function: + * + * i = b+(a-b)*(0.5+0.5*cos(f*PI)) + */ +public class CosineInterpolation implements InterpolateStrategy { + + public double interpolate(double a, double b, double f) { + return b + (a - b) * (0.5 + 0.5 * Math.cos(f * Math.PI)); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.InterpolateStrategy#interpolate(float, float, float) + */ + public final float interpolate(float a, float b, float f) { + return b + (a - b) * (float) (0.5 + 0.5 * Math.cos(f * MathUtils.PI)); + } + +} \ No newline at end of file diff --git a/src/main/java/toxi/math/DecimatedInterpolation.java b/src/main/java/toxi/math/DecimatedInterpolation.java new file mode 100644 index 0000000..b15229d --- /dev/null +++ b/src/main/java/toxi/math/DecimatedInterpolation.java @@ -0,0 +1,60 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +/** + * Delivers a number of decimated/stepped values for a given interval. E.g. by + * using 5 steps the interpolation factor is decimated to: 0, 20, 40, 60, 80 and + * 100%. By default {@link LinearInterpolation} is used, however any other + * {@link InterpolateStrategy} can be specified via the constructor. + */ +public class DecimatedInterpolation implements InterpolateStrategy { + + public int numSteps; + public InterpolateStrategy strategy; + + public DecimatedInterpolation(int steps) { + this(steps, new LinearInterpolation()); + } + + public DecimatedInterpolation(int steps, InterpolateStrategy strategy) { + this.numSteps = steps; + this.strategy = strategy; + } + + public double interpolate(double a, double b, double f) { + double fd = (int) (f * numSteps) / (double) numSteps; + return strategy.interpolate(a, b, fd); + } + + public float interpolate(float a, float b, float f) { + float fd = (int) (f * numSteps) / (float) numSteps; + return strategy.interpolate(a, b, fd); + } + +} diff --git a/src/main/java/toxi/math/ExponentialInterpolation.java b/src/main/java/toxi/math/ExponentialInterpolation.java new file mode 100644 index 0000000..cf41eeb --- /dev/null +++ b/src/main/java/toxi/math/ExponentialInterpolation.java @@ -0,0 +1,66 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +/** + * Exponential curve interpolation with adjustable exponent. Use exp in the + * following ranges to achieve these effects: + * <ul> + * <li>0.0 < x < 1.0 : ease in (steep changes towards b)</li> + * <li>1.0 : same as {@link LinearInterpolation}</li> + * <li>> 1.0 : ease-out (steep changes from a)</li> + * </ul> + */ +public class ExponentialInterpolation implements InterpolateStrategy { + + private float exponent; + + /** + * Default constructor uses square parabola (exp=2) + */ + public ExponentialInterpolation() { + this(2); + } + + /** + * @param exp + * curve exponent + */ + public ExponentialInterpolation(float exp) { + this.exponent = exp; + } + + public double interpolate(double a, double b, double f) { + return a + (b - a) * Math.pow(f, exponent); + } + + public float interpolate(float a, float b, float f) { + return a + (b - a) * (float) Math.pow(f, exponent); + } + +} diff --git a/src/main/java/toxi/math/InterpolateStrategy.java b/src/main/java/toxi/math/InterpolateStrategy.java new file mode 100644 index 0000000..1ffdc0b --- /dev/null +++ b/src/main/java/toxi/math/InterpolateStrategy.java @@ -0,0 +1,60 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +/** + * Defines a generic function to interpolate 2 float values. + */ +public interface InterpolateStrategy { + + /** + * Implements an interpolation equation using double precision values. + * + * @param a + * current value + * @param b + * target value + * @param f + * normalized interpolation factor (0.0 .. 1.0) + * @return interpolated value + */ + public double interpolate(double a, double b, double f); + + /** + * Implements an interpolation equation using float values. + * + * @param a + * current value + * @param b + * target value + * @param f + * normalized interpolation factor (0.0 .. 1.0) + * @return interpolated value + */ + public float interpolate(float a, float b, float f); +} diff --git a/src/main/java/toxi/math/Interpolation2D.java b/src/main/java/toxi/math/Interpolation2D.java new file mode 100644 index 0000000..9d81f7d --- /dev/null +++ b/src/main/java/toxi/math/Interpolation2D.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.math; + +import toxi.geom.Vec2D; + +/** + * Implementations of 2D interpolation functions (currently only bilinear). + */ +public class Interpolation2D { + + /** + * @param x + * x coord of point to filter + * @param y + * y coord of point to filter + * @param x1 + * x coord of top-left corner + * @param y1 + * y coord of top-left corner + * @param x2 + * x coord of bottom-right corner + * @param y2 + * y coord of bottom-right corner + * @param tl + * top-left value + * @param tr + * top-right value + * @param bl + * bottom-left value + * @param br + * bottom-right value + * @return interpolated value + */ + public static float bilinear(double x, double y, double x1, double y1, + double x2, double y2, float tl, float tr, float bl, float br) { + double denom = 1.0 / ((x2 - x1) * (y2 - y1)); + double dx1 = (x - x1) * denom; + double dx2 = (x2 - x) * denom; + double dy1 = y - y1; + double dy2 = y2 - y; + return (float) (tl * dx2 * dy2 + tr * dx1 * dy2 + bl * dx2 * dy1 + br + * dx1 * dy1); + } + + /** + * @param p + * point to filter + * @param p1 + * top-left corner + * @param p2 + * bottom-right corner + * @param tl + * top-left value + * @param tr + * top-right value + * @param bl + * bottom-left value + * @param br + * bottom-right value + * @return interpolated value + */ + public static float bilinear(Vec2D p, Vec2D p1, Vec2D p2, float tl, + float tr, float bl, float br) { + return bilinear(p.x, p.y, p1.x, p1.y, p2.x, p2.y, tl, tr, bl, br); + } + +} diff --git a/src/main/java/toxi/math/LinearInterpolation.java b/src/main/java/toxi/math/LinearInterpolation.java new file mode 100644 index 0000000..665a19d --- /dev/null +++ b/src/main/java/toxi/math/LinearInterpolation.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.math; + +/** + * Implementation of the linear interpolation function + * + * i = a + ( b - a ) * f + */ +public class LinearInterpolation implements InterpolateStrategy { + + public double interpolate(double a, double b, double f) { + return a + (b - a) * f; + } + + public final float interpolate(float a, float b, float f) { + return a + (b - a) * f; + } +} diff --git a/src/main/java/com/syncleus/spangraph/geom/MathUtils.java b/src/main/java/toxi/math/MathUtils.java similarity index 99% rename from src/main/java/com/syncleus/spangraph/geom/MathUtils.java rename to src/main/java/toxi/math/MathUtils.java index 63d02c8..ee12397 100644 --- a/src/main/java/com/syncleus/spangraph/geom/MathUtils.java +++ b/src/main/java/toxi/math/MathUtils.java @@ -25,7 +25,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ -package com.syncleus.spangraph.geom; +package toxi.math; import java.util.Random; diff --git a/src/main/java/toxi/math/NonLinearScaleMap.java b/src/main/java/toxi/math/NonLinearScaleMap.java new file mode 100644 index 0000000..b93e8aa --- /dev/null +++ b/src/main/java/toxi/math/NonLinearScaleMap.java @@ -0,0 +1,60 @@ +package toxi.math; + +import java.util.NavigableSet; +import java.util.SortedSet; +import java.util.TreeSet; + +public class NonLinearScaleMap { + + public class Sample implements Comparable<Sample> { + + public final double x, y; + + public Sample(double x, double y) { + this.x = x; + this.y = y; + } + + public int compareTo(Sample b) { + return (int) Math.signum(x - b.x); + } + } + + private TreeSet<Sample> samples; + + private double rangeMin = Float.MAX_VALUE; + private double rangeMax = Float.MIN_VALUE; + + public NonLinearScaleMap() { + samples = new TreeSet<Sample>(); + } + + public NonLinearScaleMap addSample(double x, double y) { + samples.add(new Sample(x, y)); + rangeMin = MathUtils.min(y, rangeMin); + rangeMax = MathUtils.max(y, rangeMax); + return this; + } + + public NavigableSet<Sample> getSamples() { + return samples; + } + + public double map(double x) { + Sample t = new Sample(x, 0); + SortedSet<Sample> aset = samples.headSet(t); + SortedSet<Sample> bset = samples.tailSet(t); + if (aset.isEmpty()) { + return bset.first().y; + } else { + if (bset.isEmpty()) { + return aset.last().y; + } else { + Sample a = aset.last(); + Sample b = bset.first(); + return MathUtils.lerp(a.y, b.y, (x - a.x) / (b.x - a.x)); + } + } + } + +} diff --git a/src/main/java/toxi/math/ScaleMap.java b/src/main/java/toxi/math/ScaleMap.java new file mode 100644 index 0000000..e858c08 --- /dev/null +++ b/src/main/java/toxi/math/ScaleMap.java @@ -0,0 +1,167 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +import toxi.util.datatypes.DoubleRange; + +/** + * This class maps values from one interval into another. By default the mapping + * is using linear projection, but can be changed by using alternative + * {@link toxi.math.InterpolateStrategy} implementations to achieve a + * non-regular mapping. + */ +public class ScaleMap { + + /** + * The actual mapping behaviour function. + * + * @see toxi.math.InterpolateStrategy + */ + protected InterpolateStrategy mapFunction = new LinearInterpolation(); + + protected double interval; + protected double mapRange; + + protected DoubleRange in, out; + + /** + * Creates a new instance to map values between the 2 number ranges + * specified. By default linear projection is used. + * + * @param minIn + * @param maxIn + * @param minOut + * @param maxOut + */ + public ScaleMap(double minIn, double maxIn, double minOut, double maxOut) { + setInputRange(minIn, maxIn); + setOutputRange(minOut, maxOut); + } + + /** + * Computes mapped value in the target interval and ensures the input value + * is clipped to source interval. + * + * @param val + * @return mapped value + */ + public double getClippedValueFor(double val) { + double t = MathUtils.clipNormalized((val - in.min) / interval); + if (Double.isNaN(t)) { + t = 0; + } + return mapFunction.interpolate(out.min, out.max, t); + } + + /** + * @return the middle value of the input range. + */ + public double getInputMedian() { + return (in.min + in.max) * 0.5; + } + + /** + * @return the in + */ + public DoubleRange getInputRange() { + return in; + } + + /** + * @return the mapped middle value of the output range. Depending on the + * mapping function used, this value might be different to the one + * returned by {@link #getOutputMedian()}. + */ + public double getMappedMedian() { + return getMappedValueFor(0.5); + } + + /** + * Computes mapped value in the target interval. Does check if input value + * is outside the input range. + * + * @param val + * @return mapped value + */ + public double getMappedValueFor(double val) { + double t = ((val - in.min) / interval); + if (Double.isNaN(t)) { + t = 0; + } + return mapFunction.interpolate(out.min, out.max, t); + } + + /** + * @return the middle value of the output range + */ + public double getOutputMedian() { + return (out.min + out.max) * 0.5; + } + + /** + * @return the output range + */ + public DoubleRange getOutputRange() { + return out; + } + + /** + * Sets new minimum & maximum values for the input range + * + * @param min + * @param max + */ + public void setInputRange(double min, double max) { + in = new DoubleRange(min, max); + interval = max - min; + } + + /** + * Overrides the mapping function used for the scale conversion. By default + * a linear mapping is used: {@link LinearInterpolation}. + * + * @param func + * interpolate strategy implementation + */ + public void setMapFunction(InterpolateStrategy func) { + mapFunction = func; + } + + /** + * Sets new minimum & maximum values for the output/target range + * + * @param min + * new min output value + * @param max + * new max output value + */ + public void setOutputRange(double min, double max) { + out = new DoubleRange(min, max); + mapRange = max - min; + } +} diff --git a/src/main/java/toxi/math/SigmoidInterpolation.java b/src/main/java/toxi/math/SigmoidInterpolation.java new file mode 100644 index 0000000..46890f6 --- /dev/null +++ b/src/main/java/toxi/math/SigmoidInterpolation.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.math; + +/** + * Implements the sigmoid interpolation function with adjustable curve sharpness + */ +public class SigmoidInterpolation implements InterpolateStrategy { + + private float sharpness; + + private float sharpPremult; + + /** + * Initializes the s-curve with default sharpness = 2 + */ + public SigmoidInterpolation() { + this(2f); + } + + public SigmoidInterpolation(float s) { + setSharpness(s); + } + + public float getSharpness() { + return sharpness; + } + + public double interpolate(double a, double b, double f) { + f = 1.0 + Math.exp(-((f * 2 - 1) * sharpPremult)); + return a + (b - a) / f; + } + + public float interpolate(float a, float b, float f) { + f = (float) (1.0 + Math.exp(-((f * 2 - 1) * sharpPremult))); + return a + (b - a) / f; + } + + private void setSharpness(float s) { + sharpness = s; + sharpPremult = 5 * s; + } +} diff --git a/src/main/java/toxi/math/SinCosLUT.java b/src/main/java/toxi/math/SinCosLUT.java new file mode 100644 index 0000000..efad551 --- /dev/null +++ b/src/main/java/toxi/math/SinCosLUT.java @@ -0,0 +1,118 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +/** + * Lookup table for fast sine & cosine computations. Tables with varying + * precisions can be created to which input angles will be rounded to. The + * sin/cos methods can be used with both positive and negative input angles as + * with the normal Math.sin()/Math.cos() versions. + */ +public final class SinCosLUT { + + /** + * default precision + */ + public static final float DEFAULT_PRECISION = 0.25f; + + private static SinCosLUT DEFAULT_INSTANCE; + + public static final SinCosLUT getDefaultInstance() { + if (DEFAULT_INSTANCE == null) { + DEFAULT_INSTANCE = new SinCosLUT(); + } + return DEFAULT_INSTANCE; + } + + /** + * Lookup table for sine values + */ + private final float[] sinLUT; + + private final float precision; + + private final int period; + private final int quadrant; + + private final float deg2rad; + private final float rad2deg; + + public SinCosLUT() { + this(DEFAULT_PRECISION); + } + + public SinCosLUT(float precision) { + this.precision = precision; + this.period = (int) (360 / precision); + this.quadrant = period >> 2; + this.deg2rad = (float) (Math.PI / 180.0) * precision; + this.rad2deg = (float) (180.0 / Math.PI) / precision; + this.sinLUT = new float[period]; + for (int i = 0; i < period; i++) { + sinLUT[i] = (float) Math.sin(i * deg2rad); + } + } + + /** + * Calculate cosine for the passed in angle in radians. + * + * @param theta + * @return cosine value for theta + */ + public final float cos(float theta) { + while (theta < 0) { + theta += MathUtils.TWO_PI; + } + return sinLUT[((int) (theta * rad2deg) + quadrant) % period]; + } + + public int getPeriod() { + return period; + } + + public float getPrecision() { + return precision; + } + + public float[] getSinLUT() { + return sinLUT; + } + + /** + * Calculates sine for the passed angle in radians. + * + * @param theta + * @return sine value for theta + */ + public final float sin(float theta) { + while (theta < 0) { + theta += MathUtils.TWO_PI; + } + return sinLUT[(int) (theta * rad2deg) % period]; + } +} diff --git a/src/main/java/toxi/math/ThresholdInterpolation.java b/src/main/java/toxi/math/ThresholdInterpolation.java new file mode 100644 index 0000000..5369717 --- /dev/null +++ b/src/main/java/toxi/math/ThresholdInterpolation.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.math; + +/** + * Defines a single step/threshold function which returns the min value for all + * factors < threshold and the max value for all others. + */ +public class ThresholdInterpolation implements InterpolateStrategy { + + public float threshold; + + public ThresholdInterpolation(float threshold) { + this.threshold = threshold; + } + + public double interpolate(double a, double b, double f) { + return f < threshold ? a : b; + } + + public float interpolate(float a, float b, float f) { + return f < threshold ? a : b; + } +} diff --git a/src/main/java/toxi/math/ZoomLensInterpolation.java b/src/main/java/toxi/math/ZoomLensInterpolation.java new file mode 100644 index 0000000..3bfa876 --- /dev/null +++ b/src/main/java/toxi/math/ZoomLensInterpolation.java @@ -0,0 +1,91 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math; + +/** + * This class provides an adjustable zoom lens to either bundle or dilate values + * around a focal point within a given interval. For a example use cases, please + * have a look at the provided ScaleMapDataViz and ZoomLens examples. + */ +public class ZoomLensInterpolation implements InterpolateStrategy { + + protected CircularInterpolation leftImpl = new CircularInterpolation(); + protected CircularInterpolation rightImpl = new CircularInterpolation(); + + protected float lensPos; + protected float lensStrength, absStrength; + + public ZoomLensInterpolation() { + this(0.5f, 1); + } + + public ZoomLensInterpolation(float lensPos, float lensStrength) { + this.lensPos = lensPos; + this.lensStrength = lensStrength; + this.absStrength = MathUtils.abs(lensStrength); + leftImpl.setFlipped(lensStrength > 0); + rightImpl.setFlipped(lensStrength < 0); + } + + public double interpolate(double min, double max, double t) { + double val = min + (max - min) * t; + if (t < lensPos) { + val += (leftImpl.interpolate(min, min + (max - min) * lensPos, t + / lensPos) - val) + * absStrength; + } else { + val += (rightImpl.interpolate(min + (max - min) * lensPos, max, + (t - lensPos) / (1 - lensPos)) - val) * absStrength; + } + return val; + } + + public float interpolate(float min, float max, float t) { + float val = min + (max - min) * t; + if (t < lensPos) { + val += (leftImpl.interpolate(min, min + (max - min) * lensPos, t + / lensPos) - val) + * absStrength; + } else { + val += (rightImpl.interpolate(min + (max - min) * lensPos, max, + (t - lensPos) / (1 - lensPos)) - val) * absStrength; + } + return val; + } + + public void setLensPos(float pos, float smooth) { + lensPos += (MathUtils.clipNormalized(pos) - lensPos) * smooth; + } + + public void setLensStrength(float str, float smooth) { + lensStrength += (MathUtils.clip(str, -1, 1) - lensStrength) * smooth; + absStrength = MathUtils.abs(lensStrength); + leftImpl.setFlipped(lensStrength > 0); + rightImpl.setFlipped(lensStrength < 0); + } +} \ No newline at end of file diff --git a/src/main/java/toxi/math/conversion/UnitTranslator.java b/src/main/java/toxi/math/conversion/UnitTranslator.java new file mode 100644 index 0000000..13fd907 --- /dev/null +++ b/src/main/java/toxi/math/conversion/UnitTranslator.java @@ -0,0 +1,157 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.conversion; + +public class UnitTranslator { + + /** + * Number of millimeters per inch + */ + public static final double INCH_MM = 25.4; + + /** + * Number of points per inch + */ + public static final double POINT_POSTSCRIPT = 72.0; + + /** + * Converts millimeters into pixels. + * + * @param mm + * millimeters + * @param dpi + * DPI resolution + * @return number of pixels + */ + public static int millisToPixels(double mm, int dpi) { + return (int) (mm / INCH_MM * dpi); + } + + /** + * Converts millimeters into PostScript points. + * + * @param mm + * millimeters + * @return number of points + */ + public static double millisToPoints(double mm) { + return mm / INCH_MM * POINT_POSTSCRIPT; + } + + /** + * Converts pixels into inches. + * + * @param pix + * pixels + * @param dpi + * DPI resolution to use + * @return number of inches + */ + public static double pixelsToInch(int pix, int dpi) { + return (double) pix / dpi; + } + + /** + * Converts pixels into millimeters. + * + * @param pix + * pixels + * @param dpi + * DPI resolution + * @return number of millimeters + */ + public static double pixelsToMillis(int pix, int dpi) { + return pixelsToInch(pix, dpi) * INCH_MM; + } + + /** + * Converts pixels into points. + * + * @param pix + * pixels + * @param dpi + * DPI resolution + * @return number of points + */ + public static double pixelsToPoints(int pix, int dpi) { + return pixelsToInch(pix, dpi) * POINT_POSTSCRIPT; + } + + /** + * Converts points into millimeters. + * + * @param pt + * @return number of millimeters + */ + public static double pointsToMillis(double pt) { + return pt / POINT_POSTSCRIPT * INCH_MM; + } + + /** + * Converts points into pixels. + * + * @param pt + * points + * @param dpi + * DPI resolution + * @return number of pixels + */ + public static int pointsToPixels(double pt, int dpi) { + return millisToPixels(pointsToMillis(pt), dpi); + } + + /** + * Converts an area measure in square inch to square millimeters. + * + * @param area + * @return square mm + */ + public static double squareInchToMillis(double area) { + return area * INCH_MM * INCH_MM; + } + + /** + * Converts an area measure in points to square inch. + * + * @param area + * @return square inch + */ + public static double squarePointsToInch(double area) { + return area / (POINT_POSTSCRIPT * POINT_POSTSCRIPT); + } + + /** + * Converts an area measure in points to square millimeters. + * + * @param area + * @return square mm + */ + public static double squarePointsToMillis(double area) { + return squareInchToMillis(squarePointsToInch(area)); + } +} diff --git a/src/main/java/toxi/math/noise/PerlinNoise.java b/src/main/java/toxi/math/noise/PerlinNoise.java new file mode 100644 index 0000000..12f9d02 --- /dev/null +++ b/src/main/java/toxi/math/noise/PerlinNoise.java @@ -0,0 +1,216 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.noise; + +import java.util.Random; + +import toxi.math.SinCosLUT; + +/** + * PERLIN NOISE taken from the java port I originally did for PApplet, based on + * an implementation by the german demo group farbrausch as used in their demo + * "Art": http://www.farb-rausch.de/fr010src.zip + */ +public class PerlinNoise { + + protected static final int PERLIN_YWRAPB = 4; + + protected static final int PERLIN_YWRAP = 1 << PERLIN_YWRAPB; + + protected static final int PERLIN_ZWRAPB = 8; + + protected static final int PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB; + + protected static final int PERLIN_SIZE = 4095; + + private static final float PERLIN_MIN_AMPLITUDE = 0.001f; + + protected int perlin_octaves = 4; // default to medium smooth + + protected float perlin_amp_falloff = 0.5f; // 50% reduction/octave + + // [toxi 031112] + // new vars needed due to recent change of cos table in PGraphics + protected int perlin_TWOPI, perlin_PI; + + protected float[] perlin_cosTable; + + protected float perlin[]; + + protected Random perlinRandom; + + public PerlinNoise() { + noiseSeed(System.nanoTime()); + } + + /** + * Computes the Perlin noise function value at point x. + */ + public float noise(float x) { + // is this legit? it's a dumb way to do it (but repair it later) + return noise(x, 0f, 0f); + } + + /** + * Computes the Perlin noise function value at the point x, y. + */ + public float noise(float x, float y) { + return noise(x, y, 0f); + } + + /** + * Computes the Perlin noise function value at x, y, z. + */ + public float noise(float x, float y, float z) { + if (perlin == null) { + if (perlinRandom == null) { + perlinRandom = new Random(); + } + perlin = new float[PERLIN_SIZE + 1]; + for (int i = 0; i < PERLIN_SIZE + 1; i++) { + perlin[i] = perlinRandom.nextFloat(); // (float)Math.random(); + } + // [toxi 031112] + // noise broke due to recent change of cos table in PGraphics + // this will take care of it + perlin_cosTable = SinCosLUT.getDefaultInstance().getSinLUT(); + perlin_TWOPI = perlin_PI = SinCosLUT.getDefaultInstance() + .getPeriod(); + perlin_PI >>= 1; + } + + if (x < 0) { + x = -x; + } + if (y < 0) { + y = -y; + } + if (z < 0) { + z = -z; + } + + int xi = (int) x, yi = (int) y, zi = (int) z; + float xf = (x - xi); + float yf = (y - yi); + float zf = (z - zi); + float rxf, ryf; + + float r = 0; + float ampl = 0.5f; + + float n1, n2, n3; + + for (int i = 0; i < perlin_octaves; i++) { + int of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB); + + rxf = noise_fsc(xf); + ryf = noise_fsc(yf); + + n1 = perlin[of & PERLIN_SIZE]; + n1 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n1); + n2 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE]; + n2 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2); + n1 += ryf * (n2 - n1); + + of += PERLIN_ZWRAP; + n2 = perlin[of & PERLIN_SIZE]; + n2 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n2); + n3 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE]; + n3 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n3); + n2 += ryf * (n3 - n2); + + n1 += noise_fsc(zf) * (n2 - n1); + + r += n1 * ampl; + ampl *= perlin_amp_falloff; + + // break if amp has no more impact + if (ampl < PERLIN_MIN_AMPLITUDE) { + break; + } + + xi <<= 1; + xf *= 2; + yi <<= 1; + yf *= 2; + zi <<= 1; + zf *= 2; + + if (xf >= 1.0f) { + xi++; + xf--; + } + if (yf >= 1.0f) { + yi++; + yf--; + } + if (zf >= 1.0f) { + zi++; + zf--; + } + } + return r; + } + + // [toxi 031112] + // now adjusts to the size of the cosLUT used via + // the new variables, defined above + private float noise_fsc(float i) { + // using bagel's cosine table instead + return 0.5f * (1.0f - perlin_cosTable[(int) ((i + 0.5f) * perlin_PI) + % perlin_TWOPI]); + } + + // [toxi 040903] + // make perlin noise quality user controlled to allow + // for different levels of detail. lower values will produce + // smoother results as higher octaves are surpressed + + public void noiseDetail(int lod) { + if (lod > 0) { + perlin_octaves = lod; + } + } + + public void noiseDetail(int lod, float falloff) { + if (lod > 0) { + perlin_octaves = lod; + } + if (falloff > 0) { + perlin_amp_falloff = falloff; + } + } + + public void noiseSeed(long what) { + if (perlinRandom == null) { + perlinRandom = new Random(); + } + perlinRandom.setSeed(what); + perlin = null; + } +} diff --git a/src/main/java/toxi/math/noise/SimplexNoise.java b/src/main/java/toxi/math/noise/SimplexNoise.java new file mode 100644 index 0000000..25b2beb --- /dev/null +++ b/src/main/java/toxi/math/noise/SimplexNoise.java @@ -0,0 +1,542 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.noise; + +/** + * Simplex Noise in 2D, 3D and 4D. Based on the example code of this paper: + * http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf + * + * @author Stefan Gustavson, Linkping University, Sweden (stegu at itn dot liu + * dot se) + * @author Karsten Schmidt (slight optimizations & restructuring) + */ +public class SimplexNoise { + + private static final double SQRT3 = Math.sqrt(3.0); + private static final double SQRT5 = Math.sqrt(5.0); + + /** + * Skewing and unskewing factors for 2D, 3D and 4D, some of them + * pre-multiplied. + */ + private static final double F2 = 0.5 * (SQRT3 - 1.0); + private static final double G2 = (3.0 - SQRT3) / 6.0; + private static final double G22 = G2 * 2.0 - 1; + + private static final double F3 = 1.0 / 3.0; + private static final double G3 = 1.0 / 6.0; + + private static final double F4 = (SQRT5 - 1.0) / 4.0; + private static final double G4 = (5.0 - SQRT5) / 20.0; + private static final double G42 = G4 * 2.0; + private static final double G43 = G4 * 3.0; + private static final double G44 = G4 * 4.0 - 1.0; + + /** + * Gradient vectors for 3D (pointing to mid points of all edges of a unit + * cube) + */ + private static final int[][] grad3 = { { 1, 1, 0 }, { -1, 1, 0 }, + { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, + { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, + { 0, 1, -1 }, { 0, -1, -1 } }; + + /** + * Gradient vectors for 4D (pointing to mid points of all edges of a unit 4D + * hypercube) + */ + private static final int[][] grad4 = { { 0, 1, 1, 1 }, { 0, 1, 1, -1 }, + { 0, 1, -1, 1 }, { 0, 1, -1, -1 }, { 0, -1, 1, 1 }, + { 0, -1, 1, -1 }, { 0, -1, -1, 1 }, { 0, -1, -1, -1 }, + { 1, 0, 1, 1 }, { 1, 0, 1, -1 }, { 1, 0, -1, 1 }, { 1, 0, -1, -1 }, + { -1, 0, 1, 1 }, { -1, 0, 1, -1 }, { -1, 0, -1, 1 }, + { -1, 0, -1, -1 }, { 1, 1, 0, 1 }, { 1, 1, 0, -1 }, + { 1, -1, 0, 1 }, { 1, -1, 0, -1 }, { -1, 1, 0, 1 }, + { -1, 1, 0, -1 }, { -1, -1, 0, 1 }, { -1, -1, 0, -1 }, + { 1, 1, 1, 0 }, { 1, 1, -1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 }, + { -1, 1, 1, 0 }, { -1, 1, -1, 0 }, { -1, -1, 1, 0 }, + { -1, -1, -1, 0 } }; + + /** + * Permutation table + */ + private static final int[] p = { 151, 160, 137, 91, 90, 15, 131, 13, 201, + 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, + 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, + 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, + 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, + 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, + 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, + 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, + 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, + 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, + 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, + 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, + 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, + 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, + 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, + 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, + 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 }; + + /** + * To remove the need for index wrapping, double the permutation table + * length + */ + private static int[] perm = new int[0x200]; + /** + * A lookup table to traverse the simplex around a given point in 4D. + * Details can be found where this table is used, in the 4D noise method. + */ + private static final int[][] simplex = { { 0, 1, 2, 3 }, { 0, 1, 3, 2 }, + { 0, 0, 0, 0 }, { 0, 2, 3, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 1, 2, 3, 0 }, { 0, 2, 1, 3 }, { 0, 0, 0, 0 }, + { 0, 3, 1, 2 }, { 0, 3, 2, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 1, 3, 2, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 2, 0, 3 }, { 0, 0, 0, 0 }, + { 1, 3, 0, 2 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 2, 3, 0, 1 }, { 2, 3, 1, 0 }, { 1, 0, 2, 3 }, { 1, 0, 3, 2 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 2, 0, 3, 1 }, + { 0, 0, 0, 0 }, { 2, 1, 3, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 2, 0, 1, 3 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 3, 0, 1, 2 }, { 3, 0, 2, 1 }, + { 0, 0, 0, 0 }, { 3, 1, 2, 0 }, { 2, 1, 0, 3 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 3, 1, 0, 2 }, { 0, 0, 0, 0 }, + { 3, 2, 0, 1 }, { 3, 2, 1, 0 } }; + + static { + for (int i = 0; i < 0x200; i++) { + perm[i] = p[i & 0xff]; + } + } + + /** + * Computes dot product in 2D. + * + * @param g + * 2-vector (grid offset) + * @param x + * @param y + * @return dot product + */ + private static double dot(int g[], double x, double y) { + return g[0] * x + g[1] * y; + } + + /** + * Computes dot product in 3D. + * + * @param g + * 3-vector (grid offset) + * @param x + * @param y + * @param z + * @return dot product + */ + private static double dot(int g[], double x, double y, double z) { + return g[0] * x + g[1] * y + g[2] * z; + } + + /** + * Computes dot product in 4D. + * + * @param g + * 4-vector (grid offset) + * @param x + * @param y + * @param z + * @param w + * @return dot product + */ + private static double dot(int g[], double x, double y, double z, double w) { + return g[0] * x + g[1] * y + g[2] * z + g[3] * w; + } + + /** + * This method is a *lot* faster than using (int)Math.floor(x). + * + * @param x + * value to be floored + * @return + */ + private static final int fastfloor(double x) { + return x >= 0 ? (int) x : (int) x - 1; + } + + /** + * Computes 2D Simplex Noise. + * + * @param x + * coordinate + * @param y + * coordinate + * @return noise value in range -1 ... +1. + */ + public static double noise(double x, double y) { + double n0 = 0, n1 = 0, n2 = 0; // Noise contributions from the three + // corners + // Skew the input space to determine which simplex cell we're in + double s = (x + y) * F2; // Hairy factor for 2D + int i = fastfloor(x + s); + int j = fastfloor(y + s); + double t = (i + j) * G2; + double x0 = x - (i - t); // The x,y distances from the cell origin + double y0 = y - (j - t); + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) + if (x0 > y0) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed + double y1 = y0 - j1 + G2; + double x2 = x0 + G22; // Offsets for last corner in (x,y) unskewed + double y2 = y0 + G22; + // Work out the hashed gradient indices of the three simplex corners + int ii = i & 0xff; + int jj = j & 0xff; + // Calculate the contribution from the three corners + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 > 0) { + t0 *= t0; + int gi0 = perm[ii + perm[jj]] % 12; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for + // 2D gradient + } + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 > 0) { + t1 *= t1; + int gi1 = perm[ii + i1 + perm[jj + j1]] % 12; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 > 0) { + t2 *= t2; + int gi2 = perm[ii + 1 + perm[jj + 1]] % 12; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + /** + * Computes 3D Simplex Noise. + * + * @param x + * coordinate + * @param y + * coordinate + * @param z + * coordinate + * @return noise value in range -1 ... +1 + */ + public static double noise(double x, double y, double z) { + double n0 = 0, n1 = 0, n2 = 0, n3 = 0; + // Noise contributions from the + // four corners + // Skew the input space to determine which simplex cell we're in + // final double F3 = 1.0 / 3.0; + double s = (x + y + z) * F3; // Very nice and simple skew factor + // for 3D + int i = fastfloor(x + s); + int j = fastfloor(y + s); + int k = fastfloor(z + s); + // final double G3 = 1.0 / 6.0; // Very nice and simple unskew factor, + // too + double t = (i + j + k) * G3; + double x0 = x - (i - t); // The x,y,z distances from the cell origin + double y0 = y - (j - t); + double z0 = z - (k - t); + // For the 3D case, the simplex shape is a slightly irregular + // tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) + // coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } // Z X Y order + } else { // x0<y0 + if (y0 < z0) { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 0; + j2 = 1; + k2 = 1; + } // Z Y X order + else if (x0 < z0) { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 0; + j2 = 1; + k2 = 1; + } // Y Z X order + else { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // Y X Z order + } + // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), + // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), + // and + // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), + // where + // c = 1/6. + double x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords + double y1 = y0 - j1 + G3; + double z1 = z0 - k1 + G3; + + double x2 = x0 - i2 + F3; // Offsets for third corner in (x,y,z) + double y2 = y0 - j2 + F3; + double z2 = z0 - k2 + F3; + + double x3 = x0 - 0.5; // Offsets for last corner in (x,y,z) + double y3 = y0 - 0.5; + double z3 = z0 - 0.5; + // Work out the hashed gradient indices of the four simplex corners + int ii = i & 0xff; + int jj = j & 0xff; + int kk = k & 0xff; + + // Calculate the contribution from the four corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; + if (t0 > 0) { + t0 *= t0; + int gi0 = perm[ii + perm[jj + perm[kk]]] % 12; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0); + } + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + if (t1 > 0) { + t1 *= t1; + int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1); + } + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + if (t2 > 0) { + t2 *= t2; + int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2); + } + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + if (t3 > 0) { + t3 *= t3; + int gi3 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12; + n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to stay just inside [-1,1] + return 32.0 * (n0 + n1 + n2 + n3); + } + + /** + * Computes 4D Simplex Noise. + * + * @param x + * coordinate + * @param y + * coordinate + * @param z + * coordinate + * @param w + * coordinate + * @return noise value in range -1 ... +1 + */ + public static double noise(double x, double y, double z, double w) { + // The skewing and unskewing factors are hairy again for the 4D case + double n0 = 0, n1 = 0, n2 = 0, n3 = 0, n4 = 0; // Noise contributions + // from the five corners + // Skew the (x,y,z,w) space to determine which cell of 24 simplices + double s = (x + y + z + w) * F4; // Factor for 4D skewing + int i = fastfloor(x + s); + int j = fastfloor(y + s); + int k = fastfloor(z + s); + int l = fastfloor(w + s); + double t = (i + j + k + l) * G4; // Factor for 4D unskewing + double x0 = x - (i - t); // The x,y,z,w distances from the cell origin + double y0 = y - (j - t); + double z0 = z - (k - t); + double w0 = w - (l - t); + // For the 4D case, the simplex is a 4D shape I won't even try to + // describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // The method below is a good way of finding the ordering of x,y,z,w and + // then find the correct traversal order for the simplex were in. + // First, six pair-wise comparisons are performed between each possible + // pair of the four coordinates, and the results are used to add up + // binary bits for an integer index. + int c = 0; + if (x0 > y0) { + c = 0x20; + } + if (x0 > z0) { + c |= 0x10; + } + if (y0 > z0) { + c |= 0x08; + } + if (x0 > w0) { + c |= 0x04; + } + if (y0 > w0) { + c |= 0x02; + } + if (z0 > w0) { + c |= 0x01; + } + int i1, j1, k1, l1; // The integer offsets for the second simplex corner + int i2, j2, k2, l2; // The integer offsets for the third simplex corner + int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some + // order. Many values of c will never occur, since e.g. x>y>z>w makes + // x<z, y<w and x<w impossible. Only the 24 indices which have non-zero + // entries make any sense. We use a thresholding to set the coordinates + // in turn from the largest magnitude. The number 3 in the "simplex" + // array is at the position of the largest coordinate. + int[] sc = simplex[c]; + i1 = sc[0] >= 3 ? 1 : 0; + j1 = sc[1] >= 3 ? 1 : 0; + k1 = sc[2] >= 3 ? 1 : 0; + l1 = sc[3] >= 3 ? 1 : 0; + // The number 2 in the "simplex" array is at the second largest + // coordinate. + i2 = sc[0] >= 2 ? 1 : 0; + j2 = sc[1] >= 2 ? 1 : 0; + k2 = sc[2] >= 2 ? 1 : 0; + l2 = sc[3] >= 2 ? 1 : 0; + // The number 1 in the "simplex" array is at the second smallest + // coordinate. + i3 = sc[0] >= 1 ? 1 : 0; + j3 = sc[1] >= 1 ? 1 : 0; + k3 = sc[2] >= 1 ? 1 : 0; + l3 = sc[3] >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to look + // that up. + double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) + double y1 = y0 - j1 + G4; + double z1 = z0 - k1 + G4; + double w1 = w0 - l1 + G4; + + double x2 = x0 - i2 + G42; // Offsets for third corner in (x,y,z,w) + double y2 = y0 - j2 + G42; + double z2 = z0 - k2 + G42; + double w2 = w0 - l2 + G42; + + double x3 = x0 - i3 + G43; // Offsets for fourth corner in (x,y,z,w) + double y3 = y0 - j3 + G43; + double z3 = z0 - k3 + G43; + double w3 = w0 - l3 + G43; + + double x4 = x0 + G44; // Offsets for last corner in (x,y,z,w) + double y4 = y0 + G44; + double z4 = z0 + G44; + double w4 = w0 + G44; + + // Work out the hashed gradient indices of the five simplex corners + int ii = i & 0xff; + int jj = j & 0xff; + int kk = k & 0xff; + int ll = l & 0xff; + + // Calculate the contribution from the five corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 > 0) { + t0 *= t0; + int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 > 0) { + t1 *= t1; + int gi1 = perm[ii + i1 + + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 > 0) { + t2 *= t2; + int gi2 = perm[ii + i2 + + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 > 0) { + t3 *= t3; + int gi3 = perm[ii + i3 + + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 > 0) { + t4 *= t4; + int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } +} \ No newline at end of file diff --git a/src/main/java/toxi/math/waves/AMFMSineWave.java b/src/main/java/toxi/math/waves/AMFMSineWave.java new file mode 100644 index 0000000..de6a983 --- /dev/null +++ b/src/main/java/toxi/math/waves/AMFMSineWave.java @@ -0,0 +1,123 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.waves; + +/** + * <p> + * Amplitude and frequency modulated sine wave. Uses 2 secondary waves to + * modulate the shape of the main wave. + * </p> + * + * <p> + * <strong>Note:</strong> You must NEVER call the update() method on the + * modulating waves. + * </p> + */ +public class AMFMSineWave extends AbstractWave { + + public AbstractWave fmod; + public AbstractWave amod; + + /** + * Creates a new instance from + * + * @param phase + * @param freq + * @param fmod + * @param amod + */ + public AMFMSineWave(float phase, float freq, AbstractWave fmod, + AbstractWave amod) { + super(phase, freq); + this.amod = amod; + this.fmod = fmod; + } + + /** + * @param phase + * @param freq + * @param offset + * @param fmod + * @param amod + */ + public AMFMSineWave(float phase, float freq, float offset, + AbstractWave fmod, AbstractWave amod) { + super(phase, freq, 1, offset); + this.amod = amod; + this.fmod = fmod; + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#pop() + */ + @Override + public void pop() { + super.pop(); + amod.pop(); + fmod.pop(); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#push() + */ + @Override + public void push() { + super.push(); + amod.push(); + fmod.push(); + } + + /** + * Resets this wave and its modulating waves as well. + * + * @see toxi.math.waves.AbstractWave#reset() + */ + public void reset() { + super.reset(); + fmod.reset(); + amod.reset(); + } + + /** + * Progresses the wave and updates the result value. You must NEVER call the + * update() method on the 2 modulating wave since this is handled + * automatically by this method. + * + * @see toxi.math.waves.AbstractWave#update() + */ + public float update() { + amp = amod.update(); + value = amp * (float) Math.sin(phase) + offset; + cyclePhase(frequency + fmod.update()); + return value; + } +} diff --git a/src/main/java/toxi/math/waves/AbstractWave.java b/src/main/java/toxi/math/waves/AbstractWave.java new file mode 100644 index 0000000..0da01ac --- /dev/null +++ b/src/main/java/toxi/math/waves/AbstractWave.java @@ -0,0 +1,202 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.waves; + +import java.util.Stack; + +/** + * Abstract wave oscillator type which needs to be subclassed to implement + * different waveforms. Please note that the frequency unit is radians, but + * conversion methods to & from Hertz ({@link #hertzToRadians(float, float)}) + * are included in this base class. + */ +public abstract class AbstractWave { + + public static final float PI = 3.14159265358979323846f; + public static final float TWO_PI = 2 * PI; + + /** + * Converts a frequency in Hertz into radians. + * + * @param hz + * frequency to convert (in Hz) + * @param sampleRate + * sampling rate in Hz (equals period length @ 1 Hz) + * @return frequency in radians + */ + public static final float hertzToRadians(float hz, float sampleRate) { + return hz / sampleRate * TWO_PI; + } + + /** + * Converts a frequency from radians to Hertz. + * + * @param f + * frequency in radians + * @param sampleRate + * sampling rate in Hz (equals period length @ 1 Hz) + * @return freq in Hz + */ + public static final float radiansToHertz(float f, float sampleRate) { + return f / TWO_PI * sampleRate; + } + + /** + * Current wave phase + */ + public float phase; + public float frequency; + public float amp; + public float offset; + public float value; + + protected float origPhase; + protected Stack<WaveState> stateStack; + + public AbstractWave() { + } + + /** + * @param phase + */ + public AbstractWave(float phase) { + this(phase, 0, 1, 0); + } + + /** + * + * @param phase + * @param freq + */ + public AbstractWave(float phase, float freq) { + this(phase, freq, 1, 0); + } + + /** + * @param phase + * @param freq + * @param amp + * @param offset + */ + public AbstractWave(float phase, float freq, float amp, float offset) { + setPhase(phase); + this.frequency = freq; + this.amp = amp; + this.offset = offset; + } + + /** + * Ensures phase remains in the 0...TWO_PI interval. + * + * @return current phase + */ + public final float cyclePhase() { + phase %= TWO_PI; + if (phase < 0) { + phase += TWO_PI; + } + return phase; + } + + /** + * Progresses phase and ensures it remains in the 0...TWO_PI interval. + * + * @param freq + * normalized progress frequency + * @return update phase value + */ + public final float cyclePhase(float freq) { + phase = (phase + freq) % TWO_PI; + if (phase < 0) { + phase += TWO_PI; + } + return phase; + } + + public void pop() { + if (stateStack == null || (stateStack != null && stateStack.empty())) { + throw new IllegalStateException("no wave states on stack"); + } + WaveState s = stateStack.pop(); + phase = s.phase; + frequency = s.frequency; + amp = s.amp; + offset = s.offset; + } + + public void push() { + if (stateStack == null) { + stateStack = new Stack<WaveState>(); + } + stateStack.push(new WaveState(phase, frequency, amp, offset)); + } + + /** + * Resets the wave phase to the last set phase value (via + * {@link #setPhase(float)}. + */ + public void reset() { + phase = origPhase; + } + + /** + * Starts the wave from a new phase. The new phase position will also be + * used for any later call to {{@link #reset()} + * + * @param phase + * new phase + */ + public void setPhase(float phase) { + this.phase = phase; + cyclePhase(); + this.origPhase = phase; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(this.getClass().getName()).append(" phase: ").append(phase); + sb.append(" frequency: ").append(frequency); + sb.append(" amp: ").append(amp); + sb.append(" offset: ").append(offset); + return sb.toString(); + } + + /** + * Updates the wave and returns new value. Implementing classes should + * manually ensure the phase remains in the 0...TWO_PI interval or by + * calling {@link #cyclePhase()}. + * + * @return current (newly calculated) wave value + */ + public abstract float update(); +} \ No newline at end of file diff --git a/src/main/java/toxi/math/waves/ConstantWave.java b/src/main/java/toxi/math/waves/ConstantWave.java new file mode 100644 index 0000000..1d45541 --- /dev/null +++ b/src/main/java/toxi/math/waves/ConstantWave.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.math.waves; + +/** + * Implements a constant value as waveform. + */ +public class ConstantWave extends AbstractWave { + + public ConstantWave(float value) { + super(); + this.value = value; + } + + public final float update() { + return value; + } +} diff --git a/src/main/java/toxi/math/waves/FMHarmonicSquareWave.java b/src/main/java/toxi/math/waves/FMHarmonicSquareWave.java new file mode 100644 index 0000000..11ecf6c --- /dev/null +++ b/src/main/java/toxi/math/waves/FMHarmonicSquareWave.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.math.waves; + +/** + * <p> + * Frequency modulated <strong>bandwidth-limited</strong> square wave using a + * fourier series of harmonics. Also uses a secondary wave to modulate the + * frequency of the main wave. + * </p> + * + * <p> + * <strong>Note:</strong> You must NEVER call the update() method on the + * modulating wave. + * </p> + */ +public class FMHarmonicSquareWave extends AbstractWave { + + public AbstractWave fmod; + + /** + * Maximum harmonics to add (make sure you stay under Nyquist freq), default + * = 9 + */ + public int maxHarmonics = 3; + + public FMHarmonicSquareWave(float phase, float freq, AbstractWave fmod) { + super(phase, freq); + this.fmod = fmod; + } + + /** + * Convenience constructor to create a non frequency modulated square wave + * + * @param phase + * @param freq + * base frequency (in radians) + * @param amp + * @param offset + */ + public FMHarmonicSquareWave(float phase, float freq, float amp, float offset) { + this(phase, freq, amp, offset, new ConstantWave(0)); + } + + public FMHarmonicSquareWave(float phase, float freq, float amp, + float offset, AbstractWave fmod) { + super(phase, freq, amp, offset); + this.fmod = fmod; + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#pop() + */ + @Override + public void pop() { + super.pop(); + fmod.pop(); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#push() + */ + @Override + public void push() { + super.push(); + fmod.push(); + } + + /** + * Resets this wave and its modulating wave as well. + * + * @see AbstractWave#reset() + */ + public void reset() { + super.reset(); + fmod.reset(); + } + + /** + * Progresses the wave and updates the result value. You must NEVER call the + * update() method on the modulating wave since this is handled + * automatically by this method. + * + * @see AbstractWave#update() + */ + public float update() { + value = 0; + for (int i = 1; i <= maxHarmonics; i += 2) { + value += 1.0 / i * (float) Math.sin(i * phase); + } + value *= amp; + value += offset; + cyclePhase(frequency + fmod.update()); + return value; + } +} diff --git a/src/main/java/toxi/math/waves/FMSawtoothWave.java b/src/main/java/toxi/math/waves/FMSawtoothWave.java new file mode 100644 index 0000000..5291863 --- /dev/null +++ b/src/main/java/toxi/math/waves/FMSawtoothWave.java @@ -0,0 +1,114 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.waves; + +/** + * <p> + * Frequency modulated bandwidth unlimited pure sawtooth wave. Uses a secondary + * wave to modulate the frequency of the main wave. + * </p> + * + * <p> + * <strong>Note:</strong> You must NEVER call the update() method on the + * modulating wave. + * </p> + */ +public class FMSawtoothWave extends AbstractWave { + + public AbstractWave fmod; + + public FMSawtoothWave(float phase, float freq, AbstractWave fmod) { + super(phase, freq); + this.fmod = fmod; + } + + /** + * Convenience constructor to create a non frequency modulated sawtooth. + * + * @param phase + * @param freq + * base frequency (in radians) + * @param amp + * @param offset + */ + public FMSawtoothWave(float phase, float freq, float amp, float offset) { + this(phase, freq, amp, offset, new ConstantWave(0)); + } + + public FMSawtoothWave(float phase, float freq, float amp, float offset, + AbstractWave fmod) { + super(phase, freq, amp, offset); + this.fmod = fmod; + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#pop() + */ + @Override + public void pop() { + super.pop(); + fmod.pop(); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#push() + */ + @Override + public void push() { + super.push(); + fmod.push(); + } + + /** + * Resets this wave and its modulating wave as well. + * + * @see AbstractWave#reset() + */ + public void reset() { + super.reset(); + fmod.reset(); + } + + /** + * Progresses the wave and updates the result value. You must NEVER call the + * update() method on the modulating wave since this is handled + * automatically by this method. + * + * @see AbstractWave#update() + */ + public float update() { + value = ((phase / TWO_PI) * 2 - 1) * amp + offset; + cyclePhase(frequency + fmod.update()); + return value; + } + +} diff --git a/src/main/java/toxi/math/waves/FMSineWave.java b/src/main/java/toxi/math/waves/FMSineWave.java new file mode 100644 index 0000000..57267e5 --- /dev/null +++ b/src/main/java/toxi/math/waves/FMSineWave.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.math.waves; + +/** + * <p> + * Frequency modulated sine wave. Uses a secondary wave to modulate the + * frequency of the main wave. + * </p> + * + * <p> + * <strong>Note:</strong> You must NEVER call the update() method on the + * modulating wave. + * </p> + */ +public class FMSineWave extends AbstractWave { + + public AbstractWave fmod; + + public FMSineWave(float phase, float freq, AbstractWave fmod) { + super(phase, freq); + this.fmod = fmod; + } + + public FMSineWave(float phase, float freq, float amp, float offset) { + this(phase, freq, amp, offset, new ConstantWave(0)); + } + + public FMSineWave(float phase, float freq, float amp, float offset, + AbstractWave fmod) { + super(phase, freq, amp, offset); + this.fmod = fmod; + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#pop() + */ + @Override + public void pop() { + super.pop(); + fmod.pop(); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#push() + */ + @Override + public void push() { + super.push(); + fmod.push(); + } + + /** + * Resets this wave and its modulating wave as well. + * + * @see toxi.math.waves.AbstractWave#reset() + */ + public void reset() { + super.reset(); + fmod.reset(); + } + + /** + * Progresses the wave and updates the result value. You must NEVER call the + * update() method on the modulating wave since this is handled + * automatically by this method. + * + * @see toxi.math.waves.AbstractWave#update() + */ + public float update() { + value = (float) (Math.sin(phase) * amp) + offset; + cyclePhase(frequency + fmod.update()); + return value; + } + +} diff --git a/src/main/java/toxi/math/waves/FMSquareWave.java b/src/main/java/toxi/math/waves/FMSquareWave.java new file mode 100644 index 0000000..d286f79 --- /dev/null +++ b/src/main/java/toxi/math/waves/FMSquareWave.java @@ -0,0 +1,113 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.waves; + +/** + * <p> + * Frequency modulated bandwidth unlimited pure digital square wave. Uses a + * secondary wave to modulate the frequency of the main wave. + * </p> + * + * <p> + * <strong>Note:</strong> You must NEVER call the update() method on the + * modulating wave. + * </p> + */ +public class FMSquareWave extends AbstractWave { + + public AbstractWave fmod; + + public FMSquareWave(float phase, float freq, AbstractWave fmod) { + super(phase, freq); + this.fmod = fmod; + } + + /** + * Convenience constructor to create a non frequency modulated square wave + * + * @param phase + * @param freq + * base frequency (in radians) + * @param amp + * @param offset + */ + public FMSquareWave(float phase, float freq, float amp, float offset) { + this(phase, freq, amp, offset, new ConstantWave(0)); + } + + public FMSquareWave(float phase, float freq, float amp, float offset, + AbstractWave fmod) { + super(phase, freq, amp, offset); + this.fmod = fmod; + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#pop() + */ + @Override + public void pop() { + super.pop(); + fmod.pop(); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#push() + */ + @Override + public void push() { + super.push(); + fmod.push(); + } + + /** + * Resets this wave and its modulating wave as well. + * + * @see AbstractWave#reset() + */ + public void reset() { + super.reset(); + fmod.reset(); + } + + /** + * Progresses the wave and updates the result value. You must NEVER call the + * update() method on the modulating wave since this is handled + * automatically by this method. + * + * @see AbstractWave#update() + */ + public float update() { + value = (phase / TWO_PI < 0.5 ? 1 : -1) * amp + offset; + cyclePhase(frequency + fmod.update()); + return value; + } +} diff --git a/src/main/java/toxi/math/waves/FMTriangleWave.java b/src/main/java/toxi/math/waves/FMTriangleWave.java new file mode 100644 index 0000000..2b2d4aa --- /dev/null +++ b/src/main/java/toxi/math/waves/FMTriangleWave.java @@ -0,0 +1,92 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.math.waves; + +import toxi.math.MathUtils; + +/** + * Implements a frequency modulated triangular wave with its peak at PI: "/\" + */ +public class FMTriangleWave extends AbstractWave { + + public AbstractWave fmod; + + public FMTriangleWave(float phase, float freq) { + this(phase, freq, 1, 0); + } + + public FMTriangleWave(float phase, float freq, float amp, float offset) { + this(phase, freq, amp, offset, new ConstantWave(0)); + } + + public FMTriangleWave(float phase, float freq, float amp, float offset, + AbstractWave fmod) { + super(phase, freq, amp, offset); + this.fmod = fmod; + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#pop() + */ + @Override + public void pop() { + super.pop(); + fmod.pop(); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#push() + */ + @Override + public void push() { + super.push(); + fmod.push(); + } + + /** + * Resets this wave and its modulating wave as well. + * + * @see AbstractWave#reset() + */ + public void reset() { + super.reset(); + fmod.reset(); + } + + @Override + public float update() { + value = 2 * amp * (MathUtils.abs(PI - phase) * MathUtils.INV_PI - 0.5f) + + offset; + cyclePhase(frequency + fmod.update()); + return value; + } +} diff --git a/src/main/java/toxi/math/waves/SineWave.java b/src/main/java/toxi/math/waves/SineWave.java new file mode 100644 index 0000000..4d60279 --- /dev/null +++ b/src/main/java/toxi/math/waves/SineWave.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.math.waves; + +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Standard Sine wave at fixed frequency and values normalized to the given + * amplitude. + */ +@XmlRootElement +public class SineWave extends AbstractWave { + + public SineWave() { + super(); + } + + /** + * @param phase + * starting phase + * @param freq + * in radians (not Hertz) + */ + public SineWave(float phase, float freq) { + super(phase, freq); + } + + /** + * @param phase + * starting phase + * @param freq + * in radians (not Hertz) + * @param amp + * amplitude factor + * @param offset + * centre oscillation value + */ + public SineWave(float phase, float freq, float amp, float offset) { + super(phase, freq, amp, offset); + } + + /* + * (non-Javadoc) + * + * @see toxi.math.waves.AbstractWave#update() + */ + public float update() { + value = (float) (Math.sin(phase) * amp) + offset; + cyclePhase(frequency); + return value; + } +} diff --git a/src/main/java/toxi/math/waves/Wave2D.java b/src/main/java/toxi/math/waves/Wave2D.java new file mode 100644 index 0000000..75c9aa3 --- /dev/null +++ b/src/main/java/toxi/math/waves/Wave2D.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.math.waves; + +import toxi.geom.Vec2D; + +public class Wave2D { + + public AbstractWave xmod, ymod; + public Vec2D pos; + + public Wave2D(AbstractWave x, AbstractWave y) { + xmod = x; + ymod = y; + pos = new Vec2D(); + } + + public void update() { + pos.x = xmod.update(); + pos.y = ymod.update(); + } +} diff --git a/src/main/java/toxi/math/waves/WaveState.java b/src/main/java/toxi/math/waves/WaveState.java new file mode 100644 index 0000000..1d54791 --- /dev/null +++ b/src/main/java/toxi/math/waves/WaveState.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.math.waves; + +public class WaveState { + + public float phase; + public float frequency; + public float amp; + public float offset; + + public WaveState(float phase, float frequency, float amp, float offset) { + this.phase = phase; + this.frequency = frequency; + this.amp = amp; + this.offset = offset; + } +} diff --git a/src/main/java/toxi/util/DateUtils.java b/src/main/java/toxi/util/DateUtils.java new file mode 100644 index 0000000..e1c438e --- /dev/null +++ b/src/main/java/toxi/util/DateUtils.java @@ -0,0 +1,133 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * A Simple timestamp generator/formatter with timezone support. + */ +public class DateUtils { + + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + public static final SimpleDateFormat FORMAT = new SimpleDateFormat( + "yyyyMMdd-HHmmss"); + + /** + * Creates a formatted timestamp string of the current datetime using the + * local host timezone. + * + * @return timestamp + */ + public static final String timeStamp() { + return timeStamp(new Date(), null); + } + + /** + * Creates a formatted timestamp string of the given date using the local + * host timezone. + * + * @param date + * @return timestamp + */ + public static final String timeStamp(Date date) { + return timeStamp(date, null); + } + + /** + * Creates a formatted timestamp string of the given date using the + * specified timezone. + * + * @param date + * @param zone + * @return timestamp + */ + public static final String timeStamp(Date date, TimeZone zone) { + if (zone == null) { + zone = TimeZone.getDefault(); + } + FORMAT.setTimeZone(zone); + return FORMAT.format(date); + } + + /** + * Creates a formatted timestamp string of the given epoch using the local + * host timezone. + * + * @param t + * unix epoch timestamp + * @return timestamp + */ + public static final String timeStamp(long t) { + return timeStamp(new Date(t), null); + } + + /** + * Creates a formatted timestamp string of the given date using the given + * timezone ID. + * + * @see TimeZone#getTimeZone(String) + * + * @param zoneID + * @param date + * @return timestamp + */ + public static final String timeStampForZone(String zoneID, Date date) { + return timeStamp(date, TimeZone.getTimeZone(zoneID)); + } + + /** + * Creates a formatted timestamp string of the current date in GMT. + * + * @return timestamp + */ + public static final String timeStampGMT() { + return timeStamp(new Date(), GMT); + } + + /** + * Creates a formatted timestamp string of the given date in GMT. + * + * @return timestamp + */ + public static final String timeStampGMT(Date date) { + return timeStamp(date, GMT); + } + + /** + * Creates a formatted timestamp string of the given epoch in GMT. + * + * @return timestamp + */ + public static final String timeStampGMT(long t) { + return timeStamp(new Date(t), GMT); + } +} diff --git a/src/main/java/toxi/util/FileSequenceDescriptor.java b/src/main/java/toxi/util/FileSequenceDescriptor.java new file mode 100644 index 0000000..4758d24 --- /dev/null +++ b/src/main/java/toxi/util/FileSequenceDescriptor.java @@ -0,0 +1,157 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util; + +import java.io.File; +import java.util.Iterator; + +/** + * A descriptor and iterator for handling file sequences. + */ +public class FileSequenceDescriptor implements Iterable<String> { + + private class SequenceIterator implements Iterator<String> { + + private int curr; + private int end; + + public SequenceIterator(int start, int end) { + this.curr = start; + this.end = end; + } + + public boolean hasNext() { + return curr < end; + } + + public String next() { + String path = getPathForIndex(curr); + curr++; + return path; + } + + public void remove() { + throw new UnsupportedOperationException("remove() not supported"); + } + + } + + public String filePattern; + public String extension; + public int numDigits; + public int start; + public int end = -1; + + /** + * Creates a new descriptor from the given sequence details. + * + * @param filePattern + * file pattern in the format: e.g. "path/basename%d04.ext" + * @param extension + * file extension (e.g. ".tga") + * @param numDigits + * number of digits used for the index + * @param start + * start index + */ + public FileSequenceDescriptor(String filePattern, String extension, + int numDigits, int start) { + this.filePattern = filePattern; + this.extension = extension; + this.numDigits = numDigits; + this.start = start; + } + + /** + * Returns the base path of the sequence, i.e. the substring of the + * sequence's file pattern from the beginning until the first occurence of + * the % sign indicating the frame numbers. + * + * @return path string + */ + public String getBasePath() { + return filePattern.substring(0, filePattern.indexOf('%')); + } + + /** + * Calculates sequence duration + * + * @return number of files in sequence + */ + public int getDuration() { + return getFinalIndex() - start; + } + + /** + * Identifies the index of the last file of the sequence. + * + * @return final index + */ + public int getFinalIndex() { + if (end == -1) { + end = start; + while (true) { + if (!new File(getPathForIndex(end)).canRead()) { + break; + } else { + end++; + } + } + } + return end; + } + + /** + * Constructs the file path for the given absolute index + * + * @param i + * index + * @return path + */ + public String getPathForIndex(int i) { + return String.format(filePattern, i); + } + + /** + * Returns the index of the first file of the sequence. + * + * @return start index + */ + public int getStartIndex() { + return start; + } + + /** + * Creates an iterator providing paths for each file in the sequence. The + * iterator does not support the remove() method and attempts to use it + * results in an {@link UnsupportedOperationException} being thrown. + */ + public Iterator<String> iterator() { + return new SequenceIterator(start, getFinalIndex()); + } +} diff --git a/src/main/java/toxi/util/FileUtils.java b/src/main/java/toxi/util/FileUtils.java new file mode 100644 index 0000000..1bd8179 --- /dev/null +++ b/src/main/java/toxi/util/FileUtils.java @@ -0,0 +1,480 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util; + +import java.awt.FileDialog; +import java.awt.Frame; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * A collection of file handling utilities. + */ +public class FileUtils { + + /** + * {@link FileDialog} constant + */ + public static final int LOAD = FileDialog.LOAD; + + /** + * {@link FileDialog} constant + */ + public static final int SAVE = FileDialog.SAVE; + + /** + * Attempts to create the full path of directories as specified by the given + * target path. The path is assumed to be a directory, NOT a file in a + * directory. For the latter, use {@link #createDirectoriesForFile(File)}. + * + * @param path + * @return true, if the operation succeeded + */ + static public boolean createDirectories(File path) { + try { + if (!path.exists()) { + path.mkdirs(); + } + return true; + } catch (SecurityException se) { + System.err.println("No permissions to create " + + path.getAbsolutePath()); + } + return false; + } + + /** + * Attempts to create the full path of directories as specified by the given + * target file. + * + * @param file + * @return true, if the operation succeeded + */ + static public boolean createDirectoriesForFile(File file) { + try { + String parentName = file.getParent(); + if (parentName != null) { + File parent = new File(parentName); + if (!parent.exists()) { + parent.mkdirs(); + } + } + return true; + } catch (SecurityException se) { + System.err.println("No permissions to create " + + file.getAbsolutePath()); + } + return false; + } + + /** + * Creates an {@link InputStream} for the given file. If the file extension + * ends with ".gz" the stream is automatically wrapped in a + * {@link GZIPInputStream} as well. + * + * @param file + * input file + * @return input stream + * @throws IOException + */ + static public InputStream createInputStream(File file) throws IOException { + if (file == null) { + throw new IllegalArgumentException("file can't be null"); + } + InputStream stream = new FileInputStream(file); + if (file.getName().toLowerCase().endsWith(".gz")) { + stream = new GZIPInputStream(stream); + } + return stream; + } + + /** + * Creates an {@link OutputStream} for the given file. If the file extension + * ends with ".gz" the stream is automatically wrapped in a + * {@link GZIPOutputStream} as well. Also attempts to create any + * intermediate directories for the file using + * {@link #createDirectoriesForFile(File)}. + * + * @param file + * output file + * @return output stream + * @throws IOException + */ + static public OutputStream createOutputStream(File file) throws IOException { + if (file == null) { + throw new IllegalArgumentException("file can't be null"); + } + createDirectoriesForFile(file); + OutputStream stream = new FileOutputStream(file); + if (file.getName().toLowerCase().endsWith(".gz")) { + stream = new GZIPOutputStream(stream); + } + return stream; + } + + /** + * Creates a {@link BufferedReader} for the given file using UTF-8 encoding. + * + * @param file + * @return reader instance + * @throws IOException + */ + public static BufferedReader createReader(File file) throws IOException { + return createReader(createInputStream(file)); + } + + /** + * Creates a {@link BufferedReader} for the given {@link InputStream} using + * UTF-8 encoding. + * + * @param input + * stream + * @return reader instance + * @throws IOException + */ + public static BufferedReader createReader(InputStream input) { + return createReader(input, "UTF-8"); + } + + /** + * Creates a {@link BufferedReader} for the given {@link InputStream} and + * using the specified encoding. + * + * @param input + * stream + * @param encoding + * text encoding to use + * @return reader instance + * @throws IOException + */ + public static BufferedReader createReader(InputStream input, String encoding) { + InputStreamReader isr = null; + try { + isr = new InputStreamReader(input, encoding); + } catch (UnsupportedEncodingException e) { + } + return new BufferedReader(isr, 0x10000); + } + + /** + * Creates a {@link BufferedWriter} for the given file using UTF-8 encoding. + * + * @param file + * @return writer instance + * @throws IOException + */ + public static BufferedWriter createWriter(File file) throws IOException { + return createWriter(createOutputStream(file)); + } + + /** + * Creates a {@link BufferedWriter} for the given {@link OutputStream} using + * UTF-8 encoding. + * + * @param out + * @return writer instance + * @throws IOException + */ + public static BufferedWriter createWriter(OutputStream out) { + return createWriter(out, "UTF-8"); + } + + /** + * Creates a {@link BufferedWriter} for the given {@link OutputStream} and + * using the specified encoding. + * + * @param out + * stream + * @param encoding + * text encoding to use + * @return writer instance + * @throws IOException + */ + public static BufferedWriter createWriter(OutputStream out, String encoding) { + OutputStreamWriter w = null; + try { + w = new OutputStreamWriter(out, encoding); + } catch (UnsupportedEncodingException e) { + } + return new BufferedWriter(w, 0x10000); + } + + /** + * <p> + * Analyses the given file path for a file sequence pattern and returns a + * {@link FileSequenceDescriptor} instance for further use to handle this + * sequence. The file pattern should be in one of these formats: + * </p> + * <ul> + * <li>base_path-00001.ext</li> + * <li>base_path001.ext</li> + * </ul> + * <p> + * The sequence index should be using leading zeros, but the number of + * digits will be identified automatically. + * </p> + * + * @param path + * file path of the first file in the sequence + * @return descriptor, or null, if the path could not be analysed + */ + public static FileSequenceDescriptor getFileSequenceDescriptorFor( + String path) { + int dotIndex = path.lastIndexOf('.'); + int zeroIndex = path.lastIndexOf('-') + 1; + if (zeroIndex == 0) { + zeroIndex = dotIndex - 1; + while (path.charAt(zeroIndex) >= '0' + && path.charAt(zeroIndex) <= '9') { + zeroIndex--; + } + zeroIndex++; + } + int numDigits = dotIndex - zeroIndex; + if (dotIndex != -1 && numDigits > 0) { + String base = path.substring(0, zeroIndex); + String extension = path.substring(dotIndex); + String filePattern = base + "%0" + numDigits + "d" + extension; + int start = Integer.parseInt(path.substring(zeroIndex, dotIndex)); + return new FileSequenceDescriptor(filePattern, extension, dotIndex + - zeroIndex, start); + } else { + return null; + } + } + + /** + * Loads the given {@link InputStream} into a byte array buffer. + * + * @param stream + * @return byte array + * @throws IOException + */ + static public byte[] loadBytes(InputStream stream) throws IOException { + BufferedInputStream input = new BufferedInputStream(stream); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int c; + while ((c = input.read()) != -1) { + buffer.write(c); + } + return buffer.toByteArray(); + } + + /** + * Loads the given {@link BufferedReader} as text, line by line into a + * {@link String}. + * + * @param reader + * @return reader contents as string + * @throws IOException + */ + public static String loadText(BufferedReader reader) throws IOException { + StringBuilder result = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + result.append(line).append("\n"); + } + return result.toString(); + } + + /** + * Loads the given {@link InputStream} as text, line by line into a + * {@link String} using UTF-8 encoding. + * + * @param input + * stream + * @return stream contents as string + * @throws IOException + */ + public static String loadText(InputStream input) throws IOException { + return loadText(input, "UTF-8"); + } + + /** + * Loads the given {@link InputStream} as text, line by line into a + * {@link String} using the specified encoding. + * + * @param input + * stream + * @param encoding + * @return stream contents as single string + * @throws IOException + */ + public static String loadText(InputStream input, String encoding) + throws IOException { + byte[] raw = loadBytes(input); + return new String(raw, encoding); + } + + /** + * Saves the given text to the specified {@link BufferedWriter} instance, + * then closes the writer afterwards. + * + * @param writer + * @param string + * @throws IOException + */ + static public void saveText(BufferedWriter writer, String string) + throws IOException { + writer.write(string); + writer.flush(); + writer.close(); + } + + /** + * Saves the given text to the specified {@link OutputStream} instance, then + * closes the underlying writer afterwards. + * + * @param output + * @param string + * @throws IOException + */ + static public void saveText(OutputStream output, String string) + throws IOException { + saveText(createWriter(output), string); + } + + /** + * Displays a standard AWT file dialog for choosing a folder (no files!). + * + * @param frame + * parent frame + * @param title + * dialog title + * @param path + * base directory (or null) + * @return path to chosen directory or null, if user has canceled + */ + public static String showDirectoryChooser(final Frame frame, + final String title, String path) { + System.setProperty("apple.awt.fileDialogForDirectories", "true"); + String result = showFileDialog(frame, title, path, + new FilenameFilter() { + + public boolean accept(File dir, String name) { + return new File(dir + "/" + name).isDirectory(); + } + }, LOAD); + System.setProperty("apple.awt.fileDialogForDirectories", "false"); + return result; + } + + /** + * Displays a standard AWT file dialog for choosing a file for loading or + * saving. + * + * @param frame + * parent frame + * @param title + * dialog title + * @param path + * base directory (or null) + * @param filter + * a FilenameFilter implementation (or null) + * @param mode + * either FileUtils.LOAD or FileUtils.SAVE + * @return path to chosen file or null, if user has canceled + */ + public static String showFileDialog(final Frame frame, final String title, + String path, FilenameFilter filter, final int mode) { + String fileID = null; + FileDialog fd = new FileDialog(frame, title, mode); + if (path != null) { + fd.setDirectory(path); + } + if (filter != null) { + fd.setFilenameFilter(filter); + } + fd.setVisible(true); + if (fd.getFile() != null) { + fileID = fd.getFile(); + fileID = fd.getDirectory() + fileID; + } + return fileID; + } + + /** + * Displays a standard AWT file dialog for choosing a file for loading or + * saving. + * + * @param frame + * parent frame + * @param title + * dialog title + * @param path + * base directory (or null) + * @param formats + * an array of allowed file extensions (or null to allow all) + * @param mode + * either FileUtils.LOAD or FileUtils.SAVE + * @return path to chosen file or null, if user has canceled + */ + public static String showFileDialog(final Frame frame, final String title, + String path, final String[] formats, final int mode) { + String fileID = null; + FileDialog fd = new FileDialog(frame, title, mode); + if (path != null) { + fd.setDirectory(path); + } + if (formats != null) { + fd.setFilenameFilter(new FilenameFilter() { + + public boolean accept(File dir, String name) { + boolean isAccepted = false; + for (String ext : formats) { + if (name.indexOf(ext) != -1) { + isAccepted = true; + break; + } + } + return isAccepted; + } + }); + } + fd.setVisible(true); + if (fd.getFile() != null) { + fileID = fd.getFile(); + fileID = fd.getDirectory() + fileID; + } + return fileID; + } +} diff --git a/src/main/java/toxi/util/datatypes/ArraySet.java b/src/main/java/toxi/util/datatypes/ArraySet.java new file mode 100644 index 0000000..c0d2c55 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/ArraySet.java @@ -0,0 +1,121 @@ +package toxi.util.datatypes; + +/* + * 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.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * An ArrayList implementation of Set. An ArraySet is good for small sets; it + * has less overhead than a HashSet or a TreeSet. + * + * @author Paul Chew + * + * Created December 2007. For use with Voronoi/Delaunay applet. + * + */ +public class ArraySet<E> extends AbstractSet<E> { + + private ArrayList<E> items; // Items of the set + + /** + * Create an empty set (default initial capacity is 3). + */ + public ArraySet() { + this(3); + } + + /** + * Create a set containing the items of the collection. Any duplicate items + * are discarded. + * + * @param collection + * the source for the items of the small set + */ + public ArraySet(Collection<? extends E> collection) { + items = new ArrayList<E>(collection.size()); + for (E item : collection) { + if (!items.contains(item)) { + items.add(item); + } + } + } + + /** + * Create an empty set with the specified initial capacity. + * + * @param initialCapacity + * the initial capacity + */ + public ArraySet(int initialCapacity) { + items = new ArrayList<E>(initialCapacity); + } + + @Override + public boolean add(E item) { + if (items.contains(item)) { + return false; + } + return items.add(item); + } + + /** + * True if any member of the collection is also in the ArraySet. + * + * @param collection + * the Collection to check + * @return true if any member of collection appears in this ArraySet + */ + public boolean containsAny(Collection<?> collection) { + for (Object item : collection) { + if (this.contains(item)) { + return true; + } + } + return false; + } + + /** + * Get the item at the specified index. + * + * @param index + * where the item is located in the ListSet + * @return the item at the specified index + * @throws IndexOutOfBoundsException + * if the index is out of bounds + */ + public E get(int index) throws IndexOutOfBoundsException { + return items.get(index); + } + + @Override + public Iterator<E> iterator() { + return items.iterator(); + } + + @Override + public int size() { + return items.size(); + } + +} diff --git a/src/main/java/toxi/util/datatypes/ArrayUtil.java b/src/main/java/toxi/util/datatypes/ArrayUtil.java new file mode 100644 index 0000000..adfae6e --- /dev/null +++ b/src/main/java/toxi/util/datatypes/ArrayUtil.java @@ -0,0 +1,377 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Random; + +/** + * A collection of array utilities. + */ +public class ArrayUtil { + + /** + * Adds all array elements to the given collection of the same type. + * + * @param <T> + * @param array + * array + * @param collection + * existing collection or null (to create a new {@link ArrayList} + * automatically) + */ + public static <T> void addArrayToCollection(T[] array, + Collection<T> collection) { + if (collection == null) { + collection = new ArrayList<T>(); + } + for (T o : array) { + collection.add(o); + } + } + + /** + * Converts the generic array into an {@link ArrayList} of the same type. + * + * @param array + * @return array list version + */ + public static <T> ArrayList<T> arrayToList(T[] array) { + ArrayList<T> list = new ArrayList<T>(array.length); + for (T element : array) { + list.add(element); + } + return list; + } + + /** + * Creates a normalized version of the values of the given int[] array. + * Supports packed integers (e.g. ARGB data) by allowing to specify a + * bitshift amount & bitmask, e.g. do this to get the normalized + * representation of the red channel of an ARGB array: + * + * <pre> + * + * // use 16 bits as shift offset for accessing red channel + * float[] red = ArrayUtil.getAsNormalizedFloatArray(argbPixels, 16, 255, 255); + * </pre> + * + * @param source + * source data + * @param bits + * number of bits to right shift each value + * @param mask + * bitmask to apply after bitshifting + * @param peak + * peak value (in the source domain) to normalize against + * @param target + * peak of the normalized values + * @return normalized values + */ + public static float[] getAsNormalizedFloatArray(int[] source, int bits, + int mask, int peak, float target) { + float invPeak = target / peak; + float[] normalized = new float[source.length]; + for (int i = 0; i < source.length; i++) { + int val = source[i]; + if (bits > 0) { + val >>= bits; + } + val &= mask; + normalized[i] = val * invPeak; + } + return normalized; + } + + /** + * Returns the index of the element where the given value is found in the + * array. + * + * @param needle + * number to find + * @param stack + * array to search + * @param maxLen + * number of elements to search + * @return array index or -1 if value couldn't be found in array + */ + public static int indexInArray(float needle, float[] stack, int maxLen) { + for (int i = 0; i < maxLen; i++) { + if (stack[i] == needle) { + return i; + } + } + return -1; + } + + /** + * Returns the index of the element where the given value is found in the + * array. + * + * @param needle + * number to find + * @param stack + * array to search + * @param maxLen + * number of elements to search + * @return array index or -1 if value couldn't be found in array + */ + public static int indexInArray(int needle, int[] stack, int maxLen) { + for (int i = 0; i < maxLen; i++) { + if (stack[i] == needle) { + return i; + } + } + return -1; + } + + /** + * Returns the index of the element where the given value is found in the + * array. The comparison uses {@link Object#equals(Object)}. + * + * @param needle + * number to find + * @param stack + * array to search + * @param maxLen + * number of elements to search + * @return array index or -1 if value couldn't be found in array + */ + public static int indexInArray(Object needle, Object[] stack, int maxLen) { + for (int i = 0; i < maxLen; i++) { + if (stack[i].equals(needle)) { + return i; + } + } + return -1; + } + + /** + * Normalizes the values in the given array to the new absolute target + * value. The original values are overridden. + * + * @param buffer + * array + * @param peak + * current peak in the source domain + * @param target + * new peak in the target domain + * @return normalized array + */ + public static float[] normalizeFloatArray(float[] buffer, float peak, + float target) { + float invPeak = target / peak; + for (int i = 0; i < buffer.length; i++) { + buffer[i] *= invPeak; + } + return buffer; + } + + /** + * Reverses the item order of the supplied byte array. + * + * @param array + */ + public static void reverse(byte[] array) { + int len = array.length - 1; + int len2 = array.length / 2; + for (int i = 0; i < len2; i++) { + byte tmp = array[i]; + array[i] = array[len - i]; + array[len - i] = tmp; + } + } + + /** + * Reverses the item order of the supplied char array. + * + * @param array + */ + public static void reverse(char[] array) { + int len = array.length - 1; + int len2 = array.length / 2; + for (int i = 0; i < len2; i++) { + char tmp = array[i]; + array[i] = array[len - i]; + array[len - i] = tmp; + } + } + + /** + * Reverses the item order of the supplied float array. + * + * @param array + */ + public static void reverse(float[] array) { + int len = array.length - 1; + int len2 = array.length / 2; + for (int i = 0; i < len2; i++) { + float tmp = array[i]; + array[i] = array[len - i]; + array[len - i] = tmp; + } + } + + /** + * Reverses the item order of the supplied int array. + * + * @param array + */ + public static void reverse(int[] array) { + int len = array.length - 1; + int len2 = array.length / 2; + for (int i = 0; i < len2; i++) { + int tmp = array[i]; + array[i] = array[len - i]; + array[len - i] = tmp; + } + } + + /** + * Reverses the item order of the supplied short array. + * + * @param array + */ + public static void reverse(short[] array) { + int len = array.length - 1; + int len2 = array.length / 2; + for (int i = 0; i < len2; i++) { + short tmp = array[i]; + array[i] = array[len - i]; + array[len - i] = tmp; + } + } + + /** + * Reverses the item order of the supplied array (generic types). + * + * @param array + */ + public static <T> void reverse(T[] array) { + int len = array.length - 1; + int len2 = array.length / 2; + for (int i = 0; i < len2; i++) { + T tmp = array[i]; + array[i] = array[len - i]; + array[len - i] = tmp; + } + } + + /** + * Rearranges the array items in random order using the default + * java.util.Random generator. Operation is in-place, no copy is created. + * + * @param array + */ + public static <T> void shuffle(T[] array) { + shuffle(array, new Random()); + } + + /** + * Rearranges the array items in random order using the given RNG. Operation + * is in-place, no copy is created. + * + * @param array + * @param rnd + */ + public static <T> void shuffle(T[] array, Random rnd) { + int N = array.length; + for (int i = 0; i < N; i++) { + int r = i + rnd.nextInt(N - i); // between i and N-1 + T swap = array[i]; + array[i] = array[r]; + array[r] = swap; + } + } + + public static String toString(byte[] array) { + StringBuilder s = new StringBuilder(); + s.append('{'); + final int max = array.length - 1; + for (int i = 0; i < array.length; i++) { + s.append(array[i]); + if (i < max) { + s.append(','); + } + } + return s.append('}').toString(); + } + + public static String toString(double[] array) { + StringBuilder s = new StringBuilder(); + s.append('{'); + final int max = array.length - 1; + for (int i = 0; i < array.length; i++) { + s.append(array[i]); + if (i < max) { + s.append(','); + } + } + return s.append('}').toString(); + } + + public static String toString(float[] array) { + StringBuilder s = new StringBuilder(); + s.append('{'); + final int max = array.length - 1; + for (int i = 0; i < array.length; i++) { + s.append(array[i]); + if (i < max) { + s.append(','); + } + } + return s.append('}').toString(); + } + + public static String toString(int[] array) { + StringBuilder s = new StringBuilder(); + s.append('{'); + final int max = array.length - 1; + for (int i = 0; i < array.length; i++) { + s.append(array[i]); + if (i < max) { + s.append(','); + } + } + return s.append('}').toString(); + } + + public static <T> String toString(T[] array) { + StringBuilder s = new StringBuilder(); + s.append('{'); + final int max = array.length - 1; + for (int i = 0; i < array.length; i++) { + s.append(array[i]); + if (i < max) { + s.append(','); + } + } + return s.append('}').toString(); + } +} diff --git a/src/main/java/toxi/util/datatypes/BiasedDoubleRange.java b/src/main/java/toxi/util/datatypes/BiasedDoubleRange.java new file mode 100644 index 0000000..744e45a --- /dev/null +++ b/src/main/java/toxi/util/datatypes/BiasedDoubleRange.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.util.datatypes; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.MathUtils; + +public class BiasedDoubleRange extends DoubleRange { + + @XmlAttribute + protected double bias = 0.5; + + @XmlAttribute + protected double standardDeviation = bias * 0.5; + + public BiasedDoubleRange() { + this(0, 1, 0.5, 1); + } + + /** + * @param min + * min value (inclusive) + * @param max + * max value (inclusive) + * @param bias + * bias value (can be outside the min/max range, but values will + * be clipped) + * @param sd + * standard deviation (if bias at range mean sd=1.0, the entire + * range will be covered) + */ + public BiasedDoubleRange(double min, double max, double bias, double sd) { + super(min, max); + setBias(bias); + setStandardDeviation(sd); + } + + public BiasedDoubleRange copy() { + BiasedDoubleRange r = new BiasedDoubleRange(min, max, bias, + standardDeviation * 2); + r.currValue = currValue; + return r; + } + + /** + * @return the bias + */ + public double getBias() { + return bias; + } + + /** + * @return the standardDeviation + */ + public double getStandardDeviation() { + return standardDeviation; + } + + @Override + public double pickRandom() { + do { + currValue = (random.nextGaussian() * standardDeviation * (max - min)) + + bias; + } while (currValue < min || currValue >= max); + return currValue; + } + + /** + * @param bias + * the bias to set + */ + public void setBias(double bias) { + this.bias = MathUtils.clip(bias, min, max); + } + + /** + * @param sd + * the standardDeviation to set + */ + public void setStandardDeviation(double sd) { + this.standardDeviation = MathUtils.clip(sd, 0, 1.0) * 0.5; + } + + @Override + public String toString() { + return "BiasedFloatRange: " + min + " -> " + max + " bias: " + bias + + " q: " + standardDeviation; + } +} diff --git a/src/main/java/toxi/util/datatypes/BiasedFloatRange.java b/src/main/java/toxi/util/datatypes/BiasedFloatRange.java new file mode 100644 index 0000000..c2b3c6a --- /dev/null +++ b/src/main/java/toxi/util/datatypes/BiasedFloatRange.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.util.datatypes; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.MathUtils; + +public class BiasedFloatRange extends FloatRange { + + @XmlAttribute + protected float bias = 0.5f; + + @XmlAttribute + protected float standardDeviation = bias * 0.5f; + + public BiasedFloatRange() { + this(0, 1, 0.5f, 1); + } + + /** + * @param min + * min value (inclusive) + * @param max + * max value (inclusive) + * @param bias + * bias value (can be outside the min/max range, but values will + * be clipped) + * @param sd + * standard deviation (if bias at range mean sd=1.0, the entire + * range will be covered) + */ + public BiasedFloatRange(float min, float max, float bias, float sd) { + super(min, max); + setBias(bias); + setStandardDeviation(sd); + } + + public BiasedFloatRange copy() { + BiasedFloatRange r = new BiasedFloatRange(min, max, bias, + standardDeviation * 2); + r.currValue = currValue; + return r; + } + + /** + * @return the bias + */ + public float getBias() { + return bias; + } + + /** + * @return the standardDeviation + */ + public float getStandardDeviation() { + return standardDeviation; + } + + @Override + public float pickRandom() { + do { + currValue = (float) (random.nextGaussian() * standardDeviation * (max - min)) + + bias; + } while (currValue < min || currValue >= max); + return currValue; + } + + /** + * @param bias + * the bias to set + */ + public void setBias(float bias) { + this.bias = MathUtils.clip(bias, min, max); + } + + /** + * @param sd + * the standardDeviation to set + */ + public void setStandardDeviation(float sd) { + this.standardDeviation = MathUtils.clip(sd, 0, 1.0f) * 0.5f; + } + + @Override + public String toString() { + return "BiasedFloatRange: " + min + " -> " + max + " bias: " + bias + + " q: " + standardDeviation; + } +} diff --git a/src/main/java/toxi/util/datatypes/BiasedIntegerRange.java b/src/main/java/toxi/util/datatypes/BiasedIntegerRange.java new file mode 100644 index 0000000..ab7e2eb --- /dev/null +++ b/src/main/java/toxi/util/datatypes/BiasedIntegerRange.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.util.datatypes; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.MathUtils; + +public class BiasedIntegerRange extends IntegerRange { + + @XmlAttribute + public int bias; + + @XmlAttribute + public float standardDeviation; + + public BiasedIntegerRange() { + this(0, 100, 50, 1.0f); + } + + /** + * @param min + * min value (inclusive) + * @param max + * max value (inclusive) + * @param bias + * bias value (can be outside the min/max range, but values will + * be clipped) + * @param sd + * standard deviation (if bias at range means sd=1.0, the entire + * range will be covered) + */ + public BiasedIntegerRange(int min, int max, int bias, float sd) { + super(min, max); + this.bias = bias; + this.standardDeviation = sd * 0.5f; + } + + public BiasedIntegerRange copy() { + BiasedIntegerRange r = new BiasedIntegerRange(min, max, bias, + standardDeviation * 2); + r.currValue = currValue; + return r; + } + + /** + * @return the bias + */ + public int getBias() { + return bias; + } + + /** + * @return the standardDeviation + */ + public float getStandardDeviation() { + return standardDeviation; + } + + @Override + public int pickRandom() { + do { + currValue = (int) (random.nextGaussian() * standardDeviation * (max - min)) + + bias; + } while (currValue < min || currValue >= max); + return currValue; + } + + /** + * @param bias + * the bias to set + */ + public void setBias(int bias) { + this.bias = MathUtils.clip(bias, min, max); + } + + /** + * @param sd + * the standardDeviation to set + */ + public void setStandardDeviation(float sd) { + this.standardDeviation = MathUtils.clip(sd, 0, 1.0f) * 0.5f; + } + + @Override + public String toString() { + return "BiasedIntegerRange: " + min + " -> " + max + " bias: " + bias + + " q: " + standardDeviation; + } +} diff --git a/src/main/java/toxi/util/datatypes/DoubleRange.java b/src/main/java/toxi/util/datatypes/DoubleRange.java new file mode 100644 index 0000000..ed21c25 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/DoubleRange.java @@ -0,0 +1,160 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.MathUtils; + +public class DoubleRange { + + public static DoubleRange fromSamples(double... samples) { + double min = Double.MAX_VALUE; + double max = Double.MIN_VALUE; + for (double s : samples) { + min = MathUtils.min(min, s); + max = MathUtils.max(max, s); + } + return new DoubleRange(min, max); + } + + public static DoubleRange fromSamples(List<Double> samples) { + double min = Double.MAX_VALUE; + double max = Double.MIN_VALUE; + for (double s : samples) { + min = MathUtils.min(min, s); + max = MathUtils.max(max, s); + } + return new DoubleRange(min, max); + } + + @XmlAttribute + public double min, max; + + @XmlAttribute(name = "default") + public double currValue; + + protected Random random = new Random(); + + public DoubleRange() { + this(0d, 1d); + } + + public DoubleRange(double min, double max) { + // swap if necessary... + if (min > max) { + double t = max; + max = min; + min = t; + } + this.min = min; + this.max = max; + this.currValue = min; + } + + public double adjustCurrentBy(double val) { + return setCurrent(currValue + val); + } + + public DoubleRange copy() { + DoubleRange range = new DoubleRange(min, max); + range.currValue = currValue; + range.random = random; + return range; + } + + /** + * Returns the value at the normalized position <code>(0.0 = min ... 1.0 = + * max-EPS)</code> within the range. Since the max value is exclusive, the + * value returned for position 1.0 is the range max value minus + * {@link MathUtils#EPS}. Also note the given position is not being clipped + * to the 0.0-1.0 interval, so when passing in values outside that interval + * will produce out-of-range values too. + * + * @param perc + * @return value within the range + */ + public final double getAt(double perc) { + return min + (max - min - MathUtils.EPS) * perc; + } + + public double getCurrent() { + return currValue; + } + + public double getMedian() { + return (min + max) * 0.5f; + } + + public double getRange() { + return max - min; + } + + public boolean isValueInRange(float val) { + return val >= min && val <= max; + } + + public double pickRandom() { + currValue = MathUtils.random(random, (float) min, (float) max); + return currValue; + } + + public DoubleRange seed(long seed) { + random.setSeed(seed); + return this; + } + + public double setCurrent(double val) { + currValue = MathUtils.clip(val, min, max); + return currValue; + } + + public DoubleRange setRandom(Random rnd) { + random = rnd; + return this; + } + + public Double[] toArray(double step) { + List<Double> range = new LinkedList<Double>(); + double v = min; + while (v < max) { + range.add(v); + v += step; + } + return range.toArray(new Double[0]); + } + + @Override + public String toString() { + return "DoubleRange: " + min + " -> " + max; + } +} \ No newline at end of file diff --git a/src/main/java/toxi/util/datatypes/FloatRange.java b/src/main/java/toxi/util/datatypes/FloatRange.java new file mode 100644 index 0000000..8499be4 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/FloatRange.java @@ -0,0 +1,160 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.MathUtils; + +public class FloatRange { + + public static FloatRange fromSamples(float... samples) { + float min = Float.MAX_VALUE; + float max = Float.MIN_VALUE; + for (float s : samples) { + min = MathUtils.min(min, s); + max = MathUtils.max(max, s); + } + return new FloatRange(min, max); + } + + public static FloatRange fromSamples(List<Float> samples) { + float min = Float.MAX_VALUE; + float max = Float.MIN_VALUE; + for (float s : samples) { + min = MathUtils.min(min, s); + max = MathUtils.max(max, s); + } + return new FloatRange(min, max); + } + + @XmlAttribute + public float min, max; + + @XmlAttribute(name = "default") + public float currValue; + + protected Random random = new Random(); + + public FloatRange() { + this(0f, 1f); + } + + public FloatRange(float min, float max) { + // swap if necessary... + if (min > max) { + float t = max; + max = min; + min = t; + } + this.min = min; + this.max = max; + this.currValue = min; + } + + public float adjustCurrentBy(float val) { + return setCurrent(currValue + val); + } + + public FloatRange copy() { + FloatRange range = new FloatRange(min, max); + range.currValue = currValue; + range.random = random; + return range; + } + + /** + * Returns the value at the normalized position <code>(0.0 = min ... 1.0 = + * max-EPS)</code> within the range. Since the max value is exclusive, the + * value returned for position 1.0 is the range max value minus + * {@link MathUtils#EPS}. Also note the given position is not being clipped + * to the 0.0-1.0 interval, so when passing in values outside that interval + * will produce out-of-range values too. + * + * @param perc + * @return value within the range + */ + public final float getAt(float perc) { + return min + (max - min - MathUtils.EPS) * perc; + } + + public float getCurrent() { + return currValue; + } + + public float getMedian() { + return (min + max) * 0.5f; + } + + public float getRange() { + return max - min; + } + + public boolean isValueInRange(float val) { + return val >= min && val <= max; + } + + public float pickRandom() { + currValue = MathUtils.random(random, min, max); + return currValue; + } + + public FloatRange seed(long seed) { + random.setSeed(seed); + return this; + } + + public float setCurrent(float val) { + currValue = MathUtils.clip(val, min, max); + return currValue; + } + + public FloatRange setRandom(Random rnd) { + random = rnd; + return this; + } + + public Float[] toArray(float step) { + List<Float> range = new LinkedList<Float>(); + double v = min; + while (v < max) { + range.add((float) v); + v += step; + } + return range.toArray(new Float[0]); + } + + @Override + public String toString() { + return "FloatRange: " + min + " -> " + max; + } +} diff --git a/src/main/java/toxi/util/datatypes/GenericSet.java b/src/main/java/toxi/util/datatypes/GenericSet.java new file mode 100644 index 0000000..10c4924 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/GenericSet.java @@ -0,0 +1,133 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Random; + +import toxi.math.MathUtils; + +public class GenericSet<T> implements Iterable<T> { + + protected ArrayList<T> items; + protected int currID = -1; + protected T current; + + protected Random random = new Random(); + + public GenericSet(Collection<T> items) { + this.items = new ArrayList<T>(items); + pickRandom(); + } + + public GenericSet(T... obj) { + items = new ArrayList<T>(obj.length); + for (int i = 0; i < obj.length; i++) { + items.add(obj[i]); + } + if (items.size() > 0) { + pickRandom(); + } + } + + public boolean add(T obj) { + boolean isAdded = items.add(obj); + if (items.size() == 1) { + pickRandom(); + } + return isAdded; + } + + public boolean addAll(Collection<T> coll) { + return items.addAll(coll); + } + + public void clear() { + items.clear(); + } + + public boolean contains(T obj) { + return items.contains(obj); + } + + public GenericSet<T> copy() { + GenericSet<T> set = new GenericSet<T>(items); + set.current = current; + set.currID = currID; + set.random = random; + return set; + } + + public T getCurrent() { + return current; + } + + public ArrayList<T> getItems() { + return items; + } + + public Iterator<T> iterator() { + return items.iterator(); + } + + public T pickRandom() { + currID = MathUtils.random(random, items.size()); + current = items.get(currID); + return current; + } + + public T pickRandomUnique() { + int size = items.size(); + if (size > 1) { + int newID = currID; + while (newID == currID) { + newID = MathUtils.random(random, size); + } + currID = newID; + } else { + currID = 0; + } + current = items.get(currID); + return current; + } + + public GenericSet<T> seed(long seed) { + random.setSeed(seed); + return this; + } + + public void setRandom(Random rnd) { + random = rnd; + } + + public int size() { + return items.size(); + } +} diff --git a/src/main/java/toxi/util/datatypes/IntegerRange.java b/src/main/java/toxi/util/datatypes/IntegerRange.java new file mode 100644 index 0000000..796d7b1 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/IntegerRange.java @@ -0,0 +1,157 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.List; +import java.util.Random; + +import javax.xml.bind.annotation.XmlAttribute; + +import toxi.math.MathUtils; + +public class IntegerRange { + + public static IntegerRange fromSamples(int... samples) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int s : samples) { + min = MathUtils.min(min, s); + max = MathUtils.max(max, s); + } + return new IntegerRange(min, max); + } + + public static IntegerRange fromSamples(List<Integer> samples) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int s : samples) { + min = MathUtils.min(min, s); + max = MathUtils.max(max, s); + } + return new IntegerRange(min, max); + } + + @XmlAttribute + public int min, max; + + @XmlAttribute(name = "default") + public int currValue; + + protected Random random = new Random(); + + public IntegerRange() { + this(0, 100); + } + + public IntegerRange(int min, int max) { + // swap if necessary... + if (min > max) { + max ^= min; + min ^= max; + max ^= min; + } + this.min = min; + this.max = max; + this.currValue = min; + } + + public int adjustCurrentBy(int val) { + return setCurrent(currValue + val); + } + + public IntegerRange copy() { + IntegerRange range = new IntegerRange(min, max); + range.currValue = currValue; + range.random = random; + return range; + } + + /** + * Returns the value at the normalized position + * <code>(0.0 = min ... 1.0 = max-1)</code> within the range. Since the max + * value is exclusive, the value returned for position 1.0 is the range max + * value minus 1. Also note the given position is not being clipped to the + * 0.0-1.0 interval, so when passing in values outside that interval will + * produce out-of-range values too. + * + * @param perc + * @return value within the range + */ + public final int getAt(float perc) { + return (int) (min + (max - min - 1) * perc); + } + + public final int getCurrent() { + return currValue; + } + + public final int getMedian() { + return (min + max) / 2; + } + + public final int getRange() { + return max - min; + } + + public final boolean isValueInRange(int val) { + return val >= min && val < max; + } + + public int pickRandom() { + currValue = MathUtils.random(random, min, max); + return currValue; + } + + public IntegerRange seed(long seed) { + random.setSeed(seed); + return this; + } + + public int setCurrent(int val) { + currValue = MathUtils.clip(val, min, max); + return currValue; + } + + public IntegerRange setRandom(Random rnd) { + random = rnd; + return this; + } + + public Integer[] toArray() { + Integer[] range = new Integer[max - min]; + for (int i = 0, j = min; j < max; i++, j++) { + range[i] = j; + } + return range; + } + + @Override + public String toString() { + return "IntegerRange: " + min + " -> " + max; + } +} diff --git a/src/main/java/toxi/util/datatypes/IntegerSet.java b/src/main/java/toxi/util/datatypes/IntegerSet.java new file mode 100644 index 0000000..5a4792d --- /dev/null +++ b/src/main/java/toxi/util/datatypes/IntegerSet.java @@ -0,0 +1,100 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.Random; + +import toxi.math.MathUtils; + +public class IntegerSet { + + public int[] items; + public int currID = -1; + public int current; + + private Random random = new Random(); + + public IntegerSet(int[] items) { + this.items = items; + pickRandom(); + } + + public IntegerSet(Integer... items) { + if (items.length > 0) { + this.items = new int[items.length]; + for (int i = 0; i < items.length; i++) { + this.items[i] = items[i]; + } + pickRandom(); + } else { + throw new IllegalArgumentException("can't create empty IntegerSet"); + } + } + + public boolean contains(int value) { + for (int i = 0; i < items.length; i++) { + if (items[i] == value) { + return true; + } + } + return false; + } + + public int getCurrent() { + return current; + } + + public int pickRandom() { + currID = MathUtils.random(random, items.length); + current = items[currID]; + return current; + } + + public int pickRandomUnique() { + if (items.length > 1) { + int newID = currID; + while (newID == currID) { + newID = MathUtils.random(random, items.length); + } + currID = newID; + } else { + currID = 0; + } + current = items[currID]; + return current; + } + + public IntegerSet seed(long seed) { + random.setSeed(seed); + return this; + } + + public void setRandom(Random rnd) { + random = rnd; + } +} diff --git a/src/main/java/toxi/util/datatypes/ItemIndex.java b/src/main/java/toxi/util/datatypes/ItemIndex.java new file mode 100644 index 0000000..4f09e85 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/ItemIndex.java @@ -0,0 +1,25 @@ +package toxi.util.datatypes; + +import java.util.List; + +public interface ItemIndex<T> { + + public void clear(); + + public T forID(int id); + + public int getID(T item); + + public List<T> getItems(); + + public int index(T item); + + public boolean isIndexed(T item); + + public int reindex(T item, T newItem); + + public int size(); + + public int unindex(T item); + +} \ No newline at end of file diff --git a/src/main/java/toxi/util/datatypes/SingletonRegistry.java b/src/main/java/toxi/util/datatypes/SingletonRegistry.java new file mode 100644 index 0000000..18b8b23 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/SingletonRegistry.java @@ -0,0 +1,87 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * Implements a registry for dynamic singleton management. Use this registry + * instead of using "new" to enforce singletons of any class with a visible + * default constructor. The registry itself is implemented as singleton. + */ +public class SingletonRegistry { + + /** + * The singleton instance of the registry itself. + */ + public static final SingletonRegistry REGISTRY = new SingletonRegistry(); + + private static HashMap<String, Object> map = new HashMap<String, Object>(); + + private static Logger logger = Logger.getLogger(SingletonRegistry.class + .getName()); + + /** + * Creates or returns an instance of the class requested by name. + * + * @param className + * @return class singleton instance + */ + public static synchronized Object getInstanceOf(String className) { + Object instance = map.get(className); + if (instance != null) { + return instance; + } + try { + instance = Class.forName(className).newInstance(); + map.put(className, instance); + logger.info("Created singleton: " + instance); + } catch (ClassNotFoundException cnf) { + logger.severe("Couldn't find class: " + className); + } catch (InstantiationException ie) { + logger.severe("Couldn't instantiate the class: " + className); + } catch (IllegalAccessException ia) { + logger.severe("Couldn't access class: " + className); + } + return instance; + } + + /** + * Alternative, more conventional accessor to the singleton instance of the + * registry itself. + * + * @return registry instance + */ + public static SingletonRegistry getRegistry() { + return REGISTRY; + } + + protected SingletonRegistry() { + } +} diff --git a/src/main/java/toxi/util/datatypes/TypedProperties.java b/src/main/java/toxi/util/datatypes/TypedProperties.java new file mode 100644 index 0000000..56fd592 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/TypedProperties.java @@ -0,0 +1,269 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.io.FileInputStream; +import java.util.HashMap; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.logging.Logger; + +/** + * Convenience wrapper providing typed access to Java {@link Properties} files. + */ +@SuppressWarnings("serial") +public class TypedProperties extends Properties { + + public static final String DELIM = "\t\n\r\f\u00A0,"; + + private static final Logger logger = Logger.getLogger(TypedProperties.class + .getName()); + + /** + * Returns a property as boolean. + * + * @param id + * property name + * @param defaultState + * @return prop value + */ + public boolean getBoolean(String id, boolean defaultState) { + return Boolean.parseBoolean(getProperty(id, "" + defaultState)); + } + + /** + * Shorthand wrapper for {@link #getByteArray(String, byte[])} automatically + * supplying an empty byte[] as default value. + * + * @param id + * @return prop values as array + */ + public byte[] getByteArray(String id) { + return getByteArray(id, new byte[0]); + } + + /** + * Returns a comma delimited property value as byte[] array. Non-byte values + * will be ignored. + * + * @param id + * prop name + * @return prop values as array + */ + public byte[] getByteArray(String id, byte[] defaultArray) { + StringTokenizer tokenizer = new StringTokenizer(getProperty(id, ""), + DELIM); + byte[] pieces = new byte[tokenizer.countTokens()]; + int index = 0; + while (tokenizer.hasMoreTokens()) { + try { + pieces[index] = Byte.parseByte(tokenizer.nextToken()); + index++; + } catch (NumberFormatException e) { + // ignore non-integer items + } + } + if (index > 0) { + byte[] result = new byte[index]; + System.arraycopy(pieces, 0, result, 0, index); + return result; + } else { + return defaultArray; + } + } + + /** + * Returns a property as float. + * + * @param id + * @param defaultValue + * @return prop value + */ + public float getFloat(String id, float defaultValue) { + return Float.parseFloat(getProperty(id, "" + defaultValue)); + } + + /** + * Shorthand wrapper for {@link #getFloatArray(String, float[])} + * automatically supplying an empty float[] array as default value. + * + * @param id + * @return prop values as array + */ + public float[] getFloatArray(String id) { + return getFloatArray(id, new float[0]); + } + + /** + * Returns a comma delimited property value as float[] array. + * + * @param id + * prop name + * @return prop items as array + */ + public float[] getFloatArray(String id, float[] defaultArray) { + StringTokenizer tokenizer = new StringTokenizer(getProperty(id, ""), + DELIM); + float pieces[] = new float[tokenizer.countTokens()]; + int index = 0; + while (tokenizer.hasMoreTokens()) { + try { + pieces[index] = Float.parseFloat(tokenizer.nextToken()); + index++; + } catch (NumberFormatException e) { + // ignore NaN items + } + } + if (index > 0) { + float[] result = new float[index]; + System.arraycopy(pieces, 0, result, 0, index); + return result; + } else { + return defaultArray; + } + } + + /** + * Returns a hexadecimal property as integer + * + * @param id + * prop name + * @param defaultValue + * @return prop value + */ + public int getHexInt(String id, int defaultValue) { + return Integer.parseInt( + getProperty(id, Integer.toHexString(defaultValue)), 16); + } + + /** + * Returns a property as integer. + * + * @param id + * property name + * @param defaultValue + * @return prop value + */ + public int getInt(String id, int defaultValue) { + return Integer.parseInt(getProperty(id, "" + defaultValue)); + } + + /** + * Shorthand wrapper for {{@link #getIntArray(String, int[])} automatically + * supplying an empty int[] array as default value. + * + * @param id + * @return prop values as array + */ + public int[] getIntArray(String id) { + return getIntArray(id, new int[0]); + } + + /** + * Returns a comma delimited property value as int[] array. Non-integer + * items will be ignored. + * + * @param id + * prop name + * @return prop items as array + */ + public int[] getIntArray(String id, int[] defaultArray) { + StringTokenizer tokenizer = new StringTokenizer(getProperty(id, ""), + DELIM); + int pieces[] = new int[tokenizer.countTokens()]; + int index = 0; + while (tokenizer.hasMoreTokens()) { + try { + pieces[index] = Integer.parseInt(tokenizer.nextToken()); + index++; + } catch (NumberFormatException e) { + // ignore non-integer items + } + } + if (index > 0) { + int[] result = new int[index]; + System.arraycopy(pieces, 0, result, 0, index); + return result; + } else { + return defaultArray; + } + } + + public String[] getStringArray(String id) { + return getStringArray(id, new String[0]); + } + + public String[] getStringArray(String id, String[] defaultArray) { + StringTokenizer tokenizer = new StringTokenizer(getProperty(id, ""), + DELIM); + int index = 0; + String[] pieces = null; + while (tokenizer.hasMoreTokens()) { + if (pieces == null) { + pieces = new String[tokenizer.countTokens()]; + } + String token = tokenizer.nextToken(); + if (token.length() > 0) { + pieces[index++] = token; + } + } + if (index > 0) { + String[] result = new String[index]; + System.arraycopy(pieces, 0, result, 0, index); + return result; + } else { + return defaultArray; + } + } + + /** + * Attempts to load properties from the specified (absolute) file path (In + * Processing use sketchPath() or dataPath() to build absolute path). + * + * @param path + * config file + * @return true, if successful. + */ + public boolean load(String path) { + try { + load(new FileInputStream(path)); + return true; + } catch (Exception e) { + logger.warning("error opening config file: " + path); + return false; + } + } + + public HashMap<String, String> toHashMap() { + HashMap<String, String> map = new HashMap<String, String>(); + for (String id : stringPropertyNames()) { + map.put(id, getProperty(id)); + } + return map; + } +} \ No newline at end of file diff --git a/src/main/java/toxi/util/datatypes/UndirectedGraph.java b/src/main/java/toxi/util/datatypes/UndirectedGraph.java new file mode 100644 index 0000000..856c07b --- /dev/null +++ b/src/main/java/toxi/util/datatypes/UndirectedGraph.java @@ -0,0 +1,126 @@ +package toxi.util.datatypes; + +/* + * 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.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Straightforward undirected graph implementation. Nodes are generic type N. + * + * @author Paul Chew Created November, December 2007. For use in + * Delaunay/Voronoi code. + * @author toxi Updated July 2010, minor API changes to be better suited for + * toxiclibs + */ +public class UndirectedGraph<N> { + + protected Map<N, Set<N>> nodeLinks = new HashMap<N, Set<N>>(); + protected Set<N> nodeIDs = Collections.unmodifiableSet(nodeLinks.keySet()); + + /** + * Add a node. If node is already in graph then no change. + * + * @param node + * the node to add + */ + public void add(N node) { + if (nodeLinks.containsKey(node)) { + return; + } + nodeLinks.put(node, new ArraySet<N>()); + } + + /** + * Add a link. If the link is already in graph then no change. + * + * @param nodeA + * one end of the link + * @param nodeB + * the other end of the link + * @throws NullPointerException + * if either endpoint is not in graph + */ + public void connect(N nodeA, N nodeB) throws NullPointerException { + nodeLinks.get(nodeA).add(nodeB); + nodeLinks.get(nodeB).add(nodeA); + } + + /** + * Remove the specified link. If link not in graph, nothing happens. + * + * @param nodeA + * one end of the link + * @param nodeB + * the other end of the link + * @throws NullPointerException + * if either endpoint is not in graph + */ + public void disconnect(N nodeA, N nodeB) throws NullPointerException { + nodeLinks.get(nodeA).remove(nodeB); + nodeLinks.get(nodeB).remove(nodeA); + } + + /** + * Report all the neighbors of node. + * + * @param node + * the node + * @return the neighbors of node + * @throws NullPointerException + * if node does not appear in graph + */ + public Set<N> getConnectedNodesFor(N node) throws NullPointerException { + return Collections.unmodifiableSet(nodeLinks.get(node)); + } + + /** + * Returns an unmodifiable Set view of the nodes contained in this graph. + * The set is backed by the graph, so changes to the graph are reflected in + * the set. + * + * @return a Set view of the graph's node set + */ + public Set<N> getNodes() { + return nodeIDs; + } + + /** + * Remove node and any links that use node. If node not in graph, nothing + * happens. + * + * @param node + * the node to remove. + */ + public void remove(N node) { + if (!nodeLinks.containsKey(node)) { + return; + } + for (N neighbor : nodeLinks.get(node)) { + nodeLinks.get(neighbor).remove(node); + } + nodeLinks.get(node).clear(); + nodeLinks.remove(node); + } + +} diff --git a/src/main/java/toxi/util/datatypes/UniqueItemIndex.java b/src/main/java/toxi/util/datatypes/UniqueItemIndex.java new file mode 100644 index 0000000..e8b8f3e --- /dev/null +++ b/src/main/java/toxi/util/datatypes/UniqueItemIndex.java @@ -0,0 +1,140 @@ +package toxi.util.datatypes; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +/** + * This class is used to build an unique set of items and offers a + * bi-directional mapping between items and their associated ID values. Items + * are added via the {@link #index(Object)} method and removed via + * {@link #unindex(Object)}. The item's {@link #hashCode()} is used as unique + * identifier, so you MUST ensure the item class satisfies the contract of + * {@link #hashCode()} and {@link #equals(Object)}. + */ +public class UniqueItemIndex<T> implements ItemIndex<T> { + + protected final HashMap<T, Integer> uniqueItems = new HashMap<T, Integer>(); + protected final ArrayList<T> index = new ArrayList<T>(); + + public UniqueItemIndex() { + } + + public UniqueItemIndex(Collection<T> items) { + for (T item : items) { + index(item); + } + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#clear() + */ + public void clear() { + uniqueItems.clear(); + index.clear(); + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#forID(int) + */ + public T forID(int id) { + return index.get(id); + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#getID(T) + */ + public int getID(T item) { + Integer id = uniqueItems.get(item); + return (id != null) ? id : -1; + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#getItems() + */ + public List<T> getItems() { + return index; + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#index(T) + */ + public int index(T item) { + Integer id = uniqueItems.get(item); + if (id == null) { + id = index.size(); + uniqueItems.put(item, id); + index.add(item); + } + return id; + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#isIndexed(T) + */ + public boolean isIndexed(T item) { + return uniqueItems.get(item) != null; + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#reindex(T, T) + */ + public int reindex(T item, T newItem) { + int id = getID(item); + if (id != -1) { + int newID = getID(newItem); + if (newID != -1) { + unindex(item); + id = getID(newItem); + } else { + index.set(id, newItem); + uniqueItems.remove(item); + uniqueItems.put(newItem, id); + } + } + return id; + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#size() + */ + public int size() { + return index.size(); + } + + /* + * (non-Javadoc) + * + * @see toxi.util.ItemIndex#unindex(T) + */ + public int unindex(T item) { + Integer id = uniqueItems.get(item); + if (id != null) { + uniqueItems.remove(item); + index.remove(item); + for (int i = id, num = index.size(); i < num; i++) { + uniqueItems.put(index.get(i), i); + } + } else { + id = -1; + } + return id; + } +} diff --git a/src/main/java/toxi/util/datatypes/WeightedRandomEntry.java b/src/main/java/toxi/util/datatypes/WeightedRandomEntry.java new file mode 100644 index 0000000..4cd2a03 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/WeightedRandomEntry.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.util.datatypes; + +/** + * This class encapsulates a single element within a {@link WeightedRandomSet}. + * + * @param <T> + */ +public class WeightedRandomEntry<T> implements + Comparable<WeightedRandomEntry<T>> { + + protected T item; + protected int weight; + + public WeightedRandomEntry(T item, int weight) { + this.item = item; + this.weight = weight; + } + + public int compareTo(WeightedRandomEntry<T> e) { + return (e.weight - weight); + } + + public String toString() { + return item.toString() + ": " + weight; + } +} \ No newline at end of file diff --git a/src/main/java/toxi/util/datatypes/WeightedRandomSet.java b/src/main/java/toxi/util/datatypes/WeightedRandomSet.java new file mode 100644 index 0000000..a53bb42 --- /dev/null +++ b/src/main/java/toxi/util/datatypes/WeightedRandomSet.java @@ -0,0 +1,118 @@ +/* + * __ .__ .__ ._____. + * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ + * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ + * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ + * |__| \____/__/\_ \__|\___ >____/__||___ /____ > + * \/ \/ \/ \/ + * + * 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.util.datatypes; + +import java.util.ArrayList; +import java.util.List; + +import toxi.math.MathUtils; + +/** + * This class provides a generic random-weight distribution of arbitary objects. + * Add elements with their weight to the set and then use the + * {@link #getRandom()} method to retrieve objects. The frequency of returned + * elements is based on their relative weight. This makes it easy to provide + * biased preferences. + * + * http://www.electricmonk.nl/log/2009/12/23/weighted-random-distribution/ + */ +public class WeightedRandomSet<T> { + + protected List<WeightedRandomEntry<T>> elements = new ArrayList<WeightedRandomEntry<T>>(); + + protected int totalWeight; + + /** + * Add a new element of type T to the set. + * + * @param item + * @param weight + * @return itself + */ + public WeightedRandomSet<T> add(T item, int weight) { + WeightedRandomEntry<T> e = new WeightedRandomEntry<T>(item, weight); + int num = elements.size(); + boolean isInserted = false; + if (num > 0) { + for (int i = 0; i < num; i++) { + if (weight < elements.get(i).weight) { + elements.add(i, e); + isInserted = true; + break; + } + } + } + if (!isInserted) { + elements.add(e); + } + totalWeight += weight; + return this; + } + + /** + * @return the elements + */ + public List<WeightedRandomEntry<T>> getElements() { + return elements; + } + + /** + * Returns a randomly picked element from the set. The frequency of + * occurance depends on the relative weight of each item. + * + * @return picked element + */ + public T getRandom() { + int rnd = MathUtils.random(totalWeight); + T choice = null; + int sum = totalWeight; + for (WeightedRandomEntry<T> e : elements) { + sum -= e.weight; + if (sum <= rnd) { + choice = e.item; + break; + } + } + return choice; + } + + /** + * Removes the given item from the set. + * + * @param item + */ + public void remove(T item) { + for (WeightedRandomEntry<T> e : elements) { + if (e.item.equals(item)) { + elements.remove(e); + totalWeight -= e.weight; + return; + } + } + } +} diff --git a/src/main/java/toxi/util/events/EventDispatcher.java b/src/main/java/toxi/util/events/EventDispatcher.java new file mode 100644 index 0000000..14e5a4b --- /dev/null +++ b/src/main/java/toxi/util/events/EventDispatcher.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.util.events; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class EventDispatcher<T> implements Iterable<T> { + + protected List<T> listeners = new LinkedList<T>(); + + public EventDispatcher() { + } + + public void addListener(T listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + public List<T> getListeners() { + return listeners; + } + + public Iterator<T> iterator() { + return listeners.iterator(); + } + + public void removeListener(T listener) { + listeners.remove(listener); + } +} -- GitLab