From e772172cbc39c942f251f3f5e46d6c30f8aef9dc Mon Sep 17 00:00:00 2001 From: gauthiier Date: Wed, 4 Mar 2015 09:41:41 +0100 Subject: [PATCH] day3 (german!) libs --- day3/libs/mathematik/library/mathematik.jar | Bin 0 -> 71479 bytes .../mathematik/src/mathematik/BSpline.java | 89 ++ .../src/mathematik/Intersection.java | 916 +++++++++++++++++ .../libs/mathematik/src/mathematik/Linef.java | 52 + .../mathematik/src/mathematik/Mathematik.java | 29 + .../mathematik/src/mathematik/Matrix3f.java | 619 ++++++++++++ .../mathematik/src/mathematik/Matrix4f.java | 433 ++++++++ .../src/mathematik/PerlinNoise.java | 178 ++++ .../mathematik/src/mathematik/Plane3f.java | 87 ++ .../mathematik/src/mathematik/Quaternion.java | 89 ++ .../mathematik/src/mathematik/Random.java | 154 +++ .../libs/mathematik/src/mathematik/Ray3f.java | 51 + day3/libs/mathematik/src/mathematik/Rayf.java | 60 ++ .../src/mathematik/Rectangle3f.java | 47 + .../mathematik/src/mathematik/Rotation.java | 156 +++ .../src/mathematik/TransformMatrix4f.java | 333 +++++++ day3/libs/mathematik/src/mathematik/Util.java | 931 ++++++++++++++++++ .../mathematik/src/mathematik/Vector2f.java | 330 +++++++ .../mathematik/src/mathematik/Vector2i.java | 123 +++ .../mathematik/src/mathematik/Vector3f.java | 629 ++++++++++++ .../mathematik/src/mathematik/Vector3i.java | 86 ++ .../mathematik/src/mathematik/Vector4f.java | 307 ++++++ .../mathematik/src/mathematik/Vectorf.java | 26 + .../WorldAxisAlignedBoundingBox.java | 37 + .../Lesson00_Particle/Lesson00_Particle.pde | 71 ++ .../Lesson01_Gravity/Lesson01_Gravity.pde | 54 + .../Lesson02_Particles/Lesson02_Particles.pde | 58 ++ .../Lesson03_Attractors.pde | 88 ++ .../Lesson04_Deflectors.pde | 102 ++ .../Lesson05_Spring/Lesson05_Spring.pde | 68 ++ .../Lesson06_Springs/Lesson06_Springs.pde | 70 ++ .../Lesson07_StableQuads.pde | 83 ++ .../Lesson08_Sticks/Lesson08_Sticks.pde | 71 ++ .../Lesson09_Cloth/Lesson09_Cloth.pde | 113 +++ .../Lesson10_Behaviors/Lesson10_Behaviors.pde | 73 ++ .../LessonX01_Overlap/LessonX01_Overlap.pde | 69 ++ .../LessonX02_Collisions.pde | 91 ++ .../LessonX03_ParticlesLeavingTrails.pde | 131 +++ .../LessonX04_StickMan/LessonX04_StickMan.pde | 146 +++ .../LessonX05_AngleConstraints.pde | 184 ++++ .../LessonX06_Ducklings.pde | 145 +++ day3/libs/teilchen/library/teilchen.jar | Bin 0 -> 479897 bytes .../teilchen/src/teilchen/BasicParticle.java | 142 +++ .../src/teilchen/BehaviorParticle.java | 72 ++ .../src/teilchen/ConditionalParticle.java | 33 + .../src/teilchen/IBehaviorParticle.java | 36 + .../teilchen/src/teilchen/IConnection.java | 30 + day3/libs/teilchen/src/teilchen/Particle.java | 70 ++ day3/libs/teilchen/src/teilchen/Physics.java | 374 +++++++ .../src/teilchen/ShortLivedParticle.java | 49 + .../src/teilchen/VectorfieldParticle.java | 48 + .../src/teilchen/behavior/Alignment.java | 107 ++ .../src/teilchen/behavior/Arrival.java | 159 +++ .../src/teilchen/behavior/Cohesion.java | 111 +++ .../teilchen/src/teilchen/behavior/Flee.java | 77 ++ .../src/teilchen/behavior/IBehavior.java | 42 + .../teilchen/src/teilchen/behavior/Motor.java | 127 +++ .../teilchen/src/teilchen/behavior/Seek.java | 96 ++ .../src/teilchen/behavior/Separation.java | 110 +++ .../src/teilchen/behavior/Steering.java | 91 ++ .../teilchen/src/teilchen/behavior/Util.java | 66 ++ .../src/teilchen/behavior/Verhalten.java | 33 + .../src/teilchen/behavior/Wander.java | 122 +++ .../constraint/AngleConstraintStick.java | 79 ++ .../src/teilchen/constraint/Angular.java | 221 +++++ .../teilchen/src/teilchen/constraint/Box.java | 179 ++++ .../src/teilchen/constraint/IConstraint.java | 35 + .../src/teilchen/constraint/ReflectBox.java | 221 +++++ .../src/teilchen/constraint/Stick.java | 158 +++ .../src/teilchen/constraint/Teleporter.java | 90 ++ .../src/teilchen/cubicle/CubicleAtom.java | 67 ++ .../src/teilchen/cubicle/CubicleEntity.java | 61 ++ .../src/teilchen/cubicle/CubicleParticle.java | 63 ++ .../src/teilchen/cubicle/CubicleWorld.java | 302 ++++++ .../src/teilchen/cubicle/ICubicleEntity.java | 60 ++ .../src/teilchen/demo/Lesson00_Particle.java | 98 ++ .../src/teilchen/demo/Lesson01_Gravity.java | 83 ++ .../src/teilchen/demo/Lesson02_Particles.java | 87 ++ .../teilchen/demo/Lesson03_Attractors.java | 116 +++ .../teilchen/demo/Lesson04_Deflectors.java | 129 +++ .../src/teilchen/demo/Lesson05_Spring.java | 95 ++ .../src/teilchen/demo/Lesson06_Springs.java | 97 ++ .../teilchen/demo/Lesson07_StableQuads.java | 111 +++ .../src/teilchen/demo/Lesson08_Sticks.java | 100 ++ .../src/teilchen/demo/Lesson09_Cloth.java | 141 +++ .../demo/Lesson10_WanderBehavior.java | 96 ++ .../demo/Lesson11_ArrivalBehavior.java | 103 ++ .../src/teilchen/demo/LessonX01_Overlap.java | 118 +++ .../teilchen/demo/LessonX02_Collisions.java | 121 +++ .../LessonX03_ParticlesLeavingTrails.java | 167 ++++ .../src/teilchen/demo/LessonX04_StickMan.java | 150 +++ .../demo/LessonX05_AngleConstraints.java | 210 ++++ .../teilchen/demo/LessonX06_Ducklings.java | 173 ++++ .../teilchen/demo/LessonX07_CubicleWorld.java | 192 ++++ .../src/teilchen/demo/LessonX08_Schwarm.java | 174 ++++ .../teilchen/force/AngleConstraintSpring.java | 101 ++ .../src/teilchen/force/Attractor.java | 114 +++ .../src/teilchen/force/DirectedAttractor.java | 63 ++ .../teilchen/src/teilchen/force/Gravity.java | 74 ++ .../teilchen/src/teilchen/force/IForce.java | 37 + .../src/teilchen/force/MuscleSpring.java | 114 +++ .../src/teilchen/force/PlaneDeflector.java | 160 +++ .../teilchen/src/teilchen/force/Spring.java | 211 ++++ .../src/teilchen/force/TearableSpring.java | 82 ++ .../src/teilchen/force/TriangleDeflector.java | 230 +++++ .../force/TriangleDeflectorIndexed.java | 40 + .../src/teilchen/force/ViscousDrag.java | 72 ++ .../teilchen/force/flowfield/FlowField.java | 503 ++++++++++ .../force/flowfield/FlowFieldForce.java | 37 + .../force/flowfield/FlowFieldForceMOUSE.java | 46 + .../force/vectorfield/VectorField.java | 212 ++++ .../vectorfield/VectorFieldGenerator.java | 28 + .../VectorFieldGeneratorAVERAGEUNITS.java | 87 ++ .../VectorFieldGeneratorRANDOM.java | 65 ++ .../force/vectorfield/VectorFieldUnit.java | 80 ++ .../src/teilchen/integration/Derivate3f.java | 39 + .../src/teilchen/integration/Euler.java | 103 ++ .../src/teilchen/integration/IIntegrator.java | 110 +++ .../src/teilchen/integration/Midpoint.java | 70 ++ .../src/teilchen/integration/RungeKutta.java | 238 +++++ .../src/teilchen/integration/Util.java | 62 ++ .../src/teilchen/integration/Verlet.java | 115 +++ .../src/teilchen/util/CollisionManager.java | 425 ++++++++ .../src/teilchen/util/CubicleWorldView.java | 86 ++ .../teilchen/src/teilchen/util/DrawLib.java | 290 ++++++ .../teilchen/src/teilchen/util/Overlap.java | 107 ++ .../teilchen/src/teilchen/util/Packing.java | 73 ++ .../src/teilchen/util/ParticleTrail.java | 141 +++ .../src/teilchen/util/SpatialEntity.java | 33 + .../src/teilchen/util/StableSpringQuad.java | 117 +++ .../src/teilchen/util/StableStickQuad.java | 85 ++ .../teilchen/src/teilchen/util/StickMan.java | 122 +++ .../libs/teilchen/src/teilchen/util/Util.java | 198 ++++ 133 files changed, 18241 insertions(+) create mode 100755 day3/libs/mathematik/library/mathematik.jar create mode 100755 day3/libs/mathematik/src/mathematik/BSpline.java create mode 100755 day3/libs/mathematik/src/mathematik/Intersection.java create mode 100755 day3/libs/mathematik/src/mathematik/Linef.java create mode 100755 day3/libs/mathematik/src/mathematik/Mathematik.java create mode 100755 day3/libs/mathematik/src/mathematik/Matrix3f.java create mode 100755 day3/libs/mathematik/src/mathematik/Matrix4f.java create mode 100755 day3/libs/mathematik/src/mathematik/PerlinNoise.java create mode 100755 day3/libs/mathematik/src/mathematik/Plane3f.java create mode 100755 day3/libs/mathematik/src/mathematik/Quaternion.java create mode 100755 day3/libs/mathematik/src/mathematik/Random.java create mode 100755 day3/libs/mathematik/src/mathematik/Ray3f.java create mode 100755 day3/libs/mathematik/src/mathematik/Rayf.java create mode 100755 day3/libs/mathematik/src/mathematik/Rectangle3f.java create mode 100755 day3/libs/mathematik/src/mathematik/Rotation.java create mode 100755 day3/libs/mathematik/src/mathematik/TransformMatrix4f.java create mode 100755 day3/libs/mathematik/src/mathematik/Util.java create mode 100755 day3/libs/mathematik/src/mathematik/Vector2f.java create mode 100755 day3/libs/mathematik/src/mathematik/Vector2i.java create mode 100755 day3/libs/mathematik/src/mathematik/Vector3f.java create mode 100755 day3/libs/mathematik/src/mathematik/Vector3i.java create mode 100755 day3/libs/mathematik/src/mathematik/Vector4f.java create mode 100755 day3/libs/mathematik/src/mathematik/Vectorf.java create mode 100755 day3/libs/mathematik/src/mathematik/WorldAxisAlignedBoundingBox.java create mode 100755 day3/libs/teilchen/examples/Lesson00_Particle/Lesson00_Particle.pde create mode 100755 day3/libs/teilchen/examples/Lesson01_Gravity/Lesson01_Gravity.pde create mode 100755 day3/libs/teilchen/examples/Lesson02_Particles/Lesson02_Particles.pde create mode 100755 day3/libs/teilchen/examples/Lesson03_Attractors/Lesson03_Attractors.pde create mode 100755 day3/libs/teilchen/examples/Lesson04_Deflectors/Lesson04_Deflectors.pde create mode 100755 day3/libs/teilchen/examples/Lesson05_Spring/Lesson05_Spring.pde create mode 100755 day3/libs/teilchen/examples/Lesson06_Springs/Lesson06_Springs.pde create mode 100755 day3/libs/teilchen/examples/Lesson07_StableQuads/Lesson07_StableQuads.pde create mode 100755 day3/libs/teilchen/examples/Lesson08_Sticks/Lesson08_Sticks.pde create mode 100755 day3/libs/teilchen/examples/Lesson09_Cloth/Lesson09_Cloth.pde create mode 100755 day3/libs/teilchen/examples/Lesson10_Behaviors/Lesson10_Behaviors.pde create mode 100755 day3/libs/teilchen/examples/LessonX01_Overlap/LessonX01_Overlap.pde create mode 100755 day3/libs/teilchen/examples/LessonX02_Collisions/LessonX02_Collisions.pde create mode 100755 day3/libs/teilchen/examples/LessonX03_ParticlesLeavingTrails/LessonX03_ParticlesLeavingTrails.pde create mode 100755 day3/libs/teilchen/examples/LessonX04_StickMan/LessonX04_StickMan.pde create mode 100755 day3/libs/teilchen/examples/LessonX05_AngleConstraints/LessonX05_AngleConstraints.pde create mode 100755 day3/libs/teilchen/examples/LessonX06_Ducklings/LessonX06_Ducklings.pde create mode 100755 day3/libs/teilchen/library/teilchen.jar create mode 100755 day3/libs/teilchen/src/teilchen/BasicParticle.java create mode 100755 day3/libs/teilchen/src/teilchen/BehaviorParticle.java create mode 100755 day3/libs/teilchen/src/teilchen/ConditionalParticle.java create mode 100755 day3/libs/teilchen/src/teilchen/IBehaviorParticle.java create mode 100755 day3/libs/teilchen/src/teilchen/IConnection.java create mode 100755 day3/libs/teilchen/src/teilchen/Particle.java create mode 100755 day3/libs/teilchen/src/teilchen/Physics.java create mode 100755 day3/libs/teilchen/src/teilchen/ShortLivedParticle.java create mode 100755 day3/libs/teilchen/src/teilchen/VectorfieldParticle.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Alignment.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Arrival.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Cohesion.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Flee.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/IBehavior.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Motor.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Seek.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Separation.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Steering.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Util.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Verhalten.java create mode 100755 day3/libs/teilchen/src/teilchen/behavior/Wander.java create mode 100755 day3/libs/teilchen/src/teilchen/constraint/AngleConstraintStick.java create mode 100755 day3/libs/teilchen/src/teilchen/constraint/Angular.java create mode 100755 day3/libs/teilchen/src/teilchen/constraint/Box.java create mode 100755 day3/libs/teilchen/src/teilchen/constraint/IConstraint.java create mode 100755 day3/libs/teilchen/src/teilchen/constraint/ReflectBox.java create mode 100755 day3/libs/teilchen/src/teilchen/constraint/Stick.java create mode 100755 day3/libs/teilchen/src/teilchen/constraint/Teleporter.java create mode 100755 day3/libs/teilchen/src/teilchen/cubicle/CubicleAtom.java create mode 100755 day3/libs/teilchen/src/teilchen/cubicle/CubicleEntity.java create mode 100755 day3/libs/teilchen/src/teilchen/cubicle/CubicleParticle.java create mode 100755 day3/libs/teilchen/src/teilchen/cubicle/CubicleWorld.java create mode 100755 day3/libs/teilchen/src/teilchen/cubicle/ICubicleEntity.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson00_Particle.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson01_Gravity.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson02_Particles.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson03_Attractors.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson04_Deflectors.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson05_Spring.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson06_Springs.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson07_StableQuads.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson08_Sticks.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson09_Cloth.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson10_WanderBehavior.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/Lesson11_ArrivalBehavior.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX01_Overlap.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX02_Collisions.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX03_ParticlesLeavingTrails.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX04_StickMan.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX05_AngleConstraints.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX06_Ducklings.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX07_CubicleWorld.java create mode 100755 day3/libs/teilchen/src/teilchen/demo/LessonX08_Schwarm.java create mode 100755 day3/libs/teilchen/src/teilchen/force/AngleConstraintSpring.java create mode 100755 day3/libs/teilchen/src/teilchen/force/Attractor.java create mode 100755 day3/libs/teilchen/src/teilchen/force/DirectedAttractor.java create mode 100755 day3/libs/teilchen/src/teilchen/force/Gravity.java create mode 100755 day3/libs/teilchen/src/teilchen/force/IForce.java create mode 100755 day3/libs/teilchen/src/teilchen/force/MuscleSpring.java create mode 100755 day3/libs/teilchen/src/teilchen/force/PlaneDeflector.java create mode 100755 day3/libs/teilchen/src/teilchen/force/Spring.java create mode 100755 day3/libs/teilchen/src/teilchen/force/TearableSpring.java create mode 100755 day3/libs/teilchen/src/teilchen/force/TriangleDeflector.java create mode 100755 day3/libs/teilchen/src/teilchen/force/TriangleDeflectorIndexed.java create mode 100755 day3/libs/teilchen/src/teilchen/force/ViscousDrag.java create mode 100755 day3/libs/teilchen/src/teilchen/force/flowfield/FlowField.java create mode 100755 day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForce.java create mode 100755 day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForceMOUSE.java create mode 100755 day3/libs/teilchen/src/teilchen/force/vectorfield/VectorField.java create mode 100755 day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGenerator.java create mode 100755 day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorAVERAGEUNITS.java create mode 100755 day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorRANDOM.java create mode 100755 day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldUnit.java create mode 100755 day3/libs/teilchen/src/teilchen/integration/Derivate3f.java create mode 100755 day3/libs/teilchen/src/teilchen/integration/Euler.java create mode 100755 day3/libs/teilchen/src/teilchen/integration/IIntegrator.java create mode 100755 day3/libs/teilchen/src/teilchen/integration/Midpoint.java create mode 100755 day3/libs/teilchen/src/teilchen/integration/RungeKutta.java create mode 100755 day3/libs/teilchen/src/teilchen/integration/Util.java create mode 100755 day3/libs/teilchen/src/teilchen/integration/Verlet.java create mode 100755 day3/libs/teilchen/src/teilchen/util/CollisionManager.java create mode 100755 day3/libs/teilchen/src/teilchen/util/CubicleWorldView.java create mode 100755 day3/libs/teilchen/src/teilchen/util/DrawLib.java create mode 100755 day3/libs/teilchen/src/teilchen/util/Overlap.java create mode 100755 day3/libs/teilchen/src/teilchen/util/Packing.java create mode 100755 day3/libs/teilchen/src/teilchen/util/ParticleTrail.java create mode 100755 day3/libs/teilchen/src/teilchen/util/SpatialEntity.java create mode 100755 day3/libs/teilchen/src/teilchen/util/StableSpringQuad.java create mode 100755 day3/libs/teilchen/src/teilchen/util/StableStickQuad.java create mode 100755 day3/libs/teilchen/src/teilchen/util/StickMan.java create mode 100755 day3/libs/teilchen/src/teilchen/util/Util.java diff --git a/day3/libs/mathematik/library/mathematik.jar b/day3/libs/mathematik/library/mathematik.jar new file mode 100755 index 0000000000000000000000000000000000000000..e078d13d27a0de4411524d4d69ec80e01c2449e9 GIT binary patch literal 71479 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 thePoints, int i, float t) { + Vector3f p = new Vector3f(); + for (int j = -2; j <= 1; j++) { + p.x += b(j, t) * thePoints.get(i + j).x; + p.y += b(j, t) * thePoints.get(i + j).y; + p.z += b(j, t) * thePoints.get(i + j).z; + } + return p; + } + + public static Vector curve(Vector thePoints, int theSteps, Vector theResult) { + for (int i = 2; i < thePoints.size() - 1; i++) { + for (int j = 1; j <= theSteps; j++) { + theResult.add(p(thePoints, i, j / (float) theSteps)); + } + } + return theResult; + } + + public static Vector curve(Vector thePoints, int theSteps) { + return curve(thePoints, theSteps, new Vector()); + } + + public static Vector closeCurve(Vector thePoints) { + /* copy points */ + Vector myClosedPoints = new Vector(); + for (int i = 0; i < thePoints.size(); i++) { + myClosedPoints.add(thePoints.get(i)); + } + + /* repeat first three points */ + if (thePoints.size() > 2) { + myClosedPoints.add(thePoints.get(0)); + myClosedPoints.add(thePoints.get(1)); + myClosedPoints.add(thePoints.get(2)); + } + return myClosedPoints; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Intersection.java b/day3/libs/mathematik/src/mathematik/Intersection.java new file mode 100755 index 0000000..6b8fb54 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Intersection.java @@ -0,0 +1,916 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +/** + * beware this is not really in good shape. i ll read my linear algebra book and + * fix this class. someday. hopefully. + */ +public final class Intersection + implements Serializable, + Mathematik { + + private static final long serialVersionUID = -5392974339890719551L; + + private static final Vector3f h = new Vector3f(); + + private static final Vector3f s = new Vector3f(); + + private static final Vector3f q = new Vector3f(); + + private static final Vector3f _myTempEdge1 = new Vector3f(); + + private static final Vector3f _myTempEdge2 = new Vector3f(); + + private static final Vector3f _myTempNormal = new Vector3f(); + + private static final Vector3f pvec = new Vector3f(); + + private static final Vector3f tvec = new Vector3f(); + + private static final Vector3f qvec = new Vector3f(); + + public static boolean intersectRayPlane(final Ray3f theRay, + final Plane3f thePlane, + final Vector3f theResult, + final boolean doPlanar, + final boolean quad) { + Vector3f diff = mathematik.Util.sub(theRay.origin, thePlane.origin); // mathematik.Util.sub(theRay.origin, v0); + Vector3f edge1 = thePlane.vectorA; // mathematik.Util.sub(v1, v0); + Vector3f edge2 = thePlane.vectorB; // mathematik.Util.sub(v2, v0); + + Vector3f norm = thePlane.normal; //new Vector3f(); + + if (thePlane.normal == null) { + thePlane.updateNormal(); + norm = thePlane.normal; + } + + float dirDotNorm = theRay.direction.dot(norm); + float sign; + if (dirDotNorm > EPSILON) { + sign = 1; + } else if (dirDotNorm < EPSILON) { + sign = -1f; + dirDotNorm = -dirDotNorm; + } else { + // ray and triangle are parallel + return false; + } + + Vector3f myCross; + myCross = new Vector3f(); + myCross.cross(diff, edge2); + float dirDotDiffxEdge2 = sign * theRay.direction.dot(myCross); + if (dirDotDiffxEdge2 > 0.0f) { + myCross = new Vector3f(); + myCross.cross(edge1, diff); + float dirDotEdge1xDiff = sign * theRay.direction.dot(myCross); + if (dirDotEdge1xDiff >= 0.0f) { + if (!quad ? dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm : dirDotEdge1xDiff <= dirDotNorm) { + float diffDotNorm = -sign * diff.dot(norm); + if (diffDotNorm >= 0.0f) { + // ray intersects triangle + // if storage vector is null, just return true, + if (theResult == null) { + return true; + } + // else fill in. + float inv = 1f / dirDotNorm; + float t = diffDotNorm * inv; + if (!doPlanar) { + theResult.set(theRay.origin); + theResult.add(theRay.direction.x * t, + theRay.direction.y * t, + theRay.direction.z * t); + } else { + // these weights can be used to determine + // interpolated values, such as texture coord. + // eg. texcoord s,t at intersection point: + // s = w0*s0 + w1*s1 + w2*s2; + // t = w0*t0 + w1*t1 + w2*t2; + float w1 = dirDotDiffxEdge2 * inv; + float w2 = dirDotEdge1xDiff * inv; + //float w0 = 1.0f - w1 - w2; + theResult.set(t, w1, w2); + } + return true; + } + } + } + } + return false; + } + +// /** +// * @deprecated not in good state. +// * @param v0 first point of the triangle. +// * @param v1 second point of the triangle. +// * @param v2 third point of the triangle. +// * @param store storage vector - if null, no intersection is calc'd +// * @param doPlanar true if we are calcing planar results. +// * @param quad +// * @return true if ray intersects triangle +// */ +// public static boolean intersectRayTriangle(final Ray3f theRay, +// final Vector3f v0, +// final Vector3f v1, +// final Vector3f v2, +// final Vector3f store, +// final boolean doPlanar, +// final boolean quad) { +// Vector3f diff = mathematik.Util.sub(theRay.origin, v0); +// Vector3f edge1 = mathematik.Util.sub(v1, v0); +// Vector3f edge2 = mathematik.Util.sub(v2, v0); +// Vector3f norm = new Vector3f(); +// norm.cross(edge1, edge2); +// +// float dirDotNorm = theRay.direction.dot(norm); +// float sign; +// if (dirDotNorm > EPSILON) { +// sign = 1; +// } else if (dirDotNorm < EPSILON) { +// sign = -1f; +// dirDotNorm = -dirDotNorm; +// } else { +// // ray and triangle are parallel +// return false; +// } +// +// Vector3f myCross; +// myCross = new Vector3f(); +// myCross.cross(diff, edge2); +// float dirDotDiffxEdge2 = sign * theRay.direction.dot(myCross); +// if (dirDotDiffxEdge2 > 0.0f) { +// myCross = new Vector3f(); +// myCross.cross(edge1, diff); +// float dirDotEdge1xDiff = sign * theRay.direction.dot(myCross); +// if (dirDotEdge1xDiff >= 0.0f) { +// if (!quad ? dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm : dirDotEdge1xDiff <= dirDotNorm) { +// float diffDotNorm = -sign * diff.dot(norm); +// if (diffDotNorm >= 0.0f) { +// // ray intersects triangle +// // if storage vector is null, just return true, +// if (store == null) { +// return true; +// } +// // else fill in. +// float inv = 1f / dirDotNorm; +// float t = diffDotNorm * inv; +// if (!doPlanar) { +// store.set(theRay.origin); +// store.add(theRay.direction.x * t, +// theRay.direction.y * t, +// theRay.direction.z * t); +// } else { +// // these weights can be used to determine +// // interpolated values, such as texture coord. +// // eg. texcoord s,t at intersection point: +// // s = w0*s0 + w1*s1 + w2*s2; +// // t = w0*t0 + w1*t1 + w2*t2; +// float w1 = dirDotDiffxEdge2 * inv; +// float w2 = dirDotEdge1xDiff * inv; +// //float w0 = 1.0f - w1 - w2; +// store.set(t, w1, w2); +// } +// return true; +// } +// } +// } +// } +// return false; +// } + /* + * Practical Analysis of Optimized Ray-Triangle Intersection + * Tomas Moeller + * Department of Computer Engineering, Chalmers University of Technology, Sweden. + * + * code rewritten to do tests on the sign of the determinant + * the division is before the test of the sign of the det + * and one CROSS has been moved out from the if-else if-else + * from -- http://www.cs.lth.se/home/Tomas_Akenine_Moller/raytri/raytri.c + */ + public static boolean intersectRayTriangle(final Vector3f orig, + final Vector3f dir, + final Vector3f vert0, + final Vector3f vert1, + final Vector3f vert2, + float[] result) { + final int T = 0; + final int U = 1; + final int V = 2; + Vector3f edge1; + Vector3f edge2; + Vector3f tvec; + Vector3f pvec; + Vector3f qvec; + float det; + float inv_det; + + /* find vectors for two edges sharing vert0 */ + edge1 = mathematik.Util.sub(vert1, vert0); + edge2 = mathematik.Util.sub(vert2, vert0); + + /* begin calculating determinant - also used to calculate U parameter */ + pvec = mathematik.Util.cross(dir, edge2); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = edge1.dot(pvec); + + /* calculate distance from vert0 to ray origin */ + tvec = mathematik.Util.sub(orig, vert0); + inv_det = 1.0f / det; + + qvec = mathematik.Util.cross(tvec, edge1); + + if (det > EPSILON) { + result[U] = tvec.dot(pvec); + if (result[U] < 0.0f || result[U] > det) { + return false; + } + + /* calculate V parameter and test bounds */ + result[V] = dir.dot(qvec); + if (result[V] < 0.0f || result[U] + result[V] > det) { + return false; + } + + } else if (det < -EPSILON) { + /* calculate U parameter and test bounds */ + result[U] = tvec.dot(pvec); + if (result[U] > 0.0f || result[U] < det) { + return false; + } + + /* calculate V parameter and test bounds */ + result[V] = dir.dot(qvec); + if (result[V] > 0.0f || result[U] + result[V] < det) { + return false; + } + } else { + return false; /* ray is parallell to the plane of the triangle */ + } + + result[T] = edge2.dot(qvec) * inv_det; + result[U] *= inv_det; + result[V] *= inv_det; + + return true; + } + + /** + * grabbed from Xith + * + * @todo not sure whether this is for ray-plane or line-plane intersection. + * but i think it s for latter, hence the method name. + * + * @param thePlane Plane3f + * @param theRay Ray3f + * @param theIntersectionPoint Vector3f + * @return float + */ + public static float intersectLinePlane(final Ray3f theRay, + final Plane3f thePlane, + final Vector3f theIntersectionPoint) { + double time = 0; + _myTempNormal.cross(thePlane.vectorA, thePlane.vectorB); + double denom = _myTempNormal.dot(theRay.direction); + + if (denom == 0) { + System.err.println("### ERROR @ Intersection / NEGATIVE_INFINITY"); + return Float.NEGATIVE_INFINITY; + } + + double numer = _myTempNormal.dot(theRay.origin); + double D = -(thePlane.origin.dot(_myTempNormal)); + time = -((numer + D) / denom); + + if (theIntersectionPoint != null) { + theIntersectionPoint.set(theRay.direction); + theIntersectionPoint.scale((float) time); + theIntersectionPoint.add(theRay.origin); + } + + return (float) time; + } + +// /** +// * @deprecated this method might contain errors. +// * @param theRay Ray3f +// * @param thePlane Plane3f +// * @param theResult Vector3f +// * @param theCullingFlag boolean +// * @return float +// */ +// public static float intersectRayTriangle(final Ray3f theRay, +// final Plane3f thePlane, +// final Vector3f theResult, +// final boolean theCullingFlag) { +// float a; +// float f; +// float u; +// float v; +// float t; +// +// _myTempEdge1.set(thePlane.vectorA); +// _myTempEdge2.set(thePlane.vectorB); +// +// h.cross(theRay.direction, _myTempEdge2); +// +// a = _myTempEdge1.dot(h); +// if (a > -EPSILON && a < EPSILON) { +// return Float.NaN; +// } +// if (theCullingFlag) { +// // u +// s.sub(theRay.origin, +// thePlane.origin); +// u = s.dot(h); +// if (u < 0.0f || u > a) { +// return Float.NaN; +// } +// // v +// v = theRay.direction.dot(q); +// if (v < 0.0f || u + v > a) { +// return Float.NaN; +// } +// // t +// q.cross(s, +// _myTempEdge1); +// t = _myTempEdge2.dot(q); +// // invert +// f = 1.0f / a; +// u *= f; +// v *= f; +// t *= f; +// } else { +// f = 1f / a; +// // u +// s.sub(theRay.origin, +// thePlane.origin); +// u = f * s.dot(h); +// if (u < 0.0f || u > 1.0f) { +// return Float.NaN; +// } +// // v +// q.cross(s, +// _myTempEdge1); +// v = f * theRay.direction.dot(q); +// if (v < 0.0 || u + v > 1.0) { +// return Float.NaN; +// } +// // t +// t = _myTempEdge2.dot(q) * f; +// } +// // result +// theResult.scale(t, +// theRay.direction); +// theResult.add(theRay.origin); +// +// return t; +// } + /** + * @deprecated this method might contain errors. + * @param theRayOrigin Vector3f + * @param theRayDirection Vector3f + * @param thePlanePointA Vector3f + * @param thePlanePointB Vector3f + * @param thePlanePointC Vector3f + * @param theResult Vector3f + * @return boolean + */ + public static boolean intersectRayPlane(final Vector3f theRayOrigin, + final Vector3f theRayDirection, + final Vector3f thePlanePointA, + final Vector3f thePlanePointB, + final Vector3f thePlanePointC, + final Vector3f theResult) { + float a; + float f; + float u; + float v; + float t; + + _myTempEdge1.sub(thePlanePointB, + thePlanePointA); + _myTempEdge2.sub(thePlanePointC, + thePlanePointA); + h.cross(theRayDirection, + _myTempEdge2); + + a = _myTempEdge1.dot(h); + if (a > -EPSILON && a < EPSILON) { + return false; // parallel + } + // u + s.sub(theRayOrigin, + thePlanePointA); + u = s.dot(h); + // v + v = theRayDirection.dot(q); + // t + q.cross(s, + _myTempEdge1); + t = _myTempEdge2.dot(q); + // invert + f = 1.0f / a; + u *= f; + v *= f; + t *= f; + + // result + theResult.scale(t, + theRayDirection); + theResult.add(theRayOrigin); + + return true; + } + + /** + * @deprecated this method might contain errors. + * @param theRay Ray3f + * @param thePlane Plane3f + * @param theResult Vector3f + * @return boolean + */ + public static boolean intersectRayPlane(final Ray3f theRay, + final Plane3f thePlane, + final Vector3f theResult) { + float a; + float f; + float u; + float v; + float t; + + _myTempEdge1.set(thePlane.vectorA); + _myTempEdge2.set(thePlane.vectorB); + h.cross(theRay.direction, + _myTempEdge2); + + a = _myTempEdge1.dot(h); + if (a > -EPSILON && a < EPSILON) { + return false; // parallel + } + // u + s.sub(theRay.origin, + thePlane.origin); + u = s.dot(h); + // v + v = theRay.direction.dot(q); + // t + q.cross(s, + _myTempEdge1); + t = _myTempEdge2.dot(q); + // invert + f = 1.0f / a; + u *= f; + v *= f; + t *= f; + + // result + theResult.scale(t, + theRay.direction); + theResult.add(theRay.origin); + + return true; + } + + /** + * @deprecated this method might contain errors. + * @param theRay Ray3f + * @param thePlane Plane3f + * @return float + */ + public static float intersectRayPlane(final Ray3f theRay, + final Plane3f thePlane) { + float a; + float f; + float u; + float v; + float t; + + _myTempEdge1.set(thePlane.vectorA); + _myTempEdge2.set(thePlane.vectorB); + h.cross(theRay.direction, _myTempEdge2); + + a = _myTempEdge1.dot(h); + if (a > -EPSILON && a < EPSILON) { + return Float.NaN; // parallel + } + // u + s.sub(theRay.origin, + thePlane.origin); + u = s.dot(h); + // v + v = theRay.direction.dot(q); + // t + q.cross(s, + _myTempEdge1); + t = _myTempEdge2.dot(q); + // invert + f = 1.0f / a; + u *= f; + v *= f; + t *= f; + + return t; + } + + /** + * Fast, Minimum Storage Ray-Triangle Intersection by Tomas Moeller & Ben + * Trumbore http://jgt.akpeters.com/papers/MollerTrumbore97/code.html + * + * @param theRayOrigin Vector3f + * @param theRayDirection Vector3f + * @param theVertex0 Vector3f + * @param theVertex1 Vector3f + * @param theVertex2 Vector3f + * @param theResult Result + * @param theCullingFlag boolean + * @return boolean + */ + public static boolean intersectRayTriangle(final Vector3f theRayOrigin, + final Vector3f theRayDirection, + final Vector3f theVertex0, + final Vector3f theVertex1, + final Vector3f theVertex2, + final IntersectionResult theResult, + final boolean theCullingFlag) { + + float det; + float inv_det; + + /* find vectors for two edges sharing vert0 */ + _myTempEdge1.sub(theVertex1, theVertex0); + _myTempEdge2.sub(theVertex2, theVertex0); + + /* begin calculating determinant - also used to calculate U parameter */ + pvec.cross(theRayDirection, _myTempEdge2); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = _myTempEdge1.dot(pvec); + + if (theCullingFlag) { /* define TEST_CULL if culling is desired */ + if (det < EPSILON) { + return false; + } + /* calculate distance from vert0 to ray origin */ + tvec.sub(theRayOrigin, theVertex0); + + /* calculate U parameter and test bounds */ + theResult.u = tvec.dot(pvec); + if (theResult.u < 0.0f || theResult.u > det) { + return false; + } + + /* prepare to test V parameter */ + qvec.cross(tvec, _myTempEdge1); + + /* calculate V parameter and test bounds */ + theResult.v = theRayDirection.dot(qvec); + if (theResult.v < 0.0f || theResult.u + theResult.v > det) { + return false; + } + + /* calculate t, scale parameters, ray intersects triangle */ + theResult.t = _myTempEdge2.dot(qvec); + inv_det = 1.0f / det; + theResult.t *= inv_det; + theResult.u *= inv_det; + theResult.v *= inv_det; + } else { /* the non-culling branch */ + if (det > -EPSILON && det < EPSILON) { + return false; + } + inv_det = 1.0f / det; + + /* calculate distance from vert0 to ray origin */ + tvec.sub(theRayOrigin, theVertex0); + + /* calculate U parameter and test bounds */ + theResult.u = tvec.dot(pvec) * inv_det; + if (theResult.u < 0.0f || theResult.u > 1.0f) { + return false; + } + + /* prepare to test V parameter */ + qvec.cross(tvec, _myTempEdge1); + + /* calculate V parameter and test bounds */ + theResult.v = theRayDirection.dot(qvec) * inv_det; + if (theResult.v < 0.0f || theResult.u + theResult.v > 1.0f) { + return false; + } + + /* calculate t, ray intersects triangle */ + theResult.t = _myTempEdge2.dot(qvec) * inv_det; + } + return true; + } + + public static class IntersectionResult { + + public float t; + + public float u; + + public float v; + + } + + public static float intersectRayTriangle(final Vector3f theRayOrigin, + final Vector3f theRayDirection, + final Vector3f thePlanePointA, + final Vector3f thePlanePointB, + final Vector3f thePlanePointC, + final Vector3f theResult, + final boolean theCullingFlag) { + float a; + float f; + float u; + float v; + float t; + + _myTempEdge1.sub(thePlanePointB, + thePlanePointA); + _myTempEdge2.sub(thePlanePointC, + thePlanePointA); + + h.cross(theRayDirection, + _myTempEdge2); + + a = _myTempEdge1.dot(h); + if (a > -EPSILON && a < EPSILON) { + return Float.NaN; + } + if (theCullingFlag) { + // u + s.sub(theRayOrigin, + thePlanePointA); + u = s.dot(h); + if (u < 0.0f || u > a) { + return Float.NaN; + } + // v + v = theRayDirection.dot(q); + if (v < 0.0f || u + v > a) { + return Float.NaN; + } + // t + q.cross(s, + _myTempEdge1); + t = _myTempEdge2.dot(q); + // invert + f = 1.0f / a; + u *= f; + v *= f; + t *= f; + } else { + f = 1f / a; + // u + s.sub(theRayOrigin, + thePlanePointA); + u = f * s.dot(h); + if (u < 0.0f || u > 1.0f) { + return Float.NaN; + } + // v + q.cross(s, + _myTempEdge1); + v = f * theRayDirection.dot(q); + if (v < 0.0 || u + v > 1.0) { + return Float.NaN; + } + // t + t = _myTempEdge2.dot(q) * f; + } + // result + theResult.scale(t, + theRayDirection); + theResult.add(theRayOrigin); + + return t; + } + + /** + * http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/raysphere.c + * Calculate the intersection of a ray and a sphere The line segment is + * defined from p1 to p2 The sphere is of radius r and centered at sc There + * are potentially two points of intersection given by p = p1 + mu1 (p2 - + * p1) p = p1 + mu2 (p2 - p1) Return FALSE if the ray doesn't intersect the + * sphere. + */ + public static boolean RaySphere(Vector3f p1, + Vector3f p2, + Vector3f sc, + float r) { + float a, b, c; + float bb4ac; + Vector3f dp = new Vector3f(); + + dp.x = p2.x - p1.x; + dp.y = p2.y - p1.y; + dp.z = p2.z - p1.z; + a = dp.x * dp.x + dp.y * dp.y + dp.z * dp.z; + b = 2 * (dp.x * (p1.x - sc.x) + dp.y * (p1.y - sc.y) + dp.z * (p1.z - sc.z)); + c = sc.x * sc.x + sc.y * sc.y + sc.z * sc.z; + c += p1.x * p1.x + p1.y * p1.y + p1.z * p1.z; + c -= 2 * (sc.x * p1.x + sc.y * p1.y + sc.z * p1.z); + c -= r * r; + bb4ac = b * b - 4 * a * c; + if (Math.abs(a) < EPSILON || bb4ac < 0) { + return false; + } + +// float mu1 = ( -b + (float) Math.sqrt(bb4ac)) / (2 * a); +// float mu2 = ( -b - (float) Math.sqrt(bb4ac)) / (2 * a); +// +// Vector3f myP1 = new Vector3f(dp); +// myP1.scale(mu1); +// myP1.add(p1); +// +// Vector3f myP2 = new Vector3f(dp); +// myP2.scale(mu2); +// myP2.add(p1); + + return true; + } + /** + * from paul bourke ( + * http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ ) + * + */ + public static final int COINCIDENT = 0; + + public static final int PARALLEL = 1; + + public static final int INTERESECTING = 2; + + public static final int NOT_INTERESECTING = 3; + + public static int lineLineIntersect(Vector2f aBegin, Vector2f aEnd, + Vector2f bBegin, Vector2f bEnd, + Vector2f theIntersection) { + float denom = ((bEnd.y - bBegin.y) * (aEnd.x - aBegin.x)) + - ((bEnd.x - bBegin.x) * (aEnd.y - aBegin.y)); + + float nume_a = ((bEnd.x - bBegin.x) * (aBegin.y - bBegin.y)) + - ((bEnd.y - bBegin.y) * (aBegin.x - bBegin.x)); + + float nume_b = ((aEnd.x - aBegin.x) * (aBegin.y - bBegin.y)) + - ((aEnd.y - aBegin.y) * (aBegin.x - bBegin.x)); + + if (denom == 0.0f) { + if (nume_a == 0.0f && nume_b == 0.0f) { + return COINCIDENT; + } + return PARALLEL; + } + + float ua = nume_a / denom; + float ub = nume_b / denom; + + if (ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f) { + if (theIntersection != null) { + // Get the intersection point. + theIntersection.x = aBegin.x + ua * (aEnd.x - aBegin.x); + theIntersection.y = aBegin.y + ua * (aEnd.y - aBegin.y); + } + return INTERESECTING; + } + + return NOT_INTERESECTING; + } + + /** + * from paul bourke ( + * http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/ ) + * + * Calculate the line segment PaPb that is the shortest route between two + * lines P1P2 and P3P4. Calculate also the values of mua and mub where Pa = + * P1 + mua (P2 - P1) Pb = P3 + mub (P4 - P3) Return FALSE if no solution + * exists. + * + */ + public static boolean lineLineIntersect(Vector3f p1, Vector3f p2, Vector3f p3, Vector3f p4, + Vector3f pa, Vector3f pb, + float[] theResult) { + + final Vector3f p13 = mathematik.Util.sub(p1, p3); + final Vector3f p43 = mathematik.Util.sub(p4, p3); + if (Math.abs(p43.x) < EPSILON && Math.abs(p43.y) < EPSILON && Math.abs(p43.z) < EPSILON) { + return false; + } + + final Vector3f p21 = mathematik.Util.sub(p2, p1); + if (Math.abs(p21.x) < EPSILON && Math.abs(p21.y) < EPSILON && Math.abs(p21.z) < EPSILON) { + return false; + } + + final float d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z; + final float d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z; + final float d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z; + final float d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z; + final float d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z; + + final float denom = d2121 * d4343 - d4321 * d4321; + if (Math.abs(denom) < EPSILON) { + return false; + } + final float numer = d1343 * d4321 - d1321 * d4343; + + final float mua = numer / denom; + final float mub = (d1343 + d4321 * mua) / d4343; + + pa.x = p1.x + mua * p21.x; + pa.y = p1.y + mua * p21.y; + pa.z = p1.z + mua * p21.z; + pb.x = p3.x + mub * p43.x; + pb.y = p3.y + mub * p43.y; + pb.z = p3.z + mub * p43.z; + + if (theResult != null) { + theResult[0] = mua; + theResult[1] = mub; + } + return true; + } + + public static Vector3f[] intersectRaySpherePoints(Vector3f pSphereCenter, + float pSphereRadius, + Vector3f pRayDirection, + Vector3f pRayOrigin) { + // Solve quadratic equation + float a = pRayDirection.lengthSquared(); + if (a == 0.0) { + return null; + } + float b = 2.0f * (pRayOrigin.dot(pRayDirection) - pRayDirection.dot(pSphereCenter)); + Vector3f tempDiff = mathematik.Util.sub(pSphereCenter, pRayOrigin); + float c = tempDiff.lengthSquared() - (pSphereRadius * pSphereRadius); + float disc = b * b - 4 * a * c; + if (disc < 0.0f) { + return null; + } + int numIntersections; + if (disc == 0.0f) { + numIntersections = 1; + } else { + numIntersections = 2; + } + + // Atleast one intersection + Vector3f[] points = new Vector3f[numIntersections]; + float t0; + float t1 = 0.0f; + t0 = ((0.5f * (-1.0f * b + (float) Math.sqrt(disc))) / a); + if (numIntersections == 2) { + t1 = ((0.5f * (-1.0f * b - (float) Math.sqrt(disc))) / a); + } + // point 1 of intersection + points[0] = new Vector3f(pRayDirection); + points[0].scale(t0); + points[0].add(pRayOrigin); + if (numIntersections == 2) { + points[1] = new Vector3f(pRayDirection); + points[1].scale(t1); + points[1].add(pRayOrigin); + } + return points; + } + + public static void main(String[] args) { + Vector3f myP1 = new Vector3f(); + Vector3f myP2 = new Vector3f(10, 10, 10); + Vector3f myP3 = new Vector3f(10, 0, 0); + Vector3f myP4 = new Vector3f(0, 10, 10); + Vector3f myPA = new Vector3f(); + Vector3f myPB = new Vector3f(); + lineLineIntersect(myP1, myP2, myP3, myP4, myPA, myPB, null); + System.out.println(myPA); + System.out.println(myPB); + } +} diff --git a/day3/libs/mathematik/src/mathematik/Linef.java b/day3/libs/mathematik/src/mathematik/Linef.java new file mode 100755 index 0000000..659b9c8 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Linef.java @@ -0,0 +1,52 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Linef + implements Serializable { + + private static final long serialVersionUID = -1748179277316146123L; + + public T p1; + + public T p2; + + public Linef(Class theClass) { + try { + p1 = theClass.newInstance(); + p2 = theClass.newInstance(); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } catch (InstantiationException ex) { + ex.printStackTrace(); + } + } + + /* + * Linef myLine = new Linef (Vector2f.class); + * Linef myLine3f = new Linef (Vector3f.class); + */ +} diff --git a/day3/libs/mathematik/src/mathematik/Mathematik.java b/day3/libs/mathematik/src/mathematik/Mathematik.java new file mode 100755 index 0000000..c239077 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Mathematik.java @@ -0,0 +1,29 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +public interface Mathematik { + + float EPSILON = 0.00001f; + +} diff --git a/day3/libs/mathematik/src/mathematik/Matrix3f.java b/day3/libs/mathematik/src/mathematik/Matrix3f.java new file mode 100755 index 0000000..83e7d1e --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Matrix3f.java @@ -0,0 +1,619 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +/** + * a 3x3 matrix. + * + * rotation + * + * rxx ryx rzx rxy ryy rzy rxz ryy rzz + * + * scale + * + * sx 0 0 0 sy 0 0 0 sz + * + * + * also read 'The Matrix and Quaternions FAQ' at + * http://www.flipcode.com/documents/matrfaq.html + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Matrix3f + implements Serializable { + + private static final long serialVersionUID = 104839874874759581L; + + public static final int IDENTITY = 1; + + public float xx; + + public float xy; + + public float xz; + + public float yx; + + public float yy; + + public float yz; + + public float zx; + + public float zy; + + public float zz; + + private final float[] _myArrayRepresentation = new float[9]; + + private final float[] _myArray4fRepresentation = new float[16]; + + public Matrix3f() { + xx = 0.0f; + xy = 0.0f; + xz = 0.0f; + yx = 0.0f; + yy = 0.0f; + yz = 0.0f; + zx = 0.0f; + zy = 0.0f; + zz = 0.0f; + } + + public Matrix3f(Matrix3f theMatrix) { + set(theMatrix); + } + + public Matrix3f(int theType) { + switch (theType) { + case IDENTITY: + setIdentity(); + break; + } + } + + private void set(float xx, + float yx, + float zx, + float xy, + float yy, + float zy, + float xz, + float yz, + float zz) { + this.xx = xx; + this.xy = xy; + this.xz = xz; + this.yx = yx; + this.yy = yy; + this.yz = yz; + this.zx = zx; + this.zy = zy; + this.zz = zz; + } + + public final void set(float[] _myArrayRepresentation) { + xx = _myArrayRepresentation[0]; + yx = _myArrayRepresentation[1]; + zx = _myArrayRepresentation[2]; + xy = _myArrayRepresentation[3]; + yy = _myArrayRepresentation[4]; + zy = _myArrayRepresentation[5]; + xz = _myArrayRepresentation[6]; + yz = _myArrayRepresentation[7]; + zz = _myArrayRepresentation[8]; + } + + public final void set(Matrix3f theMatrix) { + xx = theMatrix.xx; + xy = theMatrix.xy; + xz = theMatrix.xz; + yx = theMatrix.yx; + yy = theMatrix.yy; + yz = theMatrix.yz; + zx = theMatrix.zx; + zy = theMatrix.zy; + zz = theMatrix.zz; + } + + public void setIdentity() { + xx = 1.0f; + xy = 0.0f; + xz = 0.0f; + yx = 0.0f; + yy = 1.0f; + yz = 0.0f; + zx = 0.0f; + zy = 0.0f; + zz = 1.0f; + } + + public final void setZero() { + xx = 0.0f; + xy = 0.0f; + xz = 0.0f; + yx = 0.0f; + yy = 0.0f; + yz = 0.0f; + zx = 0.0f; + zy = 0.0f; + zz = 0.0f; + } + + public void add(float theValue) { + xx += theValue; + xy += theValue; + xz += theValue; + yx += theValue; + yy += theValue; + yz += theValue; + zx += theValue; + zy += theValue; + zz += theValue; + } + + public void add(Matrix3f theMatrixA, + Matrix3f theMatrixB) { + xx = theMatrixA.xx + theMatrixB.xx; + xy = theMatrixA.xy + theMatrixB.xy; + xz = theMatrixA.xz + theMatrixB.xz; + yx = theMatrixA.yx + theMatrixB.yx; + yy = theMatrixA.yy + theMatrixB.yy; + yz = theMatrixA.yz + theMatrixB.yz; + zx = theMatrixA.zx + theMatrixB.zx; + zy = theMatrixA.zy + theMatrixB.zy; + zz = theMatrixA.zz + theMatrixB.zz; + } + + public void add(Matrix3f theMatrix) { + xx += theMatrix.xx; + xy += theMatrix.xy; + xz += theMatrix.xz; + yx += theMatrix.yx; + yy += theMatrix.yy; + yz += theMatrix.yz; + zx += theMatrix.zx; + zy += theMatrix.zy; + zz += theMatrix.zz; + } + + public void sub(Matrix3f theMatrixA, + Matrix3f theMatrixB) { + xx = theMatrixA.xx - theMatrixB.xx; + xy = theMatrixA.xy - theMatrixB.xy; + xz = theMatrixA.xz - theMatrixB.xz; + yx = theMatrixA.yx - theMatrixB.yx; + yy = theMatrixA.yy - theMatrixB.yy; + yz = theMatrixA.yz - theMatrixB.yz; + zx = theMatrixA.zx - theMatrixB.zx; + zy = theMatrixA.zy - theMatrixB.zy; + zz = theMatrixA.zz - theMatrixB.zz; + } + + public void sub(Matrix3f theMatrix) { + xx -= theMatrix.xx; + xy -= theMatrix.xy; + xz -= theMatrix.xz; + yx -= theMatrix.yx; + yy -= theMatrix.yy; + yz -= theMatrix.yz; + zx -= theMatrix.zx; + zy -= theMatrix.zy; + zz -= theMatrix.zz; + } + + public void transpose() { + + /* + * NOTE if the matrix is a rotation matrix ie the determinant is 1, the + * transpose is equivalent to the invers of the matrix. + */ + + float mySwap = yx; + yx = xy; + xy = mySwap; + mySwap = zx; + zx = xz; + xz = mySwap; + mySwap = zy; + zy = yz; + yz = mySwap; + } + + public void transpose(Matrix3f theMatrix) { + if (this != theMatrix) { + xx = theMatrix.xx; + xy = theMatrix.yx; + xz = theMatrix.zx; + yx = theMatrix.xy; + yy = theMatrix.yy; + yz = theMatrix.zy; + zx = theMatrix.xz; + zy = theMatrix.yz; + zz = theMatrix.zz; + } else { + transpose(); + } + } + + public final float determinant() { + return xx * (yy * zz - yz * zy) + xy * (yz * zx - yx * zz) + xz * (yx * zy - yy * zx); + } + + public final void invert() { + float myDeterminant = determinant(); + if (myDeterminant == 0.0) { + return; + } + myDeterminant = 1 / myDeterminant; + set(yy * zz - zy * yz, + zx * yz - yx * zz, + yx * zy - zx * yy, + zy * xz - xy * zz, + xx * zz - zx * xz, + zx * xy - xx * zy, + xy * yz - yy * xz, + yx * xz - xx * yz, + xx * yy - yx * xy); + multiply(myDeterminant); + } + + public final void setXAxis(Vector3f theVector) { + xx = theVector.x; + yx = theVector.y; + zx = theVector.z; + } + + public final void setYAxis(Vector3f theVector) { + xy = theVector.x; + yy = theVector.y; + zy = theVector.z; + } + + public final void setZAxis(Vector3f theVector) { + xz = theVector.x; + yz = theVector.y; + zz = theVector.z; + } + + public final void getXAxis(Vector3f theVector) { + theVector.x = xx; + theVector.y = yx; + theVector.z = zx; + } + + public final void getYAxis(Vector3f theVector) { + theVector.x = xy; + theVector.y = yy; + theVector.z = zy; + } + + public final void getZAxis(Vector3f theVector) { + theVector.x = xz; + theVector.y = yz; + theVector.z = zz; + } + + public final Vector3f getXAxis() { + return new Vector3f(xx, yx, zx); + } + + public final Vector3f getYAxis() { + return new Vector3f(xy, yy, zy); + } + + public final Vector3f getZAxis() { + return new Vector3f(xz, yz, zz); + } + + public final void setXRotation(float theRadians) { + float sin = (float) Math.sin(theRadians); + float cos = (float) Math.cos(theRadians); + + xx = 1.0f; + yx = 0.0f; + zx = 0.0f; + + xy = 0.0f; + yy = cos; + zy = sin; + + xz = 0.0f; + yz = -sin; + zz = cos; + } + + public final void setYRotation(float theRadians) { + + /** + * @todo check why these differ from 'the matrix and quaternions faq' + * + * cos 0 sin(!) 0 1 0 -sin(!) 0 cos + * + */ + float sin = (float) Math.sin(theRadians); + float cos = (float) Math.cos(theRadians); + + xx = cos; + yx = 0.0f; + zx = -sin; + + xy = 0.0f; + yy = 1.0f; + zy = 0.0f; + + xz = sin; + yz = 0.0f; + zz = cos; + } + + public final void setZRotation(float theRadians) { + float sin = (float) Math.sin(theRadians); + float cos = (float) Math.cos(theRadians); + + xx = cos; + yx = sin; + zx = 0.0f; + + xy = -sin; + yy = cos; + zy = 0.0f; + + xz = 0.0f; + yz = 0.0f; + zz = 1.0f; + } + + public final void setXYZRotation(Vector3f theRotation) { + setXYZRotation(theRotation.x, + theRotation.y, + theRotation.z); + } + + public final void setXYZRotation(float theX, + float theY, + float theZ) { + /* using radiants */ + final float a = (float) Math.cos(theX); + final float b = (float) Math.sin(theX); + final float c = (float) Math.cos(theY); + final float d = (float) Math.sin(theY); + final float e = (float) Math.cos(theZ); + final float f = (float) Math.sin(theZ); + + final float ad = a * d; + final float bd = b * d; + + xx = c * e; + yx = bd * e + a * f; + zx = -ad * e + b * f; + + xy = -c * f; + yy = -bd * f + a * e; + zy = ad * f + b * e; + + xz = d; + yz = -b * c; + zz = a * c; + } + + public final void setRotation(final Vector4f theRotation) { + final float u = theRotation.x; + final float v = theRotation.y; + final float w = theRotation.z; + + final float rcos = (float) Math.cos(theRotation.w); + final float rsin = (float) Math.sin(theRotation.w); + + xx = rcos + u * u * (1 - rcos); + yx = w * rsin + v * u * (1 - rcos); + zx = -v * rsin + w * u * (1 - rcos); + + xy = -w * rsin + u * v * (1 - rcos); + yy = rcos + v * v * (1 - rcos); + zy = u * rsin + w * v * (1 - rcos); + + xz = v * rsin + u * w * (1 - rcos); + yz = -u * rsin + v * w * (1 - rcos); + zz = rcos + w * w * (1 - rcos); + } + + public final void multiply(float theValue) { + xx *= theValue; + xy *= theValue; + xz *= theValue; + yx *= theValue; + yy *= theValue; + yz *= theValue; + zx *= theValue; + zy *= theValue; + zz *= theValue; + } + + public final void multiply(Matrix3f theMatrix) { + float tmp1 = xx * theMatrix.xx + xy * theMatrix.yx + xz * theMatrix.zx; + float tmp2 = xx * theMatrix.xy + xy * theMatrix.yy + xz * theMatrix.zy; + float tmp3 = xx * theMatrix.xz + xy * theMatrix.yz + xz * theMatrix.zz; + float tmp4 = yx * theMatrix.xx + yy * theMatrix.yx + yz * theMatrix.zx; + float tmp5 = yx * theMatrix.xy + yy * theMatrix.yy + yz * theMatrix.zy; + float tmp6 = yx * theMatrix.xz + yy * theMatrix.yz + yz * theMatrix.zz; + float tmp7 = zx * theMatrix.xx + zy * theMatrix.yx + zz * theMatrix.zx; + float tmp8 = zx * theMatrix.xy + zy * theMatrix.yy + zz * theMatrix.zy; + float tmp9 = zx * theMatrix.xz + zy * theMatrix.yz + zz * theMatrix.zz; + xx = tmp1; + xy = tmp2; + xz = tmp3; + yx = tmp4; + yy = tmp5; + yz = tmp6; + zx = tmp7; + zy = tmp8; + zz = tmp9; + } + + public final void transform(Vector3f theVector) { + theVector.set(xx * theVector.x + yx * theVector.y + zx * theVector.z, + xy * theVector.x + yy * theVector.y + zy * theVector.z, + theVector.z = xz * theVector.x + yz * theVector.y + zz * theVector.z); + } + + public void setScale(Vector3f theScale) { + xx = theScale.x; + yx = 0.0f; + zx = 0.0f; + + xy = 0.0f; + yy = theScale.y; + zy = 0.0f; + + xz = 0.0f; + yz = 0.0f; + zz = theScale.z; + } + + public final float[] toArray() { + _myArrayRepresentation[0] = xx; + _myArrayRepresentation[1] = yx; + _myArrayRepresentation[2] = zx; + _myArrayRepresentation[3] = xy; + _myArrayRepresentation[4] = yy; + _myArrayRepresentation[5] = zy; + _myArrayRepresentation[6] = xz; + _myArrayRepresentation[7] = yz; + _myArrayRepresentation[8] = zz; + return _myArrayRepresentation; + } + + public final float[] toArray4f() { + /* so that opengl can understand it */ + _myArray4fRepresentation[0] = xx; + _myArray4fRepresentation[1] = yx; + _myArray4fRepresentation[2] = zx; + _myArray4fRepresentation[3] = 0; + _myArray4fRepresentation[4] = xy; + _myArray4fRepresentation[5] = yy; + _myArray4fRepresentation[6] = zy; + _myArray4fRepresentation[7] = 0; + _myArray4fRepresentation[8] = xz; + _myArray4fRepresentation[9] = yz; + _myArray4fRepresentation[10] = zz; + _myArray4fRepresentation[11] = 0; + _myArray4fRepresentation[12] = 0; + _myArray4fRepresentation[13] = 0; + _myArray4fRepresentation[14] = 0; + _myArray4fRepresentation[15] = 1; + return _myArray4fRepresentation; + } + + public String toString() { + return xx + ", " + yx + ", " + zx + "\n" + xy + ", " + yy + ", " + zy + "\n" + xz + ", " + yz + ", " + zz; + } + + public static void main(String[] args) { + Matrix3f myMatrix; + + { + /* invert and transpose */ + + System.out.println("### invert and transpose\n"); + + myMatrix = new Matrix3f(Matrix3f.IDENTITY); + myMatrix.setXYZRotation(new Vector3f(0.2f, 0.3f, 0.4f)); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.transpose(); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.transpose(); + System.out.println(myMatrix); + System.out.println(); + + myMatrix = new Matrix3f(Matrix3f.IDENTITY); + myMatrix.setXYZRotation(new Vector3f(0.2f, 0.3f, 0.4f)); + + myMatrix.invert(); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.invert(); + System.out.println(myMatrix); + System.out.println(); + } + + { + /* x */ + + myMatrix = new Matrix3f(Matrix3f.IDENTITY); + + System.out.println("### rotation x\n"); + + myMatrix.setXYZRotation(new Vector3f(0.2f, 0.0f, 0.0f)); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.setXRotation(0.2f); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.setRotation(new Vector4f(1, 0, 0, 0.2f)); + System.out.println(myMatrix); + System.out.println(); + + /* y */ + + System.out.println("### rotation y\n"); + + myMatrix.setXYZRotation(new Vector3f(0.0f, 0.3f, 0.0f)); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.setYRotation(0.3f); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.setRotation(new Vector4f(0, 1, 0, 0.3f)); + System.out.println(myMatrix); + System.out.println(); + + /* z */ + + System.out.println("### rotation z\n"); + + myMatrix.setXYZRotation(new Vector3f(0.0f, 0.0f, 0.4f)); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.setZRotation(0.4f); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.setRotation(new Vector4f(0, 0, 1, 0.4f)); + System.out.println(myMatrix); + System.out.println(); + } + } +} diff --git a/day3/libs/mathematik/src/mathematik/Matrix4f.java b/day3/libs/mathematik/src/mathematik/Matrix4f.java new file mode 100755 index 0000000..9456358 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Matrix4f.java @@ -0,0 +1,433 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +/** + * a more general 4x4 matrix. + * + * xx yx zx wx xy yy zy wy xz yz zz wz xw yw zw ww + * + * also read 'The Matrix and Quaternions FAQ' at + * http://www.flipcode.com/documents/matrfaq.html + */ +package mathematik; + + +import java.io.Serializable; + + +public class Matrix4f + implements Serializable { + + private static final long serialVersionUID = -1088603850006298206L; + + public float xx; + + public float xy; + + public float xz; + + public float xw; + + public float yx; + + public float yy; + + public float yz; + + public float yw; + + public float zx; + + public float zy; + + public float zz; + + public float zw; + + public float wx; + + public float wy; + + public float wz; + + public float ww; + + private final float[] _myArrayRepresentation = new float[16]; + + public Matrix4f() { + } + + public Matrix4f(TransformMatrix4f theTransformMatrix) { + xx = theTransformMatrix.rotation.xx; + yx = theTransformMatrix.rotation.yx; + zx = theTransformMatrix.rotation.zx; + wx = 0; + + xy = theTransformMatrix.rotation.xy; + yy = theTransformMatrix.rotation.yy; + zy = theTransformMatrix.rotation.zy; + wy = 0; + + xz = theTransformMatrix.rotation.xz; + yz = theTransformMatrix.rotation.yz; + zz = theTransformMatrix.rotation.zz; + wz = 0; + + xw = theTransformMatrix.translation.x; + yw = theTransformMatrix.translation.y; + zw = theTransformMatrix.translation.z; + ww = 1; + } + + public Matrix4f(Matrix4f theTransformMatrix) { + xx = theTransformMatrix.xx; + yx = theTransformMatrix.yx; + zx = theTransformMatrix.zx; + wx = theTransformMatrix.wx; + + xy = theTransformMatrix.xy; + yy = theTransformMatrix.yy; + zy = theTransformMatrix.zy; + wy = theTransformMatrix.wy; + + xz = theTransformMatrix.xz; + yz = theTransformMatrix.yz; + zz = theTransformMatrix.zz; + wz = theTransformMatrix.wz; + + xw = theTransformMatrix.xw; + yw = theTransformMatrix.yw; + zw = theTransformMatrix.zw; + ww = theTransformMatrix.ww; + } + + public Matrix4f(float[] theMatrix) { + set(theMatrix); + } + + public Matrix4f(double[] theMatrix) { + set(theMatrix); + } + + public Matrix4f(float xx, + float yx, + float zx, + float wx, + float xy, + float yy, + float zy, + float wy, + float xz, + float yz, + float zz, + float wz, + float xw, + float yw, + float zw, + float ww) { + this.xx = xx; + this.xy = xy; + this.xz = xz; + this.xw = xw; + + this.yx = yx; + this.yy = yy; + this.yz = yz; + this.yw = yw; + + this.zx = zx; + this.zy = zy; + this.zz = zz; + this.zw = zw; + + this.wx = wx; + this.wy = wy; + this.wz = wz; + this.ww = ww; + } + + public void set(float[] theMatrix) { + xx = theMatrix[0]; + yx = theMatrix[1]; + zx = theMatrix[2]; + wx = theMatrix[3]; + + xy = theMatrix[4]; + yy = theMatrix[5]; + zy = theMatrix[6]; + wy = theMatrix[7]; + + xz = theMatrix[8]; + yz = theMatrix[9]; + zz = theMatrix[10]; + wz = theMatrix[11]; + + xw = theMatrix[12]; + yw = theMatrix[13]; + zw = theMatrix[14]; + ww = theMatrix[15]; + } + + public void set(double[] theMatrix) { + xx = (float) theMatrix[0]; + yx = (float) theMatrix[1]; + zx = (float) theMatrix[2]; + wx = (float) theMatrix[3]; + + xy = (float) theMatrix[4]; + yy = (float) theMatrix[5]; + zy = (float) theMatrix[6]; + wy = (float) theMatrix[7]; + + xz = (float) theMatrix[8]; + yz = (float) theMatrix[9]; + zz = (float) theMatrix[10]; + wz = (float) theMatrix[11]; + + xw = (float) theMatrix[12]; + yw = (float) theMatrix[13]; + zw = (float) theMatrix[14]; + ww = (float) theMatrix[15]; + } + + public void set(float xx, + float yx, + float zx, + float wx, + float xy, + float yy, + float zy, + float wy, + float xz, + float yz, + float zz, + float wz, + float xw, + float yw, + float zw, + float ww) { + this.xx = xx; + this.xy = xy; + this.xz = xz; + this.xw = xw; + + this.yx = yx; + this.yy = yy; + this.yz = yz; + this.yw = yw; + + this.zx = zx; + this.zy = zy; + this.zz = zz; + this.zw = zw; + + this.wx = wx; + this.wy = wy; + this.wz = wz; + this.ww = ww; + } + + public void invert() { + float myDeterminant = determinant(); + if (myDeterminant == 0.0) { + return; + } + myDeterminant = 1 / myDeterminant; + set(yy * (zz * ww - zw * wz) + yz * (zw * wy - zy * ww) + yw * (zy * wz - zz * wy), + zy * (xz * ww - xw * wz) + zz * (xw * wy - xy * ww) + zw * (xy * wz - xz * wy), + wy * (xz * yw - xw * yz) + wz * (xw * yy - xy * yw) + ww * (xy * yz - xz * yy), + xy * (yw * zz - yz * zw) + xz * (yy * zw - yw * zy) + xw * (yz * zy - yy * zz), + yz * (zx * ww - zw * wx) + yw * (zz * wx - zx * wz) + yx * (zw * wz - zz * ww), + zz * (xx * ww - xw * wx) + zw * (xz * wx - xx * wz) + zx * (xw * wz - xz * ww), + wz * (xx * yw - xw * yx) + ww * (xz * yx - xx * yz) + wx * (xw * yz - xz * yw), + xz * (yw * zx - yx * zw) + xw * (yx * zz - yz * zx) + xx * (yz * zw - yw * zz), + yw * (zx * wy - zy * wx) + yx * (zy * ww - zw * wy) + yy * (zw * wx - zx * ww), + zw * (xx * wy - xy * wx) + zx * (xy * ww - xw * wy) + zy * (xw * wx - xx * ww), + ww * (xx * yy - xy * yx) + wx * (xy * yw - xw * yy) + wy * (xw * yx - xx * yw), + xw * (yy * zx - yx * zy) + xx * (yw * zy - yy * zw) + xy * (yx * zw - yw * zx), + yx * (zz * wy - zy * wz) + yy * (zx * wz - zz * wx) + yz * (zy * wx - zx * wy), + zx * (xz * wy - xy * wz) + zy * (xx * wz - xz * wx) + zz * (xy * wx - xx * wy), + wx * (xz * yy - xy * yz) + wy * (xx * yz - xz * yx) + wz * (xy * yx - xx * yy), + xx * (yy * zz - yz * zy) + xy * (yz * zx - yx * zz) + xz * (yx * zy - yy * zx)); + + multiply(myDeterminant); + } + + public void multiply(float scalar) { + xx *= scalar; + xy *= scalar; + xz *= scalar; + xw *= scalar; + yx *= scalar; + yy *= scalar; + yz *= scalar; + yw *= scalar; + zx *= scalar; + zy *= scalar; + zz *= scalar; + zw *= scalar; + wx *= scalar; + wy *= scalar; + wz *= scalar; + ww *= scalar; + } + + public final void multiply(Matrix4f theMatrix) { + multiply(this, + theMatrix); + } + + public final void multiply(Matrix4f theA, + Matrix4f theB) { + /** + * @todo here we still have an ugly bug :( + */ + set(theA.xx * theB.xx + theA.yx * theB.xy + theA.zx * theB.xz + theA.wx * theB.xw, + theA.xx * theB.yx + theA.yx * theB.yy + theA.zx * theB.yz + theA.wx * theB.yw, + theA.xx * theB.zx + theA.yx * theB.zy + theA.zx * theB.zz + theA.wx * theB.zw, + theA.xx * theB.wx + theA.yx * theB.wy + theA.zx * theB.wz + theA.wx * theB.ww, + theA.xy * theB.xx + theA.yy * theB.xy + theA.zy * theB.xz + theA.wy * theB.xw, + theA.xy * theB.yx + theA.yy * theB.yy + theA.zy * theB.yz + theA.wy * theB.yw, + theA.xy * theB.zx + theA.yy * theB.zy + theA.zy * theB.zz + theA.wy * theB.zw, + theA.xy * theB.wx + theA.yy * theB.wy + theA.zy * theB.wz + theA.wy * theB.ww, + theA.xz * theB.xx + theA.yz * theB.xy + theA.zz * theB.xz + theA.wz * theB.xw, + theA.xz * theB.yx + theA.yz * theB.yy + theA.zz * theB.yz + theA.wz * theB.yw, + theA.xz * theB.zx + theA.yz * theB.zy + theA.zz * theB.zz + theA.wz * theB.zw, + theA.xz * theB.wx + theA.yz * theB.wy + theA.zz * theB.wz + theA.wz * theB.ww, + theA.xw * theB.xx + theA.yw * theB.xy + theA.zw * theB.xz + theA.ww * theB.xw, + theA.xw * theB.yx + theA.yw * theB.yy + theA.zw * theB.yz + theA.ww * theB.yw, + theA.xw * theB.zx + theA.yw * theB.zy + theA.zw * theB.zz + theA.ww * theB.zw, + theA.xw * theB.wx + theA.yw * theB.wy + theA.zw * theB.wz + theA.ww * theB.ww); + } + + public float determinant() { + /** + * @todo check if this is correct + */ + return (xx * yy - xy * yx) * (zz * ww - zw * wz) - (xx * yz - xz * yx) * (zy * ww - zw * wy) + + (xx * yw - xw * yx) * (zy * wz - zz * wy) + (xy * yz - xz * yy) * (zx * ww - zw * wx) + - (xy * yw - xw * yy) * (zx * wz - zz * wx) + (xz * yw - xw * yz) * (zx * wy - zy * wx); + } + + public final void transform(Vector3f theResult) { + theResult.set(xx * theResult.x + xy * theResult.y + xz * theResult.z + xw, + yx * theResult.x + yy * theResult.y + yz * theResult.z + yw, + zx * theResult.x + zy * theResult.y + zz * theResult.z + zw); + } + + public final void transform(Vector4f theVector) { + theVector.set(xx * theVector.x + xy * theVector.y + xz * theVector.z + xw * theVector.w, + yx * theVector.x + yy * theVector.y + yz * theVector.z + yw * theVector.w, + zx * theVector.x + zy * theVector.y + zz * theVector.z + zw * theVector.w, + wx * theVector.x + wy * theVector.y + wz * theVector.z + ww * theVector.w); + } + + public float[] toArray() { + /* opengl format */ + _myArrayRepresentation[0] = xx; + _myArrayRepresentation[1] = yx; + _myArrayRepresentation[2] = zx; + _myArrayRepresentation[3] = wx; + + _myArrayRepresentation[4] = xy; + _myArrayRepresentation[5] = yy; + _myArrayRepresentation[6] = zy; + _myArrayRepresentation[7] = wy; + + _myArrayRepresentation[8] = xz; + _myArrayRepresentation[9] = yz; + _myArrayRepresentation[10] = zz; + _myArrayRepresentation[11] = wz; + + _myArrayRepresentation[12] = wx; + _myArrayRepresentation[13] = wy; + _myArrayRepresentation[14] = wz; + _myArrayRepresentation[15] = ww; + return _myArrayRepresentation; + } + + public final void transpose() { + float swap = yx; + yx = xy; + xy = swap; + swap = zx; + zx = xz; + xz = swap; + swap = wx; + wx = xw; + xw = swap; + swap = zy; + zy = yz; + yz = swap; + swap = wy; + wy = yw; + yw = swap; + swap = wz; + wz = zw; + zw = swap; + } + + public String toString() { + return xx + ", " + yx + ", " + zx + ", " + wx + "\n" + xy + ", " + yy + ", " + zy + ", " + wy + "\n" + xz + + ", " + yz + ", " + zz + ", " + wz + "\n" + xw + ", " + yw + ", " + zw + ", " + ww; + } + + public static void main(String[] args) { + TransformMatrix4f myTransMatrix = new TransformMatrix4f(TransformMatrix4f.IDENTITY); + myTransMatrix.rotation.setXYZRotation(new Vector3f(0.2f, 0.3f, 0.4f)); + myTransMatrix.translation.set(2, + 3, + 4); + + System.out.println("### transform matrix\n"); + + System.out.println(myTransMatrix); + System.out.println(); + + Matrix4f myMatrix = new Matrix4f(myTransMatrix); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.invert(); + System.out.println(myMatrix); + System.out.println(); + + myMatrix.invert(); + System.out.println(myMatrix); + System.out.println(); + + /* transform */ + Vector3f myVector3f = new Vector3f(5, 8, 7); + Vector4f myVector4f = new Vector4f(5, 8, 7, 1); + Vector3f myVectorOther3f = new Vector3f(5, 8, 7); + + System.out.println(); + + myMatrix.transform(myVector3f); + System.out.println(myVector3f); + + myMatrix.transform(myVector4f); + System.out.println(myVector4f); + + myTransMatrix.transform(myVectorOther3f); + System.out.println(myVectorOther3f); + } +} diff --git a/day3/libs/mathematik/src/mathematik/PerlinNoise.java b/day3/libs/mathematik/src/mathematik/PerlinNoise.java new file mode 100755 index 0000000..f327a02 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/PerlinNoise.java @@ -0,0 +1,178 @@ +package mathematik; + + +import java.util.Random; + + +public abstract class PerlinNoise { + + ////////////////////////////////////////////////////////////// + // PERLIN NOISE + // [toxi 040903] + // octaves and amplitude amount per octave are now user controlled + // via the noiseDetail() function. + // [toxi 030902] + // cleaned up code and now using bagel's cosine table to speed up + // [toxi 030901] + // implementation by the german demo group farbrausch + // as used in their demo "art": http://www.farb-rausch.de/fr010src.zip + static final int PERLIN_YWRAPB = 4; + + static final int PERLIN_YWRAP = 1 << PERLIN_YWRAPB; + + static final int PERLIN_ZWRAPB = 8; + + static final int PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB; + + static final int PERLIN_SIZE = 4095; + + static int perlin_octaves = 4; // default to medium smooth + + static float perlin_amp_falloff = 0.5f; // 50% reduction/octave + + // [toxi 031112] + // new vars needed due to recent change of cos table in PGraphics + static int perlin_TWOPI, perlin_PI; + + static float[] perlin_cosTable; + + static float[] perlin; + + static Random perlinRandom; + + public static int SEED = 0; + + /** + * Computes the Perlin noise function value at point x. + */ + public static 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 static float noise(float x, float y) { + return noise(x, y, 0f); + } + + public static float noise(float x, float y, float z) { + if (perlin == null) { + if (perlinRandom == null) { + perlinRandom = new Random(SEED); + } + 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 = cosLUT; + perlin_TWOPI = perlin_PI = SINCOS_LENGTH; + 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 = (float) (x - xi); + float yf = (float) (y - yi); + float zf = (float) (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; + 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 static float noise_fsc(float i) { + // using bagel's cosine table instead + return 0.5f * (1.0f - perlin_cosTable[(int) (i * perlin_PI) % perlin_TWOPI]); + } + // precalculate sin/cos lookup tables [toxi] + // circle resolution is determined from the actual used radii + // passed to ellipse() method. this will automatically take any + // scale transformations into account too + // [toxi 031031] + // changed table's precision to 0.5 degree steps + // introduced new vars for more flexible code + static final protected float sinLUT[]; + + static final protected float cosLUT[]; + + static final protected float SINCOS_PRECISION = 0.5f; + + static final protected int SINCOS_LENGTH = (int) (360f / SINCOS_PRECISION); + + static final float DEG_TO_RAD = (float) Math.PI / 180.0f; + + static final float RAD_TO_DEG = 180.0f / (float) Math.PI; + + static { + sinLUT = new float[SINCOS_LENGTH]; + cosLUT = new float[SINCOS_LENGTH]; + for (int i = 0; i < SINCOS_LENGTH; i++) { + sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION); + cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION); + } + } +} diff --git a/day3/libs/mathematik/src/mathematik/Plane3f.java b/day3/libs/mathematik/src/mathematik/Plane3f.java new file mode 100755 index 0000000..1fd877f --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Plane3f.java @@ -0,0 +1,87 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Plane3f + implements Serializable { + + private static final long serialVersionUID = 2390391570305327484L; + + public Vector3f origin; + + public Vector3f vectorA; + + public Vector3f vectorB; + + /** + * these fields are not used by default and left uninitialized 'null' + */ + public Vector3f normal; + + public float d = Float.NaN; + + public Plane3f() { + origin = new Vector3f(); + vectorA = new Vector3f(); + vectorB = new Vector3f(); + } + + public Plane3f(Vector3f theOrigin, + Vector3f theVectorA, + Vector3f theVectorB) { + origin = theOrigin; + vectorA = theVectorA; + vectorB = theVectorB; + } + + public void updateNormal() { + if (normal == null) { + normal = new Vector3f(); + } + Util.calculateNormal(vectorA, vectorB, normal); + } + + public void updateD() { + if (normal != null) { + d = -normal.dot(origin); + } + } +// private float intersection(Vector3f a, Vector3f b) { +// /* +// * updateNormal(); +// * updateD(); +// */ +// +// float u = normal.x * a.x + +// normal.y * a.y + +// normal.z * a.z + +// d; +// u /= normal.x * (a.x - b.x) + +// normal.y * (a.y - b.y) + +// normal.z * (a.z - b.z); +// return u; +// } +} diff --git a/day3/libs/mathematik/src/mathematik/Quaternion.java b/day3/libs/mathematik/src/mathematik/Quaternion.java new file mode 100755 index 0000000..1bac311 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Quaternion.java @@ -0,0 +1,89 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +public class Quaternion { + + public float w; + + public float x; + + public float y; + + public float z; + + public Quaternion() { + reset(); + } + + public Quaternion(float theW, float theX, float theY, float theZ) { + w = theW; + x = theX; + y = theY; + z = theZ; + } + + public void reset() { + w = 1.0f; + x = 0.0f; + y = 0.0f; + z = 0.0f; + } + + public void set(float theW, Vector3f theVector3f) { + w = theW; + x = theVector3f.x; + y = theVector3f.y; + z = theVector3f.z; + } + + public void set(Quaternion theQuaternion) { + w = theQuaternion.w; + x = theQuaternion.x; + y = theQuaternion.y; + z = theQuaternion.z; + } + + public void multiply(Quaternion theA, Quaternion theB) { + w = theA.w * theB.w - theA.x * theB.x - theA.y * theB.y - theA.z * theB.z; + x = theA.w * theB.x + theA.x * theB.w + theA.y * theB.z - theA.z * theB.y; + y = theA.w * theB.y + theA.y * theB.w + theA.z * theB.x - theA.x * theB.z; + z = theA.w * theB.z + theA.z * theB.w + theA.x * theB.y - theA.y * theB.x; + } + + public Vector4f getVectorAndAngle() { + final Vector4f theResult = new Vector4f(); + + float s = (float) Math.sqrt(1.0f - w * w); + if (s < Mathematik.EPSILON) { + s = 1.0f; + } + + theResult.w = (float) Math.acos(w) * 2.0f; + theResult.x = x / s; + theResult.y = y / s; + theResult.z = z / s; + + return theResult; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Random.java b/day3/libs/mathematik/src/mathematik/Random.java new file mode 100755 index 0000000..5873716 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Random.java @@ -0,0 +1,154 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +/** + * generate random numbers.
+ * note that if different types are requested the random generator moves on + * anyway. + */ +public class Random + implements Serializable { + + private static final long serialVersionUID = -5871934750136232555L; + + private static final java.util.Random _mySeedGenerator = new java.util.Random(System.currentTimeMillis()); + + private final java.util.Random myRandomNumberGenerator; + + private static final Random _myInstance; + + static { + _myInstance = new Random(); + } + + public static float FLOAT(float theStart, float theEnd) { + return _myInstance.getFloat(theStart, theEnd); + } + + public static float INT(int theStart, int theEnd) { + return _myInstance.getInt(theStart, theEnd); + } + + public Random() { + this(_mySeedGenerator.nextLong()); + } + + public Random(long theSeed) { + myRandomNumberGenerator = new java.util.Random(theSeed); + } + + public void setSeed(long theSeed) { + myRandomNumberGenerator.setSeed(theSeed); + } + + /** + * return a random int value from theStart to theEnd, including both values. + * + * @param theStart int + * @param theEnd int + * @return int + */ + public int getInt(int theStart, + int theEnd) { + int myDiff = (theEnd + 1) - theStart; + return myRandomNumberGenerator.nextInt(myDiff) + theStart; + } + + public int getInt() { + return myRandomNumberGenerator.nextInt(); + } + + /** + * return a random float value from theStart to theEnd, excluding both + * values. + * + * @param theStart float + * @param theEnd float + * @return float + */ + public float getFloat(float theStart, + float theEnd) { + final float myDiff = theEnd - theStart; + final float myRandomValue = myRandomNumberGenerator.nextFloat() * myDiff; + return myRandomValue + theStart; + } + + public float getFloat() { + return myRandomNumberGenerator.nextFloat(); + } + +// public static float getFloat(float theStart, +// float theEnd) { +// return _mySeedGenerator.getFloat(theStart, theEnd); +// } + public Vector3f getVector3f(float theStart, float theEnd) { + return new Vector3f(getFloat(theStart, theEnd), + getFloat(theStart, theEnd), + getFloat(theStart, theEnd)); + } + + public static void main(String[] args) { + long myTime; + Random myRandom; + + myRandom = new Random(); + System.out.println(myRandom.getFloat(20, 100)); + System.out.println(myRandom.getFloat(20, 100)); + System.out.println(myRandom.getFloat(20, 100)); + System.out.println(myRandom.getFloat(20, 100)); + System.out.println("***"); + + myRandom = new Random(); + System.out.println(myRandom.getFloat(20, 100)); + System.out.println(myRandom.getInt(20, 100)); + System.out.println(myRandom.getFloat(20, 100)); + System.out.println(myRandom.getFloat(20, 100)); + System.out.println("***"); + + /* use this random generator */ + myTime = System.currentTimeMillis(); + myRandom = new Random(0); + for (int i = 0; i < 50000000; i++) { + float myValue = myRandom.getFloat(20, 100); + if (myValue < 20 || myValue > 100) { + System.out.println(i + "ERROR"); + } + } + System.out.println("DONE: " + (System.currentTimeMillis() - myTime)); + + /* use maths random generator */ + myTime = System.currentTimeMillis(); + for (int i = 0; i < 50000000; i++) { + float myValue = (float) Math.random() * (100 - 20) + 20; + if (myValue < 20 || myValue > 100) { + System.out.println(i + "ERROR"); + } + } + System.out.println("DONE: " + (System.currentTimeMillis() - myTime)); + + } +} diff --git a/day3/libs/mathematik/src/mathematik/Ray3f.java b/day3/libs/mathematik/src/mathematik/Ray3f.java new file mode 100755 index 0000000..745ea73 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Ray3f.java @@ -0,0 +1,51 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Ray3f + implements Serializable { + + private static final long serialVersionUID = -1748179277316146625L; + + public Vector3f origin; + + public Vector3f direction; + + public Ray3f() { + origin = new Vector3f(); + direction = new Vector3f(); + } + + public Ray3f(Vector3f theOrigin, + Vector3f theDirection) { + origin = theOrigin; + direction = theDirection; + } + + public String toString() { + return "origin + " + origin + " / " + " direction " + direction; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Rayf.java b/day3/libs/mathematik/src/mathematik/Rayf.java new file mode 100755 index 0000000..0e15ad2 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Rayf.java @@ -0,0 +1,60 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +/** + * @deprecated + */ +public class Rayf + implements Serializable, Vectorf { + + private static final long serialVersionUID = -1748179277316146234L; + + public T origin; + + public T direction; + + public Rayf(Class theClass) { + try { + origin = theClass.newInstance(); + direction = theClass.newInstance(); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } catch (InstantiationException ex) { + ex.printStackTrace(); + } + } + + public Rayf(T theOrigin, + T theDirection) { + origin = theOrigin; + direction = theDirection; + } + + public String toString() { + return "origin + " + origin + " / " + " direction " + direction; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Rectangle3f.java b/day3/libs/mathematik/src/mathematik/Rectangle3f.java new file mode 100755 index 0000000..02dfbce --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Rectangle3f.java @@ -0,0 +1,47 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Rectangle3f + implements Serializable { + + private static final long serialVersionUID = -7295490882726338294L; + + public Vector3f origin; + + public Vector3f scale; + + public Rectangle3f() { + origin = new Vector3f(); + scale = new Vector3f(); + } + + public Rectangle3f(Vector3f theOrigin, + Vector3f theScale) { + origin = theOrigin; + scale = theScale; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Rotation.java b/day3/libs/mathematik/src/mathematik/Rotation.java new file mode 100755 index 0000000..3000443 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Rotation.java @@ -0,0 +1,156 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +/* + * write the current rotation into the result transformation matrix. + * if the velocity sampler is enabled the velocity will averaged + * to avoid jerky turning. + */ +/** + * @todo the sampling would be more accurate if the delta time would also be + * taken into account when updating the sampler. + */ +package mathematik; + + +public class Rotation { + + private Vector3f[] _myVelocitySampler; + + private final Vector3f _myUpVector; + + private final Vector3f _myCurrentAverageVelocity; + + private final Vector3f _myAverageVelocity; + + private int _myVelocitySamplerPosition; + + private final Vector3f _myTempUpVector; + + private final Vector3f _myTempSideVector; + + private final Vector3f _myTempForwardVector; + + public Rotation() { + this(1); + } + + public Rotation(final int theVelocitySamplerSize) { + _myVelocitySamplerPosition = 0; + setVelocitySamplerSize(theVelocitySamplerSize); + _myUpVector = new Vector3f(0, 0, 1); + _myAverageVelocity = new Vector3f(); + _myCurrentAverageVelocity = new Vector3f(); + _myTempUpVector = new Vector3f(); + _myTempSideVector = new Vector3f(); + _myTempForwardVector = new Vector3f(); + } + + public void setVelocitySamplerSize(final int theVelocitySamplerSize) { + _myVelocitySampler = new Vector3f[theVelocitySamplerSize]; + for (int i = 0; i < _myVelocitySampler.length; ++i) { + _myVelocitySampler[i] = new Vector3f(); + } + } + + public void flattenVelocitySampler(final Vector3f theVelocity) { + for (int i = 0; i < _myVelocitySampler.length; i++) { + add(theVelocity); + } + } + + public void add(final Vector3f theVelocity) { + _myVelocitySamplerPosition++; + _myVelocitySamplerPosition %= _myVelocitySampler.length; + _myAverageVelocity.sub(_myVelocitySampler[_myVelocitySamplerPosition]); + _myVelocitySampler[_myVelocitySamplerPosition].set(theVelocity); + _myAverageVelocity.add(theVelocity); + _myCurrentAverageVelocity.set(_myAverageVelocity); + _myCurrentAverageVelocity.scale(1.0f / (float) _myVelocitySampler.length); + } + + public void set(final Vector3f theVelocity, + final TransformMatrix4f theResult) { + if (_myVelocitySampler.length == 1) { + pointAt(theVelocity, theResult); + } else { + add(theVelocity); + pointAt(_myCurrentAverageVelocity, theResult); + } + } + + public void set(final TransformMatrix4f theResult) { + pointAt(_myCurrentAverageVelocity, theResult); + } + + public void set(final Matrix3f theResult) { + pointAt(_myCurrentAverageVelocity, theResult); + } + + /** + * @deprecated use 'set' instead. + * @param theVelocity Vector3f + * @param theResult TransformMatrix4f + */ + public void setRotationMatrix(final Vector3f theVelocity, + final TransformMatrix4f theResult) { + set(theVelocity, theResult); + } + + public Vector3f getAverageVelocity() { + return _myCurrentAverageVelocity; + } + + public void setUpVector(final Vector3f theUpVector) { + _myUpVector.set(theUpVector); + } + + public void setUpVectorByAngle(final float roll) { + /** + * @todo this is not thought through... + */ + _myUpVector.x = (float) Math.sin(roll); + _myUpVector.z = -(float) Math.cos(roll); + _myUpVector.y = 0; + } + + private void pointAt(final Vector3f theForwardVector, + final Matrix3f theResult) { + /* get sideVector */ + _myTempSideVector.cross(_myUpVector, theForwardVector); + _myTempSideVector.normalize(); + /* get 'real' upVector */ + _myTempUpVector.cross(_myTempSideVector, theForwardVector); + _myTempUpVector.normalize(); + /* get forwardVector */ + _myTempForwardVector.set(theForwardVector); + _myTempForwardVector.normalize(); + /* fill transformation matrix */ + theResult.setXAxis(_myTempForwardVector); + theResult.setYAxis(_myTempSideVector); + theResult.setZAxis(_myTempUpVector); + } + + private void pointAt(final Vector3f theForwardVector, + final TransformMatrix4f theResult) { + pointAt(theForwardVector, theResult.rotation); + } +} diff --git a/day3/libs/mathematik/src/mathematik/TransformMatrix4f.java b/day3/libs/mathematik/src/mathematik/TransformMatrix4f.java new file mode 100755 index 0000000..f040b2b --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/TransformMatrix4f.java @@ -0,0 +1,333 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +/** + * + * special form of a 4x4 matrix. first of all the way we represent the matrix in + * this class is similar to the way opengl handles the row column issue. + * + * transform matrix -- r = rotation, t = translation + * + * rxx rxy rxz 0 ryx ryy ryz 0 rzx rzy rzz 0 tx ty tz 1 + * + * the transform matrix is special in the way that is a 3x3 matrix for rotation + * and scale and a translation vector. the remaining four values a constants. + * + * this is the way opengl specifies a 4x4 matrix and also the way 'toArray' + * returns an array. + * + * m[0] m[4] m[8] m[12] + * + * m[1] m[5] m[9] m[13] + * + * M=( ) + * + * m[2] m[6] m[10] m[14] + * + * m[3] m[7] m[11] m[15] + * + * + * here is an excerpt from the glMultMatrix man page. + * + * "In many computer languages 4x4 arrays are represented in row-major order. + * The transformations just described represent these matrices in column-major + * order. The order of the multiplication is important. For example, if the + * current transformation is a rotation, and glMultMatrix is called with a + * translation matrix, the translation is done directly on the coordinates to be + * transformed, while the rotation is done on the results of that translation." + * + * also read 'The Matrix and Quaternions FAQ' at + * http://www.flipcode.com/documents/matrfaq.html + */ +public class TransformMatrix4f + implements Serializable { + + public static int IDENTITY = 1; + + public Matrix3f rotation; + + public Vector3f translation; + + private static final long serialVersionUID = 2946060493174800199L; + + private static final float ZERO = 0; + + private static final float ONE = 1; + + private final float[] _myArrayRepresentation = new float[16]; + + public TransformMatrix4f() { + translation = new Vector3f(); + rotation = new Matrix3f(); + } + + public TransformMatrix4f(int theType) { + translation = new Vector3f(); + rotation = new Matrix3f(theType); + toArray(); + } + + public TransformMatrix4f(TransformMatrix4f theMatrix4f) { + this(); + set(theMatrix4f); + toArray(); + } + + public TransformMatrix4f(float[] theMatrixArray) { + this(); + set(theMatrixArray); + toArray(); + } + + public final void setIdentity() { + translation.set(0, + 0, + 0); + rotation.setIdentity(); + } + + public final void setRotationIdentity() { + rotation.setIdentity(); + } + + public final void setZero() { + translation.set(0, + 0, + 0); + rotation.setZero(); + } + + public final float determinant() { + float d = rotation.xx + * ((rotation.yy * rotation.zz * ONE + rotation.yz * translation.z * ZERO + + translation.y * rotation.zy * ZERO) + - translation.y * rotation.zz * ZERO - rotation.yy * translation.z * ZERO - rotation.yz + * rotation.zy * ONE); + d -= rotation.xy + * ((rotation.yx * rotation.zz * ONE + rotation.yz * translation.z * ZERO + + translation.y * rotation.zx * ZERO) + - translation.y * rotation.zz * ZERO - rotation.yx * translation.z * ZERO - rotation.yz + * rotation.zx * ONE); + d += rotation.xz + * ((rotation.yx * rotation.zy * ONE + rotation.yy * translation.z * ZERO + + translation.y * rotation.zx * ZERO) + - translation.y * rotation.zy * ZERO - rotation.yx * translation.z * ZERO - rotation.yy + * rotation.zx * ONE); + d -= translation.x + * ((rotation.yx * rotation.zy * ZERO + rotation.yy * rotation.zz * ZERO + rotation.yz * rotation.zx * ZERO) + - rotation.yz * rotation.zy * ZERO - rotation.yx * rotation.zz * ZERO - rotation.yy + * rotation.zx * ZERO); + return d; + } + + public final void set(TransformMatrix4f mat) { + rotation.set(mat.rotation); + translation.set(mat.translation); + } + + public final void set(float[] theArrayRepresentation) { + rotation.xx = theArrayRepresentation[0]; + rotation.yx = theArrayRepresentation[1]; + rotation.zx = theArrayRepresentation[2]; + /* 3 */ + rotation.xy = theArrayRepresentation[4]; + rotation.yy = theArrayRepresentation[5]; + rotation.zy = theArrayRepresentation[6]; + /* 7 */ + rotation.xz = theArrayRepresentation[8]; + rotation.yz = theArrayRepresentation[9]; + rotation.zz = theArrayRepresentation[10]; + /* 11 */ + translation.x = theArrayRepresentation[12]; + translation.y = theArrayRepresentation[13]; + translation.z = theArrayRepresentation[14]; + /* 15 */ + } + + public final void multiply(float theValue) { + rotation.xx *= theValue; + rotation.xy *= theValue; + rotation.xz *= theValue; + translation.x *= theValue; + rotation.yx *= theValue; + rotation.yy *= theValue; + rotation.yz *= theValue; + translation.y *= theValue; + rotation.zx *= theValue; + rotation.zy *= theValue; + rotation.zz *= theValue; + translation.z *= theValue; + } + + public final void multiply(float theValue, + TransformMatrix4f theMatrix4f) { + rotation.xx = theMatrix4f.rotation.xx * theValue; + rotation.xy = theMatrix4f.rotation.xy * theValue; + rotation.xz = theMatrix4f.rotation.xz * theValue; + translation.x = theMatrix4f.translation.x * theValue; + rotation.yx = theMatrix4f.rotation.yx * theValue; + rotation.yy = theMatrix4f.rotation.yy * theValue; + rotation.yz = theMatrix4f.rotation.yz * theValue; + translation.y = theMatrix4f.translation.y * theValue; + rotation.zx = theMatrix4f.rotation.zx * theValue; + rotation.zy = theMatrix4f.rotation.zy * theValue; + rotation.zz = theMatrix4f.rotation.zz * theValue; + translation.z = theMatrix4f.translation.z * theValue; + } + + public final void multiply(TransformMatrix4f theMatrix4f) { + float tmp1 = rotation.xx * theMatrix4f.rotation.xx + rotation.xy * theMatrix4f.rotation.yx + + rotation.xz * theMatrix4f.rotation.zx + translation.x * TransformMatrix4f.ZERO; + float tmp2 = rotation.xx * theMatrix4f.rotation.xy + rotation.xy * theMatrix4f.rotation.yy + + rotation.xz * theMatrix4f.rotation.zy + translation.x * TransformMatrix4f.ZERO; + float tmp3 = rotation.xx * theMatrix4f.rotation.xz + rotation.xy * theMatrix4f.rotation.yz + + rotation.xz * theMatrix4f.rotation.zz + translation.x * TransformMatrix4f.ZERO; + float tmp4 = rotation.xx * theMatrix4f.translation.x + rotation.xy * theMatrix4f.translation.y + + rotation.xz * theMatrix4f.translation.z + translation.x * TransformMatrix4f.ONE; + + float tmp5 = rotation.yx * theMatrix4f.rotation.xx + rotation.yy * theMatrix4f.rotation.yx + + rotation.yz * theMatrix4f.rotation.zx + translation.y * TransformMatrix4f.ZERO; + float tmp6 = rotation.yx * theMatrix4f.rotation.xy + rotation.yy * theMatrix4f.rotation.yy + + rotation.yz * theMatrix4f.rotation.zy + translation.y * TransformMatrix4f.ZERO; + float tmp7 = rotation.yx * theMatrix4f.rotation.xz + rotation.yy * theMatrix4f.rotation.yz + + rotation.yz * theMatrix4f.rotation.zz + translation.y * TransformMatrix4f.ZERO; + float tmp8 = rotation.yx * theMatrix4f.translation.x + rotation.yy * theMatrix4f.translation.y + + rotation.yz * theMatrix4f.translation.z + translation.y * TransformMatrix4f.ONE; + + float tmp9 = rotation.zx * theMatrix4f.rotation.xx + rotation.zy * theMatrix4f.rotation.yx + + rotation.zz * theMatrix4f.rotation.zx + translation.z * TransformMatrix4f.ZERO; + float tmp10 = rotation.zx * theMatrix4f.rotation.xy + rotation.zy * theMatrix4f.rotation.yy + + rotation.zz * theMatrix4f.rotation.zy + translation.z * TransformMatrix4f.ZERO; + float tmp11 = rotation.zx * theMatrix4f.rotation.xz + rotation.zy * theMatrix4f.rotation.yz + + rotation.zz * theMatrix4f.rotation.zz + translation.z * TransformMatrix4f.ZERO; + float tmp12 = rotation.zx * theMatrix4f.translation.x + rotation.zy * theMatrix4f.translation.y + + rotation.zz * theMatrix4f.translation.z + translation.z * TransformMatrix4f.ONE; + + /* + float temp13 = m30 * in2.m00 + + m31 * in2.m10 + + m32 * in2.m20 + + m33 * in2.m30; + float temp14 = m30 * in2.m01 + + m31 * in2.m11 + + m32 * in2.m21 + + m33 * in2.m31; + float temp15 = m30 * in2.m02 + + m31 * in2.m12 + + m32 * in2.m22 + + m33 * in2.m32; + float temp16 = m30 * in2.m03 + + m31 * in2.m13 + + m32 * in2.m23 + + m33 * in2.m33; + */ + + rotation.xx = tmp1; + rotation.xy = tmp2; + rotation.xz = tmp3; + translation.x = tmp4; + rotation.yx = tmp5; + rotation.yy = tmp6; + rotation.yz = tmp7; + translation.y = tmp8; + rotation.zx = tmp9; + rotation.zy = tmp10; + rotation.zz = tmp11; + translation.z = tmp12; + } + + public final void transform(Vector3f theResult) { + /** + * @todo check if this is right... + */ + theResult.set(rotation.xx * theResult.x + + rotation.xy * theResult.y + + rotation.xz * theResult.z + + translation.x, + rotation.yx * theResult.x + + rotation.yy * theResult.y + + rotation.yz * theResult.z + + translation.y, + rotation.zx * theResult.x + + rotation.zy * theResult.y + + rotation.zz * theResult.z + + translation.z); + } + + public final float[] toArray() { + /* so that opengl can understand it */ + _myArrayRepresentation[0] = rotation.xx; + _myArrayRepresentation[1] = rotation.yx; + _myArrayRepresentation[2] = rotation.zx; + _myArrayRepresentation[3] = ZERO; + _myArrayRepresentation[4] = rotation.xy; + _myArrayRepresentation[5] = rotation.yy; + _myArrayRepresentation[6] = rotation.zy; + _myArrayRepresentation[7] = ZERO; + _myArrayRepresentation[8] = rotation.xz; + _myArrayRepresentation[9] = rotation.yz; + _myArrayRepresentation[10] = rotation.zz; + _myArrayRepresentation[11] = ZERO; + _myArrayRepresentation[12] = translation.x; + _myArrayRepresentation[13] = translation.y; + _myArrayRepresentation[14] = translation.z; + _myArrayRepresentation[15] = ONE; + return _myArrayRepresentation; + } + + public String toString() { + return rotation.xx + ", " + rotation.yx + ", " + rotation.zx + ", " + "0.0" + "\n" + rotation.xy + ", " + + rotation.yy + ", " + rotation.zy + ", " + "0.0" + "\n" + rotation.xz + ", " + rotation.yz + ", " + + rotation.zz + ", " + "0.0" + "\n" + translation.x + ", " + translation.y + ", " + translation.z + + ", " + "1.0"; + } + + public static void main(String[] args) { + /* multiplying matrices */ + TransformMatrix4f myScaleMatrix = new TransformMatrix4f(TransformMatrix4f.IDENTITY); + myScaleMatrix.rotation.setXAxis(new Vector3f(2, 0, 0)); + myScaleMatrix.rotation.setYAxis(new Vector3f(0, 2, 0)); + myScaleMatrix.rotation.setZAxis(new Vector3f(0, 0, 2)); + + TransformMatrix4f myTranslateMatrix = new TransformMatrix4f(TransformMatrix4f.IDENTITY); + myTranslateMatrix.translation.set(2, + 3, + 4); + + myScaleMatrix.multiply(myScaleMatrix); + myScaleMatrix.multiply(myTranslateMatrix); + System.out.println(myScaleMatrix); + + /* transform position */ + System.out.println("\n### translate"); + System.out.println(myTranslateMatrix); + System.out.println(); + Vector3f myVector = new Vector3f(10, 5, 7); + myTranslateMatrix.transform(myVector); + System.out.println(myVector); + } +} diff --git a/day3/libs/mathematik/src/mathematik/Util.java b/day3/libs/mathematik/src/mathematik/Util.java new file mode 100755 index 0000000..da88373 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Util.java @@ -0,0 +1,931 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.util.Vector; + + +/** + * + * a loose collection of linear algebra methods that are not connected to a + * specific class. + * + */ +public class Util { + + public static final Vector3f AXIS_X = new Vector3f(1, 0, 0); + + public static final Vector3f AXIS_Y = new Vector3f(0, 1, 0); + + public static final Vector3f AXIS_Z = new Vector3f(0, 0, 1); + + public static final float areaTriangle(final Vector3f v0, + final Vector3f v1, + final Vector3f v2) { + final Vector3f myAB = sub(v1, v0); + final Vector3f myAC = sub(v2, v0); + final Vector3f myCross = cross(myAB, myAC); + return 0.5f * myCross.magnitude(); + } + + public float length(Vector3f theVector3f) { + return theVector3f.length(); + } + + public static final boolean isPointInTriangle(final Vector3f v0, + final Vector3f v1, + final Vector3f v2, + final Vector3f thePoint) { +// // Compute vectors +// v0 = C - A +// v1 = B - A +// v2 = P - A + + Vector3f v00 = new Vector3f(v2); + v00.sub(v0); + + Vector3f v01 = new Vector3f(v1); + v01.sub(v0); + + Vector3f v02 = new Vector3f(thePoint); + v02.sub(v0); + + // Compute dot products + + float dot00 = v00.dot(v00); + float dot01 = v00.dot(v01); + float dot02 = v00.dot(v02); + float dot11 = v01.dot(v01); + float dot12 = v01.dot(v02); + + // Compute barycentric coordinates + float invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + float u = (dot11 * dot02 - dot01 * dot12) * invDenom; + float v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // Check if point is in triangle + return (u > 0) && (v > 0) && (u + v < 1); + } + + + /* contain */ + public static final boolean contains(final Vector3f thePosition, + final WorldAxisAlignedBoundingBox theWorldAlignedBox) { + return (contains(thePosition.x, theWorldAlignedBox.position.x, theWorldAlignedBox.scale.x) + && contains(thePosition.y, theWorldAlignedBox.position.y, theWorldAlignedBox.scale.y) + && contains(thePosition.z, theWorldAlignedBox.position.z, theWorldAlignedBox.scale.z)); + } + + public static final boolean contains(final float theTestValue, + final float theContainerValue, + final float theRange) { + return (theTestValue > theContainerValue - theRange * 0.5f + && theTestValue < theContainerValue + theRange * 0.5f); + } + + public static boolean insidePolygon(Vector2f thePoint, Vector2f[] thePolygon) { + float x = thePoint.x; + float y = thePoint.y; + + int c = 0; + for (int i = 0, j = thePolygon.length - 1; i < thePolygon.length; j = i++) { + if ((((thePolygon[i].y <= y) && (y < thePolygon[j].y)) + || ((thePolygon[j].y <= y) && (y < thePolygon[i].y))) + && (x < (thePolygon[j].x - thePolygon[i].x) * (y - thePolygon[i].y) + / (thePolygon[j].y - thePolygon[i].y) + thePolygon[i].x)) { + c = (c + 1) % 2; + } + } + return c == 1; + } + + public static final boolean insidePolygon(final Vector2f thePoint, final Vector thePolygon) { + float x = thePoint.x; + float y = thePoint.y; + + int c = 0; + for (int i = 0, j = thePolygon.size() - 1; i < thePolygon.size(); j = i++) { + if ((((thePolygon.get(i).y <= y) && (y < thePolygon.get(j).y)) + || ((thePolygon.get(j).y <= y) && (y < thePolygon.get(i).y))) + && (x < (thePolygon.get(j).x - thePolygon.get(i).x) * (y - thePolygon.get(i).y) + / (thePolygon.get(j).y - thePolygon.get(i).y) + thePolygon.get(i).x)) { + c = (c + 1) % 2; + } + } + return c == 1; + } + + public static final boolean inside2DPolygon(final Vector3f thePoint, final Vector thePolygon) { + float x = thePoint.x; + float y = thePoint.y; + + int c = 0; + for (int i = 0, j = thePolygon.size() - 1; i < thePolygon.size(); j = i++) { + if ((((thePolygon.get(i).y <= y) && (y < thePolygon.get(j).y)) + || ((thePolygon.get(j).y <= y) && (y < thePolygon.get(i).y))) + && (x < (thePolygon.get(j).x - thePolygon.get(i).x) * (y - thePolygon.get(i).y) + / (thePolygon.get(j).y - thePolygon.get(i).y) + thePolygon.get(i).x)) { + c = (c + 1) % 2; + } + } + return c == 1; + } + private static final Vector3f _myTempMin = new Vector3f(); + + private static final Vector3f _myTempMax = new Vector3f(); + + public static void updateBoundingBox(final WorldAxisAlignedBoundingBox theWorldAxisAlignedBoundingBox, + final Vector3f[] myVectors) { + + if (myVectors == null || myVectors.length == 0) { + return; + } + + /* get minimum and maximum */ + _myTempMin.set(myVectors[0]); + _myTempMax.set(myVectors[0]); + + for (int i = 1; i < myVectors.length; i++) { + /* minimum */ + if (_myTempMin.x > myVectors[i].x) { + _myTempMin.x = myVectors[i].x; + } + if (_myTempMin.y > myVectors[i].y) { + _myTempMin.y = myVectors[i].y; + } + if (_myTempMin.z > myVectors[i].z) { + _myTempMin.z = myVectors[i].z; + } + /* maximum */ + if (_myTempMax.x < myVectors[i].x) { + _myTempMax.x = myVectors[i].x; + } + if (_myTempMax.y < myVectors[i].y) { + _myTempMax.y = myVectors[i].y; + } + if (_myTempMax.z < myVectors[i].z) { + _myTempMax.z = myVectors[i].z; + } + } + + /* create world aligned boundingbox */ + /* bb position */ + theWorldAxisAlignedBoundingBox.position.sub(_myTempMax, _myTempMin); + theWorldAxisAlignedBoundingBox.position.scale(0.5f); + theWorldAxisAlignedBoundingBox.position.add(_myTempMin); + /* bb scale */ + theWorldAxisAlignedBoundingBox.scale.sub(_myTempMax, _myTempMin); + theWorldAxisAlignedBoundingBox.scale.x = Math.abs(theWorldAxisAlignedBoundingBox.scale.x); + theWorldAxisAlignedBoundingBox.scale.y = Math.abs(theWorldAxisAlignedBoundingBox.scale.y); + theWorldAxisAlignedBoundingBox.scale.z = Math.abs(theWorldAxisAlignedBoundingBox.scale.z); + } + /* transforms */ + private static final Vector3f _myTempForwardVector = new Vector3f(); + + private static final Vector3f _myTempSideVector = new Vector3f(); + + private static final Vector3f _myTempUpVector = new Vector3f(); + + public static void pointAt(final TransformMatrix4f theResult, + final Vector3f theUpVector, + final Vector3f thePointAtPosition) { + + pointAt(theResult, + theResult.translation, + theUpVector, + thePointAtPosition); + +// /* forward */ +// _myTempForwardVector.sub(theResult.translation, thePointAtPosition); +// _myTempForwardVector.normalize(); +// +// /* side */ +// _myTempSideVector.cross(theUpVector, _myTempForwardVector); +// _myTempSideVector.normalize(); +// +// /* up */ +// _myTempUpVector.cross(_myTempForwardVector, _myTempSideVector); +// _myTempUpVector.normalize(); +// +// if (!_myTempSideVector.isNaN() && +// !_myTempUpVector.isNaN() && +// !_myTempForwardVector.isNaN()) { +// theResult.rotation.setXAxis(_myTempSideVector); +// theResult.rotation.setYAxis(_myTempUpVector); +// theResult.rotation.setZAxis(_myTempForwardVector); +// } + } + + public static void pointAt(final TransformMatrix4f theResult, + final Vector3f thePosition, + final Vector3f theUpVector, + final Vector3f thePointAtPosition) { + /* forward */ + _myTempForwardVector.sub(thePosition, thePointAtPosition); + _myTempForwardVector.normalize(); + + /* side */ + _myTempSideVector.cross(theUpVector, _myTempForwardVector); + _myTempSideVector.normalize(); + + /* up */ + _myTempUpVector.cross(_myTempForwardVector, _myTempSideVector); + _myTempUpVector.normalize(); + + if (!_myTempSideVector.isNaN() + && !_myTempUpVector.isNaN() + && !_myTempForwardVector.isNaN()) { + theResult.rotation.setXAxis(_myTempSideVector); + theResult.rotation.setYAxis(_myTempUpVector); + theResult.rotation.setZAxis(_myTempForwardVector); + } + } + + public static void pointAlong(final TransformMatrix4f theResult, + final Vector3f theForwardVector, + final Vector3f theUpVector) { + /* forward */ + _myTempForwardVector.set(theForwardVector); + _myTempForwardVector.normalize(); + + /* side */ + _myTempSideVector.cross(theUpVector, _myTempForwardVector); + _myTempSideVector.normalize(); + + /* up */ + _myTempUpVector.cross(_myTempForwardVector, _myTempSideVector); + _myTempUpVector.normalize(); + + if (!_myTempSideVector.isNaN() + && !_myTempUpVector.isNaN() + && !_myTempForwardVector.isNaN()) { + theResult.rotation.setXAxis(_myTempSideVector); + theResult.rotation.setYAxis(_myTempUpVector); + theResult.rotation.setZAxis(_myTempForwardVector); + } + } + + public static final void toLocalSpace(TransformMatrix4f theLocalSpace, Vector3f theLocalResult) { + theLocalResult.sub(theLocalSpace.translation); + theLocalSpace.rotation.transform(theLocalResult); + } + + public static final float bilinearInterp(final float x, final float y, + final float q00, + final float q10, + final float q01, + final float q11) { + return q00 * (1 - x) * (1 - y) + + q10 * x * (1 - y) + + q01 * (1 - x) * y + + q11 * x * y; + } + /* normal */ + private static final Vector3f TMP_BA = new Vector3f(); + + private static final Vector3f TMP_BC = new Vector3f(); + + /** + * calculate a normal from a set of three vectors. + * + * @param pointA + * @param pointB + * @param pointC + * @param theResultNormal + */ + public static final void calculateNormal(final Vector3f pointA, + final Vector3f pointB, + final Vector3f pointC, + final Vector3f theResultNormal) { + TMP_BA.sub(pointB, pointA); + TMP_BC.sub(pointC, pointB); + + theResultNormal.cross(TMP_BA, TMP_BC); + theResultNormal.normalize(); + } + + /** + * + * @param theVectorAB Vector3f + * @param theVectorBC Vector3f + * @param theResultNormal Vector3f + */ + public static void calculateNormal(final Vector3f theVectorAB, + final Vector3f theVectorBC, + final Vector3f theResultNormal) { + theResultNormal.cross(theVectorAB, theVectorBC); + theResultNormal.normalize(); + } + + /** + * + * @param pointA Vector3f + * @param pointB Vector3f + * @param pointC Vector3f + * @return Vector3f + */ + public static Vector3f createNormal(final Vector3f pointA, + final Vector3f pointB, + final Vector3f pointC) { + final Vector3f myResultNormal = new Vector3f(); + calculateNormal(pointA, + pointB, + pointC, + myResultNormal); + return myResultNormal; + } + + /** + * + * @param theVectorAB Vector3f + * @param theVectorBC Vector3f + * @return Vector3f + */ + public static Vector3f createNormal(final Vector3f theVectorAB, + final Vector3f theVectorBC) { + final Vector3f myResultNormal = new Vector3f(); + calculateNormal(theVectorAB, + theVectorBC, + myResultNormal); + return myResultNormal; + } + + public static void createNormals(float[] theVertices, float[] theNormals) { + final int NUMBER_OF_VERTEX_COMPONENTS = 3; + final int myNumberOfPoints = 3; + for (int i = 0; i < theVertices.length; + i += (myNumberOfPoints * NUMBER_OF_VERTEX_COMPONENTS)) { + Vector3f a = new Vector3f(theVertices[i + 0], theVertices[i + 1], theVertices[i + 2]); + Vector3f b = new Vector3f(theVertices[i + 3], theVertices[i + 4], theVertices[i + 5]); + Vector3f c = new Vector3f(theVertices[i + 6], theVertices[i + 7], theVertices[i + 8]); + Vector3f myNormal = new Vector3f(); + mathematik.Util.calculateNormal(a, b, c, myNormal); + + theNormals[i + 0] = myNormal.x; + theNormals[i + 1] = myNormal.y; + theNormals[i + 2] = myNormal.z; + + theNormals[i + 3] = myNormal.x; + theNormals[i + 4] = myNormal.y; + theNormals[i + 5] = myNormal.z; + + theNormals[i + 6] = myNormal.x; + theNormals[i + 7] = myNormal.y; + theNormals[i + 8] = myNormal.z; + } + } + /* distance */ + private static final Vector3f _myTempVector = new Vector3f(); + + /** + * return the distance between to points defined by two vectors. + * + * @param theVectorA Vector3f + * @param theVectorB Vector3f + * @return float + */ + public static final float distance(Vector3f theVectorA, Vector3f theVectorB) { + _myTempVector.set(theVectorA); + return _myTempVector.distance(theVectorB); + } + + + /* add */ + /** + * add two vectors and return the result in a new instance. + * + * @param theVectorA Vector4f + * @param theVectorB Vector4f + * @return Vector4f + */ + public static final Vector4f add(Vector4f theVectorA, Vector4f theVectorB) { + return new Vector4f(theVectorA.w + theVectorB.w, + theVectorA.x + theVectorB.x, + theVectorA.y + theVectorB.y, + theVectorA.z + theVectorB.z); + } + + /** + * add two vectors and return the result in a new instance. + * + * @param theVectorA Vector3f + * @param theVectorB Vector3f + * @return Vector3f + */ + public static final Vector3f add(Vector3f theVectorA, Vector3f theVectorB) { + return new Vector3f(theVectorA.x + theVectorB.x, theVectorA.y + theVectorB.y, theVectorA.z + theVectorB.z); + } + + /** + * add two vectors and return the result in a new instance. + * + * @param theVectorA Vector2f + * @param theVectorB Vector2f + * @return Vector2f + */ + public static final Vector2f add(Vector2f theVectorA, Vector2f theVectorB) { + return new Vector2f(theVectorA.x + theVectorB.x, + theVectorA.y + theVectorB.y); + } + + /** + * add two vectors and return the result in a new instance. + * + * @param theVectorA Vector3i + * @param theVectorB Vector3i + * @return Vector3i + */ + public static final Vector3i add(Vector3i theVectorA, Vector3i theVectorB) { + return new Vector3i(theVectorA.x + theVectorB.x, theVectorA.y + theVectorB.y, theVectorA.z + theVectorB.z); + } + + /** + * add two vectors and return the result in a new instance. + * + * @param theVectorA Vector2i + * @param theVectorB Vector2i + * @return Vector2i + */ + public static final Vector2i add(Vector2i theVectorA, Vector2i theVectorB) { + return new Vector2i(theVectorA.x + theVectorB.x, theVectorA.y + theVectorB.y); + } + + + /* sub */ + /** + * subtract two vectors and return the result in a new instance. + * + * @param theVectorA Vector4f + * @param theVectorB Vector4f + * @return Vector4f + */ + public static final Vector4f sub(Vector4f theVectorA, Vector4f theVectorB) { + return new Vector4f(theVectorA.w - theVectorB.w, theVectorA.x - theVectorB.x, theVectorA.y - theVectorB.y, + theVectorA.z - theVectorB.z); + } + + /** + * subtract two vectors and return the result in a new instance. + * + * @param theVectorA Vector3f + * @param theVectorB Vector3f + * @return Vector3f + */ + public static final Vector3f sub(Vector3f theVectorA, Vector3f theVectorB) { + return new Vector3f(theVectorA.x - theVectorB.x, theVectorA.y - theVectorB.y, theVectorA.z - theVectorB.z); + } + + /** + * subtract two vectors and return the result in a new instance. + * + * @param theVectorA Vector2f + * @param theVectorB Vector2f + * @return Vector2f + */ + public static final Vector2f sub(Vector2f theVectorA, Vector2f theVectorB) { + return new Vector2f(theVectorA.x - theVectorB.x, theVectorA.y - theVectorB.y); + } + + /** + * subtract two vectors and return the result in a new instance. + * + * @param theVectorA Vector3i + * @param theVectorB Vector3i + * @return Vector3i + */ + public static final Vector3i sub(Vector3i theVectorA, Vector3i theVectorB) { + return new Vector3i(theVectorA.x - theVectorB.x, + theVectorA.y - theVectorB.y, + theVectorA.z - theVectorB.z); + } + + /** + * subtract two vectors and return the result in a new instance. + * + * @param theVectorA Vector2i + * @param theVectorB Vector2i + * @return Vector2i + */ + public static final Vector2i sub(Vector2i theVectorA, Vector2i theVectorB) { + return new Vector2i(theVectorA.x - theVectorB.x, + theVectorA.y - theVectorB.y); + } + + /** + * return the cross between two vectors in a new vector. + * + * @param theVectorA Vector3f + * @param theVectorB Vector3f + * @return Vector3f + */ + public static final Vector3f cross(Vector3f theVectorA, Vector3f theVectorB) { + final Vector3f myVector = new Vector3f(); + myVector.cross(theVectorA, theVectorB); + return myVector; + } + + /** + * return a normalized vector in a new vector. + * + * @param theVector Vector3f + * @return Vector3f + */ + public static final Vector3f normalized(Vector3f theVector) { + final Vector3f myVector = new Vector3f(theVector); + myVector.normalize(); + return myVector; + } + + /** + * + * @param theVectorA Vector2f + * @param theValue float + * @return Vector2f + */ + public static final Vector2f scale(Vector2f theVectorA, float theValue) { + return new Vector2f(theVectorA.x * theValue, + theVectorA.y * theValue); + } + + public static final Vector3f scale(Vector3f theVectorA, float theValue) { + return new Vector3f(theVectorA.x * theValue, + theVectorA.y * theValue, + theVectorA.z * theValue); + } + + public static final Vector3f scale(Vector3f theVectorA, Vector3f theValue) { + return new Vector3f(theVectorA.x * theValue.x, + theVectorA.y * theValue.y, + theVectorA.z * theValue.z); + } + + public static final Vector4f scale(Vector4f theVectorA, float theValue) { + return new Vector4f(theVectorA.x * theValue, + theVectorA.y * theValue, + theVectorA.z * theValue, + theVectorA.w * theValue); + } + + public static final float clamp(float theValue, float theMin, float theMax) { + if (theValue > theMax) { + theValue = theMax; + } + if (theValue < theMin) { + theValue = theMin; + } + return theValue; + } + + public static final float normalize(float theValue, float theStart, float theEnd) { + return (theValue - theStart) / (theEnd - theStart); + } + + public static final float map(float theValue, float theInStart, float theInEnd, float theOutStart, float theOutEnd) { + return theOutStart + (theOutEnd - theOutStart) * ((theValue - theInStart) / (theInEnd - theInStart)); + } + + public static Vector2f parseVector2f(String theString) { + String splitter; + if (contains(theString, ",")) { + splitter = ","; + } else { + throw new IllegalArgumentException(); + } + String[] coords = theString.split(splitter); + return new Vector2f(Float.parseFloat(coords[0]), + Float.parseFloat(coords[1])); + } + + public static Vector3f parseVector3f(String theString) { + String splitter; + if (contains(theString, ",")) { + splitter = ","; + } else { + throw new IllegalArgumentException(); + } + String[] coords = theString.split(splitter); + return new Vector3f(Float.parseFloat(coords[0]), + Float.parseFloat(coords[1]), + Float.parseFloat(coords[2])); + } + + public static boolean contains(String theString, CharSequence theContainedString) { + return theString.indexOf(theContainedString.toString()) > -1; + } + + /** + * the following point rotation methods are from the famous paul bourke. + * read more on his website at + * + * http://local.wasp.uwa.edu.au/~pbourke/geometry/rotate/ + * + */ + /** + * Rotate a point p by angle theta around an arbitrary axis r Return the + * rotated point. Positive angles are anticlockwise looking down the axis + * towards the origin. Assume right hand coordinate system. + * + * @param p Vector3f + * @param theta double + * @param theAxis Vector3f + * @return Vector3f + */ + public static Vector3f rotatePoint(Vector3f p, + double theta, + Vector3f theAxis) { + Vector3f myR = new Vector3f(); + Vector3f q = new Vector3f(); + double costheta, sintheta; + + myR.normalize(theAxis); + + costheta = Math.cos(theta); + sintheta = Math.sin(theta); + + q.x += (costheta + (1 - costheta) * myR.x * myR.x) * p.x; + q.x += ((1 - costheta) * myR.x * myR.y - myR.z * sintheta) * p.y; + q.x += ((1 - costheta) * myR.x * myR.z + myR.y * sintheta) * p.z; + + q.y += ((1 - costheta) * myR.x * myR.y + myR.z * sintheta) * p.x; + q.y += (costheta + (1 - costheta) * myR.y * myR.y) * p.y; + q.y += ((1 - costheta) * myR.y * myR.z - myR.x * sintheta) * p.z; + + q.z += ((1 - costheta) * myR.x * myR.z - myR.y * sintheta) * p.x; + q.z += ((1 - costheta) * myR.y * myR.z + myR.x * sintheta) * p.y; + q.z += (costheta + (1 - costheta) * myR.z * myR.z) * p.z; + + return q; + } + + + /* + * Rotate a point p by angle theta around an arbitrary line segment p1-p2 + * Return the rotated point. + * Positive angles are anticlockwise looking down the axis towards the origin. + * Assume right hand coordinate system. + */ + public static Vector3f rotatePoint(Vector3f p, + double theta, + Vector3f p1, + Vector3f p2) { + Vector3f r = new Vector3f(); + Vector3f q = new Vector3f(); + Vector3f myP = new Vector3f(); + double costheta, sintheta; + + myP.set(p); + + r.x = p2.x - p1.x; + r.y = p2.y - p1.y; + r.z = p2.z - p1.z; + myP.x -= p1.x; + myP.y -= p1.y; + myP.z -= p1.z; + + r.normalize(); + + costheta = Math.cos(theta); + sintheta = Math.sin(theta); + + q.x += (costheta + (1 - costheta) * r.x * r.x) * myP.x; + q.x += ((1 - costheta) * r.x * r.y - r.z * sintheta) * myP.y; + q.x += ((1 - costheta) * r.x * r.z + r.y * sintheta) * myP.z; + + q.y += ((1 - costheta) * r.x * r.y + r.z * sintheta) * myP.x; + q.y += (costheta + (1 - costheta) * r.y * r.y) * myP.y; + q.y += ((1 - costheta) * r.y * r.z - r.x * sintheta) * myP.z; + + q.z += ((1 - costheta) * r.x * r.z - r.y * sintheta) * myP.x; + q.z += ((1 - costheta) * r.y * r.z + r.x * sintheta) * myP.y; + q.z += (costheta + (1 - costheta) * r.z * r.z) * myP.z; + + q.x += p1.x; + q.y += p1.y; + q.z += p1.z; + + return q; + } + + /** + * + * DistancePointLine Unit Test Copyright (c) 2002, All rights reserved + * + * Damian Coventry Tuesday, 16 July 2002 + * + * Implementation of theory by Paul Bourke + * + * @param thePoint Vector3f + * @param theLineStart Vector3f + * @param theLineEnd Vector3f + * @return float + */ + public static float distancePointLineSegment(final Vector3f thePoint, + final Vector3f theLineStart, + final Vector3f theLineEnd) { + final float u = distancePointLineU(thePoint, theLineStart, theLineEnd); + + if (u < 0.0f || u > 1.0f) { + return -1; // closest point does not fall within the line segment + } + + final Vector3f myIntersection = new Vector3f(); + myIntersection.x = theLineStart.x + u * (theLineEnd.x - theLineStart.x); + myIntersection.y = theLineStart.y + u * (theLineEnd.y - theLineStart.y); + myIntersection.z = theLineStart.z + u * (theLineEnd.z - theLineStart.z); + + return thePoint.distance(myIntersection); + } + + public static float distancePointLine(final Vector3f thePoint, + final Vector3f theLineStart, + final Vector3f theLineEnd) { + final float u = distancePointLineU(thePoint, theLineStart, theLineEnd); + final Vector3f myIntersection = new Vector3f(); + myIntersection.x = theLineStart.x + u * (theLineEnd.x - theLineStart.x); + myIntersection.y = theLineStart.y + u * (theLineEnd.y - theLineStart.y); + myIntersection.z = theLineStart.z + u * (theLineEnd.z - theLineStart.z); + + return thePoint.distance(myIntersection); + } + + public static float distancePointLineU(final Vector3f thePoint, + final Vector3f theLineStart, + final Vector3f theLineEnd) { + final float myLineMagnitude = theLineStart.distance(theLineEnd); + final float u = (((thePoint.x - theLineStart.x) * (theLineEnd.x - theLineStart.x)) + + ((thePoint.y - theLineStart.y) * (theLineEnd.y - theLineStart.y)) + + ((thePoint.z - theLineStart.z) * (theLineEnd.z - theLineStart.z))) + / (myLineMagnitude * myLineMagnitude); + + return u; + } + + public static float random(float theStart, + float theEnd) { + final float myDiff = theEnd - theStart; + final float myRandomValue = (float) Math.random() * myDiff; + return myRandomValue + theStart; + } + public static final int CLOCKWISE = 1; + + public static final int COUNTERCLOCKWISE = -1; + + /** + * adapted from genius paul bourke + * http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/clockwise.htm + * + * Return the clockwise status of a curve, clockwise or counterclockwise n + * vertices making up curve p return 0 for incomputables eg: colinear points + * CLOCKWISE == 1 COUNTERCLOCKWISE == -1 It is assumed that - the polygon is + * closed - the last point is not repeated. - the polygon is simple (does + * not intersect itself or have holes) + */ + public static int isClockWise(final Vector mPoints) { + + if (mPoints.size() < 3) { + return (0); + } + + int mCount = 0; + for (int i = 0; i < mPoints.size(); i++) { + final Vector2f p1 = mPoints.get(i); + final Vector2f p2 = mPoints.get((i + 1) % mPoints.size()); + final Vector2f p3 = mPoints.get((i + 2) % mPoints.size()); + float z; + z = (p2.x - p1.x) * (p3.y - p2.y); + z -= (p2.y - p1.y) * (p3.x - p2.x); + if (z < 0) { + mCount--; + } else if (z > 0) { + mCount++; + } + } + if (mCount > 0) { + return (COUNTERCLOCKWISE); + } else if (mCount < 0) { + return (CLOCKWISE); + } else { + return (0); + } + } + + public static int isClockWise2D(final Vector mPoints) { + + if (mPoints.size() < 3) { + return (0); + } + + int mCount = 0; + for (int i = 0; i < mPoints.size(); i++) { + final Vector3f p1 = mPoints.get(i); + final Vector3f p2 = mPoints.get((i + 1) % mPoints.size()); + final Vector3f p3 = mPoints.get((i + 2) % mPoints.size()); + float z; + z = (p2.x - p1.x) * (p3.y - p2.y); + z -= (p2.y - p1.y) * (p3.x - p2.x); + if (z < 0) { + mCount--; + } else if (z > 0) { + mCount++; + } + } + if (mCount > 0) { + return (COUNTERCLOCKWISE); + } else if (mCount < 0) { + return (CLOCKWISE); + } else { + return (0); + } + } + public static final int CONVEX = 1; + + public static final int CONCAVE = -1; + + /** + * adapted from genius paul bourke + * http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/clockwise.htm + * + * Return whether a polygon in 2D is concave or convex return 0 for + * incomputables eg: colinear points CONVEX == 1 CONCAVE == -1 It is assumed + * that the polygon is simple (does not intersect itself or have holes) + */ + public static int isConvex(final Vector mPoints) { + int flag = 0; + double z; + + if (mPoints.size() < 3) { + return (0); + } + + for (int i = 0; i < mPoints.size(); i++) { + final Vector2f p1 = mPoints.get(i); + final Vector2f p2 = mPoints.get((i + 1) % mPoints.size()); + final Vector2f p3 = mPoints.get((i + 2) % mPoints.size()); + z = (p2.x - p1.x) * (p3.y - p2.y); + z -= (p2.y - p1.y) * (p3.x - p2.x); + if (z < 0) { + flag |= 1; + } else if (z > 0) { + flag |= 2; + } + if (flag == 3) { + return (CONCAVE); + } + } + if (flag != 0) { + return (CONVEX); + } else { + return (0); + } + } + + public static int isConvex2D(final Vector mPoints) { + int flag = 0; + double z; + + if (mPoints.size() < 3) { + return (0); + } + + for (int i = 0; i < mPoints.size(); i++) { + final Vector3f p1 = mPoints.get(i); + final Vector3f p2 = mPoints.get((i + 1) % mPoints.size()); + final Vector3f p3 = mPoints.get((i + 2) % mPoints.size()); + z = (p2.x - p1.x) * (p3.y - p2.y); + z -= (p2.y - p1.y) * (p3.x - p2.x); + if (z < 0) { + flag |= 1; + } else if (z > 0) { + flag |= 2; + } + if (flag == 3) { + return (CONCAVE); + } + } + if (flag != 0) { + return (CONVEX); + } else { + return (0); + } + } +} diff --git a/day3/libs/mathematik/src/mathematik/Vector2f.java b/day3/libs/mathematik/src/mathematik/Vector2f.java new file mode 100755 index 0000000..a474c8f --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Vector2f.java @@ -0,0 +1,330 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Vector2f + implements Serializable, Vectorf { + + private static final long serialVersionUID = -4652533170291767614L; + + public float x; + + public float y; + +// private float[] _myArrayRepresentation = new float[2]; + private static final float ALMOST_THRESHOLD = 0.001f; + + public Vector2f() { + x = 0.0f; + y = 0.0f; + } + + public Vector2f(float theX, + float theY) { + set(theX, + theY); + } + + public Vector2f(double theX, + double theY) { + set(theX, + theY); + } + + public Vector2f(float[] theVector) { + set(theVector); + } + + public Vector2f(double[] theVector) { + set(theVector); + } + + public Vector2f(int[] theVector) { + set(theVector); + } + + public Vector2f(Vector2f theVector) { + set(theVector); + } + + public Vector2f(Vector3f theVector) { + set(theVector.x, theVector.y); + } + + public Object clone() { + return cloneVector(); + } + + public Vector2f cloneVector() { + return new Vector2f(x, y); + } + + public final Vector2f set(float theX, + float theY) { + x = theX; + y = theY; + return this; + } + + public final Vector2f set(double theX, + double theY) { + x = (float) theX; + y = (float) theY; + return this; + } + + public final Vector2f set(float[] theVector) { + x = theVector[0]; + y = theVector[1]; + return this; + } + + public final Vector2f set(double[] theVector) { + x = (float) theVector[0]; + y = (float) theVector[1]; + return this; + } + + public final Vector2f set(int[] theVector) { + x = theVector[0]; + y = theVector[1]; + return this; + } + + public final Vector2f set(Vector2f theVector) { + x = theVector.x; + y = theVector.y; + return this; + } + + public Vector2f add(Vector2f theVectorA, + Vector2f theVectorB) { + x = theVectorA.x + theVectorB.x; + y = theVectorA.y + theVectorB.y; + return this; + } + + public Vector2f add(final int theX, final int theY) { + x += theX; + y += theY; + return this; + } + + public Vector2f add(final float theX, final float theY) { + x += theX; + y += theY; + return this; + } + + public Vector2f add(final double theX, final double theY) { + x += theX; + y += theY; + return this; + } + + public Vector2f add(Vector2f theVector) { + x += theVector.x; + y += theVector.y; + return this; + } + + public Vector2f sub(Vector2f theVectorA, + Vector2f theVectorB) { + x = theVectorA.x - theVectorB.x; + y = theVectorA.y - theVectorB.y; + return this; + } + + public Vector2f sub(Vector2f theVector) { + x -= theVector.x; + y -= theVector.y; + return this; + } + + public Vector2f sub(float theX, float theY) { + x -= theX; + y -= theY; + return this; + } + + public Vector2f scale(float theScalar, + Vector2f theVector) { + x = theScalar * theVector.x; + y = theScalar * theVector.y; + return this; + } + + public Vector2f scale(float theScalar) { + x *= theScalar; + y *= theScalar; + return this; + } + + public Vector2f scale(Vector2f theVector) { + x *= theVector.x; + y *= theVector.y; + return this; + } + + public Vector2f scale(float theXScalar, float theYScalar) { + x *= theXScalar; + y *= theYScalar; + return this; + } + + public float direction() { + return (float) Math.atan2(y, x); + } + + public float dot(Vector2f theVector) { + return x * theVector.x + y * theVector.y; + } + + public Vector2f cross(Vector2f theVector) { + x = theVector.y; + y = -theVector.x; + return this; + } + + public Vector2f cross() { + final float myX = y; + y = -x; + x = myX; + return this; + } + + public float length() { + return (float) Math.sqrt(x * x + y * y); + } + + public float lengthSquared() { + return x * x + y * y; + } + + public Vector2f normalize(Vector2f theVector) { + float inverseMag = 1.0f / (float) Math.sqrt(theVector.x * theVector.x + theVector.y * theVector.y); + x = theVector.x * inverseMag; + y = theVector.y * inverseMag; + return this; + } + + public Vector2f normalize() { + float inverseMag = 1.0f / (float) Math.sqrt(x * x + y * y); + x *= inverseMag; + y *= inverseMag; + return this; + } + + public float angle(Vector2f theVector) { + float d = dot(theVector) / (length() * theVector.length()); + if (d < -1.0f) { + d = -1.0f; + } + if (d > 1.0f) { + d = 1.0f; + } + return (float) Math.acos(d); + } + + public Vector2f rotate(float theRadians) { + final float myX = (float) (x * Math.cos(theRadians) - y * Math.sin(theRadians)); + final float myY = (float) (y * Math.cos(theRadians) + x * Math.sin(theRadians)); + x = myX; + y = myY; + return this; + } + + public final float distanceSquared(Vector2f thePoint) { + float dx = x - thePoint.x; + float dy = y - thePoint.y; + return dx * dx + dy * dy; + } + + public float distance(Vector2f thePoint) { + float dx = x - thePoint.x; + float dy = y - thePoint.y; + return (float) Math.sqrt(dx * dx + dy * dy); + } + + public float[] toArray() { + final float[] _myArrayRepresentation = new float[2]; + _myArrayRepresentation[0] = x; + _myArrayRepresentation[1] = y; + return _myArrayRepresentation; + } + + public boolean isNaN() { + if (Float.isNaN(x) || Float.isNaN(y)) { + return true; + } else { + return false; + } + } + + public boolean equals(Vector2f theVector) { + if (x == theVector.x && y == theVector.y) { + return true; + } else { + return false; + } + } + + public int hashCode() { + int hash = 5; + hash = 73 * hash + Float.floatToIntBits(this.x); + hash = 73 * hash + Float.floatToIntBits(this.y); + return hash; + } + + public boolean equals(final Object theVector) { + if (!(theVector instanceof Vector2f)) { + return false; + } + return equals((Vector2f) theVector); + } + + /** + * Returns true if both x and y are zero + * + * @return boolean, true if the vector is zero + */ + public boolean equalsZero() { + return x == 0 && y == 0; + } + + public boolean almost(Vector2f theVector) { + if (Math.abs(x - theVector.x) < ALMOST_THRESHOLD + && Math.abs(y - theVector.y) < ALMOST_THRESHOLD) { + return true; + } else { + return false; + } + } + + public String toString() { + return "(" + x + ", " + y + ")"; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Vector2i.java b/day3/libs/mathematik/src/mathematik/Vector2i.java new file mode 100755 index 0000000..13c5105 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Vector2i.java @@ -0,0 +1,123 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Vector2i + implements Serializable { + + private static final long serialVersionUID = 1578087698419866994L; + + public int x; + + public int y; + + public Vector2i() { + x = 0; + y = 0; + } + + public Vector2i(int theX, + int theY) { + set(theX, + theY); + } + + public Vector2i(Vector2i theVector) { + set(theVector); + } + + public Vector2i(int[] theVector) { + set(theVector); + } + + public void set(int theX, + int theY) { + x = theX; + y = theY; + } + + public void set(Vector2i theVector) { + x = theVector.x; + y = theVector.y; + } + + public void set(int[] theVector) { + x = theVector[0]; + y = theVector[1]; + } + + public final void sub(final Vector2i theVectorA, + final Vector2i theVectorB) { + x = theVectorA.x - theVectorB.x; + y = theVectorA.y - theVectorB.y; + } + + public final void sub(final Vector2i theVector) { + x -= theVector.x; + y -= theVector.y; + } + + public final void sub(final int theX, final int theY) { + x -= theX; + y -= theY; + } + + public final void add(final Vector2i theVectorA, + final Vector2i theVectorB) { + x = theVectorA.x + theVectorB.x; + y = theVectorA.y + theVectorB.y; + } + + public final void add(final Vector2i theVector) { + x += theVector.x; + y += theVector.y; + } + + public final void add(final int theX, final int theY) { + x += theX; + y += theY; + } + + public final void scale(float theScalar, + Vector2i theVector) { + x = (int) (theScalar * theVector.x); + y = (int) (theScalar * theVector.y); + } + + public final void scale(float theScalar) { + x *= theScalar; + y *= theScalar; + } + + public final void scale(Vector2i theVector) { + x *= theVector.x; + y *= theVector.y; + } + + public final String toString() { + return "(" + x + ", " + y + ")"; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Vector3f.java b/day3/libs/mathematik/src/mathematik/Vector3f.java new file mode 100755 index 0000000..bb8057b --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Vector3f.java @@ -0,0 +1,629 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; +import java.util.Random; + + +public class Vector3f + implements Cloneable, Serializable, Comparable, Vectorf { + + private static final long serialVersionUID = -1021495605082676516L; + + /** + * Utility for random + */ + static final Random generator = new Random(); + + /** + * The x coordinate of the vector + */ + public float x; + + /** + * The y coordinate of the vector + */ + public float y; + + /** + * The z coordinate of the vector + */ + public float z; + +// /** +// * float array for returning an array representation of the vector +// */ +// private float[] _myArrayRepresentation = new float[3]; + /** + * threshold to maintain the minimal difference between two vectors before + * they are almost the same + */ + private static final float ALMOST_THRESHOLD = 0.001f; + + /** + * Initializes a new vector. x, y, and z are set to zero. + */ + public Vector3f() { + x = 0.0f; + y = 0.0f; + z = 0.0f; + } + + /** + * Creates a new vector with the given coordinates. Inserted double values + * are casted to floats + * + * @param theX int, float or double: x coord of the new vector + * @param theY int, float or double: y coord of the new vector + * @param theZ int, float or double: z coord of the new vector + */ + public Vector3f(float theX, float theY, float theZ) { + set(theX, theY, theZ); + } + + public Vector3f(double theX, double theY, double theZ) { + set(theX, theY, theZ); + } + + public Vector3f(float theX, float theY) { + set(theX, theY); + } + + public Vector3f(double theX, double theY) { + set(theX, theY); + } + + public Vector3f(float[] theVector) { + set(theVector); + } + + public Vector3f(double[] theVector) { + set(theVector); + } + + public Vector3f(int[] theVector) { + set(theVector); + } + + public Vector3f(Vector3f theVector) { + set(theVector); + } + + public Vector3f(Vector2f theVector) { + set(theVector); + } + + public Vector3f(Vector3i theVector) { + set(theVector); + } + + public Vector3f(Vector2i theVector) { + set(theVector); + } + + public Vector3f(String theVector) { + set(theVector); + } + + public final Vector3f set(float theX, float theY, float theZ) { + x = theX; + y = theY; + z = theZ; + return this; + } + + public final Vector3f set(double theX, double theY, double theZ) { + x = (float) theX; + y = (float) theY; + z = (float) theZ; + return this; + } + + public final Vector3f set(float theX, float theY) { + x = theX; + y = theY; + return this; + } + + public final Vector3f set(double theX, double theY) { + x = (float) theX; + y = (float) theY; + return this; + } + + public final Vector3f set(float[] theVector) { + x = theVector[0]; + y = theVector[1]; + z = theVector[2]; + return this; + } + + public final Vector3f set(double[] theVector) { + x = (float) theVector[0]; + y = (float) theVector[1]; + z = (float) theVector[2]; + return this; + } + + public final Vector3f set(int[] theVector) { + x = theVector[0]; + y = theVector[1]; + z = theVector[2]; + return this; + } + + public final Vector3f set(Vector3f theVector) { + x = theVector.x; + y = theVector.y; + z = theVector.z; + return this; + } + + public final Vector3f set(Vector2f theVector) { + x = theVector.x; + y = theVector.y; + return this; + } + + public final Vector3f set(Vector3i theVector) { + x = theVector.x; + y = theVector.y; + z = theVector.z; + return this; + } + + public final Vector3f set(Vector2i theVector) { + x = theVector.x; + y = theVector.y; + return this; + } + + public final Vector3f set(String theVectorString) { + theVectorString = theVectorString.replace("(", ""); + theVectorString = theVectorString.replace(")", ""); + theVectorString = theVectorString.replaceAll(" ", ""); + String[] myComponents = theVectorString.split(","); + + try { + x = Float.parseFloat(myComponents[0]); + } catch (Exception ex) { + } + try { + y = Float.parseFloat(myComponents[1]); + } catch (Exception ex1) { + } + try { + z = Float.parseFloat(myComponents[2]); + } catch (Exception ex2) { + } + return this; + } + + public Vector3f add(Vector3f theVectorA, Vector3f theVectorB) { + x = theVectorA.x + theVectorB.x; + y = theVectorA.y + theVectorB.y; + z = theVectorA.z + theVectorB.z; + return this; + } + + public Vector3f add(Vector3f theVector) { + x += theVector.x; + y += theVector.y; + z += theVector.z; + return this; + } + + public Vector3f add(float theX, float theY) { + x += theX; + y += theY; + return this; + } + + public Vector3f add(float theX, float theY, float theZ) { + x += theX; + y += theY; + z += theZ; + return this; + } + + public Vector3f sub(Vector3f theVectorA, Vector3f theVectorB) { + x = theVectorA.x - theVectorB.x; + y = theVectorA.y - theVectorB.y; + z = theVectorA.z - theVectorB.z; + return this; + } + + public Vector3f sub(Vector3f theVector) { + x -= theVector.x; + y -= theVector.y; + z -= theVector.z; + return this; + } + + /** + * Use this method to negate a vector. The result of the negation is vector + * with the same magnitude but opposite direction. Mathematically the + * negation is the additive inverse of the vector. The sum of a value and + * its additive inerse is always zero. + * + * @shortdesc Use this method to negate a vector. + * @related scale ( ) + */ + public Vector3f negate() { + scale(-1); + return this; + } + + /** + * Use this method to scale a vector. To scale a vector each of its + * coordinates is multiplied with the given scalar. The result is a vector + * that is parallel with its origin, with a different length and possibly + * opposite direction.
+ * You can also scale a vector with another vector, in this case each coord + * of the vector is multiplied with related coord of the given vector.
+ * Another possibillity is to set and scale the vector, this means the + * vector is set to the given vector multiplied with the given scalar. + * + * @param theScalar float or int: the value the vector is scaled with + * @related divide ( ) + * @related negate ( ) + */ + public Vector3f scale(final float theScalar) { + x *= theScalar; + y *= theScalar; + z *= theScalar; + return this; + } + + /** + * + * @param theVector Vector3f: vector with the value each coord is scaled + * with + */ + public Vector3f scale(final Vector3f theVector) { + x *= theVector.x; + y *= theVector.y; + z *= theVector.z; + return this; + } + + /** + * + * @param theX float + * @param theY float + * @param theZ float + */ + public Vector3f scale(float theX, float theY, float theZ) { + x *= theX; + y *= theY; + z *= theZ; + return this; + } + + /** + * @param theScalar float or int: value the given vector is scaled with + * @param theVector Vector3f: vector the vector is set to + */ + public Vector3f scale(final float theScalar, final Vector3f theVector) { + x = theScalar * theVector.x; + y = theScalar * theVector.y; + z = theScalar * theVector.z; + return this; + } + + /** + * Dividing is nearly the the same as scaling, except + * + * @param theDivisor + */ + public Vector3f divide(final float theDivisor) { + x /= theDivisor; + y /= theDivisor; + z /= theDivisor; + return this; + } + + public Vector3f divide(final Vector3f theVector) { + x /= theVector.x; + y /= theVector.y; + z /= theVector.z; + return this; + } + + public float lengthSquared() { + return x * x + y * y + z * z; + } + + /** + * Use this method to calculate the length of a vector, the length of a + * vector is also known as its magnitude. Vectors have a magnitude and a + * direction. These values are not explicitly expressed in the vector so + * they have to be computed. + * + * @return float: the length of the vector + * @shortdesc Calculates the length of the vector. + */ + public float length() { + return (float) Math.sqrt(lengthSquared()); + } + + public float magnitude() { + return length(); + } + + /** + * Sets this vector to the cross product of two given vectors. The cross + * product returns a vector standing vertical on the two vectors. + * + * @param theVectorA + * @param theVectorB + */ + public Vector3f cross(final Vector3f theVectorA, final Vector3f theVectorB) { + return set(theVectorA.cross(theVectorB)); + } + + /** + * Returns the cross product of two vectors. The cross product returns a + * vector standing vertical on the two vectors. + * + * @param i_vector the other vector + * @return the cross product + */ + public Vector3f cross(final Vector3f theVector) { + return new Vector3f(y * theVector.z - z * theVector.y, + z * theVector.x - x * theVector.z, + x * theVector.y - y * theVector.x); + } + + /** + * Returns the dot product of two vectors. The dot product is the cosinus of + * the angle between two vectors + * + * @param theVector, the other vector + * @return float, dot product of two vectors + */ + public float dot(Vector3f theVector) { + return x * theVector.x + y * theVector.y + z * theVector.z; + } + + /** + * Sets the vector to the given one and norms it to the length of 1 + * + */ + public Vector3f normalize(Vector3f theVector) { + set(theVector); + normalize(); + return this; + } + + /** + * Norms the vector to the length of 1 + * + */ + public Vector3f normalize() { + float inverseMag = 1.0f / (float) Math.sqrt(x * x + y * y + z * z); + x *= inverseMag; + y *= inverseMag; + z *= inverseMag; + return this; + } + + /** + * Interpolates between this vector and the given vector by a given blend + * value. The blend value has to be between 0 and 1. A blend value 0 would + * change nothing, a blend value 1 would set this vector to the given one. + * + * @param blend float, blend value for interpolation + * @param i_vector Vector3f, other vector for interpolation + */ + public void interpolate(final float blend, final Vector3f i_vector) { + x = x + blend * (i_vector.x - x); + y = y + blend * (i_vector.y - y); + z = z + blend * (i_vector.z - z); + } + + /** + * Sets a position randomly distributed inside a sphere of unit radius + * centered at the origin. Orientation will be random and length will range + * between 0 and 1 + */ + public void randomize() { + x = generator.nextFloat() * 2.0F - 1.0F; + y = generator.nextFloat() * 2.0F - 1.0F; + z = generator.nextFloat() * 2.0F - 1.0F; + normalize(); + } + + public float angle(Vector3f theVector) { + float d = dot(theVector) / (length() * theVector.length()); + /** + * @todo check these lines. + */ + if (d < -1.0f) { + d = -1.0f; + } + if (d > 1.0f) { + d = 1.0f; + } + return (float) Math.acos(d); + } + + public float distanceSquared(Vector3f thePoint) { + float dx = x - thePoint.x; + float dy = y - thePoint.y; + float dz = z - thePoint.z; + return dx * dx + dy * dy + dz * dz; + } + + public float distance(Vector3f thePoint) { + float dx = x - thePoint.x; + float dy = y - thePoint.y; + float dz = z - thePoint.z; + return (float) Math.sqrt(dx * dx + dy * dy + dz * dz); + } + + public float distanceL1(Vector3f thePoint) { + return Math.abs(x - thePoint.x) + Math.abs(y - thePoint.y) + + Math.abs(z - thePoint.z); + } + + public Vector3f min(Vector3f theMin) { + if (x < theMin.x) { + x = theMin.x; + } + if (y < theMin.y) { + y = theMin.y; + } + if (z < theMin.z) { + z = theMin.z; + } + return this; + } + + public Vector3f min(float theX, float theY, float theZ) { + if (x < theX) { + x = theX; + } + if (y < theY) { + y = theY; + } + if (z < theZ) { + z = theZ; + } + return this; + } + + public Vector3f max(Vector3f theMax) { + if (x > theMax.x) { + x = theMax.x; + } + if (y > theMax.y) { + y = theMax.y; + } + if (z > theMax.z) { + z = theMax.z; + } + return this; + } + + public Vector3f max(float theX, float theY, float theZ) { + if (x > theX) { + x = theX; + } + if (y > theY) { + y = theY; + } + if (z > theZ) { + z = theZ; + } + return this; + } + + public float[] toArray() { + final float[] _myArrayRepresentation = new float[3]; + _myArrayRepresentation[0] = x; + _myArrayRepresentation[1] = y; + _myArrayRepresentation[2] = z; + return _myArrayRepresentation; + } + + public boolean isNaN() { + if (Float.isNaN(x) || Float.isNaN(y) || Float.isNaN(z)) { + return true; + } else { + return false; + } + } + + public boolean equals(final Vector3f theVector) { + if (x == theVector.x && y == theVector.y && z == theVector.z) { + return true; + } else { + return false; + } + } + + public int hashCode() { + int hash = 7; + hash = 17 * hash + Float.floatToIntBits(this.x); + hash = 17 * hash + Float.floatToIntBits(this.y); + hash = 17 * hash + Float.floatToIntBits(this.z); + return hash; + } + + public boolean equals(final Object theVector) { + if (!(theVector instanceof Vector3f)) { + return false; + } + return equals((Vector3f) theVector); + } + + public boolean almost(final Vector3f theVector) { + if (Math.abs(x - theVector.x) < ALMOST_THRESHOLD + && Math.abs(y - theVector.y) < ALMOST_THRESHOLD + && Math.abs(z - theVector.z) < ALMOST_THRESHOLD) { + return true; + } else { + return false; + } + } + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn t happen, since we are Cloneable + throw new InternalError(); + } + } + + public String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } + public static final int X = 0; + + public static final int Y = 1; + + public static final int Z = 2; + + public static final int LENGTH = 3; + + public static int COMPARE_TYPE = LENGTH; + + public int compareTo(Vector3f theVector3f) { + if (COMPARE_TYPE == LENGTH) { + final float myLengthSquared = lengthSquared(); + final float myOtherLengthSquared = theVector3f.lengthSquared(); + return myLengthSquared > myOtherLengthSquared ? 1 : (myLengthSquared < myOtherLengthSquared ? -1 : 0); + } else if (COMPARE_TYPE == X) { + return x > theVector3f.x ? 1 : (x < theVector3f.x ? -1 : 0); + } else if (COMPARE_TYPE == Y) { + return y > theVector3f.y ? 1 : (y < theVector3f.y ? -1 : 0); + } else if (COMPARE_TYPE == Z) { + return z > theVector3f.z ? 1 : (z < theVector3f.z ? -1 : 0); + } else { + return 0; + } + } +} diff --git a/day3/libs/mathematik/src/mathematik/Vector3i.java b/day3/libs/mathematik/src/mathematik/Vector3i.java new file mode 100755 index 0000000..fe47752 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Vector3i.java @@ -0,0 +1,86 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Vector3i + implements Serializable, Comparable { + + private static final long serialVersionUID = -1207335169644019377L; + + public int x; + + public int y; + + public int z; + + public Vector3i() { + x = 0; + y = 0; + z = 0; + } + + public Vector3i(int theX, int theY, int theZ) { + set(theX, theY, theZ); + } + + public Vector3i(Vector3i theVector) { + set(theVector); + } + + public Vector3i(int[] theVector) { + set(theVector); + } + + public void set(int theX, int theY, int theZ) { + x = theX; + y = theY; + z = theZ; + } + + public void set(Vector3i theVector) { + x = theVector.x; + y = theVector.y; + z = theVector.z; + } + + public void set(int[] theVector) { + x = theVector[0]; + y = theVector[1]; + z = theVector[2]; + } + + public final String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } + + public final float lengthSquared() { + return x * x + y * y + z * z; + } + + public int compareTo(Vector3i theVector3i) { + return (int) (lengthSquared() - theVector3i.lengthSquared()); + } +} diff --git a/day3/libs/mathematik/src/mathematik/Vector4f.java b/day3/libs/mathematik/src/mathematik/Vector4f.java new file mode 100755 index 0000000..da10aa8 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Vector4f.java @@ -0,0 +1,307 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +import java.io.Serializable; + + +public class Vector4f + implements Serializable, Vectorf { + + private static final long serialVersionUID = 5083919691492230155L; + + public float w; + + public float x; + + public float y; + + public float z; + + private static final float ALMOST_THRESHOLD = 0.001f; + + public Vector4f() { + x = 0; + y = 0; + z = 0; + w = 0; + } + + public Vector4f(float theX, + float theY, + float theZ, + float theW) { + set(theX, + theY, + theZ, + theW); + } + + public Vector4f(double theX, + double theY, + double theZ, + double theW) { + set(theX, + theY, + theZ, + theW); + } + + public Vector4f(float theX, + float theY, + float theZ) { + set(theX, + theY, + theZ); + } + + public Vector4f(double theX, + double theY, + double theZ) { + set(theX, + theY, + theZ); + } + + public Vector4f(float theX, + float theY) { + set(theX, + theY); + } + + public Vector4f(double theX, + double theY) { + set(theX, + theY); + } + + public Vector4f(float[] theVector) { + set(theVector); + } + + public Vector4f(double[] theVector) { + set(theVector); + } + + public Vector4f(Vector4f theVector) { + set(theVector); + } + + public Vector4f(Vector3f theVector) { + set(theVector); + } + + public Vector4f(Vector2f theVector) { + set(theVector); + } + + public final void set(float theX, + float theY, + float theZ, + float theW) { + x = theX; + y = theY; + z = theZ; + w = theW; + } + + public final void set(double theX, + double theY, + double theZ, + double theW) { + x = (float) theX; + y = (float) theY; + z = (float) theZ; + w = (float) theW; + } + + public final void set(float theX, + float theY, + float theZ) { + x = theX; + y = theY; + z = theZ; + } + + public final void set(double theX, + double theY, + double theZ) { + x = (float) theX; + y = (float) theY; + z = (float) theZ; + } + + public final void set(float theX, + float theY) { + x = theX; + y = theY; + } + + public final void set(double theX, + double theY) { + x = (float) theX; + y = (float) theY; + } + + public final void set(float[] theVector) { + w = theVector[0]; + x = theVector[1]; + y = theVector[2]; + z = theVector[3]; + } + + public final void set(double[] theVector) { + w = (float) theVector[0]; + x = (float) theVector[1]; + y = (float) theVector[2]; + z = (float) theVector[3]; + } + + public final void set(Vector4f theVector) { + x = theVector.x; + y = theVector.y; + z = theVector.z; + w = theVector.w; + } + + public final void set(Vector3f theVector) { + x = theVector.x; + y = theVector.y; + z = theVector.z; + } + + public final void set(Vector2f theVector) { + x = theVector.x; + y = theVector.y; + } + + public final void add(Vector4f theVectorA, + Vector4f theVectorB) { + w = theVectorA.w + theVectorB.w; + x = theVectorA.x + theVectorB.x; + y = theVectorA.y + theVectorB.y; + z = theVectorA.z + theVectorB.z; + } + + public final void add(Vector4f theVector) { + w += theVector.w; + x += theVector.x; + y += theVector.y; + z += theVector.z; + } + + public final void sub(Vector4f theVectorA, + Vector4f theVectorB) { + w = theVectorA.w - theVectorB.w; + x = theVectorA.x - theVectorB.x; + y = theVectorA.y - theVectorB.y; + z = theVectorA.z - theVectorB.z; + } + + public final void sub(Vector4f theVector) { + w -= theVector.w; + x -= theVector.x; + y -= theVector.y; + z -= theVector.z; + } + + public final void scale(float theScalar, + Vector4f theVector) { + w = theScalar * theVector.w; + x = theScalar * theVector.x; + y = theScalar * theVector.y; + z = theScalar * theVector.z; + } + + public final void scale(float theScalar) { + w *= theScalar; + x *= theScalar; + y *= theScalar; + z *= theScalar; + } + + public final float lengthSquared() { + return w * w + x * x + y * y + z * z; + } + + public final float length() { + return (float) Math.sqrt(w * w + x * x + y * y + z * z); + } + + public final float dot(Vector4f theVector) { + return x * theVector.x + y * theVector.y + z * theVector.z + w * theVector.w; + } + + public void normalize(Vector4f theVector) { + set(theVector); + normalize(); + } + + public final void normalize() { + final float d = 1 / length(); + x *= d; + y *= d; + z *= d; + w *= d; + } + + public final float[] toArray() { + final float[] _myArrayRepresentation = new float[4]; + _myArrayRepresentation[0] = x; + _myArrayRepresentation[1] = y; + _myArrayRepresentation[2] = z; + _myArrayRepresentation[3] = w; + return _myArrayRepresentation; + } + + public final boolean isNaN() { + if (Float.isNaN(x) || Float.isNaN(y) || Float.isNaN(z) || Float.isNaN(w)) { + return true; + } else { + return false; + } + } + + public final boolean equals(Vector4f theVector) { + if (w == theVector.w && x == theVector.x && y == theVector.y && z == theVector.z) { + return true; + } else { + return false; + } + } + + public final boolean almost(Vector4f theVector) { + if (Math.abs(w) - Math.abs(theVector.w) < ALMOST_THRESHOLD + && Math.abs(x) - Math.abs(theVector.x) < ALMOST_THRESHOLD + && Math.abs(y) - Math.abs(theVector.y) < ALMOST_THRESHOLD + && Math.abs(z) - Math.abs(theVector.z) < ALMOST_THRESHOLD) { + return true; + } else { + return false; + } + } + + public final String toString() { + return "(" + x + ", " + y + ", " + z + ", " + w + ")"; + } +} diff --git a/day3/libs/mathematik/src/mathematik/Vectorf.java b/day3/libs/mathematik/src/mathematik/Vectorf.java new file mode 100755 index 0000000..d715b27 --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/Vectorf.java @@ -0,0 +1,26 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +public interface Vectorf { +} diff --git a/day3/libs/mathematik/src/mathematik/WorldAxisAlignedBoundingBox.java b/day3/libs/mathematik/src/mathematik/WorldAxisAlignedBoundingBox.java new file mode 100755 index 0000000..b96deee --- /dev/null +++ b/day3/libs/mathematik/src/mathematik/WorldAxisAlignedBoundingBox.java @@ -0,0 +1,37 @@ +/* + * Mathematik + * + * Copyright (C) 2012 Patrick Kochlik + Dennis Paul + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package mathematik; + + +public class WorldAxisAlignedBoundingBox { + + /** + * center of bounding box + */ + public Vector3f position = new Vector3f(); + + /** + * scale around the center + */ + public Vector3f scale = new Vector3f(); + +} diff --git a/day3/libs/teilchen/examples/Lesson00_Particle/Lesson00_Particle.pde b/day3/libs/teilchen/examples/Lesson00_Particle/Lesson00_Particle.pde new file mode 100755 index 0000000..a1b283a --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson00_Particle/Lesson00_Particle.pde @@ -0,0 +1,71 @@ +import mathematik.*; + +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; + +/** + * this sketch show how to create a particle system with a single particle in it. + */ + +Physics mPhysics; + +Particle mParticle; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system. */ + mPhysics = new Physics(); + + /* + * a physic-based particle system consists of a few components. + * + * 1 particles. + * there are different kinds of particles. for now we use a simple particle. + * + * 2 forces. + * there are all kinds of forces. one of the most obvious force is the gravitational force, + * but there all kinds of different forces like attractors and springs. forces usually + * affect all particles in the system. + * + * 3 behaviors + * a behavior is special kind of force. it is something like an internal force or a motor + * that only affects a single particle. a typical force is for example the 'seek force' + * which constantly pulls a particle into a certain direction. + * + * 4 integrators. + * integrators are used to integrate acceleration and velocity to calculate the new position. + * the most well-known is the 'euler' integrator, but there are also optimized versions like 'runge-kutta' + * or 'Midpoint' or even slightly different concepts like 'verlet'. + * + */ + + /* create a particle. note that the particle is automatically added to particle system */ + mParticle = mPhysics.makeParticle(); +} + +void draw() { + /* update the particle system to the next step. usually the time step is the duration of the las frame */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particle */ + background(255); + stroke(0, 127); + fill(0, 32); + ellipse(mParticle.position().x, mParticle.position().y, 12, 12); + + /* reset particle s position and velocity */ + if (mousePressed) { + mParticle.position().set(mouseX, mouseY); + mParticle.velocity().set(mouseX - pmouseX, mouseY - pmouseY); + mParticle.velocity().scale(10); + } +} + diff --git a/day3/libs/teilchen/examples/Lesson01_Gravity/Lesson01_Gravity.pde b/day3/libs/teilchen/examples/Lesson01_Gravity/Lesson01_Gravity.pde new file mode 100755 index 0000000..3d7e16e --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson01_Gravity/Lesson01_Gravity.pde @@ -0,0 +1,54 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Gravity; + +/** + * this sketch show how to create a particle system with a single particle in it. + */ + +Physics mPhysics; + +Particle mParticle; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a gravitational force */ + Gravity mGravity = new Gravity(); + /* the direction of the gravity is defined by the 'force' vector */ + mGravity.force().set(0, 30, 0); + /* forces, like gravity or any other force, can be added to the system. they will be automatically applied to all particles */ + mPhysics.add(mGravity); + + /* create a particle and add it to the system */ + mParticle = mPhysics.makeParticle(); +} + +void draw() { + /* update the particle system. this applies the gravity to the particle */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particle */ + background(255); + stroke(0, 127); + fill(0, 32); + ellipse(mParticle.position().x, mParticle.position().y, 12, 12); + + /* reset particle s position and velocity */ + if (mousePressed) { + mParticle.position().set(mouseX, mouseY); + mParticle.velocity().set(mouseX - pmouseX, mouseY - pmouseY); + mParticle.velocity().scale(10); + } +} + diff --git a/day3/libs/teilchen/examples/Lesson02_Particles/Lesson02_Particles.pde b/day3/libs/teilchen/examples/Lesson02_Particles/Lesson02_Particles.pde new file mode 100755 index 0000000..eacf47b --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson02_Particles/Lesson02_Particles.pde @@ -0,0 +1,58 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Gravity; + +/** + * this sketch shows how to create and handle multiple particles and remove individual particles. + */ + +Physics mPhysics; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a gravitational force and add it to the particle system */ + Gravity myGravity = new Gravity(0, 30, 0); + mPhysics.add(myGravity); +} + +void draw() { + if (mousePressed) { + /* create and add a particle to the system */ + Particle mParticle = mPhysics.makeParticle(); + /* set particle to mouse position with random velocity */ + mParticle.position().set(mouseX, mouseY); + mParticle.velocity().set(random(-20, 20), random(-50)); + } + + /* update the particle system */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* remove particles right before they hit the edge of the screen */ + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle mParticle = mPhysics.particles(i); + if (mParticle.position().y > height * 0.9f) { + mPhysics.particles().remove(i); + } + } + + /* draw all the particles in the system */ + background(255); + stroke(0, 127); + fill(0, 32); + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle mParticle = mPhysics.particles(i); + ellipse(mParticle.position().x, mParticle.position().y, 10, 10); + } +} + diff --git a/day3/libs/teilchen/examples/Lesson03_Attractors/Lesson03_Attractors.pde b/day3/libs/teilchen/examples/Lesson03_Attractors/Lesson03_Attractors.pde new file mode 100755 index 0000000..20e4526 --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson03_Attractors/Lesson03_Attractors.pde @@ -0,0 +1,88 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Teleporter; +import teilchen.force.Attractor; +import teilchen.force.ViscousDrag; + +/** + * this sketch shows how to create and use attractors. + */ + +Physics mPhysics; + +Attractor mAttractor; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a viscous force that slows down all motion */ + ViscousDrag myDrag = new ViscousDrag(); + myDrag.coefficient = 0.75f; + mPhysics.add(myDrag); + + /* teleport particles from one edge of the screen to the other */ + Teleporter mTeleporter = new Teleporter(); + mTeleporter.min().set(0, 0); + mTeleporter.max().set(width, height); + mPhysics.add(mTeleporter); + + /* create some particles */ + for (int i = 0; i < 100; i++) { + Particle myParticle = mPhysics.makeParticle(); + myParticle.position().set(random(width), random(height)); + } + + /* create an attractor */ + mAttractor = new Attractor(); + mAttractor.radius(100); + mAttractor.strength(150); + mPhysics.add(mAttractor); +} + +void mousePressed() { + /* flip the direction of the attractors strength. */ + float myInvertedStrength = -1 * mAttractor.strength(); + /* a negative strength turns the attractor into a repulsor */ + mAttractor.strength(myInvertedStrength); +} + +void draw() { + /* set attractor to mouse position */ + mAttractor.position().set(mouseX, mouseY); + + /* update the particle system */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* draw */ + background(255); + + /* draw all the particles in particle system */ + fill(245); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle myParticle = mPhysics.particles(i); + ellipse(myParticle.position().x, myParticle.position().y, 12, 12); + } + + /* draw attractor. green if it is attracting and red if it is repelling */ + noStroke(); + if (mAttractor.strength() < 0) { + fill(255, 0, 0, 50); + } + else { + fill(0, 255, 0, 50); + } + ellipse(mAttractor.position().x, mAttractor.position().y, + mAttractor.radius(), mAttractor.radius()); +} + diff --git a/day3/libs/teilchen/examples/Lesson04_Deflectors/Lesson04_Deflectors.pde b/day3/libs/teilchen/examples/Lesson04_Deflectors/Lesson04_Deflectors.pde new file mode 100755 index 0000000..9c46e84 --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson04_Deflectors/Lesson04_Deflectors.pde @@ -0,0 +1,102 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.ShortLivedParticle; +import teilchen.force.Gravity; +import teilchen.force.PlaneDeflector; +import teilchen.force.ViscousDrag; + +/** + * this sketch shows + * 1 how to create and use plane deflectors + * 2 how to use 'ShortLivedParticle' + */ + +Physics mPhysics; + +PlaneDeflector mDeflector; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a deflector and add it to the particle system. + * the that defines the deflection area is defined by an + * origin and a normal. this also means that the plane s size + * is infinite. + * note that there is also a triangle delfector that is constraint + * by three points. + */ + mDeflector = new PlaneDeflector(); + /* set plane origin into the center of the screen */ + mDeflector.plane().origin.set(width / 2, height / 2, 0); + mDeflector.plane().normal.set(0, -1, 0); + /* the coefficient of restitution defines how hard particles bounce of the deflector */ + mDeflector.coefficientofrestitution(0.7f); + mPhysics.add(mDeflector); + + /* create gravitiy */ + Gravity myGravity = new Gravity(); + myGravity.force().y = 50; + mPhysics.add(myGravity); + + /* create drag */ + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 0.1f; + mPhysics.add(myViscousDrag); +} + +void draw() { + /* rotate deflector plane */ + if (mousePressed) { + final float myAngle = 2 * PI * (float)mouseX / width - PI; + mDeflector.plane().normal.set(sin(myAngle), -cos(myAngle), 0); + } + + /* create a special particle */ + ShortLivedParticle myNewParticle = new ShortLivedParticle(); + myNewParticle.position().set(mouseX, mouseY); + myNewParticle.velocity().set(0, random(100) + 50); + /* this particle is removed after a specific interval */ + myNewParticle.setMaxAge(4); + /* add particle manually to the particle system */ + mPhysics.add(myNewParticle); + + /* update physics */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* draw all the particles in the particle system */ + background(255); + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle myParticle = mPhysics.particles(i); + /* this special particle can tell you how much time it has to live. + * we map this information to its transparency. + */ + float myRatio = 1 - ((ShortLivedParticle)myParticle).ageRatio(); + stroke(0, 64 * myRatio); + fill(0, 32 * myRatio); + ellipse(myParticle.position().x, myParticle.position().y, 12, 12); + } + + /* draw deflector */ + stroke(0, 127); + line(mDeflector.plane().origin.x - mDeflector.plane().normal.y * -width, + mDeflector.plane().origin.y + mDeflector.plane().normal.x * -width, + mDeflector.plane().origin.x - mDeflector.plane().normal.y * width, + mDeflector.plane().origin.y + mDeflector.plane().normal.x * width); + + stroke(255, 0, 0, 127); + line(mDeflector.plane().origin.x, + mDeflector.plane().origin.y, + mDeflector.plane().origin.x + mDeflector.plane().normal.x * 20, + mDeflector.plane().origin.y + mDeflector.plane().normal.y * 20); +} + diff --git a/day3/libs/teilchen/examples/Lesson05_Spring/Lesson05_Spring.pde b/day3/libs/teilchen/examples/Lesson05_Spring/Lesson05_Spring.pde new file mode 100755 index 0000000..64d9e80 --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson05_Spring/Lesson05_Spring.pde @@ -0,0 +1,68 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; + +/** + * this sketch shows + * 1 how to create a viscous drag to slow motion eventually down. + * 2 how to create a spring that connects two particles. + */ + +Physics mPhysics; + +Spring mSpring; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a viscous force that slows down all motion; 0 means no slowing down. */ + ViscousDrag myDrag = new ViscousDrag(0.25f); + mPhysics.add(myDrag); + + /* create two particles that we can connect with a spring */ + Particle myA = mPhysics.makeParticle(); + myA.position().set(width / 2 - 50, height / 2); + + Particle myB = mPhysics.makeParticle(); + myB.position().set(width / 2 + 50, height / 2); + + /* create a spring force that connects two particles. + * note that there is more than one way to create a spring. + * in our case the restlength of the spring is defined by the + * particles current position. + */ + mSpring = mPhysics.makeSpring(myA, myB); +} + +void draw() { + /* set first particle to mouse position */ + if (mousePressed) { + mSpring.a().position().set(mouseX, mouseY); + } + + /* update the particle system */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particles and connecting line */ + background(255); + noFill(); + stroke(255, 0, 127, 64); + line(mSpring.a().position().x, mSpring.a().position().y, + mSpring.b().position().x, mSpring.b().position().y); + fill(245); + stroke(164); + ellipse(mSpring.a().position().x, mSpring.a().position().y, 12, 12); + ellipse(mSpring.b().position().x, mSpring.b().position().y, 12, 12); +} + diff --git a/day3/libs/teilchen/examples/Lesson06_Springs/Lesson06_Springs.pde b/day3/libs/teilchen/examples/Lesson06_Springs/Lesson06_Springs.pde new file mode 100755 index 0000000..8c8978b --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson06_Springs/Lesson06_Springs.pde @@ -0,0 +1,70 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Spring; + +/** + * this sketch shows + * 1 how to create a viscous drag to slow motion eventually down. + * 2 how to create a spring that connects two particles. + */ + +Physics mPhysics; + +Particle mRoot; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a particle to which we will connect springs */ + mRoot = mPhysics.makeParticle(width / 2, height / 2, 0.0); + /* we give the root particle a higher mass so it doesn t move as easily */ + mRoot.mass(30); +} + +void draw() { + /* create a particle at mouse position and connect it to the root particle through a spring */ + if (mousePressed) { + Particle mParticle = mPhysics.makeParticle(mouseX, mouseY, 0); + Spring mSpring = mPhysics.makeSpring(mRoot, mParticle); + /* restlength defines the desired length of the spring. in this case it is the distance between the two particles. */ + float mRestlength = mSpring.restlength(); + /* we modify the restlength to add a bit of energy into the system */ + mSpring.restlength(mRestlength * 1.5f); + } + + /* update the particle system */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particles and connecting line */ + background(255); + + /* draw springs */ + noFill(); + stroke(255, 0, 127, 64); + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces().get(i) instanceof Spring) { + Spring mSSpring = (Spring)mPhysics.forces().get(i); + line(mSSpring.a().position().x, mSSpring.a().position().y, + mSSpring.b().position().x, mSSpring.b().position().y); + } + } + /* draw particles */ + fill(245); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); i++) { + ellipse(mPhysics.particles().get(i).position().x, + mPhysics.particles().get(i).position().y, + 12, 12); + } +} + diff --git a/day3/libs/teilchen/examples/Lesson07_StableQuads/Lesson07_StableQuads.pde b/day3/libs/teilchen/examples/Lesson07_StableQuads/Lesson07_StableQuads.pde new file mode 100755 index 0000000..9c28370 --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson07_StableQuads/Lesson07_StableQuads.pde @@ -0,0 +1,83 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Physics; +import teilchen.force.Gravity; +import teilchen.force.ViscousDrag; +import teilchen.util.DrawLib; +import teilchen.Particle; +import teilchen.constraint.Box; +import teilchen.integration.RungeKutta; +import teilchen.util.StableSpringQuad; + +Physics mPhysics; + +Particle mRoot; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(60); + + mPhysics = new Physics(); + /* we use 'runge kutta' as it is more stable for this application */ + mPhysics.setInegratorRef(new RungeKutta()); + + Gravity myGravity = new Gravity(); + myGravity.force().y = 98.1f; + mPhysics.add(myGravity); + + /* add drag to smooth the spring interaction */ + mPhysics.add(new ViscousDrag(0.2f)); + + /* add a container */ + Box myBox = new Box(); + myBox.min().set(0, 0, 0); + myBox.max().set(width, height, 0); + mPhysics.add(myBox); + + /* create root */ + Particle a = mPhysics.makeParticle(0, 0); + Particle b = mPhysics.makeParticle(100, 0); + Particle c = mPhysics.makeParticle(100, 100); + Particle d = mPhysics.makeParticle(0, 100); + + new StableSpringQuad(mPhysics, d, c, mPhysics.makeParticle(100, 200), mPhysics.makeParticle(0, 200)); + + /* create stable quad from springs */ + /* first the edge-springs ... */ + final float mySpringConstant = 100; + final float mySpringDamping = 5; + mPhysics.makeSpring(a, b, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(b, c, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(c, d, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(d, a, mySpringConstant, mySpringDamping).restlength(); + /* ... then the diagonal-springs */ + mPhysics.makeSpring(a, c, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(b, d, mySpringConstant, mySpringDamping).restlength(); + + /* define 'a' as root particle for mouse interaction */ + mRoot = a; + mRoot.fixed(true); +} + +void draw() { + + /* handle particles */ + if (mousePressed) { + mRoot.fixed(true); + mRoot.position().set(mouseX, mouseY); + } + else { + mRoot.fixed(false); + } + + mPhysics.step(1f / frameRate); + + /* draw */ + background(255); + DrawLib.drawSprings(g, mPhysics, color(255, 0, 127, 64)); + DrawLib.drawParticles(g, mPhysics, 12, color(164), color(245)); +} + diff --git a/day3/libs/teilchen/examples/Lesson08_Sticks/Lesson08_Sticks.pde b/day3/libs/teilchen/examples/Lesson08_Sticks/Lesson08_Sticks.pde new file mode 100755 index 0000000..220abd7 --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson08_Sticks/Lesson08_Sticks.pde @@ -0,0 +1,71 @@ +import processing.opengl.*; + +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Stick; +import teilchen.force.Gravity; +import teilchen.integration.Verlet; + +Physics mPhysics; + +Particle[] mParticles; + +void setup() { + size(640, 480, OPENGL); + frameRate(60); + smooth(); + + mPhysics = new Physics(); + /* increase the number of iterations for contraints in each step. this can greatly relaxes tensions in the system. */ + mPhysics.contraint_iterations_per_steps = 5; + + /* add gravity for extra fun */ + mPhysics.add(new Gravity()); + + /* we chose verlet integration as it integrates much more nicely with sticks ( and constraints in general ) */ + Verlet myVerlet = new Verlet(); + myVerlet.damping(0.99f); + mPhysics.setInegratorRef(myVerlet); + + /* setup sticks to form a whip */ + mParticles = new Particle[16]; + float mSegmentLength = 20.0; + /* create root */ + for (int x = 0; x < mParticles.length; x++) { + mParticles[x] = mPhysics.makeParticle(x * mSegmentLength, 0, 0, 0.1f); + if (x > 0) { + Stick myStick = new Stick(mParticles[x - 1], + mParticles[x], + mSegmentLength); + /* damp the stick to release tensions from the system */ + myStick.damping(0.99f); + mPhysics.add(myStick); + } + } + + /* fix root particle so it can stick to the mouse later */ + mParticles[0].fixed(true); +} + +void draw() { + /* stick root particle to mouse */ + mParticles[0].position().set(mouseX, mouseY); + + /* update */ + mPhysics.step(1.0 / frameRate); + + /* draw sticks with descending stroke weight */ + background(255); + stroke(0, 192); + for (int x = 1; x < mParticles.length; x++) { + Particle p1 = mParticles[x - 1]; + Particle p2 = mParticles[x]; + final float mStrokeWeight = 4.0 * (1.0 - (float)x / mParticles.length); + strokeWeight(mStrokeWeight); + line(p1.position().x, p1.position().y, p1.position().z, + p2.position().x, p2.position().y, p2.position().z); + } +} + diff --git a/day3/libs/teilchen/examples/Lesson09_Cloth/Lesson09_Cloth.pde b/day3/libs/teilchen/examples/Lesson09_Cloth/Lesson09_Cloth.pde new file mode 100755 index 0000000..cea0c57 --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson09_Cloth/Lesson09_Cloth.pde @@ -0,0 +1,113 @@ +import processing.opengl.*; + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.IConstraint; +import teilchen.constraint.Stick; +import teilchen.force.Attractor; +import teilchen.force.Gravity; +import teilchen.integration.Verlet; + + +Physics mPhysics; + +Particle[][] mParticles; + +final int GRID_WIDTH = 32; + +final int GRID_HEIGHT = 16; + +Attractor mAttractor; + +void setup() { + size(640, 480, OPENGL); + frameRate(60); + + mPhysics = new Physics(); + mPhysics.contraint_iterations_per_steps = 5; + + Verlet myVerlet = new Verlet(); + myVerlet.damping(0.9f); + mPhysics.setInegratorRef(myVerlet); + mPhysics.add(new Gravity(new Vector3f(0, 1000f, 0))); + + mAttractor = new Attractor(); + mAttractor.strength(-15000); + mAttractor.radius(300); + mPhysics.add(mAttractor); + + mParticles = new Particle[GRID_WIDTH][GRID_HEIGHT]; + + /* setup cloth */ + float mGridStepX = ((float)width / GRID_WIDTH); + float mGridStepY = (((float)height * 0.5f) / GRID_HEIGHT); + for (int y = 0; y < GRID_HEIGHT; y++) { + for (int x = 0; x < GRID_WIDTH; x++) { + mParticles[x][y] = mPhysics.makeParticle(); + mParticles[x][y].position().set((x + 0.5f) * mGridStepX, + y * mGridStepY, + random(0, 1)); + mParticles[x][y].old_position().set(mParticles[x][y].position()); + mParticles[x][y].mass(0.1f); + + final float DAMPING = 0.9f; + if (y > 0) { + Stick myStick = new Stick(mParticles[x][y - 1], + mParticles[x][y], + mGridStepY); + myStick.damping(DAMPING); + mPhysics.add(myStick); + } + if (x > 0) { + Stick myStick = new Stick(mParticles[x - 1][y], + mParticles[x][y], + mGridStepX); + myStick.damping(DAMPING); + mPhysics.add(myStick); + } + if (x > 0 && y > 0) { + Stick myStick1 = new Stick(mParticles[x - 1][y - 1], + mParticles[x][y], + new Vector3f(mGridStepX, mGridStepY).length()); + mPhysics.add(myStick1); + Stick myStick2 = new Stick(mParticles[x][y - 1], + mParticles[x - 1][y], + new Vector3f(mGridStepX, mGridStepY).length()); + mPhysics.add(myStick2); + } + } + } + + /* fix first row */ + for (int x = 0; x < mParticles.length; x++) { + mParticles[x][0].fixed(true); + } +} + +void draw() { + + /* update */ + mAttractor.position().set(mouseX, mouseY, 50); + mPhysics.step(1.0 / frameRate); + + background(255); + + /* draw sticks */ + stroke(0, 127); + + for (int i = 0; i < mPhysics.constraints().size(); i++) { + IConstraint mConstraint = mPhysics.constraints().get(i); + if (mConstraint instanceof Stick) { + final Stick myStick = (Stick)mConstraint; + line(myStick.a().position().x, + myStick.a().position().y, + myStick.a().position().z, + myStick.b().position().x, + myStick.b().position().y, + myStick.b().position().z); + } + } +} + diff --git a/day3/libs/teilchen/examples/Lesson10_Behaviors/Lesson10_Behaviors.pde b/day3/libs/teilchen/examples/Lesson10_Behaviors/Lesson10_Behaviors.pde new file mode 100755 index 0000000..c9fa95d --- /dev/null +++ b/day3/libs/teilchen/examples/Lesson10_Behaviors/Lesson10_Behaviors.pde @@ -0,0 +1,73 @@ +import mathematik.*; + +import teilchen.BehaviorParticle; +import teilchen.Physics; +import teilchen.behavior.Arrival; + + +/** + * this sketch shows how to assign an 'arrival' behavior to a particle. + */ +Physics mPhysics; + +BehaviorParticle mParticle; + +Arrival mArrival; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(120); + colorMode(RGB, 1.0f); + noFill(); + + /* physics */ + mPhysics = new Physics(); + + /* create particles */ + mParticle = mPhysics.makeParticle(BehaviorParticle.class); + mParticle.maximumInnerForce(100); + + /* create arrival behavior */ + mArrival = new Arrival(); + mArrival.breakforce(mParticle.maximumInnerForce() * 0.25f); + mArrival.breakradius(mParticle.maximumInnerForce() * 0.25f); + mParticle.behaviors().add(mArrival); +} + +void draw() { + + /* set the arrival position to the mouse position */ + mArrival.position().set(mouseX, mouseY); + + /* update particle system */ + mPhysics.step(1.0f / frameRate); + + /* draw behavior particle */ + background(1); + stroke(0, 0.5f); + if (mArrival.arriving()) { + /* color particle red while it is arriving */ + stroke(1, 0, 0, 0.5f); + } + if (mArrival.arrived()) { + /* color particle green when it has arrived */ + stroke(0, 1, 0, 0.5f); + } + + line(mParticle.position().x, + mParticle.position().y, + mParticle.position().x + mParticle.velocity().x, + mParticle.position().y + mParticle.velocity().y); + fill(1); + ellipse(mParticle.position().x, mParticle.position().y, 12, 12); + + /* draw arrival */ + stroke(0, 0.25f); + noFill(); + ellipse(mArrival.position().x, + mArrival.position().y, + mArrival.breakradius() * 2, + mArrival.breakradius() * 2); +} + diff --git a/day3/libs/teilchen/examples/LessonX01_Overlap/LessonX01_Overlap.pde b/day3/libs/teilchen/examples/LessonX01_Overlap/LessonX01_Overlap.pde new file mode 100755 index 0000000..e07a2f5 --- /dev/null +++ b/day3/libs/teilchen/examples/LessonX01_Overlap/LessonX01_Overlap.pde @@ -0,0 +1,69 @@ +import processing.opengl.*; +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Spring; +import teilchen.util.Overlap; + +/** + * this sketch is exactly like Lesson06_Springs, except that it also shows how to remove overlaps. + */ + +Physics mPhysics; + +Particle mRoot; + +static final float PARTICLE_RADIUS = 6; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + mPhysics = new Physics(); + mRoot = mPhysics.makeParticle(width / 2, height / 2, 0.0); + mRoot.mass(30); +} + +void draw() { + if (mousePressed) { + Particle mParticle = mPhysics.makeParticle(mouseX, mouseY, 0); + Spring mSpring = mPhysics.makeSpring(mRoot, mParticle); + float mRestlength = mSpring.restlength(); + mSpring.restlength(mRestlength * 1.5f); + + /* we define a radius for the particle so the particle has dimensions */ + mParticle.radius(PARTICLE_RADIUS); + } + + /* move overlapping particles away from each other */ + Overlap.resolveOverlap(mPhysics.particles()); + + /* update the particle system */ + final float mDeltaTime = 1.0 / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particles and connecting line */ + background(255); + + /* draw springs */ + noFill(); + stroke(255, 0, 127, 64); + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces().get(i) instanceof Spring) { + Spring mSSpring = (Spring)mPhysics.forces().get(i); + line(mSSpring.a().position().x, mSSpring.a().position().y, + mSSpring.b().position().x, mSSpring.b().position().y); + } + } + /* draw particles */ + fill(245); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); i++) { + ellipse(mPhysics.particles().get(i).position().x, + mPhysics.particles().get(i).position().y, + PARTICLE_RADIUS * 2, PARTICLE_RADIUS * 2); + } +} + diff --git a/day3/libs/teilchen/examples/LessonX02_Collisions/LessonX02_Collisions.pde b/day3/libs/teilchen/examples/LessonX02_Collisions/LessonX02_Collisions.pde new file mode 100755 index 0000000..530f6f2 --- /dev/null +++ b/day3/libs/teilchen/examples/LessonX02_Collisions/LessonX02_Collisions.pde @@ -0,0 +1,91 @@ +import processing.opengl.*; + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Box; +import teilchen.force.Gravity; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.util.CollisionManager; + +static final float PARTICLE_SIZE = 12; + +CollisionManager mCollision; + +Physics mPhysics; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + noFill(); + ellipseMode(CENTER); + + mCollision = new CollisionManager(); + mCollision.distancemode(CollisionManager.DISTANCE_MODE_FIXED); + mCollision.minimumDistance(50); + + mPhysics = new Physics(); + mPhysics.add(new ViscousDrag(0.85f)); + mPhysics.add(new Gravity()); + + Box myBox = new Box(); + myBox.min().set(50, 50, 0); + myBox.max().set(width - 50, height - 50, 0); + myBox.coefficientofrestitution(0.7f); + myBox.reflect(true); + mPhysics.add(myBox); + + /* create a first particle */ + final Particle myParticle = mPhysics.makeParticle(new Vector3f(mouseX, mouseY, 0), 10); + mCollision.collision().add(myParticle); +} + +void draw() { + /* create particles */ + if (mousePressed) { + final Particle myParticle = mPhysics.makeParticle(new Vector3f(mouseX, mouseY, 0), 10); + mCollision.collision().add(myParticle); + } + + /* collision handler */ + final float mDeltaTime = 1.0 / frameRate; + mCollision.createCollisionResolvers(); + mCollision.loop(mDeltaTime); + mPhysics.step(mDeltaTime); + + /* draw */ + background(255); + drawThings(); + + mCollision.removeCollisionResolver(); +} + +void drawThings() { + /* collision springs */ + noFill(); + stroke(255, 0, 127, 64); + for (int i = 0; i < mCollision.collision().forces().size(); ++i) { + if (mCollision.collision().forces().get(i) instanceof Spring) { + Spring mySpring = (Spring)mCollision.collision_forces().get(i); + line(mySpring.a().position().x, mySpring.a().position().y, mySpring.a().position().z, + mySpring.b().position().x, mySpring.b().position().y, mySpring.b().position().z); + } + } + + /* particles */ + fill(245); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); ++i) { + Particle myParticle = mPhysics.particles().get(i); + pushMatrix(); + translate(myParticle.position().x, myParticle.position().y, myParticle.position().z); + ellipse(0, 0, + PARTICLE_SIZE, + PARTICLE_SIZE); + popMatrix(); + } +} + diff --git a/day3/libs/teilchen/examples/LessonX03_ParticlesLeavingTrails/LessonX03_ParticlesLeavingTrails.pde b/day3/libs/teilchen/examples/LessonX03_ParticlesLeavingTrails/LessonX03_ParticlesLeavingTrails.pde new file mode 100755 index 0000000..a4bdca0 --- /dev/null +++ b/day3/libs/teilchen/examples/LessonX03_ParticlesLeavingTrails/LessonX03_ParticlesLeavingTrails.pde @@ -0,0 +1,131 @@ +import mathematik.Vector3f; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.ShortLivedParticle; +import teilchen.constraint.Box; +import teilchen.force.Attractor; +import teilchen.force.Gravity; +import teilchen.force.ViscousDrag; +import teilchen.util.ParticleTrail; + +import java.util.Vector; + +Physics mPhysics; + +Vector mTrails; + +Attractor mAttractor; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(60); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a gravitational force */ + Gravity myGravity = new Gravity(); + mPhysics.add(myGravity); + myGravity.force().y = 20; + + /* create drag */ + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 0.1f; + mPhysics.add(myViscousDrag); + + final float mBorder = 40; + Box mBox = new Box(new Vector3f(mBorder, mBorder, mBorder), new Vector3f(width - mBorder, height - mBorder, 100 - mBorder)); + mBox.reflect(true); + mPhysics.add(mBox); + + /* create an attractor */ + mAttractor = new Attractor(); + mAttractor.radius(200); + mAttractor.strength(-300); + mPhysics.add(mAttractor); + + + /* create trails and particles */ + mTrails = new Vector(); + for (int i = 0; i < 500; i++) { + Particle mParticle = mPhysics.makeParticle(); + mParticle.mass(2.0f); + ParticleTrail myParticleTrail = new ParticleTrail(mPhysics, mParticle, 0.2f, random(0.5f, 1)); + myParticleTrail.mass(0.5f); + mTrails.add(myParticleTrail); + } + resetParticles(width / 2, height / 2); +} + +void resetParticles(float x, float y) { + for (ParticleTrail myTrails : mTrails) { + myTrails.particle().position().set(x + random(-10, 10), y + random(-10, 10), 0); + myTrails.particle().velocity().set(random(-10, 10), random(-10, 10), random(-10, 10)); + myTrails.fragments().clear(); + } +} + +void draw() { + /* set attractor to mouse position */ + mAttractor.position().set(mouseX, mouseY); + + for (ParticleTrail myTrails : mTrails) { + myTrails.loop(1f / frameRate); + } + + mPhysics.step(1f / frameRate); + + background(255); + for (ParticleTrail myTrail : mTrails) { + drawTrail(myTrail); + } +} + +void drawTrail(ParticleTrail theTrail) { + + final Vector mFragments = theTrail.fragments(); + final Particle mParticle = theTrail.particle(); + + /* draw head */ + if (mFragments.size() > 1) { + fill(255, 0, 127); + noStroke(); + pushMatrix(); + translate(mParticle.position().x, + mParticle.position().y, + mParticle.position().z); + sphereDetail(4); + sphere(3); + popMatrix(); + } + + /* draw trail */ + for (int i = 0; i < mFragments.size() - 1; i++) { + if (mFragments.get(i) instanceof ShortLivedParticle) { + final float mRatio = 1.0f - ((ShortLivedParticle)mFragments.get(i)).ageRatio(); + stroke(127, mRatio * 255); + strokeWeight(mRatio * 3); + } + int j = (i + 1) % mFragments.size(); + line(mFragments.get(i).position().x, + mFragments.get(i).position().y, + mFragments.get(i).position().z, + mFragments.get(j).position().x, + mFragments.get(j).position().y, + mFragments.get(j).position().z); + } + if (!mFragments.isEmpty()) { + line(mFragments.lastElement().position().x, + mFragments.lastElement().position().y, + mFragments.lastElement().position().z, + mParticle.position().x, + mParticle.position().y, + mParticle.position().z); + } +} + +void mousePressed() { + resetParticles(mouseX, mouseY); +} + diff --git a/day3/libs/teilchen/examples/LessonX04_StickMan/LessonX04_StickMan.pde b/day3/libs/teilchen/examples/LessonX04_StickMan/LessonX04_StickMan.pde new file mode 100755 index 0000000..0a32d9c --- /dev/null +++ b/day3/libs/teilchen/examples/LessonX04_StickMan/LessonX04_StickMan.pde @@ -0,0 +1,146 @@ +import mathematik.*; + +import teilchen.test.cubicle.*; +import teilchen.integration.*; +import verhalten.*; +import teilchen.*; +import verhalten.view.*; +import teilchen.test.particle.behavior.*; +import teilchen.behavior.*; +import teilchen.demo.*; +import teilchen.test.particle.springs.*; +import teilchen.gestalt.test.*; +import teilchen.cubicle.*; +import verhalten.test.*; +import teilchen.force.flowfield.*; +import teilchen.test.particle.*; +import teilchen.gestalt.util.*; +import teilchen.constraint.*; +import teilchen.force.vectorfield.*; +import teilchen.force.*; +import teilchen.util.*; + +import mathematik.*; + +import teilchen.Physics; +import teilchen.force.Attractor; +import teilchen.force.Gravity; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.integration.RungeKutta; +import teilchen.util.Overlap; +import teilchen.util.StickMan; + + + +/** + * this demo shows some advanced use of particles, springs and attractors to create stickmen. + */ + +Physics mPhysics; + +Attractor mAttractor; + +Gravity mGravity; + +ViscousDrag mViscousDrag; + +StickMan[] mMyStickMan; + +void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(60); + noFill(); + + mPhysics = new Physics(); + mPhysics.setInegratorRef(new RungeKutta()); + + mGravity = new Gravity(); + mGravity.force().y = 20; + mPhysics.add(mGravity); + + mViscousDrag = new ViscousDrag(); + mViscousDrag.coefficient = 0.85f; + mPhysics.add(mViscousDrag); + + mAttractor = new Attractor(); + mAttractor.radius(500); + mAttractor.strength(0); + mAttractor.position().set(width / 2, height / 2); + mPhysics.add(mAttractor); + + mMyStickMan = new StickMan[20]; + for (int i = 0; i < mMyStickMan.length; i++) { + mMyStickMan[i] = new StickMan(mPhysics, random(0, width), random(0.3f, 0.6f)); + } +} + +void draw() { + + mPhysics.step(1f / 60f); + Overlap.resolveOverlap(mPhysics.particles()); + + /* constraint particles */ + for (int i = 0; i < mPhysics.particles().size(); i++) { + if (mPhysics.particles(i).position().y > height - 10) { + mPhysics.particles(i).position().y = height - 10; + } + if (mPhysics.particles(i).position().x > width) { + mPhysics.particles(i).position().x = width; + } + if (mPhysics.particles(i).position().x < 0) { + mPhysics.particles(i).position().x = 0; + } + } + + /* handle particles */ + if (mousePressed) { + mAttractor.position().set(mouseX, mouseY); + if (mouseButton == RIGHT) { + mAttractor.strength(-500); + mAttractor.radius(500); + } + else { + mAttractor.strength(500); + mAttractor.radius(100); + } + } + else { + mAttractor.strength(0); + } + + if (keyPressed) { + mGravity.force().y = -10; + } + else { + mGravity.force().y = 20; + } + + /* draw */ + background(255); + + /* draw springs */ + stroke(0, 20); + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces(i) instanceof Spring) { + Spring mySpring = (Spring)mPhysics.forces(i); + line(mySpring.a().position().x, + mySpring.a().position().y, + mySpring.b().position().x, + mySpring.b().position().y); + } + } + + /* draw particles */ + for (int i = 0; i < mPhysics.particles().size(); i++) { + ellipse(mPhysics.particles(i).position().x, + mPhysics.particles(i).position().y, 5, 5); + } + + /* draw man */ + for (int i = 0; i < mMyStickMan.length; i++) { + mMyStickMan[i].draw(g); + } +} + diff --git a/day3/libs/teilchen/examples/LessonX05_AngleConstraints/LessonX05_AngleConstraints.pde b/day3/libs/teilchen/examples/LessonX05_AngleConstraints/LessonX05_AngleConstraints.pde new file mode 100755 index 0000000..89c51e9 --- /dev/null +++ b/day3/libs/teilchen/examples/LessonX05_AngleConstraints/LessonX05_AngleConstraints.pde @@ -0,0 +1,184 @@ +import mathematik.*; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.AngleConstraintStick; +import teilchen.constraint.Stick; +import teilchen.force.AngleConstraintSpring; +import teilchen.force.Gravity; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.integration.RungeKutta; + + +Physics mPhysics; + +Particle mParticleA; + +Particle mParticleB; + +Particle mParticleC; + +Particle mParticleD; + +AngleConstraintSpring mAngleConstraintABC; + +AngleConstraintStick mAngleConstraintBCD; + +void setup() { + size(640, 480); + frameRate(30); + smooth(); + + mPhysics = new Physics(); + mPhysics.setInegratorRef(new RungeKutta()); + + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 1f; + mPhysics.add(myViscousDrag); + + Gravity myGravity = new Gravity(); + myGravity.force().y = 50; + mPhysics.add(myGravity); + + /* particles */ + mParticleA = mPhysics.makeParticle(); + mParticleB = mPhysics.makeParticle(); + mParticleC = mPhysics.makeParticle(); + mParticleD = mPhysics.makeParticle(); + + mParticleA.position().set(width / 2 + 50, height / 3); + mParticleB.position().set(width / 2, height - height / 1.75f); + mParticleC.position().set(width / 2, height - height / 4); + mParticleD.position().set(width / 2, height - height / 8); + + mParticleA.radius(7); + mParticleB.radius(3); + mParticleC.radius(10); + mParticleD.radius(2); + + mParticleB.fixed(true); + + /* springs */ + Spring mSpringAB = new Spring(mParticleA, mParticleB); + mSpringAB.strength(250); + mSpringAB.damping(10); + mPhysics.add(mSpringAB); + + Spring mSpringBC = new Spring(mParticleB, mParticleC); + mSpringBC.strength(250); + mSpringBC.damping(10); + mPhysics.add(mSpringBC); + + Stick mSpringCD = new Stick(mParticleC, mParticleD); + mSpringCD.damping(1); + mPhysics.add(mSpringCD); + + /* angle constraint */ + mAngleConstraintABC = new AngleConstraintSpring(mParticleA, mParticleB, mParticleC); + mAngleConstraintABC.min_angle(PI * 0.5f); + mAngleConstraintABC.damping(1); + mAngleConstraintABC.strength(200); + mPhysics.add(mAngleConstraintABC); + + mAngleConstraintBCD = new AngleConstraintStick(mParticleB, mParticleC, mParticleD); + mAngleConstraintBCD.min_angle(PI * 0.8f); + mAngleConstraintBCD.damping(0.5f); + mPhysics.add(mAngleConstraintBCD); +} + +void draw() { + /* attach particle to mouse */ + if (mousePressed) { + mParticleA.position().set(mouseX, mouseY); + } + + /* apply constraints */ + mAngleConstraintABC.pre_step(); + mAngleConstraintBCD.pre_step(); + draw_physics(); + + mPhysics.step(1f / frameRate); + + /* remove contraints */ + mAngleConstraintABC.post_step(); + mAngleConstraintBCD.post_step(); +} + +void draw_physics() { + background(255); + + drawSprings(); + drawSticks(); + drawParticles(); +} + +void drawSprings() { + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces(i) instanceof Spring) { + final Spring mSpring = (Spring)mPhysics.forces(i); + if (mSpring instanceof AngleConstraintSpring) { + strokeWeight(1); + if (mSpring.active()) { + stroke(255, 0, 0, 64); + } + else { + stroke(255, 0, 0, 16); + } + } + else { + strokeWeight(3); + stroke(0, 128); + } + line(mSpring.a(), mSpring.b()); + } + } + strokeWeight(1); +} + +void drawSticks() { + for (int i = 0; i < mPhysics.constraints().size(); i++) { + if (mPhysics.constraints(i) instanceof Stick) { + final Stick mStick = (Stick)mPhysics.constraints(i); + if (mStick instanceof AngleConstraintStick) { + strokeWeight(1); + if (mStick.active()) { + stroke(0, 127, 255, 64); + } + else { + stroke(0, 127, 255, 16); + } + } + else { + strokeWeight(3); + stroke(0, 128); + } + line(mStick.a(), mStick.b()); + } + } + strokeWeight(1); +} + +void drawParticles() { + stroke(0); + fill(92); + drawParticle(mParticleA); + fill(127); + drawParticle(mParticleB); + fill(192); + drawParticle(mParticleC); + fill(64); + drawParticle(mParticleD); +} + +void drawParticle(Particle p) { + ellipse(p.position().x, + p.position().y, + p.radius() * 2, p.radius() * 2); +} + +void line(Particle p1, Particle p2) { + line(p1.position().x, p1.position().y, + p2.position().x, p2.position().y); +} + diff --git a/day3/libs/teilchen/examples/LessonX06_Ducklings/LessonX06_Ducklings.pde b/day3/libs/teilchen/examples/LessonX06_Ducklings/LessonX06_Ducklings.pde new file mode 100755 index 0000000..bcdc2c6 --- /dev/null +++ b/day3/libs/teilchen/examples/LessonX06_Ducklings/LessonX06_Ducklings.pde @@ -0,0 +1,145 @@ +import mathematik.*; + +import processing.opengl.*; + +import teilchen.BehaviorParticle; +import teilchen.Physics; +import teilchen.behavior.Arrival; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.util.CollisionManager; + +/** + * this demo shows how to add behaviors to particles. in this example the + * arrival behavior. + */ + +Physics mPhysics; + +ArrayList mDucklings; + +CollisionManager mCollision; + +void setup() { + size(640, 480, OPENGL); + frameRate(60); + smooth(); + colorMode(RGB, 1.0f); + + /* physics */ + mPhysics = new Physics(); + + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 0.25f; + mPhysics.add(myViscousDrag); + + mCollision = new CollisionManager(); + mCollision.minimumDistance(25); + + /* ducklings */ + mDucklings = new ArrayList(); + for (int i = 0; i < 13; i++) { + final Duckling mDuckling = new Duckling(); + if (!mDucklings.isEmpty()) { + mDuckling.arrival.setPositionRef(mDucklings.get(mDucklings.size()-1).particle.position()); + } + mCollision.collision().add(mDuckling.particle); + mDucklings.add(mDuckling); + } +} + + +void draw() { + final float mDeltaTime = 1.0f / frameRate; + background(1); + + /* update particles */ + mCollision.createCollisionResolvers(); + mCollision.loop(mDeltaTime); + mPhysics.step(mDeltaTime); + + drawCollisionSprings(); + mCollision.removeCollisionResolver(); + + mDucklings.get(0).arrival.oversteer(!mousePressed); + mDucklings.get(0).arrival.position().set(mouseX, mouseY); + + /* draw */ + for (int i=0; i < mDucklings.size(); i++) { + Duckling mDuckling = mDucklings.get(i); + drawParticle(mDuckling); + } + + /* draw arrival */ + stroke(0, 0.25f); + noFill(); + ellipse(mDucklings.get(0).arrival.position().x, + mDucklings.get(0).arrival.position().y, + 20, 20); +} + +void drawParticle(Duckling pDuckling) { + final BehaviorParticle mParticle = pDuckling.particle; + final Arrival mArrival = pDuckling.arrival; + + /* draw particle */ + stroke(0, 0.5f); + noFill(); + if (mArrival.arriving()) { + stroke(1, 0, 0, 0.5f); + } + if (mArrival.arrived()) { + stroke(0, 1, 0, 0.5f); + } + ellipse(mParticle.position().x, mParticle.position().y, + mParticle.radius() * 2, mParticle.radius() * 2); + + /* - */ + pushMatrix(); + translate(mParticle.position().x, + mParticle.position().y); + + /* draw velocity */ + stroke(1, 0, 0, 0.5f); + line(0, 0, mParticle.velocity().x, mParticle.velocity().y); + + /* draw break force */ + stroke(0, 0.5f, 1, 0.5f); + line(0, 0, mArrival.force().x, mArrival.force().y); + + /* - */ + popMatrix(); +} + +void drawCollisionSprings() { + stroke(0, 1, 0, 0.25f); + for (int i = 0; i < mCollision.collision().forces().size(); ++i) { + if (mCollision.collision().forces().get(i) instanceof Spring) { + Spring mySpring = (Spring) mCollision.collision_forces().get(i); + line(mySpring.a().position().x, mySpring.a().position().y, mySpring.a().position().z, + mySpring.b().position().x, mySpring.b().position().y, mySpring.b().position().z); + } + } +} + +class Duckling { + + BehaviorParticle particle; + + Arrival arrival; + + Duckling() { + /* create particles */ + particle = mPhysics.makeParticle(BehaviorParticle.class); + particle.position().set(random(width), random(height)); + particle.maximumInnerForce(random(50, 150)); + particle.radius(random(6, 10)); + + arrival = new Arrival(); + arrival.breakforce(random(12, 28)); + arrival.breakradius(random(45, 55)); + + particle.behaviors().add(arrival); + } +} + diff --git a/day3/libs/teilchen/library/teilchen.jar b/day3/libs/teilchen/library/teilchen.jar new file mode 100755 index 0000000000000000000000000000000000000000..60b9ff93c34fbf59b7e3ed7bc9a695482427bf93 GIT binary patch literal 479897 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

lLw(+_8w=_Y$)70OD7?Kj&)dq)DPVU|d7mMGi4 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 literal 0 HcmV?d00001 diff --git a/day3/libs/teilchen/src/teilchen/BasicParticle.java b/day3/libs/teilchen/src/teilchen/BasicParticle.java new file mode 100755 index 0000000..3eba915 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/BasicParticle.java @@ -0,0 +1,142 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +import java.io.Serializable; + +import mathematik.Vector3f; + + +public class BasicParticle + implements Particle, Serializable { + + private boolean mFixed; + + private float mAge; + + private float mMass; + + private Vector3f mPosition; + + private final Vector3f mOldPosition; + + private final Vector3f mVelocity; + + private final Vector3f mForce; + + private boolean mTagged; + + private boolean mStill; + + private float mRadius; + + private static final long serialVersionUID = 3737917975116369338L; + + public BasicParticle() { + mPosition = new Vector3f(); + mOldPosition = new Vector3f(); + mVelocity = new Vector3f(); + mForce = new Vector3f(); + mMass = 1; + mFixed = false; + mAge = 0; + mTagged = false; + mStill = false; + mRadius = 0; + } + + public boolean fixed() { + return mFixed; + } + + public void fixed(boolean theFixed) { + mFixed = theFixed; + } + + public float age() { + return mAge; + } + + public void age(float theAge) { + mAge = theAge; + } + + public float mass() { + return mMass; + } + + public void mass(float theMass) { + mMass = theMass; + } + + public Vector3f position() { + return mPosition; + } + + public Vector3f old_position() { + return mOldPosition; + } + + public void setPositionRef(Vector3f thePosition) { + mPosition = thePosition; + } + + public Vector3f velocity() { + return mVelocity; + } + + public Vector3f force() { + return mForce; + } + + public boolean dead() { + return false; + } + + public void accumulateInnerForce(final float theDeltaTime) { + } + + public boolean tagged() { + return mTagged; + } + + public void tag(boolean theTag) { + mTagged = theTag; + } + + public boolean still() { + return mStill; + } + + public void still(boolean theStill) { + mStill = theStill; + } + + public float radius() { + return mRadius; + } + + public void radius(float theRadius) { + mRadius = theRadius; + } +} diff --git a/day3/libs/teilchen/src/teilchen/BehaviorParticle.java b/day3/libs/teilchen/src/teilchen/BehaviorParticle.java new file mode 100755 index 0000000..090966e --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/BehaviorParticle.java @@ -0,0 +1,72 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +import java.util.Vector; + +import teilchen.behavior.IBehavior; + + +public class BehaviorParticle + extends BasicParticle + implements IBehaviorParticle { + + private final Vector mBehaviors; + + private float mMaximumInnerForce; + + private static final long serialVersionUID = 2735849326244271321L; + + public BehaviorParticle() { + mBehaviors = new Vector(); + mMaximumInnerForce = 50; + } + + public void accumulateInnerForce(final float theDeltaTime) { + for (final IBehavior mBehavior : mBehaviors) { + if (mBehavior != null) { + mBehavior.update(theDeltaTime, this); + force().add(mBehavior.force()); + } + } + /* clamp to maximum force */ + if (maximumInnerForce() > 0) { + final float mForceLength = force().length(); + if (mForceLength > maximumInnerForce()) { + force().scale(maximumInnerForce() / mForceLength); + } + } + } + + public float maximumInnerForce() { + return mMaximumInnerForce; + } + + public void maximumInnerForce(float theForce) { + mMaximumInnerForce = theForce; + } + + public Vector behaviors() { + return mBehaviors; + } +} diff --git a/day3/libs/teilchen/src/teilchen/ConditionalParticle.java b/day3/libs/teilchen/src/teilchen/ConditionalParticle.java new file mode 100755 index 0000000..1fbf024 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/ConditionalParticle.java @@ -0,0 +1,33 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +public abstract class ConditionalParticle + extends BasicParticle { + + public boolean dead() { + return !condition(); + } + + public abstract boolean condition(); +} diff --git a/day3/libs/teilchen/src/teilchen/IBehaviorParticle.java b/day3/libs/teilchen/src/teilchen/IBehaviorParticle.java new file mode 100755 index 0000000..9e0f2fb --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/IBehaviorParticle.java @@ -0,0 +1,36 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +import java.util.Vector; + + +public interface IBehaviorParticle + extends Particle { + + float maximumInnerForce(); + + void maximumInnerForce(float theForce); + + Vector behaviors(); +} diff --git a/day3/libs/teilchen/src/teilchen/IConnection.java b/day3/libs/teilchen/src/teilchen/IConnection.java new file mode 100755 index 0000000..cdd30ca --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/IConnection.java @@ -0,0 +1,30 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +public interface IConnection { + + Particle a(); + + Particle b(); +} diff --git a/day3/libs/teilchen/src/teilchen/Particle.java b/day3/libs/teilchen/src/teilchen/Particle.java new file mode 100755 index 0000000..37b8e39 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/Particle.java @@ -0,0 +1,70 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +import mathematik.Vector3f; + +import teilchen.util.SpatialEntity; + + +public interface Particle + extends SpatialEntity { + + boolean fixed(); + + void fixed(boolean theFixed); + + float age(); + + void age(float theAge); + + float mass(); + + void mass(float theMass); + + Vector3f position(); + + Vector3f old_position(); + + void setPositionRef(Vector3f thePosition); + + Vector3f velocity(); + + Vector3f force(); + + boolean dead(); + + boolean tagged(); + + void tag(boolean theTag); + + void accumulateInnerForce(final float theDeltaTime); + + float radius(); + + void radius(float theRadius); + + boolean still(); + + void still(boolean theStill); +} diff --git a/day3/libs/teilchen/src/teilchen/Physics.java b/day3/libs/teilchen/src/teilchen/Physics.java new file mode 100755 index 0000000..b5edc01 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/Physics.java @@ -0,0 +1,374 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + +import mathematik.Vector3f; + +import teilchen.constraint.IConstraint; +import teilchen.force.IForce; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.integration.IIntegrator; +import teilchen.integration.Midpoint; +import teilchen.integration.Verlet; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Vector; +import teilchen.integration.RungeKutta; + +public class Physics { + + private final Vector mParticles; + + private final Vector mForces; + + private final Vector mConstraints; + + private IIntegrator mIntegrator; + + private static final float EPSILON = 0.001f; + + public boolean HINT_OPTIMIZE_STILL = true; + + public boolean HINT_REMOVE_DEAD = true; + + public boolean HINT_RECOVER_NAN = true; + + public int contraint_iterations_per_steps = 1; + + public static boolean HINT_UPDATE_OLD_POSITION = true; + + public Physics() { + mParticles = new Vector(); + mForces = new Vector(); + mConstraints = new Vector(); + mIntegrator = new Midpoint(); + } + + + /* particles */ + public void add(Particle theParticle) { + mParticles.add(theParticle); + } + + public void add(Collection theParticles) { + mParticles.addAll(theParticles); + } + + public void remove(Particle theParticle) { + mParticles.remove(theParticle); + } + + public void remove(Collection theParticles) { + + mParticles.removeAll(theParticles); + } + + public Vector particles() { + return mParticles; + } + + public Particle particles(final int theIndex) { + return mParticles.get(theIndex); + } + + public BasicParticle makeParticle() { + BasicParticle myParticle = new BasicParticle(); + mParticles.add(myParticle); + return myParticle; + } + + public BasicParticle makeParticle(final Vector3f thePosition) { + BasicParticle myParticle = makeParticle(); + myParticle.setPositionRef(thePosition); + myParticle.old_position().set(myParticle.position()); + return myParticle; + } + + public BasicParticle makeParticle(final float x, final float y) { + BasicParticle myParticle = makeParticle(); + myParticle.position().set(x, y); + myParticle.old_position().set(myParticle.position()); + return myParticle; + } + + public BasicParticle makeParticle(final float x, final float y, final float z) { + BasicParticle myParticle = makeParticle(); + myParticle.position().set(x, y, z); + myParticle.old_position().set(myParticle.position()); + return myParticle; + } + + public BasicParticle makeParticle(final float x, final float y, final float z, final float pMass) { + BasicParticle myParticle = makeParticle(); + myParticle.position().set(x, y, z); + myParticle.mass(pMass); + myParticle.old_position().set(myParticle.position()); + return myParticle; + } + + public BasicParticle makeParticle(final Vector3f thePosition, final float pMass) { + BasicParticle myParticle = makeParticle(); + myParticle.setPositionRef(thePosition); + myParticle.old_position().set(myParticle.position()); + myParticle.mass(pMass); + return myParticle; + } + + public T makeParticle(Class theParticleClass) { + T myParticle; + try { + myParticle = theParticleClass.newInstance(); + mParticles.add(myParticle); + } catch (Exception ex) { + System.err.println(ex); + myParticle = null; + } + return myParticle; + } + + public void removeTags() { + for (final Particle myParticle : mParticles) { + myParticle.tag(false); + } + } + + + /* forces */ + public void add(IForce theForce) { + if (theForce instanceof ViscousDrag && mIntegrator instanceof Verlet) { + System.err.println( + "### WARNING / 'viscous drag' might have no effect with 'verlet' integration. use 'Verlet.damping(float theDamping)' instead."); + } + mForces.add(theForce); + } + + public void addForces(final Vector theForces) { + mForces.addAll(theForces); + } + + public void remove(IForce theForce) { + mForces.remove(theForce); + } + + public Vector forces() { + return mForces; + } + + public IForce forces(final int theIndex) { + return mForces.get(theIndex); + } + + public void applyForces(final float theDeltaTime) { + /* accumulate inner forces */ + synchronized (mParticles) { + final Iterator iter = mParticles.iterator(); + while (iter.hasNext()) { + final Particle myParticle = iter.next(); + if (!myParticle.fixed()) { + /* accumulate inner forces */ + myParticle.accumulateInnerForce(theDeltaTime); + } + } + } + + /* add new forces to each particle */ + synchronized (mForces) { + Iterator iter = mForces.iterator(); + while (iter.hasNext()) { + final IForce mForce = iter.next(); + if (mForce.active()) { + mForce.apply(theDeltaTime, this); + } + } + } + } + + public T makeForce(Class theForceClass) { + T myForce; + try { + myForce = theForceClass.newInstance(); + mForces.add(myForce); + } catch (Exception ex) { + System.err.println(ex); + myForce = null; + } + return myForce; + } + + public Spring makeSpring(final Particle theA, final Particle theB) { + Spring mySpring = new Spring(theA, theB); + mForces.add(mySpring); + return mySpring; + } + + public Spring makeSpring(final Particle theA, + final Particle theB, + final float theRestLength) { + Spring mySpring = new Spring(theA, theB, theRestLength); + mForces.add(mySpring); + return mySpring; + } + + public Spring makeSpring(final Particle theA, + final Particle theB, + final float theSpringConstant, + final float theSpringDamping) { + Spring mySpring = new Spring(theA, theB, theSpringConstant, theSpringDamping); + mForces.add(mySpring); + return mySpring; + } + + public Spring makeSpring(final Particle theA, + final Particle theB, + final float theSpringConstant, + final float theSpringDamping, + final float theRestLength) { + Spring mySpring = new Spring(theA, theB, theSpringConstant, theSpringDamping, theRestLength); + mForces.add(mySpring); + return mySpring; + } + + + /* constraints */ + public void add(final IConstraint theConstraint) { + mConstraints.add(theConstraint); + } + + public void addConstraints(final Vector theConstraints) { + mConstraints.addAll(theConstraints); + } + + public void remove(final IConstraint theConstraint) { + mConstraints.remove(theConstraint); + } + + public Vector constraints() { + return mConstraints; + } + + public IConstraint constraints(final int theIndex) { + return mConstraints.get(theIndex); + } + + + /* integration */ + public void setInegratorRef(IIntegrator theIntegrator) { + mIntegrator = theIntegrator; + } + + public IIntegrator getIntegrator() { + return mIntegrator; + } + + public void loop(final float theDeltaTime, final int theIterations) { + for (int i = 0; i < theIterations; i++) { + step(theDeltaTime / (float) theIterations); + } + } + + public void step(final float theDeltaTime) { + prepareParticles(theDeltaTime); + handleForces(); + integrate(theDeltaTime); + handleParticles(theDeltaTime); + handleContraints(); + } + + protected synchronized void integrate(float theDeltaTime) { + mIntegrator.step(theDeltaTime, this); + } + + protected synchronized void handleForces() { + synchronized (mForces) { + final Iterator iter = mForces.iterator(); + while (iter.hasNext()) { + final IForce myForce = iter.next(); + if (myForce.dead()) { + iter.remove(); + } + } + } + } + + protected synchronized void handleContraints() { + synchronized (mConstraints) { + for (int i = 0; i < contraint_iterations_per_steps; i++) { + final Iterator iter = mConstraints.iterator(); + while (iter.hasNext()) { + final IConstraint myContraint = iter.next(); + myContraint.apply(this); + } + } + } + } + + protected synchronized void handleParticles(float theDeltaTime) { + synchronized (mParticles) { + final Iterator iter = mParticles.iterator(); + while (iter.hasNext()) { + final Particle myParticle = iter.next(); + /* clear force */ + myParticle.force().set(0, 0, 0); + /* age particle */ + myParticle.age(myParticle.age() + theDeltaTime); + /* remove dead */ + if (HINT_REMOVE_DEAD) { + if (myParticle.dead()) { + iter.remove(); + } + } + /* recover NAN */ + if (HINT_RECOVER_NAN) { + if (myParticle.position().isNaN()) { + if (myParticle.old_position().isNaN()) { + myParticle.position().set(0, 0, 0); + } else { + myParticle.position().set(myParticle.old_position()); + } + } + if (myParticle.velocity().isNaN()) { + myParticle.velocity().set(0, 0, 0); + } + } + /* still */ + if (HINT_OPTIMIZE_STILL) { + final float mySpeed = myParticle.velocity().lengthSquared(); + myParticle.still(mySpeed > -EPSILON && mySpeed < EPSILON); + } + } + } + } + + protected synchronized void prepareParticles(float theDeltaTime) { + synchronized (mParticles) { + final Iterator iter = mParticles.iterator(); + while (iter.hasNext()) { + final Particle myParticle = iter.next(); + if (HINT_UPDATE_OLD_POSITION) { + myParticle.old_position().set(myParticle.position()); + } + } + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/ShortLivedParticle.java b/day3/libs/teilchen/src/teilchen/ShortLivedParticle.java new file mode 100755 index 0000000..fcb6557 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/ShortLivedParticle.java @@ -0,0 +1,49 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +public class ShortLivedParticle + extends BasicParticle { + + private float _myMaxAge; + + public ShortLivedParticle(float theMaxAge) { + _myMaxAge = theMaxAge; + } + + public ShortLivedParticle() { + this(1); + } + + public void setMaxAge(float theMaxAge) { + _myMaxAge = theMaxAge; + } + + public float ageRatio() { + return Math.min(age() / _myMaxAge, 1); + } + + public boolean dead() { + return age() >= _myMaxAge; + } +} diff --git a/day3/libs/teilchen/src/teilchen/VectorfieldParticle.java b/day3/libs/teilchen/src/teilchen/VectorfieldParticle.java new file mode 100755 index 0000000..737dc3b --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/VectorfieldParticle.java @@ -0,0 +1,48 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen; + + +import mathematik.Vector3i; + + +public class VectorfieldParticle + extends BasicParticle { + + private Vector3i _myLastUnit; + + public VectorfieldParticle() { + super(); + _myLastUnit = new Vector3i(); + } + + public void setLastUnit(Vector3i theUnit) { + _myLastUnit = theUnit; + } + + public Vector3i getLastUnit() { + return _myLastUnit; + } + + public void accumulateInnerForce(final float theDeltaTime) { + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Alignment.java b/day3/libs/teilchen/src/teilchen/behavior/Alignment.java new file mode 100755 index 0000000..253c209 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Alignment.java @@ -0,0 +1,107 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; +import teilchen.behavior.Util.ProximityStructure; +import java.io.Serializable; +import java.util.Vector; + + +public class Alignment + implements IBehavior, + Serializable { + + private static final long serialVersionUID = -4953599448151741585L; + + private float mProximity; + + private float mWeight; + + private final Vector3f mForce; + + private Vector mNeighbors; + + public Alignment() { + mProximity = 100.0f; + mWeight = 1.0f; + mForce = new Vector3f(); + } + + public void update(float theDeltaTime, IBehaviorParticle pParent) { + mForce.set(0, 0, 0); + if (mNeighbors != null) { + Vector mCloseNeighbors = ProximityStructure.findProximityEntities(pParent, mNeighbors, mProximity); + findCommonVelocity(mCloseNeighbors, mForce); + mForce.scale(weight()); + } + } + + private static void findCommonVelocity(Vector mCloseNeighbors, final Vector3f pForce) { + /* find away vector */ + pForce.set(0, 0, 0); + if (!mCloseNeighbors.isEmpty()) { + /** + * @todo the vectors could be weighted according to distance: 1.0 - + * distance ( for example ) + */ + for (ProximityStructure p : mCloseNeighbors) { + pForce.add(p.particle.velocity()); + } + pForce.scale(1.0f / mCloseNeighbors.size()); + pForce.normalize(); + if (pForce.isNaN()) { + pForce.set(0, 0, 0); + } + } + } + + public void neighbors(final Vector pNeighbors) { + /** + * @todo well is this OK? + */ + mNeighbors = (Vector) pNeighbors; + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float pWeight) { + mWeight = pWeight; + } + + public float proximity() { + return mProximity; + } + + public void proximity(float thePrivacyRadius) { + mProximity = thePrivacyRadius; + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Arrival.java b/day3/libs/teilchen/src/teilchen/behavior/Arrival.java new file mode 100755 index 0000000..0dedfa2 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Arrival.java @@ -0,0 +1,159 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; +import teilchen.IBehaviorParticle; + + +public class Arrival + implements IBehavior, Verhalten { + + static final long serialVersionUID = 6897889750581191781L; + + private Vector3f mSeekPosition; + + private final Vector3f mForce; + + private float mWeight; + + private float mBreakRadius; + + private float mBreakForce; + + private boolean mIsArriving; + + private boolean mHasArrived; + + private boolean mOverSteer; + + public Arrival() { + mBreakRadius = 50.0f; + mBreakForce = 50.0f; + mForce = new Vector3f(); + mSeekPosition = new Vector3f(); + mWeight = 1; + mIsArriving = false; + mHasArrived = false; + mOverSteer = true; + } + + public boolean arriving() { + return mIsArriving; + } + + public boolean arrived() { + return mHasArrived; + } + + public boolean oversteer() { + return mOverSteer; + } + + public void oversteer(boolean pOverSteer) { + mOverSteer = pOverSteer; + } + + public Vector3f position() { + return mSeekPosition; + } + + public void setPositionRef(final Vector3f pPoint) { + mSeekPosition = pPoint; + } + + public void breakforce(float pBreakForce) { + mBreakForce = pBreakForce; + } + + public float breakforce() { + return mBreakForce; + } + + public void breakradius(float pOutterRadius) { + mBreakRadius = pOutterRadius; + } + + public float breakradius() { + return mBreakRadius; + } + + public void update(float theDeltaTime, IBehaviorParticle pParent) { + mForce.sub(mSeekPosition, pParent.position()); + final float myDistanceToArrivalPoint = mForce.length(); + + /* get direction */ + if (myDistanceToArrivalPoint < mBreakRadius) { + mIsArriving = true; + final float mSpeed = pParent.velocity().length(); + final float MIN_ACCEPTABLE_SPEED = 10.0f; + if (mSpeed < MIN_ACCEPTABLE_SPEED) { + /* sleep */ + mForce.set(0, 0, 0); + mHasArrived = true; + } else { + /* break */ + final boolean USE_WEIGHTED_BREAK_FORCE = true; + if (USE_WEIGHTED_BREAK_FORCE) { + final float mRatio = myDistanceToArrivalPoint / mBreakRadius; + + final Vector3f mBreakForceVector = new Vector3f(pParent.velocity()); + mBreakForceVector.scale(-mBreakForce); + mBreakForceVector.scale(1.0f - mRatio); + + final Vector3f mSteerForce = new Vector3f(mForce); + mSteerForce.scale(pParent.maximumInnerForce() / myDistanceToArrivalPoint); + mSteerForce.scale(mRatio); + + mForce.add(mBreakForceVector, mSteerForce); + } else { + mForce.set(pParent.velocity().x * -mBreakForce, + pParent.velocity().y * -mBreakForce, + pParent.velocity().z * -mBreakForce); + } + mHasArrived = false; + } + } else { + /* outside of the outter radius continue 'seeking' */ + mForce.scale(pParent.maximumInnerForce() / myDistanceToArrivalPoint); + if (mOverSteer) { + mForce.sub(mForce, pParent.velocity()); + } + mIsArriving = false; + mHasArrived = false; + } + mForce.scale(weight()); + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float pWeight) { + mWeight = pWeight; + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Cohesion.java b/day3/libs/teilchen/src/teilchen/behavior/Cohesion.java new file mode 100755 index 0000000..8d760a4 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Cohesion.java @@ -0,0 +1,111 @@ + +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; +import teilchen.behavior.Util.ProximityStructure; +import java.io.Serializable; +import java.util.Vector; + + +public class Cohesion + implements IBehavior, + Serializable { + + private static final long serialVersionUID = -4953599448151741585L; + + private float mProximity; + + private float mWeight; + + private final Vector3f mForce; + + private Vector mNeighbors; + + public Cohesion() { + mProximity = 100.0f; + mWeight = 1.0f; + mForce = new Vector3f(); + } + + public void update(float theDeltaTime, IBehaviorParticle pParent) { + mForce.set(0, 0, 0); + if (mNeighbors != null) { + Vector mCloseNeighbors = ProximityStructure.findProximityEntities(pParent, mNeighbors, mProximity); + findTowardsVector(mCloseNeighbors, mForce); + mForce.scale(weight()); + } + } + + private static void findTowardsVector(Vector mCloseNeighbors, final Vector3f pForce) { + /* find away vector */ + if (!mCloseNeighbors.isEmpty()) { + pForce.set(0, 0, 0); + /** + * @todo the vectors could be weighted according to distance: 1.0 - + * distance ( for example ) + */ + for (ProximityStructure p : mCloseNeighbors) { + final Vector3f mTowardsVector = mathematik.Util.scale(p.distanceVec, -1.0f); + pForce.add(mTowardsVector); + } + pForce.scale(1.0f / mCloseNeighbors.size()); + pForce.normalize(); + if (pForce.isNaN()) { + pForce.set(0, 0, 0); + } + } else { + pForce.set(0, 0, 0); + } + } + + public void neighbors(final Vector pNeighbors) { + /** + * @todo well is this OK? + */ + mNeighbors = (Vector) pNeighbors; + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float pWeight) { + mWeight = pWeight; + } + + public float proximity() { + return mProximity; + } + + public void proximity(float thePrivacyRadius) { + mProximity = thePrivacyRadius; + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Flee.java b/day3/libs/teilchen/src/teilchen/behavior/Flee.java new file mode 100755 index 0000000..0d5d829 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Flee.java @@ -0,0 +1,77 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; + + +public class Flee + implements IBehavior, Verhalten { + + static final long serialVersionUID = -6530887943347815188L; + + private Vector3f mFleePosition; + + private Vector3f mForce; + + private float mWeight = 1; + + public Flee() { + mFleePosition = new Vector3f(); + mForce = new Vector3f(); + } + + public Vector3f position() { + return mFleePosition; + } + + public void setPositionRef(final Vector3f thePoint) { + mFleePosition = thePoint; + } + + public void update(float theDeltaTime, IBehaviorParticle theParent) { + mForce.sub(theParent.position(), mFleePosition); + final float myDistanceToPoint = mForce.length(); + if (myDistanceToPoint > SMALLEST_ACCEPTABLE_DISTANCE) { + mForce.scale(theParent.maximumInnerForce() / myDistanceToPoint); + mForce.sub(mForce, theParent.velocity()); + mForce.scale(weight()); + } else { + mForce.set(0, 0, 0); + } + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float theWeight) { + mWeight = theWeight; + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/IBehavior.java b/day3/libs/teilchen/src/teilchen/behavior/IBehavior.java new file mode 100755 index 0000000..af0fa37 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/IBehavior.java @@ -0,0 +1,42 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import java.io.Serializable; + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; + + +public interface IBehavior + extends Serializable { + + void update(float theDeltaTime, final IBehaviorParticle pParent); + + Vector3f force(); + + float weight(); + + void weight(float theWeight); +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Motor.java b/day3/libs/teilchen/src/teilchen/behavior/Motor.java new file mode 100755 index 0000000..17366cd --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Motor.java @@ -0,0 +1,127 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; + + +public class Motor + implements IBehavior, + Verhalten { + + static final long serialVersionUID = -3781170603537691466L; + + private Vector3f mDirection; + + private float mStrength; + + private final Vector3f mForce; + + private float mWeight; + + private boolean mAutoNormalizeDirection; + + private boolean mActive; + + private boolean mAutoUpdateDirection; + + public final Vector3f AUTO_RECOVER_DIRECTION; + + public Motor() { + mDirection = new Vector3f(1, 0, 0); + mForce = new Vector3f(); + mActive = true; + mStrength = 1; + mWeight = 1; + mAutoUpdateDirection = false; + mAutoNormalizeDirection = true; + AUTO_RECOVER_DIRECTION = new Vector3f(); + AUTO_RECOVER_DIRECTION.randomize(); + AUTO_RECOVER_DIRECTION.z = 0; + } + + public boolean active() { + return mActive; + } + + public void active(boolean pActive) { + mActive = pActive; + } + + public float strength() { + return mStrength; + } + + public void strength(final float theStrength) { + mStrength = theStrength; + } + + public Vector3f direction() { + return mDirection; + } + + public void setDirectionRef(final Vector3f theDirection) { + mDirection = theDirection; + } + + public void auto_update_direction(boolean pAutoUpdateDirection) { + mAutoUpdateDirection = pAutoUpdateDirection; + } + + public void auto_normalize_direction(boolean pAutoNormalizeDirection) { + mAutoNormalizeDirection = pAutoNormalizeDirection; + } + + public void update(float theDeltaTime, IBehaviorParticle pParent) { + if (mActive) { + if (mAutoUpdateDirection) { + if (pParent.velocity().length() > 0.0f) { + mDirection.set(pParent.velocity()); + } else { + mDirection.set(AUTO_RECOVER_DIRECTION); + } + } + if (mAutoNormalizeDirection) { + mDirection.normalize(); + } + mForce.scale(mStrength, mDirection); + mForce.scale(mWeight, mForce); + } else { + mForce.set(0, 0, 0); + } + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float theWeight) { + mWeight = theWeight; + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Seek.java b/day3/libs/teilchen/src/teilchen/behavior/Seek.java new file mode 100755 index 0000000..d3f64cd --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Seek.java @@ -0,0 +1,96 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; + + +public class Seek + implements IBehavior, Verhalten { + + static final long serialVersionUID = -3781170603537691477L; + + private Vector3f mSeekPosition; + + private Vector3f mForce; + + private float mWeight = 1; + + private float mDistanceToPoint; + + private boolean mOverSteer; + + public Seek() { + mSeekPosition = new Vector3f(); + mForce = new Vector3f(); + mOverSteer = false; + } + + public boolean oversteer() { + return mOverSteer; + } + + public void oversteer(boolean pOverSteer) { + mOverSteer = pOverSteer; + } + + public Vector3f position() { + return mSeekPosition; + } + + public void setPositionRef(final Vector3f thePoint) { + mSeekPosition = thePoint; + } + + public float distancetopoint() { + return mDistanceToPoint; + } + + public void update(float theDeltaTime, IBehaviorParticle theParent) { + mForce.sub(mSeekPosition, theParent.position()); + mDistanceToPoint = mForce.length(); + if (mDistanceToPoint > SMALLEST_ACCEPTABLE_DISTANCE) { + mForce.scale(theParent.maximumInnerForce() / mDistanceToPoint); + if (mOverSteer) { + mForce.sub(mForce, theParent.velocity()); + } + mForce.scale(weight()); + } else { + mForce.set(0, 0, 0); + } + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float theWeight) { + mWeight = theWeight; + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Separation.java b/day3/libs/teilchen/src/teilchen/behavior/Separation.java new file mode 100755 index 0000000..90d6279 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Separation.java @@ -0,0 +1,110 @@ + +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; +import teilchen.behavior.Util.ProximityStructure; +import java.io.Serializable; +import java.util.Vector; + + +public class Separation + implements IBehavior, + Serializable { + + private static final long serialVersionUID = -4953599448151741585L; + + private float mProximity; + + private float mWeight; + + private final Vector3f mForce; + + private Vector mNeighbors; + + public Separation() { + mProximity = 100.0f; + mWeight = 1.0f; + mForce = new Vector3f(); + } + + public void update(float theDeltaTime, IBehaviorParticle pParent) { + mForce.set(0, 0, 0); + if (mNeighbors != null) { + Vector mCloseNeighbors = ProximityStructure.findProximityEntities(pParent, mNeighbors, mProximity); + findAwayVector(mCloseNeighbors, mForce); + mForce.scale(weight()); + } + } + + private static void findAwayVector(Vector mCloseNeighbors, final Vector3f pForce) { + /* find away vector */ + if (!mCloseNeighbors.isEmpty()) { + pForce.set(0, 0, 0); + /** + * @todo the vectors could be weighted according to distance: 1.0 - + * distance ( for example ) + */ + for (ProximityStructure p : mCloseNeighbors) { + pForce.add(p.distanceVec); + } + pForce.scale(1.0f / mCloseNeighbors.size()); + pForce.normalize(); + if (pForce.isNaN()) { + pForce.set(0, 0, 0); + } + } else { + pForce.set(0, 0, 0); + } + } + + public void neighbors(final Vector pNeighbors) { + /** + * @todo well is this OK? + */ + mNeighbors = (Vector) pNeighbors; + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float pWeight) { + mWeight = pWeight; + } + + public float proximity() { + return mProximity; + } + + public void proximity(float thePrivacyRadius) { + mProximity = thePrivacyRadius; + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Steering.java b/day3/libs/teilchen/src/teilchen/behavior/Steering.java new file mode 100755 index 0000000..ee609d9 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Steering.java @@ -0,0 +1,91 @@ + +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + +import mathematik.Vector3f; +import teilchen.IBehaviorParticle; + +public class Steering implements IBehavior, + Verhalten { + + private final Vector3f mForce; + private final Vector3f mUPVector; + + private float mWeight; + + private float mSteering = 0.0f; + + private boolean mActive = true; + + public Steering() { + mForce = new Vector3f(); + mUPVector = new Vector3f(0, 0, -1); + mWeight = 1; + } + + public void update(float theDeltaTime, IBehaviorParticle pParent) { + if (mActive) { + /* 2D warning -- ignoring z-axis for now */ + Vector3f mDirection = new Vector3f(pParent.velocity()); + if (mDirection.lengthSquared() > 0) { + mDirection.normalize(); + mDirection = mathematik.Util.cross(mDirection, mUPVector); + mDirection.scale(mSteering); + mForce.set(mDirection); + } else { + mForce.set(0, 0, 0); + } + } else { + mForce.set(0, 0, 0); + } + } + + public float steering_strength() { + return mSteering; + } + + public void steering_strength(float pSteering) { + mSteering = pSteering; + } + + public Vector3f force() { + return mForce; + } + + public Vector3f upvector() { + return mUPVector; + } + + public float weight() { + return mWeight; + } + + public void weight(float pWeight) { + mWeight = pWeight; + } + + public void active(boolean pActive) { + mActive = pActive; + } + +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Util.java b/day3/libs/teilchen/src/teilchen/behavior/Util.java new file mode 100755 index 0000000..a9bcbfd --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Util.java @@ -0,0 +1,66 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; +import teilchen.Particle; + +import java.util.Vector; + + +public class Util { + + public static class ProximityStructure { + + public final Particle particle; + + public final Vector3f distanceVec; + + public final float distance; + + public ProximityStructure(Particle pP, Vector3f pDistanceVec, float pDistance) { + particle = pP; + distanceVec = pDistanceVec; + distance = pDistance; + } + + public static Vector findProximityEntities(IBehaviorParticle pParentEntity, + Vector pNeighborsEntity, + float pProximity) { + /* find neighbors in proximity */ + Vector mCloseNeighbors = new Vector(); + for (IBehaviorParticle p : pNeighborsEntity) { + if (!p.equals(pParentEntity)) { /* exclude self */ + final Vector3f mDistanceVec = mathematik.Util.sub(pParentEntity.position(), p.position()); + final float mDistance = mDistanceVec.length(); + if (mDistance <= pProximity) { + mCloseNeighbors.add(new ProximityStructure(p, mDistanceVec, mDistance)); + } + } + } + return mCloseNeighbors; + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Verhalten.java b/day3/libs/teilchen/src/teilchen/behavior/Verhalten.java new file mode 100755 index 0000000..f03f369 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Verhalten.java @@ -0,0 +1,33 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import java.io.Serializable; + + +public interface Verhalten + extends Serializable { + + float SMALLEST_ACCEPTABLE_DISTANCE = 0.01f; + +} diff --git a/day3/libs/teilchen/src/teilchen/behavior/Wander.java b/day3/libs/teilchen/src/teilchen/behavior/Wander.java new file mode 100755 index 0000000..0346fdb --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/behavior/Wander.java @@ -0,0 +1,122 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.behavior; + + +import mathematik.Random; +import mathematik.Vector3f; + +import teilchen.IBehaviorParticle; + + +public class Wander + implements IBehavior { + + static final long serialVersionUID = 4957162698340669663L; + + private Vector3f mForce; + + private float mSteeringStrength; + + private float mSteeringOffset; + + private float mCurrentSteeringStrength; + + private Vector3f mUpVector; + + private float mWeight; + + private final Random mRandom; + + private boolean mActive; + + public Wander() { + mRandom = new Random(); + + mForce = new Vector3f(); + mSteeringStrength = 10f; + mSteeringOffset = 5f; + + mUpVector = new Vector3f(0, 0, 1); + + mWeight = 1; + mActive = true; + } + + public boolean active() { + return mActive; + } + + public void active(boolean pActive) { + mActive = pActive; + } + + public Vector3f force() { + return mForce; + } + + public float weight() { + return mWeight; + } + + public void weight(float theWeight) { + mWeight = theWeight; + } + + public void update(float pDeltaTime, IBehaviorParticle pParent) { + if (mActive && pParent.velocity().length() > 0) { + mCurrentSteeringStrength += mRandom.getFloat(-0.5f, 0.5f) * mSteeringOffset; + mCurrentSteeringStrength = Math.max(Math.min(mCurrentSteeringStrength, mSteeringStrength), -mSteeringStrength); + + final Vector3f mWanderTarget = mathematik.Util.cross(mUpVector, pParent.velocity()); + mWanderTarget.normalize(); + mWanderTarget.scale(mCurrentSteeringStrength); + if (mWanderTarget.isNaN()) { + mForce.set(0, 0, 0); + } else { + mForce.scale(mWeight, mWanderTarget); + } + } else { + mForce.set(0, 0, 0); + } + } + + public Vector3f upvector() { + return mUpVector; + } + + public float steeringstrength() { + return mSteeringStrength; + } + + public void steeringstrength(final float theSteeringStrength) { + mSteeringStrength = theSteeringStrength; + } + + public float steeringoffset() { + return mSteeringOffset; + } + + public void steeringoffset(final float theSteeringOffset) { + mSteeringOffset = theSteeringOffset; + } +} diff --git a/day3/libs/teilchen/src/teilchen/constraint/AngleConstraintStick.java b/day3/libs/teilchen/src/teilchen/constraint/AngleConstraintStick.java new file mode 100755 index 0000000..c2a6162 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/constraint/AngleConstraintStick.java @@ -0,0 +1,79 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.constraint; + + +import mathematik.Vector3f; + +import teilchen.Particle; + + +public class AngleConstraintStick + extends Stick { + + private final Particle mParticleA; + + private final Particle mParticleB; + + private final Particle mParticleC; + + private float mMinAngle; + + /** + * + * particles are connected like this: A -- B -- C + * + * @param pParticleA + * @param pParticleB + * @param pParticleC + */ + public AngleConstraintStick(Particle pParticleA, Particle pParticleB, Particle pParticleC) { + super(pParticleA, pParticleC); + mParticleA = pParticleA; + mParticleB = pParticleB; + mParticleC = pParticleC; + mMinAngle = Float.MAX_VALUE; + } + + public void min_angle(float pAngle) { + mMinAngle = pAngle; + } + + public void pre_step() { + Vector3f ab = mathematik.Util.sub(mParticleA.position(), mParticleB.position()); + Vector3f cb = mathematik.Util.sub(mParticleC.position(), mParticleB.position()); + final float mCurrentAngle = ab.angle(cb); + + if (mCurrentAngle < mMinAngle) { + final float b = ab.length(); + final float c = cb.length(); + // a = sqrt ( b*b + c*c - 2bc*cosA ) + final float mDistance = (float) Math.sqrt(b * b + c * c - 2 * b * c * (float) Math.cos(mMinAngle)); + restlength(mDistance); + active(true); + } + } + + public void post_step() { + active(false); + } +} diff --git a/day3/libs/teilchen/src/teilchen/constraint/Angular.java b/day3/libs/teilchen/src/teilchen/constraint/Angular.java new file mode 100755 index 0000000..01004be --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/constraint/Angular.java @@ -0,0 +1,221 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.constraint; + + +import mathematik.Util; +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +/** + * @todo it probably pays of two check if we deal with a 2D or 3D constraint. it + * s just checking components once and then saving a lot of time. + */ +public class Angular + implements IConstraint { + + protected boolean mActive = true; + + private final Particle _myA; + + private final Particle _myB; + + private final Particle _myC; + + private final Vector3f _myTempA = new Vector3f(); + + private final Vector3f _myTempB = new Vector3f(); + + private float _myMinimumAngle; + + private float _myMaximumAngle; + + private final Vector3f _myTempNormal; + + public boolean OK; + + private static final double EPSILON = 0.001; + + private static final double _myStrength = 1; + + public Angular(Particle theA, Particle theB, Particle theC, + float theMinimumAngle, float theMaximumAngle) { + _myA = theA; + _myB = theB; + _myC = theC; + _myTempNormal = new Vector3f(); + range(theMinimumAngle, theMaximumAngle); + } + + public Angular(Particle theA, Particle theB, Particle theC) { + this(theA, + theB, + theC, + 0, 0); + } + + public void range(float theMinimumAngle, float theMaximumAngle) { + _myMinimumAngle = theMinimumAngle; + _myMaximumAngle = theMaximumAngle; + sortAngles(); + } + + public float minimumAngle() { + return _myMinimumAngle; + } + + public float maximumAngle() { + return _myMaximumAngle; + } + + private void sortAngles() { + final float myMaximumAngle = _myMaximumAngle; + final float myMinimumAngle = _myMinimumAngle; + _myMaximumAngle = Math.max(myMaximumAngle, myMinimumAngle); + _myMinimumAngle = Math.min(myMaximumAngle, myMinimumAngle); + } + + public void apply(Physics theParticleSystem) { + + if (!mActive) { + return; + } + + /** + * @todo test for special case: a and c are in the same place. + */ + _myTempA.sub(_myB.position(), _myA.position()); + _myTempB.sub(_myB.position(), _myC.position()); + + _myTempA.normalize(); + _myTempB.normalize(); + + /** + * @todo check for special cases! like angle being 0 etc. + */ + /** + * @todo check if the range exceeds PI. + */ + if (_myMinimumAngle < Math.PI && _myMaximumAngle > Math.PI) { + System.out.println("### WARNING split range and check twice."); + } + + float myCosinusAngle = _myTempA.dot(_myTempB); + if (myCosinusAngle > 1) { + System.out.println("### WARNING myCosinusAngle > 1: " + myCosinusAngle); + myCosinusAngle = 1; + } + + final float myTempCosMaximumAngle = (float) Math.cos(_myMaximumAngle); + final float myTempCosMinimumAngle = (float) Math.cos(_myMinimumAngle); + final float myCosMaximumAngle = Math.max(myTempCosMinimumAngle, myTempCosMaximumAngle); + final float myCosMinimumAngle = Math.min(myTempCosMinimumAngle, myTempCosMaximumAngle); + + calculateNormal(_myTempA, _myTempB); + final boolean myLeftSide = checkForHemisphere(_myTempA, _myTempB); + double myCurrentAngle = 0; + + /** + * @todo until i the split is implemented agular constraints only work + * for one side. + */ + OK = myLeftSide; + + if (myLeftSide) { + if (myCosinusAngle < myCosMinimumAngle || myCosinusAngle > myCosMaximumAngle) { + myCurrentAngle = Math.acos(myCosinusAngle); + OK = false; + } else { + OK = true; + } + } else { + myCurrentAngle = 2 * Math.PI - Math.acos(myCosinusAngle); + } + + if (!OK) { + final double myTheta; + if (myCurrentAngle > _myMaximumAngle) { + myTheta = _myMaximumAngle - myCurrentAngle; + } else if (myCosinusAngle < _myMinimumAngle) { + myTheta = -1 * (myCurrentAngle - _myMinimumAngle); + } else { + System.out.println("### WARNING puzzled."); + myTheta = 0; + } + + correctAngle(myTheta); + } + } + + private void calculateNormal(Vector3f myVectorA, Vector3f myVectorB) { + _myTempNormal.cross(myVectorA, myVectorB); + _myTempNormal.normalize(); + if (_myTempNormal.isNaN()) { + _myTempNormal.set(0, 0, 1); + System.out.println("### WARNING can t find normal."); + } + } + + private void correctAngle(double theTheta) { + if (theTheta < -EPSILON || theTheta > EPSILON) { + + Vector3f myOtherPointOnAxis = Util.add(_myB.position(), _myTempNormal); + + Vector3f myRotatedPointA = Util.rotatePoint(_myA.position(), theTheta * -0.5 * _myStrength, + _myB.position(), + myOtherPointOnAxis); + _myA.position().set(myRotatedPointA); + + Vector3f myRotatedPointB = Util.rotatePoint(_myC.position(), theTheta * 0.5 * _myStrength, + _myB.position(), + myOtherPointOnAxis); + _myC.position().set(myRotatedPointB); + + System.out.println("correct " + Math.toDegrees(theTheta) + " / " + _myTempNormal); + } + } + + private boolean checkForHemisphere(Vector3f myVectorA, Vector3f myVectorB) { + /* special case thus easy to find the direction */ + if (myVectorA.z == 0 && myVectorB.z == 0) { + return _myTempNormal.z > 0; + } else { + /** + * @todo do it the hard way and create a matrix from the two vectors + * and transform the cross vector into local space + */ + System.out.println("### WARNING calculate for 3D plane / not implemented."); + return true; + } + } + + public boolean active() { + return mActive; + } + + public void active(boolean theActiveState) { + mActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/constraint/Box.java b/day3/libs/teilchen/src/teilchen/constraint/Box.java new file mode 100755 index 0000000..85fa0fe --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/constraint/Box.java @@ -0,0 +1,179 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.constraint; + + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.integration.Verlet; + + +public class Box + implements IConstraint { + + protected boolean mActive = true; + + private final Vector3f _myMin; + + private final Vector3f _myMax; + + private boolean _myReflectFlag; + + private float _myCoefficientOfRestitution; + + private boolean _myTeleport; + + public Box(final Vector3f theMin, final Vector3f theMax) { + _myMin = theMin; + _myMax = theMax; + _myReflectFlag = true; + _myCoefficientOfRestitution = 1.0f; + _myTeleport = false; + } + + public Box() { + this(new Vector3f(), new Vector3f()); + } + + public void telelport(boolean theTeleportState) { + _myTeleport = theTeleportState; + } + + public void reflect(boolean theReflectState) { + _myReflectFlag = theReflectState; + } + + public Vector3f min() { + return _myMin; + } + + public Vector3f max() { + return _myMax; + } + private static final Vector3f[] _myNormals; + + static { + _myNormals = new Vector3f[6]; + _myNormals[0] = new Vector3f(-1, 0, 0); + _myNormals[1] = new Vector3f(0, -1, 0); + _myNormals[2] = new Vector3f(0, 0, -1); + _myNormals[3] = new Vector3f(1, 0, 0); + _myNormals[4] = new Vector3f(0, 1, 0); + _myNormals[5] = new Vector3f(0, 0, 1); + } + + public void coefficientofrestitution(float theCoefficientOfRestitution) { + _myCoefficientOfRestitution = theCoefficientOfRestitution; + } + + public float coefficientofrestitution() { + return _myCoefficientOfRestitution; + } + + public void apply(final Physics theParticleSystem) { + + if (!mActive) { + return; + } + + for (final Particle myParticle : theParticleSystem.particles()) { + if (_myTeleport) { + if (myParticle.position().x > _myMax.x) { + myParticle.position().x = _myMin.x; + } + if (myParticle.position().y > _myMax.y) { + myParticle.position().y = _myMin.y; + } + if (myParticle.position().z > _myMax.z) { + myParticle.position().z = _myMin.z; + } + if (myParticle.position().x < _myMin.x) { + myParticle.position().x = _myMax.x; + } + if (myParticle.position().y < _myMin.y) { + myParticle.position().y = _myMax.y; + } + if (myParticle.position().z < _myMin.z) { + myParticle.position().z = _myMax.z; + } + } else { + /** + * @todo to do this properly we would need to add the normals + * and normalize them. maybe later. + */ + int myTag = -1; + final Vector3f myPosition = new Vector3f(myParticle.position()); + if (myParticle.position().x > _myMax.x) { + myParticle.position().x = _myMax.x; + myTag = 0; + } + if (myParticle.position().y > _myMax.y) { + myParticle.position().y = _myMax.y; + myTag = 1; + } + if (myParticle.position().z > _myMax.z) { + myParticle.position().z = _myMax.z; + myTag = 2; + } + if (myParticle.position().x < _myMin.x) { + myParticle.position().x = _myMin.x; + myTag = 3; + } + if (myParticle.position().y < _myMin.y) { + myParticle.position().y = _myMin.y; + myTag = 4; + } + if (myParticle.position().z < _myMin.z) { + myParticle.position().z = _myMin.z; + myTag = 5; + } + if (myTag >= 0) { + if (_myReflectFlag) { + if (theParticleSystem.getIntegrator() instanceof Verlet) { + final Vector3f myDiff = mathematik.Util.sub(myPosition, myParticle.position()); + teilchen.util.Util.reflect(myDiff, _myNormals[myTag], _myCoefficientOfRestitution); +// System.out.println("### reflect " + _myNormals[myTag]); +// System.out.println("myDiff " + myDiff); + myParticle.old_position().sub(myDiff); + } else { + teilchen.util.Util.reflectVelocity(myParticle, + _myNormals[myTag], + _myCoefficientOfRestitution); + } + } else { + myParticle.velocity().set(0, 0, 0); + } + } + } + } + } + + public boolean active() { + return mActive; + } + + public void active(boolean theActiveState) { + mActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/constraint/IConstraint.java b/day3/libs/teilchen/src/teilchen/constraint/IConstraint.java new file mode 100755 index 0000000..5c93a8c --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/constraint/IConstraint.java @@ -0,0 +1,35 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.constraint; + + +import teilchen.Physics; + + +public interface IConstraint { + + void apply(final Physics theParticleSystem); + + boolean active(); + + void active(boolean theActiveState); +} diff --git a/day3/libs/teilchen/src/teilchen/constraint/ReflectBox.java b/day3/libs/teilchen/src/teilchen/constraint/ReflectBox.java new file mode 100755 index 0000000..0f560d2 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/constraint/ReflectBox.java @@ -0,0 +1,221 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.constraint; + + +import java.util.Vector; + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.integration.Verlet; + + +public class ReflectBox + implements IConstraint { + + protected boolean mActive = true; + + private final Vector3f _myMin; + + private final Vector3f _myMax; + + private float _myCoefficientOfRestitution; + + private float _myEpsilon; + + public boolean NEGATIVE_X = true; + + public boolean NEGATIVE_Y = true; + + public boolean NEGATIVE_Z = true; + + public boolean POSITIV_X = true; + + public boolean POSITIV_Y = true; + + public boolean POSITIV_Z = true; + + public ReflectBox(final Vector3f theMin, final Vector3f theMax) { + _myMin = theMin; + _myMax = theMax; + _myCoefficientOfRestitution = 1.0f; + _myEpsilon = 0.001f; + } + + public ReflectBox() { + this(new Vector3f(), new Vector3f()); + } + + public void epsilon(final float theEpsilon) { + _myEpsilon = theEpsilon; + } + + public Vector3f min() { + return _myMin; + } + + public Vector3f max() { + return _myMax; + } + private static final Vector3f[] _myNormals; + + static { + _myNormals = new Vector3f[6]; + _myNormals[0] = new Vector3f(-1, 0, 0); + _myNormals[1] = new Vector3f(0, -1, 0); + _myNormals[2] = new Vector3f(0, 0, -1); + _myNormals[3] = new Vector3f(1, 0, 0); + _myNormals[4] = new Vector3f(0, 1, 0); + _myNormals[5] = new Vector3f(0, 0, 1); + } + + public void coefficientofrestitution(float theCoefficientOfRestitution) { + _myCoefficientOfRestitution = theCoefficientOfRestitution; + } + + public float coefficientofrestitution() { + return _myCoefficientOfRestitution; + } + + public void apply(final Physics theParticleSystem) { + if (!(theParticleSystem.getIntegrator() instanceof Verlet)) { + System.out.println("### WARNING @ " + getClass().getSimpleName() + " / only works with verlet integrator."); + } + apply(theParticleSystem.particles()); + } + + public void apply(final Vector theParticles) { + apply(theParticles, null); + } + + public void apply(final Vector theParticles, final Vector theCollisionParticles) { + if (!mActive) { + return; + } + + for (final Particle myParticle : theParticles) { + final Vector3f myPositionBeforeCollision = new Vector3f(myParticle.position()); + final Vector3f p = myParticle.position(); + final Vector3f p_old = myParticle.old_position(); + final float r = myParticle.radius(); + /** + * @todo we should weight the deflection normal + */ + if (p.x + r > _myMax.x + || p.y + r > _myMax.y + || p.z + r > _myMax.z + || p.x - r < _myMin.x + || p.y - r < _myMin.y + || p.z - r < _myMin.z) { + int myNumberOfCollisions = 0; + final Vector3f myDeflectionNormal = new Vector3f(); + if (POSITIV_X) { + if (p.x + r > _myMax.x) { + final float myBorderDiff = _myMax.x - p_old.x - r; + p.x = p_old.x + myBorderDiff; + myDeflectionNormal.add(_myNormals[0]); + myNumberOfCollisions++; + } + } + + if (POSITIV_Y) { + if (p.y + r > _myMax.y) { + final float myBorderDiff = _myMax.y - p_old.y - r; + p.y = p_old.y + myBorderDiff; + myDeflectionNormal.add(_myNormals[1]); + myNumberOfCollisions++; + } + } + + if (POSITIV_Z) { + if (p.z + r > _myMax.z) { + final float myBorderDiff = _myMax.z - p_old.z - r; + p.z = p_old.z + myBorderDiff; + myDeflectionNormal.add(_myNormals[2]); + myNumberOfCollisions++; + } + } + + if (NEGATIVE_X) { + if (p.x - r < _myMin.x) { + final float myBorderDiff = _myMin.x - p_old.x + r; + p.x = p_old.x + myBorderDiff; + myDeflectionNormal.add(_myNormals[3]); + myNumberOfCollisions++; + } + } + + if (NEGATIVE_Y) { + if (p.y - r < _myMin.y) { + final float myBorderDiff = _myMin.y - p_old.y + r; + p.y = p_old.y + myBorderDiff; + myDeflectionNormal.add(_myNormals[4]); + myNumberOfCollisions++; + } + } + + if (NEGATIVE_Z) { + if (p.z - r < _myMin.z) { + final float myBorderDiff = _myMin.z - p_old.z + r; + p.z = p_old.z + myBorderDiff; + myDeflectionNormal.add(_myNormals[5]); + myNumberOfCollisions++; + } + } + + if (myNumberOfCollisions > 0) { + /* remember collided particles */ + if (theCollisionParticles != null) { + theCollisionParticles.add(myParticle); + } + /* room for optimization / we don t need to reflect twice. */ + final float mySpeed = myPositionBeforeCollision.distanceSquared(myParticle.old_position()); + if (mySpeed > _myEpsilon) { + final Vector3f myDiffAfterCollision = mathematik.Util.sub(myPositionBeforeCollision, + myParticle.position()); + final Vector3f myDiffBeforeCollision = mathematik.Util.sub(myParticle.old_position(), + myParticle.position()); + myDeflectionNormal.scale(1.0f / (float) myNumberOfCollisions); + teilchen.util.Util.reflect(myDiffAfterCollision, myDeflectionNormal, + _myCoefficientOfRestitution); + teilchen.util.Util.reflect(myDiffBeforeCollision, myDeflectionNormal, 1); + + if (!myParticle.old_position().isNaN() && !myParticle.position().isNaN()) { + myParticle.old_position().add(myParticle.position(), myDiffBeforeCollision); + myParticle.position().add(myDiffAfterCollision); + } + } + } + } + } + } + + public boolean active() { + return mActive; + } + + public void active(boolean theActiveState) { + mActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/constraint/Stick.java b/day3/libs/teilchen/src/teilchen/constraint/Stick.java new file mode 100755 index 0000000..f20bbdb --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/constraint/Stick.java @@ -0,0 +1,158 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.constraint; + + +import mathematik.Vector3f; + +import teilchen.IConnection; +import teilchen.Particle; +import teilchen.Physics; + + +public class Stick + implements IConstraint, + IConnection { + + protected final Particle mA; + + protected final Particle mB; + + protected float mRestLength; + + protected final Vector3f mTempDistanceVector; + + protected final Vector3f mTempVector; + + protected boolean mOneWay; + + protected float mDamping; + + protected boolean mActive = true; + + protected static final float EPSILON = 0.0001f; + + public Stick(Particle theA, Particle theB) { + this(theA, + theB, + theA.position().distance(theB.position())); + } + + public Stick(final Particle theA, + final Particle theB, + final float theRestLength) { + mRestLength = theRestLength; + mA = theA; + mB = theB; + mTempDistanceVector = new Vector3f(); + mTempVector = new Vector3f(); + mOneWay = false; + mDamping = 1f; + } + + public void setRestLengthByPosition() { + mRestLength = mA.position().distance(mB.position()); + } + + public float damping() { + return mDamping; + } + + public void damping(float theDamping) { + mDamping = theDamping; + } + + public float restlength() { + return mRestLength; + } + + public void restlength(float theRestLength) { + mRestLength = theRestLength; + } + + public final Particle a() { + return mA; + } + + public final Particle b() { + return mB; + } + + public void setOneWay(boolean theOneWayState) { + mOneWay = theOneWayState; + } + + public void apply(Physics theParticleSystem) { + if (!mActive) { + return; + } + if (mA.fixed() && mB.fixed()) { + return; + } + mTempDistanceVector.sub(mA.position(), mB.position()); + final float myDistanceSquared = mTempDistanceVector.lengthSquared(); + if (myDistanceSquared > 0) { + final float myDistance = (float) Math.sqrt(myDistanceSquared); + final float myDifference = mRestLength - myDistance; + if (myDifference > EPSILON || myDifference < -EPSILON) { + if (!mOneWay) { + final float myDifferenceScale = mDamping * 0.5f * myDifference / myDistance; + mTempVector.scale(myDifferenceScale, mTempDistanceVector); + if (mA.fixed()) { + mB.position().sub(mTempVector); + mB.position().sub(mTempVector); + } else if (mB.fixed()) { + mA.position().add(mTempVector); + mA.position().add(mTempVector); + } else { + mA.position().add(mTempVector); + mB.position().sub(mTempVector); + } + } else { + final float myDifferenceScale = myDifference / myDistance; + mTempVector.scale(myDifferenceScale, mTempDistanceVector); + mB.position().sub(mTempVector); + } + } + } else { + if (mA.fixed()) { + mB.position().set(mA.position()); + mB.position().x += mRestLength; + } else if (mB.fixed()) { + mA.position().set(mB.position()); + mA.position().x += mRestLength; + } else { + mB.position().set(mA.position()); + mA.position().x -= mRestLength / 2; + mB.position().x += mRestLength / 2; + } + } + } + + public boolean active() { + return mActive; + } + + public void active(boolean theActiveState) { + mActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/constraint/Teleporter.java b/day3/libs/teilchen/src/teilchen/constraint/Teleporter.java new file mode 100755 index 0000000..d3e2704 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/constraint/Teleporter.java @@ -0,0 +1,90 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.constraint; + + +import mathematik.Vector3f; +import teilchen.Particle; +import teilchen.Physics; + + +public class Teleporter + implements IConstraint { + + protected boolean mActive = true; + + private final Vector3f mMin; + + private final Vector3f mMax; + + public Teleporter() { + this(new Vector3f(), new Vector3f()); + } + + public Teleporter(final Vector3f pMin, final Vector3f pMax) { + mMin = new Vector3f(pMin); + mMax = new Vector3f(pMax); + } + + public Vector3f max() { + return mMax; + } + + public Vector3f min() { + return mMin; + } + + public void apply(Physics theParticleSystem) { + if (!mActive) { + return; + } + + for (final Particle mParticle : theParticleSystem.particles()) { + if (mParticle.position().x > mMax.x) { + mParticle.position().x -= Math.abs(mMax.x - mMin.x); + } + if (mParticle.position().y > mMax.y) { + mParticle.position().y -= Math.abs(mMax.y - mMin.y); + } + if (mParticle.position().z > mMax.z) { + mParticle.position().z -= Math.abs(mMax.z - mMin.z); + } + if (mParticle.position().x < mMin.x) { + mParticle.position().x += Math.abs(mMax.x - mMin.x); + } + if (mParticle.position().y < mMin.y) { + mParticle.position().y += Math.abs(mMax.y - mMin.y); + } + if (mParticle.position().z < mMin.z) { + mParticle.position().z += Math.abs(mMax.z - mMin.z); + } + } + } + + public boolean active() { + return mActive; + } + + public void active(boolean theActiveState) { + mActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/cubicle/CubicleAtom.java b/day3/libs/teilchen/src/teilchen/cubicle/CubicleAtom.java new file mode 100755 index 0000000..f765e00 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/cubicle/CubicleAtom.java @@ -0,0 +1,67 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.cubicle; + + +import java.util.Vector; + +import mathematik.Vector3i; + + +/* + * container class for ICubicleEntity representing one cube in the world. + */ +public class CubicleAtom { + + private Vector mContainer; + + private final Vector3i mPosition; + + public CubicleAtom(int x, int y, int z) { + mContainer = new Vector(); + mPosition = new Vector3i(x, y, z); + } + + public Vector3i position() { + return mPosition; + } + + public void add(ICubicleEntity theEntity) { + mContainer.add(theEntity); + } + + public boolean remove(ICubicleEntity theEntity) { + return mContainer.remove(theEntity); + } + + public void clear() { + mContainer.clear(); + } + + public int size() { + return mContainer.size(); + } + + public Vector data() { + return mContainer; + } +} diff --git a/day3/libs/teilchen/src/teilchen/cubicle/CubicleEntity.java b/day3/libs/teilchen/src/teilchen/cubicle/CubicleEntity.java new file mode 100755 index 0000000..b21aa49 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/cubicle/CubicleEntity.java @@ -0,0 +1,61 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.cubicle; + + +import mathematik.Vector3f; +import mathematik.Vector3i; + + +public class CubicleEntity + implements ICubicleEntity { + + private Vector3i _myCubiclePosition; + + private final Vector3f _myPosition; + + public CubicleEntity() { + _myCubiclePosition = new Vector3i(); + _myPosition = new Vector3f(); + } + + public Vector3i cubicle() { + return _myCubiclePosition; + } + + public Vector3f position() { + return _myPosition; + } + + public boolean leaving(int theX, int theY, int theZ) { + if (theX == cubicle().x + && theY == cubicle().y + && theZ == cubicle().z) { + return false; + } + return true; + } + + public boolean isActive() { + return true; + } +} diff --git a/day3/libs/teilchen/src/teilchen/cubicle/CubicleParticle.java b/day3/libs/teilchen/src/teilchen/cubicle/CubicleParticle.java new file mode 100755 index 0000000..072e33f --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/cubicle/CubicleParticle.java @@ -0,0 +1,63 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.cubicle; + + +import mathematik.Vector3f; +import mathematik.Vector3i; + +import teilchen.BasicParticle; + + +public class CubicleParticle + extends BasicParticle implements ICubicleEntity { + + private final Vector3i _myCubiclePosition; + + private final Vector3f _myPosition; + + public CubicleParticle() { + _myCubiclePosition = new Vector3i(); + _myPosition = new Vector3f(); + } + + public Vector3i cubicle() { + return _myCubiclePosition; + } + + public Vector3f position() { + return _myPosition; + } + + public boolean leaving(int theX, int theY, int theZ) { + if (theX == cubicle().x + && theY == cubicle().y + && theZ == cubicle().z) { + return false; + } + return true; + } + + public boolean isActive() { + return !fixed(); + } +} diff --git a/day3/libs/teilchen/src/teilchen/cubicle/CubicleWorld.java b/day3/libs/teilchen/src/teilchen/cubicle/CubicleWorld.java new file mode 100755 index 0000000..5d5026a --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/cubicle/CubicleWorld.java @@ -0,0 +1,302 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.cubicle; + + +import mathematik.TransformMatrix4f; +import mathematik.Vector3f; +import mathematik.Vector3i; + +import java.util.Iterator; +import java.util.Vector; + + +/* + * cubicle world handles entities and queries about a cubicles state. + */ +public class CubicleWorld { + + public static final int OFF_WORLD = -1; + + private CubicleAtom[][][] mWorld; + + private CubicleAtom mOffWorld; + + private TransformMatrix4f mTransform; + + private Vector3f mScale; + + private Vector mEntites; + + public CubicleWorld(Vector3i theNumberOfAtoms) { + this(theNumberOfAtoms.x, theNumberOfAtoms.y, theNumberOfAtoms.z); + } + + public CubicleWorld(int theNumberOfXAtoms, + int theNumberOfYAtoms, + int theNumberOfZAtoms) { + initializeAtoms(theNumberOfXAtoms, theNumberOfYAtoms, theNumberOfZAtoms); + mTransform = new TransformMatrix4f(TransformMatrix4f.IDENTITY); + mScale = new Vector3f(1, 1, 1); + mEntites = new Vector(); + } + + private void initializeAtoms(int theNumberOfXAtoms, + int theNumberOfYAtoms, + int theNumberOfZAtoms) { + mWorld = new CubicleAtom[theNumberOfXAtoms][theNumberOfYAtoms][theNumberOfZAtoms]; + for (int x = 0; x < mWorld.length; x++) { + for (int y = 0; y < mWorld[x].length; y++) { + for (int z = 0; z < mWorld[x][y].length; z++) { + mWorld[x][y][z] = new CubicleAtom(x, y, z); + } + } + } + mOffWorld = new CubicleAtom(OFF_WORLD, OFF_WORLD, OFF_WORLD); + } + + public void update() { + Iterator myIterator = mEntites.iterator(); + while (myIterator.hasNext()) { + handleEntity(myIterator.next()); + } + } + + public void add(ICubicleEntity theEntity) { + mEntites.add(theEntity); + theEntity.cubicle().set(OFF_WORLD, OFF_WORLD, OFF_WORLD); + mOffWorld.add(theEntity); + } + + public boolean remove(ICubicleEntity theEntity) { + return removeFromCubicle(theEntity) && mEntites.remove(theEntity); + } + + public void handleEntity(ICubicleEntity theEntity) { + if (theEntity.isActive()) { + + /* transform entity position into cubicle world space */ + + final Vector3i myIndex = worldposition2index(theEntity.position()); + + /* handle entites position in cubicle grid */ + if (checkBounds(myIndex.x, myIndex.y, myIndex.z)) { + if (theEntity.leaving(myIndex.x, myIndex.y, myIndex.z)) { + /* remove from previous cubicles */ + if (!removeFromCubicle(theEntity)) { + System.err.println("### ERROR @ CubicleWorld / removing entity / inworld"); + } + /* add to current cubicle */ + mWorld[myIndex.x][myIndex.y][myIndex.z].add(theEntity); + /* store cubicle */ + theEntity.cubicle().set(myIndex.x, myIndex.y, myIndex.z); + } + } else { + if (theEntity.leaving(OFF_WORLD, OFF_WORLD, OFF_WORLD)) { + /* remove from cubicles */ + if (!removeFromCubicle(theEntity)) { + System.err.println("### ERROR @ CubicleWorld / removing entity / offworld"); + } + /* add to off world */ + mOffWorld.add(theEntity); + + /* store cubicle */ + theEntity.cubicle().set(OFF_WORLD, OFF_WORLD, OFF_WORLD); + } + } + } + } + + public Vector getLocalEntities(Vector3f thePosition) { + final Vector3i myIndex = worldposition2index(thePosition); + if (checkBounds(myIndex.x, myIndex.y, myIndex.z)) { + final CubicleAtom myCubicleAtom = getAtom(myIndex.x, myIndex.y, myIndex.z); + return myCubicleAtom.data(); + } + return null; + } + + public Vector getLocalEntities(ICubicleEntity theEntity) { + final Vector3i myIndex = theEntity.cubicle(); + return getAtom(myIndex.x, myIndex.y, myIndex.z).data(); + } + + public Vector getLocalEntities(Vector3f thePosition, int pExtraRadius) { + return getLocalEntities(thePosition, pExtraRadius, pExtraRadius, pExtraRadius); + } + + public Vector getLocalEntities(Vector3f thePosition, + int theXRadius, + int theYRadius, + int theZRadius) { + final Vector3i myIndex = worldposition2index(thePosition); + if (checkBounds(myIndex.x, myIndex.y, myIndex.z)) { + final Vector mAtoms = getAtoms(myIndex.x, + myIndex.y, + myIndex.z, + theXRadius, + theYRadius, + theZRadius); + final Vector mEntities = new Vector(); + for (CubicleAtom a : mAtoms) { + mEntities.addAll(a.data()); + } + return mEntities.isEmpty() ? null : mEntities; + } else { + return null; + } + } + + public Vector getLocalEntities(ICubicleEntity theEntity, + int theXRadius, + int theYRadius, + int theZRadius) { + final Vector3i myIndex = theEntity.cubicle(); + final Vector mAtoms = getAtoms(myIndex.x, + myIndex.y, + myIndex.z, + theXRadius, + theYRadius, + theZRadius); + final Vector mEntities = new Vector(); + for (CubicleAtom a : mAtoms) { + mEntities.addAll(a.data()); + } + return mEntities.isEmpty() ? null : mEntities; + } + + public Vector entities() { + return mEntites; + } + + public Vector3i worldposition2index(Vector3f thePosition) { + /* get position */ + final Vector3f myPosition = new Vector3f(thePosition); + + /* translation */ + myPosition.sub(mTransform.translation); + + /* rotation */ + mTransform.rotation.transform(myPosition); + + /* scale */ + myPosition.divide(mScale); + + /* round off */ + final Vector3i myIndex = new Vector3i((int) Math.floor(myPosition.x), + (int) Math.floor(myPosition.y), + (int) Math.floor(myPosition.z)); + return myIndex; + } + + private boolean removeFromCubicle(ICubicleEntity theEntity) { + if (theEntity.cubicle().x == OFF_WORLD + && theEntity.cubicle().y == OFF_WORLD + && theEntity.cubicle().z == OFF_WORLD) { + /* was stored in the offworld cubicle */ + return mOffWorld.remove(theEntity); + } else { + if (checkBounds(theEntity.cubicle().x, theEntity.cubicle().y, theEntity.cubicle().z)) { + /* was stored in a cubicle */ + return mWorld[theEntity.cubicle().x][theEntity.cubicle().y][theEntity.cubicle().z].remove(theEntity); + } else { + /* values were invalid */ + System.out.println("### WARNING @ CubicleWorld / couldn t remove entity"); + return false; + } + } + } + + private boolean checkBounds(int theX, + int theY, + int theZ) { + if (theX < mWorld.length && theX >= 0) { + if (theY < mWorld[theX].length && theY >= 0) { + if (theZ < mWorld[theX][theY].length && theZ >= 0) { + return true; + } + } + } + return false; + } + + public CubicleAtom getAtom(int theX, + int theY, + int theZ) { + if (checkBounds(theX, theY, theZ)) { + return mWorld[theX][theY][theZ]; + } else { + return mOffWorld; + } + } + + public Vector getAtoms(int theX, + int theY, + int theZ, + int theXRadius, + int theYRadius, + int theZRadius) { + Vector myAtoms = new Vector(); + for (int z = -theZRadius; z < theZRadius + 1; ++z) { + for (int y = -theYRadius; y < theYRadius + 1; ++y) { + for (int x = -theXRadius; x < theXRadius + 1; ++x) { + int myX = theX + x; + int myY = theY + y; + int myZ = theZ + z; + if (checkBounds(myX, myY, myZ) && mWorld[myX][myY][myZ].size() > 0) { + myAtoms.add(mWorld[myX][myY][myZ]); + } + } + } + } + return myAtoms; + } + + public Vector3f cellscale() { + return mScale; + } + + public TransformMatrix4f transform() { + return mTransform; + } + + public CubicleAtom[][][] getDataRef() { + return mWorld; + } + + public Vector getEntities() { + return mEntites; + } + + public CubicleAtom getOffWorldAtom() { + return mOffWorld; + } + + public void removeAll() { + final Iterator iter = mEntites.iterator(); + while (iter.hasNext()) { + final ICubicleEntity c = iter.next(); + removeFromCubicle(c); + iter.remove(); + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/cubicle/ICubicleEntity.java b/day3/libs/teilchen/src/teilchen/cubicle/ICubicleEntity.java new file mode 100755 index 0000000..dca7855 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/cubicle/ICubicleEntity.java @@ -0,0 +1,60 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.cubicle; + + +import mathematik.Vector3f; +import mathematik.Vector3i; + + +public interface ICubicleEntity { + + /** + * get reference to the cubicle id + * + * @return Vector3i + */ + Vector3i cubicle(); + + /** + * get reference to position vector + * + * @return Vector3f + */ + Vector3f position(); + + /** + * returns true if the new position don t match the previously stored + * position + * + * @return boolean + */ + boolean leaving(int theX, int theY, int theZ); + + /** + * entities can be temporarily removed from the process of being updated by + * the world. + * + * @return + */ + boolean isActive(); +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson00_Particle.java b/day3/libs/teilchen/src/teilchen/demo/Lesson00_Particle.java new file mode 100755 index 0000000..9b177b9 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson00_Particle.java @@ -0,0 +1,98 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; + + +/** + * this sketch show how to create a particle system with a single particle in + * it. + */ +public class Lesson00_Particle + extends PApplet { + + private Physics mPhysics; + + private Particle mParticle; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system. */ + mPhysics = new Physics(); + + /* + * a physic-based particle system consists of a few components. + * + * 1 particles. + * there are different kinds of particles. for now we use a simple particle. + * + * 2 forces. + * there are all kinds of forces. one of the most obvious force is the gravitational force, + * but there all kinds of different forces like attractors and springs. forces usually + * affect all particles in the system. + * + * 3 behaviors + * a behavior is special kind of force. it is something like an internal force or a motor + * that only affects a single particle. a typical force is for example the 'seek force' + * which constantly pulls a particle into a certain direction. + * + * 4 integrators. + * integrators are used to integrate acceleration and velocity to calculate the new position. + * the most well-known is the 'euler' integrator, but there are also optimized versions like 'runge-kutta' + * or 'Midpoint' or even slightly different concepts like 'verlet'. + * + */ + + /* create a particle. note that the particle is automatically added to particle system */ + mParticle = mPhysics.makeParticle(); + } + + public void draw() { + /* update the particle system to the next step. usually the time step is the duration of the las frame */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particle */ + background(255); + stroke(0, 127); + fill(0, 32); + ellipse(mParticle.position().x, mParticle.position().y, 12, 12); + + /* reset particle s position and velocity */ + if (mousePressed) { + mParticle.position().set(mouseX, mouseY); + mParticle.velocity().set(mouseX - pmouseX, mouseY - pmouseY); + mParticle.velocity().scale(10); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson00_Particle.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson01_Gravity.java b/day3/libs/teilchen/src/teilchen/demo/Lesson01_Gravity.java new file mode 100755 index 0000000..0c07366 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson01_Gravity.java @@ -0,0 +1,83 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Gravity; +import processing.core.PApplet; + + +/** + * this sketch show how to create a particle system with a single particle in + * it. + */ +public class Lesson01_Gravity + extends PApplet { + + private Physics mPhysics; + + private Particle mParticle; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a gravitational force */ + Gravity mGravity = new Gravity(); + /* the direction of the gravity is defined by the 'force' vector */ + mGravity.force().set(0, 30, 0); + /* forces, like gravity or any other force, can be added to the system. they will be automatically applied to all particles */ + mPhysics.add(mGravity); + + /* create a particle and add it to the system */ + mParticle = mPhysics.makeParticle(); + } + + public void draw() { + /* update the particle system. this applies the gravity to the particle */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particle */ + background(255); + stroke(0, 127); + fill(0, 32); + ellipse(mParticle.position().x, mParticle.position().y, 12, 12); + + /* reset particle s position and velocity */ + if (mousePressed) { + mParticle.position().set(mouseX, mouseY); + mParticle.velocity().set(mouseX - pmouseX, mouseY - pmouseY); + mParticle.velocity().scale(10); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson01_Gravity.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson02_Particles.java b/day3/libs/teilchen/src/teilchen/demo/Lesson02_Particles.java new file mode 100755 index 0000000..1473234 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson02_Particles.java @@ -0,0 +1,87 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Gravity; +import processing.core.PApplet; + + +/** + * this sketch shows how to create and handle multiple particles and remove + * individual particles. + */ +public class Lesson02_Particles + extends PApplet { + + private Physics mPhysics; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a gravitational force and add it to the particle system */ + Gravity myGravity = new Gravity(0, 30, 0); + mPhysics.add(myGravity); + } + + public void draw() { + if (mousePressed) { + /* create and add a particle to the system */ + Particle mParticle = mPhysics.makeParticle(); + /* set particle to mouse position with random velocity */ + mParticle.position().set(mouseX, mouseY); + mParticle.velocity().set(random(-20, 20), random(-50)); + } + + /* update the particle system */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* remove particles right before they hit the edge of the screen */ + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle mParticle = mPhysics.particles(i); + if (mParticle.position().y > height * 0.9f) { + mPhysics.particles().remove(i); + } + } + + /* draw all the particles in the system */ + background(255); + stroke(0, 127); + fill(0, 32); + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle mParticle = mPhysics.particles(i); + ellipse(mParticle.position().x, mParticle.position().y, 10, 10); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson02_Particles.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson03_Attractors.java b/day3/libs/teilchen/src/teilchen/demo/Lesson03_Attractors.java new file mode 100755 index 0000000..fd612a9 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson03_Attractors.java @@ -0,0 +1,116 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Teleporter; +import teilchen.force.Attractor; +import teilchen.force.ViscousDrag; + + +/** + * this sketch shows how to create and use attractors. + */ +public class Lesson03_Attractors + extends PApplet { + + private Physics mPhysics; + + private Attractor mAttractor; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a viscous force that slows down all motion */ + ViscousDrag myDrag = new ViscousDrag(); + myDrag.coefficient = 0.75f; + mPhysics.add(myDrag); + + /* teleport particles from one edge of the screen to the other */ + Teleporter mTeleporter = new Teleporter(); + mTeleporter.min().set(0, 0); + mTeleporter.max().set(width, height); + mPhysics.add(mTeleporter); + + /* create some particles */ + for (int i = 0; i < 100; i++) { + Particle myParticle = mPhysics.makeParticle(); + myParticle.position().set(random(width), random(height)); + } + mPhysics.particles().firstElement().fixed(true); + + /* create an attractor */ + mAttractor = new Attractor(); + mAttractor.radius(100); + mAttractor.strength(150); + mPhysics.add(mAttractor); + } + + public void mousePressed() { + /* flip the direction of the attractors strength. */ + float myInvertedStrength = -1 * mAttractor.strength(); + /* a negative strength turns the attractor into a repulsor */ + mAttractor.strength(myInvertedStrength); + } + + public void draw() { + /* set attractor to mouse position */ + mAttractor.position().set(mouseX, mouseY); + + /* update the particle system */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* draw */ + background(255); + + /* draw all the particles in particle system */ + fill(245); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle myParticle = mPhysics.particles(i); + ellipse(myParticle.position().x, myParticle.position().y, 12, 12); + } + + /* draw attractor. green if it is attracting and red if it is repelling */ + noStroke(); + if (mAttractor.strength() < 0) { + fill(255, 0, 0, 50); + } else { + fill(0, 255, 0, 50); + } + ellipse(mAttractor.position().x, mAttractor.position().y, + mAttractor.radius(), mAttractor.radius()); + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson03_Attractors.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson04_Deflectors.java b/day3/libs/teilchen/src/teilchen/demo/Lesson04_Deflectors.java new file mode 100755 index 0000000..bb52e49 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson04_Deflectors.java @@ -0,0 +1,129 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.ShortLivedParticle; +import teilchen.force.Gravity; +import teilchen.force.PlaneDeflector; +import teilchen.force.ViscousDrag; + + +/** + * this sketch shows 1 how to create and use plane deflectors 2 how to use + * 'ShortLivedParticle' + */ +public class Lesson04_Deflectors + extends PApplet { + + private Physics mPhysics; + + private PlaneDeflector mDeflector; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a deflector and add it to the particle system. + * the that defines the deflection area is defined by an + * origin and a normal. this also means that the plane s size + * is infinite. + * note that there is also a triangle delfector that is constraint + * by three points. + */ + mDeflector = new PlaneDeflector(); + /* set plane origin into the center of the screen */ + mDeflector.plane().origin.set(width / 2, height / 2, 0); + mDeflector.plane().normal.set(0, -1, 0); + /* the coefficient of restitution defines how hard particles bounce of the deflector */ + mDeflector.coefficientofrestitution(0.7f); + mPhysics.add(mDeflector); + + /* create gravitiy */ + Gravity myGravity = new Gravity(); + myGravity.force().y = 50; + mPhysics.add(myGravity); + + /* create drag */ + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 0.1f; + mPhysics.add(myViscousDrag); + } + + public void draw() { + /* rotate deflector plane */ + if (mousePressed) { + final float myAngle = 2 * PI * (float) mouseX / width - PI; + mDeflector.plane().normal.set(sin(myAngle), -cos(myAngle), 0); + } + + /* create a special particle */ + ShortLivedParticle myNewParticle = new ShortLivedParticle(); + myNewParticle.position().set(mouseX, mouseY); + myNewParticle.velocity().set(0, random(100) + 50); + /* this particle is removed after a specific interval */ + myNewParticle.setMaxAge(4); + /* add particle manually to the particle system */ + mPhysics.add(myNewParticle); + + /* update physics */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* draw all the particles in the particle system */ + background(255); + for (int i = 0; i < mPhysics.particles().size(); i++) { + Particle myParticle = mPhysics.particles(i); + /* this special particle can tell you how much time it has to live. + * we map this information to its transparency. + */ + float myRatio = 1 - ((ShortLivedParticle) myParticle).ageRatio(); + stroke(0, 64 * myRatio); + fill(0, 32 * myRatio); + ellipse(myParticle.position().x, myParticle.position().y, 12, 12); + } + + /* draw deflector */ + stroke(0, 127); + line(mDeflector.plane().origin.x - mDeflector.plane().normal.y * -width, + mDeflector.plane().origin.y + mDeflector.plane().normal.x * -width, + mDeflector.plane().origin.x - mDeflector.plane().normal.y * width, + mDeflector.plane().origin.y + mDeflector.plane().normal.x * width); + + stroke(255, 0, 0, 127); + line(mDeflector.plane().origin.x, + mDeflector.plane().origin.y, + mDeflector.plane().origin.x + mDeflector.plane().normal.x * 20, + mDeflector.plane().origin.y + mDeflector.plane().normal.y * 20); + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson04_Deflectors.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson05_Spring.java b/day3/libs/teilchen/src/teilchen/demo/Lesson05_Spring.java new file mode 100755 index 0000000..530db78 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson05_Spring.java @@ -0,0 +1,95 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; + + +/** + * this sketch shows 1 how to create a viscous drag to slow motion eventually + * down. 2 how to create a spring that connects two particles. + */ +public class Lesson05_Spring + extends PApplet { + + private Physics mPhysics; + + private Spring mSpring; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a viscous force that slows down all motion; 0 means no slowing down. */ + ViscousDrag myDrag = new ViscousDrag(0.25f); + mPhysics.add(myDrag); + + /* create two particles that we can connect with a spring */ + Particle myA = mPhysics.makeParticle(); + myA.position().set(width / 2 - 50, height / 2); + + Particle myB = mPhysics.makeParticle(); + myB.position().set(width / 2 + 50, height / 2); + + /* create a spring force that connects two particles. + * note that there is more than one way to create a spring. + * in our case the restlength of the spring is defined by the + * particles current position. + */ + mSpring = mPhysics.makeSpring(myA, myB); + } + + public void draw() { + /* set first particle to mouse position */ + if (mousePressed) { + mSpring.a().position().set(mouseX, mouseY); + } + + /* update the particle system */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particles and connecting line */ + background(255); + noFill(); + stroke(255, 0, 127, 64); + line(mSpring.a().position().x, mSpring.a().position().y, + mSpring.b().position().x, mSpring.b().position().y); + fill(245); + stroke(164); + ellipse(mSpring.a().position().x, mSpring.a().position().y, 12, 12); + ellipse(mSpring.b().position().x, mSpring.b().position().y, 12, 12); + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson05_Spring.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson06_Springs.java b/day3/libs/teilchen/src/teilchen/demo/Lesson06_Springs.java new file mode 100755 index 0000000..b859e18 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson06_Springs.java @@ -0,0 +1,97 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Spring; +import processing.core.PApplet; + + +/** + * this sketch shows 1 how to create a viscous drag to slow motion eventually + * down. 2 how to create a spring that connects two particles. + */ +public class Lesson06_Springs + extends PApplet { + + private Physics mPhysics; + + private Particle mRoot; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a particle to which we will connect springs */ + mRoot = mPhysics.makeParticle(width / 2, height / 2, 0.0f); + /* we give the root particle a higher mass so it doesn t move as easily */ + mRoot.mass(30); + } + + public void draw() { + /* create a particle at mouse position and connect it to the root particle through a spring */ + if (mousePressed) { + Particle mParticle = mPhysics.makeParticle(mouseX, mouseY, 0); + Spring mSpring = mPhysics.makeSpring(mRoot, mParticle); + /* restlength defines the desired length of the spring. in this case it is the distance between the two particles. */ + float mRestlength = mSpring.restlength(); + /* we modify the restlength to add a bit of energy into the system */ + mSpring.restlength(mRestlength * 1.5f); + } + + /* update the particle system */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particles and connecting line */ + background(255); + + /* draw springs */ + noFill(); + stroke(255, 0, 127, 64); + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces().get(i) instanceof Spring) { + Spring mSSpring = (Spring) mPhysics.forces().get(i); + line(mSSpring.a().position().x, mSSpring.a().position().y, + mSSpring.b().position().x, mSSpring.b().position().y); + } + } + /* draw particles */ + fill(245); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); i++) { + ellipse(mPhysics.particles().get(i).position().x, + mPhysics.particles().get(i).position().y, + 12, 12); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson06_Springs.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson07_StableQuads.java b/day3/libs/teilchen/src/teilchen/demo/Lesson07_StableQuads.java new file mode 100755 index 0000000..94d1819 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson07_StableQuads.java @@ -0,0 +1,111 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import teilchen.Physics; +import teilchen.force.Gravity; +import teilchen.force.ViscousDrag; +import teilchen.util.DrawLib; +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.constraint.Box; +import teilchen.integration.RungeKutta; +import teilchen.util.StableSpringQuad; + + +public class Lesson07_StableQuads + extends PApplet { + + private Physics mPhysics; + + private Particle mRoot; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(60); + + mPhysics = new Physics(); + /* we use 'runge kutta' as it is more stable for this application */ + mPhysics.setInegratorRef(new RungeKutta()); + + Gravity myGravity = new Gravity(); + myGravity.force().y = 98.1f; + mPhysics.add(myGravity); + + /* add drag to smooth the spring interaction */ + mPhysics.add(new ViscousDrag(0.2f)); + + /* add a container */ + Box myBox = new Box(); + myBox.min().set(0, 0, 0); + myBox.max().set(width, height, 0); + mPhysics.add(myBox); + + /* create root */ + Particle a = mPhysics.makeParticle(0, 0); + Particle b = mPhysics.makeParticle(100, 0); + Particle c = mPhysics.makeParticle(100, 100); + Particle d = mPhysics.makeParticle(0, 100); + + new StableSpringQuad(mPhysics, d, c, mPhysics.makeParticle(100, 200), mPhysics.makeParticle(0, 200)); + + /* create stable quad from springs */ + /* first the edge-springs ... */ + final float mySpringConstant = 100; + final float mySpringDamping = 5; + mPhysics.makeSpring(a, b, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(b, c, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(c, d, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(d, a, mySpringConstant, mySpringDamping).restlength(); + /* ... then the diagonal-springs */ + mPhysics.makeSpring(a, c, mySpringConstant, mySpringDamping); + mPhysics.makeSpring(b, d, mySpringConstant, mySpringDamping).restlength(); + + /* define 'a' as root particle for mouse interaction */ + mRoot = a; + mRoot.fixed(true); + } + + public void draw() { + + /* handle particles */ + if (mousePressed) { + mRoot.fixed(true); + mRoot.position().set(mouseX, mouseY); + } else { + mRoot.fixed(false); + } + + mPhysics.step(1f / frameRate); + + /* draw */ + background(255); + DrawLib.drawSprings(g, mPhysics, color(255, 0, 127, 64)); + DrawLib.drawParticles(g, mPhysics, 12, color(164), color(245)); + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson07_StableQuads.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson08_Sticks.java b/day3/libs/teilchen/src/teilchen/demo/Lesson08_Sticks.java new file mode 100755 index 0000000..e2ddd9b --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson08_Sticks.java @@ -0,0 +1,100 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Stick; +import teilchen.force.Gravity; +import teilchen.integration.Verlet; +import processing.core.PApplet; + + +public class Lesson08_Sticks + extends PApplet { + + private Physics mPhysics; + + private Particle[] mParticles; + + public void setup() { + size(640, 480, OPENGL); + frameRate(60); + smooth(); + + mPhysics = new Physics(); + /* increase the number of iterations for contraints in each step. this can greatly relaxes tensions in the system. */ + mPhysics.contraint_iterations_per_steps = 5; + + /* add gravity for extra fun */ + mPhysics.add(new Gravity()); + + /* we chose verlet integration as it integrates much more nicely with sticks ( and constraints in general ) */ + Verlet myVerlet = new Verlet(); + myVerlet.damping(0.99f); + mPhysics.setInegratorRef(myVerlet); + + /* setup sticks to form a whip */ + mParticles = new Particle[16]; + float mSegmentLength = 20.0f; + /* create root */ + for (int x = 0; x < mParticles.length; x++) { + mParticles[x] = mPhysics.makeParticle(x * mSegmentLength, 0, 0, 0.1f); + if (x > 0) { + Stick myStick = new Stick(mParticles[x - 1], + mParticles[x], + mSegmentLength); + /* damp the stick to release tensions from the system */ + myStick.damping(0.99f); + mPhysics.add(myStick); + } + } + + /* fix root particle so it can stick to the mouse later */ + mParticles[0].fixed(true); + } + + public void draw() { + /* stick root particle to mouse */ + mParticles[0].position().set(mouseX, mouseY); + + /* update */ + mPhysics.step(1.0f / frameRate); + + /* draw sticks with descending stroke weight */ + background(255); + stroke(0, 192); + for (int x = 1; x < mParticles.length; x++) { + Particle p1 = mParticles[x - 1]; + Particle p2 = mParticles[x]; + final float mStrokeWeight = 4.0f * (1.0f - (float) x / mParticles.length); + strokeWeight(mStrokeWeight); + line(p1.position().x, p1.position().y, p1.position().z, + p2.position().x, p2.position().y, p2.position().z); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson08_Sticks.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson09_Cloth.java b/day3/libs/teilchen/src/teilchen/demo/Lesson09_Cloth.java new file mode 100755 index 0000000..279284d --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson09_Cloth.java @@ -0,0 +1,141 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import mathematik.Vector3f; + +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.IConstraint; +import teilchen.constraint.Stick; +import teilchen.force.Attractor; +import teilchen.force.Gravity; +import teilchen.integration.Verlet; + + +public class Lesson09_Cloth + extends PApplet { + + private Physics mPhysics; + + private Particle[][] mParticles; + + private final int GRID_WIDTH = 32; + + private final int GRID_HEIGHT = 16; + + private Attractor mAttractor; + + public void setup() { + size(640, 480, OPENGL); + frameRate(60); + + mPhysics = new Physics(); + mPhysics.contraint_iterations_per_steps = 5; + + Verlet myVerlet = new Verlet(); + myVerlet.damping(0.9f); + mPhysics.setInegratorRef(myVerlet); + mPhysics.add(new Gravity(new Vector3f(0, 1000f, 0))); + + mAttractor = new Attractor(); + mAttractor.strength(-15000); + mAttractor.radius(300); + mPhysics.add(mAttractor); + + mParticles = new Particle[GRID_WIDTH][GRID_HEIGHT]; + + /* setup cloth */ + float mGridStepX = ((float) width / GRID_WIDTH); + float mGridStepY = (((float) height * 0.5f) / GRID_HEIGHT); + for (int y = 0; y < GRID_HEIGHT; y++) { + for (int x = 0; x < GRID_WIDTH; x++) { + mParticles[x][y] = mPhysics.makeParticle(); + mParticles[x][y].position().set((x + 0.5f) * mGridStepX, + y * mGridStepY, + random(0, 1)); + mParticles[x][y].old_position().set(mParticles[x][y].position()); + mParticles[x][y].mass(0.1f); + + final float DAMPING = 0.9f; + if (y > 0) { + Stick myStick = new Stick(mParticles[x][y - 1], + mParticles[x][y], + mGridStepY); + myStick.damping(DAMPING); + mPhysics.add(myStick); + } + if (x > 0) { + Stick myStick = new Stick(mParticles[x - 1][y], + mParticles[x][y], + mGridStepX); + myStick.damping(DAMPING); + mPhysics.add(myStick); + } + if (x > 0 && y > 0) { + Stick myStick1 = new Stick(mParticles[x - 1][y - 1], + mParticles[x][y], + new Vector3f(mGridStepX, mGridStepY).length()); + mPhysics.add(myStick1); + Stick myStick2 = new Stick(mParticles[x][y - 1], + mParticles[x - 1][y], + new Vector3f(mGridStepX, mGridStepY).length()); + mPhysics.add(myStick2); + } + } + } + + /* fix first row */ + for (int x = 0; x < mParticles.length; x++) { + mParticles[x][0].fixed(true); + } + } + + public void draw() { + + /* update */ + mAttractor.position().set(mouseX, mouseY, 50); + mPhysics.step(1.0f / frameRate); + + background(255); + + /* draw sticks */ + stroke(0, 127); + for (final IConstraint myIConstraint : mPhysics.constraints()) { + if (myIConstraint instanceof Stick) { + final Stick myStick = (Stick) myIConstraint; + line(myStick.a().position().x, + myStick.a().position().y, + myStick.a().position().z, + myStick.b().position().x, + myStick.b().position().y, + myStick.b().position().z); + } + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson09_Cloth.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson10_WanderBehavior.java b/day3/libs/teilchen/src/teilchen/demo/Lesson10_WanderBehavior.java new file mode 100755 index 0000000..c71a15a --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson10_WanderBehavior.java @@ -0,0 +1,96 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.BehaviorParticle; +import teilchen.Physics; +import teilchen.behavior.Motor; +import teilchen.behavior.Wander; +import teilchen.force.ViscousDrag; + +import static processing.core.PConstants.OPENGL; +import static processing.core.PConstants.RGB; + + +/** + * this sketch shows how to assign an 'wander' behavior to a particle. + */ +public class Lesson10_WanderBehavior + extends PApplet { + + private Physics mPhysics; + + private BehaviorParticle mParticle; + + private Wander mWander; + + private Motor mMotor; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(120); + + /* physics */ + mPhysics = new Physics(); + mPhysics.add(new ViscousDrag()); + + /* create particles */ + mParticle = mPhysics.makeParticle(BehaviorParticle.class); + mParticle.position().set(width / 2, height / 2); + mParticle.maximumInnerForce(100); + mParticle.radius(10); + + /* create behavior */ + mWander = new Wander(); + mParticle.behaviors().add(mWander); + + /* a motor is required to push the particle forward - wander manipulats the direction the particle is pushed in */ + mMotor = new Motor(); + mMotor.auto_update_direction(true); /* the direction the motor pushes into is each step automatically set to the velocity */ + mMotor.strength(25); + mParticle.behaviors().add(mMotor); + } + + public void draw() { + /* update particle system */ + mPhysics.step(1.0f / frameRate); + + /* draw behavior particle */ + background(255); + + fill(1); + stroke(0, 127); + line(mParticle.position().x, + mParticle.position().y, + mParticle.position().x + mParticle.velocity().x, + mParticle.position().y + mParticle.velocity().y); + ellipse(mParticle.position().x, mParticle.position().y, + mParticle.radius() * 2, mParticle.radius() * 2); + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson10_WanderBehavior.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/Lesson11_ArrivalBehavior.java b/day3/libs/teilchen/src/teilchen/demo/Lesson11_ArrivalBehavior.java new file mode 100755 index 0000000..2b89189 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/Lesson11_ArrivalBehavior.java @@ -0,0 +1,103 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.BehaviorParticle; +import teilchen.Physics; +import teilchen.behavior.Arrival; + + +/** + * this sketch shows how to assign an 'arrival' behavior to a particle. + */ +public class Lesson11_ArrivalBehavior + extends PApplet { + + private Physics mPhysics; + + private BehaviorParticle mParticle; + + private Arrival mArrival; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(120); + colorMode(RGB, 1.0f); + noFill(); + + /* physics */ + mPhysics = new Physics(); + + /* create particles */ + mParticle = mPhysics.makeParticle(BehaviorParticle.class); + mParticle.maximumInnerForce(100); + + /* create behavior */ + mArrival = new Arrival(); + mArrival.breakforce(mParticle.maximumInnerForce() * 0.25f); + mArrival.breakradius(mParticle.maximumInnerForce() * 0.25f); + mParticle.behaviors().add(mArrival); + } + + public void draw() { + + /* set the arrival position to the mouse position */ + mArrival.position().set(mouseX, mouseY); + + /* update particle system */ + mPhysics.step(1.0f / frameRate); + + /* draw behavior particle */ + background(1); + stroke(0, 0.5f); + if (mArrival.arriving()) { + /* color particle red while it is arriving */ + stroke(1, 0, 0, 0.5f); + } + if (mArrival.arrived()) { + /* color particle green when it has arrived */ + stroke(0, 1, 0, 0.5f); + } + + line(mParticle.position().x, + mParticle.position().y, + mParticle.position().x + mParticle.velocity().x, + mParticle.position().y + mParticle.velocity().y); + fill(1); + ellipse(mParticle.position().x, mParticle.position().y, 12, 12); + + /* draw arrival */ + stroke(0, 0.25f); + noFill(); + ellipse(mArrival.position().x, + mArrival.position().y, + mArrival.breakradius() * 2, + mArrival.breakradius() * 2); + } + + public static void main(String[] args) { + PApplet.main(new String[]{Lesson11_ArrivalBehavior.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX01_Overlap.java b/day3/libs/teilchen/src/teilchen/demo/LessonX01_Overlap.java new file mode 100755 index 0000000..4406974 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX01_Overlap.java @@ -0,0 +1,118 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import mathematik.Vector3f; + +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Gravity; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.util.Overlap; +import teilchen.util.Packing; + + +/** + * this sketch is exactly like Lesson06_Springs, except that it also shows how + * to resolveOverlap overlaps. + */ +public class LessonX01_Overlap + extends PApplet { + + private Physics mPhysics; + + private Particle mRoot; + + private static final float PARTICLE_RADIUS = 13; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + + mPhysics = new Physics(); + + /* create drag */ + mPhysics.add(new ViscousDrag()); + mPhysics.add(new Gravity(new Vector3f(0, 100f, 0))); + + + mRoot = mPhysics.makeParticle(width / 2, height / 2, 0.0f); + mRoot.mass(30); + mRoot.fixed(true); + mRoot.radius(PARTICLE_RADIUS); + } + + public void draw() { + if (mousePressed) { + Particle mParticle = mPhysics.makeParticle(mouseX, mouseY, 0); + mPhysics.makeSpring(mRoot, mParticle); + + /* + * we define a radius for the particle so the particle has + * dimensions + */ + mParticle.radius(random(PARTICLE_RADIUS / 2) + PARTICLE_RADIUS); + } + + + /* move overlapping particles away from each other */ + for (int i = 0; i < 10; i++) { + mRoot.position().set(width / 2, height / 2, 0.0f); // a bit of a 'hack' + Overlap.resolveOverlap(mPhysics.particles()); + } + + /* update the particle system */ + final float mDeltaTime = 1.0f / frameRate; + mPhysics.step(mDeltaTime); + + /* draw particles and connecting line */ + background(255); + + /* draw springs */ + noFill(); + stroke(255, 0, 127, 64); + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces().get(i) instanceof Spring) { + Spring mSSpring = (Spring) mPhysics.forces().get(i); + line(mSSpring.a().position().x, mSSpring.a().position().y, + mSSpring.b().position().x, mSSpring.b().position().y); + } + } + /* draw particles */ + fill(255, 127); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); i++) { + ellipse(mPhysics.particles().get(i).position().x, + mPhysics.particles().get(i).position().y, + mPhysics.particles().get(i).radius() * 2, + mPhysics.particles().get(i).radius() * 2); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX01_Overlap.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX02_Collisions.java b/day3/libs/teilchen/src/teilchen/demo/LessonX02_Collisions.java new file mode 100755 index 0000000..e1080ab --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX02_Collisions.java @@ -0,0 +1,121 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import mathematik.Vector3f; +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Box; +import teilchen.force.Gravity; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.util.CollisionManager; + + +public class LessonX02_Collisions + extends PApplet { + + private static final float PARTICLE_SIZE = 12; + + private CollisionManager mCollision; + + private Physics mPhysics; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(30); + noFill(); + ellipseMode(CENTER); + + mCollision = new CollisionManager(); + mCollision.distancemode(CollisionManager.DISTANCE_MODE_FIXED); + mCollision.minimumDistance(50); + + mPhysics = new Physics(); + mPhysics.add(new ViscousDrag(0.85f)); + mPhysics.add(new Gravity()); + + Box myBox = new Box(); + myBox.min().set(50, 50, 0); + myBox.max().set(width - 50, height - 50, 0); + myBox.coefficientofrestitution(0.7f); + myBox.reflect(true); + mPhysics.add(myBox); + + /* create a first particle */ + final Particle myParticle = mPhysics.makeParticle(new Vector3f(mouseX, mouseY, 0), 10); + mCollision.collision().add(myParticle); + } + + public void draw() { + /* create particles */ + if (mousePressed) { + final Particle myParticle = mPhysics.makeParticle(new Vector3f(mouseX, mouseY, 0), 10); + mCollision.collision().add(myParticle); + } + + /* collision handler */ + final float mDeltaTime = 1.0f / frameRate; + mCollision.createCollisionResolvers(); + mCollision.loop(mDeltaTime); + mPhysics.step(mDeltaTime); + + /* draw */ + background(255); + drawThings(); + + mCollision.removeCollisionResolver(); + } + + private void drawThings() { + /* collision springs */ + noFill(); + stroke(255, 0, 127, 64); + for (int i = 0; i < mCollision.collision().forces().size(); ++i) { + if (mCollision.collision().forces().get(i) instanceof Spring) { + Spring mySpring = (Spring) mCollision.collision_forces().get(i); + line(mySpring.a().position().x, mySpring.a().position().y, mySpring.a().position().z, + mySpring.b().position().x, mySpring.b().position().y, mySpring.b().position().z); + } + } + + /* particles */ + fill(245); + stroke(164); + for (int i = 0; i < mPhysics.particles().size(); ++i) { + Particle myParticle = mPhysics.particles().get(i); + pushMatrix(); + translate(myParticle.position().x, myParticle.position().y, myParticle.position().z); + ellipse(0, 0, + PARTICLE_SIZE, + PARTICLE_SIZE); + popMatrix(); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX02_Collisions.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX03_ParticlesLeavingTrails.java b/day3/libs/teilchen/src/teilchen/demo/LessonX03_ParticlesLeavingTrails.java new file mode 100755 index 0000000..fcc5e5d --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX03_ParticlesLeavingTrails.java @@ -0,0 +1,167 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import mathematik.Vector3f; +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.ShortLivedParticle; +import teilchen.constraint.Box; +import teilchen.force.Attractor; +import teilchen.force.Gravity; +import teilchen.force.ViscousDrag; +import teilchen.util.ParticleTrail; + +import java.util.Vector; + + +public class LessonX03_ParticlesLeavingTrails + extends PApplet { + + private Physics mPhysics; + + private Vector mTrails; + + private Attractor mAttractor; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(60); + + /* create a particle system */ + mPhysics = new Physics(); + + /* create a gravitational force */ + Gravity myGravity = new Gravity(); + mPhysics.add(myGravity); + myGravity.force().y = 20; + + /* create drag */ + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 0.1f; + mPhysics.add(myViscousDrag); + + final float mBorder = 40; + Box mBox = new Box(new Vector3f(mBorder, mBorder, mBorder), new Vector3f(width - mBorder, height - mBorder, 100 - mBorder)); + mBox.reflect(true); + mPhysics.add(mBox); + + /* create an attractor */ + mAttractor = new Attractor(); + mAttractor.radius(200); + mAttractor.strength(-300); + mPhysics.add(mAttractor); + + + /* create trails and particles */ + mTrails = new Vector(); + for (int i = 0; i < 500; i++) { + Particle mParticle = mPhysics.makeParticle(); + mParticle.mass(2.0f); + ParticleTrail myParticleTrail = new ParticleTrail(mPhysics, + mParticle, + 0.2f, + random(0.5f, 1)); + myParticleTrail.mass(0.5f); + mTrails.add(myParticleTrail); + } + resetParticles(width / 2, height / 2); + } + + private void resetParticles(float x, float y) { + for (ParticleTrail myTrails : mTrails) { + myTrails.particle().position().set(x + random(-10, 10), y + random(-10, 10), 0); + myTrails.particle().velocity().set(random(-10, 10), random(-10, 10), random(-10, 10)); + myTrails.fragments().clear(); + } + } + + public void draw() { + /* set attractor to mouse position */ + mAttractor.position().set(mouseX, mouseY); + + for (ParticleTrail myTrails : mTrails) { + myTrails.loop(1f / frameRate); + } + + mPhysics.step(1f / frameRate); + + background(255); + for (ParticleTrail myTrail : mTrails) { + drawTrail(myTrail); + } + } + + private void drawTrail(ParticleTrail theTrail) { + + final Vector mFragments = theTrail.fragments(); + final Particle mParticle = theTrail.particle(); + + /* draw head */ + if (mFragments.size() > 1) { + fill(255, 0, 127); + noStroke(); + pushMatrix(); + translate(mParticle.position().x, + mParticle.position().y, + mParticle.position().z); + sphereDetail(4); + sphere(3); + popMatrix(); + } + + /* draw trail */ + for (int i = 0; i < mFragments.size() - 1; i++) { + if (mFragments.get(i) instanceof ShortLivedParticle) { + final float mRatio = 1.0f - ((ShortLivedParticle) mFragments.get(i)).ageRatio(); + stroke(127, mRatio * 255); + strokeWeight(mRatio * 3); + } + int j = (i + 1) % mFragments.size(); + line(mFragments.get(i).position().x, + mFragments.get(i).position().y, + mFragments.get(i).position().z, + mFragments.get(j).position().x, + mFragments.get(j).position().y, + mFragments.get(j).position().z); + } + if (!mFragments.isEmpty()) { + line(mFragments.lastElement().position().x, + mFragments.lastElement().position().y, + mFragments.lastElement().position().z, + mParticle.position().x, + mParticle.position().y, + mParticle.position().z); + } + } + + public void mousePressed() { + resetParticles(mouseX, mouseY); + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX03_ParticlesLeavingTrails.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX04_StickMan.java b/day3/libs/teilchen/src/teilchen/demo/LessonX04_StickMan.java new file mode 100755 index 0000000..935ee0e --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX04_StickMan.java @@ -0,0 +1,150 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.Physics; +import teilchen.force.Attractor; +import teilchen.force.Gravity; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.integration.RungeKutta; +import teilchen.util.Overlap; +import teilchen.util.StickMan; + + +/** + * this demo shows some advanced use of particles, springs and attractors to + * create stickmen. + */ +public class LessonX04_StickMan + extends PApplet { + + private Physics mPhysics; + + private Attractor mAttractor; + + private Gravity mGravity; + + private ViscousDrag mViscousDrag; + + private StickMan[] mMyStickMan; + + public void setup() { + size(640, 480, OPENGL); + smooth(); + frameRate(60); + noFill(); + + mPhysics = new Physics(); + mPhysics.setInegratorRef(new RungeKutta()); + + mGravity = new Gravity(); + mGravity.force().y = 20; + mPhysics.add(mGravity); + + mViscousDrag = new ViscousDrag(); + mViscousDrag.coefficient = 0.85f; + mPhysics.add(mViscousDrag); + + mAttractor = new Attractor(); + mAttractor.radius(500); + mAttractor.strength(0); + mAttractor.position().set(width / 2, height / 2); + mPhysics.add(mAttractor); + + mMyStickMan = new StickMan[20]; + for (int i = 0; i < mMyStickMan.length; i++) { + mMyStickMan[i] = new StickMan(mPhysics, random(0, width), random(0.3f, 0.6f)); + } + } + + public void draw() { + + mPhysics.step(1f / 60f); + Overlap.resolveOverlap(mPhysics.particles()); + + /* constraint particles */ + for (int i = 0; i < mPhysics.particles().size(); i++) { + if (mPhysics.particles(i).position().y > height - 10) { + mPhysics.particles(i).position().y = height - 10; + } + if (mPhysics.particles(i).position().x > width) { + mPhysics.particles(i).position().x = width; + } + if (mPhysics.particles(i).position().x < 0) { + mPhysics.particles(i).position().x = 0; + } + } + + /* handle particles */ + if (mousePressed) { + mAttractor.position().set(mouseX, mouseY); + if (mouseButton == RIGHT) { + mAttractor.strength(-500); + mAttractor.radius(500); + } else { + mAttractor.strength(500); + mAttractor.radius(100); + } + } else { + mAttractor.strength(0); + } + + if (keyPressed) { + mGravity.force().y = -10; + } else { + mGravity.force().y = 20; + } + + /* draw */ + background(255); + + /* draw springs */ + stroke(0, 20); + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces(i) instanceof Spring) { + Spring mySpring = (Spring) mPhysics.forces(i); + line(mySpring.a().position().x, + mySpring.a().position().y, + mySpring.b().position().x, + mySpring.b().position().y); + } + } + + /* draw particles */ + for (int i = 0; i < mPhysics.particles().size(); i++) { + ellipse(mPhysics.particles(i).position().x, + mPhysics.particles(i).position().y, 5, 5); + } + + /* draw man */ + for (int i = 0; i < mMyStickMan.length; i++) { + mMyStickMan[i].draw(g); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX04_StickMan.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX05_AngleConstraints.java b/day3/libs/teilchen/src/teilchen/demo/LessonX05_AngleConstraints.java new file mode 100755 index 0000000..95971bc --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX05_AngleConstraints.java @@ -0,0 +1,210 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.AngleConstraintStick; +import teilchen.constraint.Stick; +import teilchen.force.AngleConstraintSpring; +import teilchen.force.Gravity; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.integration.RungeKutta; + + +public class LessonX05_AngleConstraints + extends PApplet { + + private Physics mPhysics; + + private Particle mParticleA; + + private Particle mParticleB; + + private Particle mParticleC; + + private Particle mParticleD; + + private AngleConstraintSpring mAngleConstraintABC; + + private AngleConstraintStick mAngleConstraintBCD; + + public void setup() { + size(640, 480); + frameRate(30); + smooth(); + + mPhysics = new Physics(); + mPhysics.setInegratorRef(new RungeKutta()); + + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 1f; + mPhysics.add(myViscousDrag); + + Gravity myGravity = new Gravity(); + myGravity.force().y = 50; + mPhysics.add(myGravity); + + /* particles */ + mParticleA = mPhysics.makeParticle(); + mParticleB = mPhysics.makeParticle(); + mParticleC = mPhysics.makeParticle(); + mParticleD = mPhysics.makeParticle(); + + mParticleA.position().set(width / 2 + 50, height / 3); + mParticleB.position().set(width / 2, height - height / 1.75f); + mParticleC.position().set(width / 2, height - height / 4); + mParticleD.position().set(width / 2, height - height / 8); + + mParticleA.radius(7); + mParticleB.radius(3); + mParticleC.radius(10); + mParticleD.radius(2); + + mParticleB.fixed(true); + + /* springs */ + Spring mSpringAB = new Spring(mParticleA, mParticleB); + mSpringAB.strength(250); + mSpringAB.damping(10); + mPhysics.add(mSpringAB); + + Spring mSpringBC = new Spring(mParticleB, mParticleC); + mSpringBC.strength(250); + mSpringBC.damping(10); + mPhysics.add(mSpringBC); + + Stick mSpringCD = new Stick(mParticleC, mParticleD); + mSpringCD.damping(1); + mPhysics.add(mSpringCD); + + /* angle constraint */ + mAngleConstraintABC = new AngleConstraintSpring(mParticleA, mParticleB, mParticleC); + mAngleConstraintABC.min_angle(PI * 0.5f); + mAngleConstraintABC.damping(1); + mAngleConstraintABC.strength(200); + mPhysics.add(mAngleConstraintABC); + + mAngleConstraintBCD = new AngleConstraintStick(mParticleB, mParticleC, mParticleD); + mAngleConstraintBCD.min_angle(PI * 0.8f); + mAngleConstraintBCD.damping(0.5f); + mPhysics.add(mAngleConstraintBCD); + } + + public void draw() { + /* attach particle to mouse */ + if (mousePressed) { + mParticleA.position().set(mouseX, mouseY); + } + + /* apply constraints */ + mAngleConstraintABC.pre_step(); + mAngleConstraintBCD.pre_step(); + draw_physics(); + + mPhysics.step(1f / frameRate); + + /* remove contraints */ + mAngleConstraintABC.post_step(); + mAngleConstraintBCD.post_step(); + } + + private void draw_physics() { + background(255); + + drawSprings(); + drawSticks(); + drawParticles(); + } + + private void drawSprings() { + for (int i = 0; i < mPhysics.forces().size(); i++) { + if (mPhysics.forces(i) instanceof Spring) { + final Spring mSpring = (Spring) mPhysics.forces(i); + if (mSpring instanceof AngleConstraintSpring) { + strokeWeight(1); + if (mSpring.active()) { + stroke(255, 0, 0, 64); + } else { + stroke(255, 0, 0, 16); + } + } else { + strokeWeight(3); + stroke(0, 128); + } + line(mSpring.a(), mSpring.b()); + } + } + strokeWeight(1); + } + + private void drawSticks() { + for (int i = 0; i < mPhysics.constraints().size(); i++) { + if (mPhysics.constraints(i) instanceof Stick) { + final Stick mStick = (Stick) mPhysics.constraints(i); + if (mStick instanceof AngleConstraintStick) { + strokeWeight(1); + if (mStick.active()) { + stroke(0, 127, 255, 64); + } else { + stroke(0, 127, 255, 16); + } + } else { + strokeWeight(3); + stroke(0, 128); + } + line(mStick.a(), mStick.b()); + } + } + strokeWeight(1); + } + + private void drawParticles() { + stroke(0); + fill(92); + drawParticle(mParticleA); + fill(127); + drawParticle(mParticleB); + fill(192); + drawParticle(mParticleC); + fill(64); + drawParticle(mParticleD); + } + + private void drawParticle(Particle p) { + ellipse(p.position().x, + p.position().y, + p.radius() * 2, p.radius() * 2); + } + + private void line(Particle p1, Particle p2) { + line(p1.position().x, p1.position().y, + p2.position().x, p2.position().y); + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX05_AngleConstraints.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX06_Ducklings.java b/day3/libs/teilchen/src/teilchen/demo/LessonX06_Ducklings.java new file mode 100755 index 0000000..7aa0fc3 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX06_Ducklings.java @@ -0,0 +1,173 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import processing.core.PApplet; +import teilchen.BehaviorParticle; +import teilchen.Physics; +import teilchen.behavior.Arrival; +import teilchen.force.Spring; +import teilchen.force.ViscousDrag; +import teilchen.util.CollisionManager; + +import java.util.Vector; + + +/** + * this demo shows how to add behaviors to particles. in this example the + * arrival behavior. + */ +public class LessonX06_Ducklings + extends PApplet { + + private Physics mPhysics; + + private Vector mDucklings; + + private CollisionManager mCollision; + + public void setup() { + size(640, 480, OPENGL); + frameRate(60); + smooth(); + colorMode(RGB, 1.0f); + + /* physics */ + mPhysics = new Physics(); + + ViscousDrag myViscousDrag = new ViscousDrag(); + myViscousDrag.coefficient = 0.25f; + mPhysics.add(myViscousDrag); + + mCollision = new CollisionManager(); + mCollision.minimumDistance(25); + + /* ducklings */ + mDucklings = new Vector(); + for (int i = 0; i < 13; i++) { + final Duckling mDuckling = new Duckling(); + if (!mDucklings.isEmpty()) { + mDuckling.arrival.setPositionRef(mDucklings.lastElement().particle.position()); + } + mCollision.collision().add(mDuckling.particle); + mDucklings.add(mDuckling); + } + } + + public void draw() { + final float mDeltaTime = 1.0f / frameRate; + background(1); + + /* update particles */ + mCollision.createCollisionResolvers(); + mCollision.loop(mDeltaTime); + mPhysics.step(mDeltaTime); + + drawCollisionSprings(); + mCollision.removeCollisionResolver(); + + mDucklings.firstElement().arrival.oversteer(!mousePressed); + mDucklings.firstElement().arrival.position().set(mouseX, mouseY); + + /* draw */ + for (Duckling mDuckling : mDucklings) { + drawParticle(mDuckling); + } + + /* draw arrival */ + stroke(0, 0.25f); + noFill(); + ellipse(mDucklings.firstElement().arrival.position().x, + mDucklings.firstElement().arrival.position().y, + 20, 20); + } + + private void drawParticle(Duckling pDuckling) { + final BehaviorParticle mParticle = pDuckling.particle; + final Arrival mArrival = pDuckling.arrival; + + /* draw particle */ + stroke(0, 0.5f); + noFill(); + if (mArrival.arriving()) { + stroke(1, 0, 0, 0.5f); + } + if (mArrival.arrived()) { + stroke(0, 1, 0, 0.5f); + } + ellipse(mParticle.position().x, mParticle.position().y, + mParticle.radius() * 2, mParticle.radius() * 2); + + /* - */ + pushMatrix(); + translate(mParticle.position().x, + mParticle.position().y); + + /* draw velocity */ + stroke(1, 0, 0, 0.5f); + line(0, 0, mParticle.velocity().x, mParticle.velocity().y); + + /* draw break force */ + stroke(0, 0.5f, 1, 0.5f); + line(0, 0, mArrival.force().x, mArrival.force().y); + + /* - */ + popMatrix(); + } + + private void drawCollisionSprings() { + stroke(0, 1, 0, 0.25f); + for (int i = 0; i < mCollision.collision().forces().size(); ++i) { + if (mCollision.collision().forces().get(i) instanceof Spring) { + Spring mySpring = (Spring) mCollision.collision_forces().get(i); + line(mySpring.a().position().x, mySpring.a().position().y, mySpring.a().position().z, + mySpring.b().position().x, mySpring.b().position().y, mySpring.b().position().z); + } + } + } + + class Duckling { + + BehaviorParticle particle; + + Arrival arrival; + + Duckling() { + /* create particles */ + particle = mPhysics.makeParticle(BehaviorParticle.class); + particle.position().set(random(width), random(height)); + particle.maximumInnerForce(random(50, 150)); + particle.radius(random(6, 10)); + + arrival = new Arrival(); + arrival.breakforce(random(12, 28)); + arrival.breakradius(random(45, 55)); + + particle.behaviors().add(arrival); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX06_Ducklings.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX07_CubicleWorld.java b/day3/libs/teilchen/src/teilchen/demo/LessonX07_CubicleWorld.java new file mode 100755 index 0000000..41cc56d --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX07_CubicleWorld.java @@ -0,0 +1,192 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import mathematik.Vector3f; +import mathematik.Vector3i; + +import processing.core.PApplet; +import teilchen.cubicle.CubicleWorld; +import teilchen.cubicle.ICubicleEntity; +import teilchen.util.CubicleWorldView; +import teilchen.util.DrawLib; + +import java.util.Vector; + + +public class LessonX07_CubicleWorld + extends PApplet { + + private final int WORLD_NUMBER_OF_CUBICLES = 15; + + private final float WORLD_CUBICLE_SCALE = 20; + + private final float WORLD_SCALE = WORLD_NUMBER_OF_CUBICLES * WORLD_CUBICLE_SCALE; + + private boolean showCubicles = true; + + private float mRotationZ = 0.1f; + + private Vector3f mPosition = new Vector3f(); + + private CubicleWorld mCubicleWorld; + + private CubicleWorldView mCubicleWorldView; + + public void setup() { + size(640, 480, OPENGL); + textFont(createFont("Courier", 11)); + hint(DISABLE_DEPTH_SORT); + hint(DISABLE_DEPTH_TEST); + + /* setup world */ + mCubicleWorld = new CubicleWorld(WORLD_NUMBER_OF_CUBICLES, WORLD_NUMBER_OF_CUBICLES, WORLD_NUMBER_OF_CUBICLES); + mCubicleWorld.cellscale().set(WORLD_CUBICLE_SCALE, WORLD_CUBICLE_SCALE, WORLD_CUBICLE_SCALE); + mCubicleWorld.transform().translation.set(-WORLD_SCALE / 2, -WORLD_SCALE / 2, -WORLD_SCALE / 2); + + mCubicleWorldView = new CubicleWorldView(mCubicleWorld); + mCubicleWorldView.color_empty = color(0, 1); + mCubicleWorldView.color_full = color(0, 4); + + mCubicleWorld.add(new MCubicleEntity()); + } + + public void draw() { + /* handle entities */ + if (frameRate > 30) { + addRandomEntities(2); + } + + mCubicleWorld.update(); + Vector mEntities = mCubicleWorld.getLocalEntities(mPosition, 1); + + /* draw things */ + background(255); + + pushMatrix(); + translate(width / 2, height / 2, 0); + + /* rotate */ + if (mousePressed) { + mRotationZ += (mouseX * 0.01f - mRotationZ) * 0.05f; + } else { + mPosition.x = mouseX - width / 2; + mPosition.y = mouseY - height / 2; + } + rotateX(THIRD_PI); + rotateZ(mRotationZ); + + /* draw cubicle world */ + if (showCubicles) { + stroke(0, 127); + noFill(); + mCubicleWorldView.draw(g); + } + + /* draw entities */ + int mNumberOfPointsSelected = 0; + stroke(0, 127, 255, 127); + noFill(); + if (mEntities != null) { + mNumberOfPointsSelected = mEntities.size(); + for (ICubicleEntity mEntity : mEntities) { + MCubicleEntity m = (MCubicleEntity) mEntity; + stroke(m.color); + DrawLib.cross3(g, mEntity.position(), 5.0f); + } + } + + /* draw crosshair */ + stroke(255, 0, 0, 63); + noFill(); + beginShape(LINES); + vertex(mPosition.x, -WORLD_SCALE / 2, 0); + vertex(mPosition.x, WORLD_SCALE / 2, 0); + vertex(-WORLD_SCALE / 2, mPosition.y, 0); + vertex(WORLD_SCALE / 2, mPosition.y, 0); + endShape(); + + /* draw selection sphere */ + stroke(255, 0, 0, 63); + noFill(); + translate(mPosition.x, mPosition.y, 0); + box(WORLD_CUBICLE_SCALE); + popMatrix(); + + fill(0); + noStroke(); + text("POINTS : " + mCubicleWorld.entities().size(), 10, 12); + text("SELECTED : " + mNumberOfPointsSelected, 10, 24); + text("FPS : " + frameRate, 10, 36); + } + + private void addRandomEntities(int pNumberParticles) { + for (int i = 0; i < pNumberParticles; i++) { + MCubicleEntity mEntity = new MCubicleEntity(); + mEntity.position().x = random(-WORLD_SCALE / 2, WORLD_SCALE / 2); + mEntity.position().y = random(-WORLD_SCALE / 2, WORLD_SCALE / 2); + mEntity.position().z = random(-WORLD_SCALE / 2, WORLD_SCALE / 2); + mCubicleWorld.add(mEntity); + } + } + + class MCubicleEntity + implements ICubicleEntity { + + int color = color(0, 127, random(0, 255), 127); + + private Vector3i mCubiclePosition; + + private final Vector3f mPosition; + + public MCubicleEntity() { + mCubiclePosition = new Vector3i(); + mPosition = new Vector3f(); + } + + public Vector3i cubicle() { + return mCubiclePosition; + } + + public Vector3f position() { + return mPosition; + } + + public boolean leaving(int theX, int theY, int theZ) { + if (theX == cubicle().x + && theY == cubicle().y + && theZ == cubicle().z) { + return false; + } + return true; + } + + public boolean isActive() { + return true; + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX07_CubicleWorld.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/demo/LessonX08_Schwarm.java b/day3/libs/teilchen/src/teilchen/demo/LessonX08_Schwarm.java new file mode 100755 index 0000000..41ca7c7 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/demo/LessonX08_Schwarm.java @@ -0,0 +1,174 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.demo; + + +import mathematik.Vector3f; +import processing.core.PApplet; +import processing.core.PGraphics; +import processing.core.PMatrix3D; +import teilchen.BehaviorParticle; +import teilchen.Physics; +import teilchen.behavior.Alignment; +import teilchen.behavior.Cohesion; +import teilchen.behavior.Motor; +import teilchen.behavior.Separation; +import teilchen.behavior.Wander; +import teilchen.constraint.Teleporter; +import teilchen.force.ViscousDrag; +import teilchen.util.Util; + +import java.util.Vector; + + +public class LessonX08_Schwarm + extends PApplet { + + private Physics mPhysics; + + private Vector mSwarmEntities; + + public void setup() { + size(640, 480, OPENGL); + frameRate(60); + smooth(); + rectMode(CENTER); + hint(DISABLE_DEPTH_TEST); + + /* physics */ + mPhysics = new Physics(); + + Teleporter mTeleporter = new Teleporter(); + mTeleporter.min().set(0, 0, height / -2); + mTeleporter.max().set(width, height, height / 2); + mPhysics.add(mTeleporter); + + ViscousDrag myViscousDrag = new ViscousDrag(); + mPhysics.add(myViscousDrag); + + /* setup entities */ + mSwarmEntities = new Vector(); + for (int i = 0; i < 60; i++) { + SwarmEntity mSwarmEntity = new SwarmEntity(); + mSwarmEntity.position().set(random(mTeleporter.min().x, mTeleporter.max().x), + random(mTeleporter.min().y, mTeleporter.max().y), + random(mTeleporter.min().z, mTeleporter.max().z)); + mSwarmEntities.add(mSwarmEntity); + mPhysics.add(mSwarmEntity); + } + } + + public void draw() { + final float mDeltaTime = 1.0f / frameRate; + + /* physics */ + mPhysics.step(mDeltaTime); + + /* entities */ + for (SwarmEntity s : mSwarmEntities) { + s.update(mDeltaTime); + } + + /* draw */ + background(255); + for (SwarmEntity s : mSwarmEntities) { + s.draw(g); + } + } + + private class SwarmEntity + extends BehaviorParticle { + + private Separation separation; + + private Alignment alignment; + + private Cohesion cohesion; + + private Wander wander; + + private Motor motor; + + public SwarmEntity() { + maximumInnerForce(random(100.0f, 1000.0f)); + radius(10f); + + separation = new Separation(); + separation.proximity(20); + separation.weight(50.0f); + behaviors().add(separation); + + alignment = new Alignment(); + alignment.proximity(30); + alignment.weight(30.0f); + behaviors().add(alignment); + + cohesion = new Cohesion(); + cohesion.proximity(100); + cohesion.weight(5.0f); + behaviors().add(cohesion); + + wander = new Wander(); + behaviors().add(wander); + + motor = new Motor(); + motor.auto_update_direction(true); + motor.strength(20.0f); + behaviors().add(motor); + } + + public void update(float theDeltaTime) { + separation.neighbors(mSwarmEntities); + alignment.neighbors(mSwarmEntities); + cohesion.neighbors(mSwarmEntities); + } + + private void draw(PGraphics g) { + pushMatrix(); + + translate(position().x, position().y, position().z); + + pushMatrix(); + + PMatrix3D p = new PMatrix3D(); + Util.pointAt(p, position(), new Vector3f(0, 1, 0), mathematik.Util.add(position(), velocity())); + applyMatrix(p); + + noStroke(); + fill(0); + scale(1, 0.25f, 3); + box(6); + + popMatrix(); + + /* velocity */ + stroke(0, 31); + line(0, 0, 0, velocity().x, velocity().y, velocity().z); + + popMatrix(); + } + } + + public static void main(String[] args) { + PApplet.main(new String[]{LessonX08_Schwarm.class.getName()}); + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/AngleConstraintSpring.java b/day3/libs/teilchen/src/teilchen/force/AngleConstraintSpring.java new file mode 100755 index 0000000..276f601 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/AngleConstraintSpring.java @@ -0,0 +1,101 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import mathematik.Vector3f; + +import teilchen.Particle; + + +public class AngleConstraintSpring + extends Spring { + + private final Particle mParticleA; + + private final Particle mParticleB; + + private final Particle mParticleC; + + private float mMinAngle; + + /** + * + * particles are connected like this: A -- B -- C + * + * @param pParticleA + * @param pParticleB + * @param pParticleC + */ + public AngleConstraintSpring(Particle pParticleA, Particle pParticleB, Particle pParticleC) { + super(pParticleA, pParticleC); + mParticleA = pParticleA; + mParticleB = pParticleB; + mParticleC = pParticleC; + mMinAngle = Float.MAX_VALUE; + } + + public void min_angle(float pAngle) { + mMinAngle = pAngle; + } + + public void pre_step() { + Vector3f ab = mathematik.Util.sub(mParticleA.position(), mParticleB.position()); + Vector3f cb = mathematik.Util.sub(mParticleC.position(), mParticleB.position()); + final float mCurrentAngle = ab.angle(cb); + + if (mCurrentAngle < mMinAngle) { + final int TINY_FACTOR_MODELL = 0; + final int TRIG_MODELL = 1; + final int MAX_DISTANCE_MODELL = 2; + + final int mModell = TRIG_MODELL; + + switch (mModell) { + case TINY_FACTOR_MODELL: { + final float TINY_FACTOR = 1.1f; + final float mDistance = mParticleA.position().distance(mParticleC.position()) * TINY_FACTOR; + restlength(mDistance); + } + break; + case TRIG_MODELL: { + // a = sqrt ( b*b + c*c - 2bc*cosA ) + final float b = ab.length(); + final float c = cb.length(); + final float mDistance = (float) Math.sqrt(b * b + c * c - 2 * b * c * (float) Math.cos(mMinAngle)); + restlength(mDistance); + } + break; + case MAX_DISTANCE_MODELL: { + final float mDistance = mParticleA.position().distance(mParticleB.position()) + mParticleC.position().distance(mParticleB.position()); + restlength(mDistance); + } + break; + } + active(true); + } + } + + public void post_step() { + active(false); + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/Attractor.java b/day3/libs/teilchen/src/teilchen/force/Attractor.java new file mode 100755 index 0000000..36d10e4 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/Attractor.java @@ -0,0 +1,114 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +public class Attractor + implements IForce { + + protected Vector3f _myPosition; + + protected float _myStrength; + + protected float _myRadius; + + protected final Vector3f myTemp = new Vector3f(); + + private boolean _myActive; + + public Attractor() { + _myPosition = new Vector3f(); + _myRadius = 100; + _myStrength = 1; + _myActive = true; + } + + public Vector3f position() { + return _myPosition; + } + + public void setPositionRef(Vector3f thePosition) { + _myPosition = thePosition; + } + + public float strength() { + return _myStrength; + } + + public void strength(float theStrength) { + _myStrength = theStrength; + } + + public float radius() { + return _myRadius; + } + + public void radius(float theRadius) { + _myRadius = theRadius; + } + + public void apply(float theDeltaTime, Physics theParticleSystem) { + if (_myStrength != 0) { + for (final Particle myParticle : theParticleSystem.particles()) { + /* each particle */ + if (!myParticle.fixed()) { + myTemp.sub(_myPosition, myParticle.position()); + final float myDistance = fastInverseSqrt(1 / myTemp.lengthSquared()); + if (myDistance < _myRadius) { + float myFallOff = 1f - myDistance / _myRadius; + final float myForce = myFallOff * myFallOff * _myStrength; + myTemp.scale(myForce / myDistance); + if (!myParticle.fixed()) { + myParticle.force().add(myTemp); + } + } + } + } + } + } + + protected static float fastInverseSqrt(float v) { + final float half = 0.5f * v; + int i = Float.floatToIntBits(v); + i = 0x5f375a86 - (i >> 1); + v = Float.intBitsToFloat(i); + return v * (1.5f - half * v * v); + } + + public boolean dead() { + return false; + } + + public boolean active() { + return _myActive; + } + + public void active(boolean theActiveState) { + _myActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/DirectedAttractor.java b/day3/libs/teilchen/src/teilchen/force/DirectedAttractor.java new file mode 100755 index 0000000..ef4fad9 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/DirectedAttractor.java @@ -0,0 +1,63 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +public class DirectedAttractor + extends Attractor { + + private final Vector3f myVectorA = new Vector3f(); + + private final Vector3f myVectorB = new Vector3f(); + + public DirectedAttractor() { + super(); + } + + public void apply(float theDeltaTime, Physics theParticleSystem) { + for (final Particle myParticle : theParticleSystem.particles()) { + /* each particle */ + if (!myParticle.fixed()) { + myTemp.sub(_myPosition, myParticle.position()); + + final float myDistance = fastInverseSqrt(1 / myTemp.lengthSquared()); + if (myDistance < _myRadius) { + + myVectorA.scale(1 / myDistance, myTemp); + myVectorB.normalize(myParticle.velocity()); + float myAngle = myVectorA.dot(myVectorB); + + float myFallOff = 1f - myDistance / _myRadius; + final float myForce = myAngle * myFallOff * myFallOff * _myStrength; + myTemp.scale(myForce / myDistance); + myParticle.force().add(myTemp); + } + } + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/Gravity.java b/day3/libs/teilchen/src/teilchen/force/Gravity.java new file mode 100755 index 0000000..d71a1d7 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/Gravity.java @@ -0,0 +1,74 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +public class Gravity + implements IForce { + + private boolean _myActive; + + private Vector3f _myForce; + + public Gravity(final Vector3f theForce) { + _myActive = true; + _myForce = theForce; + } + + public Gravity() { + this(new Vector3f(0, 9.81f, 0)); + } + + public Gravity(float theX, float theY, float theZ) { + this(new Vector3f(theX, theY, theZ)); + } + + public Vector3f force() { + return _myForce; + } + + public void apply(final float theDeltaTime, final Physics theParticleSystem) { + for (final Particle myParticle : theParticleSystem.particles()) { + if (!myParticle.fixed()) { + myParticle.force().add(_myForce); + } + } + } + + public boolean dead() { + return false; + } + + public boolean active() { + return _myActive; + } + + public void active(boolean theActiveState) { + _myActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/IForce.java b/day3/libs/teilchen/src/teilchen/force/IForce.java new file mode 100755 index 0000000..b32c2e0 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/IForce.java @@ -0,0 +1,37 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import teilchen.Physics; + + +public interface IForce { + + void apply(final float theDeltaTime, final Physics theParticleSystem); + + boolean dead(); + + boolean active(); + + void active(boolean theActiveState); +} diff --git a/day3/libs/teilchen/src/teilchen/force/MuscleSpring.java b/day3/libs/teilchen/src/teilchen/force/MuscleSpring.java new file mode 100755 index 0000000..37b09ac --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/MuscleSpring.java @@ -0,0 +1,114 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import teilchen.Particle; +import teilchen.Physics; + + +public class MuscleSpring + extends Spring { + + private float mAmplitude = 1; + + private float mOffset = 0; + + private float mFrequency = 1; + + private float mInitialRestLength; + + private float mCurrentTime; + + private boolean mAutomaticContraction = true; + + public MuscleSpring(Particle theA, Particle theB) { + super(theA, theB); + mInitialRestLength = mRestLength; + } + + public MuscleSpring(final Particle theA, + final Particle theB, + final float theSpringConstant, + final float theSpringDamping, + final float theRestLength) { + super(theA, + theB, + theSpringConstant, + theSpringDamping, + theRestLength); + mInitialRestLength = mRestLength; + } + + public void setRestLengthByPosition() { + mInitialRestLength = mA.position().distance(mB.position()); + } + + public float restlength() { + return mInitialRestLength; + } + + public void restlength(float theRestLength) { + mInitialRestLength = theRestLength; + } + + public void frequency(final float theFrequency) { + mFrequency = theFrequency; + } + + public float frequency() { + return mFrequency; + } + + public void amplitude(final float theAmplitude) { + mAmplitude = theAmplitude; + } + + public float amplitude() { + return mAmplitude; + } + + /** + * set the offset of the contraction in radians. + * + * @param theOffset float + */ + public void offset(final float theOffset) { + mOffset = theOffset; + } + + public float offset() { + return mOffset; + } + + public void apply(final float theDeltaTime, final Physics theParticleSystem) { + + if (mAutomaticContraction) { + mCurrentTime += theDeltaTime; + + final float myOffset = (float) Math.sin(mCurrentTime * mFrequency + mOffset) * mAmplitude; + mRestLength = mInitialRestLength + myOffset; + } + + super.apply(theDeltaTime, theParticleSystem); + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/PlaneDeflector.java b/day3/libs/teilchen/src/teilchen/force/PlaneDeflector.java new file mode 100755 index 0000000..413404a --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/PlaneDeflector.java @@ -0,0 +1,160 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import mathematik.Plane3f; +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +public class PlaneDeflector + implements IForce { + + private final Plane3f mPlane; + + private float mCoefficientOfRestitution; + + private final Vector3f _myTempDiff; + + private final Vector3f mTempReflectionVector; + + private final Vector3f mTempNormalComponent; + + private final Vector3f mTempTangentComponent; + + private boolean _myActive; + + public PlaneDeflector() { + mPlane = new Plane3f(); + mPlane.normal = new Vector3f(0, 1, 0); + mCoefficientOfRestitution = 1.0f; + + _myTempDiff = new Vector3f(); + mTempReflectionVector = new Vector3f(); + mTempNormalComponent = new Vector3f(); + mTempTangentComponent = new Vector3f(); + _myActive = true; + } + + public void apply(final float theDeltaTime, final Physics theParticleSystem) { + for (final Particle myParticle : theParticleSystem.particles()) { + if (!myParticle.fixed()) { + /* test if particle passed plane */ + if (testParticlePosition(myParticle, mPlane) < 0) { + + /* find intersection with plane */ + Vector3f myResult = new Vector3f(); + /** + * using the normal of the plane instead of the velocity of + * the particle. though less correct it seems to be more + * stable. + */ + final float myIntersectionResult = intersectLinePlane(myParticle.position(), + mPlane.normal, + mPlane, + myResult); + + /* remove particle from collision */ + if (myIntersectionResult != Float.NEGATIVE_INFINITY + && !myResult.isNaN()) { + myParticle.position().set(myResult); + } + + /* change direction */ + seperateComponents(myParticle, mPlane); + myParticle.velocity().set(mTempReflectionVector); + } + } + } + } + + public Plane3f plane() { + return mPlane; + } + + public void coefficientofrestitution(float theCoefficientOfRestitution) { + mCoefficientOfRestitution = theCoefficientOfRestitution; + } + + public float coefficientofrestitution() { + return mCoefficientOfRestitution; + } + + private final float testParticlePosition(Particle theParticle, Plane3f thePlane) { + _myTempDiff.sub(theParticle.position(), thePlane.origin); + _myTempDiff.normalize(); + final float myAngle = _myTempDiff.dot(thePlane.normal); + return myAngle; + } + + private final void seperateComponents(Particle theParticle, Plane3f thePlane) { + /* normal */ + mTempNormalComponent.set(thePlane.normal); + mTempNormalComponent.scale(thePlane.normal.dot(theParticle.velocity())); + /* tangent */ + mTempTangentComponent.sub(theParticle.velocity(), mTempNormalComponent); + /* negate normal */ + mTempNormalComponent.scale(-mCoefficientOfRestitution); + /* set reflection vector */ + mTempReflectionVector.add(mTempTangentComponent, mTempNormalComponent); + } + + private final float intersectLinePlane(final Vector3f theRayOrigin, + final Vector3f theRayDirection, + final Plane3f thePlane, + final Vector3f theIntersectionPoint) { + float myT; + final float myDenominator = thePlane.normal.dot(theRayDirection); + + if (myDenominator == 0) { + System.err.println("### ERROR @ Intersection / NEGATIVE_INFINITY"); + return Float.NEGATIVE_INFINITY; + } + + final float numer = thePlane.normal.dot(theRayOrigin); + final float D = -thePlane.origin.dot(thePlane.normal); + myT = -((numer + D) / myDenominator); + + if (theIntersectionPoint != null) { + theIntersectionPoint.set(theRayDirection); + theIntersectionPoint.scale((float) myT); + theIntersectionPoint.add(theRayOrigin); + } + + return myT; + } + + public boolean dead() { + return false; + } + + public boolean active() { + return _myActive; + } + + public void active(boolean theActiveState) { + _myActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/Spring.java b/day3/libs/teilchen/src/teilchen/force/Spring.java new file mode 100755 index 0000000..6d48a34 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/Spring.java @@ -0,0 +1,211 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import teilchen.IConnection; +import teilchen.Particle; +import teilchen.Physics; + + +public class Spring + implements IForce, + IConnection { + + protected float mSpringConstant; + + protected float mSpringDamping; + + protected float mRestLength; + + protected Particle mA; + + protected Particle mB; + + protected boolean mOneWay; + + protected boolean mActive; + + public Spring(Particle theA, Particle theB) { + this(theA, + theB, + 2.0f, 0.1f, + theA.position().distance(theB.position())); + } + + public Spring(Particle theA, Particle theB, float theRestLength) { + this(theA, + theB, + 2.0f, 0.1f, + theRestLength); + } + + public Spring(Particle theA, + Particle theB, + final float theSpringConstant, + final float theSpringDamping) { + this(theA, + theB, + theSpringConstant, + theSpringDamping, + theA.position().distance(theB.position())); + } + + public Spring(final Particle theA, + final Particle theB, + final float theSpringConstant, + final float theSpringDamping, + final float theRestLength) { + mSpringConstant = theSpringConstant; + mSpringDamping = theSpringDamping; + mRestLength = theRestLength; + mA = theA; + mB = theB; + mOneWay = false; + mActive = true; + } + + public void setRestLengthByPosition() { + mRestLength = mA.position().distance(mB.position()); + } + + public float restlength() { + return mRestLength; + } + + public void restlength(float theRestLength) { + mRestLength = theRestLength; + } + + public final Particle a() { + return mA; + } + + public final Particle b() { + return mB; + } + + public final Particle a(Particle theA) { + return mA = theA; + } + + public final Particle b(Particle theB) { + return mB = theB; + } + + public final float currentLength() { + return mA.position().distance(mB.position()); + } + + /** + * spring constant. + * + * @return float + */ + public final float strength() { + return mSpringConstant; + } + + /** + * spring constant. + * + * @param theSpringConstant float + */ + public final void strength(float theSpringConstant) { + mSpringConstant = theSpringConstant; + } + + public final float damping() { + return mSpringDamping; + } + + public final void damping(float theSpringDamping) { + mSpringDamping = theSpringDamping; + } + + public void setOneWay(boolean theOneWayState) { + mOneWay = theOneWayState; + } + + public void apply(final float theDeltaTime, final Physics theParticleSystem) { +// if (!mA.fixed() || !mB.fixed()) { + float a2bX = mA.position().x - mB.position().x; + float a2bY = mA.position().y - mB.position().y; + float a2bZ = mA.position().z - mB.position().z; + final float myInversDistance = fastInverseSqrt(a2bX * a2bX + a2bY * a2bY + a2bZ * a2bZ); + final float myDistance = 1.0F / myInversDistance; + + if (myDistance == 0.0F) { + a2bX = 0.0F; + a2bY = 0.0F; + a2bZ = 0.0F; + } else { + a2bX *= myInversDistance; + a2bY *= myInversDistance; + a2bZ *= myInversDistance; + } + + final float mSpringForce = -(myDistance - mRestLength) * mSpringConstant; + final float Va2bX = mA.velocity().x - mB.velocity().x; + final float Va2bY = mA.velocity().y - mB.velocity().y; + final float Va2bZ = mA.velocity().z - mB.velocity().z; + final float mDampingForce = -mSpringDamping * (a2bX * Va2bX + a2bY * Va2bY + a2bZ * Va2bZ); + final float r = mSpringForce + mDampingForce; + a2bX *= r; + a2bY *= r; + a2bZ *= r; + + if (mOneWay) { + if (!mB.fixed()) { + mB.force().add(-2 * a2bX, -2 * a2bY, -2 * a2bZ); + } + } else { + if (!mA.fixed()) { + mA.force().add(a2bX, a2bY, a2bZ); + } + if (!mB.fixed()) { + mB.force().add(-a2bX, -a2bY, -a2bZ); + } + } +// } + } + + protected static float fastInverseSqrt(float v) { + final float half = 0.5f * v; + int i = Float.floatToIntBits(v); + i = 0x5f375a86 - (i >> 1); + v = Float.intBitsToFloat(i); + return v * (1.5f - half * v * v); + } + + public boolean dead() { + return mA.dead() || mB.dead(); + } + + public boolean active() { + return mActive; + } + + public void active(boolean theActiveState) { + mActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/TearableSpring.java b/day3/libs/teilchen/src/teilchen/force/TearableSpring.java new file mode 100755 index 0000000..33c190a --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/TearableSpring.java @@ -0,0 +1,82 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import teilchen.Particle; +import teilchen.Physics; + + +public class TearableSpring + extends Spring { + + private boolean _myTornApart = false; + + private float _myTearDistance = -1; + + public TearableSpring(Particle theA, Particle theB) { + super(theA, + theB, + 2.0f, 0.1f, + theA.position().distance(theB.position())); + } + + public TearableSpring(final Particle theA, + final Particle theB, + final float theSpringConstant, + final float theSpringDamping, + final float theRestLength, + final float theTearDistance) { + super(theA, + theB, + theSpringConstant, + theSpringDamping, + theRestLength); + _myTearDistance = theTearDistance; + } + + public final float teardistance() { + return _myTearDistance; + } + + public final void teardistance(float theTearDistance) { + _myTearDistance = theTearDistance; + } + + public void apply(final float theDeltaTime, final Physics theParticleSystem) { + /* check if spring will tear */ + if (_myTearDistance > 0) { + final float myActualDistance = a().position().distance(b().position()); + if (myActualDistance > restlength() + _myTearDistance) { + _myTornApart = true; + } + } + /* apply force if spring is ok */ + if (!_myTornApart) { + super.apply(theDeltaTime, theParticleSystem); + } + } + + public boolean dead() { + return _myTornApart || super.dead(); + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/TriangleDeflector.java b/day3/libs/teilchen/src/teilchen/force/TriangleDeflector.java new file mode 100755 index 0000000..eea9271 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/TriangleDeflector.java @@ -0,0 +1,230 @@ +package teilchen.force; + +import mathematik.Intersection; +import mathematik.Intersection.IntersectionResult; +import mathematik.Vector3f; +import mathematik.WorldAxisAlignedBoundingBox; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.IConstraint; + +public class TriangleDeflector + implements IConstraint { + + private final Vector3f a; + + private final Vector3f b; + + private final Vector3f c; + + private final Vector3f mNormal; + + private float mCoefficientOfRestitution; + + private final Vector3f mTempReflectionVector; + + private final Vector3f mTempNormalComponent; + + private final Vector3f mTempTangentComponent; + + private final IntersectionResult mIntersectionResult; + + private final Vector3f mTempPointOfIntersection = new Vector3f(); + + private final WorldAxisAlignedBoundingBox mWorldAxisAlignedBoundingBox; + + private final Vector3f[] mVectorCollection; + + public boolean AUTO_UPDATE = true; + + private boolean mGotHit = false; + + private boolean mActive; + + public TriangleDeflector() { + a = new Vector3f(); + b = new Vector3f(); + c = new Vector3f(); + + /* hmmm. */ + mVectorCollection = new Vector3f[3]; + mVectorCollection[0] = a; + mVectorCollection[1] = b; + mVectorCollection[2] = c; + + mNormal = new Vector3f(); + mCoefficientOfRestitution = 1.0f; + + mTempReflectionVector = new Vector3f(); + mTempNormalComponent = new Vector3f(); + mTempTangentComponent = new Vector3f(); + mIntersectionResult = new IntersectionResult(); + mWorldAxisAlignedBoundingBox = new WorldAxisAlignedBoundingBox(); + + mActive = true; + } + + public Vector3f a() { + return a; + } + + public Vector3f b() { + return b; + } + + public Vector3f c() { + return c; + } + + public WorldAxisAlignedBoundingBox boundingbox() { + return mWorldAxisAlignedBoundingBox; + } + + public void updateProperties() { + mVectorCollection[0] = a; + mVectorCollection[1] = b; + mVectorCollection[2] = c; + mathematik.Util.calculateNormal(a, b, c, mNormal); + mathematik.Util.updateBoundingBox(mWorldAxisAlignedBoundingBox, mVectorCollection); + } + + private float mPreviousT = -1.0f; + + public void apply(Physics pParticleSystem) { +// public void apply(final float pDeltaTime, final Physics pParticleSystem) { + + /* update triangle properties -- maybe this is better not done automatically */ + if (AUTO_UPDATE) { + updateProperties(); + } + + mGotHit = false; + for (final Particle myParticle : pParticleSystem.particles()) { + if (!myParticle.fixed()) { + final boolean IGNORE_BOUNDING_BOX = true; + + /* adjust boundingbox width to particle velocity to avoid particle shooting through the boundingbox */ + final Vector3f myTempBoundingBoxScale = new Vector3f(mWorldAxisAlignedBoundingBox.scale); + if (!IGNORE_BOUNDING_BOX) { + if (myParticle.velocity().x > mWorldAxisAlignedBoundingBox.scale.x) { + mWorldAxisAlignedBoundingBox.scale.x = myParticle.velocity().x; + } + if (myParticle.velocity().y > mWorldAxisAlignedBoundingBox.scale.y) { + mWorldAxisAlignedBoundingBox.scale.y = myParticle.velocity().y; + } + if (myParticle.velocity().z > mWorldAxisAlignedBoundingBox.scale.z) { + mWorldAxisAlignedBoundingBox.scale.z = myParticle.velocity().z; + } + } + + /* only test if in bounding box */ + if (IGNORE_BOUNDING_BOX || mathematik.Util.contains(myParticle.position(), mWorldAxisAlignedBoundingBox)) { + final Vector3f mRay; + final int RAY_FROM_VELOCITY = 0; + final int RAY_FROM_NORMAL = 1; + final int RAY_FROM_OLD_POSITION = 2; + final int CREATE_RAY_FROM = RAY_FROM_OLD_POSITION; + + switch (CREATE_RAY_FROM) { + case RAY_FROM_VELOCITY: + mRay = myParticle.velocity(); + break; + case RAY_FROM_NORMAL: + mRay = mathematik.Util.scale(mNormal, -myParticle.velocity().length()); + break; + case RAY_FROM_OLD_POSITION: + mRay = mathematik.Util.sub(myParticle.position(), myParticle.old_position()); + break; + default: + mRay = new Vector3f(1, 0, 0); + break; + } + + if (mRay.isNaN()) { + break; + } + if (mRay.lengthSquared() == 0) { + break; + } + final boolean mSuccess = Intersection.intersectRayTriangle(myParticle.position(), + mRay, + a, b, c, + mIntersectionResult, + true); + /* is particle past plane. */ + if (mSuccess && mIntersectionResult.t <= 0 && mPreviousT > 0) { + mTempPointOfIntersection.set(mRay); + mTempPointOfIntersection.scale(mIntersectionResult.t); + mTempPointOfIntersection.add(myParticle.position()); + myParticle.position().set(mTempPointOfIntersection); + + /* reflect velocity i.e. change direction */ + seperateComponents(myParticle, mNormal); + myParticle.velocity().set(mTempReflectionVector); + + mGotHit = true; + myParticle.tag(true); + markParticle(myParticle); +// mPreviousT = 0.0f; /* ??? */ + + } + if (mSuccess) { + mPreviousT = mIntersectionResult.t; + } else { + mPreviousT = 0.0f; + } + + } + + /* reset boundingbox scale */ + if (!IGNORE_BOUNDING_BOX) { + mWorldAxisAlignedBoundingBox.scale.set(myTempBoundingBoxScale); + } + } + } + } + + protected void markParticle(Particle pParticle) { + } + + public boolean hit() { + return mGotHit; + } + + public void coefficientofrestitution(float pCoefficientOfRestitution) { + mCoefficientOfRestitution = pCoefficientOfRestitution; + } + + public float coefficientofrestitution() { + return mCoefficientOfRestitution; + } + + private void seperateComponents(Particle pParticle, Vector3f pNormal) { + /* normal */ + mTempNormalComponent.set(pNormal); + mTempNormalComponent.scale(pNormal.dot(pParticle.velocity())); + /* tangent */ + mTempTangentComponent.sub(pParticle.velocity(), mTempNormalComponent); + /* negate normal */ + mTempNormalComponent.scale(-mCoefficientOfRestitution); + /* set reflection vector */ + mTempReflectionVector.add(mTempTangentComponent, mTempNormalComponent); + } + + public Vector3f normal() { + return mNormal; + } + + public boolean dead() { + return false; + } + + public boolean active() { + return mActive; + } + + public void active(boolean pActiveState) { + mActive = pActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/TriangleDeflectorIndexed.java b/day3/libs/teilchen/src/teilchen/force/TriangleDeflectorIndexed.java new file mode 100755 index 0000000..6c546bb --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/TriangleDeflectorIndexed.java @@ -0,0 +1,40 @@ +package teilchen.force; + + +public class TriangleDeflectorIndexed + extends TriangleDeflector { + + private final int a_index; + + private final int b_index; + + private final int c_index; + + private final float[] _myVertices; + + public TriangleDeflectorIndexed(float[] theVertices, int theA, int theB, int theC) { + super(); + _myVertices = theVertices; + a_index = theA; + b_index = theB; + c_index = theC; + updateProperties(); + } + + public void updateProperties() { + updateVertices(); + super.updateProperties(); + } + + private void updateVertices() { + a().set(_myVertices[a_index + 0], + _myVertices[a_index + 1], + _myVertices[a_index + 2]); + b().set(_myVertices[b_index + 0], + _myVertices[b_index + 1], + _myVertices[b_index + 2]); + c().set(_myVertices[c_index + 0], + _myVertices[c_index + 1], + _myVertices[c_index + 2]); + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/ViscousDrag.java b/day3/libs/teilchen/src/teilchen/force/ViscousDrag.java new file mode 100755 index 0000000..a7877d1 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/ViscousDrag.java @@ -0,0 +1,72 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force; + + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.integration.Verlet; + + +public class ViscousDrag + implements IForce { + + public float coefficient; + + private boolean _myActive; + + public ViscousDrag(float theCoefficient) { + coefficient = theCoefficient; + _myActive = true; + } + + public ViscousDrag() { + this(1.0f); + } + + public final void apply(final float theDeltaTime, final Physics theParticleSystem) { + if (theParticleSystem.getIntegrator() instanceof Verlet) { + return; + } + if (coefficient != 0) { + for (final Particle myParticle : theParticleSystem.particles()) { + if (!myParticle.fixed()) { + myParticle.force().add(myParticle.velocity().x * -coefficient, + myParticle.velocity().y * -coefficient, + myParticle.velocity().z * -coefficient); + } + } + } + } + + public boolean dead() { + return false; + } + + public boolean active() { + return _myActive; + } + + public void active(boolean theActiveState) { + _myActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/flowfield/FlowField.java b/day3/libs/teilchen/src/teilchen/force/flowfield/FlowField.java new file mode 100755 index 0000000..d84e899 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/flowfield/FlowField.java @@ -0,0 +1,503 @@ +package teilchen.force.flowfield; + + +import java.util.Vector; +import mathematik.TransformMatrix4f; +import mathematik.Vector3f; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.IForce; + + +/* + * todo: + * make density switchable + * clean redundancies + */ +public class FlowField implements IForce { + + public int n = 100; // The size of the calculation grid + + private int _myGridSize = n + 2; // Extra grid space for boundary + + private int _myPixelSize = 10; // The size of each grid square on the screen + + // I unravelled the 1D arrays from Jos Stam's paper back to 2D arrays, as we don't have compile time macros in Java... + private float[][] u = new float[_myGridSize][_myGridSize]; + + private float[][] v = new float[_myGridSize][_myGridSize]; + + private float[][] uPrev = new float[_myGridSize][_myGridSize]; + + private float[][] vPrev = new float[_myGridSize][_myGridSize]; + + private float[][] dens = new float[_myGridSize][_myGridSize]; + + private float[][] densPrev = new float[_myGridSize][_myGridSize]; + + private float viscosity = 0.0001f; // Viscosity of fluid + + public float deltatime = 0.2f; // Rate of change + + private float diff = 0.0001f; // Degree of diffusion of density over time + + private int densityBrushSize = n / 10; // Size of the density area applied with the mouse + + public int velocityBrushSize = n / 20; // Ditto velocity + + private int lineSpacing = n / n; // Spacing between velocity and normal lines + + public boolean showHair = true; + + public boolean showVelocity = true; + + public boolean calculateDensity = true; + + public boolean calculateVelocity = true; + + private Vector3f _myScale; + + private TransformMatrix4f _myTransform; + + private Vector _myForces; + + public float forceScale = 1; + + public FlowField() { + n = 100; + init(); + } + + public FlowField(int theResolutionX, int theResolutionY, Vector3f theScale) { + n = theResolutionX; + init(); + _myScale.set(theScale); + } + + private void init() { + _myScale = new Vector3f(); + _myTransform = new TransformMatrix4f(); + _myForces = new Vector(); + _myGridSize = n + 2; + u = new float[_myGridSize][_myGridSize]; + v = new float[_myGridSize][_myGridSize]; + uPrev = new float[_myGridSize][_myGridSize]; + vPrev = new float[_myGridSize][_myGridSize]; + dens = new float[_myGridSize][_myGridSize]; + densPrev = new float[_myGridSize][_myGridSize]; + } + + public void reset() { + initVelocity(); + initDensity(); + } + + public void addForce(FlowFieldForce theForce) { + _myForces.add(theForce); + } + + private void updateForces() { + for (FlowFieldForce myForce : _myForces) { + myForce.applyForce(this); + } + } + + public Vector forces() { + return _myForces; + } + + public void setForce() { + initField(densPrev); + initField(uPrev); + initField(vPrev); + } + + public float[][] u() { + return u; + } + + public float[][] v() { + return v; + } + +//float[][] field, int x, int y, float s, float r + public void setForceArea(float[][] field, int x, int y, float s, float r) { + for (int i = (int) (clamp(x - r, 1, n)); i <= (int) (clamp(x + r, 1, n)); i++) { + int dx = x - i; + for (int j = (int) (clamp(y - r, 1, n)); j <= (int) (clamp(y + r, 1, n)); j++) { + int dy = y - j; + float f = 1 - ((float) Math.sqrt(dx * dx + dy * dy) / r); + field[i][j] += clamp(f, 0, 1) * s; + } + } + } + + public void setForce(float theX, float theY, Vector3f theStrength, float theRadius) { + int x = (int) (theX / _myScale.x * n); + int y = (int) (theY / _myScale.y * n); + for (int i = (int) (clamp(x - theRadius, 1, n)); i <= (int) (clamp(x + theRadius, 1, n)); i++) { + int dx = x - i; + for (int j = (int) (clamp(y - theRadius, 1, n)); j <= (int) (clamp(y + theRadius, 1, n)); j++) { + int dy = y - j; + float f = 1 - ((float) Math.sqrt(dx * dx + dy * dy) / theRadius); + u[i][j] += clamp(f, 0, 1) * theStrength.x; + v[i][j] += clamp(f, 0, 1) * theStrength.y; + } + } + } + +//public void calculateVelocity(float[][] u, float[][] v, float[][] u0, float[][] v0, float visc, float dt + public void calculateVelocity() { + + addSource(u, uPrev, deltatime); + addSource(v, vPrev, deltatime); + + float[][] tmp; + tmp = u; + u = uPrev; + uPrev = tmp; + tmp = v; + v = vPrev; + vPrev = tmp; + + diffuse(1, u, uPrev, viscosity, deltatime); + diffuse(2, v, vPrev, viscosity, deltatime); + + project(u, v, uPrev, vPrev); + + tmp = u; + u = uPrev; + uPrev = tmp; + tmp = v; + v = vPrev; + vPrev = tmp; + + advect(1, u, uPrev, uPrev, vPrev, deltatime); + advect(2, v, vPrev, uPrev, vPrev, deltatime); + + project(u, v, uPrev, vPrev); + } + +// public void calculateDensity(float[][] x, float[][] x0, float[][] u, float[][] v, float diff, float dt) { + public void calculateDensity() { + float[][] tmp; + + addSource(dens, densPrev, deltatime); + tmp = dens; + dens = densPrev; + densPrev = tmp; + diffuse(0, dens, densPrev, diff, deltatime); + tmp = dens; + dens = densPrev; + densPrev = tmp; + advect(0, dens, densPrev, u, v, deltatime); + } + + public void loop(final float theDeltaTime) { + updateForces(); + setForce(); + deltatime = theDeltaTime; + + if (calculateVelocity) { + calculateVelocity(); + } + if (calculateDensity) { + calculateDensity(); + } + } + +// public void draw(GLContext theRenderContext) { +// GL gl = ( theRenderContext).gl; +// float vu; +// float vv; +// +// gl.glPushMatrix(); +// JoglUtil.applyTransform(gl, +// _myTransformMode, +// transform, +// rotation, +// scale); +// +// material.begin(theRenderContext); +// +// for (int y = 1; y <= n; y++) { +// for (int x = 1; x <= n; x++) { +// if (showHair) { +// if ((x % lineSpacing) == 0 && (y % lineSpacing) == 0) { +// +// float myRatioX = x / (float) n; +// float myRatioY = y / (float) n; +// vu = u[x][y]; +// vv = v[x][y]; +// Vector2f myDirection = new Vector2f(vu, vv); +// float VIEW_VECTOR_LENGTH = myDirection.length(); +// float myAlpha = material.color.a * VIEW_VECTOR_LENGTH; +// gl.glLineWidth(10); +// +// float myTargetLength = VIEW_VECTOR_LENGTH; +// +// gl.glBegin(GL.GL_LINES); +// gl.glColor4f(material.color.r, material.color.g, material.color.b, 0); +// gl.glVertex3f(myRatioX, +// myRatioY, +// 0); +// +// myTargetLength = VIEW_VECTOR_LENGTH * 0.05f; +// gl.glColor4f(material.color.r, material.color.g, material.color.b, myAlpha); +// gl.glVertex3f(myRatioX + vu * myTargetLength, +// myRatioY + vv * myTargetLength, +// 0); +// gl.glEnd(); +// +// gl.glBegin(GL.GL_LINES); +// gl.glColor4f(material.color.r, material.color.g, material.color.b, myAlpha); +// gl.glVertex3f(myRatioX + vu * myTargetLength, +// myRatioY + vv * myTargetLength, +// 0); +// +// myTargetLength = VIEW_VECTOR_LENGTH * 0.6f; +// gl.glColor4f(material.color.r, material.color.g, material.color.b, 0); +// gl.glVertex3f(myRatioX + vu * myTargetLength, +// myRatioY + vv * myTargetLength, +// 0); +// gl.glEnd(); +// } +// } +// if (showVelocity) { +// if ((x % lineSpacing) == 0 && (y % lineSpacing) == 0) { +// +// float myRatioX = x / (float) n; +// float myRatioY = y / (float) n; +// vu = u[x][y]; +// vv = v[x][y]; +// Vector2f myDirection = new Vector2f(vu, vv); +// myDirection.normalize(); +// myDirection.scale(3); +// +// gl.glBegin(GL.GL_LINES); +// gl.glVertex3f(myRatioX, +// myRatioY, +// 0); +// gl.glVertex3f(myRatioX + vu, +// myRatioY + vv, +// 0); +// gl.glEnd(); +// } +// } +// } +// } +// gl.glPopMatrix(); +// material.end(theRenderContext); +// } + public Vector3f scale() { + return _myScale; + } + + public TransformMatrix4f transform() { + return _myTransform; + } + + public Vector3f position() { + return _myTransform.translation; + } + + private float clamp(float f, float minf, float maxf) { + return Math.max(Math.min(f, maxf), minf); + } + + private void initField(float[][] f) { + for (int i = 0; i < _myGridSize; i++) { + for (int j = 0; j < _myGridSize; j++) { + f[i][j] = 0.0f; + } + } + } + + private void initVelocity() { + initField(u); + initField(v); + initField(uPrev); + initField(vPrev); + } + + private void initDensity() { + initField(dens); + initField(densPrev); + } + + public Vector3f getForce(Vector3f thePosition) { + Vector3f myDeltaPos = new Vector3f(thePosition); + myDeltaPos.sub(position()); + float myRatioX = myDeltaPos.x / _myScale.x; + float myRatioY = myDeltaPos.y / _myScale.y; + if (myRatioX >= 0 && myRatioX <= 1 && myRatioY >= 0 && myRatioY <= 1) { + int myIndexX = (int) (myRatioX * (u.length - 1)); + int myIndexY = (int) (myRatioY * (v.length - 1)); + if (u[myIndexX] == null || v[myIndexY] == null) { + System.out.println(myIndexX + " " + myIndexY); + } + Vector3f myForce = new Vector3f(u()[myIndexX][myIndexY], v()[myIndexX][myIndexY], 0); + return myForce; + } + return new Vector3f(); + } + + private void addSource(float[][] x, float[][] s, float dt) { + for (int i = 0; i < _myGridSize; i++) { + for (int j = 0; j < _myGridSize; j++) { + x[i][j] += s[i][j] * dt; + } + } + } + + private void setBnd(int b, float[][] x) { +// for (int i = 1; i <= n; i++) { +// if (b == 1) { +// x[0][i] = -x[1][i]; +// } else { +// x[0][i] = x[1][i]; +// } +// if (b == 1) { +// x[n + 1][i] = -x[n][i]; +// } else { +// x[n + 1][i] = x[n][i]; +// } +// if (b == 2) { +// x[i][0] = -x[i][1]; +// } else { +// x[i][0] = x[i][1]; +// } +// if (b == 2) { +// x[i][n + 1] = -x[i][n]; +// } else { +// x[i][n + 1] = x[i][n]; +// } +// +// } +// x[0][0] = 0.5f * (x[1][0] + x[0][1]); +// x[0][n + 1] = 0.5f * (x[1][n + 1] + x[0][n]); +// x[n + 1][0] = 0.5f * (x[n][0] + x[n + 1][1]); +// x[n + 1][n + 1] = 0.5f * (x[n][n + 1] + x[n + 1][n]); + } + + private void diffuse(int b, float[][] x, float[][] x0, float diff, float dt) { + + int i, j, k; + float a = dt * diff * n * n; + + /* todo: why is it '20' */ +// for (k = 0; k < 20; k++) { + for (i = 1; i <= n; i++) { + for (j = 1; j <= n; j++) { + x[i][j] = (x0[i][j] + a * (x[i - 1][j] + x[i + 1][j] + x[i][j - 1] + x[i][j + 1])) / (1 + 4 * a); + } + } + setBnd(b, x); +// } + } + + private void project(float[][] u, float[][] v, float[][] p, float[][] div) { + int i; + int j; + int k; + float h; + + h = 1.0f / n; + /* todo check loop */ + for (i = 1; i <= n; i++) { + for (j = 1; j <= n; j++) { + div[i][j] = -0.5f * h * (u[i + 1][j] - u[i - 1][j] + v[i][j + 1] - v[i][j - 1]); + p[i][j] = 0; + } + } + setBnd(0, div); + setBnd(0, p); + + /* todo: why is it '20' */ + for (k = 0; k < 20; k++) { + for (i = 1; i <= n; i++) { + for (j = 1; j <= n; j++) { + p[i][j] = (div[i][j] + p[i - 1][j] + p[i + 1][j] + p[i][j - 1] + p[i][j + 1]) / 4; + } + } + setBnd(0, p); + } + + /* todo check loop */ + for (i = 1; i <= n; i++) { + for (j = 1; j <= n; j++) { + u[i][j] -= 0.5f * (p[i + 1][j] - p[i - 1][j]) / h; + v[i][j] -= 0.5f * (p[i][j + 1] - p[i][j - 1]) / h; + } + } + setBnd(1, u); + setBnd(2, v); + } + + private void advect(int b, float[][] d, float[][] d0, float[][] u, float[][] v, float dt) { + int i; + int j; + int i0; + int j0; + int i1; + int j1; + float x; + float y; + float s0; + float t0; + float s1; + float t1; + float dt0; + + dt0 = dt * n; + /* todo check loop */ + for (i = 1; i <= n; i++) { + for (j = 1; j <= n; j++) { + x = i - dt0 * u[i][j]; + y = j - dt0 * v[i][j]; + + x = Math.max(0.5f, x); + x = Math.min(n + 0.5f, x); + + i0 = (int) Math.floor(x); + i1 = i0 + 1; + + y = Math.max(0.5f, y); + y = Math.min(n + 0.5f, y); + + j0 = (int) Math.floor(y); + j1 = j0 + 1; + + s1 = x - i0; + s0 = 1 - s1; + t1 = y - j0; + t0 = 1 - t1; + + d[i][j] = s0 * (t0 * d0[i0][j0] + t1 * d0[i0][j1]) + + s1 * (t0 * d0[i1][j0] + t1 * d0[i1][j1]); + } + } + setBnd(b, d); + } + + public void apply(float theDeltaTime, Physics theParticleSystem) { + for (Particle myParticle : theParticleSystem.particles()) { + if (!myParticle.fixed()) { + Vector3f myForce = getForce(myParticle.position()); + myForce.scale(forceScale); + myParticle.force().add(myForce); + } + } + } + + public boolean dead() { + return false; + } + + public boolean active() { + return true; + } + + public void active(boolean theActiveState) { + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForce.java b/day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForce.java new file mode 100755 index 0000000..314fff3 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForce.java @@ -0,0 +1,37 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force.flowfield; + + +import mathematik.Vector3f; + + +public abstract class FlowFieldForce { + + public Vector3f strength = new Vector3f(); + + public float range; + + public Vector3f position = new Vector3f(); + + public abstract void applyForce(FlowField theField); +} diff --git a/day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForceMOUSE.java b/day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForceMOUSE.java new file mode 100755 index 0000000..0f65833 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/flowfield/FlowFieldForceMOUSE.java @@ -0,0 +1,46 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force.flowfield; + + +import mathematik.Vector3f; + + +public class FlowFieldForceMOUSE extends FlowFieldForce { + + private Vector3f _myOldPosition; + + public FlowFieldForceMOUSE() { + _myOldPosition = new Vector3f(); + } + + public void setPosition(Vector3f thePosition) { + position.set(thePosition); + strength.set(thePosition); + strength.sub(_myOldPosition); + _myOldPosition.set(position); + } + + public void applyForce(FlowField theField) { + theField.setForce(position.x, position.y, strength, range); + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorField.java b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorField.java new file mode 100755 index 0000000..a5015b1 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorField.java @@ -0,0 +1,212 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force.vectorfield; + + +import mathematik.Vector3f; +import mathematik.Vector3i; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.VectorfieldParticle; +import teilchen.force.IForce; + + +public class VectorField + implements IForce { + + private VectorFieldUnit[][][] _myField; + + public float strength; + + private Vector3f _myPosition; + + private Vector3f _myScale; + + private boolean _myActive; + + public VectorField(VectorFieldGenerator theGenerator) { + /* init fields */ + strength = 1; + _myPosition = new Vector3f(); + _myScale = new Vector3f(1f, 1f, 1f); + _myField = theGenerator.data(); + _myActive = true; + } + + public VectorFieldUnit[][][] data() { + return _myField; + } + + public Vector3f getPosition() { + return _myPosition; + } + + public void setPosition(final Vector3f thePosition) { + _myPosition.set(thePosition); + for (int x = 0; x < _myField.length; x++) { + for (int y = 0; y < _myField[x].length; y++) { + for (int z = 0; z < _myField[x][y].length; z++) { + Vector3f myPosition = new Vector3f(x + * (_myScale.x / (float) _myField.length), + y * (_myScale.y / (float) _myField[x].length), + z * (_myScale.z / (float) _myField[x][y].length)); + _myField[x][y][z].getPosition().set(myPosition); + _myField[x][y][z].getPosition().add(thePosition); + } + } + } + } + + public final Vector3f getScale() { + return _myScale; + } + + public void setScale(final Vector3f theScale) { + _myScale.set(theScale); + for (int x = 0; x < _myField.length; x++) { + for (int y = 0; y < _myField[x].length; y++) { + for (int z = 0; z < _myField[x][y].length; z++) { + Vector3f myUnitScale = new Vector3f(_myScale.x + / (float) _myField.length, + _myScale.y / (float) _myField[x].length, + _myScale.z / (float) _myField[x][y].length); + _myField[x][y][z].setScale(myUnitScale); + Vector3f myPosition = new Vector3f(x + * (_myScale.x / (float) _myField.length), + y * (_myScale.y / (float) _myField[x].length), + z * (_myScale.z / (float) _myField[x][y].length)); + myPosition.add(_myPosition); + _myField[x][y][z].setPosition(myPosition); + } + } + } + } + +// public void setScale(final Vector3f theScale) { +// _myScale.set(theScale); +// for (int x = 0; x < _myField.length; x++) { +// for (int y = 0; y < _myField[x].length; y++) { +// for (int z = 0; z < _myField[x][y].length; z++) { +// Vector3f myUnitScale = new Vector3f(_myScale.x, +// _myScale.y, +// _myScale.z); +// _myField[x][y][z].setScale(myUnitScale); +// Vector3f myPosition = new Vector3f(x * +// (_myScale.x), +// y * (_myScale.y), +// z * (_myScale.z)); +// myPosition.add(_myPosition); +// _myField[x][y][z].setPosition(myPosition); +// } +// } +// } +//} + /* IForce */ + private Vector3f force(float theDeltaTime, VectorfieldParticle theParticle) { + if (isInBoundingBox(theParticle)) { + Vector3i myUnit = checkIfIsInside(theParticle, 1); + if (myUnit != null) { + Vector3f myAcceleration = new Vector3f(_myField[myUnit.x][ + myUnit.y][ + myUnit.z]. + getAcceleration()); + myAcceleration.scale(strength); + theParticle.setLastUnit(myUnit); + return myAcceleration; + } + for (int x = 0; x < _myField.length; x++) { + for (int y = 0; y < _myField[x].length; y++) { + for (int z = 0; z < _myField[x][y].length; z++) { + if (_myField[x][y][z].isInside(theParticle.position())) { + Vector3f myAcceleration = new Vector3f(_myField[x][ + y][z]. + getAcceleration()); + myAcceleration.scale(strength); + theParticle.setLastUnit(new Vector3i(x, y, z)); + return myAcceleration; + } + } + } + } + } + return new Vector3f(); + } + + private boolean isInBoundingBox(VectorfieldParticle theParticle) { + if (theParticle.position().x >= _myPosition.x + && theParticle.position().x <= _myPosition.x + _myScale.x + && theParticle.position().y >= _myPosition.y + && theParticle.position().y <= _myPosition.y + _myScale.y + && theParticle.position().z >= _myPosition.z + && theParticle.position().z <= _myPosition.z + _myScale.z) { + return true; + } + return false; + } + + private Vector3i checkIfIsInside(VectorfieldParticle theParticle, + int theRadius) { + Vector3i myUnit = new Vector3i(theParticle.getLastUnit()); + for (int x = myUnit.x - theRadius; x < myUnit.x + theRadius; x++) { + for (int y = myUnit.y - theRadius; y < myUnit.y + theRadius; y++) { + for (int z = myUnit.z - theRadius; z < myUnit.z + theRadius; z++) { + if (x >= 0 && x < _myField.length + && y >= 0 && y < _myField[x].length + && z >= 0 && z < _myField[x][y].length) { + if (_myField[x][y][z].isInside(theParticle.position())) { + return new Vector3i(x, y, z); + } + } + } + } + } + return null; + } + + public void apply(final float theDeltaTime, + final Physics theParticleSystem) { + /** + * @todo clean up force method + */ + for (final Particle myParticle : theParticleSystem.particles()) { + if (!myParticle.fixed()) { + if (myParticle instanceof VectorfieldParticle) { + myParticle.force().add(force(theDeltaTime, + (VectorfieldParticle) myParticle)); + } + } + } + } + + public boolean dead() { + return false; + } + + public boolean active() { + return _myActive; + } + + public void active(boolean theActiveState) { + _myActive = theActiveState; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGenerator.java b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGenerator.java new file mode 100755 index 0000000..a8345af --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGenerator.java @@ -0,0 +1,28 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force.vectorfield; + + +public interface VectorFieldGenerator { + + VectorFieldUnit[][][] data(); +} diff --git a/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorAVERAGEUNITS.java b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorAVERAGEUNITS.java new file mode 100755 index 0000000..59eff58 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorAVERAGEUNITS.java @@ -0,0 +1,87 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force.vectorfield; + + +import mathematik.Vector3f; + + +public class VectorFieldGeneratorAVERAGEUNITS + implements VectorFieldGenerator { + + private VectorFieldUnit[][][] _myField; + + public VectorFieldGeneratorAVERAGEUNITS(int theSizeX, + int theSizeY, + int theSizeZ, + Vector3f[] theCoordinats, + Vector3f[] theAccelerations) { + /* populate field */ + _myField = new VectorFieldUnit[theSizeX][theSizeY][theSizeZ]; + for (int x = 0; x < theSizeX; x++) { + for (int y = 0; y < theSizeY; y++) { + for (int z = 0; z < theSizeZ; z++) { + Vector3f myPosition = new Vector3f(x + * (1f / (float) theSizeX), + y * (1f / (float) theSizeY), + z * (1f / (float) theSizeZ)); + Vector3f myScale = new Vector3f(1f + / (float) theSizeX, + 1f / (float) theSizeY, + 1f / (float) theSizeZ); + Vector3f myAcceleration = new Vector3f(1f, 1f, 1f); + VectorFieldUnit myUnit = new VectorFieldUnit(myPosition, + myScale, + myAcceleration); + _myField[x][y][z] = myUnit; + } + } + } + + float myMaxDistance = (float) Math.sqrt(2); + for (int x = 0; x < theSizeX; x++) { + for (int y = 0; y < theSizeY; y++) { + for (int z = 0; z < theSizeZ; z++) { + Vector3f myAcceleration = new Vector3f(); + VectorFieldUnit myUnit = _myField[x][y][z]; + for (int i = 0; i < theCoordinats.length; i++) { + Vector3f myDistance = new Vector3f(); + VectorFieldUnit myOtherUnit = _myField[ (int) theCoordinats[i].x][ (int) theCoordinats[i].y][ (int) theCoordinats[i].z]; + myDistance.set(myOtherUnit.getPosition()); + myDistance.sub(myUnit.getPosition()); + float myRatio = myMaxDistance - myDistance.length(); + Vector3f myTempAcceleration = new Vector3f(); + myTempAcceleration.set(theAccelerations[i]); + myTempAcceleration.scale(myRatio); + myAcceleration.add(myTempAcceleration); + } + myAcceleration.scale(1f / myMaxDistance); + myUnit.setAcceleration(myAcceleration); + } + } + } + } + + public VectorFieldUnit[][][] data() { + return _myField; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorRANDOM.java b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorRANDOM.java new file mode 100755 index 0000000..a9e3b29 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldGeneratorRANDOM.java @@ -0,0 +1,65 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force.vectorfield; + + +import mathematik.Vector3f; + + +public class VectorFieldGeneratorRANDOM + implements VectorFieldGenerator { + + private VectorFieldUnit[][][] _myField; + + public VectorFieldGeneratorRANDOM(int theSizeX, + int theSizeY, + int theSizeZ) { + _myField = new VectorFieldUnit[theSizeX][theSizeY][theSizeZ]; + for (int x = 0; x < theSizeX; x++) { + for (int y = 0; y < theSizeY; y++) { + for (int z = 0; z < theSizeZ; z++) { + Vector3f myPosition = new Vector3f(x + * (1f / (float) theSizeX), + y * (1f / (float) theSizeY), + z * (1f / (float) theSizeZ)); + Vector3f myScale = new Vector3f(1f + / (float) theSizeX, + 1f / (float) theSizeY, + 1f / (float) theSizeZ); + Vector3f myAcceleration = new Vector3f((float) Math. + random() - 0.5f, + (float) Math.random() - 0.5f, + (float) Math.random() - 0.5f); + myAcceleration.normalize(); + VectorFieldUnit myUnit = new VectorFieldUnit(myPosition, + myScale, + myAcceleration); + _myField[x][y][z] = myUnit; + } + } + } + } + + public VectorFieldUnit[][][] data() { + return _myField; + } +} diff --git a/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldUnit.java b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldUnit.java new file mode 100755 index 0000000..860f6ef --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/force/vectorfield/VectorFieldUnit.java @@ -0,0 +1,80 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.force.vectorfield; + + +import mathematik.Vector3f; + + +public class VectorFieldUnit { + + private Vector3f _myPosition; + + private Vector3f _myScale; + + private Vector3f _myAcceleration; + + public VectorFieldUnit(final Vector3f thePosition, + final Vector3f theScale, + final Vector3f theAcceleration) { + _myPosition = new Vector3f(thePosition); + _myScale = new Vector3f(theScale); + _myAcceleration = new Vector3f(theAcceleration); + } + + public void setAcceleration(final Vector3f theAcceleration) { + _myAcceleration.set(theAcceleration); + } + + public Vector3f getAcceleration() { + return _myAcceleration; + } + + public Vector3f getPosition() { + return _myPosition; + } + + public void setPosition(final Vector3f thePosition) { + _myPosition.set(thePosition); + } + + public Vector3f getScale() { + return _myScale; + } + + public void setScale(final Vector3f theScale) { + _myScale.set(theScale); + } + + public boolean isInside(final Vector3f thePosition) { + if (thePosition.x >= _myPosition.x + && thePosition.x < _myPosition.x + _myScale.x + && thePosition.y >= _myPosition.y + && thePosition.y < _myPosition.y + _myScale.y + && thePosition.z >= _myPosition.z + && thePosition.z < _myPosition.z + _myScale.z) { + return true; + } else { + return false; + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/integration/Derivate3f.java b/day3/libs/teilchen/src/teilchen/integration/Derivate3f.java new file mode 100755 index 0000000..a4f627d --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/integration/Derivate3f.java @@ -0,0 +1,39 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.integration; + + +public final class Derivate3f { + + public float px; + + public float py; + + public float pz; + + public float vx; + + public float vy; + + public float vz; + +} diff --git a/day3/libs/teilchen/src/teilchen/integration/Euler.java b/day3/libs/teilchen/src/teilchen/integration/Euler.java new file mode 100755 index 0000000..62faaa6 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/integration/Euler.java @@ -0,0 +1,103 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.integration; + + +import java.util.Iterator; + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +public class Euler + implements IIntegrator { + + private final Vector3f mTemp1; + + private final Vector3f mTemp2; + + public Euler() { + mTemp1 = new Vector3f(); + mTemp2 = new Vector3f(); + } + + public void step(final float theDeltaTime, final Physics theParticleSystem) { + + theParticleSystem.applyForces(theDeltaTime); + + synchronized (theParticleSystem.particles()) { + final Iterator myIterator = theParticleSystem.particles().iterator(); + while (myIterator.hasNext()) { + final Particle myParticle = myIterator.next(); + if (!myParticle.fixed()) { + integrate(theDeltaTime, myParticle); + } + } + } + } + + private void integrate(final float theDeltaTime, final Particle theParticle) { + mTemp1.set(theParticle.force()); + mTemp1.scale(theDeltaTime / theParticle.mass()); + + mTemp2.set(theParticle.velocity()); + mTemp2.scale(theDeltaTime); + + theParticle.velocity().add(mTemp1); + theParticle.position().add(mTemp2); + } +} + +/* this version scales better with the other integrators but is definitly slower */ +//import java.util.Vector; +// +//import particles.IParticle; +//import particles.ParticleSystem; +// +// +//public class Euler +// implements IIntegrator { +// +// private final Vector myK1 = new Vector (); +// +// public void step(final float theDeltaTime, final ParticleSystem theParticleSystem) { +// +// Util.checkContainerSize(theParticleSystem.particles().size(), myK1, Derivate3f.class); +// +// theParticleSystem.applyForces(theDeltaTime); +// Util.calculateDerivatives(theParticleSystem.particles(), myK1); +// for (int i = 0; i < theParticleSystem.particles().size(); i++) { +// Particle myParticle = theParticleSystem.particles().get(i); +// if (!myParticle.fixed()) { +// myParticle.position().x += myK1.get(i).px * theDeltaTime; +// myParticle.position().y += myK1.get(i).py * theDeltaTime; +// myParticle.position().z += myK1.get(i).pz * theDeltaTime; +// myParticle.velocity().x += myK1.get(i).vx * theDeltaTime; +// myParticle.velocity().y += myK1.get(i).vy * theDeltaTime; +// myParticle.velocity().z += myK1.get(i).vz * theDeltaTime; +// } +// } +// } +//} + diff --git a/day3/libs/teilchen/src/teilchen/integration/IIntegrator.java b/day3/libs/teilchen/src/teilchen/integration/IIntegrator.java new file mode 100755 index 0000000..7c170ad --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/integration/IIntegrator.java @@ -0,0 +1,110 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.integration; + + +import teilchen.Physics; + + +public interface IIntegrator { + + void step(final float theDeltaTime, final Physics theParticleSystem); +} +/** + * from paul bourke http://astronomy.swin.edu.au/~pbourke/modelling/solver/ + */ + +/* + // A 1st order 1D DE solver. + // Assumes the function is not time dependent. + // Parameters + // h - step size + // y0 - last value + // method - algorithm to use [0,5] + // fcn - evaluate the derivative of the field + + double Solver1D(double h, double y0, int method, double ( * fcn) (double)) { + double ynew; + double k1, k2, k3, k4, k5, k6; + + switch (method) { + case 0: // Euler method + k1 = h * ( * fcn) (y0); + ynew = y0 + k1; + break; + case 1: // Modified Euler + k1 = h * ( * fcn) (y0); + k2 = h * ( * fcn) (y0 + k1); + ynew = y0 + (k1 + k2) / 2; + break; + case 2: // Heuns method + k1 = h * ( * fcn) (y0); + k2 = h * ( * fcn) (y0 + 2 * k1 / 3); + ynew = y0 + k1 / 4 + 3 * k2 / 4; + break; + case 3: // Midpoint + k1 = h * ( * fcn) (y0); + k2 = h * ( * fcn) (y0 + k1 / 2); + ynew = y0 + k2; + break; + case 4: // 4'th order Runge-kutta + k1 = h * ( * fcn) (y0); + k2 = h * ( * fcn) (y0 + k1 / 2); + k3 = h * ( * fcn) (y0 + k2 / 2); + k4 = h * ( * fcn) (y0 + k3); + ynew = y0 + k1 / 6 + k2 / 3 + k3 / 3 + k4 / 6; + break; + case 5: // England 4'th order, six stage + k1 = h * ( * fcn) (y0); + k2 = h * ( * fcn) (y0 + k1 / 2); + k3 = h * ( * fcn) (y0 + (k1 + k2) / 4); + k4 = h * ( * fcn) (y0 - k2 + 2 * k3); + k5 = h * ( * fcn) (y0 + (7 * k1 + 10 * k2 + k4) / 27); + k6 = h * ( * fcn) (y0 + (28 * k1 - 125 * k2 + 546 * k3 + 54 * k4 - 378 * k5) / 625); + ynew = y0 + k1 / 6 + 4 * k3 / 6 + k4 / 6; + break; + } + + return (ynew); + } + */ + +/* example C program */ + +/* + int main(int argc, char * * argv) { + double t; + double dt = 0.1; // Step size + double T = 100; // Simulation duration + double y = 1; // Initial value + + for (t = 0; t < T; t += dt) { + printf("%g %g\n", t, y); + y = Solver1D(dt, y, MIDPOINT, (double ( * ) (double)) EvalFcn); + } + } + + // Sample derivative function + double EvalFcn(double x) { + return ( -0.05 * x); + } + */ diff --git a/day3/libs/teilchen/src/teilchen/integration/Midpoint.java b/day3/libs/teilchen/src/teilchen/integration/Midpoint.java new file mode 100755 index 0000000..8015e47 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/integration/Midpoint.java @@ -0,0 +1,70 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.integration; + + +import java.util.Vector; + +import teilchen.Particle; +import teilchen.Physics; + + +public class Midpoint + implements IIntegrator { + + private final Vector mK1 = new Vector(); + + public void step(final float theDeltaTime, final Physics theParticleSystem) { + + Util.checkContainerSize(theParticleSystem.particles().size(), mK1, Derivate3f.class); + + /* one */ + theParticleSystem.applyForces(theDeltaTime); + Util.calculateDerivatives(theParticleSystem.particles(), mK1); + for (int i = 0; i < theParticleSystem.particles().size(); i++) { + Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + myParticle.position().x += mK1.get(i).px * theDeltaTime / 2; + myParticle.position().y += mK1.get(i).py * theDeltaTime / 2; + myParticle.position().z += mK1.get(i).pz * theDeltaTime / 2; + myParticle.position().x += mK1.get(i).vx * theDeltaTime / 2; + myParticle.position().y += mK1.get(i).vy * theDeltaTime / 2; + myParticle.position().z += mK1.get(i).vz * theDeltaTime / 2; + } + } + + /* two */ + theParticleSystem.applyForces(theDeltaTime); + Util.calculateDerivatives(theParticleSystem.particles(), mK1); + for (int i = 0; i < theParticleSystem.particles().size(); i++) { + Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + myParticle.position().x += mK1.get(i).px * theDeltaTime; + myParticle.position().y += mK1.get(i).py * theDeltaTime; + myParticle.position().z += mK1.get(i).pz * theDeltaTime; + myParticle.velocity().x += mK1.get(i).vx * theDeltaTime; + myParticle.velocity().y += mK1.get(i).vy * theDeltaTime; + myParticle.velocity().z += mK1.get(i).vz * theDeltaTime; + } + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/integration/RungeKutta.java b/day3/libs/teilchen/src/teilchen/integration/RungeKutta.java new file mode 100755 index 0000000..de062b0 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/integration/RungeKutta.java @@ -0,0 +1,238 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.integration; + + +import java.util.Vector; + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +public class RungeKutta + implements IIntegrator { + + private final Vector mOriginalPositions; + + private final Vector mOriginalVelocities; + + private final Vector mK1Forces; + + private final Vector mK1Velocities; + + private final Vector mK2Forces; + + private final Vector mK2Velocities; + + private final Vector mK3Forces; + + private final Vector mK3Velocities; + + private final Vector mK4Forces; + + private final Vector mK4Velocities; + + public RungeKutta() { + mOriginalPositions = new Vector(); + mOriginalVelocities = new Vector(); + mK1Forces = new Vector(); + mK1Velocities = new Vector(); + mK2Forces = new Vector(); + mK2Velocities = new Vector(); + mK3Forces = new Vector(); + mK3Velocities = new Vector(); + mK4Forces = new Vector(); + mK4Velocities = new Vector(); + } + + public void step(final float theDeltaTime, + final Physics theParticleSystem) { + + final int mySize = theParticleSystem.particles().size(); + Util.checkContainerSize(mySize, mOriginalPositions, Vector3f.class); + Util.checkContainerSize(mySize, mOriginalVelocities, Vector3f.class); + Util.checkContainerSize(mySize, mK1Forces, Vector3f.class); + Util.checkContainerSize(mySize, mK1Velocities, Vector3f.class); + Util.checkContainerSize(mySize, mK2Forces, Vector3f.class); + Util.checkContainerSize(mySize, mK2Velocities, Vector3f.class); + Util.checkContainerSize(mySize, mK3Forces, Vector3f.class); + Util.checkContainerSize(mySize, mK3Velocities, Vector3f.class); + Util.checkContainerSize(mySize, mK4Forces, Vector3f.class); + Util.checkContainerSize(mySize, mK4Velocities, Vector3f.class); + + /* save original position and velocities */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + mOriginalPositions.get(i).set(myParticle.position()); + mOriginalVelocities.get(i).set(myParticle.velocity()); + } + } + + /* get all the k1 values */ + theParticleSystem.applyForces(theDeltaTime); + + /* save the intermediate forces */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + mK1Forces.get(i).set(myParticle.force()); + mK1Velocities.get(i).set(myParticle.velocity()); + } + } + + /* get k2 values */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + final Vector3f originalPosition = mOriginalPositions.get(i); + final Vector3f k1Velocity = mK1Velocities.get(i); + + myParticle.position().x = originalPosition.x + k1Velocity.x * 0.5f * theDeltaTime; + myParticle.position().y = originalPosition.y + k1Velocity.y * 0.5f * theDeltaTime; + myParticle.position().z = originalPosition.z + k1Velocity.z * 0.5f * theDeltaTime; + + final Vector3f originalVelocity = mOriginalVelocities.get(i); + final Vector3f k1Force = mK1Forces.get(i); + + myParticle.velocity().x = originalVelocity.x + k1Force.x * 0.5f * theDeltaTime / myParticle.mass(); + myParticle.velocity().y = originalVelocity.y + k1Force.y * 0.5f * theDeltaTime / myParticle.mass(); + myParticle.velocity().z = originalVelocity.z + k1Force.z * 0.5f * theDeltaTime / myParticle.mass(); + } + } + + theParticleSystem.applyForces(theDeltaTime); + + /* save the intermediate forces */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + mK2Forces.get(i).set(myParticle.force()); + mK2Velocities.get(i).set(myParticle.velocity()); + } + } + + /* get k3 values */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + final Vector3f originalPosition = mOriginalPositions.get(i); + final Vector3f k2Velocity = mK2Velocities.get(i); + + myParticle.position().x = originalPosition.x + k2Velocity.x * 0.5f * theDeltaTime; + myParticle.position().y = originalPosition.y + k2Velocity.y * 0.5f * theDeltaTime; + myParticle.position().z = originalPosition.z + k2Velocity.z * 0.5f * theDeltaTime; + + final Vector3f originalVelocity = mOriginalVelocities.get(i); + final Vector3f k2Force = mK2Forces.get(i); + + myParticle.velocity().x = originalVelocity.x + k2Force.x * 0.5f * theDeltaTime / myParticle.mass(); + myParticle.velocity().y = originalVelocity.y + k2Force.y * 0.5f * theDeltaTime / myParticle.mass(); + myParticle.velocity().z = originalVelocity.z + k2Force.z * 0.5f * theDeltaTime / myParticle.mass(); + } + } + + theParticleSystem.applyForces(theDeltaTime); + + /* save the intermediate forces */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + (mK3Forces.get(i)).set(myParticle.force()); + (mK3Velocities.get(i)).set(myParticle.velocity()); + } + } + + /* get k4 values */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + final Vector3f originalPosition = mOriginalPositions.get(i); + final Vector3f k3Velocity = mK3Velocities.get(i); + + myParticle.position().x = originalPosition.x + k3Velocity.x * theDeltaTime; + myParticle.position().y = originalPosition.y + k3Velocity.y * theDeltaTime; + myParticle.position().z = originalPosition.z + k3Velocity.z * theDeltaTime; + + final Vector3f originalVelocity = mOriginalVelocities.get(i); + final Vector3f k3Force = mK3Forces.get(i); + + myParticle.velocity().x = originalVelocity.x + k3Force.x * theDeltaTime / myParticle.mass(); + myParticle.velocity().y = originalVelocity.y + k3Force.y * theDeltaTime / myParticle.mass(); + myParticle.velocity().z = originalVelocity.z + k3Force.z * theDeltaTime / myParticle.mass(); + } + } + + theParticleSystem.applyForces(theDeltaTime); + + /* save the intermediate forces */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + mK4Forces.get(i).set(myParticle.force()); + mK4Velocities.get(i).set(myParticle.velocity()); + } + } + + /* put them all together and what do you get? */ + for (int i = 0; i < theParticleSystem.particles().size(); ++i) { + final Particle myParticle = theParticleSystem.particles().get(i); + if (!myParticle.fixed()) { + /* update position */ + final Vector3f originalPosition = mOriginalPositions.get(i); + final Vector3f k1Velocity = mK1Velocities.get(i); + final Vector3f k2Velocity = mK2Velocities.get(i); + final Vector3f k3Velocity = mK3Velocities.get(i); + final Vector3f k4Velocity = mK4Velocities.get(i); + + myParticle.position().x = originalPosition.x + + theDeltaTime / 6.0f + * (k1Velocity.x + 2.0f * k2Velocity.x + 2.0f * k3Velocity.x + k4Velocity.x); + myParticle.position().y = originalPosition.y + + theDeltaTime / 6.0f + * (k1Velocity.y + 2.0f * k2Velocity.y + 2.0f * k3Velocity.y + k4Velocity.y); + myParticle.position().z = originalPosition.z + + theDeltaTime / 6.0f + * (k1Velocity.z + 2.0f * k2Velocity.z + 2.0f * k3Velocity.z + k4Velocity.z); + + /* update velocity */ + final Vector3f originalVelocity = mOriginalVelocities.get(i); + final Vector3f k1Force = mK1Forces.get(i); + final Vector3f k2Force = mK2Forces.get(i); + final Vector3f k3Force = mK3Forces.get(i); + final Vector3f k4Force = mK4Forces.get(i); + + myParticle.velocity().x = originalVelocity.x + + theDeltaTime / (6.0f * myParticle.mass()) + * (k1Force.x + 2.0f * k2Force.x + 2.0f * k3Force.x + k4Force.x); + myParticle.velocity().y = originalVelocity.y + + theDeltaTime / (6.0f * myParticle.mass()) + * (k1Force.y + 2.0f * k2Force.y + 2.0f * k3Force.y + k4Force.y); + myParticle.velocity().z = originalVelocity.z + + theDeltaTime / (6.0f * myParticle.mass()) + * (k1Force.z + 2.0f * k2Force.z + 2.0f * k3Force.z + k4Force.z); + } + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/integration/Util.java b/day3/libs/teilchen/src/teilchen/integration/Util.java new file mode 100755 index 0000000..1aa427a --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/integration/Util.java @@ -0,0 +1,62 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.integration; + + +import java.util.List; + +import teilchen.Particle; + + +public final class Util { + + public static final void checkContainerSize(final int theSize, + final List theContainer, + Class theClass) { + final int myDiff = theSize - theContainer.size(); + if (myDiff > 0) { + for (int i = 0; i < myDiff; i++) { + try { + theContainer.add(theClass.newInstance()); + } catch (Exception ex) { + System.err.println(ex); + } + } + } else if (myDiff < 0) { + for (int i = 0; i < myDiff; i++) { + theContainer.remove(myDiff + theSize); + } + } + } + + public static final void calculateDerivatives(final List theParticles, + final List theDerivates) { + for (int i = 0; i < theParticles.size(); i++) { + theDerivates.get(i).px = theParticles.get(i).velocity().x; + theDerivates.get(i).py = theParticles.get(i).velocity().y; + theDerivates.get(i).pz = theParticles.get(i).velocity().z; + theDerivates.get(i).vx = theParticles.get(i).force().x / theParticles.get(i).mass(); + theDerivates.get(i).vy = theParticles.get(i).force().y / theParticles.get(i).mass(); + theDerivates.get(i).vz = theParticles.get(i).force().z / theParticles.get(i).mass(); + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/integration/Verlet.java b/day3/libs/teilchen/src/teilchen/integration/Verlet.java new file mode 100755 index 0000000..9797334 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/integration/Verlet.java @@ -0,0 +1,115 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.integration; + + +import java.util.Iterator; + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; + + +public class Verlet + implements IIntegrator { + + private final Vector3f temp1; + + private final Vector3f temp2; + + private float _myDamping; + + public Verlet() { + this(1.0f); + } + + public Verlet(final float theDamping) { + _myDamping = theDamping; + temp1 = new Vector3f(); + temp2 = new Vector3f(); + } + + public float damping() { + return _myDamping; + } + + public void damping(float theDamping) { + _myDamping = theDamping; + } + + public void step(final float theDeltaTime, final Physics theParticleSystem) { + + theParticleSystem.applyForces(theDeltaTime); + + synchronized (theParticleSystem.particles()) { + final Iterator myIterator = theParticleSystem.particles().iterator(); + while (myIterator.hasNext()) { + final Particle myParticle = myIterator.next(); + if (!myParticle.fixed()) { + integrate(theDeltaTime, myParticle); + } + } + } + } + + private final void integrate(float theDeltaTime, Particle theParticle) { + final Vector3f myOldPosition = new Vector3f(theParticle.position()); + + /* + Physics simulation using Verlet integration + sgreen@nvidia.com 6/2002 + + based on Thomas Jakobsen's "Advanced Character Physics": + http://www.ioi.dk/Homepages/tj/publications/gdc2001.htm + + basic idea: + x' = x + v*dt + v' = v + a*dt + + x' = x + (v + a*dt) * dt + = x + v*dt + a*dt^2 + + v ~= (x - ox) / dt + + x' = x + (x - ox) + a*dt^2 + */ + + /* v ~= (x - ox) / dt */ + theParticle.velocity().sub(theParticle.position(), theParticle.old_position()); + theParticle.velocity().scale(1.0f / theDeltaTime); + + /* x' = x + (x - ox) + a*dt^2 */ + temp1.set(theParticle.force()); + temp1.scale(1.0f / theParticle.mass()); + temp1.scale(theDeltaTime * theDeltaTime); + temp2.sub(theParticle.position(), theParticle.old_position()); + + temp2.scale(_myDamping); + + theParticle.position().add(temp1); + theParticle.position().add(temp2); + + /* --- */ + theParticle.old_position().set(myOldPosition); + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/CollisionManager.java b/day3/libs/teilchen/src/teilchen/util/CollisionManager.java new file mode 100755 index 0000000..d01a044 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/CollisionManager.java @@ -0,0 +1,425 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import java.util.Vector; + +import mathematik.Random; +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Stick; +import teilchen.cubicle.CubicleParticle; +import teilchen.cubicle.CubicleWorld; +import teilchen.cubicle.ICubicleEntity; +import teilchen.force.IForce; +import teilchen.force.Spring; + + +/** + * this manager uses it s own particle system. we could make it more integrated + * by using a shared physic world. this would of course make everthing more + * slower. + */ +public class CollisionManager { + + public boolean HINT_IGNORE_STILL_OR_FIXED = false; + + private float mCollisionSpringConstant; + + private float mCollisionSpringDamping; + + private final Physics mCollisionPhysics; + + private float mMinimumDistance; + + private Vector3f mResolveSamePosition; + + public enum ResolverType { +// COLLISION_STICK, COLLISION_SPRING, + + SPRING, STICK + + } + private final Random mRandom; + + private ResolverType mResolverType; + + private float mCollisionResolverIntervalCounter = 1; + + private float mCollisionResolverInterval = 0; + + private int mDistanceMode = DISTANCE_MODE_FIXED; + + public static final int DISTANCE_MODE_RADIUS = 0; + + public static final int DISTANCE_MODE_FIXED = 1; + + public CollisionManager() { + this(new Physics()); + } + + public CollisionManager(final Physics thePhysics) { + mCollisionPhysics = thePhysics; + mResolveSamePosition = new Vector3f(1, 1, 1); + mCollisionSpringConstant = 20.0f; + mCollisionSpringDamping = 1.0f; + mMinimumDistance = 20; + mResolverType = ResolverType.SPRING; + mRandom = new Random(); + } + + public void distancemode(int theDistanceMode) { + mDistanceMode = theDistanceMode; + } + + public void setResolverType(ResolverType theResolverType) { + mResolverType = theResolverType; + } + + public Vector3f resolveSamePosition() { + return mResolveSamePosition; + } + + public void springDamping(float theSpringDamping) { + mCollisionSpringDamping = theSpringDamping; + } + + public float springDamping() { + return mCollisionSpringDamping; + } + + public void springConstant(float theSpringConstant) { + mCollisionSpringConstant = theSpringConstant; + } + + public float springConstant() { + return mCollisionSpringConstant; + } + + public void minimumDistance(float theMinimumDistance) { + mMinimumDistance = theMinimumDistance; + } + + public float minimumDistance() { + return mMinimumDistance; + } + + public Physics collision() { + return mCollisionPhysics; + } + + public Vector collision_forces() { + return mCollisionPhysics.forces(); + } + + public void loop(float theDeltaTime) { + +// /* collision resolver */ +// if (mCollisionResolverIntervalCounter > mCollisionResolverInterval) { +// mCollisionResolverIntervalCounter = 0; +//// createCollisionResolvers(); +// } else { +// mCollisionResolverIntervalCounter += theDeltaTime; +// } + + /* physics */ + mCollisionPhysics.step(theDeltaTime); + +// /* remove collision resolver */ +// removeCollisionResolver(); + } + + public void autoloop(float theDeltaTime) { + /* collision resolver */ + if (mCollisionResolverIntervalCounter > mCollisionResolverInterval) { + mCollisionResolverIntervalCounter = 0; +// mCollisionResolverIntervalCounter -= mCollisionResolverInterval; +// mCollisionResolverIntervalCounter %= mCollisionResolverInterval; + createCollisionResolvers(); + } else { + mCollisionResolverIntervalCounter += theDeltaTime; + } + + /* physics */ + mCollisionPhysics.step(theDeltaTime); + + /* remove collision resolver */ + removeCollisionResolver(); + } + + public void removeCollisionResolver() { + mCollisionPhysics.forces().clear(); + mCollisionPhysics.constraints().clear(); + } + + public void createCollisionResolvers() { + for (int i = 0; i < mCollisionPhysics.particles().size(); i++) { + createCollisionResolver(mCollisionPhysics.particles().get(i), i); + } + } + + private void createCollisionResolver(final Particle theParticle, final int theStart) { + if (HINT_IGNORE_STILL_OR_FIXED) { + if (theParticle.fixed() || theParticle.still()) { + return; + } + } + for (int j = theStart; j < mCollisionPhysics.particles().size(); j++) { + Particle myOtherParticle = mCollisionPhysics.particles().get(j); + if (theParticle != myOtherParticle) { // && !myOtherParticle.fixed()) { + final float myDistance = theParticle.position().distance(myOtherParticle.position()); + final float myMinimumDistance = getMinimumDistance(theParticle, myOtherParticle); + if (myDistance < myMinimumDistance) { + if (theParticle.fixed() && myOtherParticle.fixed()) { +// continue; + } + /** + * because of the way we handle the collision resolver + * creation there is no need to check for multiple spring + * connections. + * checkSpringConnectionExistence(mCollisionPhysics.getForces(), + * myParticle, myOtherParticle); + */ + if (mResolverType == ResolverType.SPRING) { + Spring mySpring = new Spring(theParticle, + myOtherParticle, + mCollisionSpringConstant, + mCollisionSpringDamping, + myMinimumDistance); + mCollisionPhysics.add(mySpring); + } else if (mResolverType == ResolverType.STICK) { + Stick mySpring = new Stick(theParticle, + myOtherParticle, + myMinimumDistance); + mCollisionPhysics.add(mySpring); + } + + /* hack to prevent particles from being in the same place */ + if (myDistance < mathematik.Mathematik.EPSILON && myDistance > -mathematik.Mathematik.EPSILON) { + myOtherParticle.position().x += mRandom.getFloat(mResolveSamePosition.x * -0.5f, + mResolveSamePosition.x * 0.5f); + myOtherParticle.position().y += mRandom.getFloat(mResolveSamePosition.y * -0.5f, + mResolveSamePosition.y * 0.5f); + myOtherParticle.position().z += mRandom.getFloat(mResolveSamePosition.z * -0.5f, + mResolveSamePosition.z * 0.5f); + } + } + } + } + } + + public void createCollisionResolvers(final CubicleWorld theWorld) { + for (int i = 0; i < mCollisionPhysics.particles().size(); i++) { + final Particle myParticle = mCollisionPhysics.particles().get(i); + if (myParticle instanceof CubicleParticle) { + createCollisionResolver(theWorld, (CubicleParticle) myParticle); + } + } + } + + private void createCollisionResolver(final CubicleWorld theWorld, final CubicleParticle theParticle) { + if (HINT_IGNORE_STILL_OR_FIXED) { + if (theParticle.fixed() || theParticle.still()) { + return; + } + } + + final Vector myNeigbors = theWorld.getLocalEntities(theParticle); + if (myNeigbors.size() > 1) { + for (int j = 0; j < myNeigbors.size(); j++) { + final ICubicleEntity myEntity = myNeigbors.get(j); + if (myEntity instanceof Particle) { + final Particle myOtherParticle = (Particle) myEntity; + if (theParticle != myOtherParticle) { + final float myDistance = theParticle.position().distance(myOtherParticle.position()); + final float myMinimumDistance = getMinimumDistance(theParticle, myOtherParticle); + if (myDistance < myMinimumDistance) { + if (theParticle.fixed() && myOtherParticle.fixed()) { + continue; + } + /** + * because of the way we handle the collision + * resolver creation there is no need to check for + * multiple spring connections. + * checkSpringConnectionExistence(mCollisionPhysics.getForces(), + * myParticle, myOtherParticle); + */ + if (mResolverType == ResolverType.SPRING) { + Spring mySpring = new Spring(theParticle, + myOtherParticle, + mCollisionSpringConstant, + mCollisionSpringDamping, + myMinimumDistance); + mCollisionPhysics.add(mySpring); + } else if (mResolverType == ResolverType.STICK) { + Stick mySpring = new Stick(theParticle, + myOtherParticle, + myMinimumDistance); + mCollisionPhysics.add(mySpring); + } + + /* hack to prevent particles from being in the same place */ + if (myDistance < mathematik.Mathematik.EPSILON + && myDistance > -mathematik.Mathematik.EPSILON) { + myOtherParticle.position().x += mRandom.getFloat(mResolveSamePosition.x * -0.5f, + mResolveSamePosition.x * 0.5f); + myOtherParticle.position().y += mRandom.getFloat(mResolveSamePosition.y * -0.5f, + mResolveSamePosition.y * 0.5f); + myOtherParticle.position().z += mRandom.getFloat(mResolveSamePosition.z * -0.5f, + mResolveSamePosition.z * 0.5f); + } + } + } + } + } + } + } + + private float getMinimumDistance(Particle theParticle, Particle myOtherParticle) { + final float myMinimumDistance; + + if (mDistanceMode == DISTANCE_MODE_RADIUS) { + myMinimumDistance = theParticle.radius() + myOtherParticle.radius(); + } else { + myMinimumDistance = mMinimumDistance; + } + return myMinimumDistance; + } + + public static class CollisionSpring + extends Spring { + + public CollisionSpring(Particle theA, Particle theB) { + super(theA, + theB, + 2.0f, 0.1f, + theA.position().distance(theB.position())); + } + + public CollisionSpring(Particle theA, + Particle theB, + final float theSpringConstant, + final float theSpringDamping) { + super(theA, + theB, + theSpringConstant, + theSpringDamping, + theA.position().distance(theB.position())); + } + + public CollisionSpring(final Particle theA, + final Particle theB, + final float theSpringConstant, + final float theSpringDamping, + final float theRestLength) { + super(theA, + theB, + theSpringConstant, + theSpringDamping, + theRestLength); + } + + public void apply(final float theDeltaTime, final Physics theParticleSystem) { + if (!mA.fixed() || !mB.fixed()) { + float a2bX = mA.position().x - mB.position().x; + float a2bY = mA.position().y - mB.position().y; + float a2bZ = mA.position().z - mB.position().z; + final float myInversDistance = fastInverseSqrt(a2bX * a2bX + a2bY * a2bY + a2bZ * a2bZ); + final float myDistance = 1.0F / myInversDistance; + + if (myDistance < mRestLength) { + if (myDistance == 0.0F) { + a2bX = 0.0F; + a2bY = 0.0F; + a2bZ = 0.0F; + } else { + a2bX *= myInversDistance; + a2bY *= myInversDistance; + a2bZ *= myInversDistance; + } + + final float mSpringForce = -(myDistance - mRestLength) * mSpringConstant; + final float Va2bX = mA.velocity().x - mB.velocity().x; + final float Va2bY = mA.velocity().y - mB.velocity().y; + final float Va2bZ = mA.velocity().z - mB.velocity().z; + final float mDampingForce = -mSpringDamping * (a2bX * Va2bX + a2bY * Va2bY + a2bZ * Va2bZ); + final float r = mSpringForce + mDampingForce; + a2bX *= r; + a2bY *= r; + a2bZ *= r; + if (!mA.fixed()) { + mA.force().add(a2bX, a2bY, a2bZ); + } + + if (!mB.fixed()) { + mB.force().add(-a2bX, -a2bY, -a2bZ); + } + } + } + } + } + + public static class CollisionStick + extends Stick { + + public CollisionStick(Particle theA, Particle theB) { + super(theA, theB); + } + + public CollisionStick(final Particle theA, + final Particle theB, + final float theRestLength) { + super(theA, theB, theRestLength); + } + + public void apply(Physics theParticleSystem) { + if (!mA.fixed() || !mB.fixed()) { + mTempDistanceVector.sub(mA.position(), mB.position()); + final float myDistanceSquared = mTempDistanceVector.lengthSquared(); + if (myDistanceSquared > 0) { + if (myDistanceSquared < mRestLength * mRestLength) { + final float myDistance = (float) Math.sqrt(myDistanceSquared); + final float myDifference = mRestLength - myDistance; + if (myDifference > EPSILON || myDifference < -EPSILON) { + if (!mOneWay) { + final float myDifferenceScale = 0.5f * myDifference / myDistance; + mTempVector.scale(myDifferenceScale, mTempDistanceVector); + mA.position().add(mTempVector); + mB.position().sub(mTempVector); + } else { + final float myDifferenceScale = myDifference / myDistance; + mTempVector.scale(myDifferenceScale, mTempDistanceVector); + mB.position().sub(mTempVector); + } + } + } + } else { + mB.position().set(mA.position()); + mB.position().x += mRestLength; + } + } + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/CubicleWorldView.java b/day3/libs/teilchen/src/teilchen/util/CubicleWorldView.java new file mode 100755 index 0000000..74c4c9a --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/CubicleWorldView.java @@ -0,0 +1,86 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import mathematik.TransformMatrix4f; +import mathematik.Vector3f; + +import processing.core.PGraphics; +import teilchen.cubicle.CubicleAtom; +import teilchen.cubicle.CubicleWorld; + + +public class CubicleWorldView { + + private final CubicleWorld mCubicleWorld; + + public int color_full = -1; + + public int color_empty = -8421505; + + public CubicleWorldView(CubicleWorld theWorld) { + mCubicleWorld = theWorld; + } + + public void draw(PGraphics theParent) { + + /* collect data */ + final CubicleAtom[][][] myData = mCubicleWorld.getDataRef(); + final TransformMatrix4f myTransform = mCubicleWorld.transform(); + final Vector3f myScale = mCubicleWorld.cellscale(); + + /* draw world */ + theParent.pushMatrix(); + + /* rotation */ + float[] f = myTransform.toArray(); + theParent.translate(f[12], f[13], f[14]); + theParent.applyMatrix(f[0], f[1], f[2], f[3], + f[4], f[5], f[6], f[7], + f[8], f[9], f[10], f[11], + 0, 0, 0, f[15]); + + /* scale */ + theParent.scale(myScale.x, myScale.y, myScale.z); + for (int x = 0; x < myData.length; x++) { + for (int y = 0; y < myData[x].length; y++) { + for (int z = 0; z < myData[x][y].length; z++) { + CubicleAtom myCubicle = myData[x][y][z]; + theParent.pushMatrix(); + theParent.translate(x, y, z); + theParent.translate(mCubicleWorld.cellscale().x / 2 / myScale.x, + mCubicleWorld.cellscale().y / 2 / myScale.y, + mCubicleWorld.cellscale().z / 2 / myScale.z); + if (myCubicle.size() > 0) { + theParent.stroke(color_full); + } else { + theParent.stroke(color_empty); + } + theParent.box(1); + theParent.popMatrix(); + } + } + } + theParent.popMatrix(); + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/DrawLib.java b/day3/libs/teilchen/src/teilchen/util/DrawLib.java new file mode 100755 index 0000000..071252f --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/DrawLib.java @@ -0,0 +1,290 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import java.util.List; + +import mathematik.Vector3f; +import mathematik.WorldAxisAlignedBoundingBox; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Attractor; +import teilchen.force.IForce; +import teilchen.force.Spring; +import teilchen.force.TriangleDeflector; +import processing.core.PGraphics; + + +public class DrawLib { + + public static void drawAttractor(final PGraphics g, + final List theForces, + int theColor) { + for (final IForce myForce : theForces) { + if (myForce instanceof Attractor) { + draw(g, (Attractor) myForce, theColor); + } + } + } + + /** + * draw attractors. + * + * @param g PGraphics + * @param myAttractor Attractor + * @param theColor int + */ + public static void draw(final PGraphics g, final Attractor myAttractor, int theColor) { + g.sphereDetail(6); + g.noFill(); + g.stroke(theColor); + + g.pushMatrix(); + g.translate(myAttractor.position().x, + myAttractor.position().y, + myAttractor.position().z); + g.sphere(myAttractor.radius()); + g.popMatrix(); + } + + public static void drawParticles(final PGraphics g, + final Physics theParticleSystem, + float theSize, + int theColor) { + draw(g, + theParticleSystem.particles(), + theSize, + theColor); + } + + public static void drawParticles(final PGraphics g, + final Physics theParticleSystem, + float theSize, + int theStrokeColor, + int theFillColor) { + draw(g, + theParticleSystem.particles(), + theSize, + theStrokeColor, + theFillColor); + } + + public static void drawSprings(final PGraphics g, + final Physics theParticleSystem, + int theColor) { + /* draw springs */ + g.stroke(theColor); + for (int i = 0; i < theParticleSystem.forces().size(); i++) { + if (theParticleSystem.forces(i) instanceof Spring) { + Spring mySpring = (Spring) theParticleSystem.forces(i); + g.line(mySpring.a().position().x, + mySpring.a().position().y, + mySpring.b().position().x, + mySpring.b().position().y); + } + } + } + + /** + * draw particles. + * + * @param g PGraphics + * @param theParticles Vector + * @param theSize float + * @param theColor int + */ + public static void draw(final PGraphics g, + final List theParticles, + float theSize, + int theColor) { + g.stroke(theColor); + g.noFill(); + for (Particle myParticle : theParticles) { + g.pushMatrix(); + g.translate(myParticle.position().x, + myParticle.position().y, + myParticle.position().z); + g.ellipse(0, 0, theSize, theSize); + g.popMatrix(); + } + } + + /** + * draw particles. + * + * @param g PGraphics + * @param theParticles Vector + * @param theSize float + * @param theStrokeColor int + */ + public static void draw(final PGraphics g, + final List theParticles, + float theSize, + int theStrokeColor, + int theFillColor) { + g.stroke(theStrokeColor); + g.fill(theFillColor); + for (Particle myParticle : theParticles) { + g.pushMatrix(); + g.translate(myParticle.position().x, + myParticle.position().y, + myParticle.position().z); + g.ellipse(0, 0, theSize, theSize); + g.popMatrix(); + } + } + + /** + * draw triangle deflector with bounding box. + * + * @param g PGraphics + * @param theTriangleDeflector TriangleDeflector + * @param theTriangleColor int + * @param theBBColor int + * @param theNormalColor int + */ + public static void draw(final PGraphics g, + final TriangleDeflector theTriangleDeflector, + int theTriangleColor, + int theBBColor, + int theNormalColor) { + /* triangle */ + int myTriangleColor = theTriangleColor; + if (theTriangleDeflector.hit()) { + myTriangleColor = theBBColor; + } + draw(g, + theTriangleDeflector.a(), theTriangleDeflector.b(), theTriangleDeflector.c(), + myTriangleColor, + theNormalColor); + + /* bb */ + draw(g, + theTriangleDeflector.boundingbox(), + theBBColor); + } + + /** + * draw buunding box. + * + * @param g PGraphics + * @param theWorldAxisAlignedBoundingBox WorldAxisAlignedBoundingBox + * @param theColor int + */ + public static void draw(final PGraphics g, + final WorldAxisAlignedBoundingBox theWorldAxisAlignedBoundingBox, + int theColor) { + g.stroke(theColor); + g.pushMatrix(); + g.translate(theWorldAxisAlignedBoundingBox.position.x, + theWorldAxisAlignedBoundingBox.position.y, + theWorldAxisAlignedBoundingBox.position.z); + g.box(theWorldAxisAlignedBoundingBox.scale.x, + theWorldAxisAlignedBoundingBox.scale.y, + theWorldAxisAlignedBoundingBox.scale.z); + g.popMatrix(); + } + + /** + * draw a triangle with a normal + * + * @param g PGraphics + * @param a Vector3f + * @param b Vector3f + * @param c Vector3f + * @param theTriangleColor int + * @param theNormalColor int + */ + public static void draw(final PGraphics g, + final Vector3f a, final Vector3f b, final Vector3f c, + int theTriangleColor, int theNormalColor) { + g.stroke(theTriangleColor); + g.beginShape(PGraphics.TRIANGLES); + g.vertex(a.x, a.y, a.z); + g.vertex(b.x, b.y, b.z); + g.vertex(c.x, c.y, c.z); + g.endShape(); + g.noFill(); + + Vector3f myNormal = new Vector3f(); + mathematik.Util.calculateNormal(a, b, c, myNormal); + myNormal.scale(50); + + Vector3f myCenterOfMass = new Vector3f(); + myCenterOfMass.add(a); + myCenterOfMass.add(b); + myCenterOfMass.add(c); + myCenterOfMass.scale(1f / 3f); + + g.stroke(theNormalColor); + g.line(myCenterOfMass.x, + myCenterOfMass.y, + myCenterOfMass.z, + myCenterOfMass.x + myNormal.x, + myCenterOfMass.y + myNormal.y, + myCenterOfMass.z + myNormal.z); + } + + public static void cross2(final PGraphics g, + final Vector3f pPosition, + float pSize) { + g.line( + pPosition.x + pSize, + pPosition.y + pSize, + pPosition.z, + pPosition.x - pSize, + pPosition.y - pSize, + pPosition.z); + g.line( + pPosition.x + pSize, + pPosition.y - pSize, + pPosition.z, + pPosition.x - pSize, + pPosition.y + pSize, + pPosition.z); + } + + public static void cross3(final PGraphics g, + final Vector3f pPosition, + float pSize) { + g.line(pPosition.x - pSize, + pPosition.y, + pPosition.z, + pPosition.x + pSize, + pPosition.y, + pPosition.z); + g.line(pPosition.x, + pPosition.y - pSize, + pPosition.z, + pPosition.x, + pPosition.y + pSize, + pPosition.z); + g.line(pPosition.x, + pPosition.y, + pPosition.z - pSize, + pPosition.x, + pPosition.y, + pPosition.z + pSize); + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/Overlap.java b/day3/libs/teilchen/src/teilchen/util/Overlap.java new file mode 100755 index 0000000..6546866 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/Overlap.java @@ -0,0 +1,107 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import mathematik.Vector3f; + +import java.util.List; + + +public class Overlap { + + public static Vector3f RESOLVE_SAME_PLACE = new Vector3f(1, 0, 0); + + public static void resolveOverlap(E theEntityA, + E theEntityB) { + if (theEntityB == theEntityA) { + return; + } + + if (theEntityA.radius() == 0 || theEntityB.radius() == 0) { + return; + } + + final Vector3f mAB = mathematik.Util.sub(theEntityA.position(), theEntityB.position()); + final float myDistance = mAB.length(); + + if (myDistance > 0) { + float myOverlap = theEntityB.radius() + theEntityA.radius() - myDistance; + + if (myOverlap > 0) { + mAB.scale(0.5f * myOverlap / myDistance); + theEntityA.position().add(mAB); + theEntityB.position().sub(mAB); + } + } else { + if (RESOLVE_SAME_PLACE != null) { + final Vector3f myOffset = new Vector3f(RESOLVE_SAME_PLACE); + myOffset.scale(theEntityB.radius() + theEntityA.radius()); + myOffset.scale(0.5f); + theEntityA.position().add(myOffset); + theEntityB.position().sub(myOffset); + } + } + } + + public static void resolveOverlap(E theEntity, + E[] theEntities) { + if (theEntities == null || theEntities.length < 1) { + return; + } + + for (int i = 0; i < theEntities.length; i++) { + resolveOverlap(theEntities[i], theEntity); + } + } + + public static void resolveOverlap(E theEntity, + List theEntities) { + if (theEntities == null || theEntities.size() < 1) { + return; + } + + for (int i = 0; i < theEntities.size(); i++) { + resolveOverlap(theEntities.get(i), theEntity); + } + } + + public static void resolveOverlap(List theEntities) { + if (theEntities == null || theEntities.isEmpty()) { + return; + } + + /** + * @todo room for improvement. there is some redundant testing going on + * here. + */ + for (int i = 0; i < theEntities.size(); i++) { + for (int j = 0; j < theEntities.size(); j++) { + if (i == j) { + continue; + } +// final SpatialEntity myOtherEntity = theEntities.get(j); + resolveOverlap(theEntities.get(i), theEntities.get(j)); + } + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/Packing.java b/day3/libs/teilchen/src/teilchen/util/Packing.java new file mode 100755 index 0000000..37e3701 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/Packing.java @@ -0,0 +1,73 @@ +package teilchen.util; + + +import mathematik.Vector3f; + +import java.util.List; +import java.util.Vector; + + +public class Packing { + + public static void pack(Vector mShapes, Vector3f pCenter, float pDamping) { + resolveOverlap(mShapes); + contract(mShapes, pCenter, pDamping); + } + + public static void resolveOverlap(List mEntities) { + /* @TODO this is somewhat redundant to 'AntiOverlap' */ + for (int i = 0; i < mEntities.size(); i++) { + final SpatialEntity mA = mEntities.get(i); + for (int j = i + 1; j < mEntities.size(); j++) { + if (i != j) { + final SpatialEntity mB = mEntities.get(j); + final float mIdealDistance = mA.radius() + mB.radius(); + final Vector3f mAB = mathematik.Util.sub(mB.position(), mA.position()); + final float mDistance = mAB.length(); + if (mDistance > 0.0f) { + if (mDistance < mIdealDistance) { + mAB.normalize(); + mAB.scale((mIdealDistance - mDistance) * 0.5f); + mB.position().add(mAB); + mA.position().sub(mAB); + } + } + } + } + } + } + + public static void contract(Vector mEntities, Vector3f pCenter, float pDamping) { + for (int i = 0; i < mEntities.size(); i++) { + final SpatialEntity mEntity = mEntities.get(i); + final Vector3f v = mathematik.Util.sub(mEntity.position(), pCenter); + v.scale(pDamping); + mEntity.position().sub(v); + } + } + + public static class PackingEntity + implements SpatialEntity { + + private Vector3f mPosition; + + private float mRadius; + + public PackingEntity() { + mPosition = new Vector3f(); + mRadius = 1; + } + + public float radius() { + return mRadius; + } + + public Vector3f position() { + return mPosition; + } + + public void radius(float pRadius) { + mRadius = pRadius; + } + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/ParticleTrail.java b/day3/libs/teilchen/src/teilchen/util/ParticleTrail.java new file mode 100755 index 0000000..fb3dae7 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/ParticleTrail.java @@ -0,0 +1,141 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import java.util.Vector; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.ShortLivedParticle; + + +public class ParticleTrail { + + private Physics _myTrailParticleSystem; + + private final Particle _myParticle; + + private final float _myInterval; + + private final Vector _myFragments; + + private final float _myFragmentLifetime; + + private float _myCurrentTime; + + private boolean _myFixState; + + private Class _myParticleClass = ShortLivedParticle.class; + + private float mTrailParticleMass = 1.0f; + + public ParticleTrail(final Physics theTrailParticleSystem, + final Particle theParticle, + float theInterval, + float theFragmentLifetime) { + _myTrailParticleSystem = theTrailParticleSystem; + _myParticle = theParticle; + _myInterval = theInterval; + _myFragmentLifetime = theFragmentLifetime; + _myFragments = new Vector(); + _myFixState = false; + } + + public void mass(float pMass) { + mTrailParticleMass = pMass; + } + + public float mass() { + return mTrailParticleMass; + } + + public void fix(boolean theFixState) { + _myFixState = theFixState; + } + + public Particle particle() { + return _myParticle; + } + + public void clear() { + _myFragments.clear(); + } + + public Vector fragments() { + return _myFragments; + } + + public void set() { + /* this would be more precise but has other issues -- _myCurrentTime -= _myInterval; */ + _myCurrentTime = 0; + final Particle myParticle = makeParticle(); + _myFragments.add(myParticle); + } + + public void loop(float theDeltaTime) { + _myCurrentTime += theDeltaTime; + + if (_myCurrentTime > _myInterval) { + set(); + } + + for (int i = 0; i < _myFragments.size(); i++) { + Particle myTrailFragment = _myFragments.get(i); + if (myTrailFragment.dead()) { + _myFragments.remove(myTrailFragment); + } + } + } + + public void setParticleClass(Class theClass) { + _myParticleClass = theClass; + } + + private static T createParticle(Class theParticleClass) { + T myParticle; + try { + myParticle = theParticleClass.newInstance(); + } catch (Exception ex) { + System.err.println(ex); + myParticle = null; + } + return myParticle; + } + + protected Particle makeParticle() { + final Particle myTrailFragment = createParticle(_myParticleClass); + myTrailFragment.mass(mTrailParticleMass); + + if (_myTrailParticleSystem != null) { + _myTrailParticleSystem.add(myTrailFragment); + } + + if (myTrailFragment instanceof ShortLivedParticle) { + ((ShortLivedParticle) myTrailFragment).setMaxAge(_myFragmentLifetime); + } + + myTrailFragment.position().set(_myParticle.position()); + myTrailFragment.fixed(_myFixState); + return myTrailFragment; + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/SpatialEntity.java b/day3/libs/teilchen/src/teilchen/util/SpatialEntity.java new file mode 100755 index 0000000..73a2918 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/SpatialEntity.java @@ -0,0 +1,33 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import mathematik.Vector3f; + + +public interface SpatialEntity { + + float radius(); + + Vector3f position(); +} diff --git a/day3/libs/teilchen/src/teilchen/util/StableSpringQuad.java b/day3/libs/teilchen/src/teilchen/util/StableSpringQuad.java new file mode 100755 index 0000000..d40d42b --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/StableSpringQuad.java @@ -0,0 +1,117 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.Spring; + + +public class StableSpringQuad { + + public Particle a; + + public Particle b; + + public Particle c; + + public Particle d; + + public Spring ab; + + public Spring bc; + + public Spring cd; + + public Spring da; + + public Spring ac; + + public Spring bd; + + /** + * this utility method creates a 'stable' shape from 4 positions. in this + * case a stable shape is created by connecting the four positions' edge + * plus two diagonals to create a stable quad.
+ * the positions should be in counter clockwise order.
+ * the positions are stored as reference which means that if you change + * either of the vectors afterwards it will also change the position of the + * connected particles. + * + * @param theParticleSystem ParticleSystem + * @param a Vector3f + * @param b Vector3f + * @param c Vector3f + * @param d Vector3f + */ + public StableSpringQuad(final Physics theParticleSystem, + final Vector3f theA, + final Vector3f theB, + final Vector3f theC, + final Vector3f theD) { + a = theParticleSystem.makeParticle(); + b = theParticleSystem.makeParticle(); + c = theParticleSystem.makeParticle(); + d = theParticleSystem.makeParticle(); + + a.setPositionRef(theA); + b.setPositionRef(theB); + c.setPositionRef(theC); + d.setPositionRef(theD); + + /* edges */ + final float mySpringConstant = 100; + final float mySpringDamping = 5; + ab = theParticleSystem.makeSpring(a, b, mySpringConstant, mySpringDamping); + bc = theParticleSystem.makeSpring(b, c, mySpringConstant, mySpringDamping); + cd = theParticleSystem.makeSpring(c, d, mySpringConstant, mySpringDamping); + da = theParticleSystem.makeSpring(d, a, mySpringConstant, mySpringDamping); + /* diagonals */ + ac = theParticleSystem.makeSpring(a, c, mySpringConstant, mySpringDamping); + bd = theParticleSystem.makeSpring(b, d, mySpringConstant, mySpringDamping); + } + + public StableSpringQuad(final Physics theParticleSystem, + final Particle pA, + final Particle pB, + final Particle pC, + final Particle pD) { + a = pA; + b = pB; + c = pC; + d = pD; + + /* edges */ + final float mySpringConstant = 500; + final float mySpringDamping = 5; + ab = theParticleSystem.makeSpring(a, b, mySpringConstant, mySpringDamping); + bc = theParticleSystem.makeSpring(b, c, mySpringConstant, mySpringDamping); + cd = theParticleSystem.makeSpring(c, d, mySpringConstant, mySpringDamping); + da = theParticleSystem.makeSpring(d, a, mySpringConstant, mySpringDamping); + /* diagonals */ + ac = theParticleSystem.makeSpring(a, c, mySpringConstant, mySpringDamping); + bd = theParticleSystem.makeSpring(b, d, mySpringConstant, mySpringDamping); + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/StableStickQuad.java b/day3/libs/teilchen/src/teilchen/util/StableStickQuad.java new file mode 100755 index 0000000..fe31a8d --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/StableStickQuad.java @@ -0,0 +1,85 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import mathematik.Vector3f; + +import teilchen.Particle; +import teilchen.Physics; +import teilchen.constraint.Stick; + + +public class StableStickQuad { + + public Particle a; + + public Particle b; + + public Particle c; + + public Particle d; + + public Stick ab; + + public Stick bc; + + public Stick cd; + + public Stick da; + + public Stick ac; + + public Stick bd; + + public StableStickQuad(final Physics theParticleSystem, + final Vector3f theA, + final Vector3f theB, + final Vector3f theC, + final Vector3f theD) { + a = theParticleSystem.makeParticle(); + b = theParticleSystem.makeParticle(); + c = theParticleSystem.makeParticle(); + d = theParticleSystem.makeParticle(); + + a.setPositionRef(theA); + b.setPositionRef(theB); + c.setPositionRef(theC); + d.setPositionRef(theD); + + /* edges */ + ab = new Stick(a, b); + bc = new Stick(b, c); + cd = new Stick(c, d); + da = new Stick(d, a); + theParticleSystem.add(ab); + theParticleSystem.add(bc); + theParticleSystem.add(cd); + theParticleSystem.add(da); + + /* diagonals */ + ac = new Stick(a, c); + bd = new Stick(b, d); + theParticleSystem.add(ac); + theParticleSystem.add(bd); + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/StickMan.java b/day3/libs/teilchen/src/teilchen/util/StickMan.java new file mode 100755 index 0000000..575b0e1 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/StickMan.java @@ -0,0 +1,122 @@ +package teilchen.util; + + +import mathematik.Vector3f; + +import teilchen.BasicParticle; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.MuscleSpring; +import processing.core.PApplet; +import processing.core.PGraphics; + + +public class StickMan { + + private StableSpringQuad _myQuad; + + private float _myScale; + + private BasicParticle myLeftFoot; + + private BasicParticle myRightFoot; + + private BasicParticle myLeftHand; + + private BasicParticle myRightHand; + + public StickMan(Physics theParticleSystem, float theOffset, float theScale) { + + _myScale = theScale; + + /* body */ + _myQuad = new StableSpringQuad(theParticleSystem, + new Vector3f(10 * theScale + theOffset, 50 * theScale), + new Vector3f(40 * theScale + theOffset, 50 * theScale), + new Vector3f(50 * theScale + theOffset, 0), + new Vector3f(0 + theOffset, 0)); + /* legs */ + myLeftFoot = theParticleSystem.makeParticle(new Vector3f(10 * theScale + theOffset, 100 * theScale), 0.5f); + theParticleSystem.makeSpring(_myQuad.a, myLeftFoot, 100, 0.1f); + theParticleSystem.makeSpring(_myQuad.b, myLeftFoot, 100, 0.1f); + theParticleSystem.makeSpring(_myQuad.d, myLeftFoot, 2, 1f); + + myRightFoot = theParticleSystem.makeParticle(new Vector3f(40 * theScale + theOffset, 100 * theScale), 0.5f); + theParticleSystem.makeSpring(_myQuad.a, myRightFoot, 100, 0.1f); + theParticleSystem.makeSpring(_myQuad.b, myRightFoot, 100, 0.1f); + theParticleSystem.makeSpring(_myQuad.c, myRightFoot, 2, 1f); + + /* arms */ + myRightHand = theParticleSystem.makeParticle(new Vector3f(70 * theScale + theOffset, 0), 0.1f); + theParticleSystem.makeSpring(_myQuad.c, myRightHand, 100, 0.1f); + theParticleSystem.makeSpring(_myQuad.b, myRightHand, 10, 0.1f); + + myLeftHand = theParticleSystem.makeParticle(new Vector3f(-20 * theScale + theOffset, 0), 0.1f); + theParticleSystem.makeSpring(_myQuad.d, myLeftHand, 100, 0.1f); + theParticleSystem.makeSpring(_myQuad.a, myLeftHand, 10, 0.1f); + + theParticleSystem.makeSpring(myLeftHand, myRightHand, 10, 0.1f); + + myLeftFoot.radius(7 * theScale); + myRightFoot.radius(7 * theScale); + myLeftHand.radius(20 * theScale); + myRightHand.radius(20 * theScale); + _myQuad.a.radius(7 * theScale); + _myQuad.a.radius(7 * theScale); + _myQuad.a.radius(7 * theScale); + _myQuad.a.radius(7 * theScale); + + final float myMass = 2f; + myLeftFoot.mass(myMass * theScale); + myRightFoot.mass(myMass * theScale); + myLeftHand.mass(myMass * theScale); + myRightHand.mass(myMass * theScale); + _myQuad.a.mass(myMass * theScale); + _myQuad.b.mass(myMass * theScale); + _myQuad.c.mass(myMass * theScale); + _myQuad.d.mass(myMass * theScale); + + /* make legs move */ + MuscleSpring myMuscleSpring = new MuscleSpring(myLeftFoot, myRightFoot); + myMuscleSpring.amplitude(20 * theScale); + myMuscleSpring.strength(100); + myMuscleSpring.offset((float) Math.random() * 2 * PApplet.PI); + theParticleSystem.add(myMuscleSpring); + } + + public void draw(PGraphics g) { + + g.stroke(255, 0, 0, 127); + + /* draw arms */ + g.line(myRightHand.position().x, myRightHand.position().y, + _myQuad.c.position().x, _myQuad.c.position().y); + g.line(myLeftHand.position().x, myLeftHand.position().y, + _myQuad.d.position().x, _myQuad.d.position().y); + + /* draw legs */ + g.line(myRightFoot.position().x, myRightFoot.position().y, + _myQuad.b.position().x, _myQuad.b.position().y); + g.line(myLeftFoot.position().x, myLeftFoot.position().y, + _myQuad.a.position().x, _myQuad.a.position().y); + + /* draw body */ + g.line(_myQuad.a.position().x, _myQuad.a.position().y, + _myQuad.b.position().x, _myQuad.b.position().y); + g.line(_myQuad.b.position().x, _myQuad.b.position().y, + _myQuad.c.position().x, _myQuad.c.position().y); + g.line(_myQuad.c.position().x, _myQuad.c.position().y, + _myQuad.d.position().x, _myQuad.d.position().y); + g.line(_myQuad.d.position().x, _myQuad.d.position().y, + _myQuad.a.position().x, _myQuad.a.position().y); + + /* draw head */ + g.ellipse((_myQuad.c.position().x + _myQuad.d.position().x) * 0.5f, + (_myQuad.c.position().y + _myQuad.d.position().y) * 0.5f, + 40 * _myScale, 40 * _myScale); + } + + public Particle anchor() { + return _myQuad.a; + } +} diff --git a/day3/libs/teilchen/src/teilchen/util/Util.java b/day3/libs/teilchen/src/teilchen/util/Util.java new file mode 100755 index 0000000..8dcc472 --- /dev/null +++ b/day3/libs/teilchen/src/teilchen/util/Util.java @@ -0,0 +1,198 @@ +/* + * Teilchen + * + * Copyright (C) 2013 + * + * 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. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * {@link http://www.gnu.org/licenses/lgpl.html} + * + */ +package teilchen.util; + + +import mathematik.Vector3f; + +import processing.core.PMatrix3D; +import teilchen.Particle; +import teilchen.Physics; +import teilchen.force.IForce; +import teilchen.force.TriangleDeflector; +import teilchen.force.TriangleDeflectorIndexed; + +import java.util.Vector; +import teilchen.constraint.IConstraint; + + +public class Util { + + public final static void satisfyNeighborConstraints(final Vector theParticles, final float theRelaxedness) { + for (int i = 0; i < theParticles.size(); i++) { + final Particle p1 = theParticles.get(i); + for (int j = i + 1; j < theParticles.size(); j++) { + final Particle p2 = theParticles.get(j); + /* satisfy overlap */ + if (p1.position().almost(p2.position())) { + p1.position().x += 0.01f; + p2.position().x -= 0.01f; + continue; + } + /* recover bad positions */ + if (p1.position().isNaN()) { + p1.position().set(p1.old_position()); + } + if (p2.position().isNaN()) { + p2.position().set(p2.old_position()); + } + final float myDistance = p1.position().distance(p2.position()); + /* skip bad values */ + if (myDistance == 0.0f || Float.isNaN(myDistance)) { + continue; + } + final float myDesiredDistance = p1.radius() + p2.radius(); + if (myDistance < myDesiredDistance) { + final Vector3f myDiff = mathematik.Util.sub(p1.position(), p2.position()); + myDiff.scale(1.0f / myDistance); + myDiff.scale(myDesiredDistance - myDistance); + myDiff.scale(0.5f); + myDiff.scale(theRelaxedness); + p1.position().add(myDiff); + p2.position().sub(myDiff); + } + } + } + } + private static final Vector3f TMP_NORMAL = new Vector3f(); + + private static final Vector3f TMP_TANGENT = new Vector3f(); + + public final static void reflectVelocity(final Particle theParticle, + final Vector3f theNormal, + float theCoefficientOfRestitution) { + final Vector3f myVelocity = theParticle.velocity(); + /* normal */ + TMP_NORMAL.set(theNormal); + TMP_NORMAL.scale(theNormal.dot(myVelocity)); + /* tangent */ + TMP_TANGENT.sub(myVelocity, TMP_NORMAL); + /* negate normal */ + TMP_NORMAL.scale(-theCoefficientOfRestitution); + /* set reflection vector */ + myVelocity.add(TMP_TANGENT, TMP_NORMAL); + + /* also set old position */ + if (Physics.HINT_UPDATE_OLD_POSITION) { + theParticle.old_position().set(theParticle.position()); + } + } + + public final static void reflect(final Vector3f theVector, final Vector3f theNormal, final float theCoefficientOfRestitution) { + final Vector3f myNormalComponent = new Vector3f(); + final Vector3f myTangentComponent = new Vector3f(); + + /* normal */ + myNormalComponent.set(theNormal); + myNormalComponent.scale(theNormal.dot(theVector)); + /* tangent */ + myTangentComponent.sub(theVector, myNormalComponent); + /* negate normal */ + myNormalComponent.scale(-theCoefficientOfRestitution); + /* set reflection vector */ + theVector.add(myTangentComponent, myNormalComponent); + } + + public final static void reflect(final Vector3f theVector, final Vector3f theNormal) { + /* normal */ + TMP_NORMAL.set(theNormal); + TMP_NORMAL.scale(theNormal.dot(theVector)); + /* tangent */ + TMP_TANGENT.sub(theVector, TMP_NORMAL); + /* negate normal */ + TMP_NORMAL.scale(-1.0f); + /* set reflection vector */ + theVector.add(TMP_TANGENT, TMP_NORMAL); + } + + public static final Vector createTriangleDeflectors(final float[] theVertices, + final float theCoefficientOfRestitution) { + final Vector myDeflectors = new Vector(); + for (int i = 0; i < theVertices.length / 9; i++) { + final TriangleDeflector myTriangleDeflector = new TriangleDeflector(); + myTriangleDeflector.a().set(theVertices[i * 9 + 0], + theVertices[i * 9 + 1], + theVertices[i * 9 + 2]); + myTriangleDeflector.b().set(theVertices[i * 9 + 3], + theVertices[i * 9 + 4], + theVertices[i * 9 + 5]); + myTriangleDeflector.c().set(theVertices[i * 9 + 6], + theVertices[i * 9 + 7], + theVertices[i * 9 + 8]); + myTriangleDeflector.coefficientofrestitution(theCoefficientOfRestitution); + myTriangleDeflector.updateProperties(); + myDeflectors.add(myTriangleDeflector); + } + return myDeflectors; + } + + public static final Vector createTriangleDeflectorsIndexed(final float[] theVertices, + final float theCoefficientOfRestitution) { + final Vector myDeflectors = new Vector(); + for (int i = 0; i < theVertices.length / 9; i++) { + final TriangleDeflectorIndexed myTriangleDeflector = new TriangleDeflectorIndexed( + theVertices, + i * 9 + 0, + i * 9 + 3, + i * 9 + 6); + myTriangleDeflector.coefficientofrestitution(theCoefficientOfRestitution); + myTriangleDeflector.updateProperties(); + myDeflectors.add(myTriangleDeflector); + } + return myDeflectors; + } + + public static void pointAt(final PMatrix3D pResult, + final Vector3f pPosition, + final Vector3f pUpVector, /* should be normalized */ + final Vector3f pPointAtPosition) { + + /* forward */ + final Vector3f mForwardVector = mathematik.Util.sub(pPosition, pPointAtPosition); + mForwardVector.normalize(); + + /* side */ + final Vector3f mSideVector = mathematik.Util.cross(pUpVector, mForwardVector); + mSideVector.normalize(); + + /* up */ + final Vector3f mUpVector = mathematik.Util.cross(mForwardVector, mSideVector); + mUpVector.normalize(); + + if (!mSideVector.isNaN() + && !mUpVector.isNaN() + && !mForwardVector.isNaN()) { + /* x */ + pResult.m00 = mSideVector.x; + pResult.m10 = mSideVector.y; + pResult.m20 = mSideVector.z; + /* y */ + pResult.m01 = mUpVector.x; + pResult.m11 = mUpVector.y; + pResult.m21 = mUpVector.z; + /* z */ + pResult.m02 = mForwardVector.x; + pResult.m12 = mForwardVector.y; + pResult.m22 = mForwardVector.z; + } + } +}