changeset 0:c10bc63824e7

Initial commit!
author zzzzrrr <mason.green@gmail.com>
date Fri, 20 Mar 2009 06:41:25 -0400
parents
children 8ee020a019e0
files boundaryListener.d build-dmd-win.bat contactListener.d gui.cfg main.d melee.d melee.geany models.d orz.d render.d ship.d steer.d themes/default.cfg themes/default/Angle.cfg themes/default/Button.cfg themes/default/Check.cfg themes/default/Combo.cfg themes/default/FramedTopLevelWindow.cfg themes/default/Input.cfg themes/default/Menu.cfg themes/default/Picker.cfg themes/default/Progressbar.cfg themes/default/ScrollView.cfg themes/default/Scrollbar.cfg themes/default/ScrollbarButton.cfg themes/default/Slider.cfg themes/default/Spinner.cfg themes/default/TabButton.cfg themes/default/TabView.cfg themes/default/TextList.cfg themes/default/TickBox.cfg themes/default/WindowFrame.cfg themes/default/WindowFrameButton.cfg themes/default/XCheck.cfg themes/default/img/Thumbs.db themes/default/img/arrow0.png themes/default/img/arrow1.png themes/default/img/arrow2.png themes/default/img/arrow3.png themes/default/img/button.png themes/default/img/button_active.png themes/default/img/hsliderarrow.png themes/default/img/menuShadow.png themes/default/img/progress.png themes/default/img/progress_box.png themes/default/img/radio.png themes/default/img/radio_active.png themes/default/img/radio_hover.png themes/default/img/scrollbutton.png themes/default/img/scrollbutton_active.png themes/default/img/scrollhandle.png themes/default/img/spinner_down.png themes/default/img/spinner_up.png themes/default/img/tbw.png themes/default/img/tbw_active.png themes/default/img/tbw_hover.png themes/default/img/tick.png themes/default/img/tick_active.png themes/default/img/tick_hover.png themes/default/img/vsliderarrow.png themes/default/img/winframe_bg.png themes/default/img/winframe_close.png themes/default/img/winframe_maximize.png themes/default/img/winframe_minimize.png themes/default/img/winframe_restore.png urQuan.d verdana.ttf
diffstat 67 files changed, 3144 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/boundaryListener.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * Based on Box2D by Erin Catto, http://www.box2d.org
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.boundaryListener;
+
+import melee.melee;
+
+// bzWorld boundary callback
+class BoundaryListener : bzBoundaryListener
+{
+	Melee melee;
+
+	this(Melee melee)
+	{
+		this.melee = melee;
+	}
+
+	void violation(bzBody rBody)
+	{
+        melee.boundaryViolated(rBody);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build-dmd-win.bat	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,4 @@
+::jake -I.. -I../blaze -I../xf/ext zlib.lib -inline -release -O main.d
+rebuild -I.. -I../blaze -I../xf/ext zlib.lib -inline -release -O main.d
+pause
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contactListener.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * Based on Box2D by Erin Catto, http://www.box2d.org
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.contactListener;
+
+import melee.melee;
+
+// bzWorld contact callback
+class ContactListener : bzContactListener
+{
+
+	Melee melee;
+
+	this(Melee melee) {
+		this.melee = melee;
+	}
+
+	void add(bzContactPoint point)
+	{
+		if (melee.pointCount == k_maxContactPoints) {
+			return;
+		}
+
+		ContactPoint *cp = &melee.points[melee.pointCount];
+		cp.shape1 = point.shape1;
+		cp.shape2 = point.shape2;
+		cp.position = point.position;
+		cp.normal = point.normal;
+		cp.id = point.id;
+		cp.state = ContactState.e_contactAdded;
+
+		++melee.pointCount;
+	}
+
+	void persist(bzContactPoint point)
+	{
+		if (melee.pointCount == k_maxContactPoints) {
+			return;
+		}
+
+		ContactPoint *cp = &melee.points[melee.pointCount];
+		cp.shape1 = point.shape1;
+		cp.shape2 = point.shape2;
+		cp.position = point.position;
+		cp.normal = point.normal;
+		cp.id = point.id;
+		cp.state = ContactState.e_contactPersisted;
+
+		++melee.pointCount;
+	}
+
+	void remove(bzContactPoint point)
+	{
+		if (melee.pointCount == k_maxContactPoints) {
+			return;
+		}
+
+		ContactPoint *cp = &melee.points[melee.pointCount];
+		cp.shape1 = point.shape1;
+		cp.shape2 = point.shape2;
+		cp.position = point.position;
+		cp.normal = point.normal;
+		cp.id = point.id;
+		cp.state = ContactState.e_contactRemoved;
+
+		++melee.pointCount;
+	}
+
+	void result(bzContactResult point) {}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,20 @@
+import "themes/default.cfg"
+
+new FramedTopLevelWindow main {
+    frame.text = "OpenMelee";
+    showCursor = false;
+    size = 900 610;
+    [hexpand hfill vexpand vfill] new HBox {
+        [vexpand vfill] new VBox controls {
+            size = 175 0;
+            layout = {
+                padding = 5 5;
+            }
+        }
+        [hexpand hfill vexpand vfill] new GLViewport glview;
+    }
+} @overlay {
+    [hexpand vexpand hfill vfill] new Group .overlay {
+        layout = Ghost;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.main;
+
+import tango.io.Stdout : Stdout;
+
+version(distrib) import tango.io.vfs.ZipFolder;
+import tango.time.StopWatch;
+import fc = tango.text.convert.Float : toString;
+import tango.util.log.Trace;
+import tango.math.Math;
+
+import xf.core.JobHub;
+import xf.hybrid.Hybrid;
+import xf.hybrid.backend.GL;
+import xf.omg.core.LinearAlgebra;
+
+import melee.melee;
+import melee.render;
+
+const ITERS_PER_SECOND = 60;
+
+void main() {
+
+    Settings settings;
+    float timeStep = settings.hz > 0.0f ? 1.0f / settings.hz : 0.0f;
+    version(distrib) gui.vfs.mount(new ZipFolder("./gui.zip"));
+    scope cfg = loadHybridConfig("./gui.cfg");
+    scope renderer = new Renderer;
+    auto whut = new Render(&settings);
+            
+    gui.begin(cfg).retained;
+    gui.push(`main`);
+    GLViewport(`glview`).renderingHandler(&whut.draw)
+    .addHandler(&whut.onClick)
+    .addHandler(&whut.onMove)
+    .addHandler(&whut.onKey)
+    .addHandler(&whut.onDT)
+    .addHandler(&whut.onMouseEnter)
+    .addHandler(&whut.onMouseLeave)
+    .grabKeyboardFocus;
+    gui.pop();
+    gui.immediate.end;
+
+    StopWatch timer;
+
+    jobHub.addRepeatableJob( {
+                whut.world.step(timeStep, settings.velocityIterations,
+                                settings.positionIterations);
+    }, ITERS_PER_SECOND);
+
+    bool running = true;
+
+    jobHub.addPreFrameJob( {
+        // Clean out the kill list
+        uint[] key = whut.killList.keys;
+        foreach(k; key) {
+            whut.world.destroyBody(whut.killList[k]);
+            whut.killList.remove(k);
+        }
+    });
+
+    jobHub.addPostFrameJob( {
+        gui.begin(cfg);
+        gui.push(`main`);
+        if (gui().getProperty!(bool)("frame.closeClicked")) {
+            running = false;
+        }
+
+        if(whut.thrust) {
+            whut.ship1.thrust();
+        }
+
+        gui().setProperty!(bool)("showCursor", true);
+        gui.pop();
+        gui.end;
+        gui.render(renderer);
+        
+        {
+            vec2 p1 = vec2.from(whut.ship1.rBody.position);
+            vec2 p2 = vec2.from(whut.ship2.rBody.position);
+            vec2 distance = p1 - p2;
+            whut.viewCenter = p1 - (distance * 0.5f);
+        }
+        
+    });
+
+    while (running && !whut.quit) {
+        float delta = timer.stop;
+        timer.start;
+        jobHub.update(delta);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/melee.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.melee;
+
+import Integer = tango.text.convert.Integer;
+import tango.math.Math;
+import tango.math.random.Kiss;
+
+import xf.hybrid.Event;
+import xf.input.KeySym;
+import xf.omg.core.LinearAlgebra;
+
+public import blaze.all;
+
+import melee.boundaryListener;
+import melee.contactListener;
+import melee.ship;
+import melee.urQuan;
+import melee.orz;
+
+// Cursor scale factor
+const CURSORSIZE = 0.05f;
+
+const INIT_SPAWN_SIZE = 0.5f;
+
+// Dragging stuffs
+const BUNGEE_K = 1.5f;
+// Damping factor for dragging
+const DRAGDAMP = 20.0f;
+
+// Size of hinges
+const HINGE_RADIUS = 0.05f;
+
+// Smallest allowed dimension
+const MIN_DIMENSION = 0.1;
+
+// Water stuffs
+const MAX_PARTICLES = 10000;
+const WATER_BOUNCE = 0.01;
+const WATER_FRICTION = 0.5;
+const WATER_RADIUS = 0.05f;
+const MAX_CIRCLE_RES = 32;
+
+const k_maxContactPoints = 2048;
+
+enum ContactState {
+    e_contactAdded,
+    e_contactPersisted,
+    e_contactRemoved
+}
+
+struct ContactPoint {
+    bzShape shape1;
+    bzShape shape2;
+    bzVec2 normal;
+    bzVec2 position;
+    bzVec2 velocity;
+    bzContactID id;
+    ContactState state;
+}
+
+// Melee settings. Some can be controlled in the GUI.
+struct Settings {
+    float hz                 = 60;
+    int velocityIterations = 8;
+    int positionIterations = 2;
+    bool drawShapes = true;
+    bool drawJoints = true;
+    bool drawControllers;
+    bool drawCoreShapes;
+    bool drawAABBs;
+    bool drawOBBs;
+    bool drawPairs;
+    bool drawContactPoints;
+    bool drawContactNormals;
+    bool drawContactForces;
+    bool drawFrictionForces;
+    bool drawCOMs;
+    bool drawStats;
+    bool enableWarmStarting;
+    bool enableTOI;
+}
+
+// Dirty, dirty hack for communicating config changes to Main
+// TODO: Harass h3 to add .changed to hybrid so this isn't necessary
+struct ConfigChange (T)
+{
+    protected
+    {
+        bool _pending = false;
+        T _value;
+    }
+
+    T value()
+    {
+        _pending = false;
+        return _value;
+    }
+
+    void value(T change)
+    {
+        _value = change;
+        _pending = true;
+    }
+
+    bool pending()
+    {
+        return _pending;
+    }
+
+    T opAssign(T change)
+    {
+        value(change);
+        return _value;
+    }
+
+    bool opEquals(T other)
+    {
+        return _value == other;
+    }
+}
+
+T randomRange(T = int) (T min, T max)
+{
+    return min + Kiss.instance.natural() % (max + 1 - min);
+}
+
+class Melee
+{
+
+	this(Settings *settings)
+	{
+		this.settings = settings;
+		init();
+		spawnRect = vec2(INIT_SPAWN_SIZE, INIT_SPAWN_SIZE);
+		// bzWorld boundary callback
+		m_boundaryListener = new BoundaryListener(this);
+		// bzContact callback
+		m_contactListener = new ContactListener(this);
+	}
+
+	void init() {
+	    // Define world boundaries
+		bzAABB worldAABB;
+		worldAABB.lowerBound.set(-200.0f, -100.0f);
+		worldAABB.upperBound.set(200.0f, 200.0f);
+		world = new bzWorld(worldAABB, gravity, allowSleep);
+		world.boundaryListener = m_boundaryListener;
+		world.contactListener = m_contactListener;
+		viewCenter = vec2(10, 10);
+		ship1 = new Orz(world);
+		ship2 = new UrQuan(world);
+	}
+
+	void drag()
+	{
+
+	}
+
+	EventHandling onClick(MouseButtonEvent e)
+	{
+        return EventHandling.Stop;
+	}
+
+	EventHandling onKey(KeyboardEvent e)
+	{
+		if (e.unicode == '+') {         // HACK: the windows input writer doesn't do plus correctly yet   - h3
+			e.keySym = KeySym.plus;
+		}
+		if (e.down) {
+			switch (e.keySym) {
+			case KeySym.Escape:
+                quit = true;
+                break;
+			default:
+			    char key = cast(char) e.keySym;
+			    if(key == 'w') {
+			        thrust = true;
+			        break;
+			    } else {
+			        ship1.turn(key);
+			    }
+				break;
+			}
+        // Key released
+		} else {
+		    char key = cast(char) e.keySym;
+		    if(key == 'w') {
+		         thrust = false;
+		    } else if (key == 'd' || key == 'a') {
+                ship1.rBody.angularVelocity = 0.0f;
+            }
+		}
+
+		return EventHandling.Stop;
+	}
+
+	// Mouse move
+	EventHandling onMove(MouseMoveEvent e)
+	{
+		return EventHandling.Stop;
+	}
+
+	EventHandling onDT(TimeUpdateEvent e)
+	{
+		return EventHandling.Continue;
+	}
+
+	EventHandling onMouseEnter(MouseEnterEvent e)
+	{
+		return EventHandling.Continue;
+	}
+
+	EventHandling onMouseLeave(MouseLeaveEvent e)
+	{
+		return EventHandling.Continue;
+	}
+
+	protected
+	{
+		const bzVec2 gravity = bzVec2(0.0f, 0.0f);
+		bool allowSleep = false;
+
+		vec2 spawnRect;
+
+		vec2[] drawing;
+
+		vec2i screenSize = vec2i.zero;
+		vec2 mousePos = vec2.zero;
+
+		bool scaling = false;
+		bool full = false;
+
+		vec2 spawnStart;
+
+		bool preserveBullet = false;
+
+		float waterDelta = 0;
+	}
+
+	void boundaryViolated(bzBody rBody)
+	{
+        uint key = rBody.toHash();
+        killList[key] = rBody;
+	}
+
+	bool quit;
+
+	// Ortho view zoom
+	float zoom = 40;
+	int pointCount;
+	vec2 viewCenter;
+    bzBody[uint] killList;
+
+	bzWorld world;
+	Settings *settings;
+
+	// bzWorld boundary listener. Destroy bodies that leave world bzAABB
+	bzBoundaryListener m_boundaryListener;
+	bzContactListener m_contactListener;
+	ContactPoint[k_maxContactPoints] points;
+
+	ConfigChange!(vec2) editChange;
+    bool thrust;
+
+	Ship ship1;
+	Ship ship2;
+}
+
+// Utility functions
+bzVec2 toBlaze(vec2 vec)
+{
+    auto ret = bzVec2(vec.x, vec.y);
+    return ret;
+}
+
+vec2 rotate(vec2 point, float rad)
+{
+    return vec2(cos(rad) * point.x - sin(rad) * point.y, sin(rad) * point.x + cos(rad) * point.y);
+}
+
+class Select
+{
+	bool select;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/melee.geany	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,29 @@
+
+[indentation]
+indent_width=4
+indent_type=0
+indent_hard_tab_width=8
+detect_indent=false
+indent_mode=2
+
+[project]
+name=melee
+base_path=C:\\Documents and Settings\\greenma3\\workspace\\melee
+make_in_base_path=false
+description=
+run_cmd=
+
+[files]
+current_page=11
+FILE_NAME_0=3688;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\main.d;0
+FILE_NAME_1=108;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\boundaryListener.d;0
+FILE_NAME_2=108;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\contactListener.d;0
+FILE_NAME_3=6553;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\melee.d;0
+FILE_NAME_4=176;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\models.d;0
+FILE_NAME_5=0;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\orz.d;0
+FILE_NAME_6=9256;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\render.d;0
+FILE_NAME_7=2582;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\ship.d;0
+FILE_NAME_8=5220;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\steer.d;0
+FILE_NAME_9=762;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\urQuan.d;0
+FILE_NAME_10=143;None;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\melee\\build-dmd-win.bat;0
+FILE_NAME_11=13795;D;0;16;0;1;1;C:\\Documents and Settings\\greenma3\\workspace\\blaze\\examples\\testBed\\framework\\test.d;0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/models.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,239 @@
+void spathiEluder(inout bzVec2[][] bodyVertex, inout bzVec2[][] vertex)
+{
+	vertex.length = bodyVertex.length = 11;
+
+	vertex[0].length = bodyVertex[0].length = 19;
+	// Hull
+	bodyVertex[0][0] = bzVec2(2.24,31.54);
+	bodyVertex[0][1] = bzVec2(14.14,28.28);
+	bodyVertex[0][2] = bzVec2(23.89,20.72);
+	bodyVertex[0][3] = bzVec2(30.00,10.00);
+	bodyVertex[0][4] = bzVec2(31.54, -2.24);
+	bodyVertex[0][5] = bzVec2(28.28,-14.14);
+	bodyVertex[0][6] = bzVec2(22.48,-21.62);
+	bodyVertex[0][7] = bzVec2(20.72,-23.89);
+	bodyVertex[0][8] = bzVec2(10.00,-30.00);
+	bodyVertex[0][9] = bzVec2(-2.24,-31.54);
+	bodyVertex[0][10] = bzVec2(-8.19,-29.91);
+	bodyVertex[0][11] = bzVec2(-19.02,-24.50);
+	bodyVertex[0][12] = bzVec2(-23.89,-20.72);
+	bodyVertex[0][13] = bzVec2(-30.00,-10.00);
+	bodyVertex[0][14] = bzVec2(-31.00,-5.00);
+	bodyVertex[0][15] = bzVec2(-31.00,5.00);
+	bodyVertex[0][16] = bzVec2(-28.28,14.14);
+	bodyVertex[0][17] = bzVec2(-20.72,23.89);
+	bodyVertex[0][18] = bzVec2(-10.00,30.00);
+
+	vertex[1].length = bodyVertex[1].length = 4;
+	// Body
+	bodyVertex[1][0] = bzVec2(14.14,28.28);
+	bodyVertex[1][1] = bzVec2(23.89,20.72);
+	bodyVertex[1][2] = bzVec2(44.56,47.70);
+	bodyVertex[1][3] = bzVec2(35.97,54.29);
+
+	vertex[2].length = bodyVertex[2].length = 4;
+	// Top Strut
+	bodyVertex[2][0] = bzVec2(22.48,-21.62);
+	bodyVertex[2][1] = bzVec2(28.28,-14.14);
+	bodyVertex[2][2] = bzVec2(53.41,-29.39);
+	bodyVertex[2][3] = bzVec2(46.67,-37.86);
+
+	vertex[3].length = bodyVertex[3].length = 4;
+	// Top Wing
+	bodyVertex[3][0] = bzVec2(-19.02,-24.50);
+	bodyVertex[3][1] = bzVec2(-8.19,-29.91);
+	bodyVertex[3][2] = bzVec2(-36.26,-62.25);
+	bodyVertex[3][3] = bzVec2(-42.85,-53.66);
+
+	vertex[4].length = bodyVertex[4].length = 4;
+	// Bottom Strut
+	bodyVertex[4][0] = bzVec2(-31.00,-5.00);
+	bodyVertex[4][1] = bzVec2(-31.00,5.00);
+	bodyVertex[4][2] = bzVec2(-46.00,5.00);
+	bodyVertex[4][3] = bzVec2(-46.00,-5.00);
+
+	vertex[5].length = bodyVertex[5].length = 4;
+	// Bottom Wing
+	bodyVertex[5][0] = bzVec2(-10.00,30.00);
+	bodyVertex[5][1] = bzVec2(-20.72,23.89);
+	bodyVertex[5][2] = bzVec2(-50.00,65.86);
+	bodyVertex[5][3] = bzVec2(-40.00,70.00);
+
+	vertex[6].length = bodyVertex[6].length = 8;
+	// Rear, top Pod
+	bodyVertex[6][0] = bzVec2(-40.00 ,70.00);
+	bodyVertex[6][1] = bzVec2(-35.86 , 80.00);
+	bodyVertex[6][2] = bzVec2( -40.00, 90.00);
+	bodyVertex[6][3] = bzVec2( -50.00,94.14);
+	bodyVertex[6][4] = bzVec2( -60.00, 90.00);
+	bodyVertex[6][5] = bzVec2(-64.14 , 80.00);
+	bodyVertex[6][6] = bzVec2(-60.00 , 70.00);
+	bodyVertex[6][7] = bzVec2(-50.00 ,65.86);
+
+	vertex[7].length = bodyVertex[7].length = 8;
+	// Rear, middle pod
+	bodyVertex[7][0] = bzVec2(-46.00,5.00);
+	bodyVertex[7][1] = bzVec2( -46.00,-5.00);
+	bodyVertex[7][2] = bzVec2(-54.47 ,-12.02);
+	bodyVertex[7][3] = bzVec2( -65.30, -13.11);
+	bodyVertex[7][4] = bzVec2( -73.02, -5.53);
+	bodyVertex[7][5] = bzVec2( -73.11,5.30);
+	bodyVertex[7][6] = bzVec2( -65.53, 13.02);
+	bodyVertex[7][7] = bzVec2( -54.70,13.11);
+
+
+	vertex[8].length = bodyVertex[8].length = 8;
+	// Rear, bottom pod
+	bodyVertex[8][0] = bzVec2( -36.26,-62.25);
+	bodyVertex[8][1] = bzVec2( -42.85,-53.66);
+	bodyVertex[8][2] = bzVec2( -53.59,-52.25);
+	bodyVertex[8][3] = bzVec2( -62.17,-58.84);
+	bodyVertex[8][4] = bzVec2( -63.59,-69.57);
+	bodyVertex[8][5] = bzVec2( -57.00,-78.16);
+	bodyVertex[8][6] = bzVec2( -46.26,-79.57);
+	bodyVertex[8][7] = bzVec2( -37.68,-72.98);
+
+	vertex[9].length = bodyVertex[9].length = 8;
+
+	bodyVertex[9][0] = bzVec2( 53.41, -29.39);
+	bodyVertex[9][1] = bzVec2( 46.67,-37.86);
+	bodyVertex[9][2] = bzVec2( 47.89,-47.61);
+	bodyVertex[9][3] = bzVec2( 56.36, -55.35);
+	bodyVertex[9][4] = bzVec2( 67.12,-54.12);
+	bodyVertex[9][5] = bzVec2( 73.86, -45.65);
+	bodyVertex[9][6] = bzVec2( 72.63,-34.90);
+	bodyVertex[9][7] = bzVec2( 64.16,-28.16);
+
+	vertex[10].length = bodyVertex[10].length = 8;
+
+	bodyVertex[10][0] = bzVec2( 35.97, 54.29);
+	bodyVertex[10][1] = bzVec2( 44.56, 47.70);
+	bodyVertex[10][2] = bzVec2( 56.29, 49.12 );
+	bodyVertex[10][3] = bzVec2( 61.88, 57.70);
+	bodyVertex[10][4] = bzVec2( 60.47, 68.44);
+	bodyVertex[10][5] = bzVec2( 51.88, 75.02);
+	bodyVertex[10][6] = bzVec2( 41.15, 73.61);
+	bodyVertex[10][7] = bzVec2( 34.56, 65.02);
+}
+
+void urQuanDreadnought(inout bzVec2[][] bodyVertex, inout bzVec2[][] vertex)
+{
+	vertex.length = bodyVertex.length = 6;
+
+	vertex[0].length = bodyVertex[0].length = 8;
+	// Head
+	bodyVertex[0][0] = bzVec2(42,49);
+	bodyVertex[0][1] = bzVec2(63,49);
+	bodyVertex[0][2] = bzVec2(70,45.5);
+	bodyVertex[0][3] = bzVec2(73.5,38.5);
+	bodyVertex[0][4] = bzVec2(73.5,-42);
+	bodyVertex[0][5] = bzVec2(70,-49);
+	bodyVertex[0][6] = bzVec2(63,-56);
+	bodyVertex[0][7] = bzVec2(42,-56);
+
+	vertex[1].length = bodyVertex[1].length = 4;
+	// Body
+	bodyVertex[1][0] = bzVec2(-70,-28);
+	bodyVertex[1][1] = bzVec2(-70,24.5);
+	bodyVertex[1][2] = bzVec2(42,24.5);
+	bodyVertex[1][3] = bzVec2(42,-31.5);
+
+	vertex[2].length = bodyVertex[2].length = 4;
+	// Top Strut
+	bodyVertex[2][0] = bzVec2(0,24.5);
+	bodyVertex[2][1] = bzVec2(-28,24.5);
+	bodyVertex[2][2] = bzVec2(-28,42);
+	bodyVertex[2][3] = bzVec2(0,42);
+
+	vertex[3].length = bodyVertex[3].length = 4;
+	// Top Wing
+	bodyVertex[3][0] = bzVec2(-70,42);
+	bodyVertex[3][1] = bzVec2(-49,63);
+	bodyVertex[3][2] = bzVec2(28,63);
+	bodyVertex[3][3] = bzVec2(28,42);
+
+	vertex[4].length = bodyVertex[4].length = 4;
+	// Bottom Strut
+	bodyVertex[4][0] = bzVec2(0,-31.5);
+	bodyVertex[4][1] = bzVec2(0,-49);
+	bodyVertex[4][2] = bzVec2(-28,-49);
+	bodyVertex[4][3] = bzVec2(-28,-31.5);
+
+	vertex[5].length = bodyVertex[5].length = 4;
+	// Bottom Wing
+	bodyVertex[5][0] = bzVec2(-70,-49);
+	bodyVertex[5][1] = bzVec2(28,-49);
+	bodyVertex[5][2] = bzVec2(28,-70);
+	bodyVertex[5][3] = bzVec2(-42,-70);
+}
+
+void yehatTerminator(inout bzVec2[][] bodyVertex, inout bzVec2[][] vertex)
+{
+	vertex.length = bodyVertex.length = 4;
+	vertex[0].length = bodyVertex[0].length = 3;
+	// Cockpit
+	bodyVertex[0][0] = bzVec2(-14,21);
+	bodyVertex[0][1] = bzVec2(0,42);
+	bodyVertex[0][2] = bzVec2(14,21);
+
+
+	vertex[1].length = bodyVertex[1].length = 4;
+	// Body
+	bodyVertex[1][0] = bzVec2(-14,21);
+	bodyVertex[1][1] = bzVec2(-14,-21);
+	bodyVertex[1][2] = bzVec2(14,-21);
+	bodyVertex[1][3] = bzVec2(14,21);
+
+	vertex[2].length = bodyVertex[2].length = 4;
+	// Right Wing
+	bodyVertex[2][0] = bzVec2(14,21);
+	bodyVertex[2][1] = bzVec2(14,-21);
+	bodyVertex[2][2] = bzVec2(70,0);
+	bodyVertex[2][3] = bzVec2(84,59.5);
+
+	vertex[3].length = bodyVertex[3].length = 4;
+	// Left Wing
+	bodyVertex[3][0] = bzVec2(-14,21);
+	bodyVertex[3][1] = bzVec2(-14,-21);
+	bodyVertex[3][2] = bzVec2(-70,0);
+	bodyVertex[3][3] = bzVec2(-84,59.5);
+}
+
+void orzNemesis(inout bzVec2[][] bodyVertex, inout bzVec2[][] vertex)
+{
+	vertex.length = bodyVertex.length = 3;
+	vertex[0].length = bodyVertex[0].length = 4;
+	// Body
+	bodyVertex[0][0] = bzVec2(-28,21);
+	bodyVertex[0][1] = bzVec2(-28,-28);
+	bodyVertex[0][2] = bzVec2(42,-21);
+	bodyVertex[0][3] = bzVec2(42,14);
+
+	vertex[1].length = bodyVertex[1].length = 5;
+	// Top Wing
+	bodyVertex[1][0] = bzVec2(-28,21);
+	bodyVertex[1][1] = bzVec2(-70,63);
+	bodyVertex[1][2] = bzVec2(-49,63);
+	bodyVertex[1][3] = bzVec2(70,14);
+	bodyVertex[1][4] = bzVec2(42,14);
+
+	vertex[2].length = bodyVertex[2].length = 5;
+	// Bottom Wing
+	bodyVertex[2][0] = bzVec2(-28,-28);
+	bodyVertex[2][1] = bzVec2(-70,-63);
+	bodyVertex[2][2] = bzVec2(-49,-63);
+	bodyVertex[2][3] = bzVec2(70,-21);
+	bodyVertex[2][4] = bzVec2(42,-21);
+}
+
+void triangle(inout bzVec2[][] bodyVertex, inout bzVec2[][] vertex)
+{
+
+	vertex.length = bodyVertex.length = 1;
+	vertex[0].length = bodyVertex[0].length = 3;
+
+	bodyVertex[0][0] = bzVec2(0.0f,7.0f);
+	bodyVertex[0][1] = bzVec2(7.0f,-7.0f);
+	bodyVertex[0][2] = bzVec2(-7.0f,-7.0f);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/orz.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.orz;
+
+import tango.io.Stdout;
+
+import blaze.dynamics.bzBody : bzBody;
+import blaze.bzWorld: bzWorld;
+import blaze.dynamics.bzBodyDef;
+import blaze.collision.shapes.bzPolygon : bzPolyDef;
+import blaze.common.bzMath: bzVec2, PI;
+
+import melee.ship;
+
+// UrQuan Dreadnought
+class Orz : Ship
+{
+
+    float scale = 0.01;
+
+    this(bzWorld world) {
+
+        super.engineForce = bzVec2(5, 0);
+        super.turnForce = bzVec2(0, 300);
+        super.rightTurnPoint = bzVec2(-0.1, 0);
+        super.leftTurnPoint = bzVec2(0.1, 0);
+
+        auto bodyDef = new bzBodyDef;
+        bodyDef.position = bzVec2(10,10);
+        bodyDef.angle = PI/2;
+
+        super.rBody = world.createBody(bodyDef);
+
+        float density = 2.0f;
+
+        // Body
+        auto b = new bzPolyDef(density);
+        b.vertices.length = 4;
+        b.vertices[0] = bzVec2(42,14) * scale;
+        b.vertices[1] = bzVec2(-28,21) * scale;
+        b.vertices[2] = bzVec2(-28,-28) * scale;
+        b.vertices[3] = bzVec2(42,-21) * scale;
+        super.shapes.add(rBody.createShape(b));
+
+        // Top Wing
+        auto tWing = new bzPolyDef(density);
+        tWing.vertices.length = 5;
+        tWing.vertices[4] = bzVec2(-28,21) * scale;
+        tWing.vertices[3] = bzVec2(-70,63) * scale;
+        tWing.vertices[2] = bzVec2(-49,63) * scale;
+        tWing.vertices[1] = bzVec2(70,14) * scale;
+        tWing.vertices[0] = bzVec2(42,14) * scale;
+        super.shapes.add(rBody.createShape(tWing));
+
+        // Bottom Wing
+        auto bWing = new bzPolyDef(density);
+        bWing.vertices.length = 5;
+        bWing.vertices[0] = bzVec2(-28,-28) * scale;
+        bWing.vertices[1] = bzVec2(-70,-63) * scale;
+        bWing.vertices[2] = bzVec2(-49,-63) * scale;
+        bWing.vertices[3] = bzVec2(70,-21) * scale;
+        bWing.vertices[4] = bzVec2(42,-21) * scale;
+        super.shapes.add(rBody.createShape(bWing));
+
+        super.rBody.setMassFromShapes();
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * Based on Box2D by Erin Catto, http://www.box2d.org
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.render;
+
+import xf.dog.Dog;
+import xf.omg.core.LinearAlgebra;
+import xf.hybrid.Event;
+import xf.hybrid.Font;
+
+import melee.melee;
+
+/// Color for drawing. Each value has the range [0,1].
+struct Color {
+    static Color opCall(float r, float g, float b)
+    {
+        Color u;
+        u.r = r;
+        u.g = g;
+        u.b = b;
+        return u;
+    }
+
+    float r = 0;
+    float g = 0;
+    float b = 0;
+}
+
+class Render : Melee
+{
+
+this(Settings *settings) {
+
+	super(settings);
+}
+
+void drawCircle(GL gl, vec2 center, float radius, bool water = false, float theta = float.nan)
+{
+    int segs = cast(int)(radius) + 20;
+    if (segs > MAX_CIRCLE_RES) segs = MAX_CIRCLE_RES;
+    double coef = 2.0 * PI / segs;
+
+    auto realTheta = (theta <>= 0 ? theta : 0);
+    if (water) {
+        gl.immediate(GL_TRIANGLE_FAN,
+        {
+            gl.Vertex2fv(center.ptr);
+            for (int n = 0; n <= segs; n++) {
+                double rads = n * coef;
+                gl.Vertex2f(radius * cos(rads + realTheta) + center.x, radius * sin(rads + realTheta) + center.y);
+            }
+        });
+    }
+
+    gl.immediate(GL_LINE_STRIP,
+    {
+        for (int n = 0; n <= segs; n++) {
+            double rads = n * coef;
+            gl.Vertex2f(radius * cos(rads + realTheta) + center.x, radius * sin(rads + realTheta) + center.y);
+        }
+        if (theta <>= 0)
+            gl.Vertex2fv(center.ptr);
+    });
+}
+
+void drawSolidCircle(GL gl, vec2 center, float radius, vec2 axis, Color color)
+{
+    const k_segments = 16.0f;
+    const k_increment = 2.0f * PI / k_segments;
+    float theta = 0.0f;
+    gl.Enable(GL_BLEND);
+    gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    gl.Color4f(0.5f * color.r, 0.5f * color.g, 0.5f * color.b, 0.5f);
+    gl.Begin(GL_TRIANGLE_FAN);
+    for (int i = 0; i < k_segments; ++i) {
+        vec2 v = center + radius * vec2(cos(theta), sin(theta));
+        gl.Vertex2f(v.x, v.y);
+        theta += k_increment;
+    }
+    gl.End();
+    gl.Disable(GL_BLEND);
+
+    theta = 0.0f;
+    gl.Color4f(color.r, color.g, color.b, 1.0f);
+    gl.Begin(GL_LINE_LOOP);
+    for (int i = 0; i < k_segments; ++i) {
+        vec2 v = center + radius * vec2(cos(theta), sin(theta));
+        gl.Vertex2f(v.x, v.y);
+        theta += k_increment;
+    }
+    gl.End();
+
+    vec2 p = center + radius * axis;
+    gl.Begin(GL_LINES);
+    gl.Vertex2f(center.x, center.y);
+    gl.Vertex2f(p.x, p.y);
+    gl.End();
+}
+
+void drawPolygon(GL gl, vec2[] glVerts, Color color)
+{
+    gl.Color3f(color.r, color.g, color.b);
+    gl.immediate(GL_LINE_LOOP,
+    {
+        foreach (v; glVerts)
+            gl.Vertex2fv(v.ptr);
+    });
+}
+
+void drawSolidPolygon(GL gl, vec2[] vertices, Color color)
+{
+    gl.Enable(GL_BLEND);
+    gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    gl.Color4f(0.5f * color.r, 0.5f * color.g, 0.5f * color.b, 0.5f);
+    gl.Begin(GL_TRIANGLE_FAN);
+    for (int i = 0; i < vertices.length; ++i) {
+        gl.Vertex2f(vertices[i].x, vertices[i].y);
+    }
+    gl.End();
+    gl.Disable(GL_BLEND);
+
+    gl.Color4f(color.r, color.g, color.b, 1.0f);
+    gl.Begin(GL_LINE_LOOP);
+    for (int i = 0; i < vertices.length; ++i) {
+        gl.Vertex2f(vertices[i].x, vertices[i].y);
+    }
+    gl.End();
+}
+
+
+void drawPoint(GL gl, vec2 p, float size, Color color)
+{
+    gl.Color3f(color.r, color.g, color.b);
+    gl.PointSize(size);
+    gl.Begin(GL_POINTS);
+    gl.Vertex2f(p.x, p.y);
+    gl.End();
+    gl.PointSize(1.0f);
+}
+
+void drawSegment(GL gl, vec2 begin, vec2 end, Color color)
+{
+    gl.Color3f(color.r, color.g, color.b);
+    gl.immediate(GL_LINES,
+    {
+        gl.Vertex2fv(begin.ptr);
+        gl.Vertex2fv(end.ptr);
+    });
+}
+
+// TODO: handle inequal radii correctly
+void connectCircles(GL gl, vec2 center1, float radius1, vec2 center2, float radius2)
+{
+    auto d = center2 - center1;
+    if (!d.length)
+        return;
+    d *= (d.length - radius1) / d.length;
+    center1 += d;
+    center2 -= d;
+    gl.immediate(GL_LINES,
+    {
+        gl.Vertex2fv(center1.ptr);
+        gl.Vertex2fv(center2.ptr);
+    });
+}
+
+void drawXForm(GL gl, bzXForm xf)
+{
+    bzVec2 p1 = xf.position, p2;
+    const k_axisScale = 0.4f;
+
+    gl.Begin(GL_LINES);
+    {
+        gl.Color3f(1.0f, 0.0f, 0.0f);
+        gl.Vertex2f(p1.x, p1.y);
+        p2 = p1 + k_axisScale * xf.R.col1;
+        gl.Vertex2f(p2.x, p2.y);
+
+        gl.Color3f(0.0f, 1.0f, 0.0f);
+        gl.Vertex2f(p1.x, p1.y);
+        p2 = p1 + k_axisScale * xf.R.col2;
+        gl.Vertex2f(p2.x, p2.y);
+    }
+    gl.End();
+}
+
+void drawSpring(GL gl, vec2 a, vec2 b, uint zigs)
+{
+    zigs++;
+
+    // Portion of length dedicated to connectors
+    const float connPart = 0.2;
+
+    vec2 inc = (b - a) / (zigs);
+    // One step from a to b
+    vec2 zigLen = inc * (1 - connPart);
+    // Length of a connector
+    vec2 connLen = inc * (connPart / 2) * zigs;
+    // Width of a zig
+    vec2 zigWidth = (b - a).rotatedHalfPi.normalized;
+    gl.immediate(GL_LINE_STRIP,
+    {
+        gl.Vertex2fv(a.ptr);
+
+        a += connLen;
+        gl.Vertex2fv(a.ptr);
+
+        bool dir = true;
+        a += zigWidth / 2 + zigLen / 2;
+        for (int i = 0; i < zigs; i++) {
+         gl.Vertex2fv(a.ptr);
+         a += zigLen;
+         if (dir) {
+             a -= zigWidth;
+         }else {
+             a += zigWidth;
+         }
+         dir = !dir;
+        }
+
+        gl.Vertex2fv((b - connLen).ptr);
+        gl.Vertex2fv(b.ptr);
+    });
+}
+
+void drawShape(GL gl, bzShape shape, bzXForm xf, Color color, bool core)
+{
+    Color coreColor = Color(0.9f, 0.6f, 0.6f);
+
+    switch (shape.type) {
+    case bzShapeType.CIRCLE:
+        auto circle = cast(bzCircle)shape;
+
+        vec2 center = vec2.from(bzMul(xf, circle.localPosition));
+        float radius = circle.radius;
+        vec2 axis = vec2.from(xf.R.col1);
+
+        gl.drawSolidCircle(center, radius, axis, color);
+
+        if (core) {
+            gl.Color3f(coreColor.r, coreColor.g, coreColor.b);
+            gl.drawCircle(center, radius - k_toiSlop);
+        }
+        break;
+    case bzShapeType.POLYGON:
+    {
+        bzPolygon poly = cast(bzPolygon)shape;
+        bzVec2[] vertices = poly.worldVertices;
+        vec2[]  verts;
+        verts.length = vertices.length;
+        foreach (int i, v; vertices) {
+            verts[i] = vec2.from(v);
+        }
+
+        gl.drawSolidPolygon(verts, color);
+
+        if (core) {
+            bzVec2[] localCoreVertices = poly.coreVertices;
+            verts.length = localCoreVertices.length;
+            for (int i = 0; i < localCoreVertices.length; ++i) {
+                verts[i] = vec2.from(bzMul(xf, localCoreVertices[i]));
+            }
+            gl.drawPolygon(verts, coreColor);
+        }
+    }
+    break;
+
+    case bzShapeType.EDGE:
+    {
+        bzEdge edge = cast(bzEdge)shape;
+
+        vec2 p1 = vec2.from(bzMul(xf, edge.vertex1));
+        vec2 p2 = vec2.from(bzMul(xf, edge.vertex2));
+        gl.drawSegment(p1, p2, color);
+
+        if (core) {
+            p1 = vec2.from(bzMul(xf, edge.coreVertex1));
+            p2 = vec2.from(bzMul(xf, edge.coreVertex2));
+            gl.drawSegment(p1, p2, coreColor);
+        }
+    }
+    break;
+    }
+}
+
+void draw(vec2i screenSize, GL gl)
+{
+    this.screenSize = screenSize;
+
+    gl.LoadIdentity();
+    gl.MatrixMode(GL_PROJECTION);
+    gl.LoadIdentity();
+    // Left, right, bottom, top
+    gl.gluOrtho2D(-screenSize.x / zoom, screenSize.x / zoom, -screenSize.y / zoom, screenSize.y / zoom);
+    gl.Translatef(-viewCenter.x, -viewCenter.y, 0);
+    gl.MatrixMode(GL_MODELVIEW);
+    gl.Disable(GL_DEPTH_TEST);
+    gl.LoadIdentity();
+    gl.Clear(GL_COLOR_BUFFER_BIT);
+
+    // Draw dynamic bodies
+    if (settings.drawShapes) {
+        for (bzBody b = world.bodyList; b; b = b.next) {
+            for (bzShape shape = b.shapeList; shape; shape = shape.next) {
+
+                bzShape s = shape;
+                bzXForm xf = b.xf;
+
+                if (b.isStatic) {
+                    gl.drawShape(s, xf, Color(0.5f, 0.9f, 0.5f), settings.drawCoreShapes);
+                }else if (b.isSleeping) {
+                    gl.drawShape(s, xf, Color(0.5f, 0.5f, 0.9f), settings.drawCoreShapes);
+                }else if (b.userData) {
+                    auto ss = cast(Select)b.userData;
+                    if (ss) {
+                        gl.drawShape(s, xf, Color(0, .5, 1), settings.drawCoreShapes);
+                    }
+                }else {
+                    gl.drawShape(s, xf, Color(0.9f, 0.9f, 0.9f), settings.drawCoreShapes);
+                }
+
+                gl.LoadIdentity();
+                gl.Flush();
+            }
+        }
+
+        // Draw water
+        bzFluidParticle[] particles = world.particles;
+        gl.Color3f(0, 0, 1);
+        foreach (p; particles) {
+            gl.drawCircle(vec2.from(p.position), WATER_RADIUS, true);
+        }
+    }
+
+    // Draw joints
+    if (settings.drawJoints) {
+        Color color = Color(0, 0, 1);
+        gl.Color3f(0, 0, 1);
+
+        gl.LineWidth(1);
+        for (bzJoint joint = world.jointList; joint; joint = joint.next) {
+            auto distance = cast(bzDistanceJoint)joint;
+            auto pulley = cast(bzPulleyJoint)joint;
+            auto revolute = cast(bzRevoluteJoint)joint;
+            auto prismatic = cast(bzPrismaticJoint)joint;
+            auto line = cast(bzLineJoint)joint;
+            if (distance) {
+                color = Color(.5, .5, 0);
+                // Endpoints
+                vec2 a = vec2.from(distance.anchor1);
+                vec2 b = vec2.from(distance.anchor2);
+                // Circles
+                gl.drawCircle(a, HINGE_RADIUS);
+                gl.drawCircle(b, HINGE_RADIUS);
+                // Connecting line
+                gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS);
+            }else if (pulley) {
+                auto a = vec2.from(pulley.anchor1);
+                auto b = vec2.from(pulley.groundAnchor1);
+                auto c = vec2.from(pulley.groundAnchor2);
+                auto d = vec2.from(pulley.anchor2);
+                gl.drawCircle(a, HINGE_RADIUS);
+                gl.drawCircle(b, HINGE_RADIUS);
+                gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS);
+                gl.drawSegment(b, c, color);
+                gl.drawCircle(c, HINGE_RADIUS);
+                gl.drawCircle(d, HINGE_RADIUS);
+                gl.connectCircles(c, HINGE_RADIUS, d, HINGE_RADIUS);
+            }else if (revolute) {
+                auto a = vec2.from(revolute.rBody1.position);
+                auto b = vec2.from(revolute.anchor1);
+                auto c = vec2.from(revolute.rBody2.position);
+                gl.drawCircle(a, HINGE_RADIUS);
+                gl.drawCircle(b, HINGE_RADIUS);
+                gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS);
+                gl.drawCircle(c, HINGE_RADIUS);
+                gl.connectCircles(b, HINGE_RADIUS, c, HINGE_RADIUS);
+            }else if (prismatic) {
+                auto a = vec2.from(prismatic.rBody1.position);
+                auto b = vec2.from(prismatic.anchor1);
+                auto c = vec2.from(prismatic.rBody2.position);
+                gl.drawCircle(a, HINGE_RADIUS);
+                gl.drawCircle(b, HINGE_RADIUS);
+                gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS);
+                gl.drawCircle(c, HINGE_RADIUS);
+                gl.connectCircles(b, HINGE_RADIUS, c, HINGE_RADIUS);
+            }else if (line) {
+                auto a = vec2.from(line.rBody1.position);
+                auto b = vec2.from(line.anchor1);
+                auto c = vec2.from(line.rBody2.position);
+                gl.drawCircle(a, HINGE_RADIUS);
+                gl.drawCircle(b, HINGE_RADIUS);
+                gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS);
+                gl.drawCircle(c, HINGE_RADIUS);
+                gl.connectCircles(b, HINGE_RADIUS, c, HINGE_RADIUS);
+            }
+        }
+
+        if (settings.drawControllers) {
+            bzForceGenerator[] forces = world.forces;
+            foreach (f; forces) {
+                auto spring1 = cast(bzSpring1) f;
+                auto spring2 = cast(bzSpring2) f;
+                auto buoyancy = cast(bzBuoyancy) f;
+
+                if (spring1) {
+                    auto bungee1 = cast(bzBungee1)spring1;
+                    if (bungee1) {
+                        gl.Color3f(.5, .5, 0);
+                        // Endpoints
+                        vec2 a = vec2.from(bungee1.rBody.position);
+                        vec2 b = vec2.from(bungee1.anchor);
+                        // Circles
+                        gl.drawCircle(a, HINGE_RADIUS);
+                        gl.drawCircle(b, HINGE_RADIUS);
+                        // Connecting line
+                        gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS);
+                    }else {
+                        uint zigs = 10;
+                        auto anchor1 = vec2.from(spring1.anchor);
+                        auto anchor2 = vec2.from(spring1.rBody.position);
+                        gl.drawSpring(anchor1, anchor2, zigs);
+                    }
+                }
+
+                if (spring2) {
+                    auto bungee2 = cast(bzBungee2)spring2;
+                    if (bungee2) {
+                        gl.Color3f(.5, .5, 0);
+                        // Endpoints
+                        vec2 a = vec2.from(bungee2.rBody.position);
+                        vec2 b = vec2.from(bungee2.otherBody.position);
+                        // Circles
+                        gl.drawCircle(a, HINGE_RADIUS);
+                        gl.drawCircle(b, HINGE_RADIUS);
+                        // Connecting line
+                        gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS);
+                    }else {
+                        uint zigs = 10;
+                        auto anchor1 = vec2.from(spring2.otherBody.position);
+                        auto anchor2 = vec2.from(spring2.rBody.position);
+                        gl.drawSpring(anchor1, anchor2, zigs);
+                    }
+                }
+
+                if(buoyancy) {
+                    float plane = buoyancy.planeOffset;
+                    vec2 p1 = vec2(-50, plane);
+                    vec2 p2 = vec2(50, plane);
+                    gl.drawSegment(p1, p2, color);
+                }
+            }
+        }
+    }
+
+    if(settings.drawPairs) {
+
+        bzBroadPhase bp = world.broadPhase;
+		bzVec2 invQ;
+		invQ.set(1.0f / bp.m_quantizationFactor.x, 1.0f / bp.m_quantizationFactor.y);
+		Color color = Color(0.9f, 0.9f, 0.3f);
+
+        const k_tableCapacity = bzPairManager.TABLE_CAPACITY;
+
+		for (int i = 0; i < k_tableCapacity; ++i)
+		{
+			ushort index = bp.m_pairManager.m_hashTable[i];
+			while (index < bp.m_pairManager.m_pairs.length)
+			{
+                if(index == bzPairManager.NULL_PROXY) {
+                    break;
+                }
+				bzPair pair = bp.m_pairManager.m_pairs[index];
+				bzProxy p1 = bp.m_proxyPool[pair.proxyId1];
+				bzProxy p2 = bp.m_proxyPool[pair.proxyId2];
+
+				bzAABB b1, b2;
+				b1.lowerBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p1.lowerBounds[0]].value;
+				b1.lowerBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p1.lowerBounds[1]].value;
+				b1.upperBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p1.upperBounds[0]].value;
+				b1.upperBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p1.upperBounds[1]].value;
+				b2.lowerBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p2.lowerBounds[0]].value;
+				b2.lowerBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p2.lowerBounds[1]].value;
+				b2.upperBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p2.upperBounds[0]].value;
+				b2.upperBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p2.upperBounds[1]].value;
+
+				bzVec2 x1 = 0.5f * (b1.lowerBound + b1.upperBound);
+				bzVec2 x2 = 0.5f * (b2.lowerBound + b2.upperBound);
+
+				gl.drawSegment(vec2.from(x1),vec2.from(x2), color);
+
+				index = pair.next;
+			}
+		}
+    }
+
+    // Draw axis aligned bounding boxes (bzAABB)
+    if (settings.drawAABBs) {
+        bzBroadPhase bp = world.broadPhase;
+        bzVec2 worldLower = bp.m_worldAABB.lowerBound;
+        bzVec2 worldUpper = bp.m_worldAABB.upperBound;
+        Color color;
+        bzVec2 invQ;
+        invQ.set(1.0f / bp.m_quantizationFactor.x, 1.0f / bp.m_quantizationFactor.y);
+        color = Color(1.0f, 1.0f, 1.0f);
+
+        for (int i = 0; i < k_maxProxies; ++i) {
+            bzProxy p = bp.m_proxyPool[i];
+            if (!p.isValid) {
+                continue;
+            }
+
+            bzAABB b;
+            b.lowerBound.x = worldLower.x + invQ.x * bp.m_bounds[0][p.lowerBounds[0]].value;
+            b.lowerBound.y = worldLower.y + invQ.y * bp.m_bounds[1][p.lowerBounds[1]].value;
+            b.upperBound.x = worldLower.x + invQ.x * bp.m_bounds[0][p.upperBounds[0]].value;
+            b.upperBound.y = worldLower.y + invQ.y * bp.m_bounds[1][p.upperBounds[1]].value;
+
+            vec2 vs[4];
+            vs[0] = vec2(b.lowerBound.x, b.lowerBound.y);
+            vs[1] = vec2(b.upperBound.x, b.lowerBound.y);
+            vs[2] = vec2(b.upperBound.x, b.upperBound.y);
+            vs[3] = vec2(b.lowerBound.x, b.upperBound.y);
+
+            drawPolygon(gl, vs, color);
+        }
+
+        vec2 vs[4];
+        vs[0] = vec2(worldLower.x, worldLower.y);
+        vs[1] = vec2(worldUpper.x, worldLower.y);
+        vs[2] = vec2(worldUpper.x, worldUpper.y);
+        vs[3] = vec2(worldLower.x, worldUpper.y);
+
+        color = Color(0.3f, 0.9f, 0.9f);
+        drawPolygon(gl, vs, color);
+    }
+
+    // Draw contact points
+    if (settings.drawContactPoints) {
+        const k_axisScale = 0.3f;
+        const k_forceScale = 0.01f;
+
+
+        for (int i = 0; i < pointCount; ++i) {
+            ContactPoint point = points[i];
+            Color color;
+
+            if (point.state == ContactState.e_contactAdded) {
+                // Add
+                color = Color(0.3f, 0.95f, 0.3f);
+                vec2 p = vec2.from(point.position);
+                gl.drawPoint(p, 10.0f, color);
+            }else if (point.state == ContactState.e_contactPersisted) {
+                // Persist
+                color = Color(0.3f, 0.3f, 0.95f);
+                vec2 p = vec2.from(point.position);
+                gl.drawPoint(p, 5.0f, color);
+            }else {
+                // Remove
+                color = Color(0.95f, 0.3f, 0.3f);
+                vec2 p = vec2.from(point.position);
+                gl.drawPoint(p, 10.0f, color);
+            }
+
+            if (settings.drawContactNormals == 1) {
+                vec2 p1 = vec2.from(point.position);
+                vec2 p2 = p1 + k_axisScale * vec2.from(point.normal);
+                color = Color(0.4f, 0.9f, 0.4f);
+                gl.drawSegment(p1, p2, color);
+            }else if (settings.drawContactForces) { /*
+                       vec2 p1 = vec2.from(point.position);
+                       vec2 p2 = p1 + k_forceScale * vec2.from(point.normalForce * point.normal);
+                       color = Color(0.9f, 0.9f, 0.3f);
+                       gl.drawSegment(p1, p2, color);*/
+            }
+
+            if (settings.drawFrictionForces) { /*
+                      vec2 tangent = vec2.from(bzCross(point.normal, 1.0f));
+                      vec2 p1 = point.position;
+                      vec2 p2 = p1 + k_forceScale * vec2.from(point.tangentForce) * tangent;
+                      color = Color(0.9f, 0.9f, 0.3f);
+                      gl.drawSegment(p1, p2, color); */
+            }
+        }
+    }
+
+    if (settings.drawOBBs) {
+        Color color = Color(0.5f, 0.3f, 0.5f);
+
+        for (bzBody b = world.bodyList; b; b = b.next) {
+            bzXForm xf = b.xf;
+            for (bzShape s = b.shapeList; s; s = s.next) {
+                if (s.type != bzShapeType.POLYGON) {
+                    continue;
+                }
+
+                bzPolygon poly = cast(bzPolygon)s;
+                bzOBB obb = poly.obb;
+                bzVec2 h = obb.extents;
+                bzVec2 vs[4];
+                vs[0].set(-h.x, -h.y);
+                vs[1].set(h.x, -h.y);
+                vs[2].set(h.x, h.y);
+                vs[3].set(-h.x, h.y);
+
+                vec2[4] v;
+                for (int i = 0; i < 4; ++i) {
+                    vs[i] = obb.center + bzMul(obb.R, vs[i]);
+                    v[i] = vec2.from(bzMul(xf, vs[i]));
+                }
+
+                drawPolygon(gl, v, color);
+            }
+        }
+    }
+
+    if (settings.drawCOMs) {
+        for (bzBody b = world.bodyList; b; b = b.next) {
+            bzXForm xf = b.xf;
+            xf.position = b.worldCenter;
+            drawXForm(gl, xf);
+        }
+    }
+
+    // Nonphysical stuffs
+    // Universal '.' cursor
+    gl.Color3f(1, 1, 1);
+    gl.immediate(GL_POINTS,
+    {
+        gl.Vertex2fv(mousePos.ptr);
+    });
+
+    pointCount = 0;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ship.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.ship;
+
+import tango.io.Stdout: Stdout;
+
+import tango.util.container.LinkedList : LinkedList;
+import blaze.dynamics.bzBody : bzBody;
+import blaze.collision.shapes.bzShape : bzShape;
+import blaze.common.bzMath: bzVec2, bzCross;
+
+alias LinkedList!(bzShape) ShapeList;
+
+abstract class Ship
+{
+
+    bzBody rBody;
+    ShapeList shapes;
+    bzVec2 engineForce;
+    bzVec2 turnForce;
+    bzVec2 leftTurnPoint;
+    bzVec2 rightTurnPoint;
+
+    this() {
+        shapes = new ShapeList;
+    }
+
+    void thrust() {
+        rBody.force += engineForce.rotate(rBody.angle);
+    }
+
+    void turn(char key) {
+        switch(key) {
+            case 'd':
+                rBody.torque += bzCross(rightTurnPoint.rotate(rBody.angle), 
+                                turnForce.rotate(rBody.angle));
+                break;
+            case 'a':
+                rBody.torque += bzCross(leftTurnPoint.rotate(rBody.angle), 
+                                turnForce.rotate(rBody.angle));
+                break;
+            default:
+                break;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/steer.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,480 @@
+// ----------------------------------------------------------------------------
+//
+//
+// OpenSteer -- Steering Behaviors for Autonomous Characters
+//
+// Copyright (c) 2002-2003, Sony Computer Entertainment America
+// Original author: Craig Reynolds <craig_reynolds@playstation.sony.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+//
+// ----------------------------------------------------------------------------
+//
+//
+// SteerLibraryMixin
+//
+// This mixin (class with templated superclass) adds the "steering library"
+// functionality to a given base class.  SteerLibraryMixin assumes its base
+// class supports the Ship interface.
+//
+// 10-04-04 bk:  put everything into the OpenSteer namespace
+// 02-06-03 cwr: create mixin (from "SteerMass")
+// 06-03-02 cwr: removed TS dependencies
+// 11-21-01 cwr: created
+//
+//
+// ----------------------------------------------------------------------------
+module melee.steer;
+
+    class Steer
+    {
+        // Constructor: initializes state
+        this ()
+        {
+            // set inital state
+            reset ();
+        }
+
+        // reset state
+        void reset (void)
+        {
+            // initial state of wander behavior
+            wanderSide = 0;
+            wanderUp = 0;
+
+            // default to non-gaudyPursuitAnnotation
+            gaudyPursuitAnnotation = false;
+        }
+
+        // -------------------------------------------------- steering behaviors
+
+        // Wander behavior
+        float wanderSide;
+        float wanderUp;
+
+        bzVec2 steerForWander (float dt) {
+            // random walk wanderSide and wanderUp between -1 and +1
+            float speed = 12 * dt; // maybe this (12) should be an argument?
+            wanderSide = scalarRandomWalk (wanderSide, speed, -1, +1);
+            wanderUp   = scalarRandomWalk (wanderUp,   speed, -1, +1);
+
+            // return a pure lateral steering vector: (+/-Side) + (+/-Up)
+            return (side() * wanderSide) + (up() * wanderUp);
+        }
+
+        // Seek behavior
+        bzVec2 steerForSeek (bzVec2 target) {
+            bzVec2 desiredVelocity = target - position;
+            return desiredVelocity - velocity;
+        }
+
+        // Flee behavior
+        bzVec2 steerForFlee (bzVec2 target) {
+            bzVec2 desiredVelocity = position - target;
+            return desiredVelocity - velocity();
+        }
+
+        // xxx proposed, experimental new seek/flee [cwr 9-16-02]
+        bzVec2 xxxsteerForFlee (bzVec2 target) {
+            bzVec2 offset = position - target;
+            bzVec2 desiredVelocity = offset.truncateLength (maxSpeed ());
+            return desiredVelocity - velocity();
+        }
+
+        bzVec2 xxxsteerForSeek (bzVec2 target) {
+            //  bzVec2 offset = target - position;
+            bzVec2 offset = target - position;
+            bzVec2 desiredVelocity = offset.truncateLength (maxSpeed ()); //xxxnew
+            return desiredVelocity - velocity();
+        }
+
+        // ------------------------------------------------------------------------
+        // Obstacle Avoidance behavior
+        //
+        // Returns a steering force to avoid a given obstacle.  The purely
+        // lateral steering force will turn our vehicle towards a silhouette edge
+        // of the obstacle.  Avoidance is required when (1) the obstacle
+        // intersects the vehicle's current path, (2) it is in front of the
+        // vehicle, and (3) is within minTimeToCollision seconds of travel at the
+        // vehicle's current velocity.  Returns a zero vector value (bzVec2::zero)
+        // when no avoidance is required.
+        bzVec2 steerToAvoidObstacle (float minTimeToCollision, Obstacle obstacle) {
+
+            bzVec2 avoidance = obstacle.steerToAvoid (this, minTimeToCollision);
+            // XXX more annotation modularity problems (assumes spherical obstacle)
+            if (avoidance != bzVec2::zero)
+                annotateAvoidObstacle (minTimeToCollision * speed());
+
+            return avoidance;
+        }
+
+        // avoids all obstacles in an ObstacleGroup
+
+        bzVec2 steerToAvoidObstacles (float minTimeToCollision, 
+                                      ObstacleGroup obstacles) {
+
+            bzVec2 avoidance;
+            PathIntersection nearest, next;
+            float minDistanceToCollision = minTimeToCollision * speed();
+
+            next.intersect = false;
+            nearest.intersect = false;
+
+            // test all obstacles for intersection with my forward axis,
+            // select the one whose point of intersection is nearest
+            for (ObstacleIterator o = obstacles.begin(); o != obstacles.end(); o++)
+            {
+                // xxx this should be a generic call on Obstacle, rather than
+                // xxx this code which presumes the obstacle is spherical
+                findNextIntersectionWithSphere ((SphericalObstacle)**o, next);
+
+                if ((nearest.intersect == false) ||
+                    ((next.intersect != false)
+                     (next.distance < nearest.distance)))
+                    nearest = next;
+            }
+
+            // when a nearest intersection was found
+            if ((nearest.intersect != false)
+                (nearest.distance < minDistanceToCollision))
+            {
+                // show the corridor that was checked for collisions
+                annotateAvoidObstacle (minDistanceToCollision);
+
+                // compute avoidance steering force: take offset from obstacle to me,
+                // take the component of that which is lateral (perpendicular to my
+                // forward direction), set length to maxForce, add a bit of forward
+                // component (in capture the flag, we never want to slow down)
+                bzVec2 offset = position - nearest.obstacle.center;
+                avoidance = offset.perpendicularComponent (forward());
+                avoidance = avoidance.normalize ();
+                avoidance *= maxForce ();
+                avoidance += forward() * maxForce () * 0.75;
+            }
+
+            return avoidance;
+        }
+
+        // ------------------------------------------------------------------------
+        // Unaligned collision avoidance behavior: avoid colliding with other
+        // nearby vehicles moving in unconstrained directions.  Determine which
+        // (if any) other other vehicle we would collide with first, then steers
+        // to avoid the site of that potential collision.  Returns a steering
+        // force vector, which is zero length if there is no impending collision.
+
+        bzVec2 steerToAvoidNeighbors (float minTimeToCollision, AVGroup others) {
+
+            // first priority is to prevent immediate interpenetration
+            bzVec2 separation = steerToAvoidCloseNeighbors (0, others);
+            if (separation != bzVec2::zero) return separation;
+
+            // otherwise, go on to consider potential future collisions
+            float steer = 0;
+            Ship* threat = NULL;
+
+            // Time (in seconds) until the most immediate collision threat found
+            // so far.  Initial value is a threshold: don't look more than this
+            // many frames into the future.
+            float minTime = minTimeToCollision;
+
+            // xxx solely for annotation
+            bzVec2 xxxThreatPositionAtNearestApproach;
+            bzVec2 xxxOurPositionAtNearestApproach;
+
+            // for each of the other vehicles, determine which (if any)
+            // pose the most immediate threat of collision.
+            for (AVIterator i = others.begin(); i != others.end(); i++)
+            {
+                Ship other = **i;
+                if (other != this)
+                {
+                    // avoid when future positions are this close (or less)
+                    float collisionDangerThreshold = radius() * 2;
+
+                    // predicted time until nearest approach of "this" and "other"
+                    float time = predictNearestApproachTime (other);
+
+                    // If the time is in the future, sooner than any other
+                    // threatened collision...
+                    if ((time >= 0)  (time < minTime))
+                    {
+                        // if the two will be close enough to collide,
+                        // make a note of it
+                        if (computeNearestApproachPositions (other, time)
+                            < collisionDangerThreshold)
+                        {
+                            minTime = time;
+                            threat = other;
+                            xxxThreatPositionAtNearestApproach
+                                = hisPositionAtNearestApproach;
+                            xxxOurPositionAtNearestApproach
+                                = ourPositionAtNearestApproach;
+                        }
+                    }
+                }
+            }
+
+            // if a potential collision was found, compute steering to avoid
+            if (threat)
+            {
+                // parallel: +1, perpendicular: 0, anti-parallel: -1
+                float parallelness = forward.dot(threat.forward);
+                float angle = 0.707f;
+
+                if (parallelness < -angle)
+                {
+                    // anti-parallel "head on" paths:
+                    // steer away from future threat position
+                    bzVec2 offset = xxxThreatPositionAtNearestApproach - position;
+                    float sideDot = offset.dot(side());
+                    steer = (sideDot > 0) ? -1.0f : 1.0f;
+                }
+                else
+                {
+                    if (parallelness > angle)
+                    {
+                        // parallel paths: steer away from threat
+                        bzVec2 offset = threat.position - position;
+                        float sideDot = offset.dot(side());
+                        steer = (sideDot > 0) ? -1.0f : 1.0f;
+                    }
+                    else
+                    {
+                        // perpendicular paths: steer behind threat
+                        // (only the slower of the two does this)
+                        if (threat.speed() <= speed())
+                        {
+                            float sideDot = side().dot(threat.velocity);
+                            steer = (sideDot > 0) ? -1.0f : 1.0f;
+                        }
+                    }
+                }
+            }
+
+            return side() * steer;
+        }
+
+        // Given two vehicles, based on their current positions and velocities,
+        // determine the time until nearest approach
+        float predictNearestApproachTime (Ship other) {
+
+            // imagine we are at the origin with no velocity,
+            // compute the relative velocity of the other vehicle
+            bzVec2 myVelocity = velocity;
+            bzVec2 otherVelocity = other.velocity;
+            bzVec2 relVelocity = otherVelocity - myVelocity;
+            float relSpeed = relVelocity.length;
+
+            // for parallel paths, the vehicles will always be at the same distance,
+            // so return 0 (aka "now") since "there is no time like the present"
+            if (relSpeed == 0) return 0;
+
+            // Now consider the path of the other vehicle in this relative
+            // space, a line defined by the relative position and velocity.
+            // The distance from the origin (our vehicle) to that line is
+            // the nearest approach.
+
+            // Take the unit tangent along the other vehicle's path
+            bzVec2 relTangent = relVelocity / relSpeed;
+
+            // find distance from its path to origin (compute offset from
+            // other to us, find length of projection onto path)
+            bzVec2 relPosition = position - other.position;
+            float projection = relTangent.dot(relPosition);
+
+            return projection / relSpeed;
+        }
+
+        // Given the time until nearest approach (predictNearestApproachTime)
+        // determine position of each vehicle at that time, and the distance
+        // between them
+        float computeNearestApproachPositions (Ship other, float time) {
+
+            bzVec2 myTravel =  forward *  speed * time;
+            bzVec2 otherTravel = other.forward * other.speed * time;
+
+            bzVec2 myFinal =  position + myTravel;
+            bzVec2 otherFinal = other.position + otherTravel;
+
+            // xxx for annotation
+            ourPositionAtNearestApproach = myFinal;
+            hisPositionAtNearestApproach = otherFinal;
+
+            return bzVec2::distance (myFinal, otherFinal);
+        }
+
+            // otherwise return zero
+            return bzVec2::zero;
+        }
+
+        // ------------------------------------------------------------------------
+        // pursuit of another vehicle ( version with ceiling on prediction time)
+
+        bzVec2 steerForPursuit (Ship quarry) {
+            return steerForPursuit (quarry, FLT_MAX);
+        }
+
+        bzVec2 steerForPursuit (Ship quarry, float maxPredictionTime) {
+
+            // offset from this to quarry, that distance, unit vector toward quarry
+            bzVec2 offset = quarry.position - position;
+            float distance = offset.length ();
+            bzVec2 unitOffset = offset / distance;
+
+            // how parallel are the paths of "this" and the quarry
+            // (1 means parallel, 0 is pependicular, -1 is anti-parallel)
+            float parallelness = forward.dot(quarry.forward());
+
+            // how "forward" is the direction to the quarry
+            // (1 means dead ahead, 0 is directly to the side, -1 is straight back)
+            float forwardness = forward.dot(unitOffset);
+
+            float directTravelTime = distance / speed;
+            int f = intervalComparison (forwardness,  -0.707f, 0.707f);
+            int p = intervalComparison (parallelness, -0.707f, 0.707f);
+
+            float timeFactor = 0; // to be filled in below
+            bzVec2 color;           // to be filled in below (xxx just for debugging)
+
+            // Break the pursuit into nine cases, the cross product of the
+            // quarry being [ahead, aside, or behind] us and heading
+            // [parallel, perpendicular, or anti-parallel] to us.
+            switch (f)
+            {
+            case +1:
+                switch (p)
+                {
+                case +1:          // ahead, parallel
+                    timeFactor = 4;
+                    color = gBlack;
+                    break;
+                case 0:           // ahead, perpendicular
+                    timeFactor = 1.8f;
+                    color = gGray50;
+                    break;
+                case -1:          // ahead, anti-parallel
+                    timeFactor = 0.85f;
+                    color = gWhite;
+                    break;
+                }
+                break;
+            case 0:
+                switch (p)
+                {
+                case +1:          // aside, parallel
+                    timeFactor = 1;
+                    color = gRed;
+                    break;
+                case 0:           // aside, perpendicular
+                    timeFactor = 0.8f;
+                    color = gYellow;
+                    break;
+                case -1:          // aside, anti-parallel
+                    timeFactor = 4;
+                    color = gGreen;
+                    break;
+                }
+                break;
+            case -1:
+                switch (p)
+                {
+                case +1:          // behind, parallel
+                    timeFactor = 0.5f;
+                    color= gCyan;
+                    break;
+                case 0:           // behind, perpendicular
+                    timeFactor = 2;
+                    color= gBlue;
+                    break;
+                case -1:          // behind, anti-parallel
+                    timeFactor = 2;
+                    color = gMagenta;
+                    break;
+                }
+                break;
+            }
+
+            // estimated time until intercept of quarry
+            float et = directTravelTime * timeFactor;
+
+            // xxx experiment, if kept, this limit should be an argument
+            float etl = (et > maxPredictionTime) ? maxPredictionTime : et;
+
+            // estimated position of quarry at intercept
+            bzVec2 target = quarry.predictFuturePosition (etl);
+
+            // annotation
+            annotationLine (position,
+                            target,
+                            gaudyPursuitAnnotation ? color : gGray40);
+
+            return steerForSeek (target);
+        }
+
+        // ------------------------------------------------------------------------
+        // evasion of another vehicle
+        bzVec2 steerForEvasion (Ship menace,  float maxPredictionTime)  {
+
+            // offset from this to menace, that distance, unit vector toward menace
+            bzVec2 offset = menace.position - position;
+            float distance = offset.length;
+
+            float roughTime = distance / menace.speed;
+            float predictionTime = ((roughTime > maxPredictionTime) ? maxPredictionTime : roughTime);
+            bzVec2 target = menace.predictFuturePosition (predictionTime);
+
+            return steerForFlee (target);
+        }
+
+
+        // ------------------------------------------------------------------------
+        // tries to maintain a given speed, returns a maxForce-clipped steering
+        // force along the forward/backward axis
+        bzVec2 steerForTargetSpeed (float targetSpeed) {
+            float mf = maxForce();
+            float speedError = targetSpeed - speed ();
+            return forward () * clip (speedError, -mf, +mf);
+        }
+
+
+        // ----------------------------------------------------------- utilities
+        bool isAhead (bzVec2 target) {return isAhead (target, 0.707f);};
+        bool isAside (bzVec2 target) {return isAside (target, 0.707f);};
+        bool isBehind (bzVec2 target) {return isBehind (target, -0.707f);};
+
+        bool isAhead (bzVec2 target, float cosThreshold)
+        {
+            bzVec2 targetDirection = (target - position ()).normalize ();
+            return forward().dot(targetDirection) > cosThreshold;
+        }
+
+        bool isAside (bzVec2 target, float cosThreshold)
+        {
+            bzVec2 targetDirection = (target - position ()).normalize ();
+            float dp = forward().dot(targetDirection);
+            return (dp < cosThreshold)  (dp > -cosThreshold);
+        }
+
+        bool isBehind (bzVec2 target, float cosThreshold)
+        {
+            bzVec2 targetDirection = (target - position).normalize ();
+            return forward().dot(targetDirection) < cosThreshold;
+        }
+    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,21 @@
+import "themes/default/Button.cfg"
+import "themes/default/Check.cfg"
+import "themes/default/Combo.cfg"
+import "themes/default/FramedTopLevelWindow.cfg"
+import "themes/default/Input.cfg"
+import "themes/default/Menu.cfg"
+import "themes/default/Picker.cfg"
+import "themes/default/Progressbar.cfg"
+import "themes/default/Scrollbar.cfg"
+import "themes/default/ScrollbarButton.cfg"
+import "themes/default/ScrollView.cfg"
+import "themes/default/TabButton.cfg"
+import "themes/default/TabView.cfg"
+import "themes/default/TextList.cfg"
+import "themes/default/TickBox.cfg"
+import "themes/default/WindowFrame.cfg"
+import "themes/default/WindowFrameButton.cfg"
+import "themes/default/XCheck.cfg"
+import "themes/default/Slider.cfg"
+import "themes/default/Spinner.cfg"
+import "themes/default/Angle.cfg"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Angle.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,13 @@
+widget Angle {
+	layout = Layered;
+	size = 40 40;
+	shape = Rectangle;
+
+	[hexpand hfill vexpand vfill] new GLViewport glView;
+	glView = sub(glView);
+
+	radians = false;
+	filled = true;
+	angleWidth = 2;
+	limitWidth = 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Button.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,52 @@
+// define what a Button really is...
+widget Button {
+	layout = Layered;
+	size = 30 0;
+	
+	[hfill vfill] new Graphic body {
+		size = 0 23;
+		
+		style.normal = {
+			image = grid("themes/default/img/button.png", hline(2, 73), vline(2, 21));
+			background = solid(white);
+		}
+		
+		style.active = {
+			image = grid("themes/default/img/button_active.png", hline(2, 73), vline(2, 21));
+		}
+	}
+	
+	[hfill vfill] new Graphic bodyOver {
+		style.normal = {
+			background = solid(rgba(1, 1, 1, 0));
+		}
+		
+		style.hover = {
+			background = solid(rgba(1, 1, 1, .07));
+			deactivation = .3;
+		}
+	}
+
+	[hfill vfill] new HBox {
+		layout = {
+			padding = 6 3;
+			spacing = 2;
+		}
+		
+		[hexpand vexpand] new HBox leftExtra;
+		[vexpand] new Label text {
+			style.normal = {
+				color = rgb(.8, .8, .8);
+			}
+			
+			text = "Button";
+			fontSize = 11;
+		}
+		[hexpand vexpand] new HBox rightExtra;
+	}
+	
+	leftExtra = sub(leftExtra);
+	rightExtra = sub(rightExtra);
+	label = sub(text);
+	text = prop(text.text);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Check.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,12 @@
+widget Check {
+	layout = HBox;
+	layout = {
+		spacing = 5;
+	}
+	
+	[vexpand] new TickBox tick;
+	new Label label;
+	
+	text = prop(label.text);
+	checked = prop(tick.checked);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Combo.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,39 @@
+widget Combo {
+	layout = VBox;
+	size = 100 0;
+	
+	[hexpand hfill] new HBox {
+		[hexpand hfill] new Input input {
+			size = 80 0;
+		}
+		
+		[vexpand vfill] new ScrollbarButton button {
+			arrowDir = 3;
+		}
+	}
+	
+	
+	new HBox popup {
+		[hexpand hfill vexpand vfill] new ScrollView {
+			children.useChildSize = 1;
+			
+			[hexpand hfill vexpand vfill] new VBox {
+				shape = Rectangle;
+				style.normal = {
+					background = solid(rgba(.1, .1, .1, .8));
+					border = 1 rgba(.4, .4, .4, .7);
+				}
+				layout = {
+					padding = 1 1;
+				}
+
+				[hexpand hfill vexpand vfill] new TextList textList;			
+			}
+		}
+	}
+	
+	input = sub(input);
+	popup = sub(popup);
+	button = sub(button);
+	textList = sub(popup.textList);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/FramedTopLevelWindow.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,27 @@
+widget FramedTopLevelWindow {
+	layout = Layered;
+	
+	[hfill vfill] new WindowFrame frame {
+		text = "Hybrid test app 1";
+		
+		shape = Rectangle;
+		style.normal = {
+			background = solid(rgb(.22, .22, .22));
+		}
+		
+		layout = Bin;
+		new Group clientArea {
+			shape = Rectangle;
+			style.normal = {
+				background = solid(rgb(.22, .22, .22));
+			}
+		}
+	}
+	
+	[hfill vfill] new Group overlay {
+	}
+	
+	frame = sub(frame);
+	children = sub(frame.clientArea);
+	overlay = sub(overlay);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Input.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,27 @@
+widget Input {
+	shape = Rectangle;
+	layout = Bin;
+	layout = {
+		padding = 3 3;
+	}
+	
+	style.normal = {
+		background = solid(rgba(.3, .3, .3, 1));
+		border = 1 black;
+	}
+	
+	[hexpand vexpand hfill vfill] new InputArea area {
+		style.normal = {
+			textInput = {
+				caretColor = white;
+			}
+		}
+	}
+	
+	text = prop(area.text);
+	font = prop(area.font);
+	fontFace = prop(area.fontFace);
+	fontSize = prop(area.fontSize);
+	hasFocus = prop(area.hasFocus);
+	inputArea = sub(area);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Menu.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,96 @@
+widget HMenu {
+	new HBox children {
+		layout = {
+			spacing = 8;
+			padding = 8 2;
+		}
+	}
+	
+	children = sub(children);
+}
+
+
+widget HMenuItem {
+	[hexpand hfill] new Group {
+		shape = Rectangle;
+		style.active = {
+			background = solid(rgba(1, 1, 1, .1));
+			border = 1 black;
+		}
+		layout = {
+			padding = 2 2;
+		}
+
+		new Label label {
+			fontSize = 11;
+			style.normal = {
+				color = rgb(.8, .8, .8);
+			}
+		}
+	}
+	
+	label = sub(label);
+	text = prop(label.text);
+	
+	childDir = 3;
+}
+
+
+widget VMenu {
+	[hexpand hfill vexpand vfill] new Graphic {
+		renderOversize = 8 8;
+		renderOffset = 2 2;
+		style.normal = {
+			background = solid(rgba(0, 0, 0, .5));
+			image = grid("themes/default/img/menuShadow.png", hline(14, 66), vline(14, 66));
+		}
+		
+		[hexpand hfill vexpand vfill] new Group {
+			size = 80 0;
+			shape = Rectangle;
+			style.normal = {
+				background = solid(rgb(.18, .18, .18));
+				border = 1 rgb(.25, .25, .25);
+			}
+			layout = {
+				padding = 4 3;
+			}
+			
+			[hfill hexpand] new VBox children {
+				layout = {
+					spacing = 2;
+					padding = 2 2;
+					attribs = "hexpand hfill";
+				}
+			}
+		}
+	}
+		
+	children = sub(children);
+}
+
+
+widget VMenuItem {
+	[hexpand hfill] new Group {
+		shape = Rectangle;
+		style.active = {
+			background = solid(rgba(1, 1, 1, .05));
+			border = 1 black;
+		}
+		layout = {
+			padding = 2 2;
+		}
+
+		new Label label {
+			fontSize = 11;
+			style.normal = {
+				color = rgb(.8, .8, .8);
+			}
+		}
+	}
+	
+	label = sub(label);
+	text = prop(label.text);
+
+	childDir = 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Picker.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,22 @@
+widget Picker {
+	layout = Layered;
+	
+	[hfill vfill] new Group {
+		layout = Free;
+		new Group background {
+		}
+	}
+
+	[hfill vfill] new VBox main {
+	}
+	
+	[hfill vfill] new Group {
+		layout = Free;
+		new Group foreground {
+		}
+	}
+	
+	children = sub(main);
+	background = sub(background);
+	foreground = sub(foreground);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Progressbar.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,18 @@
+widget Progressbar {
+	layout = HBox;
+	layout = {
+		padding = 2 2;
+	}
+	shape = Rectangle;
+	style.normal = {
+		background = solid(white);
+		image = grid("themes/default/img/progress.png", hline(1, 20), vline(1, 11));
+	}
+	
+	[hexpand hfill vexpand vfill] new HTiledImage img {
+		size = 0 13;
+		file = "themes/default/img/progress_box.png";
+	}
+	
+	img = sub(img);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/ScrollView.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,27 @@
+widget ScrollView {
+	layout = HBox;
+	
+	//[hexpand vexpand hfill vfill] new HBox {
+		[hexpand vexpand hfill vfill] new VBox {
+			[hexpand vexpand hfill vfill] new ClipView clipView {
+			}
+			
+			[hexpand hfill] new HScrollbar hscroll;
+		}
+		
+		[vexpand vfill] new VBox {
+			[vexpand vfill] new VScrollbar vscroll;
+			
+			new Graphic corner {
+				size = 17 17;
+			}
+		}
+	//}
+	
+	hscroll = sub(hscroll);
+	vscroll = sub(vscroll);
+	clipView = sub(clipView);
+	corner = sub(corner);
+	
+	children = sub(clipView);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Scrollbar.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,79 @@
+widget HScrollbar {
+	[hexpand hfill] new HBox {
+		shape = Rectangle;
+		style.normal = {
+			background = solid(rgba(0.26, 0.26, 0.26, 1));
+		}
+		
+		new ScrollbarButton button1 {
+			arrowDir = 2;
+		}
+		
+		[hexpand hfill] new HWidgetSlider slider {
+			new Graphic {
+				size = 17 17;
+				style.normal = {
+					image = grid("themes/default/img/scrollhandle.png", hline(3, 14), vline(3, 14));
+					background = solid(white);
+					border = 1 rgba(.1, .1, .1, .8);
+				}
+			}
+			
+			handleSize = .2;
+			skip = false;
+		}
+
+		new ScrollbarButton button2 {
+			arrowDir = 0;
+		}
+	}
+	
+	handleSize = prop(slider.handleSize);
+	position = prop(slider.position);
+	skipSize = prop(slider.skipSize);
+
+	button1 = sub(button1);
+	button2 = sub(button2);
+	slider = sub(slider);
+}
+
+
+
+widget VScrollbar {
+	[vexpand vfill] new VBox {
+		shape = Rectangle;
+		style.normal = {
+			background = solid(rgba(0.26, 0.26, 0.26, 1));
+		}
+
+		new ScrollbarButton button1 {
+			arrowDir = 1;
+		}
+		
+		[vexpand vfill] new VWidgetSlider slider {
+			new Graphic {
+				size = 17 17;
+				style.normal = {
+					image = grid("themes/default/img/scrollhandle.png", hline(3, 14), vline(3, 14));
+					background = solid(white);
+					border = 1 rgba(.1, .1, .1, .8);
+				}
+			}
+			
+			handleSize = .2;
+			skip = false;
+		}
+
+		new ScrollbarButton button2 {
+			arrowDir = 3;
+		}
+	}
+
+	handleSize = prop(slider.handleSize);
+	position = prop(slider.position);
+	skipSize = prop(slider.skipSize);
+
+	button1 = sub(button1);
+	button2 = sub(button2);
+	slider = sub(slider);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/ScrollbarButton.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,38 @@
+widget ScrollbarButton {
+	layout = Layered;
+	
+	[hfill vfill] new Graphic {
+		size = 17 17;
+		
+		style.normal = {
+			image = grid("themes/default/img/scrollbutton.png", hline(3, 14), vline(3, 14));
+			background = solid(white);
+		}
+		
+		style.active = {
+			background = solid(rgba(1, 1, 1, 0));
+		}
+	}
+
+	[hfill vfill] new Graphic {
+		size = 17 17;
+		
+		style.normal = {
+			image = grid("themes/default/img/scrollbutton_active.png", hline(3, 14), vline(3, 14));
+			background = solid(rgba(1, 1, 1, 0));
+		}
+		
+		style.active = {
+			background = solid(white);
+		}
+	}
+
+	new Icon icon {
+		addIcon = "themes/default/img/arrow0.png";
+		addIcon = "themes/default/img/arrow1.png";
+		addIcon = "themes/default/img/arrow2.png";
+		addIcon = "themes/default/img/arrow3.png";
+	}
+	
+	arrowDir = prop(icon.iconIndex);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Slider.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,49 @@
+widget HSlider {
+	shape = Rectangle;
+	style.normal = {
+		border = 1 rgb(.118, .118, .118);
+		background = hgradient(rgb(0, 0, 0), rgb(.4, .4, .4));
+	}
+	
+	[hexpand hfill] new HWidgetSlider wslider {
+		shape = Rectangle;
+		style.normal = {
+		}
+		
+		new Graphic {
+			size = 13 13;
+			style.normal = {
+				background = solid(white);
+				image = file("themes/default/img/hsliderarrow.png");
+			}
+		}
+	}
+	
+	wslider = sub(wslider);
+}
+
+
+
+widget VSlider {
+	shape = Rectangle;
+	style.normal = {
+		border = 1 rgb(.118, .118, .118);
+		background = vgradient(rgb(.4, .4, .4), rgb(0, 0, 0));
+	}
+	
+	[vexpand vfill] new VWidgetSlider wslider {
+		shape = Rectangle;
+		style.normal = {
+		}
+		
+		new Graphic {
+			size = 13 13;
+			style.normal = {
+				background = solid(white);
+				image = file("themes/default/img/vsliderarrow.png");
+			}
+		}
+	}
+	
+	wslider = sub(wslider);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/Spinner.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,80 @@
+widget Spinner {
+	new VBox {
+		new SpinnerButton button2 {
+			icon.iconIndex = 0;
+		}
+
+		new SpinnerButton button1 {
+			icon.iconIndex = 1;
+		}
+	}
+	
+	button1 = sub(button1);
+	button2 = sub(button2);
+}
+
+
+widget SpinnerButton {
+	layout = Layered;
+	
+	[hfill vfill] new Icon icon {
+		addIcon = "themes/default/img/spinner_up.png";
+		addIcon = "themes/default/img/spinner_down.png";
+	}
+	
+	[hfill vfill] new Graphic {
+		style.normal = {
+			background = solid(rgba(1, 1, 1, 0));
+		}
+		style.hover = {
+			background = solid(rgba(1, 1, 1, .2));
+			deactivation = .3;
+		}
+		style.active = {
+			background = solid(rgba(1, 1, 1, .4));
+		}
+	}
+	
+	icon = sub(icon);
+}
+
+
+widget InputSpinner {
+	size = 60 0;
+	shape = Rectangle;
+	style.normal = {
+		border = 1 black;
+		background = solid(rgb(.2, .21, .24));
+	}
+	layout = HBox;
+	layout = {
+		spacing = 2;
+	}
+	
+	[hexpand hfill vexpand hfill] new Group {
+		layout = {
+			padding = 2 2;
+		}
+		
+		[hexpand hfill vexpand] new InputArea input {
+			shape = Rectangle;
+			style.normal = {
+				background = solid(black);
+				textInput = {
+					caretColor = rgb(.6, .7, 1);
+				}
+			}
+			
+			fontSize = 11;
+			text = "0";
+		}
+	}
+	new Spinner spinner;
+	
+	
+	spinner = sub(spinner);
+	input = sub(input);
+	value = prop(spinner.value);
+
+	value = 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/TabButton.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,50 @@
+widget TabButton {
+	layout = HBox;
+	layout = {
+		spacing = -1;
+	}
+	size = 80 0;
+	
+	[vexpand vfill hexpand hfill] new Group {
+		layout = Layered;
+		
+		[hfill vfill] new Graphic {
+			size = 0 22;
+			style.normal = {
+				background = solid(white);
+				image = grid("themes/default/img/tbw.png", hline(1, 47), vline(3, 19));
+			}
+			style.active = {
+				image = grid("themes/default/img/tbw_active.png", hline(1, 72), vline(1, 21));
+			}
+			style.hover = {
+				image = grid("themes/default/img/tbw_hover.png", hline(1, 51), vline(3, 19));
+			}
+		}
+
+		[hfill vfill] new HBox {
+			layout = {
+				padding = 6 3;
+				spacing = 2;
+			}
+			
+			[hexpand vexpand] new HBox leftExtra;
+			[vexpand] new Label label {
+				style.normal = {
+					color = rgb(.6, .6, .6);
+				}
+				style.active = {
+					color = rgb(.8, .8, .8);
+				}
+			}
+			[hexpand vexpand] new HBox rightExtra;
+		}	
+	}
+	
+	leftExtra = sub(leftExtra);
+	rightExtra = sub(rightExtra);
+	label = sub(label);
+	text = prop(label.text);
+
+	text = "Tab";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/TabView.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,26 @@
+widget TabView {
+	layout = VBox;
+	layout = {
+		spacing = -2;
+	}
+
+	new HBox tabList {
+		layout = {
+			spacing = -1;
+		}
+	}
+	
+	[hexpand hfill vexpand vfill] new Group clientArea {
+		shape = Rectangle;
+		style.normal = {
+			background = solid(rgb(.255, .255, .255));
+			border = 1 rgb(.118, .118, .118);
+		}
+		layout = {
+			padding = 5 5;
+		}
+	}
+	
+	tabList = sub(tabList);
+	clientArea = sub(clientArea);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/TextList.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,19 @@
+widget TextList {
+	[hexpand hfill] new Picker picker {
+	} @background {
+		[hexpand vexpand hfill vfill] new Graphic {
+			style.normal = {
+				background = solid(rgba(1, 1, 1, .4));
+			}
+		}
+	} @foreground {
+		[hexpand vexpand hfill vfill] new Graphic {
+			style.normal = {
+				background = solid(rgba(0, 0, 0, .2));
+			}
+		}
+	}
+	
+	picker = sub(picker);
+	pickedIdx = prop(picker.pickedIdx);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/TickBox.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,37 @@
+widget TickBox {
+	layout = Layered;
+	
+	[hfill vfill] new Graphic {
+		size = 13 13;
+
+		style.normal = {
+			background = solid(white);
+			image = file("themes/default/img/tick.png");
+		}
+	}
+
+	[hfill vfill] new Graphic {
+		style.normal = {
+			background = solid(rgba(1, 1, 1, 0));
+			image = file("themes/default/img/tick_active.png");
+		}
+
+		style.active = {
+			background = solid(white);
+			deactivation = .2;
+		}
+	}
+
+	[hfill vfill] new Graphic {
+		style.normal = {
+			background = solid(rgba(1, 1, 1, 0));
+			image = file("themes/default/img/tick_hover.png");
+		}
+
+		style.hover = {
+			background = solid(white);
+			activation = .1;
+			deactivation = .7;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/WindowFrame.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,50 @@
+widget WindowFrame {
+	layout = VBox;
+	shape = Rectangle;
+	style.normal = {
+		border = 1 black;
+	}
+	
+	[hexpand hfill] new HBox handle {
+		size = 0 23;
+		shape = Rectangle;
+		layout = {
+			padding = 5 0;
+			spacing = 4;
+		}
+		
+		style.normal = {
+			border = 1 black;
+			background = solid(rgb(.6, .6, .6));
+			image = grid("themes/default/img/winframe_bg.png", hline(1, 9), vline(0, 23));
+		}
+		style.active = {
+			background = solid(white);
+		}
+		
+		[hexpand vexpand] new Label caption {
+		}
+		
+		[vexpand] new WindowFrameButton minimizeButton {
+			addIcon = "themes/default/img/winframe_minimize.png";
+		}
+		[vexpand] new WindowFrameButton maximizeButton {
+			addIcon = "themes/default/img/winframe_maximize.png";
+			addIcon = "themes/default/img/winframe_restore.png";
+		}
+		[vexpand] new WindowFrameButton closeButton {
+			addIcon = "themes/default/img/winframe_close.png";
+		}
+	}
+	
+	[hexpand hfill vexpand vfill] new VBox clientArea {
+	}
+	
+	children = sub(clientArea);
+	handle = sub(handle);
+	
+	minimizeClicked = prop(handle.minimizeButton.clicked);
+	maximizeClicked = prop(handle.maximizeButton.clicked);
+	closeClicked = prop(handle.closeButton.clicked);
+	text = prop(handle.caption.text);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/WindowFrameButton.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,20 @@
+widget WindowFrameButton {
+	layout = Layered;
+	
+	new Icon icon {
+	}
+	
+	[hfill vfill] new Graphic hlight {
+		style.normal = {
+			background = solid(rgba(1, 1, 1, 0));
+		}
+
+		style.active = {
+			background = solid(rgba(1, 1, 1, .2));
+		}
+	}
+	
+	addIcon = prop(icon.addIcon);
+	icon = sub(icon);
+	hlight = sub(hlight);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/default/XCheck.cfg	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,47 @@
+widget XCheck {
+	layout = HBox;
+	layout = {
+		spacing = 5;
+	}
+	
+	[vexpand] new Group tick {
+		layout = Layered;
+		
+		[hfill vfill] new Graphic {
+			size = 13 13;
+
+			style.normal = {
+				background = solid(white);
+				image = file("themes/default/img/radio.png");
+			}
+		}
+
+		[hfill vfill] new Graphic {
+			style.normal = {
+				background = solid(rgba(1, 1, 1, 0));
+				image = file("themes/default/img/radio_active.png");
+			}
+
+			style.active = {
+				background = solid(white);
+				deactivation = .2;
+			}
+		}
+
+		[hfill vfill] new Graphic {
+			style.normal = {
+				background = solid(rgba(1, 1, 1, 0));
+				image = file("themes/default/img/radio_hover.png");
+			}
+
+			style.hover = {
+				background = solid(white);
+				activation = .5;
+				deactivation = .7;
+			}
+		}
+	}
+	new Label label;
+	
+	text = prop(label.text);
+}
Binary file themes/default/img/Thumbs.db has changed
Binary file themes/default/img/arrow0.png has changed
Binary file themes/default/img/arrow1.png has changed
Binary file themes/default/img/arrow2.png has changed
Binary file themes/default/img/arrow3.png has changed
Binary file themes/default/img/button.png has changed
Binary file themes/default/img/button_active.png has changed
Binary file themes/default/img/hsliderarrow.png has changed
Binary file themes/default/img/menuShadow.png has changed
Binary file themes/default/img/progress.png has changed
Binary file themes/default/img/progress_box.png has changed
Binary file themes/default/img/radio.png has changed
Binary file themes/default/img/radio_active.png has changed
Binary file themes/default/img/radio_hover.png has changed
Binary file themes/default/img/scrollbutton.png has changed
Binary file themes/default/img/scrollbutton_active.png has changed
Binary file themes/default/img/scrollhandle.png has changed
Binary file themes/default/img/spinner_down.png has changed
Binary file themes/default/img/spinner_up.png has changed
Binary file themes/default/img/tbw.png has changed
Binary file themes/default/img/tbw_active.png has changed
Binary file themes/default/img/tbw_hover.png has changed
Binary file themes/default/img/tick.png has changed
Binary file themes/default/img/tick_active.png has changed
Binary file themes/default/img/tick_hover.png has changed
Binary file themes/default/img/vsliderarrow.png has changed
Binary file themes/default/img/winframe_bg.png has changed
Binary file themes/default/img/winframe_close.png has changed
Binary file themes/default/img/winframe_maximize.png has changed
Binary file themes/default/img/winframe_minimize.png has changed
Binary file themes/default/img/winframe_restore.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/urQuan.d	Fri Mar 20 06:41:25 2009 -0400
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2009, Mason Green (zzzzrrr)
+ * 
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * * Neither the name of the polygonal nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without specific
+ *   prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+module melee.urQuan;
+
+import blaze.dynamics.bzBody : bzBody;
+import blaze.bzWorld: bzWorld;
+import blaze.dynamics.bzBodyDef;
+import blaze.collision.shapes.bzPolygon : bzPolyDef;
+import blaze.common.bzMath: bzVec2;
+
+import melee.ship;
+
+// UrQuan Dreadnought
+class UrQuan : Ship
+{
+
+    float scale = 0.01;
+
+    this(bzWorld world) {
+
+        auto bodyDef = new bzBodyDef;
+        bodyDef.position = bzVec2(10,5);
+        super.rBody = world.createBody(bodyDef);
+        float density = 2.0f;
+
+        // Head
+        auto head = new bzPolyDef;
+        head.vertices.length = 8;
+        head.vertices[0] = bzVec2(42,49) * scale;
+        head.vertices[7] = bzVec2(63,49) * scale;
+        head.vertices[6] = bzVec2(70,45.5) * scale;
+        head.vertices[5] = bzVec2(73.5,38.5) * scale;
+        head.vertices[4] = bzVec2(73.5,-42) * scale;
+        head.vertices[3] = bzVec2(70,-49) * scale;
+        head.vertices[2] = bzVec2(63,-56) * scale;
+        head.vertices[1] = bzVec2(42,-56) * scale;
+        super.shapes.add(rBody.createShape(head));
+
+        // Body
+        auto b = new bzPolyDef(density);
+        b.vertices.length = 4;
+        b.vertices[0] = bzVec2(-70,-28) * scale;
+        b.vertices[3] = bzVec2(-70,24.5) * scale;
+        b.vertices[2] = bzVec2(42,24.5) * scale;
+        b.vertices[1] = bzVec2(42,-31.5) * scale;
+        super.shapes.add(rBody.createShape(b));
+
+        // Top Strut
+        auto tStrut = new bzPolyDef(density);
+        tStrut.vertices.length = 4;
+        tStrut.vertices[0] = bzVec2(0,24.5) * scale;
+        tStrut.vertices[3] = bzVec2(-28,24.5) * scale;
+        tStrut.vertices[2] = bzVec2(-28,42) * scale;
+        tStrut.vertices[1] = bzVec2(0,42) * scale;
+        super.shapes.add(rBody.createShape(tStrut));
+
+        // Top Wing
+        auto tWing = new bzPolyDef(density);
+        tWing.vertices.length = 4;
+        tWing.vertices[0] = bzVec2(-70,42) * scale;
+        tWing.vertices[3] = bzVec2(-49,63) * scale;
+        tWing.vertices[2] = bzVec2(28,63) * scale;
+        tWing.vertices[1] = bzVec2(28,42) * scale;
+        super.shapes.add(rBody.createShape(tWing));
+
+        // Bottom Strut
+        auto bStrut = new bzPolyDef(density);
+        bStrut.vertices.length = 4;
+        bStrut.vertices[0] = bzVec2(0,-31.5) * scale;
+        bStrut.vertices[3] = bzVec2(0,-49) * scale;
+        bStrut.vertices[2] = bzVec2(-28,-49) * scale;
+        bStrut.vertices[1] = bzVec2(-28,-31.5) * scale;
+        super.shapes.add(rBody.createShape(bStrut));
+
+        // Bottom Wing
+        auto bWing = new bzPolyDef(density);
+        bWing.vertices.length = 4;
+        bWing.vertices[0] = bzVec2(-70,-49) * scale;
+        bWing.vertices[3] = bzVec2(28,-49) * scale;
+        bWing.vertices[2] = bzVec2(28,-70) * scale;
+        bWing.vertices[1] = bzVec2(-42,-70) * scale;
+        super.shapes.add(rBody.createShape(bWing));
+
+        super.rBody.setMassFromShapes();
+    }
+}
Binary file verdana.ttf has changed