[Helix] Base pairs now render
[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
SG
42
43 PVector rotatePoint(final PVector pt, final float rads) {
fd8a39b0 44 Vec3D axisVec3D = new Vec3D(vector.x, vector.y, vector.z);
ffb4b60e
SG
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);
fd8a39b0
SG
50 return new PVector(rotatedPt.x, rotatedPt.y, rotatedPt.z);
51 }
52 }
53
54 private class Helix {
55 private final Line axis;
ee1f21c7
SG
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
fd8a39b0
SG
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;
fd8a39b0 83
ee1f21c7
SG
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);
fd8a39b0
SG
87 phaseNormal.normalize();
88 phaseNormal.mult(radius);
89 }
ee1f21c7 90
ee1f21c7
SG
91 Line getAxis() {
92 return axis;
93 }
94
fd8a39b0 95 void step(int deltaMs) {
ee1f21c7
SG
96 // Rotate
97 if (rotationPeriod != 0) {
117f538a 98 this.phase = (phase + ((float)deltaMs / (float)rotationPeriod) * TWO_PI);
ee1f21c7 99 }
fd8a39b0 100 }
ee1f21c7 101
fd8a39b0
SG
102 PVector pointOnToroidalAxis(float t) {
103 PVector p = axis.getPointAt(t);
104 PVector middle = PVector.add(p, phaseNormal);
117f538a 105 return axis.rotatePoint(middle, (t / period) * TWO_PI + phase);
fd8a39b0 106 }
ee1f21c7
SG
107
108 color colorOfPoint(final PVector p) {
fd8a39b0
SG
109 // Find the appropriate point for the current rotation
110 // of the helix.
117f538a 111 float t = axis.getTValue(p);
fd8a39b0 112 PVector toroidPoint = pointOnToroidalAxis(t);
ee1f21c7 113
fd8a39b0
SG
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);
ee1f21c7 118
117f538a 119 // Soften edges by fading brightness.
c27cb078 120 float b = constrain(100*(1 - ((d-.5*girth)/(girth*.5))), 0, 100);
117f538a 121 return color((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, 80, b);
fd8a39b0
SG
122 }
123 }
124
125 private final Helix h1;
126 private final Helix h2;
ee1f21c7 127
fd8a39b0
SG
128 private final BasicParameter helix1On = new BasicParameter("H1ON", 1);
129 private final BasicParameter helix2On = new BasicParameter("H2ON", 1);
ee1f21c7 130
f904d86b
SG
131 private final BasicParameter basePairsOn = new BasicParameter("BPON", 1);
132 private final BasicParameter spokePeriodParam = new BasicParameter("SPPD", 0.40);
133 private final BasicParameter spokePhaseParam = new BasicParameter("SPPH", 0.25);
134
135 private static final float helixCoilPeriod = 100;
136 private static final float helixCoilRadius = 45;
137 private static final float helixCoilGirth = 20;
138 private static final float helixCoilRotationPeriod = 10000;
139
fd8a39b0
SG
140 public HelixPattern(GLucose glucose) {
141 super(glucose);
ee1f21c7 142
fd8a39b0
SG
143 addParameter(helix1On);
144 addParameter(helix2On);
f904d86b
SG
145 addParameter(basePairsOn);
146 addParameter(spokePhaseParam);
147 addParameter(spokePeriodParam);
148
149 PVector origin = new PVector(100, 50, 45);
150 PVector axis = new PVector(1,0,0);
ee1f21c7 151
fd8a39b0 152 h1 = new Helix(
f904d86b
SG
153 new Line(origin, axis),
154 helixCoilPeriod,
155 helixCoilRadius,
156 helixCoilGirth,
157 0,
158 helixCoilRotationPeriod);
fd8a39b0 159 h2 = new Helix(
f904d86b
SG
160 new Line(origin, axis),
161 helixCoilPeriod,
162 helixCoilRadius,
163 helixCoilGirth,
fd8a39b0 164 PI,
f904d86b 165 helixCoilRotationPeriod);
fd8a39b0 166 }
ee1f21c7 167
fd8a39b0
SG
168 void run(int deltaMs) {
169 boolean h1on = helix1On.getValue() > 0.5;
170 boolean h2on = helix2On.getValue() > 0.5;
f904d86b
SG
171 boolean spokesOn = (float)basePairsOn.getValue() > 0.5;
172 float spokePeriod = (float)spokePeriodParam.getValue() * 100 + 1;
173 float spokeGirth = 10;
174 float spokePhase = (float)spokePhaseParam.getValue() * spokePeriod;
175 float spokeRadius = helixCoilRadius - helixCoilGirth*.5f;
ee1f21c7 176
fd8a39b0
SG
177 h1.step(deltaMs);
178 h2.step(deltaMs);
ee1f21c7 179
fd8a39b0 180 for (Point p : model.points) {
f904d86b
SG
181 PVector pt = new PVector(p.x,p.y,p.z);
182 color h1c = h1.colorOfPoint(pt);
183 color h2c = h2.colorOfPoint(pt);
184
185 // Find the closest spoke's t-value and calculate its
186 // axis. Until everything animates in the model reference
187 // frame, this has to be calculated at every step because
188 // the helices rotate.
189 float t = h1.getAxis().getTValue(pt) + spokePhase;
190 float spokeAxisTValue = floor(((t + spokePeriod/2) / spokePeriod)) * spokePeriod;
191 PVector h1point = h1.pointOnToroidalAxis(spokeAxisTValue);
192 PVector h2point = h2.pointOnToroidalAxis(spokeAxisTValue);
193 PVector spokeVector = PVector.sub(h2point, h1point);
194 spokeVector.normalize();
195 Line spokeLine = new Line(h1point, spokeVector);
196 float spokeLength = PVector.dist(h1point, h2point);
197 // TODO(shaheen) investigate why h1.getAxis().getPointAt(spokeAxisTValue) doesn't quite
198 // have the same value.
199 PVector spokeCenter = PVector.add(h1point, PVector.mult(spokeVector, spokeLength/2.f));
200 PVector spokeStart = PVector.add(spokeCenter, PVector.mult(spokeLine.getVector(), -spokeRadius));
201 PVector spokeEnd = PVector.add(spokeCenter, PVector.mult(spokeLine.getVector(), spokeRadius));
202 float spokeStartTValue = spokeLine.getTValue(spokeStart);
203 float spokeEndTValue = spokeLine.getTValue(spokeEnd);
204 PVector pointOnSpoke = spokeLine.projectPoint(pt);
205 float projectedTValue = spokeLine.getTValue(pointOnSpoke);
206 float percentage = constrain(PVector.dist(pointOnSpoke, spokeStart) / spokeLength, 0.f, 1.f);
207 float b = ((PVector.dist(pt, pointOnSpoke) < spokeGirth) && (PVector.dist(pointOnSpoke, spokeCenter) < spokeRadius)) ? 100.f : 0.f;
208
209 color spokeColor;
210
211 if (spokeStartTValue < spokeEndTValue) {
212 spokeColor = lerpColor(h1c, h2c, percentage);
213 } else {
214 spokeColor = lerpColor(h2c, h1c, percentage);
215 }
216
217 spokeColor = color(hue(spokeColor), 80.f, b);
218
219 if (!h1on) {
220 h1c = color(0,0,0);
221 }
ee1f21c7 222
f904d86b
SG
223 if (!h2on) {
224 h2c = color(0,0,0);
fd8a39b0 225 }
ee1f21c7 226
f904d86b
SG
227 if (!spokesOn) {
228 spokeColor = color(0,0,0);
fd8a39b0 229 }
ee1f21c7 230
fd8a39b0
SG
231 // The helices are positioned to not overlap. If that changes,
232 // a better blending formula is probably needed.
f904d86b 233 colors[p.index] = blendColor(blendColor(h1c, h2c, ADD), spokeColor, ADD);
fd8a39b0
SG
234 }
235 }
236}
237