[Helix] A rotating double helix.
[SugarCubes.git] / ShaheenGandhi.pde
1 import toxi.geom.Vec3D;
2 import toxi.geom.Matrix4x4;
3
4 class HelixPattern extends SCPattern {
5
6 // Stores a line in point + vector form
7 private class Line {
8 private final PVector origin;
9 private final PVector vector;
10
11 Line(PVector pt, PVector v) {
12 origin = pt;
13 vector = v.get();
14 vector.normalize();
15 }
16
17 PVector getPoint() {
18 return origin;
19 }
20
21 PVector getVector() {
22 return vector;
23 }
24
25 PVector getPointAt(float t) {
26 PVector pt = PVector.mult(vector, t);
27 pt.add(origin);
28 return pt;
29 }
30
31 boolean isColinear(PVector pt) {
32 PVector projected = projected(pt);
33 return projected.x==pt.x && projected.y==pt.y && projected.z==pt.z;
34 }
35
36 float getTValue(PVector pt) {
37 PVector subtraction = PVector.sub(pt, origin);
38 return subtraction.dot(vector);
39 }
40
41 PVector projected(PVector pt) {
42 return getPointAt(getTValue(pt));
43 }
44
45 PVector rotatePoint(PVector pt, float rads) {
46 Vec3D axisVec3D = new Vec3D(vector.x, vector.y, vector.z);
47 Matrix4x4 mat = new Matrix4x4();
48 mat.rotateAroundAxis(axisVec3D, rads);
49 Vec3D ptVec3D = new Vec3D(pt.x, pt.y, pt.z);
50 Vec3D rotatedPt = mat.applyTo(ptVec3D);
51 return new PVector(rotatedPt.x, rotatedPt.y, rotatedPt.z);
52 }
53 }
54
55 private class Helix {
56 private final Line axis;
57 private final float period;
58 private final float rotationPeriod;
59 private final float radius;
60 private final float girth;
61 private final PVector referencePoint;
62 private float phase;
63 private PVector phaseNormal;
64
65 Helix(Line axis, float period, float radius, float girth, float phase, float rotationPeriod) {
66 this.axis = axis;
67 this.period = period;
68 this.radius = radius;
69 this.girth = girth;
70 this.phase = phase;
71 this.rotationPeriod = rotationPeriod;
72
73 // Generate a normal that will rotate to
74 // produce the helical shape.
75 PVector pt = new PVector(0, 1, 0);
76 if (this.axis.isColinear(pt)) {
77 pt = new PVector(0, 0, 1);
78 if (this.axis.isColinear(pt)) {
79 pt = new PVector(0, 1, 1);
80 }
81 }
82
83 this.referencePoint = pt;
84 this.phase = phase;
85
86 setPhaseNormalFromPhase();
87 }
88
89 private void setPhaseNormalFromPhase() {
90 phaseNormal = axis.getVector().cross(axis.rotatePoint(referencePoint, phase));
91 phaseNormal.normalize();
92 phaseNormal.mult(radius);
93 }
94
95 private void setPhase(float phase) {
96 this.phase = phase;
97 setPhaseNormalFromPhase();
98 }
99
100 void step(int deltaMs) {
101 setPhase(phase + (deltaMs / rotationPeriod) * TWO_PI);
102 }
103
104 PVector pointOnToroidalAxis(float t) {
105 PVector p = axis.getPointAt(t);
106 PVector middle = PVector.add(p, phaseNormal);
107 return axis.rotatePoint(middle, (t / period) * TWO_PI);
108 }
109
110 color colorOfPoint(PVector p) {
111 // Calculate the projection of this point to the axis.
112 PVector projectedPoint = axis.projected(p);
113
114 // Find the appropriate point for the current rotation
115 // of the helix.
116 float t = axis.getTValue(projectedPoint);
117 PVector toroidPoint = pointOnToroidalAxis(t);
118
119 // The rotated point represents the middle of the girth of
120 // the helix. Figure out if the current point is inside that
121 // region.
122 float d = PVector.dist(p, toroidPoint);
123 boolean inToroid = abs(d) < girth;
124
125 return color((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, (inToroid ? 100 : 0), (inToroid ? 100 : 0));
126 }
127 }
128
129 private final Helix h1;
130 private final Helix h2;
131
132 private final BasicParameter helix1On = new BasicParameter("H1ON", 1);
133 private final BasicParameter helix2On = new BasicParameter("H2ON", 1);
134
135 public HelixPattern(GLucose glucose) {
136 super(glucose);
137
138 addParameter(helix1On);
139 addParameter(helix2On);
140
141 h1 = new Helix(
142 new Line(new PVector(100, 50, 70), new PVector(1,0,0)),
143 700, // period
144 50, // radius
145 30, // girth
146 0, // phase
147 10000); // rotation period (ms)
148 h2 = new Helix(
149 new Line(new PVector(100, 50, 70), new PVector(1,0,0)),
150 700,
151 50,
152 30,
153 PI,
154 10000);
155
156 // TODO(shaheen) calculate line segments between
157 // toroidal points selected by stepping the
158 // parameterized t value. select base pairs and
159 // associated colors. lerp between colors for each
160 // base pair to produce a DNA effect.
161 }
162
163 void run(int deltaMs) {
164 boolean h1on = helix1On.getValue() > 0.5;
165 boolean h2on = helix2On.getValue() > 0.5;
166
167 h1.step(deltaMs);
168 h2.step(deltaMs);
169
170 for (Point p : model.points) {
171 color h1c = color(0,0,0);
172 color h2c = color(0,0,0);
173
174 if (h1on) {
175 h1c = h1.colorOfPoint(new PVector(p.x,p.y,p.z));
176 }
177
178 if (h2on) {
179 h2c = h2.colorOfPoint(new PVector(p.x,p.y,p.z));
180 }
181
182 // The helices are positioned to not overlap. If that changes,
183 // a better blending formula is probably needed.
184 colors[p.index] = blendColor(h1c, h2c, ADD);
185 }
186 }
187 }
188