1 import toxi.geom.Vec3D;
2 import toxi.geom.Matrix4x4;
4 class HelixPattern extends SCPattern {
6 // Stores a line in point + vector form
8 private final PVector origin;
9 private final PVector vector;
11 Line(PVector pt, PVector v) {
25 PVector getPointAt(final float t) {
26 return PVector.add(origin, PVector.mult(vector, t));
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;
34 float getTValue(final PVector pt) {
35 PVector subtraction = PVector.sub(pt, origin);
36 return subtraction.dot(vector);
39 PVector projectPoint(final PVector pt) {
40 return getPointAt(getTValue(pt));
43 PVector rotatePoint(final PVector p, final float t) {
44 final PVector o = origin;
45 final PVector v = vector;
47 final float cost = cos(t);
48 final float sint = sin(t);
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;
53 return new PVector(x, y, z);
58 private final Line axis;
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
63 private final PVector referencePoint;
65 private PVector phaseNormal;
67 Helix(Line axis, float period, float radius, float girth, float phase, float rotationPeriod) {
73 this.rotationPeriod = rotationPeriod;
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);
85 this.referencePoint = pt;
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);
90 phaseNormal.normalize();
91 phaseNormal.mult(radius);
98 PVector getPhaseNormal() {
106 void step(int deltaMs) {
108 if (rotationPeriod != 0) {
109 this.phase = (phase + ((float)deltaMs / (float)rotationPeriod) * TWO_PI);
113 PVector pointOnToroidalAxis(float t) {
114 PVector p = axis.getPointAt(t);
115 PVector middle = PVector.add(p, phaseNormal);
116 return axis.rotatePoint(middle, (t / period) * TWO_PI + phase);
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);
126 color colorOfPoint(final PVector p) {
127 final float t = axis.getTValue(p);
128 final PVector axisPoint = axis.getPointAt(t);
130 // For performance reasons, cut out points that are outside of
131 // the tube where the toroidal coil lives.
132 if (abs(myDist(p, axisPoint) - radius) > girth*.5f) {
136 // Find the appropriate point for the current rotation
138 PVector toroidPoint = axisPoint;
139 toroidPoint.add(phaseNormal);
140 toroidPoint = axis.rotatePoint(toroidPoint, (t / period) * TWO_PI + phase);
142 // The rotated point represents the middle of the girth of
143 // the helix. Figure out if the current point is inside that
145 float d = myDist(p, toroidPoint);
147 // Soften edges by fading brightness.
148 float b = constrain(100*(1 - ((d-.5*girth)/(girth*.5))), 0, 100);
149 return color((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, 80, b);
153 private final Helix h1;
154 private final Helix h2;
156 private final BasicParameter helix1On = new BasicParameter("H1ON", 1);
157 private final BasicParameter helix2On = new BasicParameter("H2ON", 1);
158 private final BasicParameter basePairsOn = new BasicParameter("BPON", 1);
160 private static final float helixCoilPeriod = 100;
161 private static final float helixCoilRadius = 45;
162 private static final float helixCoilGirth = 20;
163 private static final float helixCoilRotationPeriod = 10000;
165 private static final float spokePeriod = 40;
166 private static final float spokeGirth = 10;
167 private static final float spokePhase = 10;
168 private static final float spokeRadius = 35; // helixCoilRadius - helixCoilGirth*.5f;
170 public HelixPattern(GLucose glucose) {
173 addParameter(helix1On);
174 addParameter(helix2On);
175 addParameter(basePairsOn);
177 PVector origin = new PVector(100, 50, 45);
178 PVector axis = new PVector(1,0,0);
181 new Line(origin, axis),
186 helixCoilRotationPeriod);
188 new Line(origin, axis),
193 helixCoilRotationPeriod);
196 private color calculateSpokeColor(final color h1c, final color h2c, final PVector pt) {
197 // Find the closest spoke's t-value and calculate its
198 // axis. Until everything animates in the model reference
199 // frame, this has to be calculated at every step because
200 // the helices rotate.
201 Line axis = h1.getAxis();
202 float t = axis.getTValue(pt) + spokePhase;
203 float spokeAxisTValue = floor(((t + spokePeriod/2) / spokePeriod)) * spokePeriod;
204 PVector h1point = axis.getPointAt(t);
205 h1point.add(h1.getPhaseNormal());
206 h1point = axis.rotatePoint(h1point, (t / helixCoilPeriod) * TWO_PI + h1.getPhase());
207 // TODO(shaheen) investigate why h1.getAxis().getPointAt(spokeAxisTValue) doesn't quite
208 // have the same value as finding the middle between h1point and h2point.
209 PVector spokeCenter = h1.getAxis().getPointAt(spokeAxisTValue);
210 PVector spokeVector = PVector.sub(h1point, spokeCenter);
211 spokeVector.normalize();
212 Line spokeLine = new Line(h1point, spokeVector);
213 PVector pointOnSpoke = spokeLine.projectPoint(pt);
214 float b = ((PVector.dist(pt, pointOnSpoke) < spokeGirth) && (PVector.dist(pointOnSpoke, spokeCenter) < spokeRadius)) ? 100.f : 0.f;
215 return color(100, 80.f, b);
218 void run(int deltaMs) {
219 boolean h1on = helix1On.getValue() > 0.5;
220 boolean h2on = helix2On.getValue() > 0.5;
221 boolean spokesOn = (float)basePairsOn.getValue() > 0.5;
226 for (Point p : model.points) {
227 PVector pt = new PVector(p.x,p.y,p.z);
228 color h1c = h1.colorOfPoint(pt);
229 color h2c = h2.colorOfPoint(pt);
230 color spokeColor = calculateSpokeColor(h1c, h2c, pt);
241 spokeColor = color(0,0,0);
244 // The helices are positioned to not overlap. If that changes,
245 // a better blending formula is probably needed.
246 colors[p.index] = blendColor(blendColor(h1c, h2c, ADD), spokeColor, ADD);