+ public void run(double deltaMs) {
+ float hv = lx.getBaseHuef();
+ for (LXPoint p : model.points) {
+ float bv = max(0, 100 - abs(p.z - zPos.getValuef()));
+ colors[p.index] = lx.hsb(hv, 100, bv);
+ }
+ }
+}
+
+/**
+ * This shows how to iterate over towers, enumerated in the model.
+ */
+class TestTowerPattern extends TestPattern {
+ private final SawLFO towerIndex = new SawLFO(0, model.towers.size(), 1000*model.towers.size());
+
+ public TestTowerPattern(LX lx) {
+ super(lx);
+ addModulator(towerIndex).trigger();
+ }
+
+ public void run(double deltaMs) {
+ int ti = 0;
+ for (Tower t : model.towers) {
+ for (LXPoint p : t.points) {
+ colors[p.index] = lx.hsb(
+ lx.getBaseHuef(),
+ 100,
+ max(0, 100 - 80*LXUtils.wrapdistf(ti, towerIndex.getValuef(), model.towers.size()))
+ );
+ }
+ ++ti;
+ }
+ }
+
+}
+
+/**
+ * This is a demonstration of how to use the projection library. A projection
+ * creates a mutation of the coordinates of all the points in the model, creating
+ * virtual x,y,z coordinates. In effect, this is like virtually rotating the entire
+ * art car. However, since in reality the car does not move, the result is that
+ * it appears that the object we are drawing on the car is actually moving.
+ *
+ * Keep in mind that what we are creating a projection of is the view coordinates.
+ * Depending on your intuition, some operations may feel backwards. For instance,
+ * if you translate the view to the right, it will make it seem that the object
+ * you are drawing has moved to the left. If you scale the view up 2x, objects
+ * drawn with the same absolute values will seem to be half the size.
+ *
+ * If this feels counterintuitive at first, don't worry. Just remember that you
+ * are moving the pixels, not the structure. We're dealing with a finite set
+ * of sparse, non-uniformly spaced pixels. Mutating the structure would move
+ * things to a space where there are no pixels in 99% of the cases.
+ */
+class TestProjectionPattern extends TestPattern {
+
+ private final LXProjection projection;
+ private final SawLFO angle = new SawLFO(0, TWO_PI, 9000);
+ private final SinLFO yPos = new SinLFO(-20, 40, 5000);
+
+ public TestProjectionPattern(LX lx) {
+ super(lx);
+ projection = new LXProjection(model);
+ addModulator(angle).trigger();
+ addModulator(yPos).trigger();
+ }
+
+ public void run(double deltaMs) {
+ // For the same reasons described above, it may logically feel to you that
+ // some of these operations are in reverse order. Again, just keep in mind that
+ // the car itself is what's moving, not the object
+ projection.reset()
+
+ // Translate so the center of the car is the origin, offset by yPos
+ .translateCenter(0, yPos.getValuef(), 0)
+
+ // Rotate around the origin (now the center of the car) about an X-vector
+ .rotate(angle.getValuef(), 1, 0, 0)
+
+ // Scale up the Y axis (objects will look smaller in that access)
+ .scale(1, 1.5, 1);
+
+ float hv = lx.getBaseHuef();
+ for (LXVector c : projection) {
+ float d = sqrt(c.x*c.x + c.y*c.y + c.z*c.z); // distance from origin
+ // d = abs(d-60) + max(0, abs(c.z) - 20); // life saver / ring thing
+ d = max(0, abs(c.y) - 10 + .1*abs(c.z) + .02*abs(c.x)); // plane / spear thing
+ colors[c.index] = lx.hsb(
+ (hv + .6*abs(c.x) + abs(c.z)) % 360,