return getPointAt(getTValue(pt));
}
- PVector rotatePoint(final PVector pt, final float rads) {
- Vec3D axisVec3D = new Vec3D(vector.x, vector.y, vector.z);
- Vec3D originVec3D = new Vec3D(origin.x, origin.y, origin.z);
- Matrix4x4 mat = new Matrix4x4().identity()
- .rotateAroundAxis(axisVec3D, rads);
- Vec3D ptVec3D = new Vec3D(pt.x, pt.y, pt.z).sub(originVec3D);
- Vec3D rotatedPt = mat.applyTo(ptVec3D).add(originVec3D);
- return new PVector(rotatedPt.x, rotatedPt.y, rotatedPt.z);
+ PVector rotatePoint(final PVector p, final float t) {
+ final PVector o = origin;
+ final PVector v = vector;
+
+ final float cost = cos(t);
+ final float sint = sin(t);
+
+ 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;
+ 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;
+ 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;
+ return new PVector(x, y, z);
}
}
}
color colorOfPoint(final PVector p) {
+ float t = axis.getTValue(p);
+
+ // For performance reasons, cut out points that are outside of
+ // the tube where the toroidal coil lives.
+ if (abs(PVector.dist(p, axis.getPointAt(t)) - radius) > girth*.5f) {
+ return color(0,0,0);
+ }
+
// Find the appropriate point for the current rotation
// of the helix.
- float t = axis.getTValue(p);
PVector toroidPoint = pointOnToroidalAxis(t);
// The rotated point represents the middle of the girth of
private final BasicParameter helix1On = new BasicParameter("H1ON", 1);
private final BasicParameter helix2On = new BasicParameter("H2ON", 1);
+ private final BasicParameter basePairsOn = new BasicParameter("BPON", 1);
+
+ private static final float helixCoilPeriod = 100;
+ private static final float helixCoilRadius = 45;
+ private static final float helixCoilGirth = 20;
+ private static final float helixCoilRotationPeriod = 10000;
+
+ private static final float spokePeriod = 40;
+ private static final float spokeGirth = 10;
+ private static final float spokePhase = 10;
+ private static final float spokeRadius = 35; // helixCoilRadius - helixCoilGirth*.5f;
public HelixPattern(GLucose glucose) {
super(glucose);
addParameter(helix1On);
addParameter(helix2On);
+ addParameter(basePairsOn);
+
+ PVector origin = new PVector(100, 50, 45);
+ PVector axis = new PVector(1,0,0);
h1 = new Helix(
- new Line(new PVector(100, 50, 45), new PVector(1,0,0)),
- 100, // period
- 40, // radius
- 30, // girth
- 0, // phase
- 10000); // rotation period (ms)
+ new Line(origin, axis),
+ helixCoilPeriod,
+ helixCoilRadius,
+ helixCoilGirth,
+ 0,
+ helixCoilRotationPeriod);
h2 = new Helix(
- new Line(new PVector(100, 50, 70), new PVector(1,0,0)),
- 100,
- 40,
- 30,
+ new Line(origin, axis),
+ helixCoilPeriod,
+ helixCoilRadius,
+ helixCoilGirth,
PI,
- 10000);
-
- // TODO(shaheen) calculate line segments between
- // toroidal points selected by stepping the
- // parameterized t value. select base pairs and
- // associated colors. lerp between colors for each
- // base pair to produce a DNA effect.
-
+ helixCoilRotationPeriod);
+ }
+
+ private color calculateSpokeColor(final color h1c, final color h2c, final PVector pt) {
+ // Find the closest spoke's t-value and calculate its
+ // axis. Until everything animates in the model reference
+ // frame, this has to be calculated at every step because
+ // the helices rotate.
+ float t = h1.getAxis().getTValue(pt) + spokePhase;
+ float spokeAxisTValue = floor(((t + spokePeriod/2) / spokePeriod)) * spokePeriod;
+ PVector h1point = h1.pointOnToroidalAxis(spokeAxisTValue);
+ // TODO(shaheen) investigate why h1.getAxis().getPointAt(spokeAxisTValue) doesn't quite
+ // have the same value as finding the middle between h1point and h2point.
+ PVector spokeCenter = h1.getAxis().getPointAt(spokeAxisTValue);
+ PVector spokeVector = PVector.sub(h1point, spokeCenter);
+ spokeVector.normalize();
+ Line spokeLine = new Line(h1point, spokeVector);
+ PVector pointOnSpoke = spokeLine.projectPoint(pt);
+ float b = ((PVector.dist(pt, pointOnSpoke) < spokeGirth) && (PVector.dist(pointOnSpoke, spokeCenter) < spokeRadius)) ? 100.f : 0.f;
+ return color(100, 80.f, b);
}
void run(int deltaMs) {
boolean h1on = helix1On.getValue() > 0.5;
boolean h2on = helix2On.getValue() > 0.5;
+ boolean spokesOn = (float)basePairsOn.getValue() > 0.5;
h1.step(deltaMs);
h2.step(deltaMs);
for (Point p : model.points) {
- color h1c = color(0,0,0);
- color h2c = color(0,0,0);
+ PVector pt = new PVector(p.x,p.y,p.z);
+ color h1c = h1.colorOfPoint(pt);
+ color h2c = h2.colorOfPoint(pt);
+ color spokeColor = calculateSpokeColor(h1c, h2c, pt);
+
+ if (!h1on) {
+ h1c = color(0,0,0);
+ }
- if (h1on) {
- h1c = h1.colorOfPoint(new PVector(p.x,p.y,p.z));
+ if (!h2on) {
+ h2c = color(0,0,0);
}
- if (h2on) {
- h2c = h2.colorOfPoint(new PVector(p.x,p.y,p.z));
+ if (!spokesOn) {
+ spokeColor = color(0,0,0);
}
// The helices are positioned to not overlap. If that changes,
// a better blending formula is probably needed.
- colors[p.index] = blendColor(h1c, h2c, ADD);
+ colors[p.index] = blendColor(blendColor(h1c, h2c, ADD), spokeColor, ADD);
}
}
}