Commit be93ed58 authored by Ashley Chen's avatar Ashley Chen
Browse files

Merge branch 'dev/pal' into 'master'

UI, Placement, and Data Model updates

See merge request !16
parents a2b909f1 9231c98d
/**
* The FLIGHT GUI window that draws the stairwell of the Packard
* building at Stanford with FLIGHT's Fractal Flyers. The Fractal
* Flyers visualize light colors and wing position.
*
* @author Philip Levis <pal@cs.stanford.edu>
*/
class UIFlightDrawer extends UI3dComponent implements GeometryConstants {
// These variables are all derived from constants in GeometryConstants -pal
// The X offset of the vertical bars on the front window. These are
// used as reference points for drawing the overhead pipes as well
// as the front window bars.
private final float[] VBAR_OFFSETS;
// The Y offset of the horizontal bars on the front window. These are
// used as reference points for drawing the front window as well as
// drawing Fractal Flyers that are hanging off the front window.
private final float[] HBAR_OFFSETS;
// The X and Z offset of reference points on the back wall. These
// correspond to a subset of the VBAR_OFFSETS, but rotated -P/4 along
// the Y axis.
private final float[] BACK_WALL_OFFSETS;
FlightModel model;
UIFlightDrawer(FlightModel model) {
super();
this.model = model;
VBAR_OFFSETS = new float[GeometryConstants.FRONT_WINDOW_VBAR_SPACINGS.length];
float xPos = 0.0;
for (int i = 0; i < GeometryConstants.FRONT_WINDOW_VBAR_SPACINGS.length; i++) {
xPos += GeometryConstants.FRONT_WINDOW_VBAR_SPACINGS[i];
VBAR_OFFSETS[i] = xPos;
}
HBAR_OFFSETS = new float[GeometryConstants.FRONT_WINDOW_HBAR_SPACINGS.length];
float yPos = 0.0;
for (int i = 0; i < GeometryConstants.FRONT_WINDOW_HBAR_SPACINGS.length; i++) {
yPos += GeometryConstants.FRONT_WINDOW_HBAR_SPACINGS[i];
HBAR_OFFSETS[i] = yPos;
}
BACK_WALL_OFFSETS = new float[GeometryConstants.BACK_WALL_SPACINGS.length];
yPos = 0;
for (int i = 0; i < GeometryConstants.BACK_WALL_SPACINGS.length; i++) {
yPos += GeometryConstants.BACK_WALL_SPACINGS[i];
BACK_WALL_OFFSETS[i] = yPos;
}
}
protected void onDraw(UI ui, PGraphics pg) {
try {
pg.pushMatrix();
drawBuilding(pg);
drawFlyers(pg);
pg.popMatrix();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
/**
* Draw all of the Fractal Flyers.
*
*/
private void drawFlyers(PGraphics pg) {
computeFlyerLightsAndWings();
// We draw each flyer by first computing a geometric
// transformation (matrix) for its position and orientation,
// drawing it, then popping the tranformation. This allows the
// draw code to just operate in a normalized coordinate space. -pal
for (Flyer flyer : model.getFlyers()) {
pg.pushMatrix();
float x = flyer.getX();
float y = flyer.getY();
float z = flyer.getZ();
pg.translate(x, y, z);
pg.rotateY(-flyer.getRotation() * (float)Math.PI / 180);
pg.rotateZ(-flyer.getTilt() * (float)Math.PI / 180);
drawFlyer(pg, flyer);
pg.popMatrix();
}
}
/**
* Draw one Fractal Flyer at (0,0,0). its long dimension
* along the X axis, its narrow dimension along the Z axis,
* and its body hanging down in the Y axis.
*/
private void drawFlyer(PGraphics pg, Flyer flyer) {
LXEngine.Frame frame = lx.getUIFrame();
int colors[] = frame.getColors();
// All of these values are in inches, taken from
// FRACTAL_FLYER/drawings/body-dimensions-for-ui-graphics.pdf
pg.pushMatrix();
// (0, 0, 0) should be at the hanging point, so transate forward
// to that point
pg.translate(FLYER_HANGING_OFFSET, 0, 0);
pg.noStroke();
// top plate
pg.fill(#a0a0a0);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(3.33f, 0, 4.04f);
pg.vertex(26.26, 0, 0);
pg.vertex(3.33f, 0, -4.04f);
pg.endShape(CLOSE);
// Left wing
Wing leftWing = flyer.getLeftWing();
pg.fill(colors[wingLightIndex(flyer.getIndex(), false, 0)]); // Just use point 0 for the wing for now
pg.pushMatrix();
pg.translate(4.01, 0, 4.96);
pg.rotateY(PI/18); // The wing edge is 10 degrees from centerline
float langle = (float)leftWing.getSkew() / 90f * PI / 2f;
pg.rotateX(langle);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(cos(PI/3f) * 7.87f, 0, sin(PI/3f) * 7.87f);
pg.vertex(23.036, 0, 0);
pg.endShape(CLOSE);
pg.popMatrix();
// Right wing
Wing rightWing = flyer.getRightWing();
pg.fill(colors[wingLightIndex(flyer.getIndex(), true, 0)]); // Just use point 0 for the wing for now
pg.pushMatrix();
pg.translate(4.01, 0, -4.96);
pg.rotateY(- PI/18); // The wing edge is 10 degrees from centerline
float rangle = (float)rightWing.getSkew() / 90f * PI / 2f;
pg.rotateX(-rangle);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(cos(PI/3f) * 7.87f, 0, - sin(PI/3f) * 7.87f);
pg.vertex(23.036, 0, 0);
pg.endShape(CLOSE);
pg.popMatrix();
// Body shell
pg.fill(colors[bodyLightIndex(flyer.getIndex())]);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(3.33f, 0, 4.04f);
pg.vertex(5.29f, -4.39f, 0);
pg.endShape(CLOSE);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(3.33f, 0, -4.04f);
pg.vertex(5.29f, -4.39f, 0);
pg.endShape(CLOSE);
pg.beginShape();
pg.vertex(26.26, 0, 0);
pg.vertex(3.33f, 0, 4.04f);
pg.vertex(5.29f, -4.39f, 0);
pg.endShape(CLOSE);
pg.beginShape();
pg.vertex(26.26, 0, 0);
pg.vertex(3.33f, 0, -4.04f);
pg.vertex(5.29f, -4.39f, 0);
pg.endShape(CLOSE);
pg.popMatrix();
}
/**
* A hook for UI-specific additions to the visualization of the Fractal
* Flyers. Used, for example, to allow the UI to highlight a Fractal
* Flyer that has been selected in the physical layout tool.
*/
private void computeFlyerLightsAndWings() {
//flyerHighlighter.highlight();
}
/** Draws the Packard stairwell: the walls, the ceiling pipes, and
* the stairs themselves. Assumes that (0, 0, 0) is the narrow
* corner (left, if facing it from the outside), the front window
* runs along the X axis, and -Z is in front of the building.
*/
private void drawBuilding(PGraphics pg) {
drawWalls(pg);
drawCeiling(pg);
drawStairs(pg);
}
/**
* Draws a cylinder of radius r and height h, with one end
* centered at (0, 0, 0) and the other at (0, 0, h). If you want
* to move the cylinder, call this method having set up a matrix
* so these coordinates are at your desired position. Used to draw
* pipes in the ceiling of the stairwell. Uses whatever fill color
* was specified before calling.
*/
private void drawCylinder(PGraphics pg, float r, float h) {
int sides = 36;
float angle = 360 / sides;
// draw top shape
beginShape();
for (int i = 0; i < sides; i++) {
float x = cos( radians( i * angle ) ) * r;
float y = sin( radians( i * angle ) ) * r;
vertex( x, y, 0 );
}
endShape(CLOSE);
// draw bottom shape
beginShape();
for (int i = 0; i < sides; i++) {
float x = cos( radians( i * angle ) ) * r;
float y = sin( radians( i * angle ) ) * r;
vertex( x, y, h );
}
endShape(CLOSE);
// draw body
beginShape(TRIANGLE_STRIP);
for (int i = 0; i < sides + 1; i++) {
float x = cos( radians( i * angle ) ) * r;
float y = sin( radians( i * angle ) ) * r;
vertex( x, y, h);
vertex( x, y, 0);
}
endShape(CLOSE);
}
/** Draw a ceiling pipe from (x0, z0) to (x1, z1). Y is constant,
* at GeometryConstants.FRONT_WINDOW_NADIR_HEIGHT (the height of
* the pipe superstructure).
*/
private void drawCeilingPipe(PGraphics pg, float x0, float z0, float x1, float z1) {
pg.noStroke();
pg.fill(#c0c0c0);
pg.pushMatrix();
float y0 = FRONT_WINDOW_NADIR_HEIGHT + GeometryConstants.FRONT_WINDOW_BAR_WIDTH / 2.0f;
float y1 = FRONT_WINDOW_NADIR_HEIGHT + GeometryConstants.FRONT_WINDOW_BAR_WIDTH / 2.0f;
float xAngle = atan((x1 - x0)/(z1 - z0));
float length = sqrt((x1 - x0) * (x1 - x0) + (z1 - z0) * (z1 - z0));
pg.translate(x0, y0, z0);
pg.rotateY(xAngle);
drawCylinder(pg, 0.5f * GeometryConstants.FEET, length);
pg.popMatrix();
}
/**
* Draw all of the ceiling pipes. Their placement is based on the
* prow architectural drawing in the project repository. Four pipes
* stretch from the front window to the back wall, while four other
* pipes join with these to form two internal triangles.
*/
private void drawCeiling(PGraphics pg) {
// Because the back wall is at a 45 degree angle to the front window,
// the X and Z displacement is the same. -pal
// Draw straight pipes
for (int i = 0; i < BACK_WALL_OFFSETS.length; i++) {
float backWallOffset = BACK_WALL_OFFSETS[i] * 0.707;
// Straight pipe across
drawCeilingPipe(pg,
VBAR_OFFSETS[i * 2], 0.0,
backWallOffset, backWallOffset);
if (i == 1 || i == 2) {
float nextFX = VBAR_OFFSETS[2 * (i + 1)];
float nextFZ = 0.0f;
float nextBX = BACK_WALL_OFFSETS[i + 1] * 0.707;
float nextBZ = BACK_WALL_OFFSETS[i + 1] * 0.707;
float midpointX = nextFX + ((nextBX - nextFX) / 2.0);
float midpointZ = nextFZ + ((nextBZ - nextFZ)/ 2.0f);
// Two pipes of the V that meet at next one
drawCeilingPipe(pg,
VBAR_OFFSETS[i * 2], 0.0,
midpointX, midpointZ);
drawCeilingPipe(pg,
midpointX, midpointZ,
backWallOffset, backWallOffset);
}
}
}
/** Draw a flight of up or down stairs at a given yOffset (floor
* height). Facing the stairs from the back wall, up stairs are
* on the left (-x) while down stairs are on the right (+x). It
* also draws half of the corner platform.
*/
private void drawStairFlight(PGraphics pg, float yOffset, boolean up) {
pg.fill(#202020);
pg.pushMatrix();
pg.translate(0, yOffset, 0);
if (up) {
pg.pushMatrix();
pg.translate(-168.0f + 126.0f * sin(PI/8f),
0,
32.0f + 126f * cos(PI/8f));
pg.rotateY(PI/8f);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(0, 90f, 186f);
// This 128 is a hacked constant. I got sick of trying
// to do the triginometry. -pal
pg.vertex(0, 90f, 186f + 128f);
pg.vertex(69f, 90f, 186f);
pg.vertex(69f, 0, 0);
pg.endShape(CLOSE);
pg.popMatrix();
} else {
pg.pushMatrix();
pg.translate(168.0f - 126.0f * sin(PI/8f),
0,
32.0f + 126f * cos(PI/8f));
pg.rotateY(-PI/8f);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(0, -90f, 186f);
// This 128 is a hacked constant. I got sick of trying
// to do the triginometry. -pal
pg.vertex(0, -90f, 186f + 128f);
pg.vertex(-69f, -90f, 186f);
pg.vertex(-69f, 0, 0);
pg.endShape(CLOSE);
pg.popMatrix();
}
pg.popMatrix();
}
/** Draw a platform against the interior wall at a given yOffset;
* this is the platform that the doors from the interior of
* the building open on to, so you can take stairs up and down.
* Constants taken from the DWG model of the stairwell in the
* repository.
*/
private void drawPlatform(PGraphics pg, float yOffset) {
pg.fill(#202020);
pg.pushMatrix();
pg.translate(0, yOffset, 0);
pg.beginShape();
pg.vertex(126.0f, 0, 0.0f);
pg.vertex(126.0f, 0, 32.0f);
pg.vertex(168.0f, 0, 32.0f);
pg.vertex(168.0f - 126.0f * sin(PI/8f),
0,
32.0f + 126f * cos(PI/8f));
pg.vertex(168.0f - 126.0f * sin(PI/8f) - 69f * sin(5f / 8f * PI),
0,
32f + 126f * cos(PI/8f) + 69f * cos(5f / 8f * PI));
pg.vertex(168.0f - 126.0f * sin(PI/8f) - 69f * sin(5f / 8f * PI) + 21f * sin(PI/8f),
0,
32f + 126f * cos(PI/8f) + 69f * cos(5f / 8f * PI) - 21f * cos(PI/8f));
pg.vertex(-168.0f + 126.0f * sin(PI/8f) + 69f * sin(5f / 8f * PI) - 21f * sin(PI/8f),
0,
32f + 126f * cos(PI/8f) + 69f * cos(5f / 8f * PI) - 21f * cos(PI/8f));
pg.vertex(-168.0f + 126.0f * sin(PI/8f) + 69f * sin(5f / 8f * PI),
0,
32f + 126f * cos(PI/8f) + 69f * cos(5f / 8f * PI));
pg.vertex(-168.0f + 126.0f * sin(PI/8),
0,
32.0f + 126f * cos(PI/8));
pg.vertex(-168.0f, 0, 32.0f);
pg.vertex(-126.0f, 0, 32.0f);
pg.vertex(-126.0f, 0, 0.0f);
pg.endShape(CLOSE);
pg.popMatrix();
}
/**
* Draw all of the stairs: the platforms on the ground floor, floor 2,
* and floor 3 as well as the stairs that connec them.
*/
private void drawStairs(PGraphics pg) {
pg.pushMatrix();
// Translate to the midpoint of the center wall that
// the stair platforms are on; this is halfway between
// the end of the front wall (WIDTH) and the end of the back wall
// (0.707 * WIDTH) -pal
pg.translate(FRONT_WINDOW_WIDTH * (1f + 0.707) / 2f,
0f,
FRONT_WINDOW_WIDTH * (0.707) / 2f);
// The stairwell is 45 degrees; this means the mid angle from the
// center of the stair wall -22.5 degrees from along the X axis,
// so -(PI/4 + P/16)
pg.rotateY(- 5.0f/8.0f * PI);
drawPlatform(pg, 0f);
drawPlatform(pg, 180f);
drawPlatform(pg, 360f);
drawStairFlight(pg, 0, true);
drawStairFlight(pg, 180f, false);
drawStairFlight(pg, 180f, true);
drawStairFlight(pg, 360f, false);
pg.popMatrix();
}
/**
* Draw the back wall in white and the latticework on the front
* window.
*/
private void drawWalls(PGraphics pg) {
pg.fill(#c0c0c0);
pg.stroke(#202020);
// Vertical bars: need a polygon not a rectangle
// because their top has an angle with the roof.
for (int i = 0; i < VBAR_OFFSETS.length; i++) {
float offset = VBAR_OFFSETS[i];
float center = FRONT_WINDOW_VBAR_HEIGHTS[i];
pg.beginShape();
pg.vertex(offset, 0.0);
pg.vertex(offset + FRONT_WINDOW_BAR_WIDTH, 0);
pg.vertex(offset + FRONT_WINDOW_BAR_WIDTH, center);
pg.vertex(offset, center);
pg.endShape(CLOSE);
}
// Horizontal bars
for (float offset: HBAR_OFFSETS) {
pg.rect(0,
offset,
FRONT_WINDOW_WIDTH,
FRONT_WINDOW_BAR_WIDTH);
}
//Top roof edge bar
pg.beginShape();
pg.vertex(0, FRONT_WINDOW_CORNER_HEIGHT, 0);
for (int i = 0; i < VBAR_OFFSETS.length; i++) {
pg.vertex(VBAR_OFFSETS[i] + FRONT_WINDOW_BAR_WIDTH, FRONT_WINDOW_VBAR_HEIGHTS[i], 0);
}
for (int i = VBAR_OFFSETS.length - 1; i >= 0; i--) {
pg.vertex(VBAR_OFFSETS[i] + FRONT_WINDOW_TOP_WIDTH, FRONT_WINDOW_VBAR_HEIGHTS[i] + FRONT_WINDOW_TOP_WIDTH, 0);
}
pg.vertex(0, FRONT_WINDOW_CORNER_HEIGHT + FRONT_WINDOW_TOP_WIDTH, 0);
pg.endShape(CLOSE);
// Bottom floor: 45 degree angle means back corner is at sqrt(2)
pg.beginShape();
pg.fill(#808080);
pg.vertex(0, -2, 0);
pg.vertex(FRONT_WINDOW_WIDTH, -2, 0);
pg.vertex(FRONT_WINDOW_WIDTH * 0.707, -2, FRONT_WINDOW_WIDTH * 0.707);
pg.endShape(CLOSE);
// Back wall: 45 degree angle means far corner is at sqrt(2)
pg.fill(#f0f0f0);
pg.beginShape();
pg.vertex(0, 0, 0);
pg.vertex(0, FRONT_WINDOW_CORNER_HEIGHT, 0);
pg.vertex(FRONT_WINDOW_WIDTH * 0.707,
FRONT_WINDOW_CORNER_HEIGHT,
FRONT_WINDOW_CORNER_HEIGHT * 0.707);
pg.vertex(FRONT_WINDOW_WIDTH * 0.707,
0,
FRONT_WINDOW_CORNER_HEIGHT * 0.707);
pg.endShape(CLOSE);
}
}
/** Top-level Processing sketch for running the FLIGHT GUI. To run
* this, load it in Processing. Other sketches contain supporting code.
*
* @author Philip Levis <pal@cs.stanford.edu>
*/
import heronarts.lx.*;
import heronarts.lx.audio.*;
import heronarts.lx.color.LXColor;
......@@ -60,91 +66,128 @@ private static int WIDTH = 1280;
private static int HEIGHT = 800;
private static boolean FULLSCREEN = false;
void settings() {
size(WIDTH, HEIGHT, P3D);
}
// Starts wings as Red and bodies as Blue, why not.
void initializeModel() {
model = new Model(flyerConfigurations).getFlightModel();
List<LightSamplePoint> wingLights = model.getAllWingsLights();
for (LightSamplePoint light: wingLights) {
light.setColor(LXColor.hsb(0, 0.5, 0.8));
}
List<LightSamplePoint> bodyLights = model.getAllBodyLights();
for (LightSamplePoint light: bodyLights) {
light.setColor(LXColor.hsb(0.7, 0.5, 0.8));
}
/**
* Required by Processing. Sets up the graphics context/window size
* and tells Processing to use a 3D optimized graphics engine.
*/
void settings() { size(WIDTH, HEIGHT, P3D); }
/**
* Given an array of FlyerConfigs, builds the LXModel with flyers,
* wings, and lights. Initializes the wings and bodies to red
* and blue as defaults.
*/
void initializeModel(FlyerConfig[] flyers) {
model = new Model(flyers).getFlightModel();
List<LightSamplePointModel> wingLights = model.getAllWingsLights();
}
/**
* Loads the Flyer configuration JSON file
* for their positions and metadata, creates the data model from this
* information, and creates an LXStudio instance. This method is required
* and called by Processing.
*/
void setup() {
frameRate(90);
System.out.println("Starting LXStudio and Initialized Fractal Flyer data model from " + Config.FLYER_CONFIG_FILE);
System.out.println("Starting LXStudio; initialized Fractal Flyer data model from " + Config.FLYER_CONFIG_FILE);
IO io = new IO(sketchPath());
flyerConfigurations = io.loadConfigFile(Config.FLYER_CONFIG_FILE);
initializeModel();
initializeModel(flyerConfigurations);
lx = new LXStudio(this, model);
}
/**
* Creates the ProcessingEngine that triggers Patterns and generally
* controls FLIGHT. This method is required and called by Processing.
*/
void initializeUI(LXStudio lx, LXStudio.UI ui) {
pengine = new ProcessingEngine(sketchPath(), model, lx);
lx.engine.setThreaded(true);
lx.engine.addLoopTask(new BlackPinkTest(lx));
}
/**
* Creates the FLIGHT GUI. This method is required and called by
* Processing.
*/
public void onUIReady(LXStudio lx, LXStudio.UI ui) {
System.out.println("Creating UI.");
FlightGui.this.flyerHighlighter = new FlyerHighlighter(lx, flyerConfigurations);
FlightGui.this.flyerHighlighter = new FlyerHighlighter(lx, flyerConfigurations, model);
lx.addEffect(flyerHighlighter);
configureUI(ui);
configureUI(lx, ui);
}
void configureUI(LXStudio.UI ui) {
// Don't want all of the standard LXStudio stuff.
ui.bottomTray.setVisible(false);
ui.contentPicker.setVisible(false);
ui.helpBar.setVisible(false);
ui.leftPane.setVisible(false);
ui.preview.setVisible(false);
ui.rightPane.setVisible(false);
// Set up the camera view the UI uses to display the FLIGHT
// installation.
viewContext = new UI3dContext(ui) {
protected void beforeDraw(LXStudio.UI ui, PGraphics pg) {
//hint(ENABLE_DEPTH_TEST);
}
protected void afterDraw(LXStudio.UI ui, PGraphics pg) {
//hint(DISABLE_DEPTH_TEST);
}
};
// Configure the camera to rotate around a center point that's
// in the middle of the front window and 15 feet inside.
// Also set some camera angle constants so things look OK. Something
// more principled would be great. -pal
viewContext.setRadius(75*GeometryConstants.FEET);
viewContext.setCenter(GeometryConstants.FRONT_WINDOW_WIDTH / 2,
GeometryConstants.FRONT_WINDOW_CORNER_HEIGHT / 2,
15 * GeometryConstants.FEET);
viewContext.setTheta((float)(30.0 * Math.PI / 180.0));
viewContext.setPhi((float) (10.0 * Math.PI / 180.0));
viewContext.setCameraVelocity(10000.0);
viewContext.setCameraAcceleration(10000.0);
viewContext.setInteractionMode(UI3dContext.InteractionMode.ZOOM);
viewContext.addComponent(new UIFlightDisplay());
// Adds the control panel for selecting and moving Flyers
ui.addLayer(viewContext);