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