X-Git-Url: https://git.piment-noir.org/?p=SugarCubes.git;a=blobdiff_plain;f=ShaheenGandhi.pde;h=cda8fed4358c5b18302cdce3c6394179456a11c4;hp=740883ddbb71b50da56d6b042c46453844a3b21d;hb=e0b9d8726c661f54f47340449037ac7f6b410de6;hpb=c27cb07897f82f0b9f515587855a1473def2a312 diff --git a/ShaheenGandhi.pde b/ShaheenGandhi.pde index 740883d..cda8fed 100644 --- a/ShaheenGandhi.pde +++ b/ShaheenGandhi.pde @@ -7,57 +7,59 @@ class HelixPattern extends SCPattern { private class Line { private final PVector origin; private final PVector vector; - + Line(PVector pt, PVector v) { origin = pt; vector = v.get(); vector.normalize(); } - + PVector getPoint() { return origin; } - + PVector getVector() { return vector; } - - PVector getPointAt(float t) { - PVector pt = PVector.mult(vector, t); - pt.add(origin); - return pt; + + PVector getPointAt(final float t) { + return PVector.add(origin, PVector.mult(vector, t)); } - - boolean isColinear(PVector pt) { - PVector projected = projected(pt); + + boolean isColinear(final PVector pt) { + PVector projected = projectPoint(pt); return projected.x==pt.x && projected.y==pt.y && projected.z==pt.z; } - - float getTValue(PVector pt) { + + float getTValue(final PVector pt) { PVector subtraction = PVector.sub(pt, origin); return subtraction.dot(vector); - } - - PVector projected(PVector pt) { + } + + PVector projectPoint(final PVector pt) { return getPointAt(getTValue(pt)); } - - PVector rotatePoint(PVector pt, float rads) { - Vec3D axisVec3D = new Vec3D(vector.x, vector.y, vector.z); - Matrix4x4 mat = new Matrix4x4(); - mat.rotateAroundAxis(axisVec3D, rads); - Vec3D ptVec3D = new Vec3D(pt.x, pt.y, pt.z); - Vec3D rotatedPt = mat.applyTo(ptVec3D); - 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); } } private class Helix { private final Line axis; - private final float period; - private final float rotationPeriod; - private final float radius; - private final float girth; + private final float period; // period of coil + private final float rotationPeriod; // animation period + private final float radius; // radius of coil + private final float girth; // girth of coil private final PVector referencePoint; private float phase; private PVector phaseNormal; @@ -81,110 +83,196 @@ class HelixPattern extends SCPattern { } this.referencePoint = pt; - this.phase = phase; - setPhaseNormalFromPhase(); - } - - private void setPhaseNormalFromPhase() { - phaseNormal = axis.getVector().cross(axis.rotatePoint(referencePoint, phase)); + // The normal is calculated by the cross product of the axis + // and a random point that is not colinear with it. + phaseNormal = axis.getVector().cross(referencePoint); phaseNormal.normalize(); phaseNormal.mult(radius); } - - private void setPhase(float phase) { - this.phase = phase; - setPhaseNormalFromPhase(); + + Line getAxis() { + return axis; } - void step(int deltaMs) { - setPhase(phase + (deltaMs / rotationPeriod) * TWO_PI); + PVector getPhaseNormal() { + return phaseNormal; } + float getPhase() { + return phase; + } + + void step(double deltaMs) { + // Rotate + if (rotationPeriod != 0) { + this.phase = (phase + ((float)deltaMs / (float)rotationPeriod) * TWO_PI); + } + } + PVector pointOnToroidalAxis(float t) { PVector p = axis.getPointAt(t); PVector middle = PVector.add(p, phaseNormal); - return axis.rotatePoint(middle, (t / period) * TWO_PI); + return axis.rotatePoint(middle, (t / period) * TWO_PI + phase); } - color colorOfPoint(PVector p) { - // Calculate the projection of this point to the axis. - PVector projectedPoint = axis.projected(p); - + private float myDist(PVector p1, PVector p2) { + final float x = p2.x-p1.x; + final float y = p2.y-p1.y; + final float z = p2.z-p1.z; + return sqrt(x*x + y*y + z*z); + } + + color colorOfPoint(final PVector p) { + final float t = axis.getTValue(p); + final PVector axisPoint = axis.getPointAt(t); + + // For performance reasons, cut out points that are outside of + // the tube where the toroidal coil lives. + if (abs(myDist(p, axisPoint) - radius) > girth*.5f) { + return lx.hsb(0,0,0); + } + // Find the appropriate point for the current rotation // of the helix. - float t = axis.getTValue(projectedPoint); - PVector toroidPoint = pointOnToroidalAxis(t); - + PVector toroidPoint = axisPoint; + toroidPoint.add(phaseNormal); + toroidPoint = axis.rotatePoint(toroidPoint, (t / period) * TWO_PI + phase); + // The rotated point represents the middle of the girth of // the helix. Figure out if the current point is inside that // region. - float d = PVector.dist(p, toroidPoint); - boolean inToroid = d < girth; - - // Soften edges by fading brightness + float d = myDist(p, toroidPoint); + + // Soften edges by fading brightness. float b = constrain(100*(1 - ((d-.5*girth)/(girth*.5))), 0, 100); - return color((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, (inToroid ? 80 : 0), b); + return lx.hsb((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, 80, b); + } + } + + private class BasePairInfo { + Line line; + float colorPhase1; + float colorPhase2; + + BasePairInfo(Line line, float colorPhase1, float colorPhase2) { + this.line = line; + this.colorPhase1 = colorPhase1; + this.colorPhase2 = colorPhase2; } } private final Helix h1; private final Helix h2; - + private final BasePairInfo[] basePairs; + 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 = 50; + private static final float helixCoilGirth = 30; + private static final float helixCoilRotationPeriod = 5000; + + private static final float spokePeriod = 40; + private static final float spokeGirth = 20; + private static final float spokePhase = 10; + private static final float spokeRadius = helixCoilRadius - helixCoilGirth*.5f; - public HelixPattern(GLucose glucose) { - super(glucose); - + private static final float tMin = -200; + private static final float tMax = 200; + + public HelixPattern(LX lx) { + super(lx); + addParameter(helix1On); addParameter(helix2On); - + addParameter(basePairsOn); + + PVector origin = new PVector(100, 50, 55); + PVector axis = new PVector(1,0,0); + h1 = new Helix( - new Line(new PVector(100, 50, 70), new PVector(1,0,0)), - 700, // period - 50, // 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)), - 700, - 50, - 30, + new Line(origin, axis), + helixCoilPeriod, + helixCoilRadius, + helixCoilGirth, PI, - 10000); + helixCoilRotationPeriod); + + basePairs = new BasePairInfo[(int)floor((tMax - tMin)/spokePeriod)]; + } - // 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. - + private void calculateSpokes() { + float colorPhase = PI/6; + for (float t = tMin + spokePhase; t < tMax; t += spokePeriod) { + int spokeIndex = (int)floor((t - tMin)/spokePeriod); + PVector h1point = h1.pointOnToroidalAxis(t); + PVector spokeCenter = h1.getAxis().getPointAt(t); + PVector spokeVector = PVector.sub(h1point, spokeCenter); + Line spokeLine = new Line(spokeCenter, spokeVector); + basePairs[spokeIndex] = new BasePairInfo(spokeLine, colorPhase * spokeIndex, colorPhase * (spokeIndex + 1)); + } } - void run(int deltaMs) { + private color calculateSpokeColor(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. + Line axis = h1.getAxis(); + float t = axis.getTValue(pt) + spokePhase; + int spokeIndex = (int)floor((t - tMin + spokePeriod/2) / spokePeriod); + if (spokeIndex < 0 || spokeIndex >= basePairs.length) { + return lx.hsb(0,0,0); + } + BasePairInfo basePair = basePairs[spokeIndex]; + Line spokeLine = basePair.line; + PVector pointOnSpoke = spokeLine.projectPoint(pt); + float d = PVector.dist(pt, pointOnSpoke); + float b = (PVector.dist(pointOnSpoke, spokeLine.getPoint()) < spokeRadius) ? constrain(100*(1 - ((d-.5*spokeGirth)/(spokeGirth*.5))), 0, 100) : 0.f; + float phase = spokeLine.getTValue(pointOnSpoke) < 0 ? basePair.colorPhase1 : basePair.colorPhase2; + return lx.hsb((lx.getBaseHuef() + (360*(phase / TWO_PI)))%360, 80.f, b); + } + + void run(double 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); - - if (h1on) { - h1c = h1.colorOfPoint(new PVector(p.x,p.y,p.z)); + calculateSpokes(); + + for (LXPoint p : model.points) { + PVector pt = new PVector(p.x,p.y,p.z); + color h1c = h1.colorOfPoint(pt); + color h2c = h2.colorOfPoint(pt); + color spokeColor = calculateSpokeColor(pt); + + if (!h1on) { + h1c = lx.hsb(0,0,0); } - - if (h2on) { - h2c = h2.colorOfPoint(new PVector(p.x,p.y,p.z)); + + if (!h2on) { + h2c = lx.hsb(0,0,0); } - + + if (!spokesOn) { + spokeColor = lx.hsb(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); } } }