/**********************************************************
Copyright (C) 2001 Daniel Selman
First distributed with the book "Java 3D Programming"
by Daniel Selman and published by Manning Publications.
http://manning.com/selman
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
The license can be found on the WWW at:
http://www.fsf.org/copyleft/gpl.html
Or by writing to:
Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Authors can be contacted at:
Daniel Selman: daniel@selman.org
If you make changes you think others would like, please
contact one of the authors or someone at the
www.j3d.org web site.
**************************************************************/
/**
* This example creates a 3D fly-over of the city of Boston. The viewer is
* animated using a RotPosScaleTCBSplinePathInterpolator as well as 3
* helicopters. The example uses PointSounds attached to the helicopters to
* generate 3D spatial audio.
*/
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GraphicsConfigTemplate;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;
import javax.media.j3d.Alpha;
import javax.media.j3d.Appearance;
import javax.media.j3d.AudioDevice;
import javax.media.j3d.Background;
import javax.media.j3d.BackgroundSound;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DistanceLOD;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.GraphicsConfigTemplate3D;
import javax.media.j3d.Group;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Locale;
import javax.media.j3d.MediaContainer;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PointSound;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Sound;
import javax.media.j3d.Switch;
import javax.media.j3d.Texture;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.media.j3d.ViewPlatform;
import javax.media.j3d.VirtualUniverse;
import javax.media.j3d.WakeupCondition;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnCollisionEntry;
import javax.media.j3d.WakeupOnCollisionExit;
import javax.media.j3d.WakeupOr;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.vecmath.Color3f;
import javax.vecmath.Point2f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Quat4f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4f;
import com.sun.j3d.audioengines.javasound.JavaSoundMixer;
import com.sun.j3d.loaders.Scene;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.behaviors.interpolators.RotPosScaleTCBSplinePathInterpolator;
import com.sun.j3d.utils.behaviors.interpolators.TCBKeyFrame;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.image.TextureLoader;
/*
* This example uses a Spline Interpolator to animate a fly-over the city of
* Boston. The city is rendered using satellite images with a Level of Detail
* behavior. The scene includes a number of moving helicopters, each with an
* associated sound.
*/
public class SplineInterpolatorTest extends Java3dApplet {
// size of the 3D window - enlage on powerful systems
private static int m_kWidth = 200;
private static int m_kHeight = 200;
// a shared appearance for the buildings we create
private Appearance m_BuildingAppearance = null;
// the size of the high resolution "world".
// the world is centered at 0,0,0 and extends
// to +- LAND_WIDTH in the x direction and
//+- LAND_LENGTH in the z direction.
// These dimensions are loosely based on pixel
// coordinates from the texture images
private static final float LAND_WIDTH = 180;
private static final float LAND_LENGTH = 180;
// the satellite images used as textures have
// been manually edited so that the water in the
// images corresponds to the following RGB values.
// this allows the application to avoid creating
// buildings in the water!
private static final float WATER_COLOR_RED = 0;
private static final float WATER_COLOR_GREEN = 57;
private static final float WATER_COLOR_BLUE = 123;
public SplineInterpolatorTest() {
initJava3d();
}
// scale eveything so we can use pixel coordinates
protected double getScale() {
return 0.1;
}
protected int getCanvas3dWidth(Canvas3D c3d) {
return m_kWidth;
}
protected int getCanvas3dHeight(Canvas3D c3d) {
return m_kHeight;
}
protected Bounds createApplicationBounds() {
m_ApplicationBounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
10.0);
return m_ApplicationBounds;
}
// we want a texture mapped background of a sky
protected Background createBackground() {
// add the sky backdrop
Background back = new Background();
back.setApplicationBounds(getApplicationBounds());
BranchGroup bgGeometry = new BranchGroup();
// create an appearance and assign the texture image
Appearance app = new Appearance();
Texture tex = new TextureLoader("sky.gif", this).getTexture();
app.setTexture(tex);
Sphere sphere = new Sphere(1.0f, Primitive.GENERATE_TEXTURE_COORDS
| Primitive.GENERATE_NORMALS_INWARD, app);
bgGeometry.addChild(sphere);
back.setGeometry(bgGeometry);
return back;
}
// this controls how close to a helicopter we can
// be and still hear it. If the helicopters sound
// scheduling bounds intersect our ViewPlatformActivationRadius
// the sound of the helicopter is potentially audible.
protected float getViewPlatformActivationRadius() {
return 20;
}
// creates the objects within our world
protected BranchGroup createSceneBranchGroup() {
BranchGroup objRoot = super.createSceneBranchGroup();
// create a root TG in case we need to scale the scene
TransformGroup objTrans = new TransformGroup();
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
Transform3D t3d = new Transform3D();
objTrans.setTransform(t3d);
Group hiResGroup = createLodLand(objTrans);
createBuildings(objTrans);
createHelicopters(objTrans);
// connect
objRoot.addChild(objTrans);
return objRoot;
}
// we create 2 TransformGroups above the ViewPlatform:
// the first merely applies a scale, while the second
// has a RotPosScaleTCBSplinePathInterpolator attached
// so that the viewer of the scene is animated along
// a spline curve.
public TransformGroup[] getViewTransformGroupArray() {
TransformGroup[] tgArray = new TransformGroup[2];
tgArray[0] = new TransformGroup();
tgArray[1] = new TransformGroup();
Transform3D t3d = new Transform3D();
t3d.setScale(getScale());
t3d.invert();
tgArray[0].setTransform(t3d);
// create an Alpha object for the Interpolator
Alpha alpha = new Alpha(-1, Alpha.INCREASING_ENABLE
| Alpha.DECREASING_ENABLE, 0, 0, 25000, 4000, 100, 20000, 5000,
50);
// ensure the Interpolator can access the TG
tgArray[1].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
try {
// create the Interpolator and load the keyframes from disk
RotPosScaleTCBSplinePathInterpolator splineInterpolator = Utils
.createSplinePathInterpolator(new UiAlpha(alpha),
tgArray[1], new Transform3D(), new URL(
getWorkingDirectory(),
"rotate_viewer_spline.xls"));
// set the scheduling bounds and attach to the scenegraph
splineInterpolator.setSchedulingBounds(getApplicationBounds());
tgArray[1].addChild(splineInterpolator);
} catch (Exception e) {
System.err.println(e.toString());
}
return tgArray;
}
// overidden so that the example can use audio
protected AudioDevice createAudioDevice(PhysicalEnvironment pe) {
return new JavaSoundMixer(pe);
}
// creates a Switch group that contains two versions
// of the world - the first is a high resolution version,
// the second if a lower resolution version.
public Group createLodLand(Group g) {
Switch switchNode = new Switch();
switchNode.setCapability(Switch.ALLOW_SWITCH_WRITE);
Group hiResGroup = createLand(switchNode);
createEnvirons(switchNode);
// create a DistanceLOD that will select the child of
// the Switch node based on distance. Here we are selecting
// child 0 (high res) if we are closer than 180 units to
// 0,0,0 and child 1 (low res) otherwise.
float[] distanceArray = { 180 };
DistanceLOD distanceLod = new DistanceLOD(distanceArray);
distanceLod.setSchedulingBounds(getApplicationBounds());
distanceLod.addSwitch(switchNode);
g.addChild(distanceLod);
g.addChild(switchNode);
return hiResGroup;
}
// creates a high resolution representation of the world.
// a single texture mapped square and a larger (water colored)
// square to act as a horizon.
public Group createLand(Group g) {
Land land = new Land(this, g, ComplexObject.GEOMETRY
| ComplexObject.TEXTURE);
Group hiResGroup = land.createObject(new Appearance(), new Vector3d(),
new Vector3d(LAND_WIDTH, 1, LAND_LENGTH), "boston.gif", null,
null);
Appearance app = new Appearance();
app.setColoringAttributes(new ColoringAttributes(
WATER_COLOR_RED / 255f, WATER_COLOR_GREEN / 255f,
WATER_COLOR_BLUE / 255f, ColoringAttributes.FASTEST));
Land base = new Land(this, hiResGroup, ComplexObject.GEOMETRY);
base.createObject(app, new Vector3d(0, -5, 0), new Vector3d(
4 * LAND_WIDTH, 1, 4 * LAND_LENGTH), null, null, null);
return hiResGroup;
}
// creates a low resolution version of the world and
// applies the low resolution satellite image
public Group createEnvirons(Group g) {
Land environs = new Land(this, g, ComplexObject.GEOMETRY
| ComplexObject.TEXTURE);
return environs.createObject(new Appearance(), new Vector3d(),
new Vector3d(2 * LAND_WIDTH, 1, 2 * LAND_LENGTH),
"environs.gif", null, null);
}
// returns true if the given x,z location in the world
// corresponds to water in the satellite image
protected boolean isLocationWater(BufferedImage image, float posX,
float posZ) {
Color color = null;
float imageWidth = image.getWidth();
float imageHeight = image.getHeight();
// range from 0 to 1
float nPixelX = (posX + LAND_WIDTH) / (2 * LAND_WIDTH);
float nPixelY = (posZ + LAND_LENGTH) / (2 * LAND_LENGTH);
// rescale
nPixelX *= imageWidth;
nPixelY *= imageHeight;
if (nPixelX >= 0 && nPixelX < imageWidth && nPixelY >= 0
&& nPixelY < imageHeight) {
color = new Color(image.getRGB((int) nPixelX, (int) nPixelY));
return (color.getBlue() >= WATER_COLOR_BLUE
&& color.getGreen() <= WATER_COLOR_GREEN && color.getRed() <= WATER_COLOR_RED);
}
return false;
}
// creates up to 120 building objects - ensures that
// buildings are not positioned over water.
public Group createBuildings(Group g) {
m_BuildingAppearance = new Appearance();
BranchGroup bg = new BranchGroup();
Texture tex = new TextureLoader("boston.gif", this).getTexture();
BufferedImage image = ((ImageComponent2D) tex.getImage(0)).getImage();
final int nMaxBuildings = 120;
for (int n = 0; n < nMaxBuildings; n++) {
Cuboid building = new Cuboid(this, bg, ComplexObject.GEOMETRY
| ComplexObject.TEXTURE);
float posX = (int) Utils.getRandomNumber(0, LAND_WIDTH);
float posZ = (int) Utils.getRandomNumber(0, LAND_LENGTH);
if (isLocationWater(image, posX, posZ) == false) {
building.createObject(m_BuildingAppearance, new Vector3d(posX,
0, posZ), new Vector3d(Utils.getRandomNumber(3, 2),
Utils.getRandomNumber(8, 7), Utils
.getRandomNumber(3, 2)), "house.gif", null,
null);
}
}
g.addChild(bg);
return bg;
}
// creates three helicopters
public void createHelicopters(Group g) {
for (int n = 0; n < 3; n++)
createHelicopter(g);
}
// edit the positions of the clipping
// planes so we don't clip on the front
// plane prematurely
protected double getBackClipDistance() {
return 50.0;
}
protected double getFrontClipDistance() {
return 0.1;
}
// creates a single helicopter object
public Group createHelicopter(Group g) {
BranchGroup bg = new BranchGroup();
Helicopter heli = new Helicopter(this, bg, ComplexObject.GEOMETRY
| ComplexObject.SOUND);
heli.createObject(new Appearance(), new Vector3d(Utils.getRandomNumber(
0, LAND_WIDTH), Utils.getRandomNumber(15, 5), Utils
.getRandomNumber(0, LAND_LENGTH)), new Vector3d(10, 10, 10),
null, "heli.wav", null);
g.addChild(bg);
return bg;
}
public static void main(String[] args) {
SplineInterpolatorTest splineInterpolatorTest = new SplineInterpolatorTest();
splineInterpolatorTest.saveCommandLineArguments(args);
new MainFrame(splineInterpolatorTest, m_kWidth, m_kHeight);
}
}
/*******************************************************************************
* Copyright (C) 2001 Daniel Selman
*
* First distributed with the book "Java 3D Programming" by Daniel Selman and
* published by Manning Publications. http://manning.com/selman
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* The license can be found on the WWW at: http://www.fsf.org/copyleft/gpl.html
*
* Or by writing to: Free Software Foundation, Inc., 59 Temple Place - Suite
* 330, Boston, MA 02111-1307, USA.
*
* Authors can be contacted at: Daniel Selman: daniel@selman.org
*
* If you make changes you think others would like, please contact one of the
* authors or someone at the www.j3d.org web site.
******************************************************************************/
//*****************************************************************************
/**
* Java3dApplet
*
* Base class for defining a Java 3D applet. Contains some useful methods for
* defining views and scenegraphs etc.
*
* @author Daniel Selman
* @version 1.0
*/
//*****************************************************************************
abstract class Java3dApplet extends Applet {
public static int m_kWidth = 300;
public static int m_kHeight = 300;
protected String[] m_szCommandLineArray = null;
protected VirtualUniverse m_Universe = null;
protected BranchGroup m_SceneBranchGroup = null;
protected Bounds m_ApplicationBounds = null;
// protected com.tornadolabs.j3dtree.Java3dTree m_Java3dTree = null;
public Java3dApplet() {
}
public boolean isApplet() {
try {
System.getProperty("user.dir");
System.out.println("Running as Application.");
return false;
} catch (Exception e) {
}
System.out.println("Running as Applet.");
return true;
}
public URL getWorkingDirectory() throws java.net.MalformedURLException {
URL url = null;
try {
File file = new File(System.getProperty("user.dir"));
System.out.println("Running as Application:");
System.out.println(" " + file.toURL());
return file.toURL();
} catch (Exception e) {
}
System.out.println("Running as Applet:");
System.out.println(" " + getCodeBase());
return getCodeBase();
}
public VirtualUniverse getVirtualUniverse() {
return m_Universe;
}
//public com.tornadolabs.j3dtree.Java3dTree getJ3dTree() {
//return m_Java3dTree;
// }
public Locale getFirstLocale() {
java.util.Enumeration e = m_Universe.getAllLocales();
if (e.hasMoreElements() != false)
return (Locale) e.nextElement();
return null;
}
protected Bounds getApplicationBounds() {
if (m_ApplicationBounds == null)
m_ApplicationBounds = createApplicationBounds();
return m_ApplicationBounds;
}
protected Bounds createApplicationBounds() {
m_ApplicationBounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
100.0);
return m_ApplicationBounds;
}
protected Background createBackground() {
Background back = new Background(new Color3f(0.9f, 0.9f, 0.9f));
back.setApplicationBounds(createApplicationBounds());
return back;
}
public void initJava3d() {
// m_Java3dTree = new com.tornadolabs.j3dtree.Java3dTree();
m_Universe = createVirtualUniverse();
Locale locale = createLocale(m_Universe);
BranchGroup sceneBranchGroup = createSceneBranchGroup();
ViewPlatform vp = createViewPlatform();
BranchGroup viewBranchGroup = createViewBranchGroup(
getViewTransformGroupArray(), vp);
createView(vp);
Background background = createBackground();
if (background != null)
sceneBranchGroup.addChild(background);
// m_Java3dTree.recursiveApplyCapability(sceneBranchGroup);
// m_Java3dTree.recursiveApplyCapability(viewBranchGroup);
locale.addBranchGraph(sceneBranchGroup);
addViewBranchGroup(locale, viewBranchGroup);
onDoneInit();
}
protected void onDoneInit() {
// m_Java3dTree.updateNodes(m_Universe);
}
protected double getScale() {
return 1.0;
}
public TransformGroup[] getViewTransformGroupArray() {
TransformGroup[] tgArray = new TransformGroup[1];
tgArray[0] = new TransformGroup();
// move the camera BACK a little...
// note that we have to invert the matrix as
// we are moving the viewer
Transform3D t3d = new Transform3D();
t3d.setScale(getScale());
t3d.setTranslation(new Vector3d(0.0, 0.0, -20.0));
t3d.invert();
tgArray[0].setTransform(t3d);
return tgArray;
}
protected void addViewBranchGroup(Locale locale, BranchGroup bg) {
locale.addBranchGraph(bg);
}
protected Locale createLocale(VirtualUniverse u) {
return new Locale(u);
}
protected BranchGroup createSceneBranchGroup() {
m_SceneBranchGroup = new BranchGroup();
return m_SceneBranchGroup;
}
protected View createView(ViewPlatform vp) {
View view = new View();
PhysicalBody pb = createPhysicalBody();
PhysicalEnvironment pe = createPhysicalEnvironment();
AudioDevice audioDevice = createAudioDevice(pe);
if (audioDevice != null) {
pe.setAudioDevice(audioDevice);
audioDevice.initialize();
}
view.setPhysicalEnvironment(pe);
view.setPhysicalBody(pb);
if (vp != null)
view.attachViewPlatform(vp);
view.setBackClipDistance(getBackClipDistance());
view.setFrontClipDistance(getFrontClipDistance());
Canvas3D c3d = createCanvas3D();
view.addCanvas3D(c3d);
addCanvas3D(c3d);
return view;
}
protected PhysicalBody createPhysicalBody() {
return new PhysicalBody();
}
protected AudioDevice createAudioDevice(PhysicalEnvironment pe) {
JavaSoundMixer javaSoundMixer = new JavaSoundMixer(pe);
if (javaSoundMixer == null)
System.out.println("create of audiodevice failed");
return javaSoundMixer;
}
protected PhysicalEnvironment createPhysicalEnvironment() {
return new PhysicalEnvironment();
}
protected float getViewPlatformActivationRadius() {
return 100;
}
protected ViewPlatform createViewPlatform() {
ViewPlatform vp = new ViewPlatform();
vp.setViewAttachPolicy(View.RELATIVE_TO_FIELD_OF_VIEW);
vp.setActivationRadius(getViewPlatformActivationRadius());
return vp;
}
protected Canvas3D createCanvas3D() {
GraphicsConfigTemplate3D gc3D = new GraphicsConfigTemplate3D();
gc3D.setSceneAntialiasing(GraphicsConfigTemplate.PREFERRED);
GraphicsDevice gd[] = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getScreenDevices();
Canvas3D c3d = new Canvas3D(gd[0].getBestConfiguration(gc3D));
c3d.setSize(getCanvas3dWidth(c3d), getCanvas3dHeight(c3d));
return c3d;
}
protected int getCanvas3dWidth(Canvas3D c3d) {
return m_kWidth;
}
protected int getCanvas3dHeight(Canvas3D c3d) {
return m_kHeight;
}
protected double getBackClipDistance() {
return 100.0;
}
protected double getFrontClipDistance() {
return 1.0;
}
protected BranchGroup createViewBranchGroup(TransformGroup[] tgArray,
ViewPlatform vp) {
BranchGroup vpBranchGroup = new BranchGroup();
if (tgArray != null && tgArray.length > 0) {
Group parentGroup = vpBranchGroup;
TransformGroup curTg = null;
for (int n = 0; n < tgArray.length; n++) {
curTg = tgArray[n];
parentGroup.addChild(curTg);
parentGroup = curTg;
}
tgArray[tgArray.length - 1].addChild(vp);
} else
vpBranchGroup.addChild(vp);
return vpBranchGroup;
}
protected void addCanvas3D(Canvas3D c3d) {
setLayout(new BorderLayout());
add(c3d, BorderLayout.CENTER);
doLayout();
}
protected VirtualUniverse createVirtualUniverse() {
return new VirtualUniverse();
}
protected void saveCommandLineArguments(String[] szArgs) {
m_szCommandLineArray = szArgs;
}
protected String[] getCommandLineArguments() {
return m_szCommandLineArray;
}
}
//*****************************************************************************
/**
* Utils
*
* @author Daniel Selman
* @version 1.0
*/
//*****************************************************************************
class Utils {
// convert an angular rotation about an axis to a Quaternion
static Quat4f createQuaternionFromAxisAndAngle(Vector3d axis, double angle) {
double sin_a = Math.sin(angle / 2);
double cos_a = Math.cos(angle / 2);
// use a vector so we can call normalize
Vector4f q = new Vector4f();
q.x = (float) (axis.x * sin_a);
q.y = (float) (axis.y * sin_a);
q.z = (float) (axis.z * sin_a);
q.w = (float) cos_a;
// It is necessary to normalise the quaternion
// in case any values are very close to zero.
q.normalize();
// convert to a Quat4f and return
return new Quat4f(q);
}
// convert three rotations about the Euler axes to a Quaternion
static Quat4f createQuaternionFromEuler(double angleX, double angleY,
double angleZ) {
// simply call createQuaternionFromAxisAndAngle
// for each axis and multiply the results
Quat4f qx = createQuaternionFromAxisAndAngle(new Vector3d(1, 0, 0),
angleX);
Quat4f qy = createQuaternionFromAxisAndAngle(new Vector3d(0, 1, 0),
angleY);
Quat4f qz = createQuaternionFromAxisAndAngle(new Vector3d(0, 0, 1),
angleZ);
// qx = qx * qy
qx.mul(qy);
// qx = qx * qz
qx.mul(qz);
return qx;
}
static public double getRandomNumber(double basis, double random) {
return basis + ((float) Math.random() * random * 2f) - (random);
}
static public double getRandomNumber(double basis, double random,
double scale) {
double value = basis + ((float) Math.random() * random * 2f) - (random);
return value * scale;
}
static public StringBuffer readFile(URL urlFile) {
// allocate a temporary buffer to store the input file
StringBuffer szBufferData = new StringBuffer();
Vector keyFramesVector = new Vector();
try {
InputStream inputStream = urlFile.openStream();
int nChar = 0;
// read the entire file into the StringBuffer
while (true) {
nChar = inputStream.read();
// if we have not hit the end of file
// add the character to the StringBuffer
if (nChar != -1)
szBufferData.append((char) nChar);
else
// EOF
break;
}
inputStream.close();
} catch (Exception e) {
System.err.println(e.toString());
return null;
}
return szBufferData;
}
static public RotPosScaleTCBSplinePathInterpolator createSplinePathInterpolator(
Alpha alpha, TransformGroup tg, Transform3D axis, URL urlKeyframes) {
TCBKeyFrame[] keyFrames = readKeyFrames(urlKeyframes);
if (keyFrames != null)
return new RotPosScaleTCBSplinePathInterpolator(alpha, tg, axis,
keyFrames);
return null;
}
static public TCBKeyFrame[] readKeyFrames(URL urlKeyframes) {
StringBuffer szBufferData = readFile(urlKeyframes);
if (szBufferData == null)
return null;
Vector keyFramesVector = new Vector();
// create a tokenizer to tokenize the input file at whitespace
java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(
szBufferData.toString());
// each keyframe is defined as follows
// - knot (0 >= k <= 1)
// - position (x,y,z)
// - rotation (rx,ry,rz)
// - scale (x,y,z)
// - tension (-1 >= t <= 1)
// - continuity (-1 >= c <= 1)
// - bias (-1 >= b <= 1)
// - linear (int - 0 or 1)
while (true) {
try {
float knot = Float.parseFloat(tokenizer.nextToken());
float posX = Float.parseFloat(tokenizer.nextToken());
float posY = Float.parseFloat(tokenizer.nextToken());
float posZ = Float.parseFloat(tokenizer.nextToken());
float rotX = Float.parseFloat(tokenizer.nextToken());
float rotY = Float.parseFloat(tokenizer.nextToken());
float rotZ = Float.parseFloat(tokenizer.nextToken());
float scaleX = Float.parseFloat(tokenizer.nextToken());
float scaleY = Float.parseFloat(tokenizer.nextToken());
float scaleZ = Float.parseFloat(tokenizer.nextToken());
float tension = Float.parseFloat(tokenizer.nextToken());
float continuity = Float.parseFloat(tokenizer.nextToken());
float bias = Float.parseFloat(tokenizer.nextToken());
int linear = Integer.parseInt(tokenizer.nextToken());
TCBKeyFrame keyframe = new TCBKeyFrame(knot, linear,
new Point3f(posX, posY, posZ),
createQuaternionFromEuler(rotX, rotY, rotZ),
new Point3f(scaleX, scaleY, scaleZ), tension,
continuity, bias);
keyFramesVector.add(keyframe);
} catch (Exception e) {
break;
}
}
// create the return structure and populate
TCBKeyFrame[] keysReturn = new TCBKeyFrame[keyFramesVector.size()];
for (int n = 0; n < keysReturn.length; n++)
keysReturn[n] = (TCBKeyFrame) keyFramesVector.get(n);
// return the array
return keysReturn;
}
}
//this class defines an Alpha class that returns a random
//value every N milliseconds.
class UiAlpha extends Alpha implements ChangeListener {
protected Alpha m_Alpha = null;
protected float m_AlphaValue = 0.5f;
JButton m_Button = null;
boolean m_bAuto = true;
public UiAlpha(Alpha alpha) {
m_Alpha = alpha;
Frame frame = new Frame("Alpha Control Panel");
JPanel panel = new JPanel();
frame.add(panel);
addUiToPanel(panel);
frame.pack();
frame.setSize(new Dimension(400, 80));
frame.validate();
frame.setVisible(true);
}
protected void addUiToPanel(JPanel panel) {
JSlider slider = new JSlider();
slider.addChangeListener(this);
panel.add(slider);
m_Button = new JButton("Auto");
m_Button.addChangeListener(this);
panel.add(m_Button);
}
public void stateChanged(ChangeEvent e) {
if (e.getSource() instanceof JSlider) {
m_AlphaValue = ((JSlider) e.getSource()).getValue() / 100.0f;
m_bAuto = false;
} else {
m_bAuto = true;
}
}
// core method override
// returns the Alpha value for a given time
public float value(long time) {
if (m_bAuto == true)
return m_Alpha.value(time);
return m_AlphaValue;
}
}
class Land extends ComplexObject {
public static final float WIDTH = 1.0f;
public static final float LENGTH = 1.0f;
public static final float HEIGHT = 0.0f;
public Land(Component comp, Group g, int nFlags) {
super(comp, g, nFlags);
}
protected Group createGeometryGroup(Appearance app, Vector3d position,
Vector3d scale, String szTextureFile, String szSoundFile) {
int nFlags = GeometryArray.COORDINATES | GeometryArray.NORMALS;
if ((m_nFlags & TEXTURE) == TEXTURE)
nFlags |= GeometryArray.TEXTURE_COORDINATE_2;
QuadArray quadArray = new QuadArray(4, nFlags);
float[] coordArray = { -WIDTH, HEIGHT, LENGTH, WIDTH, HEIGHT, LENGTH,
WIDTH, HEIGHT, -LENGTH, -WIDTH, HEIGHT, -LENGTH };
quadArray.setCoordinates(0, coordArray, 0, coordArray.length / 3);
for (int n = 0; n < coordArray.length / 3; n++)
quadArray.setNormal(n, new Vector3f(0, 1, 0));
if ((m_nFlags & TEXTURE) == TEXTURE) {
float[] texArray = { 0, 0, 1, 0, 1, 1, 0, 1 };
quadArray.setTextureCoordinates(0, 0, texArray, 0,
coordArray.length / 3);
setTexture(app, szTextureFile);
}
BranchGroup bg = new BranchGroup();
Shape3D shape = new Shape3D(quadArray, app);
bg.addChild(shape);
return bg;
}
}
abstract class ComplexObject extends BranchGroup {
protected Group m_ParentGroup = null;
protected int m_nFlags = 0;
protected BackgroundSound m_CollideSound = null;
protected Component m_Component = null;
protected TransformGroup m_TransformGroup = null;
protected TransformGroup m_BehaviorTransformGroup = null;
public static final int SOUND = 0x001;
public static final int GEOMETRY = 0x002;
public static final int TEXTURE = 0x004;
public static final int COLLISION = 0x008;
public static final int COLLISION_SOUND = 0x010;
public ComplexObject(Component comp, Group group, int nFlags) {
m_ParentGroup = group;
m_nFlags = nFlags;
m_Component = comp;
}
public Bounds getGeometryBounds() {
return new BoundingSphere(new Point3d(0, 0, 0), 100);
}
private MediaContainer loadSoundFile(String szFile) {
try {
File file = new File(System.getProperty("user.dir"));
URL url = file.toURL();
URL soundUrl = new URL(url, szFile);
return new MediaContainer(soundUrl);
} catch (Exception e) {
System.err.println("Error could not load sound file: " + e);
System.exit(-1);
}
return null;
}
protected void setTexture(Appearance app, String szFile) {
Texture tex = new TextureLoader(szFile, m_Component).getTexture();
app.setTexture(tex);
}
abstract protected Group createGeometryGroup(Appearance app,
Vector3d position, Vector3d scale, String szTextureFile,
String szSoundFile);
protected Group loadGeometryGroup(String szModel, Appearance app)
throws java.io.FileNotFoundException {
// load the object file
Scene scene = null;
Shape3D shape = null;
// read in the geometry information from the data file
ObjectFile objFileloader = new ObjectFile(ObjectFile.RESIZE);
scene = objFileloader.load(szModel);
// retrieve the Shape3D object from the scene
BranchGroup branchGroup = scene.getSceneGroup();
shape = (Shape3D) branchGroup.getChild(0);
shape.setAppearance(app);
return branchGroup;
}
protected int getSoundLoop(boolean bCollide) {
return 1;
}
protected float getSoundPriority(boolean bCollide) {
return 1.0f;
}
protected float getSoundInitialGain(boolean bCollide) {
return 1.0f;
}
protected boolean getSoundInitialEnable(boolean bCollide) {
return true;
}
protected boolean getSoundContinuousEnable(boolean bCollide) {
return false;
}
protected Bounds getSoundSchedulingBounds(boolean bCollide) {
return new BoundingSphere(new Point3d(0, 0, 0), 1.0);
}
protected boolean getSoundReleaseEnable(boolean bCollide) {
return true;
}
protected Point2f[] getSoundDistanceGain(boolean bCollide) {
return null;
}
protected void setSoundAttributes(Sound sound, boolean bCollide) {
sound.setCapability(Sound.ALLOW_ENABLE_WRITE);
sound.setCapability(Sound.ALLOW_ENABLE_READ);
sound.setSchedulingBounds(getSoundSchedulingBounds(bCollide));
sound.setEnable(getSoundInitialEnable(bCollide));
sound.setLoop(getSoundLoop(bCollide));
sound.setPriority(getSoundPriority(bCollide));
sound.setInitialGain(getSoundInitialGain(bCollide));
sound.setContinuousEnable(getSoundContinuousEnable(bCollide));
sound.setReleaseEnable(bCollide);
if (sound instanceof PointSound) {
PointSound pointSound = (PointSound) sound;
pointSound.setInitialGain(getSoundInitialGain(bCollide));
Point2f[] gainArray = getSoundDistanceGain(bCollide);
if (gainArray != null)
pointSound.setDistanceGain(gainArray);
}
}
public Group createObject(Appearance app, Vector3d position,
Vector3d scale, String szTextureFile, String szSoundFile,
String szCollisionSound) {
m_TransformGroup = new TransformGroup();
Transform3D t3d = new Transform3D();
t3d.setScale(scale);
t3d.setTranslation(position);
m_TransformGroup.setTransform(t3d);
m_BehaviorTransformGroup = new TransformGroup();
if ((m_nFlags & GEOMETRY) == GEOMETRY)
m_BehaviorTransformGroup.addChild(createGeometryGroup(app,
position, scale, szTextureFile, szSoundFile));
if ((m_nFlags & SOUND) == SOUND) {
MediaContainer media = loadSoundFile(szSoundFile);
PointSound pointSound = new PointSound(media,
getSoundInitialGain(false), 0, 0, 0);
setSoundAttributes(pointSound, false);
m_BehaviorTransformGroup.addChild(pointSound);
}
if ((m_nFlags & COLLISION) == COLLISION) {
m_BehaviorTransformGroup
.setCapability(Node.ENABLE_COLLISION_REPORTING);
m_BehaviorTransformGroup.setCollidable(true);
m_BehaviorTransformGroup.setCollisionBounds(getGeometryBounds());
if ((m_nFlags & COLLISION_SOUND) == COLLISION_SOUND) {
MediaContainer collideMedia = loadSoundFile(szCollisionSound);
m_CollideSound = new BackgroundSound(collideMedia, 1);
setSoundAttributes(m_CollideSound, true);
m_TransformGroup.addChild(m_CollideSound);
}
CollisionBehavior collision = new CollisionBehavior(
m_BehaviorTransformGroup, this);
collision.setSchedulingBounds(getGeometryBounds());
m_BehaviorTransformGroup.addChild(collision);
}
m_TransformGroup.addChild(m_BehaviorTransformGroup);
m_ParentGroup.addChild(m_TransformGroup);
return m_BehaviorTransformGroup;
}
public void onCollide(boolean bCollide) {
System.out.println("Collide: " + bCollide);
if (m_CollideSound != null && bCollide == true)
m_CollideSound.setEnable(true);
}
public void attachBehavior(Behavior beh) {
m_BehaviorTransformGroup
.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
beh.setSchedulingBounds(getGeometryBounds());
m_BehaviorTransformGroup.addChild(beh);
}
public TransformGroup getBehaviorTransformGroup() {
return m_BehaviorTransformGroup;
}
public void attachSplinePathInterpolator(Alpha alpha, Transform3D axis,
URL urlKeyframes) {
// read a spline path definition file and
// add a Spline Path Interpolator to the TransformGroup for the object.
m_BehaviorTransformGroup
.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
RotPosScaleTCBSplinePathInterpolator splineInterpolator = Utils
.createSplinePathInterpolator(alpha, m_BehaviorTransformGroup,
axis, urlKeyframes);
if (splineInterpolator != null) {
splineInterpolator.setSchedulingBounds(getGeometryBounds());
m_BehaviorTransformGroup.addChild(splineInterpolator);
} else {
System.out.println("attachSplinePathInterpolator failed for: "
+ urlKeyframes);
}
}
}
/**
* This class is a simple behavior that invokes the KeyNavigator to modify the
* view platform transform.
*/
class CollisionBehavior extends Behavior {
private WakeupOnCollisionEntry wakeupOne = null;
private WakeupOnCollisionExit wakeupTwo = null;
private WakeupCriterion[] wakeupArray = new WakeupCriterion[2];
private WakeupCondition wakeupCondition = null;
private ComplexObject m_Owner = null;
public CollisionBehavior(Node node, ComplexObject owner) {
wakeupOne = new WakeupOnCollisionEntry(node,
WakeupOnCollisionEntry.USE_GEOMETRY);
wakeupTwo = new WakeupOnCollisionExit(node,
WakeupOnCollisionExit.USE_GEOMETRY);
wakeupArray[0] = wakeupOne;
wakeupArray[1] = wakeupTwo;
wakeupCondition = new WakeupOr(wakeupArray);
m_Owner = owner;
}
/**
* Override Behavior's initialize method to setup wakeup criteria.
*/
public void initialize() {
// Establish initial wakeup criteria
wakeupOn(wakeupCondition);
}
/**
* Override Behavior's stimulus method to handle the event.
*/
public void processStimulus(Enumeration criteria) {
WakeupCriterion genericEvt;
while (criteria.hasMoreElements()) {
genericEvt = (WakeupCriterion) criteria.nextElement();
if (genericEvt instanceof WakeupOnCollisionEntry) {
m_Owner.onCollide(true);
} else if (genericEvt instanceof WakeupOnCollisionExit) {
m_Owner.onCollide(false);
}
}
// Set wakeup criteria for next time
wakeupOn(wakeupCondition);
}
}
//creates a 2x2x2 cuboid with its base at y=0
class Cuboid extends ComplexObject {
public Cuboid(Component comp, Group g, int nFlags) {
super(comp, g, nFlags);
}
protected Group createGeometryGroup(Appearance app, Vector3d position,
Vector3d scale, String szTextureFile, String szSoundFile) {
int nFlags = GeometryArray.COORDINATES | GeometryArray.NORMALS;
if ((m_nFlags & TEXTURE) == TEXTURE)
nFlags |= GeometryArray.TEXTURE_COORDINATE_2;
QuadArray quadArray = new QuadArray(24, nFlags);
quadArray.setCoordinates(0, verts, 0, 24);
for (int n = 0; n < 24; n++)
quadArray.setNormal(n, normals[n / 4]);
if ((m_nFlags & TEXTURE) == TEXTURE) {
quadArray.setTextureCoordinates(0, 0, tcoords, 0, 24);
setTexture(app, szTextureFile);
}
Shape3D shape = new Shape3D(quadArray, app);
BranchGroup bg = new BranchGroup();
bg.addChild(shape);
return bg;
}
private static final float[] verts = {
// front face
1.0f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, -1.0f, 2.0f, 1.0f, -1.0f, 0.0f,
1.0f,
// back face
-1.0f, 0.0f, -1.0f, -1.0f, 2.0f, -1.0f, 1.0f, 2.0f, -1.0f, 1.0f,
0.0f, -1.0f,
// right face
1.0f, 0.0f, -1.0f, 1.0f, 2.0f, -1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 0.0f,
1.0f,
// left face
-1.0f, 0.0f, 1.0f, -1.0f, 2.0f, 1.0f, -1.0f, 2.0f, -1.0f, -1.0f,
0.0f, -1.0f,
// top face
1.0f, 2.0f, 1.0f, 1.0f, 2.0f, -1.0f, -1.0f, 2.0f, -1.0f, -1.0f,
2.0f, 1.0f,
// bottom face
-1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f,
0.0f, 1.0f, };
private static final float[] tcoords = {
// front
1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
// back
1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
//right
1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
// left
1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
// top
1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
// bottom
0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f };
private static final Vector3f[] normals = { new Vector3f(0.0f, 0.0f, 1.0f), // front
// face
new Vector3f(0.0f, 0.0f, -1.0f), // back face
new Vector3f(1.0f, 0.0f, 0.0f), // right face
new Vector3f(-1.0f, 0.0f, 0.0f), // left face
new Vector3f(0.0f, 1.0f, 0.0f), // top face
new Vector3f(0.0f, -1.0f, 0.0f), // bottom face
};
}
class Helicopter extends ComplexObject
{
public static final float WIDTH = 2.0f;
public static final float HEIGHT = 2.0f;
public static final float LENGTH = 2.0f;
public Helicopter( Component comp, Group g, int nFlags )
{
super( comp, g, nFlags );
}
protected Group createGeometryGroup( Appearance app, Vector3d position, Vector3d scale, String szTextureFile, String szSoundFile )
{
TransformGroup tg = new TransformGroup( );
// we need to flip the helicopter model
// 90 degrees about the X axis
Transform3D t3d = new Transform3D( );
t3d.rotX( Math.toRadians( -90 ) );
tg.setTransform( t3d );
try
{
tg.addChild( loadGeometryGroup( "heli.obj", app ) );
// create an Alpha object for the Interpolator
Alpha alpha = new Alpha( -1,
Alpha.INCREASING_ENABLE | Alpha.DECREASING_ENABLE,
(long) Utils.getRandomNumber( 0, 500 ),
(long)Utils.getRandomNumber( 0, 500 ),
(long)Utils.getRandomNumber( 20000, 5000 ),
4000,
100,
(long) Utils.getRandomNumber( 20000, 5000 ),
5000,
50 );
attachSplinePathInterpolator( alpha,
new Transform3D( ),
new URL( ((Java3dApplet) m_Component).getWorkingDirectory( ), "heli_spline.xls" ) );
}
catch( Exception e )
{
System.err.println( e.toString( ) );
}
return tg;
}
protected int getSoundLoop( boolean bCollide )
{
return -1;
}
protected float getSoundPriority( boolean bCollide )
{
return 1.0f;
}
protected float getSoundInitialGain( boolean bCollide )
{
return 3.0f;
}
protected Point2f[] getSoundDistanceGain( boolean bCollide )
{
Point2f[] gainArray = new Point2f[2];
gainArray[0] = new Point2f( 2, 0.2f );
gainArray[1] = new Point2f( 20, 0.05f );
return gainArray;
}
protected boolean getSoundInitialEnable( boolean bCollide )
{
return true;
}
protected boolean getSoundContinuousEnable( boolean bCollide )
{
return false;
}
protected Bounds getSoundSchedulingBounds( boolean bCollide )
{
return new BoundingSphere( new Point3d( 0,0,0 ), 20 );
}
protected boolean getSoundReleaseEnable( boolean bCollide )
{
return true;
}
}
//heli.obj
/*
# Mon Jun 21 15:53:56 1993
#
#
g
v -0.010000 13.150000 3.670000
v -0.620000 3.690000 3.670000
v -0.620000 -3.740000 3.670000
v 0.580000 -3.740000 3.670000
v 0.580000 3.690000 3.670000
v -0.010000 13.150000 0.000000
v -0.620000 3.690000 0.000000
v -0.620000 -3.740000 0.000000
v 0.580000 -3.740000 0.000000
v 0.580000 3.690000 0.000000
v 0.000000 -3.320000 6.130000
v 1.220000 -2.890000 6.130000
v 1.840000 -2.020000 6.130000
v 2.450000 -0.720000 6.130000
v 0.610000 10.060000 6.130000
v -0.590000 10.060000 6.130000
v -2.430000 -0.720000 6.130000
v -1.820000 -2.020000 6.130000
v -1.200000 -2.890000 6.130000
v 0.000000 -3.320000 3.820000
v 1.220000 -2.890000 3.820000
v 1.840000 -2.020000 3.820000
v 2.450000 -0.720000 3.820000
v 0.610000 10.060000 3.820000
v -0.590000 10.060000 3.820000
v -2.430000 -0.720000 3.820000
v -1.820000 -2.020000 3.820000
v -1.200000 -2.890000 3.820000
v 1.110000 0.000000 8.100000
v 1.890000 0.010000 7.730000
v 2.660000 0.020000 7.160000
v 1.040000 0.350000 8.100000
v 1.790000 0.600000 7.730000
v 2.530000 0.840000 7.160000
v 0.890000 0.660000 8.100000
v 1.520000 1.130000 7.730000
v 2.150000 1.580000 7.160000
v 0.640000 0.900000 8.100000
v 1.090000 1.540000 7.730000
v 1.540000 2.170000 7.160000
v 0.330000 1.070000 8.100000
v 0.570000 1.800000 7.730000
v 0.800000 2.540000 7.160000
v 0.000000 1.110000 8.100000
v -0.010000 1.890000 7.730000
v -0.010000 2.660000 7.160000
v -0.350000 1.040000 8.100000
v -0.580000 1.790000 7.730000
v -0.840000 2.530000 7.160000
v -0.660000 0.890000 8.100000
v -1.120000 1.520000 7.730000
v -1.580000 2.150000 7.160000
v -0.900000 0.640000 8.100000
v -1.540000 1.090000 7.730000
v -2.170000 1.550000 7.160000
v -1.040000 0.330000 8.100000
v -1.800000 0.570000 7.730000
v -2.540000 0.800000 7.160000
v -1.110000 0.000000 8.100000
v -1.890000 0.000000 7.730000
v -2.660000 -0.010000 7.160000
v -1.040000 -0.350000 8.100000
v -1.790000 -0.580000 7.730000
v -2.540000 -0.840000 7.160000
v -0.890000 -0.660000 8.100000
v -1.520000 -1.120000 7.730000
v -2.150000 -1.580000 7.160000
v -0.640000 -0.900000 8.100000
v -1.090000 -1.530000 7.730000
v -1.550000 -2.170000 7.160000
v -0.330000 -1.040000 8.100000
v -0.570000 -1.800000 7.730000
v -0.800000 -2.540000 7.160000
v 0.000000 -1.110000 8.100000
v 0.000000 -1.890000 7.730000
v 0.010000 -2.660000 7.160000
v 0.350000 -1.040000 8.100000
v 0.580000 -1.790000 7.730000
v 0.830000 -2.540000 7.160000
v 0.660000 -0.890000 8.100000
v 1.120000 -1.520000 7.730000
v 1.580000 -2.150000 7.160000
v 0.900000 -0.640000 8.100000
v 1.530000 -1.100000 7.730000
v 2.170000 -1.550000 7.160000
v 0.000000 0.000000 8.300000
v 1.040000 -0.330000 8.100000
v 1.800000 -0.570000 7.730000
v 2.540000 -0.800000 7.160000
v 0.000000 0.000000 7.160000
v -0.150000 -0.840000 6.860000
v 0.160000 -1.370000 6.860000
v 0.160000 -2.420000 6.860000
v 0.520000 -2.420000 6.860000
v 0.520000 -2.960000 6.860000
v 0.160000 -2.960000 6.860000
v 0.880000 -5.090000 6.860000
v 0.880000 -30.559999 6.860000
v 0.520000 -31.090000 6.860000
v -0.510000 -31.090000 6.860000
v -0.870000 -30.559999 6.860000
v -0.870000 -5.090000 6.860000
v -0.150000 -2.960000 6.860000
v -0.510000 -2.960000 6.860000
v -0.510000 -2.420000 6.860000
v -0.150000 -2.420000 6.860000
v -0.150000 -0.840000 6.890000
v 0.160000 -1.370000 6.890000
v 0.160000 -2.420000 6.890000
v 0.520000 -2.420000 6.890000
v 0.520000 -2.960000 6.890000
v 0.160000 -2.960000 6.890000
v 0.880000 -5.090000 6.890000
v 0.880000 -30.559999 6.890000
v 0.520000 -31.090000 6.890000
v -0.510000 -31.090000 6.890000
v -0.870000 -30.559999 6.890000
v -0.870000 -5.090000 6.890000
v -0.150000 -2.960000 6.890000
v -0.510000 -2.960000 6.890000
v -0.510000 -2.420000 6.890000
v -0.150000 -2.420000 6.890000
v 0.750000 -0.410000 6.860000
v 1.360000 -0.230000 6.860000
v 2.360000 -0.550000 6.860000
v 2.480000 -0.210000 6.860000
v 2.980000 -0.380000 6.860000
v 2.880000 -0.720000 6.860000
v 5.120000 -0.690000 6.860000
v 29.350000 -8.570000 6.860000
v 29.740000 -9.070000 6.860000
v 29.410000 -10.090000 6.860000
v 28.799999 -10.270000 6.860000
v 4.570000 -2.400000 6.860000
v 2.770000 -1.040000 6.860000
v 2.660000 -1.400000 6.860000
v 2.160000 -1.220000 6.860000
v 2.260000 -0.900000 6.860000
v 0.750000 -0.410000 6.900000
v 1.360000 -0.230000 6.900000
v 2.360000 -0.550000 6.900000
v 2.480000 -0.210000 6.900000
v 2.980000 -0.380000 6.900000
v 2.880000 -0.720000 6.900000
v 5.120000 -0.690000 6.900000
v 29.350000 -8.570000 6.900000
v 29.740000 -9.070000 6.900000
v 29.410000 -10.090000 6.900000
v 28.799999 -10.270000 6.900000
v 4.570000 -2.400000 6.900000
v 2.770000 -1.040000 6.900000
v 2.660000 -1.400000 6.900000
v 2.160000 -1.220000 6.900000
v 2.260000 -0.900000 6.900000
v 0.630000 0.580000 6.860000
v 0.670000 1.210000 6.860000
v 1.280000 2.060000 6.860000
v 0.990000 2.280000 6.860000
v 1.320000 2.710000 6.860000
v 1.600000 2.490000 6.860000
v 2.260000 4.640000 6.860000
v 17.240000 25.240000 6.860000
v 17.840000 25.459999 6.860000
v 18.700001 24.850000 6.860000
v 18.680000 24.200001 6.860000
v 3.700000 3.600000 6.860000
v 1.880000 2.300000 6.860000
v 2.170000 2.080000 6.860000
v 1.840000 1.650000 6.860000
v 1.560000 1.870000 6.860000
v 0.630000 0.580000 6.890000
v 0.670000 1.210000 6.890000
v 1.280000 2.060000 6.890000
v 0.990000 2.280000 6.890000
v 1.320000 2.710000 6.890000
v 1.600000 2.490000 6.890000
v 2.260000 4.640000 6.890000
v 17.240000 25.240000 6.890000
v 17.840000 25.459999 6.890000
v 18.700001 24.850000 6.890000
v 18.680000 24.200001 6.890000
v 3.700000 3.600000 6.890000
v 1.880000 2.300000 6.890000
v 2.170000 2.080000 6.890000
v 1.840000 1.650000 6.890000
v 1.560000 1.870000 6.890000
v -0.340000 0.780000 6.860000
v -0.920000 1.010000 6.860000
v -1.540000 1.870000 6.860000
v -1.830000 1.660000 6.860000
v -2.150000 2.080000 6.860000
v -1.860000 2.310000 6.860000
v -3.690000 3.600000 6.860000
v -18.660000 24.209999 6.860000
v -18.690001 24.850000 6.860000
v -17.820000 25.469999 6.860000
v -17.219999 25.250000 6.860000
v -2.250000 4.650000 6.860000
v -1.590000 2.500000 6.860000
v -1.300000 2.710000 6.860000
v -0.980000 2.280000 6.860000
v -1.260000 2.070000 6.860000
v -0.340000 0.780000 6.890000
v -0.920000 1.010000 6.890000
v -1.540000 1.870000 6.890000
v -1.830000 1.660000 6.890000
v -2.150000 2.080000 6.890000
v -1.860000 2.310000 6.890000
v -3.690000 3.600000 6.890000
v -18.660000 24.209999 6.890000
v -18.690001 24.850000 6.890000
v -17.820000 25.469999 6.890000
v -17.219999 25.250000 6.890000
v -2.250000 4.650000 6.890000
v -1.590000 2.500000 6.890000
v -1.300000 2.710000 6.890000
v -0.980000 2.280000 6.890000
v -1.260000 2.070000 6.890000
v -0.820000 -0.060000 6.860000
v -1.230000 -0.540000 6.860000
v -2.220000 -0.880000 6.860000
v -2.120000 -1.220000 6.860000
v -2.630000 -1.380000 6.860000
v -2.740000 -1.030000 6.860000
v -4.540000 -2.380000 6.860000
v -28.770000 -10.250000 6.860000
v -29.379999 -10.070000 6.860000
v -29.700001 -9.080000 6.860000
v -29.320000 -8.560000 6.860000
v -5.090000 -0.690000 6.860000
v -2.850000 -0.720000 6.860000
v -2.960000 -0.380000 6.860000
v -2.440000 -0.210000 6.860000
v -2.320000 -0.560000 6.860000
v -0.820000 -0.060000 6.890000
v -1.230000 -0.540000 6.890000
v -2.220000 -0.880000 6.890000
v -2.120000 -1.220000 6.890000
v -2.630000 -1.380000 6.890000
v -2.740000 -1.030000 6.890000
v -4.540000 -2.380000 6.890000
v -28.770000 -10.250000 6.890000
v -29.379999 -10.070000 6.890000
v -29.700001 -9.080000 6.890000
v -29.320000 -8.560000 6.890000
v -5.090000 -0.690000 6.890000
v -2.850000 -0.720000 6.890000
v -2.960000 -0.380000 6.890000
v -2.440000 -0.210000 6.890000
v -2.320000 -0.560000 6.890000
v 5.670000 -9.090000 -7.210000
v 5.670000 -7.990000 -8.530000
v 5.670000 6.200000 -8.530000
v 5.670000 6.200000 -8.750000
v 5.670000 -7.990000 -8.750000
v 5.670000 -9.180000 -7.210000
v 6.040000 -9.090000 -7.210000
v 6.040000 -7.990000 -8.530000
v 6.040000 6.200000 -8.530000
v 6.040000 6.200000 -8.750000
v 6.040000 -7.990000 -8.750000
v 6.040000 -9.180000 -7.210000
v -6.030000 -9.080000 -7.200000
v -6.030000 -7.980000 -8.520000
v -6.030000 6.200000 -8.520000
v -6.030000 6.200000 -8.740000
v -6.030000 -7.980000 -8.740000
v -6.030000 -9.170000 -7.200000
v -5.660000 -9.080000 -7.200000
v -5.660000 -7.980000 -8.520000
v -5.660000 6.200000 -8.520000
v -5.660000 6.200000 -8.740000
v -5.660000 -7.980000 -8.740000
v -5.660000 -9.170000 -7.200000
v 2.960000 -4.140000 -3.420000
v 3.130000 -4.140000 -3.320000
v 6.160000 -4.140000 -8.560000
v 5.980000 -4.140000 -8.660000
v 2.960000 -4.330000 -3.420000
v 3.130000 -4.330000 -3.320000
v 6.160000 -4.330000 -8.560000
v 5.980000 -4.330000 -8.660000
v 2.960000 2.330000 -3.400000
v 3.130000 2.330000 -3.300000
v 6.150000 2.330000 -8.540000
v 5.990000 2.330000 -8.640000
v 2.960000 2.150000 -3.400000
v 3.130000 2.150000 -3.300000
v 6.150000 2.150000 -8.540000
v 5.990000 2.150000 -8.640000
v -3.120000 -4.130000 -3.310000
v -2.950000 -4.130000 -3.410000
v -5.970000 -4.130000 -8.650000
v -6.150000 -4.130000 -8.550000
v -3.120000 -4.320000 -3.310000
v -2.950000 -4.320000 -3.410000
v -5.970000 -4.320000 -8.650000
v -6.150000 -4.320000 -8.550000
v -3.100000 2.330000 -3.300000
v -2.930000 2.330000 -3.400000
v -5.960000 2.330000 -8.630000
v -6.120000 2.330000 -8.540000
v -3.100000 2.150000 -3.300000
v -2.930000 2.150000 -3.400000
v -5.960000 2.150000 -8.630000
v -6.120000 2.150000 -8.540000
v -5.090000 33.450001 7.670000
v -5.090000 35.240002 10.180000
v -5.090000 36.660000 10.180000
v -5.090000 35.959999 6.090000
v -5.090000 34.520000 6.090000
v -4.910000 33.450001 7.670000
v -4.910000 35.240002 10.180000
v -4.910000 36.660000 10.180000
v -4.910000 35.959999 6.090000
v -4.910000 34.520000 6.090000
v 4.660000 33.450001 7.670000
v 4.660000 35.240002 10.180000
v 4.660000 36.660000 10.180000
v 4.660000 35.959999 6.090000
v 4.660000 34.520000 6.090000
v 4.840000 33.450001 7.670000
v 4.840000 35.240002 10.180000
v 4.840000 36.660000 10.180000
v 4.840000 35.959999 6.090000
v 4.840000 34.520000 6.090000
v -4.880000 36.480000 8.270000
v 4.580000 36.480000 8.270000
v 4.580000 36.480000 7.930000
v -4.880000 36.480000 7.930000
v -4.880000 34.180000 8.270000
v 4.580000 34.180000 8.270000
v 4.580000 34.180000 7.930000
v -4.880000 34.180000 7.930000
v 0.140000 36.180000 7.750000
v 0.140000 34.950001 7.750000
v 0.140000 31.260000 2.330000
v 0.140000 34.029999 -1.700000
v 0.140000 35.250000 -1.700000
v 0.140000 34.029999 2.330000
v -0.130000 36.180000 7.750000
v -0.130000 34.950001 7.750000
v -0.130000 31.260000 2.330000
v -0.130000 34.029999 -1.700000
v -0.130000 35.250000 -1.700000
v -0.130000 34.029999 2.330000
v 1.430000 -10.690000 0.000000
v 2.740000 -9.840000 0.010000
v 3.810000 -8.830000 0.020000
v 4.730000 -6.960000 0.040000
v 5.270000 -4.100000 0.040000
v 5.190000 -0.720000 0.040000
v 4.350000 2.820000 0.030000
v 1.820000 8.390000 0.010000
v 0.820000 11.770000 0.000000
v 0.660000 13.130000 0.000000
v 0.660000 10.250000 0.000000
v 1.390000 -10.690000 0.370000
v 2.640000 -9.840000 0.700000
v 3.690000 -8.830000 0.980000
v 4.570000 -6.960000 1.220000
v 5.090000 -4.100000 1.360000
v 5.020000 -0.720000 1.340000
v 4.190000 2.820000 1.120000
v 1.750000 8.390000 0.460000
v 0.790000 11.770000 0.210000
v 0.640000 13.130000 0.170000
v 0.640000 10.250000 0.170000
v 1.250000 -10.690000 0.710000
v 2.380000 -9.840000 1.340000
v 3.320000 -8.830000 1.870000
v 4.130000 -6.960000 2.320000
v 4.600000 -4.100000 2.590000
v 4.530000 -0.720000 2.550000
v 3.790000 2.820000 2.130000
v 1.580000 8.390000 0.890000
v 0.710000 11.770000 0.400000
v 0.580000 13.130000 0.330000
v 0.580000 10.250000 0.330000
v 1.030000 -10.690000 1.000000
v 1.980000 -9.840000 1.900000
v 2.750000 -8.830000 2.640000
v 3.410000 -6.960000 3.280000
v 3.800000 -4.100000 3.650000
v 3.750000 -0.720000 3.600000
v 3.140000 2.820000 3.020000
v 1.310000 8.390000 1.260000
v 0.580000 11.770000 0.570000
v 0.480000 13.130000 0.470000
v 0.480000 10.250000 0.470000
v 0.750000 -10.690000 1.230000
v 1.440000 -9.840000 2.330000
v 2.000000 -8.830000 3.250000
v 2.490000 -6.960000 4.030000
v 2.780000 -4.100000 4.480000
v 2.730000 -0.720000 4.420000
v 2.280000 2.820000 3.710000
v 0.950000 8.390000 1.550000
v 0.430000 11.770000 0.710000
v 0.340000 13.130000 0.570000
v 0.340000 10.250000 0.570000
v 0.420000 -10.690000 1.380000
v 0.810000 -9.840000 2.630000
v 1.130000 -8.830000 3.650000
v 1.410000 -6.960000 4.530000
v 1.570000 -4.100000 5.040000
v 1.550000 -0.720000 4.960000
v 1.290000 2.820000 4.160000
v 0.520000 8.390000 1.740000
v 0.230000 11.770000 0.790000
v 0.190000 13.130000 0.640000
v 0.190000 10.250000 0.640000
v 0.060000 -10.690000 1.440000
v 0.130000 -9.840000 2.750000
v 0.180000 -8.830000 3.820000
v 0.230000 -6.960000 4.750000
v 0.260000 -4.100000 5.280000
v 0.260000 -0.720000 5.210000
v 0.210000 2.820000 4.360000
v 0.070000 8.390000 1.830000
v 0.020000 11.770000 0.830000
v 0.010000 13.130000 0.670000
v 0.010000 10.250000 0.670000
v -0.290000 -10.690000 1.420000
v -0.550000 -9.840000 2.690000
v -0.760000 -8.830000 3.750000
v -0.940000 -6.960000 4.650000
v -1.040000 -4.100000 5.180000
v -1.030000 -0.720000 5.110000
v -0.860000 2.820000 4.280000
v -0.370000 8.390000 1.800000
v -0.170000 11.770000 0.810000
v -0.140000 13.130000 0.670000
v -0.140000 10.250000 0.670000
v -0.640000 -10.690000 1.300000
v -1.210000 -9.840000 2.480000
v -1.670000 -8.830000 3.450000
v -2.070000 -6.960000 4.280000
v -2.310000 -4.100000 4.760000
v -2.270000 -0.720000 4.690000
v -1.910000 2.820000 3.930000
v -0.810000 8.390000 1.650000
v -0.370000 11.770000 0.750000
v -0.310000 13.130000 0.610000
v -0.310000 10.250000 0.610000
v -0.950000 -10.690000 1.100000
v -1.780000 -9.840000 2.080000
v -2.480000 -8.830000 2.920000
v -3.070000 -6.960000 3.630000
v -3.420000 -4.100000 4.040000
v -3.370000 -0.720000 3.980000
v -2.830000 2.820000 3.330000
v -1.190000 8.390000 1.400000
v -0.550000 11.770000 0.640000
v -0.450000 13.130000 0.510000
v -0.450000 10.250000 0.510000
v -1.190000 -10.690000 0.840000
v -2.260000 -9.840000 1.600000
v -3.140000 -8.830000 2.220000
v -3.880000 -6.960000 2.760000
v -4.320000 -4.100000 3.060000
v -4.260000 -0.720000 3.020000
v -3.570000 2.820000 2.530000
v -1.510000 8.390000 1.040000
v -0.690000 11.770000 0.480000
v -0.570000 13.130000 0.390000
v -0.570000 10.250000 0.390000
v -1.370000 -10.690000 0.510000
v -2.580000 -9.840000 0.980000
v -3.590000 -8.830000 1.370000
v -4.440000 -6.960000 1.700000
v -4.940000 -4.100000 1.900000
v -4.880000 -0.720000 1.870000
v -4.090000 2.820000 1.570000
v -1.720000 8.390000 0.650000
v -0.790000 11.770000 0.290000
v -0.650000 13.130000 0.240000
v -0.650000 10.250000 0.240000
v -1.450000 -10.690000 0.160000
v -2.750000 -9.840000 0.310000
v -3.820000 -8.830000 0.440000
v -4.730000 -6.960000 0.540000
v -5.270000 -4.100000 0.610000
v -5.190000 -0.720000 0.600000
v -4.360000 2.820000 0.500000
v -1.840000 8.390000 0.210000
v -0.850000 11.770000 0.080000
v -0.690000 13.130000 0.070000
v -0.690000 10.250000 0.070000
v -1.450000 -10.690000 -0.190000
v -2.740000 -9.840000 -0.370000
v -3.810000 -8.830000 -0.510000
v -4.730000 -6.960000 -0.630000
v -5.260000 -4.100000 -0.710000
v -5.180000 -0.720000 -0.690000
v -4.340000 2.820000 -0.580000
v -1.830000 8.390000 -0.240000
v -0.850000 11.770000 -0.110000
v -0.690000 13.130000 -0.090000
v -0.690000 10.250000 -0.090000
v -1.360000 -10.690000 -0.540000
v -2.570000 -9.840000 -1.040000
v -3.570000 -8.830000 -1.440000
v -4.420000 -6.960000 -1.780000
v -4.920000 -4.100000 -1.990000
v -4.850000 -0.720000 -1.960000
v -4.070000 2.820000 -1.640000
v -1.720000 8.390000 -0.690000
v -0.790000 11.770000 -0.310000
v -0.650000 13.130000 -0.250000
v -0.650000 10.250000 -0.250000
v -1.160000 -10.690000 -0.860000
v -2.230000 -9.840000 -1.630000
v -3.100000 -8.830000 -2.280000
v -3.840000 -6.960000 -2.830000
v -4.270000 -4.100000 -3.140000
v -4.190000 -0.720000 -3.100000
v -3.530000 2.820000 -2.590000
v -1.490000 8.390000 -1.090000
v -0.690000 11.770000 -0.500000
v -0.560000 13.130000 -0.410000
v -0.560000 10.250000 -0.410000
v -0.920000 -10.690000 -1.130000
v -1.750000 -9.840000 -2.140000
v -2.430000 -8.830000 -2.970000
v -3.010000 -6.960000 -3.690000
v -3.350000 -4.100000 -4.110000
v -3.310000 -0.720000 -4.050000
v -2.770000 2.820000 -3.390000
v -1.160000 8.390000 -1.430000
v -0.540000 11.770000 -0.650000
v -0.440000 13.130000 -0.520000
v -0.440000 10.250000 -0.520000
v -0.620000 -10.690000 -1.320000
v -1.160000 -9.840000 -2.500000
v -1.620000 -8.830000 -3.490000
v -2.000000 -6.960000 -4.320000
v -2.220000 -4.100000 -4.810000
v -2.190000 -0.720000 -4.730000
v -1.840000 2.820000 -3.970000
v -0.780000 8.390000 -1.670000
v -0.360000 11.770000 -0.760000
v -0.290000 13.130000 -0.620000
v -0.290000 10.250000 -0.620000
v -0.270000 -10.690000 -1.430000
v -0.500000 -9.840000 -2.720000
v -0.690000 -8.830000 -3.770000
v -0.860000 -6.960000 -4.670000
v -0.960000 -4.100000 -5.210000
v -0.950000 -0.720000 -5.130000
v -0.790000 2.820000 -4.300000
v -0.340000 8.390000 -1.810000
v -0.160000 11.770000 -0.830000
v -0.130000 13.130000 -0.670000
v -0.130000 10.250000 -0.670000
v 0.080000 -10.690000 -1.450000
v 0.170000 -9.840000 -2.760000
v 0.250000 -8.830000 -3.820000
v 0.310000 -6.960000 -4.750000
v 0.350000 -4.100000 -5.280000
v 0.340000 -0.720000 -5.210000
v 0.280000 2.820000 -4.360000
v 0.100000 8.390000 -1.830000
v 0.030000 11.770000 -0.840000
v 0.030000 13.130000 -0.680000
v 0.030000 10.250000 -0.680000
v 0.440000 -10.690000 -1.380000
v 0.850000 -9.840000 -2.620000
v 1.190000 -8.830000 -3.640000
v 1.480000 -6.960000 -4.510000
v 1.650000 -4.100000 -5.020000
v 1.620000 -0.720000 -4.940000
v 1.360000 2.820000 -4.150000
v 0.560000 8.390000 -1.740000
v 0.240000 11.770000 -0.800000
v 0.190000 13.130000 -0.650000
v 0.190000 10.250000 -0.650000
v 0.760000 -10.690000 -1.220000
v 1.480000 -9.840000 -2.320000
v 2.050000 -8.830000 -3.220000
v 2.550000 -6.960000 -4.000000
v 2.840000 -4.100000 -4.440000
v 2.810000 -0.720000 -4.380000
v 2.330000 2.820000 -3.680000
v 0.980000 8.390000 -1.540000
v 0.430000 11.770000 -0.710000
v 0.350000 13.130000 -0.570000
v 0.350000 10.250000 -0.570000
v 1.040000 -10.690000 -0.990000
v 2.010000 -9.840000 -1.870000
v 2.790000 -8.830000 -2.600000
v 3.470000 -6.960000 -3.230000
v 3.870000 -4.100000 -3.590000
v 3.800000 -0.720000 -3.550000
v 3.190000 2.820000 -2.970000
v 1.330000 8.390000 -1.250000
v 0.580000 11.770000 -0.570000
v 0.490000 13.130000 -0.470000
v 0.490000 10.250000 -0.470000
v 1.260000 -10.690000 -0.690000
v 2.410000 -9.840000 -1.310000
v 3.350000 -8.830000 -1.820000
v 4.170000 -6.960000 -2.260000
v 4.630000 -4.100000 -2.520000
v 4.570000 -0.720000 -2.480000
v 3.830000 2.820000 -2.080000
v 1.600000 8.390000 -0.870000
v 0.720000 11.770000 -0.400000
v 0.580000 13.130000 -0.320000
v 0.580000 10.250000 -0.320000
v -0.010000 -11.200000 0.000000
v 1.390000 -10.690000 -0.350000
v 2.660000 -9.840000 -0.670000
v 3.700000 -8.830000 -0.930000
v 4.600000 -6.960000 -1.150000
v 5.120000 -4.100000 -1.280000
v 5.040000 -0.720000 -1.260000
v 4.230000 2.820000 -1.040000
v 1.760000 8.390000 -0.440000
v 0.790000 11.770000 -0.200000
v 0.650000 13.130000 -0.160000
v 0.650000 10.250000 -0.160000
v -0.010000 10.250000 0.000000
v 0.450000 31.430000 3.060000
v 0.990000 5.730000 3.050000
v 0.430000 31.430000 2.940000
v 0.960000 5.730000 2.810000
v 0.390000 31.430000 2.840000
v 0.860000 5.730000 2.580000
v 0.330000 31.430000 2.740000
v 0.710000 5.730000 2.380000
v 0.230000 31.430000 2.680000
v 0.520000 5.730000 2.210000
v 0.130000 31.430000 2.620000
v 0.290000 5.730000 2.100000
v 0.020000 31.430000 2.610000
v 0.040000 5.730000 2.070000
v -0.080000 31.430000 2.610000
v -0.190000 5.730000 2.080000
v -0.190000 31.430000 2.640000
v -0.430000 5.730000 2.160000
v -0.290000 31.430000 2.710000
v -0.630000 5.730000 2.300000
v -0.360000 31.430000 2.800000
v -0.800000 5.730000 2.480000
v -0.420000 31.430000 2.900000
v -0.920000 5.730000 2.710000
v -0.450000 31.430000 3.010000
v -0.990000 5.730000 2.940000
v -0.450000 31.430000 3.120000
v -0.980000 5.730000 3.190000
v -0.420000 31.430000 3.230000
v -0.920000 5.730000 3.430000
v -0.360000 31.430000 3.330000
v -0.790000 5.730000 3.650000
v -0.280000 31.430000 3.410000
v -0.620000 5.730000 3.830000
v -0.190000 31.430000 3.470000
v -0.410000 5.730000 3.970000
v -0.070000 31.430000 3.510000
v -0.170000 5.730000 4.030000
v 0.030000 31.430000 3.510000
v 0.060000 5.730000 4.050000
v 0.130000 31.430000 3.490000
v 0.310000 5.730000 4.000000
v 0.240000 31.430000 3.440000
v 0.520000 5.730000 3.900000
v 0.330000 31.430000 3.370000
v 0.720000 5.730000 3.740000
v 0.390000 31.430000 3.270000
v 0.870000 5.730000 3.530000
v 0.000000 31.620001 3.060000
v 0.440000 31.430000 3.170000
v 0.960000 5.730000 3.300000
v 0.000000 2.760000 3.060000
# 675 vertices
# 0 vertex parms
# 0 texture vertices
# 0 normals
g TURBINE
f 1 6 7
f 1 7 2
f 2 7 8
f 2 8 3
f 3 8 9
f 3 9 4
f 4 9 10
f 4 10 5
f 5 10 6
f 5 6 1
f 1 2 3
f 6 8 7
f 1 3 4
f 6 9 8
f 1 4 5
f 6 10 9
f 11 20 21
f 11 21 12
f 12 21 22
f 12 22 13
f 13 22 23
f 13 23 14
f 14 23 24
f 14 24 15
f 15 24 25
f 15 25 16
f 16 25 26
f 16 26 17
f 17 26 27
f 17 27 18
f 18 27 28
f 18 28 19
f 19 28 20
f 19 20 11
f 11 12 13
f 20 22 21
f 11 13 14
f 20 23 22
f 11 14 15
f 20 24 23
f 11 15 16
f 20 25 24
f 11 16 17
f 20 26 25
f 11 17 18
f 20 27 26
f 11 18 19
f 20 28 27
g ROTOR
f 86 29 32
f 29 30 33
f 29 33 32
f 30 31 34
f 30 34 33
f 31 90 34
f 86 32 35
f 32 33 36
f 32 36 35
f 33 34 37
f 33 37 36
f 34 90 37
f 86 35 38
f 35 36 39
f 35 39 38
f 36 37 40
f 36 40 39
f 37 90 40
f 86 38 41
f 38 39 42
f 38 42 41
f 39 40 43
f 39 43 42
f 40 90 43
f 86 41 44
f 41 42 45
f 41 45 44
f 42 43 46
f 42 46 45
f 43 90 46
f 86 44 47
f 44 45 48
f 44 48 47
f 45 46 49
f 45 49 48
f 46 90 49
f 86 47 50
f 47 48 51
f 47 51 50
f 48 49 52
f 48 52 51
f 49 90 52
f 86 50 53
f 50 51 54
f 50 54 53
f 51 52 55
f 51 55 54
f 52 90 55
f 86 53 56
f 53 54 57
f 53 57 56
f 54 55 58
f 54 58 57
f 55 90 58
f 86 56 59
f 56 57 60
f 56 60 59
f 57 58 61
f 57 61 60
f 58 90 61
f 86 59 62
f 59 60 63
f 59 63 62
f 60 61 64
f 60 64 63
f 61 90 64
f 86 62 65
f 62 63 66
f 62 66 65
f 63 64 67
f 63 67 66
f 64 90 67
f 86 65 68
f 65 66 69
f 65 69 68
f 66 67 70
f 66 70 69
f 67 90 70
f 86 68 71
f 68 69 72
f 68 72 71
f 69 70 73
f 69 73 72
f 70 90 73
f 86 71 74
f 71 72 75
f 71 75 74
f 72 73 76
f 72 76 75
f 73 90 76
f 86 74 77
f 74 75 78
f 74 78 77
f 75 76 79
f 75 79 78
f 76 90 79
f 86 77 80
f 77 78 81
f 77 81 80
f 78 79 82
f 78 82 81
f 79 90 82
f 86 80 83
f 80 81 84
f 80 84 83
f 81 82 85
f 81 85 84
f 82 90 85
f 86 83 87
f 83 84 88
f 83 88 87
f 84 85 89
f 84 89 88
f 85 90 89
f 86 87 29
f 87 88 30
f 87 30 29
f 88 89 31
f 88 31 30
f 89 90 31
f 91 107 108
f 91 108 92
f 92 108 109
f 92 109 93
f 93 109 110
f 93 110 94
f 94 110 111
f 94 111 95
f 95 111 112
f 95 112 96
f 96 112 113
f 96 113 97
f 97 113 114
f 97 114 98
f 98 114 115
f 98 115 99
f 99 115 116
f 99 116 100
f 100 116 117
f 100 117 101
f 101 117 118
f 101 118 102
f 102 118 119
f 102 119 103
f 103 119 120
f 103 120 104
f 104 120 121
f 104 121 105
f 105 121 122
f 105 122 106
f 106 122 107
f 106 107 91
f 93 94 95
f 109 111 110
f 93 95 96
f 109 112 111
f 96 97 98
f 112 114 113
f 96 98 99
f 112 115 114
f 96 99 100
f 112 116 115
f 93 96 100
f 109 116 112
f 103 104 105
f 119 121 120
f 103 105 106
f 119 122 121
f 106 91 92
f 122 108 107
f 103 106 92
f 119 108 122
f 103 92 93
f 119 109 108
f 93 100 101
f 109 117 116
f 93 101 102
f 109 118 117
f 93 102 103
f 109 119 118
f 123 139 140
f 123 140 124
f 124 140 141
f 124 141 125
f 125 141 142
f 125 142 126
f 126 142 143
f 126 143 127
f 127 143 144
f 127 144 128
f 128 144 145
f 128 145 129
f 129 145 146
f 129 146 130
f 130 146 147
f 130 147 131
f 131 147 148
f 131 148 132
f 132 148 149
f 132 149 133
f 133 149 150
f 133 150 134
f 134 150 151
f 134 151 135
f 135 151 152
f 135 152 136
f 136 152 153
f 136 153 137
f 137 153 154
f 137 154 138
f 138 154 139
f 138 139 123
f 125 126 127
f 141 143 142
f 125 127 128
f 141 144 143
f 128 129 130
f 144 146 145
f 128 130 131
f 144 147 146
f 128 131 132
f 144 148 147
f 125 128 132
f 141 148 144
f 135 136 137
f 151 153 152
f 135 137 138
f 151 154 153
f 138 123 124
f 154 140 139
f 135 138 124
f 151 140 154
f 135 124 125
f 151 141 140
f 125 132 133
f 141 149 148
f 125 133 134
f 141 150 149
f 125 134 135
f 141 151 150
f 155 171 172
f 155 172 156
f 156 172 173
f 156 173 157
f 157 173 174
f 157 174 158
f 158 174 175
f 158 175 159
f 159 175 176
f 159 176 160
f 160 176 177
f 160 177 161
f 161 177 178
f 161 178 162
f 162 178 179
f 162 179 163
f 163 179 180
f 163 180 164
f 164 180 181
f 164 181 165
f 165 181 182
f 165 182 166
f 166 182 183
f 166 183 167
f 167 183 184
f 167 184 168
f 168 184 185
f 168 185 169
f 169 185 186
f 169 186 170
f 170 186 171
f 170 171 155
f 157 158 159
f 173 175 174
f 157 159 160
f 173 176 175
f 160 161 162
f 176 178 177
f 160 162 163
f 176 179 178
f 160 163 164
f 176 180 179
f 157 160 164
f 173 180 176
f 167 168 169
f 183 185 184
f 167 169 170
f 183 186 185
f 170 155 156
f 186 172 171
f 167 170 156
f 183 172 186
f 167 156 157
f 183 173 172
f 157 164 165
f 173 181 180
f 157 165 166
f 173 182 181
f 157 166 167
f 173 183 182
f 187 203 204
f 187 204 188
f 188 204 205
f 188 205 189
f 189 205 206
f 189 206 190
f 190 206 207
f 190 207 191
f 191 207 208
f 191 208 192
f 192 208 209
f 192 209 193
f 193 209 210
f 193 210 194
f 194 210 211
f 194 211 195
f 195 211 212
f 195 212 196
f 196 212 213
f 196 213 197
f 197 213 214
f 197 214 198
f 198 214 215
f 198 215 199
f 199 215 216
f 199 216 200
f 200 216 217
f 200 217 201
f 201 217 218
f 201 218 202
f 202 218 203
f 202 203 187
f 189 190 191
f 205 207 206
f 189 191 192
f 205 208 207
f 192 193 194
f 208 210 209
f 192 194 195
f 208 211 210
f 192 195 196
f 208 212 211
f 189 192 196
f 205 212 208
f 199 200 201
f 215 217 216
f 199 201 202
f 215 218 217
f 202 187 188
f 218 204 203
f 199 202 188
f 215 204 218
f 199 188 189
f 215 205 204
f 189 196 197
f 205 213 212
f 189 197 198
f 205 214 213
f 189 198 199
f 205 215 214
f 219 235 236
f 219 236 220
f 220 236 237
f 220 237 221
f 221 237 238
f 221 238 222
f 222 238 239
f 222 239 223
f 223 239 240
f 223 240 224
f 224 240 241
f 224 241 225
f 225 241 242
f 225 242 226
f 226 242 243
f 226 243 227
f 227 243 244
f 227 244 228
f 228 244 245
f 228 245 229
f 229 245 246
f 229 246 230
f 230 246 247
f 230 247 231
f 231 247 248
f 231 248 232
f 232 248 249
f 232 249 233
f 233 249 250
f 233 250 234
f 234 250 235
f 234 235 219
f 221 222 223
f 237 239 238
f 221 223 224
f 237 240 239
f 224 225 226
f 240 242 241
f 224 226 227
f 240 243 242
f 224 227 228
f 240 244 243
f 221 224 228
f 237 244 240
f 231 232 233
f 247 249 248
f 231 233 234
f 247 250 249
f 234 219 220
f 250 236 235
f 231 234 220
f 247 236 250
f 231 220 221
f 247 237 236
f 221 228 229
f 237 245 244
f 221 229 230
f 237 246 245
f 221 230 231
f 237 247 246
g FEET
f 251 257 258
f 251 258 252
f 252 258 259
f 252 259 253
f 253 259 260
f 253 260 254
f 254 260 261
f 254 261 255
f 255 261 262
f 255 262 256
f 256 262 257
f 256 257 251
f 252 253 254
f 258 260 259
f 252 254 255
f 258 261 260
f 251 252 255
f 257 261 258
f 251 255 256
f 257 262 261
f 263 269 270
f 263 270 264
f 264 270 271
f 264 271 265
f 265 271 272
f 265 272 266
f 266 272 273
f 266 273 267
f 267 273 274
f 267 274 268
f 268 274 269
f 268 269 263
f 264 265 266
f 270 272 271
f 264 266 267
f 270 273 272
f 263 264 267
f 269 273 270
f 263 267 268
f 269 274 273
g LEGS
f 275 279 280
f 275 280 276
f 276 280 281
f 276 281 277
f 277 281 282
f 277 282 278
f 278 282 279
f 278 279 275
f 275 276 277
f 279 281 280
f 275 277 278
f 279 282 281
f 283 287 288
f 283 288 284
f 284 288 289
f 284 289 285
f 285 289 290
f 285 290 286
f 286 290 287
f 286 287 283
f 283 284 285
f 287 289 288
f 283 285 286
f 287 290 289
f 291 295 296
f 291 296 292
f 292 296 297
f 292 297 293
f 293 297 298
f 293 298 294
f 294 298 295
f 294 295 291
f 291 292 293
f 295 297 296
f 291 293 294
f 295 298 297
f 299 303 304
f 299 304 300
f 300 304 305
f 300 305 301
f 301 305 306
f 301 306 302
f 302 306 303
f 302 303 299
f 299 300 301
f 303 305 304
f 299 301 302
f 303 306 305
g TAIL
f 307 312 313
f 307 313 308
f 308 313 314
f 308 314 309
f 309 314 315
f 309 315 310
f 310 315 316
f 310 316 311
f 311 316 312
f 311 312 307
f 307 308 309
f 312 314 313
f 307 309 310
f 312 315 314
f 307 310 311
f 312 316 315
f 317 322 323
f 317 323 318
f 318 323 324
f 318 324 319
f 319 324 325
f 319 325 320
f 320 325 326
f 320 326 321
f 321 326 322
f 321 322 317
f 317 318 319
f 322 324 323
f 317 319 320
f 322 325 324
f 317 320 321
f 322 326 325
f 327 331 332
f 327 332 328
f 328 332 333
f 328 333 329
f 329 333 334
f 329 334 330
f 330 334 331
f 330 331 327
f 327 328 329
f 331 333 332
f 327 329 330
f 331 334 333
f 335 341 342
f 335 342 336
f 336 342 343
f 336 343 337
f 337 343 344
f 337 344 338
f 338 344 345
f 338 345 339
f 339 345 346
f 339 346 340
f 340 346 341
f 340 341 335
f 340 335 336
f 346 342 341
f 340 336 337
f 346 343 342
f 337 338 339
f 343 345 344
f 337 339 340
f 343 346 345
g BODY
f 611 347 358
f 347 348 359
f 347 359 358
f 348 349 360
f 348 360 359
f 349 350 361
f 349 361 360
f 350 351 362
f 350 362 361
f 351 352 363
f 351 363 362
f 352 353 364
f 352 364 363
f 353 354 365
f 353 365 364
f 354 355 366
f 354 366 365
f 355 356 367
f 355 367 366
f 356 357 368
f 356 368 367
f 357 623 368
f 611 358 369
f 358 359 370
f 358 370 369
f 359 360 371
f 359 371 370
f 360 361 372
f 360 372 371
f 361 362 373
f 361 373 372
f 362 363 374
f 362 374 373
f 363 364 375
f 363 375 374
f 364 365 376
f 364 376 375
f 365 366 377
f 365 377 376
f 366 367 378
f 366 378 377
f 367 368 379
f 367 379 378
f 368 623 379
f 611 369 380
f 369 370 381
f 369 381 380
f 370 371 382
f 370 382 381
f 371 372 383
f 371 383 382
f 372 373 384
f 372 384 383
f 373 374 385
f 373 385 384
f 374 375 386
f 374 386 385
f 375 376 387
f 375 387 386
f 376 377 388
f 376 388 387
f 377 378 389
f 377 389 388
f 378 379 390
f 378 390 389
f 379 623 390
f 611 380 391
f 380 381 392
f 380 392 391
f 381 382 393
f 381 393 392
f 382 383 394
f 382 394 393
f 383 384 395
f 383 395 394
f 384 385 396
f 384 396 395
f 385 386 397
f 385 397 396
f 386 387 398
f 386 398 397
f 387 388 399
f 387 399 398
f 388 389 400
f 388 400 399
f 389 390 401
f 389 401 400
f 390 623 401
f 611 391 402
f 391 392 403
f 391 403 402
f 392 393 404
f 392 404 403
f 393 394 405
f 393 405 404
f 394 395 406
f 394 406 405
f 395 396 407
f 395 407 406
f 396 397 408
f 396 408 407
f 397 398 409
f 397 409 408
f 398 399 410
f 398 410 409
f 399 400 411
f 399 411 410
f 400 401 412
f 400 412 411
f 401 623 412
f 611 402 413
f 402 403 414
f 402 414 413
f 403 404 415
f 403 415 414
f 404 405 416
f 404 416 415
f 405 406 417
f 405 417 416
f 406 407 418
f 406 418 417
f 407 408 419
f 407 419 418
f 408 409 420
f 408 420 419
f 409 410 421
f 409 421 420
f 410 411 422
f 410 422 421
f 411 412 423
f 411 423 422
f 412 623 423
f 611 413 424
f 413 414 425
f 413 425 424
f 414 415 426
f 414 426 425
f 415 416 427
f 415 427 426
f 416 417 428
f 416 428 427
f 417 418 429
f 417 429 428
f 418 419 430
f 418 430 429
f 419 420 431
f 419 431 430
f 420 421 432
f 420 432 431
f 421 422 433
f 421 433 432
f 422 423 434
f 422 434 433
f 423 623 434
f 611 424 435
f 424 425 436
f 424 436 435
f 425 426 437
f 425 437 436
f 426 427 438
f 426 438 437
f 427 428 439
f 427 439 438
f 428 429 440
f 428 440 439
f 429 430 441
f 429 441 440
f 430 431 442
f 430 442 441
f 431 432 443
f 431 443 442
f 432 433 444
f 432 444 443
f 433 434 445
f 433 445 444
f 434 623 445
f 611 435 446
f 435 436 447
f 435 447 446
f 436 437 448
f 436 448 447
f 437 438 449
f 437 449 448
f 438 439 450
f 438 450 449
f 439 440 451
f 439 451 450
f 440 441 452
f 440 452 451
f 441 442 453
f 441 453 452
f 442 443 454
f 442 454 453
f 443 444 455
f 443 455 454
f 444 445 456
f 444 456 455
f 445 623 456
f 611 446 457
f 446 447 458
f 446 458 457
f 447 448 459
f 447 459 458
f 448 449 460
f 448 460 459
f 449 450 461
f 449 461 460
f 450 451 462
f 450 462 461
f 451 452 463
f 451 463 462
f 452 453 464
f 452 464 463
f 453 454 465
f 453 465 464
f 454 455 466
f 454 466 465
f 455 456 467
f 455 467 466
f 456 623 467
f 611 457 468
f 457 458 469
f 457 469 468
f 458 459 470
f 458 470 469
f 459 460 471
f 459 471 470
f 460 461 472
f 460 472 471
f 461 462 473
f 461 473 472
f 462 463 474
f 462 474 473
f 463 464 475
f 463 475 474
f 464 465 476
f 464 476 475
f 465 466 477
f 465 477 476
f 466 467 478
f 466 478 477
f 467 623 478
f 611 468 479
f 468 469 480
f 468 480 479
f 469 470 481
f 469 481 480
f 470 471 482
f 470 482 481
f 471 472 483
f 471 483 482
f 472 473 484
f 472 484 483
f 473 474 485
f 473 485 484
f 474 475 486
f 474 486 485
f 475 476 487
f 475 487 486
f 476 477 488
f 476 488 487
f 477 478 489
f 477 489 488
f 478 623 489
f 611 479 490
f 479 480 491
f 479 491 490
f 480 481 492
f 480 492 491
f 481 482 493
f 481 493 492
f 482 483 494
f 482 494 493
f 483 484 495
f 483 495 494
f 484 485 496
f 484 496 495
f 485 486 497
f 485 497 496
f 486 487 498
f 486 498 497
f 487 488 499
f 487 499 498
f 488 489 500
f 488 500 499
f 489 623 500
f 611 490 501
f 490 491 502
f 490 502 501
f 491 492 503
f 491 503 502
f 492 493 504
f 492 504 503
f 493 494 505
f 493 505 504
f 494 495 506
f 494 506 505
f 495 496 507
f 495 507 506
f 496 497 508
f 496 508 507
f 497 498 509
f 497 509 508
f 498 499 510
f 498 510 509
f 499 500 511
f 499 511 510
f 500 623 511
f 611 501 512
f 501 502 513
f 501 513 512
f 502 503 514
f 502 514 513
f 503 504 515
f 503 515 514
f 504 505 516
f 504 516 515
f 505 506 517
f 505 517 516
f 506 507 518
f 506 518 517
f 507 508 519
f 507 519 518
f 508 509 520
f 508 520 519
f 509 510 521
f 509 521 520
f 510 511 522
f 510 522 521
f 511 623 522
f 611 512 523
f 512 513 524
f 512 524 523
f 513 514 525
f 513 525 524
f 514 515 526
f 514 526 525
f 515 516 527
f 515 527 526
f 516 517 528
f 516 528 527
f 517 518 529
f 517 529 528
f 518 519 530
f 518 530 529
f 519 520 531
f 519 531 530
f 520 521 532
f 520 532 531
f 521 522 533
f 521 533 532
f 522 623 533
f 611 523 534
f 523 524 535
f 523 535 534
f 524 525 536
f 524 536 535
f 525 526 537
f 525 537 536
f 526 527 538
f 526 538 537
f 527 528 539
f 527 539 538
f 528 529 540
f 528 540 539
f 529 530 541
f 529 541 540
f 530 531 542
f 530 542 541
f 531 532 543
f 531 543 542
f 532 533 544
f 532 544 543
f 533 623 544
f 611 534 545
f 534 535 546
f 534 546 545
f 535 536 547
f 535 547 546
f 536 537 548
f 536 548 547
f 537 538 549
f 537 549 548
f 538 539 550
f 538 550 549
f 539 540 551
f 539 551 550
f 540 541 552
f 540 552 551
f 541 542 553
f 541 553 552
f 542 543 554
f 542 554 553
f 543 544 555
f 543 555 554
f 544 623 555
f 611 545 556
f 545 546 557
f 545 557 556
f 546 547 558
f 546 558 557
f 547 548 559
f 547 559 558
f 548 549 560
f 548 560 559
f 549 550 561
f 549 561 560
f 550 551 562
f 550 562 561
f 551 552 563
f 551 563 562
f 552 553 564
f 552 564 563
f 553 554 565
f 553 565 564
f 554 555 566
f 554 566 565
f 555 623 566
f 611 556 567
f 556 557 568
f 556 568 567
f 557 558 569
f 557 569 568
f 558 559 570
f 558 570 569
f 559 560 571
f 559 571 570
f 560 561 572
f 560 572 571
f 561 562 573
f 561 573 572
f 562 563 574
f 562 574 573
f 563 564 575
f 563 575 574
f 564 565 576
f 564 576 575
f 565 566 577
f 565 577 576
f 566 623 577
f 611 567 578
f 567 568 579
f 567 579 578
f 568 569 580
f 568 580 579
f 569 570 581
f 569 581 580
f 570 571 582
f 570 582 581
f 571 572 583
f 571 583 582
f 572 573 584
f 572 584 583
f 573 574 585
f 573 585 584
f 574 575 586
f 574 586 585
f 575 576 587
f 575 587 586
f 576 577 588
f 576 588 587
f 577 623 588
f 611 578 589
f 578 579 590
f 578 590 589
f 579 580 591
f 579 591 590
f 580 581 592
f 580 592 591
f 581 582 593
f 581 593 592
f 582 583 594
f 582 594 593
f 583 584 595
f 583 595 594
f 584 585 596
f 584 596 595
f 585 586 597
f 585 597 596
f 586 587 598
f 586 598 597
f 587 588 599
f 587 599 598
f 588 623 599
f 611 589 600
f 589 590 601
f 589 601 600
f 590 591 602
f 590 602 601
f 591 592 603
f 591 603 602
f 592 593 604
f 592 604 603
f 593 594 605
f 593 605 604
f 594 595 606
f 594 606 605
f 595 596 607
f 595 607 606
f 596 597 608
f 596 608 607
f 597 598 609
f 597 609 608
f 598 599 610
f 598 610 609
f 599 623 610
f 611 600 612
f 600 601 613
f 600 613 612
f 601 602 614
f 601 614 613
f 602 603 615
f 602 615 614
f 603 604 616
f 603 616 615
f 604 605 617
f 604 617 616
f 605 606 618
f 605 618 617
f 606 607 619
f 606 619 618
f 607 608 620
f 607 620 619
f 608 609 621
f 608 621 620
f 609 610 622
f 609 622 621
f 610 623 622
f 611 612 347
f 612 613 348
f 612 348 347
f 613 614 349
f 613 349 348
f 614 615 350
f 614 350 349
f 615 616 351
f 615 351 350
f 616 617 352
f 616 352 351
f 617 618 353
f 617 353 352
f 618 619 354
f 618 354 353
f 619 620 355
f 619 355 354
f 620 621 356
f 620 356 355
f 621 622 357
f 621 357 356
f 622 623 357
g TUBE
f 672 624 626
f 624 625 627
f 624 627 626
f 625 675 627
f 672 626 628
f 626 627 629
f 626 629 628
f 627 675 629
f 672 628 630
f 628 629 631
f 628 631 630
f 629 675 631
f 672 630 632
f 630 631 633
f 630 633 632
f 631 675 633
f 672 632 634
f 632 633 635
f 632 635 634
f 633 675 635
f 672 634 636
f 634 635 637
f 634 637 636
f 635 675 637
f 672 636 638
f 636 637 639
f 636 639 638
f 637 675 639
f 672 638 640
f 638 639 641
f 638 641 640
f 639 675 641
f 672 640 642
f 640 641 643
f 640 643 642
f 641 675 643
f 672 642 644
f 642 643 645
f 642 645 644
f 643 675 645
f 672 644 646
f 644 645 647
f 644 647 646
f 645 675 647
f 672 646 648
f 646 647 649
f 646 649 648
f 647 675 649
f 672 648 650
f 648 649 651
f 648 651 650
f 649 675 651
f 672 650 652
f 650 651 653
f 650 653 652
f 651 675 653
f 672 652 654
f 652 653 655
f 652 655 654
f 653 675 655
f 672 654 656
f 654 655 657
f 654 657 656
f 655 675 657
f 672 656 658
f 656 657 659
f 656 659 658
f 657 675 659
f 672 658 660
f 658 659 661
f 658 661 660
f 659 675 661
f 672 660 662
f 660 661 663
f 660 663 662
f 661 675 663
f 672 662 664
f 662 663 665
f 662 665 664
f 663 675 665
f 672 664 666
f 664 665 667
f 664 667 666
f 665 675 667
f 672 666 668
f 666 667 669
f 666 669 668
f 667 675 669
f 672 668 670
f 668 669 671
f 668 671 670
f 669 675 671
f 672 670 673
f 670 671 674
f 670 674 673
f 671 675 674
f 672 673 624
f 673 674 625
f 673 625 624
f 674 675 625
# 1270 elements
*/