--- /dev/null
+import toxi.geom.Vec3D;
+import toxi.geom.Matrix4x4;
+
+class HelixPattern extends SCPattern {
+
+ // Stores a line in point + vector form
+ private class Line {
+ private final PVector origin;
+ private final PVector vector;
+
+ Line(PVector pt, PVector v) {
+ origin = pt;
+ vector = v.get();
+ vector.normalize();
+ }
+
+ PVector getPoint() {
+ return origin;
+ }
+
+ PVector getVector() {
+ return vector;
+ }
+
+ PVector getPointAt(float t) {
+ PVector pt = PVector.mult(vector, t);
+ pt.add(origin);
+ return pt;
+ }
+
+ boolean isColinear(PVector pt) {
+ PVector projected = projected(pt);
+ return projected.x==pt.x && projected.y==pt.y && projected.z==pt.z;
+ }
+
+ float getTValue(PVector pt) {
+ PVector subtraction = PVector.sub(pt, origin);
+ return subtraction.dot(vector);
+ }
+
+ PVector projected(PVector pt) {
+ return getPointAt(getTValue(pt));
+ }
+
+ PVector rotatePoint(PVector pt, float rads) {
+ Vec3D axisVec3D = new Vec3D(vector.x, vector.y, vector.z);
+ Matrix4x4 mat = new Matrix4x4();
+ mat.rotateAroundAxis(axisVec3D, rads);
+ Vec3D ptVec3D = new Vec3D(pt.x, pt.y, pt.z);
+ Vec3D rotatedPt = mat.applyTo(ptVec3D);
+ return new PVector(rotatedPt.x, rotatedPt.y, rotatedPt.z);
+ }
+ }
+
+ private class Helix {
+ private final Line axis;
+ private final float period;
+ private final float rotationPeriod;
+ private final float radius;
+ private final float girth;
+ private final PVector referencePoint;
+ private float phase;
+ private PVector phaseNormal;
+
+ Helix(Line axis, float period, float radius, float girth, float phase, float rotationPeriod) {
+ this.axis = axis;
+ this.period = period;
+ this.radius = radius;
+ this.girth = girth;
+ this.phase = phase;
+ this.rotationPeriod = rotationPeriod;
+
+ // Generate a normal that will rotate to
+ // produce the helical shape.
+ PVector pt = new PVector(0, 1, 0);
+ if (this.axis.isColinear(pt)) {
+ pt = new PVector(0, 0, 1);
+ if (this.axis.isColinear(pt)) {
+ pt = new PVector(0, 1, 1);
+ }
+ }
+
+ this.referencePoint = pt;
+ this.phase = phase;
+
+ setPhaseNormalFromPhase();
+ }
+
+ private void setPhaseNormalFromPhase() {
+ phaseNormal = axis.getVector().cross(axis.rotatePoint(referencePoint, phase));
+ phaseNormal.normalize();
+ phaseNormal.mult(radius);
+ }
+
+ private void setPhase(float phase) {
+ this.phase = phase;
+ setPhaseNormalFromPhase();
+ }
+
+ void step(int deltaMs) {
+ setPhase(phase + (deltaMs / rotationPeriod) * TWO_PI);
+ }
+
+ PVector pointOnToroidalAxis(float t) {
+ PVector p = axis.getPointAt(t);
+ PVector middle = PVector.add(p, phaseNormal);
+ return axis.rotatePoint(middle, (t / period) * TWO_PI);
+ }
+
+ color colorOfPoint(PVector p) {
+ // Calculate the projection of this point to the axis.
+ PVector projectedPoint = axis.projected(p);
+
+ // Find the appropriate point for the current rotation
+ // of the helix.
+ float t = axis.getTValue(projectedPoint);
+ PVector toroidPoint = pointOnToroidalAxis(t);
+
+ // The rotated point represents the middle of the girth of
+ // the helix. Figure out if the current point is inside that
+ // region.
+ float d = PVector.dist(p, toroidPoint);
+ boolean inToroid = abs(d) < girth;
+
+ return color((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, (inToroid ? 100 : 0), (inToroid ? 100 : 0));
+ }
+ }
+
+ private final Helix h1;
+ private final Helix h2;
+
+ private final BasicParameter helix1On = new BasicParameter("H1ON", 1);
+ private final BasicParameter helix2On = new BasicParameter("H2ON", 1);
+
+ public HelixPattern(GLucose glucose) {
+ super(glucose);
+
+ addParameter(helix1On);
+ addParameter(helix2On);
+
+ h1 = new Helix(
+ new Line(new PVector(100, 50, 70), new PVector(1,0,0)),
+ 700, // period
+ 50, // radius
+ 30, // girth
+ 0, // phase
+ 10000); // rotation period (ms)
+ h2 = new Helix(
+ new Line(new PVector(100, 50, 70), new PVector(1,0,0)),
+ 700,
+ 50,
+ 30,
+ PI,
+ 10000);
+
+ // TODO(shaheen) calculate line segments between
+ // toroidal points selected by stepping the
+ // parameterized t value. select base pairs and
+ // associated colors. lerp between colors for each
+ // base pair to produce a DNA effect.
+ }
+
+ void run(int deltaMs) {
+ boolean h1on = helix1On.getValue() > 0.5;
+ boolean h2on = helix2On.getValue() > 0.5;
+
+ h1.step(deltaMs);
+ h2.step(deltaMs);
+
+ for (Point p : model.points) {
+ color h1c = color(0,0,0);
+ color h2c = color(0,0,0);
+
+ if (h1on) {
+ h1c = h1.colorOfPoint(new PVector(p.x,p.y,p.z));
+ }
+
+ if (h2on) {
+ h2c = h2.colorOfPoint(new PVector(p.x,p.y,p.z));
+ }
+
+ // The helices are positioned to not overlap. If that changes,
+ // a better blending formula is probably needed.
+ colors[p.index] = blendColor(h1c, h2c, ADD);
+ }
+ }
+}
+