[Helix][perf] Reduce toroidal coil point calculations for spokes by half
[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 p, final float t) {
44 final PVector o = origin;
45 final PVector v = vector;
46
47 float x = (o.x*(v.y*v.y + v.z*v.z) - v.x*(o.y*v.y + o.z*v.z - v.x*p.x - v.y*p.y - v.z*p.z))*(1 - cos(t)) + p.x*cos(t) + (-o.z*v.y + o.y*v.z - v.z*p.y + v.y*p.z)*sin(t);
48 float y = (o.y*(v.x*v.x + v.z*v.z) - v.y*(o.x*v.x + o.z*v.z - v.x*p.x - v.y*p.y - v.z*p.z))*(1 - cos(t)) + p.y*cos(t) + (o.z*v.x - o.x*v.z + v.z*p.x - v.x*p.z)*sin(t);
49 float z = (o.z*(v.x*v.x + v.y*v.y) - v.z*(o.x*v.x + o.y*v.y - v.x*p.x - v.y*p.y - v.z*p.z))*(1 - cos(t)) + p.z*cos(t) + (-o.y*v.x + o.x*v.y - v.y*p.x + v.x*p.y)*sin(t);
50 return new PVector(x, y, 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 float t = axis.getTValue(p);
110
111 // For performance reasons, cut out points that are outside of
112 // the tube where the toroidal coil lives.
113 if (abs(PVector.dist(p, axis.getPointAt(t)) - radius) > girth*.5f) {
114 return color(0,0,0);
115 }
116
117 // Find the appropriate point for the current rotation
118 // of the helix.
119 PVector toroidPoint = pointOnToroidalAxis(t);
120
121 // The rotated point represents the middle of the girth of
122 // the helix. Figure out if the current point is inside that
123 // region.
124 float d = PVector.dist(p, toroidPoint);
125
126 // Soften edges by fading brightness.
127 float b = constrain(100*(1 - ((d-.5*girth)/(girth*.5))), 0, 100);
128 return color((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, 80, b);
129 }
130 }
131
132 private final Helix h1;
133 private final Helix h2;
134
135 private final BasicParameter helix1On = new BasicParameter("H1ON", 1);
136 private final BasicParameter helix2On = new BasicParameter("H2ON", 1);
137 private final BasicParameter basePairsOn = new BasicParameter("BPON", 1);
138
139 private static final float helixCoilPeriod = 100;
140 private static final float helixCoilRadius = 45;
141 private static final float helixCoilGirth = 20;
142 private static final float helixCoilRotationPeriod = 10000;
143
144 private static final float spokePeriod = 40;
145 private static final float spokeGirth = 10;
146 private static final float spokePhase = 10;
147 private static final float spokeRadius = 35; // helixCoilRadius - helixCoilGirth*.5f;
148
149 public HelixPattern(GLucose glucose) {
150 super(glucose);
151
152 addParameter(helix1On);
153 addParameter(helix2On);
154 addParameter(basePairsOn);
155
156 PVector origin = new PVector(100, 50, 45);
157 PVector axis = new PVector(1,0,0);
158
159 h1 = new Helix(
160 new Line(origin, axis),
161 helixCoilPeriod,
162 helixCoilRadius,
163 helixCoilGirth,
164 0,
165 helixCoilRotationPeriod);
166 h2 = new Helix(
167 new Line(origin, axis),
168 helixCoilPeriod,
169 helixCoilRadius,
170 helixCoilGirth,
171 PI,
172 helixCoilRotationPeriod);
173 }
174
175 private color calculateSpokeColor(final color h1c, final color h2c, final PVector pt) {
176 // Find the closest spoke's t-value and calculate its
177 // axis. Until everything animates in the model reference
178 // frame, this has to be calculated at every step because
179 // the helices rotate.
180 float t = h1.getAxis().getTValue(pt) + spokePhase;
181 float spokeAxisTValue = floor(((t + spokePeriod/2) / spokePeriod)) * spokePeriod;
182 PVector h1point = h1.pointOnToroidalAxis(spokeAxisTValue);
183 // TODO(shaheen) investigate why h1.getAxis().getPointAt(spokeAxisTValue) doesn't quite
184 // have the same value as finding the middle between h1point and h2point.
185 PVector spokeCenter = h1.getAxis().getPointAt(spokeAxisTValue);
186 PVector spokeVector = PVector.sub(h1point, spokeCenter);
187 spokeVector.normalize();
188 Line spokeLine = new Line(h1point, spokeVector);
189 PVector pointOnSpoke = spokeLine.projectPoint(pt);
190 float b = ((PVector.dist(pt, pointOnSpoke) < spokeGirth) && (PVector.dist(pointOnSpoke, spokeCenter) < spokeRadius)) ? 100.f : 0.f;
191 return color(100, 80.f, b);
192 }
193
194 void run(int deltaMs) {
195 boolean h1on = helix1On.getValue() > 0.5;
196 boolean h2on = helix2On.getValue() > 0.5;
197 boolean spokesOn = (float)basePairsOn.getValue() > 0.5;
198
199 h1.step(deltaMs);
200 h2.step(deltaMs);
201
202 for (Point p : model.points) {
203 PVector pt = new PVector(p.x,p.y,p.z);
204 color h1c = h1.colorOfPoint(pt);
205 color h2c = h2.colorOfPoint(pt);
206 color spokeColor = calculateSpokeColor(h1c, h2c, pt);
207
208 if (!h1on) {
209 h1c = color(0,0,0);
210 }
211
212 if (!h2on) {
213 h2c = color(0,0,0);
214 }
215
216 if (!spokesOn) {
217 spokeColor = color(0,0,0);
218 }
219
220 // The helices are positioned to not overlap. If that changes,
221 // a better blending formula is probably needed.
222 colors[p.index] = blendColor(blendColor(h1c, h2c, ADD), spokeColor, ADD);
223 }
224 }
225 }
226