L8on Strips 3d Life
[SugarCubes.git] / ShaheenGandhi.pde
CommitLineData
fd8a39b0
SG
1import toxi.geom.Vec3D;
2import toxi.geom.Matrix4x4;
3
4class 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;
ee1f21c7 10
fd8a39b0
SG
11 Line(PVector pt, PVector v) {
12 origin = pt;
13 vector = v.get();
14 vector.normalize();
15 }
ee1f21c7 16
fd8a39b0
SG
17 PVector getPoint() {
18 return origin;
19 }
ee1f21c7 20
fd8a39b0
SG
21 PVector getVector() {
22 return vector;
23 }
ee1f21c7
SG
24
25 PVector getPointAt(final float t) {
26 return PVector.add(origin, PVector.mult(vector, t));
fd8a39b0 27 }
ee1f21c7
SG
28
29 boolean isColinear(final PVector pt) {
30 PVector projected = projectPoint(pt);
fd8a39b0
SG
31 return projected.x==pt.x && projected.y==pt.y && projected.z==pt.z;
32 }
ee1f21c7
SG
33
34 float getTValue(final PVector pt) {
fd8a39b0
SG
35 PVector subtraction = PVector.sub(pt, origin);
36 return subtraction.dot(vector);
ee1f21c7
SG
37 }
38
39 PVector projectPoint(final PVector pt) {
fd8a39b0
SG
40 return getPointAt(getTValue(pt));
41 }
ee1f21c7 42
44521cb7
SG
43 PVector rotatePoint(final PVector p, final float t) {
44 final PVector o = origin;
45 final PVector v = vector;
46
d814f9ad
SG
47 final float cost = cos(t);
48 final float sint = sin(t);
49
50 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 - cost) + p.x*cost + (-o.z*v.y + o.y*v.z - v.z*p.y + v.y*p.z)*sint;
51 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 - cost) + p.y*cost + (o.z*v.x - o.x*v.z + v.z*p.x - v.x*p.z)*sint;
52 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 - cost) + p.z*cost + (-o.y*v.x + o.x*v.y - v.y*p.x + v.x*p.y)*sint;
44521cb7 53 return new PVector(x, y, z);
fd8a39b0
SG
54 }
55 }
56
57 private class Helix {
58 private final Line axis;
ee1f21c7
SG
59 private final float period; // period of coil
60 private final float rotationPeriod; // animation period
61 private final float radius; // radius of coil
62 private final float girth; // girth of coil
fd8a39b0
SG
63 private final PVector referencePoint;
64 private float phase;
65 private PVector phaseNormal;
66
67 Helix(Line axis, float period, float radius, float girth, float phase, float rotationPeriod) {
68 this.axis = axis;
69 this.period = period;
70 this.radius = radius;
71 this.girth = girth;
72 this.phase = phase;
73 this.rotationPeriod = rotationPeriod;
74
75 // Generate a normal that will rotate to
76 // produce the helical shape.
77 PVector pt = new PVector(0, 1, 0);
78 if (this.axis.isColinear(pt)) {
79 pt = new PVector(0, 0, 1);
80 if (this.axis.isColinear(pt)) {
81 pt = new PVector(0, 1, 1);
82 }
83 }
84
85 this.referencePoint = pt;
fd8a39b0 86
ee1f21c7
SG
87 // The normal is calculated by the cross product of the axis
88 // and a random point that is not colinear with it.
89 phaseNormal = axis.getVector().cross(referencePoint);
fd8a39b0
SG
90 phaseNormal.normalize();
91 phaseNormal.mult(radius);
92 }
ee1f21c7 93
ee1f21c7
SG
94 Line getAxis() {
95 return axis;
96 }
34d49545
SG
97
98 PVector getPhaseNormal() {
99 return phaseNormal;
100 }
101
102 float getPhase() {
103 return phase;
104 }
ee1f21c7 105
34327c96 106 void step(double deltaMs) {
ee1f21c7
SG
107 // Rotate
108 if (rotationPeriod != 0) {
117f538a 109 this.phase = (phase + ((float)deltaMs / (float)rotationPeriod) * TWO_PI);
ee1f21c7 110 }
fd8a39b0 111 }
ee1f21c7 112
fd8a39b0
SG
113 PVector pointOnToroidalAxis(float t) {
114 PVector p = axis.getPointAt(t);
115 PVector middle = PVector.add(p, phaseNormal);
117f538a 116 return axis.rotatePoint(middle, (t / period) * TWO_PI + phase);
fd8a39b0 117 }
e034041e
SG
118
119 private float myDist(PVector p1, PVector p2) {
120 final float x = p2.x-p1.x;
121 final float y = p2.y-p1.y;
122 final float z = p2.z-p1.z;
123 return sqrt(x*x + y*y + z*z);
124 }
ee1f21c7
SG
125
126 color colorOfPoint(final PVector p) {
b8f61125
SG
127 final float t = axis.getTValue(p);
128 final PVector axisPoint = axis.getPointAt(t);
2a7c5e4d
SG
129
130 // For performance reasons, cut out points that are outside of
131 // the tube where the toroidal coil lives.
e034041e 132 if (abs(myDist(p, axisPoint) - radius) > girth*.5f) {
a41f334c 133 return lx.hsb(0,0,0);
2a7c5e4d
SG
134 }
135
fd8a39b0
SG
136 // Find the appropriate point for the current rotation
137 // of the helix.
34d49545
SG
138 PVector toroidPoint = axisPoint;
139 toroidPoint.add(phaseNormal);
140 toroidPoint = axis.rotatePoint(toroidPoint, (t / period) * TWO_PI + phase);
ee1f21c7 141
fd8a39b0
SG
142 // The rotated point represents the middle of the girth of
143 // the helix. Figure out if the current point is inside that
144 // region.
e034041e 145 float d = myDist(p, toroidPoint);
ee1f21c7 146
117f538a 147 // Soften edges by fading brightness.
c27cb078 148 float b = constrain(100*(1 - ((d-.5*girth)/(girth*.5))), 0, 100);
a41f334c 149 return lx.hsb((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, 80, b);
fd8a39b0
SG
150 }
151 }
6dfcb1d0
SG
152
153 private class BasePairInfo {
154 Line line;
155 float colorPhase1;
156 float colorPhase2;
157
158 BasePairInfo(Line line, float colorPhase1, float colorPhase2) {
159 this.line = line;
160 this.colorPhase1 = colorPhase1;
161 this.colorPhase2 = colorPhase2;
162 }
163 }
fd8a39b0
SG
164
165 private final Helix h1;
166 private final Helix h2;
6dfcb1d0 167 private final BasePairInfo[] basePairs;
ee1f21c7 168
fd8a39b0
SG
169 private final BasicParameter helix1On = new BasicParameter("H1ON", 1);
170 private final BasicParameter helix2On = new BasicParameter("H2ON", 1);
f904d86b 171 private final BasicParameter basePairsOn = new BasicParameter("BPON", 1);
f904d86b
SG
172
173 private static final float helixCoilPeriod = 100;
9cdbec1a
SG
174 private static final float helixCoilRadius = 50;
175 private static final float helixCoilGirth = 30;
176 private static final float helixCoilRotationPeriod = 5000;
f904d86b 177
7992264a 178 private static final float spokePeriod = 40;
9cdbec1a 179 private static final float spokeGirth = 20;
7992264a 180 private static final float spokePhase = 10;
9cdbec1a 181 private static final float spokeRadius = helixCoilRadius - helixCoilGirth*.5f;
53d41764
SG
182
183 private static final float tMin = -200;
184 private static final float tMax = 200;
7992264a 185
fd8a39b0
SG
186 public HelixPattern(GLucose glucose) {
187 super(glucose);
ee1f21c7 188
fd8a39b0
SG
189 addParameter(helix1On);
190 addParameter(helix2On);
f904d86b 191 addParameter(basePairsOn);
f904d86b 192
ab0a5c95 193 PVector origin = new PVector(100, 50, 55);
f904d86b 194 PVector axis = new PVector(1,0,0);
ee1f21c7 195
fd8a39b0 196 h1 = new Helix(
f904d86b
SG
197 new Line(origin, axis),
198 helixCoilPeriod,
199 helixCoilRadius,
200 helixCoilGirth,
201 0,
202 helixCoilRotationPeriod);
fd8a39b0 203 h2 = new Helix(
f904d86b
SG
204 new Line(origin, axis),
205 helixCoilPeriod,
206 helixCoilRadius,
207 helixCoilGirth,
fd8a39b0 208 PI,
f904d86b 209 helixCoilRotationPeriod);
53d41764 210
6dfcb1d0 211 basePairs = new BasePairInfo[(int)floor((tMax - tMin)/spokePeriod)];
53d41764
SG
212 }
213
214 private void calculateSpokes() {
6dfcb1d0 215 float colorPhase = PI/6;
53d41764
SG
216 for (float t = tMin + spokePhase; t < tMax; t += spokePeriod) {
217 int spokeIndex = (int)floor((t - tMin)/spokePeriod);
218 PVector h1point = h1.pointOnToroidalAxis(t);
219 PVector spokeCenter = h1.getAxis().getPointAt(t);
220 PVector spokeVector = PVector.sub(h1point, spokeCenter);
6dfcb1d0
SG
221 Line spokeLine = new Line(spokeCenter, spokeVector);
222 basePairs[spokeIndex] = new BasePairInfo(spokeLine, colorPhase * spokeIndex, colorPhase * (spokeIndex + 1));
53d41764 223 }
fd8a39b0 224 }
7992264a 225
53d41764 226 private color calculateSpokeColor(final PVector pt) {
7992264a
SG
227 // Find the closest spoke's t-value and calculate its
228 // axis. Until everything animates in the model reference
229 // frame, this has to be calculated at every step because
230 // the helices rotate.
34d49545
SG
231 Line axis = h1.getAxis();
232 float t = axis.getTValue(pt) + spokePhase;
53d41764
SG
233 int spokeIndex = (int)floor((t - tMin + spokePeriod/2) / spokePeriod);
234 if (spokeIndex < 0 || spokeIndex >= basePairs.length) {
a41f334c 235 return lx.hsb(0,0,0);
53d41764 236 }
6dfcb1d0
SG
237 BasePairInfo basePair = basePairs[spokeIndex];
238 Line spokeLine = basePair.line;
7992264a 239 PVector pointOnSpoke = spokeLine.projectPoint(pt);
dd6b1c27
SG
240 float d = PVector.dist(pt, pointOnSpoke);
241 float b = (PVector.dist(pointOnSpoke, spokeLine.getPoint()) < spokeRadius) ? constrain(100*(1 - ((d-.5*spokeGirth)/(spokeGirth*.5))), 0, 100) : 0.f;
6dfcb1d0 242 float phase = spokeLine.getTValue(pointOnSpoke) < 0 ? basePair.colorPhase1 : basePair.colorPhase2;
a41f334c 243 return lx.hsb((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, 80.f, b);
7992264a 244 }
ee1f21c7 245
34327c96 246 void run(double deltaMs) {
fd8a39b0
SG
247 boolean h1on = helix1On.getValue() > 0.5;
248 boolean h2on = helix2On.getValue() > 0.5;
f904d86b 249 boolean spokesOn = (float)basePairsOn.getValue() > 0.5;
ee1f21c7 250
fd8a39b0
SG
251 h1.step(deltaMs);
252 h2.step(deltaMs);
53d41764 253 calculateSpokes();
ee1f21c7 254
2bb56822 255 for (LXPoint p : model.points) {
f904d86b
SG
256 PVector pt = new PVector(p.x,p.y,p.z);
257 color h1c = h1.colorOfPoint(pt);
258 color h2c = h2.colorOfPoint(pt);
53d41764 259 color spokeColor = calculateSpokeColor(pt);
f904d86b
SG
260
261 if (!h1on) {
a41f334c 262 h1c = lx.hsb(0,0,0);
f904d86b 263 }
ee1f21c7 264
f904d86b 265 if (!h2on) {
a41f334c 266 h2c = lx.hsb(0,0,0);
fd8a39b0 267 }
ee1f21c7 268
f904d86b 269 if (!spokesOn) {
a41f334c 270 spokeColor = lx.hsb(0,0,0);
fd8a39b0 271 }
ee1f21c7 272
fd8a39b0
SG
273 // The helices are positioned to not overlap. If that changes,
274 // a better blending formula is probably needed.
f904d86b 275 colors[p.index] = blendColor(blendColor(h1c, h2c, ADD), spokeColor, ADD);
fd8a39b0
SG
276 }
277 }
278}
279