summaryrefslogtreecommitdiffstats
path: root/src/net/java/joglutils/msg/test/DisplayShelf.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/joglutils/msg/test/DisplayShelf.java')
-rw-r--r--src/net/java/joglutils/msg/test/DisplayShelf.java578
1 files changed, 7 insertions, 571 deletions
diff --git a/src/net/java/joglutils/msg/test/DisplayShelf.java b/src/net/java/joglutils/msg/test/DisplayShelf.java
index b78f73a..02d9e28 100644
--- a/src/net/java/joglutils/msg/test/DisplayShelf.java
+++ b/src/net/java/joglutils/msg/test/DisplayShelf.java
@@ -37,585 +37,20 @@
package net.java.joglutils.msg.test;
-import java.awt.BasicStroke;
import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Container;
import java.awt.DisplayMode;
import java.awt.Frame;
-import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.*;
-import java.awt.image.*;
-import java.net.*;
-import java.util.*;
-import javax.imageio.*;
+
import javax.swing.*;
-import javax.swing.event.*;
import javax.media.opengl.*;
-import com.sun.opengl.util.j2d.*;
-
-import net.java.joglutils.msg.actions.*;
-import net.java.joglutils.msg.collections.*;
-import net.java.joglutils.msg.math.*;
-import net.java.joglutils.msg.misc.*;
-import net.java.joglutils.msg.nodes.*;
/** A test implementing a 3D display shelf component. */
-public class DisplayShelf extends Container {
- private GLCanvas canvas;
-
- private float DEFAULT_ASPECT_RATIO = 0.665f;
- // This also affects the spacing
- private float DEFAULT_HEIGHT = 1.5f;
- private float DEFAULT_ON_SCREEN_FRAC = 0.5f;
- private float EDITING_ON_SCREEN_FRAC = 0.95f;
- private float offsetFrac;
-
- private float STACKED_SPACING_FRAC = 0.3f;
- private float SELECTED_SPACING_FRAC = 0.6f;
- private float EDITED_SPACING_FRAC = 1.5f;
-
- // This is how much we raise the geometry above the floor in single image mode
- private float SINGLE_IMAGE_MODE_RAISE_FRAC = 2.0f;
-
- // The camera
- private PerspectiveCamera camera;
-
- static class TitleGraph {
- String url;
- Separator sep = new Separator();
- Transform xform = new Transform();
- Texture2 texture = new Texture2();
- Coordinate3 coords = new Coordinate3();
-
- TitleGraph(String url) {
- this.url = url;
- }
- }
-
- private Group root;
- private Separator imageRoot;
- private String[] images;
- private List<TitleGraph> titles = new ArrayList<TitleGraph>();
- private JSlider slider;
- private int targetIndex;
- // This encodes both the current position and the horizontal animation alpha
- private float currentIndex;
- // This encodes the animation alpha for the z-coordinate motion
- // associated with going in to and out of editing mode
- private float currentZ;
- private float targetZ;
- // This is effectively a constant
- private float viewingZ;
- // This is also currently effectively a constant, though we need to
- // compute it dynamically for each picture to get it to show up
- // centered
- private float editingZ;
- // This encodes our current Y coordinate in editing mode
- private float currentY;
- // This encodes our target Y coordinate in editing mode
- private float targetY;
- // If the difference between the current and target values of any of
- // the above are > EPSILON, then we will continue repainting
- private static final float EPSILON = 1.0e-3f;
- private SystemTime time;
- private boolean animating;
- private boolean forceRecompute;
- // Single image mode toggle
- private boolean singleImageMode;
-
- // A scale factor for the animation speed
- private static final float ANIM_SCALE_FACTOR = 7.0f;
- // The rotation angle of the titles
- private static final float ROT_ANGLE = (float) Math.toRadians(75);
-
- // Visual progress of downloads
- private Texture2 clockTexture;
- private volatile boolean doneLoading;
-
- private void computeCoords(Coordinate3 coordNode, float aspectRatio) {
- Vec3fCollection coords = coordNode.getData();
- if (coords == null) {
- coords = new Vec3fCollection();
- Vec3f zero = new Vec3f();
- for (int i = 0; i < 6; i++) {
- coords.add(zero);
- }
- coordNode.setData(coords);
- }
- // Now compute the actual values
- Vec3f lowerLeft = new Vec3f(-0.5f * DEFAULT_HEIGHT * aspectRatio, 0, 0);
- Vec3f lowerRight = new Vec3f( 0.5f * DEFAULT_HEIGHT * aspectRatio, 0, 0);
- Vec3f upperLeft = new Vec3f(-0.5f * DEFAULT_HEIGHT * aspectRatio, DEFAULT_HEIGHT, 0);
- Vec3f upperRight = new Vec3f( 0.5f * DEFAULT_HEIGHT * aspectRatio, DEFAULT_HEIGHT, 0);
- // First triangle
- coords.set(0, upperRight);
- coords.set(1, upperLeft);
- coords.set(2, lowerLeft);
- // Second triangle
- coords.set(3, upperRight);
- coords.set(4, lowerLeft);
- coords.set(5, lowerRight);
- }
-
- private static void drawClock(Graphics2D g, int minsPastMidnight,
- int x, int y, int width, int height) {
- g.setColor(Color.DARK_GRAY);
- g.fillRect(x, y, width, height);
- g.setColor(Color.GRAY);
- int midx = (int) (x + (width / 2.0f));
- int midy = (int) (y + (height / 2.0f));
- int sz = (int) (0.8f * Math.min(width, height));
- g.setStroke(new BasicStroke(sz / 20.0f,
- BasicStroke.CAP_ROUND,
- BasicStroke.JOIN_MITER));
- int arcSz = (int) (0.4f * sz);
- int smallHandSz = (int) (0.3f * sz);
- int bigHandSz = (int) (0.4f * sz);
- g.drawRoundRect(midx - (sz / 2), midy - (sz / 2),
- sz, sz,
- arcSz, arcSz);
- float hour = minsPastMidnight / 60.0f;
- int min = minsPastMidnight % 60;
- float hourAngle = hour * 2.0f * (float) Math.PI / 12;
- float minAngle = min * 2.0f * (float) Math.PI / 60;
-
- g.drawLine(midx, midy,
- midx + (int) (smallHandSz * Math.cos(hourAngle)),
- midy + (int) (smallHandSz * Math.sin(hourAngle)));
- g.drawLine(midx, midy,
- midx + (int) (bigHandSz * Math.cos(minAngle)),
- midy + (int) (bigHandSz * Math.sin(minAngle)));
- }
-
- private void startLoading() {
- final List<TitleGraph> queuedGraphs = new ArrayList<TitleGraph>();
- queuedGraphs.addAll(titles);
-
- Thread loaderThread = new Thread(new Runnable() {
- public void run() {
- try {
- while (queuedGraphs.size() > 0) {
- TitleGraph graph = queuedGraphs.remove(0);
- BufferedImage img = null;
- try {
- img = ImageIO.read(new URL(graph.url));
- } catch (Exception e) {
- System.out.println("Exception loading " + graph.url + ":");
- e.printStackTrace();
- }
- if (img != null) {
- graph.sep.replaceChild(clockTexture, graph.texture);
- graph.texture.setTexture(img, false);
- // Figure out the new aspect ratio based on the image's width and height
- float aspectRatio = (float) img.getWidth() / (float) img.getHeight();
- // Compute new coordinates
- computeCoords(graph.coords, aspectRatio);
- // Schedule a repaint
- canvas.repaint();
- }
- }
- } finally {
- doneLoading = true;
- }
- }
- });
- // Avoid having the loader thread preempt the rendering thread
- loaderThread.setPriority(Thread.NORM_PRIORITY - 2);
- loaderThread.start();
- }
-
- private void startClockAnimation() {
- Thread clockAnimThread = new Thread(new Runnable() {
- public void run() {
- while (!doneLoading) {
- canvas.repaint();
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- }
- }
- }
- });
- clockAnimThread.start();
- }
-
- private void setTargetIndex(int index) {
- this.targetIndex = index;
- if (!animating) {
- time.rebase();
- }
- recomputeTargetYZ(true);
- canvas.repaint();
- }
-
- private void recomputeTargetYZ(boolean animate) {
- if (singleImageMode) {
- // Compute a target Y and Z depth based on the image we want to view
-
- // FIXME: right now the Y and Z targets are always the same, but
- // once we adjust the images to fit within a bounding square,
- // they won't be
- targetY = (0.5f + SINGLE_IMAGE_MODE_RAISE_FRAC) * DEFAULT_HEIGHT;
- editingZ = 0.5f * DEFAULT_HEIGHT / (EDITING_ON_SCREEN_FRAC * (float) Math.tan(camera.getHeightAngle()));
- targetZ = editingZ;
- } else {
- targetY = 0.5f * DEFAULT_HEIGHT;
- targetZ = viewingZ;
- }
-
- if (!animate) {
- currentY = targetY;
- currentZ = targetZ;
- currentIndex = targetIndex;
- }
- }
-
- private boolean recompute() {
- if (!forceRecompute) {
- if (Math.abs(targetIndex - currentIndex) < EPSILON &&
- Math.abs(targetZ - currentZ) < EPSILON &&
- Math.abs(targetY - currentY) < EPSILON)
- return false;
- }
-
- forceRecompute = false;
-
- time.update();
- float deltaT = (float) time.deltaT();
-
- // Make the animation speed independent of frame rate
- currentIndex = currentIndex + (targetIndex - currentIndex) * deltaT * ANIM_SCALE_FACTOR;
- currentZ = currentZ + (targetZ - currentZ) * deltaT * ANIM_SCALE_FACTOR;
- currentY = currentY + (targetY - currentY) * deltaT * ANIM_SCALE_FACTOR;
- // An alpha of 0 indicates we're fully in viewing mode
- // An alpha of 1 indicates we're fully in editing mode
- float zAlpha = (currentZ - viewingZ) / (editingZ - viewingZ);
-
- // Recompute the positions and orientations of each title, and the position of the camera
- int firstIndex = (int) Math.floor(currentIndex);
- int secondIndex = (int) Math.ceil(currentIndex);
- if (secondIndex == firstIndex) {
- secondIndex = firstIndex + 1;
- }
-
- float alpha = currentIndex - firstIndex;
-
- int idx = 0;
- float curPos = 0.0f;
- float stackedSpacing = DEFAULT_HEIGHT * (zAlpha * EDITED_SPACING_FRAC + (1.0f - zAlpha) * STACKED_SPACING_FRAC);
- float selectedSpacing = DEFAULT_HEIGHT * (zAlpha * EDITED_SPACING_FRAC + (1.0f - zAlpha) * SELECTED_SPACING_FRAC);
- float angle = (1.0f - zAlpha) * ROT_ANGLE;
- float y = zAlpha * DEFAULT_HEIGHT * SINGLE_IMAGE_MODE_RAISE_FRAC;
- Rotf posAngle = new Rotf(Vec3f.Y_AXIS, angle);
- Rotf negAngle = new Rotf(Vec3f.Y_AXIS, -angle);
- float offset = 0;
-
- // Only bump the selected title out of the list if we're in viewing mode and close to it
- if (Math.abs(targetIndex - currentIndex) < 3.0) {
- offset = (1.0f - zAlpha) * offsetFrac * DEFAULT_HEIGHT;
- }
- for (TitleGraph graph : titles) {
- if (idx < firstIndex) {
- graph.xform.getTransform().setRotation(posAngle);
- graph.xform.getTransform().setTranslation(new Vec3f(curPos, y, 0));
- curPos += stackedSpacing;
- } else if (idx > secondIndex) {
- graph.xform.getTransform().setRotation(negAngle);
- graph.xform.getTransform().setTranslation(new Vec3f(curPos, y, 0));
- curPos += stackedSpacing;
- } else if (idx == firstIndex) {
- // Bump the position of this title
- curPos += (1.0f - alpha) * (selectedSpacing - stackedSpacing);
-
- // The camera is glued to this position
- float cameraPos = curPos + alpha * selectedSpacing;
-
- // Interpolate
- graph.xform.getTransform().setRotation(new Rotf(Vec3f.Y_AXIS, alpha * angle));
- graph.xform.getTransform().setTranslation(new Vec3f(curPos, y, (1.0f - alpha) * offset));
-
- // Now recompute the position of the camera
- // Aim to get the titles to fill a certain fraction of the vertical field of view
- camera.setPosition(new Vec3f(cameraPos,
- currentY,
- currentZ));
-
- // Maintain this much distance between the two animating titles
- curPos += selectedSpacing;
- } else {
- // Interpolate
- graph.xform.getTransform().setRotation(new Rotf(Vec3f.Y_AXIS, (1.0f - alpha) * -angle));
- graph.xform.getTransform().setTranslation(new Vec3f(curPos, y, alpha * offset));
-
- curPos += stackedSpacing + alpha * (selectedSpacing - stackedSpacing);
- }
-
- ++idx;
- }
-
- return true;
- }
-
- public DisplayShelf(Group root, String[] images) {
- this.images = images;
- this.root = root;
- time = new SystemTime();
- time.rebase();
- setLayout(new BorderLayout());
- camera = new PerspectiveCamera();
- camera.setNearDistance(1.0f);
- camera.setFarDistance(100.0f);
- camera.setHeightAngle((float) Math.PI / 8);
- // This could / should be computed elsewhere, especially if we add
- // the ability to dynamically adjust the camera's height angle
- viewingZ = 0.5f * DEFAULT_HEIGHT / (DEFAULT_ON_SCREEN_FRAC * (float) Math.tan(camera.getHeightAngle()));
- // Compute the fraction by which we offset the selected title
- // based on a couple of known good points
- offsetFrac = (float) (((3 * Math.PI / 40) / camera.getHeightAngle()) + 0.1f);
- canvas = new GLCanvas();
- canvas.addGLEventListener(new Listener());
- canvas.addMouseListener(new MListener());
- canvas.addKeyListener(new KeyAdapter() {
- public void keyPressed(KeyEvent e) {
- switch (e.getKeyCode()) {
- case KeyEvent.VK_SPACE:
- setSingleImageMode(!getSingleImageMode(), true);
- break;
-
- case KeyEvent.VK_ENTER:
- setSingleImageMode(!getSingleImageMode(), false);
- break;
-
- case KeyEvent.VK_LEFT:
- slider.setValue(Math.max(0, targetIndex - 1));
- break;
-
- case KeyEvent.VK_RIGHT:
- slider.setValue(Math.min(titles.size() - 1, targetIndex + 1));
- break;
- }
- }
- });
- add(canvas, BorderLayout.CENTER);
- slider = new JSlider(0, images.length - 1, 0);
- slider.addChangeListener(new ChangeListener() {
- public void stateChanged(ChangeEvent e) {
- setTargetIndex(slider.getValue());
- }
- });
- add(slider, BorderLayout.SOUTH);
- }
-
- public void setSingleImageMode(boolean singleImageMode, boolean animateTransition) {
- this.singleImageMode = singleImageMode;
- if (!animating) {
- time.rebase();
- }
- recomputeTargetYZ(animateTransition);
- forceRecompute = !animateTransition;
- canvas.repaint();
- }
-
- public boolean getSingleImageMode() {
- return singleImageMode;
- }
-
- class Listener implements GLEventListener {
- private GLRenderAction ra = new GLRenderAction();
-
- public void init(GLAutoDrawable drawable) {
- GL gl = drawable.getGL();
-
- // Build the scene graph
- root.removeAllChildren();
-
- // The clock
- clockTexture = new Texture2();
- clockTexture.initTextureRenderer((int) (300 * DEFAULT_HEIGHT * DEFAULT_ASPECT_RATIO),
- (int) (300 * DEFAULT_HEIGHT),
- false);
-
- // The images
- imageRoot = new Separator();
-
- // The mirrored images, under the floor
- Separator mirrorRoot = new Separator();
-
- Transform mirrorXform = new Transform();
- // Mirror vertically
- mirrorXform.getTransform().set(1, 1, -1.0f);
- mirrorRoot.addChild(mirrorXform);
- // Assume we know what we're doing here with setting per-vertex
- // colors for each piece of geometry in one shot
- Color4 colorNode = new Color4();
- Vec4fCollection colors = new Vec4fCollection();
- Vec4f fadeTop = new Vec4f(0.75f, 0.75f, 0.75f, 0.75f);
- Vec4f fadeBot = new Vec4f(0.25f, 0.25f, 0.25f, 0.25f);
- // First triangle
- colors.add(fadeTop);
- colors.add(fadeTop);
- colors.add(fadeBot);
- // Second triangle
- colors.add(fadeTop);
- colors.add(fadeBot);
- colors.add(fadeBot);
- colorNode.setData(colors);
- mirrorRoot.addChild(colorNode);
-
- TriangleSet tris = new TriangleSet();
- tris.setNumTriangles(2);
-
- int i = 0;
- for (String image : images) {
- TitleGraph graph = new TitleGraph(image);
- titles.add(graph);
- computeCoords(graph.coords, DEFAULT_ASPECT_RATIO);
- graph.xform.getTransform().setTranslation(new Vec3f(i, 0, 0));
- Separator sep = graph.sep;
- sep.addChild(graph.xform);
- sep.addChild(graph.coords);
- // Add in the clock texture at the beginning
- sep.addChild(clockTexture);
- TextureCoordinate2 texCoordNode = new TextureCoordinate2();
- Vec2fCollection texCoords = new Vec2fCollection();
- // Texture coordinates for two triangles
- // First triangle
- texCoords.add(new Vec2f( 1, 1));
- texCoords.add(new Vec2f( 0, 1));
- texCoords.add(new Vec2f( 0, 0));
- // Second triangle
- texCoords.add(new Vec2f( 1, 1));
- texCoords.add(new Vec2f( 0, 0));
- texCoords.add(new Vec2f( 1, 0));
- texCoordNode.setData(texCoords);
- sep.addChild(texCoordNode);
-
- sep.addChild(tris);
-
- // Add this to each rendering root
- imageRoot.addChild(sep);
- mirrorRoot.addChild(sep);
-
- ++i;
- }
-
- // Now produce the floor geometry
- float maxSpacing = DEFAULT_HEIGHT * Math.max(STACKED_SPACING_FRAC, Math.max(SELECTED_SPACING_FRAC, EDITED_SPACING_FRAC));
- float minx = -i * maxSpacing;
- float maxx = 2 * i * maxSpacing;
- // Furthest back from the camera
- float minz = -2 * DEFAULT_HEIGHT;
- // Assume this will be close enough to cover all of the mirrored geometry
- float maxz = DEFAULT_HEIGHT;
- Separator floorRoot = new Separator();
- Blend blend = new Blend();
- blend.setEnabled(true);
- blend.setSourceFunc(Blend.ONE);
- blend.setDestFunc(Blend.ONE_MINUS_SRC_ALPHA);
- floorRoot.addChild(blend);
- Coordinate3 floorCoords = new Coordinate3();
- floorCoords.setData(new Vec3fCollection());
- // First triangle
- floorCoords.getData().add(new Vec3f(maxx, 0, minz));
- floorCoords.getData().add(new Vec3f(minx, 0, minz));
- floorCoords.getData().add(new Vec3f(minx, 0, maxz));
- // Second triangle
- floorCoords.getData().add(new Vec3f(maxx, 0, minz));
- floorCoords.getData().add(new Vec3f(minx, 0, maxz));
- floorCoords.getData().add(new Vec3f(maxx, 0, maxz));
- floorRoot.addChild(floorCoords);
- // Colors
- Vec4f gray = new Vec4f(0.4f, 0.4f, 0.4f, 0.4f);
- Vec4f clearGray = new Vec4f(0.0f, 0.0f, 0.0f, 0.0f);
- Color4 floorColors = new Color4();
- floorColors.setData(new Vec4fCollection());
- // First triangle
- floorColors.getData().add(gray);
- floorColors.getData().add(gray);
- floorColors.getData().add(clearGray);
- // Second triangle
- floorColors.getData().add(gray);
- floorColors.getData().add(clearGray);
- floorColors.getData().add(clearGray);
- floorRoot.addChild(floorColors);
-
- floorRoot.addChild(tris);
-
- // Now set up the overall scene graph
- root.addChild(camera);
- root.addChild(imageRoot);
- root.addChild(mirrorRoot);
- root.addChild(floorRoot);
-
- startLoading();
- startClockAnimation();
- recomputeTargetYZ(false);
- forceRecompute = true;
- recompute();
- }
-
- public void display(GLAutoDrawable drawable) {
- // Recompute position of camera and orientation of images
- boolean repaintAgain = recompute();
-
- if (!doneLoading) {
- if (!repaintAgain) {
- time.update();
- }
-
- TextureRenderer rend = clockTexture.getTextureRenderer();
- Graphics2D g = rend.createGraphics();
- drawClock(g, (int) (time.time() * 30),
- 0, 0, rend.getWidth(), rend.getHeight());
- g.dispose();
- rend.markDirty(0, 0, rend.getWidth(), rend.getHeight());
- }
-
- // Redraw
- GL gl = drawable.getGL();
- gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
- ra.apply(root);
-
- if (repaintAgain) {
- animating = true;
- canvas.repaint();
- } else {
- animating = false;
- }
- }
-
- public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
- }
-
- public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
- }
-
- class MListener extends MouseAdapter {
- RayPickAction ra = new RayPickAction();
-
- public void mousePressed(MouseEvent e) {
- ra.setPoint(e.getX(), e.getY(), e.getComponent());
- // Apply to the scene root
- ra.apply(root);
- List<PickedPoint> pickedPoints = ra.getPickedPoints();
- Path p = null;
- if (!pickedPoints.isEmpty())
- p = pickedPoints.get(0).getPath();
- if (p != null && p.size() > 1) {
- int idx = imageRoot.findChild(p.get(p.size() - 2));
- if (idx >= 0) {
- // Need to keep the slider and this mechanism in sync
- slider.setValue(idx);
- }
- }
- }
- }
-
+public class DisplayShelf {
public static void main(String[] args) {
Frame f = new Frame("Display Shelf test");
f.setLayout(new BorderLayout());
@@ -685,10 +120,11 @@ public class DisplayShelf extends Container {
"http://download.java.net/media/jogl/builds/ds_tmp/mzi.uswlslxx.200x200-75.jpg"
};
- Separator root = new Separator();
-
- DisplayShelf shelf = new DisplayShelf(root, images);
- f.add(shelf);
+ DisplayShelfRenderer renderer = new DisplayShelfRenderer(images);
+ GLCanvas canvas = new GLCanvas(new GLCapabilities(), null, renderer.getSharedContext(), null);
+ canvas.setFocusable(true);
+ canvas.addGLEventListener(renderer);
+ f.add(canvas);
GraphicsDevice dev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
DisplayMode curMode = dev.getDisplayMode();
int height = (int) (0.5f * curMode.getWidth());