//
//
//
/**
GeoImage imports a list of pictures and corresponding labels.
It textures a picture on a rectangle while preserving aspect ratio
(the picture is called the GeoImage). The texturing takes place on both
sides of the rectangle so that the picture is seen from either side.
The sample animates the geoimage in 3-D, punctuates when the geoimage is in
the image plane with an audible tick, uses arrow keys and the space
bar to cycle through the pictures and corresponding labels, and renders
the original picture as a bitmap, the animate geoimage, and the label.
The key DirectAnimation points that are demonstrated include:
- aggregate events using orEvent
- creating a counter with application event data and the untilNotify method
- using number behaviors as indexes to arrays of behaviors
- newUninitbvr and init for looping transform and sound
- texturing images on rectangles while preserving aspect ratio
- 2-D/3-D interplay, via the relation of geoimage with picture
- using sound to punctuate instances in the animation
- rendering text into images
**/
//
//
// imports
import java.awt.*; // use Event and Dimension
import com.ms.dxmedia.*; // DirectAnimation classes
import java.net.*; // building URLs
public class GeoImage extends DXMApplet {
public void init() {
super.init() ;
setModel(new GeoImageModel(this));
}
}
class GeoImageModel extends Model {
// stores the height of the applet viewport in the member variable.
private NumberBvr _halfHeightNum;
GeoImageModel(DXMApplet dxma) {
Dimension dim = dxma.getSize();
// The base unit of measure for DirectAnimation is the meter.
// Convert the size into meters by multiplying it with the pixelBvr.
_halfHeightNum = mul(toBvr(dim.height*0.5),pixelBvr);
}
//
// return max(x, y)
public static NumberBvr maxNumBvr(NumberBvr x, NumberBvr y) {
return (NumberBvr) cond(gt(y, x), y, x);
}
//
public void createModel(BvrsToRun blst) {
//
//
// set the URL bases for importing the media
URL mediaBase = getImportBase();
URL imgBase = buildURL(mediaBase,"image/");
URL sndBase = buildURL(mediaBase,"sound/");
URL geoBase = buildURL(mediaBase,"geometry/");
// a [-1, 1] square in X and Y with [0, 1] texture coordinates for
// both of its faces.
GeometryBvr square =
importGeometry(buildURL(geoBase, "square.x"));
// import a list of images, some of these images could have been .gif images
ImageBvr pics[] = {
importImage(buildURL(imgBase, "phantom.jpg")),
importImage(buildURL(imgBase, "hiddenBeachSS.jpg")),
importImage(buildURL(imgBase, "hiddenBeachSG.jpg")),
importImage(buildURL(imgBase, "jotunheimen1.jpg")),
importImage(buildURL(imgBase, "redWoodCar.jpg")),
importImage(buildURL(imgBase, "wenatcheeCascades.jpg")),
importImage(buildURL(imgBase, "tulipsHol1.jpg")),
importImage(buildURL(imgBase, "tulipsHol2.jpg")),
importImage(buildURL(imgBase, "yosemiteCreek.jpg")),
importImage(buildURL(imgBase, "kidsUtah.jpg")),
importImage(buildURL(imgBase, "foliageUtah.jpg"))};
// construct a corresponding array of behaviors
ArrayBvr pictures = new ArrayBvr(pics);
// ditto for corresponding labels
StringBvr lbls[] = {
toBvr("Phantom Is., Crater Lk., Oregon"),
toBvr("Hidden Beach, Klamath, California"),
toBvr("Hidden Beach, Klamath, California"),
toBvr("Jotenheimen, Norway"),
toBvr("Redwoods N.P., California"),
toBvr("Wenatchee, Cascades, Washington"),
toBvr("Tulip Garden, Holland"),
toBvr("Tulip Garden, Holland"),
toBvr("Yosemite, California"),
toBvr("Centerville, Utah"),
toBvr("Bountiful, Utah")};
ArrayBvr lables = new ArrayBvr(lbls);
// Construct a NumberBvr counter object, and use it as an interactive
// index to select a picture and a corresponding lable.
NumberBvr numPics = lables.length();
index = (NumberBvr) (new Counter(numPics)).getBvr().runOnce();
ImageBvr picture = (ImageBvr) pictures.nth(index);
StringBvr lable = (StringBvr) lables.nth(index);
//
//
// Extract the extent of the picture and scale the square
// into a rectangle with the same extent, to preserve the
// picture's aspect ratios upon texturing
Bbox2Bvr pictureBbox = picture.boundingBox();
Point2Bvr ll = pictureBbox.getMin();
Point2Bvr ur = pictureBbox.getMax();
NumberBvr xUpperRight = ur.getX();
NumberBvr yUpperRight = ur.getY();
Transform3Bvr scaleSquare = scale(xUpperRight, yUpperRight, toBvr(1));
NumberBvr maxHalfExtent = maxNumBvr(xUpperRight, yUpperRight);
// GeoImage has the property that when it lies in the image plane
// it will have dimensions equal to its bitmap counterpart.
GeometryBvr geoImage = square.transform(scaleSquare).
// The picture is normalized before texturing since the
// square's texture coordinates assume a unit square texture.
texture(picture.transform(scale(1,-1)).mapToUnitSquare());
//
//
// the length of the first segment of the animation in seconds
NumberBvr period = toBvr(6);
// time-varying angle, does a complete cycle per period
NumberBvr aniAngle = mul(localTime, mul(toBvr(2),
div(toBvr(Math.PI), period)));
// A translation between front and back Z limits used for
// travel of the center of the geometric image.
// The limits are related to the extent of the picture.
NumberBvr distOfTravel = mul(toBvr(20), maxHalfExtent);
Transform3Bvr trans = translate(toBvr(0), toBvr(0),
mul(neg(distOfTravel), sin(aniAngle)));
// a rotation with rate "aniAngle" around a time varying axis
Transform3Bvr rot = rotate(vector3(sin(aniAngle), cos(aniAngle),
div(sin(aniAngle), toBvr(2))), aniAngle);
// tumbling in 3-D animation, performs a complete cycle in one "period":
Transform3Bvr rot1 = compose(trans, rot);
// open page animation, opens in one quarter of "period":
Transform3Bvr rot2 =
compose(translate(neg(xUpperRight), toBvr(0), toBvr(0)),
compose(rotate(yVector3, neg(aniAngle)),
translate(xUpperRight, toBvr(0), toBvr(0))));
// close page animation, closes in one quarter of "period":
Transform3Bvr rot3 =
compose(translate(neg(xUpperRight), toBvr(0), toBvr(0)),
// the -PI/2 is to pick up from where the page open animation left of
compose(rotate(yVector3, add(div(neg(toBvr(Math.PI)), toBvr(2)), aniAngle)),
translate(xUpperRight, toBvr(0), toBvr(0))));
// the cyclic animation of the geoimage:
// - tumbling in 3-D in one "period"
// - open page in quarter of "period"
// - close page in quarter of "period"
NumberBvr quarterPeriod = div(period, toBvr(4));
Transform3Bvr loopingTrans = Transform3Bvr.newUninitBvr();
loopingTrans.init(until(rot1, timer(period),
until(rot2, timer(quarterPeriod),
until(rot3, timer(quarterPeriod), loopingTrans))));
GeometryBvr loopingGeoImage = geoImage.transform(loopingTrans);
//
//
// Generate a tick everytime the geoimage lies in the image plane.
// At that point the original picture (bitmap in image plane)
// coincides with geoimage (a 3-D rendering of a textured rectangle).
// GeoImage's projection is larger than the picture when closer than the
// image plane and its projection is smaller when it's further away.
// import the ping sound.
SoundBvr ping = importSound(buildURL(sndBase, "butin.wav"), null);
// Could have set the looping sound like the transform above;
// Instead, demonstrate the cycler.
// alternative:
// NumberBvr halfPeriod = div(period, toBvr(2));
// SoundBvr loopingSound = SoundBvr.newUninitBvr();
// loopingSound.init(until(ping, timer(halfPeriod), loopingSound));
NumberBvr halfPeriod = div(period, toBvr(2));
Behavior sounds[] = { ping };
Cycler cyl = new Cycler(sounds, timer(halfPeriod));
SoundBvr loopingSound = (SoundBvr)(cyl.getBvr());
// label is yellow, bold, centered horizontally, and 15% above bottom
Transform2Bvr lablePos = translate(toBvr(0), mul(toBvr(-0.85), _halfHeightNum));
FontStyleBvr fs = defaultFont.color(yellow).bold();
ImageBvr lableImage = stringImage(lable, fs).transform(lablePos);
//
//
// Don't want part of geoimage to be clipped when its center
// is at distOfTravel, so set the near plane slightly further than that
// and set the projection point a bit further than the near plane.
NumberBvr nearDist = add(distOfTravel, mul(toBvr(2.5), maxHalfExtent));
CameraBvr camera = perspectiveCamera(add(nearDist,
mul(maxHalfExtent, toBvr(10))), nearDist);
// turn on the lights and render geoimage into an image
GeometryBvr lights = ambientLight;
GeometryBvr scene = union(lights, loopingGeoImage);
ImageBvr renderedGeo = scene.render(camera);
// finally set what's to be displayed on a blue background
ImageBvr model = overlay(renderedGeo,
overlay(lableImage,
overlay(picture, solidColorImage(blue))));
// the sound behavior to be presented
setSound(loopingSound);
// the image behavior to be rendered
setImage(model);
}
public void cleanup() {
super.cleanup();
index = null;
}
NumberBvr index;
//
}
//
// This class produces a bidirectional counter that cycles in a given range
// upon button presses.
class Counter extends Statics implements UntilNotifier {
// constructor for a counter object
public Counter(NumberBvr limit) {
_counterLimit = limit;
// upon a skip event invoke the notifier
_value = (NumberBvr)untilNotify(_initValue, skip, this);
}
public Behavior notify(Object eventData, Behavior previous, BvrsToRun lst) {
// extract the application data (an integer), into a number behavior
NumberBvr adjustment = toBvr(((Integer)eventData).intValue());
// use it as an inc/decrement
NumberBvr value = add((NumberBvr)previous, adjustment);
// cycle between 0 and (limit - 1)
value = (NumberBvr)cond(eq(value, toBvr(-1)),
sub(_counterLimit, toBvr(1)), // if -1, wrap around
cond(eq(value, _counterLimit), toBvr(0), value)); // if limit, wrap around
// repeat with the new value
return untilNotify(value, skip, this);
}
public NumberBvr getBvr () {
return _value;
}
// Key events with corresponding event data.
// For non-ASCII keys use the Java designation for the key.
private static final DXMEvent skipForward1 = keyDown(Event.RIGHT).attachData(new Integer(1));
private static final DXMEvent skipBack1 = keyDown(Event.LEFT).attachData(new Integer(-1));
// For ASCII keys use the ASCII letter between single quotes.
private static final DXMEvent skipForward2 = keyDown(' ').attachData(new Integer(1));
// There is also a timeout event for automatically switching between pictures
// if no manual key presses are issued.
private static final DXMEvent auto = timer(toBvr(8)).attachData(new Integer(1));
// Aggregate the four events above into a single event.
private static final DXMEvent skip = orEvent(skipForward1,
orEvent(skipBack1,
orEvent(skipForward2, auto)));
// These are aspects of the counter.
private NumberBvr _counterLimit;
private NumberBvr _initValue = toBvr(0); // start counting at 0
private NumberBvr _value; // current value of counter
}
//