From 13e8bc70ff1f59fc4bcd5b49fcd9f10bd673b366 Mon Sep 17 00:00:00 2001
From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com>
Date: Fri, 28 May 2021 16:58:28 +0530
Subject: [PATCH] add files
---
.gitignore | 5 +
compile.hxml | 8 +
index.html | 22 ++
src/CameraController.hx | 43 ++
src/DifBuilder.hx | 252 ++++++++++++
src/InteriorGeometry.hx | 20 +
src/Main.hx | 89 +++++
src/Marble.hx | 406 +++++++++++++++++++
src/collision/Collision.hx | 571 +++++++++++++++++++++++++++
src/collision/CollisionEntity.hx | 222 +++++++++++
src/collision/CollisionInfo.hx | 15 +
src/collision/CollisionPacket.hx | 67 ++++
src/collision/CollisionSurface.hx | 63 +++
src/collision/CollisionWorld.hx | 30 ++
src/dif/AISpecialNode.hx | 27 ++
src/dif/AnimatedLight.hx | 34 ++
src/dif/BSPNode.hx | 107 +++++
src/dif/BSPSolidLeaf.hx | 24 ++
src/dif/ConvexHull.hx | 86 ++++
src/dif/CoordBin.hx | 28 ++
src/dif/Dif.hx | 149 +++++++
src/dif/Edge.hx | 31 ++
src/dif/Edge2.hx | 48 +++
src/dif/FFSurface.hx | 39 ++
src/dif/ForceField.hx | 63 +++
src/dif/GameEntity.hx | 42 ++
src/dif/Interior.hx | 328 +++++++++++++++
src/dif/InteriorPathFollower.hx | 57 +++
src/dif/LightMap.hx | 41 ++
src/dif/LightState.hx | 37 ++
src/dif/NullSurface.hx | 43 ++
src/dif/Plane.hx | 24 ++
src/dif/Polyhedron.hx | 36 ++
src/dif/PolyhedronEdge.hx | 30 ++
src/dif/Portal.hx | 33 ++
src/dif/ReaderExtensions.hx | 91 +++++
src/dif/StateData.hx | 28 ++
src/dif/Surface.hx | 149 +++++++
src/dif/TexGenEQ.hx | 28 ++
src/dif/TexMatrix.hx | 32 ++
src/dif/Trigger.hx | 44 +++
src/dif/VehicleCollision.hx | 67 ++++
src/dif/Version.hx | 14 +
src/dif/WayPoint.hx | 32 ++
src/dif/WindingIndex.hx | 24 ++
src/dif/WriterExtensions.hx | 49 +++
src/dif/Zone.hx | 47 +++
src/dif/io/BytesReader.hx | 60 +++
src/dif/io/BytesWriter.hx | 50 +++
src/dif/math/Box3F.hx | 104 +++++
src/dif/math/PlaneF.hx | 84 ++++
src/dif/math/Point2F.hx | 15 +
src/dif/math/Point3F.hx | 94 +++++
src/dif/math/Point4F.hx | 35 ++
src/dif/math/QuatF.hx | 35 ++
src/dif/math/SphereF.hx | 35 ++
src/octree/IOctreeElement.hx | 8 +
src/octree/IOctreeObject.hx | 9 +
src/octree/Octree.hx | 205 ++++++++++
src/octree/OctreeIntersection.hx | 11 +
src/octree/OctreeNode.hx | 221 +++++++++++
src/octree/PriorityQueue.hx | 57 +++
src/octree/PriorityQueueNode.hx | 13 +
src/octreenarrowphase/IOctreeNode.hx | 7 +
src/octreenarrowphase/Octree.hx | 84 ++++
src/octreenarrowphase/OctreeNode.hx | 168 ++++++++
src/octreenarrowphase/OctreePoint.hx | 21 +
67 files changed, 5041 insertions(+)
create mode 100644 .gitignore
create mode 100644 compile.hxml
create mode 100644 index.html
create mode 100644 src/CameraController.hx
create mode 100644 src/DifBuilder.hx
create mode 100644 src/InteriorGeometry.hx
create mode 100644 src/Main.hx
create mode 100644 src/Marble.hx
create mode 100644 src/collision/Collision.hx
create mode 100644 src/collision/CollisionEntity.hx
create mode 100644 src/collision/CollisionInfo.hx
create mode 100644 src/collision/CollisionPacket.hx
create mode 100644 src/collision/CollisionSurface.hx
create mode 100644 src/collision/CollisionWorld.hx
create mode 100644 src/dif/AISpecialNode.hx
create mode 100644 src/dif/AnimatedLight.hx
create mode 100644 src/dif/BSPNode.hx
create mode 100644 src/dif/BSPSolidLeaf.hx
create mode 100644 src/dif/ConvexHull.hx
create mode 100644 src/dif/CoordBin.hx
create mode 100644 src/dif/Dif.hx
create mode 100644 src/dif/Edge.hx
create mode 100644 src/dif/Edge2.hx
create mode 100644 src/dif/FFSurface.hx
create mode 100644 src/dif/ForceField.hx
create mode 100644 src/dif/GameEntity.hx
create mode 100644 src/dif/Interior.hx
create mode 100644 src/dif/InteriorPathFollower.hx
create mode 100644 src/dif/LightMap.hx
create mode 100644 src/dif/LightState.hx
create mode 100644 src/dif/NullSurface.hx
create mode 100644 src/dif/Plane.hx
create mode 100644 src/dif/Polyhedron.hx
create mode 100644 src/dif/PolyhedronEdge.hx
create mode 100644 src/dif/Portal.hx
create mode 100644 src/dif/ReaderExtensions.hx
create mode 100644 src/dif/StateData.hx
create mode 100644 src/dif/Surface.hx
create mode 100644 src/dif/TexGenEQ.hx
create mode 100644 src/dif/TexMatrix.hx
create mode 100644 src/dif/Trigger.hx
create mode 100644 src/dif/VehicleCollision.hx
create mode 100644 src/dif/Version.hx
create mode 100644 src/dif/WayPoint.hx
create mode 100644 src/dif/WindingIndex.hx
create mode 100644 src/dif/WriterExtensions.hx
create mode 100644 src/dif/Zone.hx
create mode 100644 src/dif/io/BytesReader.hx
create mode 100644 src/dif/io/BytesWriter.hx
create mode 100644 src/dif/math/Box3F.hx
create mode 100644 src/dif/math/PlaneF.hx
create mode 100644 src/dif/math/Point2F.hx
create mode 100644 src/dif/math/Point3F.hx
create mode 100644 src/dif/math/Point4F.hx
create mode 100644 src/dif/math/QuatF.hx
create mode 100644 src/dif/math/SphereF.hx
create mode 100644 src/octree/IOctreeElement.hx
create mode 100644 src/octree/IOctreeObject.hx
create mode 100644 src/octree/Octree.hx
create mode 100644 src/octree/OctreeIntersection.hx
create mode 100644 src/octree/OctreeNode.hx
create mode 100644 src/octree/PriorityQueue.hx
create mode 100644 src/octree/PriorityQueueNode.hx
create mode 100644 src/octreenarrowphase/IOctreeNode.hx
create mode 100644 src/octreenarrowphase/Octree.hx
create mode 100644 src/octreenarrowphase/OctreeNode.hx
create mode 100644 src/octreenarrowphase/OctreePoint.hx
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..2b062af4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+interiors
+*.hl
+*.js
+*.js.map
+.vscode
\ No newline at end of file
diff --git a/compile.hxml b/compile.hxml
new file mode 100644
index 00000000..209b8e69
--- /dev/null
+++ b/compile.hxml
@@ -0,0 +1,8 @@
+-cp src
+-lib headbutt
+-lib heaps
+-lib hlsdl
+-lib polygonal-ds
+-hl marblegame.hl
+--main Main
+-debug
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..09ad9e75
--- /dev/null
+++ b/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+ Hello Heaps
+
+
+
+
+
+
+
diff --git a/src/CameraController.hx b/src/CameraController.hx
new file mode 100644
index 00000000..acc19d4e
--- /dev/null
+++ b/src/CameraController.hx
@@ -0,0 +1,43 @@
+import h3d.Vector;
+import hxsl.Types.Matrix;
+import h3d.scene.Scene;
+
+enum CameraMode {
+ FreeOrbit;
+ FixedOrbit;
+}
+
+class CameraController {
+ var scene:Scene;
+
+ var view:Matrix;
+ var projection:Matrix;
+
+ public var Position:Vector;
+ public var Direction:Vector;
+ public var Up:Vector;
+ public var LookAtPoint:Vector;
+ public var Mode = CameraMode.FixedOrbit;
+ public var CameraSensitivity = 0.6;
+ public var CameraPanSensitivity = 0.05;
+ public var CameraZoomSensitivity = 0.7;
+ public var CameraZoomSpeed = 15.0;
+ public var CameraZoomDeceleration = 250.0;
+ public var CameraSpeed = 15.0;
+
+ var camZoomSpeed:Float;
+
+ public var CameraDistance = 5;
+ public var CameraMinDistance = 1;
+ public var CameraMaxDistance = 25;
+ public var CameraPitch:Float;
+ public var CameraYaw:Float;
+
+ public function new(scene:Scene) {
+ this.scene = scene;
+ }
+
+ function createLookAt() {
+ this.view = Matrix.lookAtX(LookAtPoint.sub(Position), Up);
+ }
+}
diff --git a/src/DifBuilder.hx b/src/DifBuilder.hx
new file mode 100644
index 00000000..27384165
--- /dev/null
+++ b/src/DifBuilder.hx
@@ -0,0 +1,252 @@
+package src;
+
+import h3d.Vector;
+import collision.CollisionSurface;
+import collision.CollisionEntity;
+import h3d.mat.Data.Wrap;
+import hxd.fs.FileSystem;
+import hxd.res.Loader;
+import hxd.res.Image;
+import h3d.mat.Texture;
+import haxe.io.Path;
+import hxd.File;
+import h3d.scene.Mesh;
+import h3d.prim.UV;
+import h3d.col.Point;
+import h3d.prim.Polygon;
+import dif.math.Point2F;
+import dif.math.Point3F;
+import h3d.prim.BigPrimitive;
+import dif.Interior;
+import dif.Dif;
+import src.InteriorGeometry;
+
+class DifBuilderTriangle {
+ public var texture:String;
+ public var normal1:Point3F;
+ public var normal2:Point3F;
+ public var normal3:Point3F;
+ public var p1:Point3F;
+ public var p2:Point3F;
+ public var p3:Point3F;
+ public var uv1:Point2F;
+ public var uv2:Point2F;
+ public var uv3:Point2F;
+
+ public function new() {}
+}
+
+class DifBuilder {
+ public static function loadDif(path:String, loader:Loader) {
+ var dif = Dif.Load(path);
+
+ var geo = dif.interiors[0];
+
+ var hulls = geo.convexHulls;
+
+ var triangles = [];
+ var textures = [];
+
+ var collider = new CollisionEntity();
+
+ for (i in 0...hulls.length) {
+ var hullTris = [];
+ var hull = hulls[i];
+
+ for (j in hull.surfaceStart...(hull.surfaceStart + hull.surfaceCount)) {
+ var surfaceindex = geo.hullSurfaceIndices[j];
+ var surface = geo.surfaces[surfaceindex];
+ var planeindex = surface.planeIndex;
+
+ var planeFlipped = (planeindex & 0x8000) == 0x8000;
+ if (planeFlipped)
+ planeindex &= ~0x8000;
+
+ var plane = geo.planes[planeindex];
+ var normal = geo.normals[plane.normalIndex];
+
+ if (planeFlipped)
+ normal = normal.scalar(-1);
+
+ var texture = geo.materialList[surface.textureIndex];
+ if (!textures.contains(texture))
+ textures.push(texture);
+
+ var points = geo.points;
+
+ var colliderSurface = new CollisionSurface();
+ colliderSurface.points = [];
+ colliderSurface.normals = [];
+ colliderSurface.indices = [];
+
+ for (k in (surface.windingStart + 2)...(surface.windingStart + surface.windingCount)) {
+ var p1, p2, p3;
+ if ((k - (surface.windingStart + 2)) % 2 == 0) {
+ p1 = points[geo.windings[k]];
+ p2 = points[geo.windings[k - 1]];
+ p3 = points[geo.windings[k - 2]];
+ } else {
+ p1 = points[geo.windings[k - 2]];
+ p2 = points[geo.windings[k - 1]];
+ p3 = points[geo.windings[k]];
+ }
+
+ var texgen = geo.texGenEQs[surface.texGenIndex];
+
+ var uv1 = new Point2F(p1.x * texgen.planeX.x
+ + p1.y * texgen.planeX.y
+ + p1.z * texgen.planeX.z
+ + texgen.planeX.d,
+ p1.x * texgen.planeY.x
+ + p1.y * texgen.planeY.y
+ + p1.z * texgen.planeY.z
+ + texgen.planeY.d);
+ var uv2 = new Point2F(p2.x * texgen.planeX.x
+ + p2.y * texgen.planeX.y
+ + p2.z * texgen.planeX.z
+ + texgen.planeX.d,
+ p2.x * texgen.planeY.x
+ + p2.y * texgen.planeY.y
+ + p2.z * texgen.planeY.z
+ + texgen.planeY.d);
+ var uv3 = new Point2F(p3.x * texgen.planeX.x
+ + p3.y * texgen.planeX.y
+ + p3.z * texgen.planeX.z
+ + texgen.planeX.d,
+ p3.x * texgen.planeY.x
+ + p3.y * texgen.planeY.y
+ + p3.z * texgen.planeY.z
+ + texgen.planeY.d);
+
+ var tri = new DifBuilderTriangle();
+ tri.texture = texture;
+ tri.normal1 = normal;
+ tri.normal2 = normal;
+ tri.normal3 = normal;
+ tri.p1 = p1;
+ tri.p2 = p2;
+ tri.p3 = p3;
+ tri.uv1 = uv1;
+ tri.uv2 = uv2;
+ tri.uv3 = uv3;
+ triangles.push(tri);
+ hullTris.push(tri);
+
+ colliderSurface.points.push(new Vector(-p1.x, p1.y, p1.z));
+ colliderSurface.points.push(new Vector(-p2.x, p2.y, p2.z));
+ colliderSurface.points.push(new Vector(-p3.x, p3.y, p3.z));
+ colliderSurface.normals.push(new Vector(-normal.x, normal.y, normal.z));
+ colliderSurface.normals.push(new Vector(-normal.x, normal.y, normal.z));
+ colliderSurface.normals.push(new Vector(-normal.x, normal.y, normal.z));
+ colliderSurface.indices.push(colliderSurface.indices.length);
+ colliderSurface.indices.push(colliderSurface.indices.length);
+ colliderSurface.indices.push(colliderSurface.indices.length);
+ }
+
+ colliderSurface.generateBoundingBox();
+ collider.addSurface(colliderSurface);
+ }
+ }
+
+ var mats = new Map>();
+
+ for (index => value in triangles) {
+ if (mats.exists(value.texture)) {
+ mats[value.texture].push(value);
+ } else {
+ mats.set(value.texture, [value]);
+ }
+ }
+
+ collider.generateBoundingBox();
+ var ig = new InteriorGeometry();
+ ig.collider = collider;
+
+ function canFindTex(tex:String) {
+ if (tex.indexOf('/') != -1) {
+ tex = tex.split('/')[1];
+ }
+
+ if (File.exists(Path.directory(path) + "/" + tex + ".jpg")) {
+ return true;
+ }
+ if (File.exists(Path.directory(path) + "/" + tex + ".png")) {
+ return true;
+ }
+ var prevDir = Path.directory(Path.directory(path));
+
+ if (File.exists(prevDir + "/" + tex + ".jpg")) {
+ return true;
+ }
+ if (File.exists(prevDir + "/" + tex + ".png")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function tex(tex:String):String {
+ if (tex.indexOf('/') != -1) {
+ tex = tex.split('/')[1];
+ }
+
+ if (File.exists(Path.directory(path) + "/" + tex + ".jpg")) {
+ return Path.directory(path) + "/" + tex + ".jpg";
+ }
+ if (File.exists(Path.directory(path) + "/" + tex + ".png")) {
+ return Path.directory(path) + "/" + tex + ".png";
+ }
+
+ var prevDir = Path.directory(Path.directory(path));
+
+ if (File.exists(prevDir + "/" + tex + ".jpg")) {
+ return prevDir + "/" + tex + ".jpg";
+ }
+ if (File.exists(prevDir + "/" + tex + ".png")) {
+ return prevDir + "/" + tex + ".png";
+ }
+
+ return null;
+ }
+
+ for (grp => tris in mats) {
+ var points = [];
+ var normals = [];
+ var uvs = [];
+
+ for (tri in tris) {
+ var p1 = new Point(-tri.p1.x, tri.p1.y, tri.p1.z);
+ var p2 = new Point(-tri.p2.x, tri.p2.y, tri.p2.z);
+ var p3 = new Point(-tri.p3.x, tri.p3.y, tri.p3.z);
+ var n1 = new Point(-tri.normal1.x, tri.normal1.y, tri.normal1.z);
+ var n2 = new Point(-tri.normal2.x, tri.normal2.y, tri.normal2.z);
+ var n3 = new Point(-tri.normal3.x, tri.normal3.y, tri.normal3.z);
+ var uv1 = new UV(tri.uv1.x, tri.uv1.y);
+ var uv2 = new UV(tri.uv2.x, tri.uv2.y);
+ var uv3 = new UV(tri.uv3.x, tri.uv3.y);
+ points.push(p3);
+ points.push(p2);
+ points.push(p1);
+ normals.push(n3);
+ normals.push(n2);
+ normals.push(n1);
+ uvs.push(uv3);
+ uvs.push(uv2);
+ uvs.push(uv1);
+ }
+
+ var prim = new Polygon(points);
+ prim.uvs = uvs;
+ prim.normals = normals;
+
+ var texture:Texture = loader.load(tex(grp)).toImage().toTexture();
+ texture.wrap = Wrap.Repeat;
+ var material = h3d.mat.Material.create(texture);
+ // material.mainPass.wireframe = true;
+
+ var mesh = new Mesh(prim, material, ig);
+ }
+
+ return ig;
+ }
+}
diff --git a/src/InteriorGeometry.hx b/src/InteriorGeometry.hx
new file mode 100644
index 00000000..583fbabc
--- /dev/null
+++ b/src/InteriorGeometry.hx
@@ -0,0 +1,20 @@
+package src;
+
+import collision.CollisionEntity;
+import headbutt.threed.Headbutt;
+import headbutt.threed.shapes.Sphere;
+import glm.Vec3;
+import dif.math.Point3F;
+import h3d.scene.Mesh;
+import h3d.col.Bounds;
+import h3d.scene.RenderContext;
+import h3d.prim.Polygon;
+import h3d.scene.Object;
+
+class InteriorGeometry extends Object {
+ public var collider:CollisionEntity;
+
+ public function new() {
+ super();
+ }
+}
diff --git a/src/Main.hx b/src/Main.hx
new file mode 100644
index 00000000..7bd5a43d
--- /dev/null
+++ b/src/Main.hx
@@ -0,0 +1,89 @@
+package;
+
+import collision.CollisionWorld;
+import src.Marble;
+import hxd.res.Loader;
+import hxd.fs.LocalFileSystem;
+import hxd.fs.FileSystem;
+import src.DifBuilder;
+import h3d.Vector;
+import h3d.scene.fwd.DirLight;
+import h3d.mat.Material;
+import h3d.prim.Cube;
+import h3d.scene.*;
+
+class Main extends hxd.App {
+ var scene:Scene;
+ var fileSystem:FileSystem;
+
+ var marble:Marble;
+ var collisionWorld:CollisionWorld;
+
+ override function init() {
+ super.init();
+
+ this.fileSystem = new LocalFileSystem(".", null);
+
+ var loader = new Loader(fileSystem);
+
+ var cube = new Cube();
+ cube.addUVs();
+ cube.addNormals();
+
+ this.collisionWorld = new CollisionWorld();
+
+ var db = DifBuilder.loadDif("interiors/beginner/beginner_finish.dif", loader);
+ collisionWorld.addEntity(db.collider);
+
+ var mat = Material.create();
+ var difbounds = new CustomObject(cube, mat, s3d);
+ var bound = db.collider.boundingBox;
+ var oct = collisionWorld.octree;
+ difbounds.setPosition(oct.root.min.x, oct.root.min.y, oct.root.min.z);
+
+ var difbounds2 = new CustomObject(cube, mat, s3d);
+ difbounds2.setPosition(oct.root.min.x + oct.root.size, oct.root.min.y + oct.root.size, oct.root.min.z + oct.root.size);
+
+ var difbounds3 = new CustomObject(cube, mat, s3d);
+ difbounds3.setPosition(bound.xMin, bound.yMin, bound.zMin);
+ var difbounds4 = new CustomObject(cube, mat, s3d);
+ difbounds4.setPosition(bound.xMax, bound.yMax, bound.zMax);
+
+ // for (surf in db.collider.surfaces) {
+ // var surfmin = new CustomObject(cube, mat, s3d);
+ // var bound = surf.boundingBox;
+ // surfmin.setPosition(bound.xMin, bound.yMin, bound.zMin);
+
+ // var surfmax = new CustomObject(cube, mat, s3d);
+ // surfmax.setPosition(bound.xMax, bound.yMax, bound.zMax);
+ // }
+
+ s3d.addChild(db);
+
+ // var mat = Material.create();
+ // var so = new CustomObject(cube, mat);
+ // so.setPosition(0, 0, 0);
+ // s3d.addChild(so);
+
+ var dirlight = new DirLight(new Vector(0.5, 0.5, -0.5), s3d);
+ dirlight.enableSpecular = true;
+ s3d.lightSystem.ambientLight.set(0.3, 0.3, 0.3);
+
+ // s3d.camera.
+
+ marble = new Marble();
+ s3d.addChild(marble);
+ marble.setPosition(0, 0, 5);
+ // marble.setPosition(-10, -5, 5);
+ s3d.addChild(marble.camera);
+ }
+
+ override function update(dt:Float) {
+ super.update(dt);
+ marble.update(dt, this.collisionWorld);
+ }
+
+ static function main() {
+ new Main();
+ }
+}
diff --git a/src/Marble.hx b/src/Marble.hx
new file mode 100644
index 00000000..eadd78ac
--- /dev/null
+++ b/src/Marble.hx
@@ -0,0 +1,406 @@
+package src;
+
+import hxd.Key;
+import collision.CollisionInfo;
+import h3d.Matrix;
+import collision.CollisionWorld;
+import h3d.col.ObjectCollider;
+import h3d.col.Collider.GroupCollider;
+import h3d.Vector;
+import h3d.scene.CameraController;
+import h3d.mat.Material;
+import h3d.scene.CustomObject;
+import h3d.prim.Sphere;
+import h3d.scene.Object;
+
+class Move {
+ public var d:Vector;
+ public var jump:Bool;
+ public var powerup:Bool;
+
+ public function new() {}
+}
+
+class Marble extends Object {
+ public var camera:CameraController;
+
+ var velocity:Vector;
+ var omega:Vector;
+
+ var _radius = 0.2;
+ var _maxRollVelocity = 15;
+ var _angularAcceleration = 75;
+ var _jumpImpulse = 7.5;
+ var _kineticFriction = 0.7;
+ var _staticFriction = 1.1;
+ var _brakingAcceleration = 30;
+ var _gravity = 20;
+ var _airAccel = 5;
+ var _maxDotSlide = 0.5;
+ var _minBounceVel = 0.1;
+ var _bounceKineticFriction = 0.2;
+ var _bounceRestitution = 0.5;
+ var _bounceYet:Bool;
+ var _bounceSpeed:Float;
+ var _bouncePos:Vector;
+ var _bounceNormal:Vector;
+ var _slipAmount:Float;
+ var _contactTime:Float;
+ var _totalTime:Float;
+
+ public function new() {
+ super();
+ var geom = Sphere.defaultUnitSphere();
+ geom.addUVs();
+ var obj = new CustomObject(geom, Material.create(), this);
+ obj.scale(_radius);
+
+ this.velocity = new Vector();
+ this.omega = new Vector();
+ this.camera = new CameraController(20);
+ }
+
+ function findContacts(collisiomWorld:CollisionWorld) {
+ var c = collisiomWorld.sphereIntersection(this.getAbsPos().getPosition(), this.velocity, _radius);
+ return c;
+ }
+
+ function getMarbleAxis() {
+ var gravitydir = new Vector(0, 0, -1);
+ var cammat = Matrix.I();
+ var xrot = new Matrix();
+ xrot.initRotationX(this.camera.phi);
+ var zrot = new Matrix();
+ zrot.initRotationZ(this.camera.theta);
+ cammat.multiply(xrot, zrot);
+ var updir = new Vector(0, 0, 1);
+ var motiondir = new Vector(cammat._21, cammat._22, cammat._23);
+ var sidedir = motiondir.clone().cross(updir);
+
+ sidedir.normalize();
+ motiondir = updir.clone().cross(sidedir);
+ return [sidedir, motiondir, updir];
+ }
+
+ function getExternalForces(m:Move, dt:Float, contacts:Array) {
+ var gWorkGravityDir = new Vector(0, 0, -1);
+ var A = gWorkGravityDir.clone().multiply(this._gravity);
+ if (contacts.length == 0) {
+ var axes = this.getMarbleAxis();
+ var sideDir = axes[0];
+ var motionDir = axes[1];
+ var upDir = axes[2];
+ A = A.add(sideDir.clone()
+ .multiply(m.d.x)
+ .add(motionDir.clone().multiply(m.d.y))
+ .multiply(this._airAccel));
+ }
+ return A;
+ }
+
+ function computeMoveForces(m:Move) {
+ var aControl = new Vector();
+ var desiredOmega = new Vector();
+ var currentGravityDir = new Vector(0, 0, -1);
+ var R = currentGravityDir.clone().multiply(-this._radius);
+ var rollVelocity = this.omega.clone().cross(R);
+ var axes = this.getMarbleAxis();
+ var sideDir = axes[0];
+ var motionDir = axes[1];
+ var upDir = axes[2];
+ var currentYVelocity = rollVelocity.dot(motionDir);
+ var currentXVelocity = rollVelocity.dot(sideDir);
+ var mv = m.d;
+
+ mv = mv.multiply(1.53846157);
+ var mvlen = mv.length();
+ if (mvlen > 1) {
+ mv = mv.multiply(1 / mvlen);
+ }
+ var desiredYVelocity = this._maxRollVelocity * mv.y;
+ var desiredXVelocity = this._maxRollVelocity * mv.x;
+ if (desiredYVelocity != 0 || desiredXVelocity != 0) {
+ if (currentYVelocity > desiredYVelocity && desiredYVelocity > 0) {
+ desiredYVelocity = currentYVelocity;
+ } else if (currentYVelocity < desiredYVelocity && desiredYVelocity < 0) {
+ desiredYVelocity = currentYVelocity;
+ }
+ if (currentXVelocity > desiredXVelocity && desiredXVelocity > 0) {
+ desiredXVelocity = currentXVelocity;
+ } else if (currentXVelocity < desiredXVelocity && desiredXVelocity < 0) {
+ desiredXVelocity = currentXVelocity;
+ }
+ var rsq = R.lengthSq();
+ desiredOmega = R.clone().cross(motionDir.clone().multiply(desiredYVelocity).add(sideDir.clone().multiply(desiredXVelocity))).multiply(1 / rsq);
+ aControl = desiredOmega.clone().sub(this.omega);
+ var aScalar = aControl.length();
+ if (aScalar > this._angularAcceleration) {
+ aControl = aControl.multiply(this._angularAcceleration / aScalar);
+ }
+ return {result: false, aControl: aControl, desiredOmega: desiredOmega};
+ }
+ return {result: true, aControl: aControl, desiredOmega: desiredOmega};
+ }
+
+ function velocityCancel(surfaceSlide:Bool, noBounce:Bool, contacts:Array) {
+ var SurfaceDotThreshold = 0.001;
+ var looped = false;
+ var itersIn = 0;
+ var done:Bool;
+ do {
+ done = true;
+ itersIn++;
+ for (i in 0...contacts.length) {
+ var sVel = this.velocity.clone().sub(contacts[i].velocity);
+ var surfaceDot = contacts[i].normal.dot(sVel);
+ if ((!looped && surfaceDot < 0) || surfaceDot < -SurfaceDotThreshold) {
+ var velLen = this.velocity.length();
+ var surfaceVel = contacts[i].normal.clone().multiply(surfaceDot);
+ this.ReportBounce(contacts[i].point, contacts[i].normal, -surfaceDot);
+ if (noBounce) {
+ this.velocity = this.velocity.sub(surfaceVel);
+ } else if (contacts[i].collider != null) {
+ var info = contacts[i];
+ var bounce2 = 0.5;
+ var normV = info.normal.clone().multiply(this.velocity.dot(info.normal));
+ normV = normV.multiply(1 + bounce2);
+ this.velocity = this.velocity.sub(normV);
+ } else {
+ var velocity2 = contacts[i].velocity;
+ if (velocity2.length() > 0.0001 && !surfaceSlide && surfaceDot > -this._maxDotSlide * velLen) {
+ var vel = this.velocity.clone();
+ vel = vel.sub(surfaceVel);
+ vel.normalize();
+ vel = vel.multiply(velLen);
+ this.velocity = vel;
+ surfaceSlide = true;
+ } else if (surfaceDot > -this._minBounceVel) {
+ var vel = this.velocity.clone();
+ vel = vel.sub(surfaceVel);
+ this.velocity = vel;
+ } else {
+ var restitution = this._bounceRestitution;
+ restitution *= contacts[i].restitution;
+ var velocityAdd = surfaceVel.clone().multiply(-(1 + restitution));
+ var vAtC = sVel.clone().add(this.omega.clone().cross(contacts[i].normal.clone().multiply(this._radius).multiply(-1)));
+ var normalVel = -contacts[i].normal.dot(sVel);
+ vAtC = vAtC.sub(contacts[i].normal.clone().multiply(contacts[i].normal.dot(sVel)));
+ var vAtCMag = vAtC.length();
+ if (vAtCMag != 0) {
+ var friction = this._bounceKineticFriction * contacts[i].friction;
+ var angVMagnitude = 5 * friction * normalVel / (2 * this._radius);
+ if (angVMagnitude > vAtCMag / this._radius) {
+ angVMagnitude = vAtCMag / this._radius;
+ }
+ var vAtCDir = vAtC.clone().multiply(1 / vAtCMag);
+ var deltaOmega = contacts[i].normal.clone()
+ .multiply(-1)
+ .cross(vAtCDir.clone().multiply(-1))
+ .multiply(angVMagnitude);
+ this.omega = this.omega.add(deltaOmega);
+ this.velocity = this.velocity.sub(deltaOmega.clone().multiply(-1).cross(contacts[i].normal.clone().multiply(-this._radius)));
+ }
+ this.velocity = this.velocity.add(velocityAdd);
+ }
+ }
+ done = false;
+ }
+ }
+ looped = true;
+ if (itersIn > 6 && noBounce) {
+ done = true;
+ }
+ } while (!done);
+ // if (velocity.LengthSquared() < 625f)
+ // {
+ var gotOne = false;
+ var dir = new Vector(0, 0, 0);
+ for (j in 0...contacts.length) {
+ var dir2 = dir.clone().add(contacts[j].normal);
+ if (dir2.lengthSq() < 0.01) {
+ dir2 = dir2.add(contacts[j].normal);
+ }
+ dir = dir2;
+ gotOne = true;
+ }
+ if (gotOne) {
+ dir.normalize();
+ var soFar = 0.0;
+ for (k in 0...contacts.length) {
+ if (contacts[k].penetration < this._radius) {
+ var timeToSeparate = 0.1;
+ var dist = contacts[k].penetration;
+ var outVel = this.velocity.clone().add(dir.clone().multiply(soFar)).dot(contacts[k].normal);
+
+ if (timeToSeparate * outVel < dist) {
+ soFar += (dist - outVel * timeToSeparate) / timeToSeparate / contacts[k].normal.dot(dir);
+ }
+ }
+ }
+ // if (soFar < -25) soFar = -25;
+ // if (soFar > 25) soFar = 25;
+ this.velocity = this.velocity.add(dir.clone().multiply(soFar));
+ }
+ // }
+ }
+
+ function applyContactForces(dt:Float, m:Move, isCentered:Bool, aControl:Vector, desiredOmega:Vector, A:Vector, contacts:Array) {
+ var a = new Vector();
+ this._slipAmount = 0;
+ var gWorkGravityDir = new Vector(0, 0, -1);
+ var bestSurface = -1;
+ var bestNormalForce = 0.0;
+ for (i in 0...contacts.length) {
+ if (contacts[i].collider == null) {
+ var normalForce = -contacts[i].normal.dot(A);
+ if (normalForce > bestNormalForce) {
+ bestNormalForce = normalForce;
+ bestSurface = i;
+ }
+ }
+ }
+ var bestContact = (bestSurface != -1) ? contacts[bestSurface] : new CollisionInfo();
+ var canJump = bestSurface != -1;
+ if (canJump && m.jump) {
+ var velDifference = this.velocity.clone().sub(bestContact.velocity);
+ var sv = bestContact.normal.dot(velDifference);
+ if (sv < 0) {
+ sv = 0;
+ }
+ if (sv < this._jumpImpulse) {
+ this.velocity = this.velocity.add(bestContact.normal.clone().multiply((this._jumpImpulse - sv)));
+ }
+ }
+ for (j in 0...contacts.length) {
+ var normalForce2 = -contacts[j].normal.dot(A);
+ if (normalForce2 > 0 && contacts[j].normal.dot(this.velocity.clone().sub(contacts[j].velocity)) <= 0.0001) {
+ A = A.add(contacts[j].normal.multiply(normalForce2));
+ }
+ }
+ if (bestSurface != -1) {
+ // TODO: FIX
+ // bestContact.velocity - bestContact.normal * Vector3.Dot(bestContact.normal, bestContact.velocity);
+ var vAtC = this.velocity.clone().add(this.omega.clone().cross(bestContact.normal.clone().multiply(-this._radius))).sub(bestContact.velocity);
+ var vAtCMag = vAtC.length();
+ var slipping = false;
+ var aFriction = new Vector(0, 0, 0);
+ var AFriction = new Vector(0, 0, 0);
+ if (vAtCMag != 0) {
+ slipping = true;
+ var friction = this._kineticFriction * bestContact.friction;
+ var angAMagnitude = 5 * friction * bestNormalForce / (2 * this._radius);
+ var AMagnitude = bestNormalForce * friction;
+ var totalDeltaV = (angAMagnitude * this._radius + AMagnitude) * dt;
+ if (totalDeltaV > vAtCMag) {
+ var fraction = vAtCMag / totalDeltaV;
+ angAMagnitude *= fraction;
+ AMagnitude *= fraction;
+ slipping = false;
+ }
+ var vAtCDir = vAtC.clone().multiply(1 / vAtCMag);
+ aFriction = bestContact.normal.clone()
+ .multiply(-1)
+ .cross(vAtCDir.clone().multiply(-1))
+ .multiply(angAMagnitude);
+ AFriction = vAtCDir.clone().multiply(-AMagnitude);
+ this._slipAmount = vAtCMag - totalDeltaV;
+ }
+ if (!slipping) {
+ var R = gWorkGravityDir.clone().multiply(-this._radius);
+ var aadd = R.cross(A).multiply(1 / R.lengthSq());
+ if (isCentered) {
+ var nextOmega = this.omega.add(a.clone().multiply(dt));
+ aControl = desiredOmega.clone().sub(nextOmega);
+ var aScalar = aControl.length();
+ if (aScalar > this._brakingAcceleration) {
+ aControl = aControl.multiply(this._brakingAcceleration / aScalar);
+ }
+ }
+ var Aadd = aControl.clone().cross(bestContact.normal.multiply(-this._radius)).multiply(-1);
+ var aAtCMag = aadd.clone().cross(bestContact.normal.multiply(-this._radius)).add(Aadd).length();
+ var friction2 = this._staticFriction * bestContact.friction;
+
+ if (aAtCMag > friction2 * bestNormalForce) {
+ friction2 = this._kineticFriction * bestContact.friction;
+ Aadd = Aadd.multiply(friction2 * bestNormalForce / aAtCMag);
+ }
+ A = A.add(Aadd);
+ a = a.add(aadd);
+ }
+ A = A.add(AFriction);
+ a = a.add(aFriction);
+ }
+ a = a.add(aControl);
+ return [A, a];
+ }
+
+ function ReportBounce(pos:Vector, normal:Vector, speed:Float) {
+ if (this._bounceYet && speed < this._bounceSpeed) {
+ return;
+ }
+ this._bounceYet = true;
+ this._bouncePos = pos;
+ this._bounceSpeed = speed;
+ this._bounceNormal = normal;
+ }
+
+ function advancePhysics(m:Move, dt:Float, collisionWorld:CollisionWorld) {
+ var contacts = this.findContacts(collisionWorld);
+ var cmf = this.computeMoveForces(m);
+ var isCentered:Bool = cmf.result;
+ var aControl = cmf.aControl;
+ var desiredOmega = cmf.desiredOmega;
+ this.velocityCancel(isCentered, false, contacts);
+ var A = this.getExternalForces(m, dt, contacts);
+ var retf = this.applyContactForces(dt, m, isCentered, aControl, desiredOmega, A, contacts);
+ A = retf[0];
+ var a = retf[1];
+ this.velocity = this.velocity.add(A.multiply(dt));
+ this.omega = this.omega.add(a.multiply(dt));
+ this.velocityCancel(isCentered, true, contacts);
+ this._totalTime += dt;
+ if (contacts.length != 0) {
+ this._contactTime += dt;
+ }
+ }
+
+ public function update(dt:Float, collisionWorld:CollisionWorld) {
+ var move = new Move();
+ move.d = new Vector();
+ if (Key.isDown(Key.W)) {
+ move.d.x -= 1;
+ }
+ if (Key.isDown(Key.S)) {
+ move.d.x += 1;
+ }
+ if (Key.isDown(Key.A)) {
+ move.d.y += 1;
+ }
+ if (Key.isDown(Key.D)) {
+ move.d.y -= 1;
+ }
+ if (Key.isDown(Key.SPACE)) {
+ move.jump = true;
+ }
+
+ var timeRemaining = dt;
+ var it = 0;
+ do {
+ if (timeRemaining <= 0)
+ break;
+
+ var timeStep = 0.00800000037997961;
+ if (timeRemaining < 0.00800000037997961)
+ timeStep = timeRemaining;
+
+ advancePhysics(move, timeStep, collisionWorld);
+ var newPos = this.getAbsPos().getPosition().add(this.velocity.multiply(timeStep));
+ this.setPosition(newPos.x, newPos.y, newPos.z);
+
+ timeRemaining -= timeStep;
+ it++;
+ } while (it <= 10);
+
+ this.camera.target.load(this.getAbsPos().getPosition().toPoint());
+ }
+}
diff --git a/src/collision/Collision.hx b/src/collision/Collision.hx
new file mode 100644
index 00000000..6bb0a969
--- /dev/null
+++ b/src/collision/Collision.hx
@@ -0,0 +1,571 @@
+package collision;
+
+import dif.math.Point3F;
+import dif.math.PlaneF;
+import h3d.col.Plane;
+import h3d.Vector;
+
+typedef ISCResult = {
+ var result:Bool;
+ var tSeg:Float;
+ var tCap:Float;
+}
+
+typedef CPSSResult = {
+ var result:Float;
+ var s:Float;
+ var t:Float;
+ var c1:Vector;
+ var c2:Vector;
+}
+
+typedef ITSResult = {
+ var result:Bool;
+ var normal:Vector;
+ var point:Vector;
+}
+
+class Collision {
+ public static function IntersectLineSphere(start:Vector, end:Vector, center:Vector, radius:Float) {
+ var d = end.sub(start).normalized();
+ var v = center.sub(start);
+ var t = v.dot(d);
+ var p = start.add(d.multiply(t));
+ var dist = center.distance(p);
+
+ if (dist > radius) {
+ return null;
+ } else
+ return p;
+ }
+
+ public static function IntersectTriangleSphere(v0:Vector, v1:Vector, v2:Vector, normal:Vector, center:Vector, radius:Float) {
+ var radiusSq = radius * radius;
+
+ var res:ITSResult = {
+ result: false,
+ point: null,
+ normal: null
+ };
+
+ var p = PlaneF.PointNormal(new Point3F(v0.x, v0.y, v0.z), new Point3F(normal.x, normal.y, normal.z));
+ var pdist = p.distance(new Point3F(center.x, center.y, center.z));
+
+ if (pdist < 0) {
+ return res; // Dont collide internal edges
+ }
+
+ function toDifPoint(pt:Vector) {
+ return new Point3F(pt.x, pt.y, pt.z);
+ }
+
+ function fromDifPoint(pt:Point3F) {
+ return new Vector(pt.x, pt.y, pt.z);
+ }
+
+ if (pdist < radius) {
+ var t = -toDifPoint(center).dot(p.getNormal()) / p.getNormal().lengthSq();
+ var pt = fromDifPoint(p.project(toDifPoint(center))); // center.add(fromDifPoint(p.getNormal().scalar(t)));
+ if (PointInTriangle(pt, v0, v1, v2)) {
+ res.result = true;
+ res.point = pt;
+ res.normal = center.sub(pt).normalized();
+ return res;
+ }
+ // return res;
+ }
+
+ // Check points
+ if (center.sub(v0).lengthSq() < radiusSq) {
+ res.result = true;
+ res.point = v0;
+ res.normal = center.sub(v0).normalized();
+ // center.sub(v0).normalized();
+ return res;
+ }
+ if (center.sub(v1).lengthSq() < radiusSq) {
+ res.result = true;
+ res.point = v1;
+ res.normal = center.sub(v1).normalized();
+
+ return res;
+ }
+ if (center.sub(v2).lengthSq() < radiusSq) {
+ res.result = true;
+ res.point = v2;
+ res.normal = center.sub(v2).normalized();
+
+ return res;
+ }
+
+ // Check edges
+ var r1 = IntersectLineSphere(v0, v1, center, radius);
+ if (r1 != null) {
+ res.result = true;
+ res.point = r1;
+ res.normal = center.sub(r1).normalized();
+ return res;
+ }
+ var r2 = IntersectLineSphere(v1, v2, center, radius);
+ if (r2 != null) {
+ res.result = true;
+ res.point = r2;
+ res.normal = center.sub(r2).normalized();
+ return res;
+ }
+ var r3 = IntersectLineSphere(v2, v0, center, radius);
+ if (r3 != null) {
+ res.result = true;
+ res.point = r3;
+ res.normal = center.sub(r3).normalized();
+ return res;
+ }
+
+ // Check plane
+ // var p = PlaneF.ThreePoints(toDifPoint(v0), toDifPoint(v1), toDifPoint(v2));
+ return res;
+ }
+
+ public static function IntersectSegmentCapsule(segStart:Vector, segEnd:Vector, capStart:Vector, capEnd:Vector, radius:Float) {
+ var cpssres = Collision.ClosestPtSegmentSegment(segStart, segEnd, capStart, capEnd);
+ var res:ISCResult = {
+ result: cpssres.result < radius * radius,
+ tSeg: cpssres.s,
+ tCap: cpssres.t
+ }
+ return res;
+ }
+
+ public static function ClosestPtSegmentSegment(p1:Vector, q1:Vector, p2:Vector, q2:Vector) {
+ var Epsilon = 0.0001;
+ var d3 = q1.sub(p1);
+ var d2 = q2.sub(p2);
+ var r = p1.sub(p2);
+ var a = d3.dot(d3);
+ var e = d2.dot(d2);
+ var f = d2.dot(r);
+
+ var res:CPSSResult = {
+ s: 0,
+ t: 0,
+ c1: null,
+ c2: null,
+ result: -1
+ }
+
+ if (a <= Epsilon && e <= Epsilon) {
+ res = {
+ s: 0,
+ t: 0,
+ c1: p1,
+ c2: p2,
+ result: p1.sub(p2).dot(p1.sub(p2))
+ }
+ return res;
+ }
+ if (a <= Epsilon) {
+ res.s = 0;
+ res.t = f / e;
+ if (res.t > 1)
+ res.t = 1;
+ if (res.t < 0)
+ res.t = 0;
+ } else {
+ var c3 = d3.dot(r);
+ if (e <= Epsilon) {
+ res.t = 0;
+ if (-c3 / a > 1)
+ res.s = 1;
+ else if (-c3 / a < 0)
+ res.s = 0;
+ else
+ res.s = (-c3 / a);
+ } else {
+ var b = d3.dot(d2);
+ var denom = a * e - b * b;
+ if (denom != 0) {
+ res.s = (b * f - c3 * e) / denom;
+ if (res.s > 1)
+ res.s = 1;
+ if (res.s < 0)
+ res.s = 0;
+ } else {
+ res.s = 0;
+ }
+ res.t = (b * res.s + f) / e;
+ if (res.t < 0) {
+ res.t = 0;
+ res.s = -c3 / a;
+ if (res.s > 1)
+ res.s = 1;
+ if (res.s < 0)
+ res.s = 0;
+ } else if (res.t > 1) {
+ res.t = 1;
+ res.s = (b - c3) / a;
+ if (res.s > 1)
+ res.s = 1;
+ if (res.s < 0)
+ res.s = 0;
+ }
+ }
+ }
+ res.c1 = p1.add(d3.multiply(res.s));
+ res.c2 = p2.add(d2.multiply(res.t));
+ res.result = res.c1.sub(res.c2).lengthSq();
+ return res;
+ }
+
+ private static function PointInTriangle(point:Vector, v0:Vector, v1:Vector, v2:Vector):Bool {
+ var u = v1.sub(v0);
+ var v = v2.sub(v0);
+ var w = point.sub(v0);
+
+ var vw = v.cross(w);
+ var vu = v.cross(u);
+
+ if (vw.dot(vu) < 0.0) {
+ return false;
+ }
+
+ var uw = u.cross(w);
+ var uv = u.cross(v);
+
+ if (uw.dot(uv) < 0.0) {
+ return false;
+ }
+
+ var d:Float = uv.length();
+ var r:Float = vw.length() / d;
+ var t:Float = uw.length() / d;
+
+ return (r + t) <= 1;
+ }
+
+ private static function PointInTriangle2(point:Vector, a:Vector, b:Vector, c:Vector):Bool {
+ var a1 = a.sub(point);
+ var b1 = b.sub(point);
+ var c1 = c.sub(point);
+
+ var u = b1.cross(c1);
+ var v = c1.cross(a1);
+
+ if (u.dot(v) < 0)
+ return false;
+
+ var w = a1.cross(b1);
+ return !(u.dot(w) < 0);
+ }
+
+ private static function GetLowestRoot(a:Float, b:Float, c:Float, max:Float):Null {
+ // check if solution exists
+ var determinant:Float = b * b - 4.0 * a * c;
+
+ // if negative there is no solution
+ if (determinant < 0.0) {
+ return null;
+ }
+
+ // calculate two roots
+ var sqrtD:Float = Math.sqrt(determinant);
+ var r1:Float = (-b - sqrtD) / (2 * a);
+ var r2:Float = (-b + sqrtD) / (2 * a);
+
+ // set x1 <= x2
+ if (r1 > r2) {
+ var temp:Float = r2;
+ r2 = r1;
+ r1 = temp;
+ }
+
+ // get lowest root
+ if (r1 > 0 && r1 < max) {
+ return r1;
+ }
+
+ if (r2 > 0 && r2 < max) {
+ return r2;
+ }
+
+ // no solutions
+ return null;
+ }
+
+ public static function ClosestPtPointTriangle(pt:Vector, radius:Float, p0:Vector, p1:Vector, p2:Vector, normal:Vector) {
+ var closest:Vector = null;
+ var ptDot = pt.dot(normal);
+ var triDot = p0.dot(normal);
+ if (Math.abs(ptDot - triDot) > radius * 1.1) {
+ return null;
+ }
+ closest = pt.add(normal.multiply(triDot - ptDot));
+ if (Collision.PointInTriangle2(closest, p0, p1, p2)) {
+ return closest;
+ }
+ var t = 10.0;
+ var r1 = Collision.IntersectSegmentCapsule(pt, pt, p0, p1, radius);
+ if (r1.result && r1.tSeg < t) {
+ closest = p0.add((p1.sub(p0).multiply(r1.tCap)));
+ t = r1.tSeg;
+ }
+ var r2 = Collision.IntersectSegmentCapsule(pt, pt, p1, p2, radius);
+ if (r2.result && r2.tSeg < t) {
+ closest = p1.add((p2.sub(p1).multiply(r2.tCap)));
+ t = r2.tSeg;
+ }
+ var r3 = Collision.IntersectSegmentCapsule(pt, pt, p2, p0, radius);
+ if (r3.result && r3.tSeg < t) {
+ closest = p2.add((p2.sub(p2).multiply(r3.tCap)));
+ t = r3.tSeg;
+ }
+ var res = t < 1;
+ if (res) {
+ return closest;
+ }
+ return null;
+ }
+
+ public static function CheckTriangle(packet:CollisionPacket, p1:Vector, p2:Vector, p3:Vector) {
+ function toDifPoint(pt:Vector) {
+ return new Point3F(pt.x, pt.y, pt.z);
+ }
+
+ function fromDifPoint(pt:Point3F) {
+ return new Vector(pt.x, pt.y, pt.z);
+ }
+
+ var plane = PlaneF.ThreePoints(toDifPoint(p1), toDifPoint(p2), toDifPoint(p3));
+
+ // only check front facing triangles
+ var dist = plane.distance(toDifPoint(packet.e_norm_velocity));
+ if (dist < 0) {
+ return packet;
+ }
+
+ // get interval of plane intersection
+ var t0:Float = 0.0;
+ var t1:Float = 0.0;
+ var embedded_in_plane:Bool = false;
+
+ // signed distance from sphere to point on plane
+ var signed_dist_to_plane:Float = plane.distance(toDifPoint(packet.e_base_point));
+
+ // cache this as we will reuse
+ var normal_dot_vel = plane.getNormal().dot(toDifPoint(packet.e_velocity));
+
+ // if sphere is moving parrallel to plane
+ if (normal_dot_vel == 0.0) {
+ if (Math.abs(signed_dist_to_plane) >= 1.0) {
+ // no collision possible
+ return packet;
+ } else {
+ // sphere is in plane in whole range [0..1]
+ embedded_in_plane = true;
+ t0 = 0.0;
+ t1 = 1.0;
+ }
+ } else {
+ // N dot D is not 0, calc intersect interval
+ t0 = (-1.0 - signed_dist_to_plane) / normal_dot_vel;
+ t1 = (1.0 - signed_dist_to_plane) / normal_dot_vel;
+
+ // swap so t0 < t1
+ if (t0 > t1) {
+ var temp = t1;
+ t1 = t0;
+ t0 = temp;
+ }
+
+ // check that at least one result is within range
+ if (t0 > 1.0 || t1 < 0.0) {
+ // both values outside range [0,1] so no collision
+ return packet;
+ }
+
+ // clamp to [0,1]
+ if (t0 < 0.0) {
+ t0 = 0.0;
+ }
+ if (t1 < 0.0) {
+ t1 = 0.0;
+ }
+ if (t0 > 1.0) {
+ t0 = 1.0;
+ }
+ if (t1 > 1.0) {
+ t1 = 1.0;
+ }
+ }
+
+ // time to check for a collision
+ var collision_point:Vector = new Vector(0.0, 0.0, 0.0);
+ var found_collision:Bool = false;
+ var t:Float = 1.0;
+
+ // first check collision with the inside of the triangle
+ if (!embedded_in_plane) {
+ var plane_intersect:Vector = packet.e_base_point.sub(fromDifPoint(plane.getNormal()));
+ var temp:Vector = packet.e_velocity.multiply(t0);
+ plane_intersect = plane_intersect.add(temp);
+
+ if (Collision.PointInTriangle(plane_intersect, p1, p2, p3)) {
+ found_collision = true;
+ t = t0;
+ collision_point = plane_intersect;
+ }
+ }
+
+ // no collision yet, check against points and edges
+ if (!found_collision) {
+ var velocity = packet.e_velocity.clone();
+ var base = packet.e_base_point.clone();
+
+ var velocity_sq_length = velocity.lengthSq();
+ var a:Float = velocity_sq_length;
+ var b:Float = 0.0;
+ var c:Float = 0.0;
+
+ // equation is a*t^2 + b*t + c = 0
+ // check against points
+
+ // p1
+ var temp = base.sub(p1);
+ b = 2.0 * velocity.dot(temp);
+ temp = p1.sub(base);
+ c = temp.lengthSq() - 1.0;
+ var new_t = Collision.GetLowestRoot(a, b, c, t);
+ if (new_t != null) {
+ t = new_t;
+ found_collision = true;
+ collision_point = p1;
+ }
+
+ // p2
+ if (!found_collision) {
+ temp = base.sub(p2);
+ b = 2.0 * velocity.dot(temp);
+ temp = p2.sub(base);
+ c = temp.lengthSq() - 1.0;
+ new_t = Collision.GetLowestRoot(a, b, c, t);
+ if (new_t != null) {
+ t = new_t;
+ found_collision = true;
+ collision_point = p2;
+ }
+ }
+
+ // p3
+ if (!found_collision) {
+ temp = base.sub(p3);
+ b = 2.0 * velocity.dot(temp);
+ temp = p3.sub(base);
+ c = temp.lengthSq() - 1.0;
+ new_t = Collision.GetLowestRoot(a, b, c, t);
+ if (new_t != null) {
+ t = new_t;
+ found_collision = true;
+ collision_point = p3;
+ }
+ }
+
+ // check against edges
+ // p1 -> p2
+ var edge = p2.sub(p1);
+ var base_to_vertex = p1.sub(base);
+ var edge_sq_length = edge.lengthSq();
+ var edge_dot_velocity = edge.dot(velocity);
+ var edge_dot_base_to_vertex = edge.dot(base_to_vertex);
+
+ // calculate params for equation
+ a = edge_sq_length * -velocity_sq_length + edge_dot_velocity * edge_dot_velocity;
+ b = edge_sq_length * (2.0 * velocity.dot(base_to_vertex)) - 2.0 * edge_dot_velocity * edge_dot_base_to_vertex;
+ c = edge_sq_length * (1.0 - base_to_vertex.lengthSq()) + edge_dot_base_to_vertex * edge_dot_base_to_vertex;
+
+ // do we collide against infinite edge
+ new_t = Collision.GetLowestRoot(a, b, c, t);
+ if (new_t != null) {
+ // check if intersect is within line segment
+ var f = (edge_dot_velocity * new_t - edge_dot_base_to_vertex) / edge_sq_length;
+ if (f >= 0.0 && f <= 1.0) {
+ t = new_t;
+ found_collision = true;
+ collision_point = p1.add(edge.multiply(f));
+ }
+ }
+
+ // p2 -> p3
+ edge = p3.sub(p2);
+ base_to_vertex = p2.sub(base);
+ edge_sq_length = edge.lengthSq();
+ edge_dot_velocity = edge.dot(velocity);
+ edge_dot_base_to_vertex = edge.dot(base_to_vertex);
+
+ // calculate params for equation
+ a = edge_sq_length * -velocity_sq_length + edge_dot_velocity * edge_dot_velocity;
+ b = edge_sq_length * (2.0 * velocity.dot(base_to_vertex)) - 2.0 * edge_dot_velocity * edge_dot_base_to_vertex;
+ c = edge_sq_length * (1.0 - base_to_vertex.lengthSq()) + edge_dot_base_to_vertex * edge_dot_base_to_vertex;
+
+ // do we collide against infinite edge
+ new_t = Collision.GetLowestRoot(a, b, c, t);
+ if (new_t != null) {
+ // check if intersect is within line segment
+ var f = (edge_dot_velocity * new_t - edge_dot_base_to_vertex) / edge_sq_length;
+ if (f >= 0.0 && f <= 1.0) {
+ t = new_t;
+ found_collision = true;
+ collision_point = p2.add(edge.multiply(f));
+ }
+ }
+
+ // p3 -> p1
+ edge = p1.sub(p3);
+ base_to_vertex = p3.sub(base);
+ edge_sq_length = edge.lengthSq();
+ edge_dot_velocity = edge.dot(velocity);
+ edge_dot_base_to_vertex = edge.dot(base_to_vertex);
+
+ // calculate params for equation
+ a = edge_sq_length * -velocity_sq_length + edge_dot_velocity * edge_dot_velocity;
+ b = edge_sq_length * (2.0 * velocity.dot(base_to_vertex)) - 2.0 * edge_dot_velocity * edge_dot_base_to_vertex;
+ c = edge_sq_length * (1.0 - base_to_vertex.lengthSq()) + edge_dot_base_to_vertex * edge_dot_base_to_vertex;
+
+ // do we collide against infinite edge
+ new_t = Collision.GetLowestRoot(a, b, c, t);
+ if (new_t != null) {
+ // check if intersect is within line segment
+ var f = (edge_dot_velocity * new_t - edge_dot_base_to_vertex) / edge_sq_length;
+ if (f >= 0.0 && f <= 1.0) {
+ t = new_t;
+ found_collision = true;
+ collision_point = p3.add(edge.multiply(f));
+ }
+ }
+ }
+
+ // set results
+ if (found_collision) {
+ // distance to collision, t is time of collision
+ var dist_to_coll = t * packet.e_velocity.length();
+
+ // are we the closest hit?
+ if (!packet.found_collision || dist_to_coll < packet.nearest_distance) {
+ packet.nearest_distance = dist_to_coll;
+ packet.intersect_point = collision_point;
+ packet.found_collision = true;
+ }
+
+ // HACK: USE SENSORS FOR THIS AND YOU DON'T GET WALL HITS ANYMORE
+ // Work out the hit normal so we can determine if the player is in
+ // contact with a wall or the ground.
+ var n = collision_point.sub(packet.e_base_point);
+ n.normalize();
+
+ var dz = n.dot(new Vector(0, 0, 1));
+ if (dz <= -0.5) {
+ packet.grounded = true;
+ }
+ }
+
+ return packet;
+ }
+}
diff --git a/src/collision/CollisionEntity.hx b/src/collision/CollisionEntity.hx
new file mode 100644
index 00000000..39ad2b96
--- /dev/null
+++ b/src/collision/CollisionEntity.hx
@@ -0,0 +1,222 @@
+package collision;
+
+import dif.math.Point3F;
+import dif.math.PlaneF;
+import h3d.col.Plane;
+import octree.Octree;
+import h3d.col.Ray;
+import h3d.Vector;
+import octree.IOctreeObject;
+import h3d.Matrix;
+import h3d.col.Bounds;
+
+class CollisionEntity implements IOctreeObject {
+ public var boundingBox:Bounds;
+
+ var octree:Octree;
+
+ public var surfaces:Array;
+
+ public var priority:Int;
+ public var position:Int;
+
+ var transform:Matrix;
+
+ public function new() {
+ this.octree = new Octree();
+ this.surfaces = [];
+ this.transform = Matrix.I();
+ }
+
+ public function addSurface(surface:CollisionSurface) {
+ this.octree.insert(surface);
+ this.surfaces.push(surface);
+ }
+
+ public function generateBoundingBox() {
+ var boundingBox = new Bounds();
+ for (surface in this.surfaces) {
+ var tform = surface.boundingBox.clone();
+ tform.transform(transform);
+ boundingBox.add(tform);
+ }
+ this.boundingBox = boundingBox;
+ }
+
+ public function isIntersectedByRay(rayOrigin:Vector, rayDirection:Vector, intersectionPoint:Vector):Bool {
+ // TEMP cause bruh
+ return boundingBox.rayIntersection(Ray.fromValues(rayOrigin.x, rayOrigin.y, rayOrigin.z, rayDirection.x, rayDirection.y, rayDirection.z), true) != -1;
+ }
+
+ public function getElementType() {
+ return 2;
+ }
+
+ public function setPriority(priority:Int) {
+ this.priority = priority;
+ }
+
+ public function sphereIntersection(position:Vector, velocity:Vector, radius:Float) {
+ var invMatrix = transform.clone();
+ invMatrix.invert();
+ var localpos = position.clone();
+ localpos.transform(invMatrix);
+
+ var bigRad = 2 * velocity.length() + radius * 1.1;
+
+ var surfaces = octree.radiusSearch(position, radius * 1.1);
+
+ var contacts = [];
+
+ function toDifPoint(pt:Vector) {
+ return new Point3F(pt.x, pt.y, pt.z);
+ }
+
+ function fromDifPoint(pt:Point3F) {
+ return new Vector(pt.x, pt.y, pt.z);
+ }
+
+ for (obj in surfaces) {
+ var surface:CollisionSurface = cast obj;
+
+ var i = 0;
+ while (i < surface.indices.length) {
+ var v0 = surface.points[surface.indices[i]];
+ var v = surface.points[surface.indices[i + 1]];
+ var v2 = surface.points[surface.indices[i + 2]];
+
+ // var packet = new CollisionPacket(position, velocity, new Vector(radius, radius, radius));
+ // packet.e_base_point = packet.e_position.clone();
+ // packet.e_norm_velocity = packet.e_velocity.clone().normalized();
+ // packet.nearest_distance = 1e20;
+
+ // var plane = PlaneF.PointNormal(toDifPoint(v), toDifPoint(surface.normals[surface.indices[i]]));
+
+ // var retpacket = Collision.CheckTriangle(packet, v0.multiply(1 / radius), v.multiply(1 / radius), v2.multiply(1 / radius));
+
+ // if (retpacket.found_collision) {
+ // var cinfo = new CollisionInfo();
+ // cinfo.restitution = 1;
+ // cinfo.friction = 1;
+ // cinfo.normal = surface.normals[surface.indices[i]];
+ // cinfo.point = retpacket.intersect_point;
+ // cinfo.velocity = new Vector();
+ // cinfo.collider = null;
+ // cinfo.penetration = radius - (position.sub(cinfo.point).dot(cinfo.normal));
+ // contacts.push(cinfo);
+ // }
+ // var plane = PlaneF.ThreePoints(toDifPoint(v0), toDifPoint(v), toDifPoint(v2));
+
+ // var distance = plane.distance(toDifPoint(position));
+
+ // if (Math.abs(distance) <= radius + 0.001) {
+ // var lastVertex = surface.points[surface.indices[surface.indices.length - 1]];
+
+ // var contactVert = plane.project(toDifPoint(position));
+ // var separation = Math.sqrt(radius * radius - distance * distance);
+
+ // for (j in 0...surface.indices.length) {
+ // var vertex = surface.points[surface.indices[i]];
+ // if (vertex != lastVertex) {
+ // var vertPlane = PlaneF.ThreePoints(toDifPoint(vertex).add(plane.getNormal()), toDifPoint(vertex), toDifPoint(lastVertex));
+ // var vertDistance = vertPlane.distance(contactVert);
+ // if (vertDistance < 0.0) {
+ // if (vertDistance < -(separation + 0.0001))
+ // return contacts;
+ // // return contacts;
+
+ // if (PlaneF.ThreePoints(vertPlane.getNormal().add(toDifPoint(vertex)), toDifPoint(vertex),
+ // toDifPoint(vertex).add(plane.getNormal()))
+ // .distance(contactVert) >= 0.0) {
+ // if (PlaneF.ThreePoints(toDifPoint(lastVertex).sub(vertPlane.getNormal()), toDifPoint(lastVertex),
+ // toDifPoint(lastVertex).add(plane.getNormal()))
+ // .distance(contactVert) >= 0.0) {
+ // contactVert = vertPlane.project(contactVert);
+ // break;
+ // }
+ // contactVert = toDifPoint(lastVertex);
+ // } else {
+ // contactVert = toDifPoint(vertex);
+ // }
+ // }
+ // lastVertex = vertex;
+ // }
+
+ // var cinfo = new CollisionInfo();
+ // cinfo.restitution = 1;
+ // cinfo.friction = 1;
+ // cinfo.normal = surface.normals[i];
+ // cinfo.point = fromDifPoint(contactVert);
+ // cinfo.velocity = new Vector();
+ // cinfo.collider = null;
+ // cinfo.penetration = radius - (position.sub(cinfo.point).dot(cinfo.normal));
+ // contacts.push(cinfo);
+ // }
+ // }
+
+ // // var norm = Plane.fromPoints(v0.toPoint(), v.toPoint(), v2.toPoint());
+
+ // var cinfo = new CollisionInfo();
+ // cinfo.restitution = 1;
+ // cinfo.friction = 1;
+ // cinfo.normal = surface.normals[i];
+ // cinfo.point = fromDifPoint(contactVert);
+ // cinfo.velocity = new Vector();
+ // cinfo.collider = null;
+ // cinfo.penetration = radius - (position.sub(cinfo.point).dot(cinfo.normal));
+ // contacts.push(cinfo);
+ // }
+
+ var res = Collision.IntersectTriangleSphere(v0, v, v2, surface.normals[surface.indices[i]], position, radius);
+ var closest = res.point;
+ // Collision.ClosestPtPointTriangle(position, radius, v0, v, v2, surface.normals[surface.indices[i]]);
+ if (res.result) {
+ if (position.sub(closest).lengthSq() < radius * radius) {
+ var normal = res.normal;
+
+ if (position.sub(closest).dot(surface.normals[surface.indices[i]]) > 0) {
+ normal.normalize();
+
+ var cinfo = new CollisionInfo();
+ cinfo.normal = res.normal; // surface.normals[surface.indices[i]];
+ cinfo.point = closest;
+ // cinfo.collider = this;
+ cinfo.velocity = new Vector();
+ cinfo.penetration = radius - (position.sub(closest).dot(normal));
+ cinfo.restitution = 1;
+ cinfo.friction = 1;
+ contacts.push(cinfo);
+ }
+ }
+ }
+
+ // var res = Collision.IntersectTriangleSphere(v0, v, v2, surface.normals[surface.indices[i]], position, radius);
+ // var closest = res.point;
+ // var closest = Collision.ClosestPtPointTriangle(position, radius, v0, v, v2, surface.normals[surface.indices[i]]);
+ // if (closest != null) {
+ // if (position.sub(closest).lengthSq() < radius * radius) {
+ // var normal = position.sub(closest);
+
+ // if (position.sub(closest).dot(surface.normals[surface.indices[i]]) > 0) {
+ // normal.normalize();
+
+ // var cinfo = new CollisionInfo();
+ // cinfo.normal = normal;
+ // cinfo.point = closest;
+ // // cinfo.collider = this;
+ // cinfo.velocity = new Vector();
+ // cinfo.penetration = radius - (position.sub(closest).dot(normal));
+ // cinfo.restitution = 1;
+ // cinfo.friction = 1;
+ // contacts.push(cinfo);
+ // }
+ // }
+ // }
+
+ i += 3;
+ }
+ }
+
+ return contacts;
+ }
+}
diff --git a/src/collision/CollisionInfo.hx b/src/collision/CollisionInfo.hx
new file mode 100644
index 00000000..beb9d29d
--- /dev/null
+++ b/src/collision/CollisionInfo.hx
@@ -0,0 +1,15 @@
+package collision;
+
+import h3d.Vector;
+
+class CollisionInfo {
+ public var point:Vector;
+ public var normal:Vector;
+ public var velocity:Vector;
+ public var collider:CollisionEntity;
+ public var friction:Float;
+ public var restitution:Float;
+ public var penetration:Float;
+
+ public function new() {}
+}
diff --git a/src/collision/CollisionPacket.hx b/src/collision/CollisionPacket.hx
new file mode 100644
index 00000000..af26dbb5
--- /dev/null
+++ b/src/collision/CollisionPacket.hx
@@ -0,0 +1,67 @@
+package collision;
+
+import h3d.Vector;
+
+@:publicFields
+class CollisionPacket {
+ static var LARGE_NUMBER:Float = 1e20;
+
+ /** Position in world space. Assumes Z-up. **/
+ var r3_position = new Vector(0.0, 0.0, 0.0);
+
+ /** Velocity in world space. Assumes Z-up. **/
+ var r3_velocity = new Vector(0.0, 0.0, 0.0);
+
+ /** Ellipsoid radius in world units. Assumes Z-up. **/
+ var e_radius = new Vector(1.0, 1.0, 1.0);
+
+ /** Position in ellipsoid-space. Used internally. **/
+ var e_position = new Vector(0.0, 0.0, 0.0);
+
+ /** Velocity in ellipsoid-space. Used internally. **/
+ var e_velocity = new Vector(0.0, 0.0, 0.0);
+
+ /** Found a collision. **/
+ var found_collision = false;
+
+ /** Distance to nearest collision if `found_collision` is set, otherwise `1e20` **/
+ var nearest_distance = LARGE_NUMBER;
+
+ /** Iteration depth. Useful for debugging slow collisions. **/
+ var depth:Int = 0;
+
+ // internal stuff
+ var e_norm_velocity = new Vector(0.0, 0.0, 0.0);
+ var e_base_point = new Vector(0.0, 0.0, 0.0);
+ var intersect_point = new Vector(0.0, 0.0, 0.0);
+
+ /** `true` if packet is on something reasonable to call the ground.
+ *
+ * _in practice, you probably want a sensor in your game code instead._ **/
+ var grounded:Bool = false;
+
+ /** Recalculate e-space from r3 vectors. Call this if you updated `r3_*` **/
+ inline function to_e() {
+ this.e_position = new Vector(this.r3_position.x / this.e_radius.x, this.r3_position.y / this.e_radius.y, this.r3_position.z / this.e_radius.z);
+ this.e_velocity = new Vector(this.r3_velocity.x / this.e_radius.x, this.r3_velocity.y / this.e_radius.y, this.r3_velocity.z / this.e_radius.z);
+ }
+
+ /** Recalculate e-space from r3 vectors. Call this if you updated `e_*` **/
+ inline function to_r3() {
+ this.r3_position = new Vector(this.e_position.x * this.e_radius.x, this.e_position.y * this.e_radius.y, this.e_position.z * this.e_radius.z);
+ this.r3_velocity = new Vector(this.e_velocity.x * this.e_radius.x, this.e_velocity.y * this.e_radius.y, this.e_velocity.z * this.e_radius.z);
+ }
+
+ inline function new(?position:Vector, ?velocity:Vector, ?radius:Vector) {
+ if (position != null) {
+ this.r3_position = position;
+ }
+ if (velocity != null) {
+ this.r3_velocity = velocity;
+ }
+ if (radius != null) {
+ this.e_radius = radius;
+ }
+ this.to_e();
+ }
+}
diff --git a/src/collision/CollisionSurface.hx b/src/collision/CollisionSurface.hx
new file mode 100644
index 00000000..6a4f7e2c
--- /dev/null
+++ b/src/collision/CollisionSurface.hx
@@ -0,0 +1,63 @@
+package collision;
+
+import h3d.col.Bounds;
+import octree.IOctreeObject;
+import h3d.Vector;
+
+class CollisionSurface implements IOctreeObject {
+ public var priority:Int;
+ public var position:Int;
+
+ public var boundingBox:Bounds;
+
+ public var points:Array;
+ public var normals:Array;
+ public var indices:Array;
+
+ public function new() {}
+
+ public function getElementType() {
+ return 2;
+ }
+
+ public function generateBoundingBox() {
+ var boundingBox = new Bounds();
+ boundingBox.xMin = 10e8;
+ boundingBox.yMin = 10e8;
+ boundingBox.zMin = 10e8;
+ boundingBox.xMax = -10e8;
+ boundingBox.yMax = -10e8;
+ boundingBox.zMax = -10e8;
+
+ for (point in points) {
+ if (point.x > boundingBox.xMax) {
+ boundingBox.xMax = point.x;
+ }
+ if (point.x < boundingBox.xMin) {
+ boundingBox.xMin = point.x;
+ }
+ if (point.y > boundingBox.yMax) {
+ boundingBox.yMax = point.y;
+ }
+ if (point.y < boundingBox.yMin) {
+ boundingBox.yMin = point.y;
+ }
+ if (point.z > boundingBox.zMax) {
+ boundingBox.zMax = point.z;
+ }
+ if (point.z < boundingBox.zMin) {
+ boundingBox.zMin = point.z;
+ }
+ }
+ this.boundingBox = boundingBox;
+ }
+
+ public function setPriority(priority:Int) {
+ this.priority = priority;
+ }
+
+ public function isIntersectedByRay(rayOrigin:Vector, rayDirection:Vector, intersectionPoint:Vector):Bool {
+ // TEMP cause bruh
+ return true;
+ }
+}
diff --git a/src/collision/CollisionWorld.hx b/src/collision/CollisionWorld.hx
new file mode 100644
index 00000000..89c6b0bb
--- /dev/null
+++ b/src/collision/CollisionWorld.hx
@@ -0,0 +1,30 @@
+package collision;
+
+import h3d.Vector;
+import octree.Octree;
+
+class CollisionWorld {
+ public var octree:Octree;
+
+ public function new() {
+ this.octree = new Octree();
+ }
+
+ public function sphereIntersection(position:Vector, velocity:Vector, radius:Float) {
+ var searchdist = velocity.length() + radius;
+ var intersections = this.octree.radiusSearch(position, searchdist);
+
+ var contacts = [];
+
+ for (obj in intersections) {
+ var entity:CollisionEntity = cast obj;
+
+ contacts = contacts.concat(entity.sphereIntersection(position, velocity, radius));
+ }
+ return contacts;
+ }
+
+ public function addEntity(entity:CollisionEntity) {
+ this.octree.insert(entity);
+ }
+}
diff --git a/src/dif/AISpecialNode.hx b/src/dif/AISpecialNode.hx
new file mode 100644
index 00000000..46c89fa1
--- /dev/null
+++ b/src/dif/AISpecialNode.hx
@@ -0,0 +1,27 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.Point3F;
+
+using dif.WriterExtensions;
+
+@:expose
+class AISpecialNode {
+ public var name:String;
+ public var position:Point3F;
+
+ public function new(name, position) {
+ this.name = name;
+ this.position = position;
+ }
+
+ public static function read(io:BytesReader) {
+ return new AISpecialNode(io.readStr(), Point3F.read(io));
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeStr(this.name);
+ this.position.write(io);
+ }
+}
diff --git a/src/dif/AnimatedLight.hx b/src/dif/AnimatedLight.hx
new file mode 100644
index 00000000..64f397c1
--- /dev/null
+++ b/src/dif/AnimatedLight.hx
@@ -0,0 +1,34 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import haxe.Int32;
+
+@:expose
+class AnimatedLight {
+ public var nameIndex:Int32;
+ public var stateIndex:Int32;
+ public var stateCount:Int;
+ public var flags:Int;
+ public var duration:Int32;
+
+ public function new(nameIndex:Int32, stateIndex:Int32, stateCount:Int, flags:Int, duration:Int32) {
+ this.nameIndex = nameIndex;
+ this.stateIndex = stateIndex;
+ this.stateCount = stateCount;
+ this.flags = flags;
+ this.duration = duration;
+ }
+
+ public static function read(io:BytesReader) {
+ return new AnimatedLight(io.readInt32(), io.readInt32(), io.readInt16(), io.readInt16(), io.readInt32());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.nameIndex);
+ io.writeInt32(this.stateIndex);
+ io.writeUInt16(this.stateCount);
+ io.writeUInt16(this.flags);
+ io.writeInt32(this.duration);
+ }
+}
diff --git a/src/dif/BSPNode.hx b/src/dif/BSPNode.hx
new file mode 100644
index 00000000..450873e6
--- /dev/null
+++ b/src/dif/BSPNode.hx
@@ -0,0 +1,107 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class BSPNode {
+ public var planeIndex:Int;
+ public var frontIndex:Int;
+ public var backIndex:Int;
+
+ public var isFrontLeaf:Bool;
+ public var isFrontSolid:Bool;
+
+ public var isBackLeaf:Bool;
+ public var isBackSolid:Bool;
+
+ public function new(planeIndex, frontIndex, backIndex, isFrontLeaf, isFrontSolid, isBackLeaf, isBackSolid) {
+ this.planeIndex = planeIndex;
+ this.frontIndex = frontIndex;
+ this.backIndex = backIndex;
+ this.isFrontLeaf = isFrontLeaf;
+ this.isFrontSolid = isFrontSolid;
+ this.isBackLeaf = isBackLeaf;
+ this.isBackSolid = isBackSolid;
+ }
+
+ public static function read(io:BytesReader, version:Version) {
+ var planeIndex = io.readUInt16();
+ var frontIndex,
+ backIndex,
+ isfrontleaf = false,
+ isfrontsolid = false,
+ isbackleaf = false,
+ isbacksolid = false;
+ if (version.interiorVersion >= 14) {
+ frontIndex = io.readInt32();
+ backIndex = io.readInt32();
+ if ((frontIndex & 0x80000) != 0) {
+ frontIndex = (frontIndex & ~0x80000) | 0x8000;
+ isfrontleaf = true;
+ }
+ if ((frontIndex & 0x40000) != 0) {
+ frontIndex = (frontIndex & ~0x40000) | 0x4000;
+ isfrontsolid = true;
+ }
+ if ((backIndex & 0x80000) != 0) {
+ backIndex = (backIndex & ~0x80000) | 0x8000;
+ isbackleaf = true;
+ }
+ if ((backIndex & 0x40000) != 0) {
+ backIndex = (backIndex & ~0x40000) | 0x4000;
+ isbacksolid = true;
+ }
+ } else {
+ frontIndex = io.readUInt16();
+ backIndex = io.readUInt16();
+ if ((frontIndex & 0x8000) != 0) {
+ isfrontleaf = true;
+ }
+ if ((frontIndex & 0x4000) != 0) {
+ isfrontsolid = true;
+ }
+ if ((backIndex & 0x8000) != 0) {
+ isbackleaf = true;
+ }
+ if ((backIndex & 0x4000) != 0) {
+ isbacksolid = true;
+ }
+ }
+ return new BSPNode(planeIndex, frontIndex, backIndex, isfrontleaf, isfrontsolid, isbackleaf, isbacksolid);
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeUInt16(this.planeIndex);
+
+ if (version.interiorVersion >= 14) {
+ var frontwrite = this.frontIndex;
+ var frontwrite = frontIndex;
+ if (this.isFrontLeaf) {
+ frontwrite &= ~0x8000;
+ frontwrite |= 0x80000;
+ }
+ if (this.isFrontSolid) {
+ frontwrite &= ~0x4000;
+ frontwrite |= 0x40000;
+ }
+
+ io.writeInt32(frontwrite);
+
+ var backwrite = backIndex;
+ if (this.isBackLeaf) {
+ backwrite &= ~0x8000;
+ backwrite |= 0x80000;
+ }
+ if (this.isBackSolid) {
+ backwrite &= ~0x4000;
+ backwrite |= 0x40000;
+ }
+
+ io.writeInt32(backwrite);
+ } else {
+ io.writeInt16(this.frontIndex);
+ io.writeInt16(this.backIndex);
+ }
+ }
+}
diff --git a/src/dif/BSPSolidLeaf.hx b/src/dif/BSPSolidLeaf.hx
new file mode 100644
index 00000000..969abd55
--- /dev/null
+++ b/src/dif/BSPSolidLeaf.hx
@@ -0,0 +1,24 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class BSPSolidLeaf {
+ public var surfaceStart:Int;
+ public var surfaceCount:Int;
+
+ public function new(surfaceStart, surfaceCount) {
+ this.surfaceStart = surfaceStart;
+ this.surfaceCount = surfaceCount;
+ }
+
+ public static function read(io:BytesReader) {
+ return new BSPSolidLeaf(io.readInt32(), io.readInt16());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.surfaceStart);
+ io.writeInt16(this.surfaceCount);
+ }
+}
diff --git a/src/dif/ConvexHull.hx b/src/dif/ConvexHull.hx
new file mode 100644
index 00000000..03c3af83
--- /dev/null
+++ b/src/dif/ConvexHull.hx
@@ -0,0 +1,86 @@
+package dif;
+
+import haxe.Int32;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class ConvexHull {
+ public var hullStart:Int32;
+ public var hullCount:Int32;
+ public var minX:Float;
+ public var minY:Float;
+ public var minZ:Float;
+ public var maxX:Float;
+ public var maxY:Float;
+ public var maxZ:Float;
+ public var surfaceStart:Int32;
+ public var surfaceCount:Int32;
+ public var planeStart:Int32;
+ public var polyListPlaneStart:Int32;
+ public var polyListPointStart:Int32;
+ public var polyListStringStart:Int32;
+ public var staticMesh:Bool;
+
+ public function new() {
+ this.hullStart = 0;
+ this.hullCount = 0;
+ this.minX = 0;
+ this.minY = 0;
+ this.minZ = 0;
+ this.maxX = 0;
+ this.maxY = 0;
+ this.maxZ = 0;
+ this.surfaceStart = 0;
+ this.surfaceCount = 0;
+ this.planeStart = 0;
+ this.polyListPlaneStart = 0;
+ this.polyListPointStart = 0;
+ this.polyListStringStart = 0;
+ this.staticMesh = false;
+ }
+
+ public static function read(io:BytesReader, version:Version) {
+ var ret = new ConvexHull();
+ ret.hullStart = io.readInt32();
+ ret.hullCount = io.readUInt16();
+ ret.minX = io.readFloat();
+ ret.minY = io.readFloat();
+ ret.minZ = io.readFloat();
+ ret.maxX = io.readFloat();
+ ret.maxY = io.readFloat();
+ ret.maxZ = io.readFloat();
+ ret.surfaceStart = io.readInt32();
+ ret.surfaceCount = io.readUInt16();
+ ret.planeStart = io.readInt32();
+ ret.polyListPlaneStart = io.readInt32();
+ ret.polyListPointStart = io.readInt32();
+ ret.polyListStringStart = io.readInt32();
+
+ if (version.interiorVersion >= 12) {
+ ret.staticMesh = io.readByte() > 0;
+ }
+ return ret;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.hullStart);
+ io.writeUInt16(this.hullCount);
+ io.writeFloat(this.minX);
+ io.writeFloat(this.minY);
+ io.writeFloat(this.minZ);
+ io.writeFloat(this.maxX);
+ io.writeFloat(this.maxY);
+ io.writeFloat(this.maxZ);
+ io.writeInt32(this.surfaceStart);
+ io.writeUInt16(this.surfaceCount);
+ io.writeInt32(this.planeStart);
+ io.writeInt32(this.polyListPlaneStart);
+ io.writeInt32(this.polyListPointStart);
+ io.writeInt32(this.polyListStringStart);
+
+ if (version.interiorVersion >= 12) {
+ io.writeByte(this.staticMesh ? 1 : 0);
+ }
+ }
+}
diff --git a/src/dif/CoordBin.hx b/src/dif/CoordBin.hx
new file mode 100644
index 00000000..9e2fcacd
--- /dev/null
+++ b/src/dif/CoordBin.hx
@@ -0,0 +1,28 @@
+package dif;
+
+import haxe.Int32;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class CoordBin {
+ public var binStart:Int32;
+ public var binCount:Int32;
+
+ public function new() {
+ this.binStart = 0;
+ this.binCount = 0;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new CoordBin();
+ ret.binStart = io.readInt32();
+ ret.binCount = io.readInt32();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.binStart);
+ io.writeInt32(this.binCount);
+ }
+}
diff --git a/src/dif/Dif.hx b/src/dif/Dif.hx
new file mode 100644
index 00000000..bc7a6a92
--- /dev/null
+++ b/src/dif/Dif.hx
@@ -0,0 +1,149 @@
+package dif;
+
+import haxe.io.Bytes;
+#if sys
+import sys.io.File;
+#end
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+#if js
+import js.lib.ArrayBuffer;
+#end
+#if python
+import python.Bytearray;
+#end
+#if cs
+import cs.NativeArray;
+#end
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class Dif {
+ public var difVersion:Int;
+ public var previewIncluded:Int;
+ public var interiors:Array;
+ public var subObjects:Array;
+ public var triggers:Array;
+ public var interiorPathfollowers:Array;
+ public var forceFields:Array;
+ public var aiSpecialNodes:Array;
+ public var vehicleCollision:VehicleCollision = null;
+ public var gameEntities:Array = null;
+
+ public function new() {}
+
+ #if sys
+ public static function Load(path:String) {
+ var f = File.read(path);
+ var bytes = f.readAll();
+ var br = new BytesReader(bytes);
+ return Dif.read(br);
+ }
+
+ public static function Save(dif:Dif, version:Version, path:String) {
+ var f = File.write(path);
+ var bw = new BytesWriter();
+ dif.write(bw, version);
+ f.write(bw.getBuffer());
+ }
+ #end
+
+ public static function LoadFromBuffer(buffer:haxe.io.Bytes) {
+ var br = new BytesReader(buffer);
+ return Dif.read(br);
+ }
+
+ public static function SaveToBuffer(dif:Dif, version:Version) {
+ var bw = new BytesWriter();
+ dif.write(bw, version);
+ return bw.getBuffer();
+ }
+
+ #if js
+ public static function LoadFromArrayBuffer(buffer:ArrayBuffer) {
+ var br = new BytesReader(Bytes.ofData(buffer));
+ return Dif.read(br);
+ }
+
+ public static function SaveToArrayBuffer(dif:Dif, version:Version) {
+ var bw = new BytesWriter();
+ dif.write(bw, version);
+ return bw.getBuffer().getData();
+ }
+ #end
+
+ #if python
+ public static function LoadFromByteArray(buffer:Bytearray) {
+ var br = new BytesReader(Bytes.ofData(buffer));
+ return Dif.read(br);
+ }
+
+ public static function SaveToByteArray(dif:Dif, version:Version) {
+ var bw = new BytesWriter();
+ dif.write(bw, version);
+ return bw.getBuffer().getData();
+ }
+ #end
+
+ #if cs
+ public static function LoadFromArray(buffer:cs.NativeArray) {
+ var br = new BytesReader(Bytes.ofData(buffer));
+ return Dif.read(br);
+ }
+
+ public static function SaveToArray(dif:Dif, version:Version) {
+ var bw = new BytesWriter();
+ dif.write(bw, version);
+ return bw.getBuffer().getData();
+ }
+ #end
+
+ public static function read(io:BytesReader) {
+ var ret = new Dif();
+ var version = new Version();
+ version.difVersion = io.readInt32();
+ ret.difVersion = version.difVersion;
+ ret.previewIncluded = io.readByte();
+ ret.interiors = io.readArray(io -> Interior.read(io, version));
+ ret.subObjects = io.readArray(io -> Interior.read(io, version));
+ ret.triggers = io.readArray(Trigger.read);
+ ret.interiorPathfollowers = io.readArray(InteriorPathFollower.read);
+ ret.forceFields = io.readArray(ForceField.read);
+ ret.aiSpecialNodes = io.readArray(AISpecialNode.read);
+ var readVehicleCollision = io.readInt32();
+ if (readVehicleCollision == 1)
+ ret.vehicleCollision = VehicleCollision.read(io, version);
+ var readGameEntities = io.readInt32();
+ if (readGameEntities == 2)
+ ret.gameEntities = io.readArray(GameEntity.read);
+
+ return ret;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.difVersion);
+ io.writeByte(this.previewIncluded);
+
+ io.writeArray(this.interiors, (io, p) -> p.write(io, version));
+ io.writeArray(this.subObjects, (io, p) -> p.write(io, version));
+ io.writeArray(this.triggers, (io, p) -> p.write(io));
+ io.writeArray(this.interiorPathfollowers, (io, p) -> p.write(io));
+ io.writeArray(this.forceFields, (io, p) -> p.write(io));
+ io.writeArray(this.aiSpecialNodes, (io, p) -> p.write(io));
+ if (this.vehicleCollision != null) {
+ io.writeInt32(1);
+ this.vehicleCollision.write(io, version);
+ } else {
+ io.writeInt32(0);
+ }
+ if (this.gameEntities != null) {
+ io.writeInt32(2);
+ io.writeArray(this.gameEntities, (io, p) -> p.write(io));
+ } else {
+ io.writeInt32(0);
+ }
+ io.writeInt32(0);
+ }
+}
diff --git a/src/dif/Edge.hx b/src/dif/Edge.hx
new file mode 100644
index 00000000..8918dd6f
--- /dev/null
+++ b/src/dif/Edge.hx
@@ -0,0 +1,31 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import haxe.Int32;
+
+@:expose
+class Edge {
+ public var pointIndex0:Int32;
+ public var pointIndex1:Int32;
+ public var surfaceIndex0:Int32;
+ public var surfaceIndex1:Int32;
+
+ public function new(pointIndex0, pointIndex1, surfaceIndex0, surfaceIndex1) {
+ this.pointIndex0 = pointIndex0;
+ this.pointIndex1 = pointIndex1;
+ this.surfaceIndex0 = surfaceIndex0;
+ this.surfaceIndex1 = surfaceIndex1;
+ }
+
+ public static function read(io:BytesReader, version:Version) {
+ return new Edge(io.readInt32(), io.readInt32(), io.readInt32(), io.readInt32());
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.pointIndex0);
+ io.writeInt32(this.pointIndex1);
+ io.writeInt32(this.surfaceIndex0);
+ io.writeInt32(this.surfaceIndex1);
+ }
+}
diff --git a/src/dif/Edge2.hx b/src/dif/Edge2.hx
new file mode 100644
index 00000000..8a068fa4
--- /dev/null
+++ b/src/dif/Edge2.hx
@@ -0,0 +1,48 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import haxe.Int32;
+
+@:expose
+class Edge2 {
+ public var vertex0:Int32;
+ public var vertex1:Int32;
+ public var normal0:Int32;
+ public var normal1:Int32;
+ public var face0:Int32;
+ public var face1:Int32;
+
+ public function new() {
+ this.vertex0 = 0;
+ this.vertex1 = 0;
+ this.normal0 = 0;
+ this.normal1 = 0;
+ this.face0 = 0;
+ this.face1 = 0;
+ }
+
+ public static function read(io:BytesReader, version:Version) {
+ var ret = new Edge2();
+ ret.vertex0 = io.readInt32();
+ ret.vertex1 = io.readInt32();
+ ret.normal0 = io.readInt32();
+ ret.normal1 = io.readInt32();
+ if (version.interiorVersion >= 3) {
+ ret.face0 = io.readInt32();
+ ret.face1 = io.readInt32();
+ }
+ return ret;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.vertex0);
+ io.writeInt32(this.vertex1);
+ io.writeInt32(this.normal0);
+ io.writeInt32(this.normal1);
+ if (version.interiorVersion >= 3) {
+ io.writeInt32(this.face0);
+ io.writeInt32(this.face1);
+ }
+ }
+}
diff --git a/src/dif/FFSurface.hx b/src/dif/FFSurface.hx
new file mode 100644
index 00000000..d43c9e2d
--- /dev/null
+++ b/src/dif/FFSurface.hx
@@ -0,0 +1,39 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class FFSurface {
+ public var windingStart:Int;
+ public var windingCount:Int;
+ public var planeIndex:Int;
+ public var surfaceFlags:Int;
+ public var fanMask:Int;
+
+ public function new() {
+ this.windingStart = 0;
+ this.windingCount = 0;
+ this.planeIndex = 0;
+ this.surfaceFlags = 0;
+ this.fanMask = 0;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new FFSurface();
+ ret.windingStart = io.readInt32();
+ ret.windingCount = io.readByte();
+ ret.planeIndex = io.readInt16();
+ ret.surfaceFlags = io.readByte();
+ ret.fanMask = io.readInt32();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.windingStart);
+ io.writeByte(this.windingCount);
+ io.writeInt16(this.planeIndex);
+ io.writeByte(this.surfaceFlags);
+ io.writeInt32(this.fanMask);
+ }
+}
diff --git a/src/dif/ForceField.hx b/src/dif/ForceField.hx
new file mode 100644
index 00000000..52aad780
--- /dev/null
+++ b/src/dif/ForceField.hx
@@ -0,0 +1,63 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.Point3F;
+import dif.math.SphereF.Spheref;
+import dif.math.Box3F;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class ForceField {
+ public var forceFieldFileVersion:Int;
+ public var name:String;
+ public var triggers:Array;
+ public var boundingBox:Box3F;
+ public var boundingSphere:Spheref;
+ public var normals:Array;
+ public var planes:Array;
+ public var bspNodes:Array;
+ public var bspSolidLeaves:Array;
+ public var windings:Array;
+ public var surfaces:Array;
+ public var solidLeafSurfaces:Array;
+ public var color:Array;
+
+ public function new() {}
+
+ public static function read(io:BytesReader) {
+ var ret = new ForceField();
+ ret.forceFieldFileVersion = io.readInt32();
+ ret.name = io.readStr();
+ ret.triggers = io.readArray(io -> io.readStr());
+ ret.boundingBox = Box3F.read(io);
+ ret.boundingSphere = Spheref.read(io);
+ ret.normals = io.readArray(Point3F.read);
+ ret.planes = io.readArray(Plane.read);
+ ret.bspNodes = io.readArray(io -> BSPNode.read(io, new Version()));
+ ret.bspSolidLeaves = io.readArray(BSPSolidLeaf.read);
+ ret.windings = io.readArray(io -> io.readInt32());
+ ret.surfaces = io.readArray(FFSurface.read);
+ ret.solidLeafSurfaces = io.readArray(io -> io.readInt32());
+ ret.color = io.readColorF();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.forceFieldFileVersion);
+ io.writeStr(this.name);
+ io.writeArray(this.triggers, (io, p) -> io.writeStr(p));
+ this.boundingBox.write(io);
+ this.boundingSphere.write(io);
+ io.writeArray(this.normals, (io, p) -> p.write(io));
+ io.writeArray(this.planes, (io, p) -> p.write(io));
+ io.writeArray(this.bspNodes, (io, p) -> p.write(io, new Version()));
+ io.writeArray(this.bspSolidLeaves, (io, p) -> p.write(io));
+ io.writeArray(this.windings, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.surfaces, (io, p) -> p.write(io));
+ io.writeArray(this.solidLeafSurfaces, (io, p) -> io.writeInt32(p));
+ io.writeColorF(this.color);
+ }
+}
diff --git a/src/dif/GameEntity.hx b/src/dif/GameEntity.hx
new file mode 100644
index 00000000..d99eb708
--- /dev/null
+++ b/src/dif/GameEntity.hx
@@ -0,0 +1,42 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.Point4F;
+import haxe.xml.Access;
+import haxe.ds.StringMap;
+import dif.math.Point3F;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class GameEntity {
+ public var datablock:String;
+ public var gameClass:String;
+ public var position:Point3F;
+ public var properties:StringMap;
+
+ public function new() {
+ this.datablock = "";
+ this.gameClass = "";
+ this.position = new Point3F();
+ this.properties = new StringMap();
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new GameEntity();
+ ret.datablock = io.readStr();
+ ret.gameClass = io.readStr();
+ ret.position = Point3F.read(io);
+ ret.properties = io.readDictionary();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeStr(this.datablock);
+ io.writeStr(this.gameClass);
+ this.position.write(io);
+ io.writeDictionary(this.properties);
+ }
+}
diff --git a/src/dif/Interior.hx b/src/dif/Interior.hx
new file mode 100644
index 00000000..f6fc2e18
--- /dev/null
+++ b/src/dif/Interior.hx
@@ -0,0 +1,328 @@
+package dif;
+
+import dif.math.Point4F;
+import dif.math.Point3F;
+import dif.math.SphereF.Spheref;
+import dif.math.Box3F;
+import haxe.io.BytesBuffer;
+import haxe.io.Bytes;
+import haxe.Int32;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class Interior {
+ public var detailLevel:Int;
+ public var minPixels:Int;
+ public var boundingBox:Box3F;
+ public var boundingSphere:Spheref;
+ public var hasAlarmState:Int;
+ public var numLightStateEntries:Int;
+ public var normals:Array;
+ public var planes:Array;
+ public var points:Array;
+ public var pointVisibilities:Array;
+ public var texGenEQs:Array;
+ public var bspNodes:Array;
+ public var bspSolidLeaves:Array;
+ public var materialListVersion:Int;
+ public var materialList:Array;
+ public var windings:Array;
+ public var windingIndices:Array;
+ public var edges:Array;
+ public var zones:Array;
+ public var zoneSurfaces:Array;
+ public var zoneStaticMeshes:Array;
+ public var zonePortalList:Array;
+ public var portals:Array;
+ public var surfaces:Array;
+ public var edges2:Array;
+ public var normals2:Array;
+ public var normalIndices:Array;
+ public var normalLMapIndices:Array;
+ public var alarmLMapIndices:Array;
+ public var nullSurfaces:Array;
+ public var lightMaps:Array;
+ public var solidLeafSurfaces:Array;
+ public var animatedLights:Array;
+ public var lightStates:Array;
+ public var stateDatas:Array;
+ public var stateDataFlags:Int;
+ public var stateDataBuffers:Array;
+ public var nameBuffer:Array;
+ public var numSubObjects:Int;
+ public var convexHulls:Array;
+ public var convexHullEmitStrings:Array;
+ public var hullIndices:Array;
+ public var hullPlaneIndices:Array;
+ public var hullEmitStringIndices:Array;
+ public var hullSurfaceIndices:Array;
+ public var polyListPlanes:Array;
+ public var polyListPoints:Array;
+ public var polyListStrings:Array;
+ public var coordBins:Array;
+ public var coordBinIndices:Array;
+ public var coordBinMode:Int;
+ public var baseAmbientColor:Array;
+ public var alarmAmbientColor:Array;
+ public var numStaticMeshes:Int;
+ public var texNormals:Array;
+ public var texMatrices:Array;
+ public var texMatIndices:Array;
+ public var extendedLightMapData:Int;
+ public var lightMapBorderSize:Int;
+
+ public function new() {}
+
+ public static function read(io:BytesReader, version:Version) {
+ if (version.interiorType == "?")
+ version.interiorType = "tgea";
+
+ version.interiorVersion = io.readInt32();
+ var it = new Interior();
+ it.detailLevel = io.readInt32();
+ it.minPixels = io.readInt32();
+ it.boundingBox = Box3F.read(io);
+ it.boundingSphere = Spheref.read(io);
+ it.hasAlarmState = io.readByte();
+ it.numLightStateEntries = io.readInt32();
+ it.normals = io.readArray(Point3F.read);
+ it.planes = io.readArray(Plane.read);
+ it.points = io.readArray(Point3F.read);
+ if (version.interiorVersion == 4) {
+ it.pointVisibilities = new Array();
+ } else {
+ it.pointVisibilities = io.readArray((io) -> {
+ return io.readByte();
+ });
+ }
+ it.texGenEQs = io.readArray(TexGenEQ.read);
+ it.bspNodes = io.readArray((io) -> {
+ return BSPNode.read(io, version);
+ });
+ it.bspSolidLeaves = io.readArray(BSPSolidLeaf.read);
+ it.materialListVersion = io.readByte();
+ it.materialList = io.readArray((io) -> io.readStr());
+ it.windings = io.readArrayAs((signed, param) -> param > 0, (io) -> io.readInt32(), io -> io.readInt16());
+ it.windingIndices = io.readArray(WindingIndex.read);
+ if (version.interiorVersion >= 12) {
+ it.edges = io.readArray(io -> Edge.read(io, version));
+ }
+ it.zones = io.readArray(io -> Zone.read(io, version));
+ it.zoneSurfaces = io.readArrayAs((signed, param) -> false, io -> io.readInt16(), io -> io.readInt16());
+ if (version.interiorVersion >= 12) {
+ it.zoneStaticMeshes = io.readArray(io -> io.readInt32());
+ }
+ it.zonePortalList = io.readArrayAs((signed, param) -> false, io -> io.readInt16(), io -> io.readInt16());
+ it.portals = io.readArray(Portal.read);
+
+ var pos = io.tell();
+ try {
+ it.surfaces = io.readArray(io -> Surface.read(io, version, it));
+ if (version.interiorType == "?") {
+ version.interiorType = "tge";
+ }
+ } catch (e) {
+ if (version.interiorType == "tgea")
+ version.interiorType = "tge";
+ io.seek(pos);
+ try {
+ it.surfaces = io.readArray(io -> Surface.read(io, version, it));
+ } catch (e) {}
+ }
+
+ if (version.interiorVersion >= 2 && version.interiorVersion <= 5) {
+ it.edges2 = io.readArray(io -> Edge2.read(io, version));
+ if (version.interiorVersion >= 4 && version.interiorVersion <= 5) {
+ it.normals2 = io.readArray(Point3F.read);
+ it.normalIndices = io.readArrayAs((alt, param) -> alt && param == 0, io -> io.readUInt16(), io -> io.readByte());
+ }
+ }
+
+ if (version.interiorVersion == 4) {
+ it.normalLMapIndices = io.readArray(io -> io.readByte());
+ it.alarmLMapIndices = new Array();
+ } else if (version.interiorVersion >= 13) {
+ it.normalLMapIndices = io.readArray(io -> io.readInt32());
+ it.alarmLMapIndices = io.readArray(io -> io.readInt32());
+ } else {
+ it.normalLMapIndices = io.readArray(io -> io.readByte());
+ it.alarmLMapIndices = io.readArray(io -> io.readByte());
+ }
+
+ it.nullSurfaces = io.readArray(io -> NullSurface.read(io, version));
+ if (version.interiorVersion != 4) {
+ it.lightMaps = io.readArray(io -> LightMap.read(io, version));
+ if (it.lightMaps.length > 0 && version.interiorType == "mbg") {
+ version.interiorType = "tge";
+ }
+ } else {
+ it.lightMaps = new Array();
+ }
+ it.solidLeafSurfaces = io.readArrayAs((alt, void) -> alt, io -> io.readInt32(), io -> io.readUInt16());
+ it.animatedLights = io.readArray(AnimatedLight.read);
+ it.lightStates = io.readArray(LightState.read);
+
+ if (version.interiorVersion == 4) {
+ it.stateDatas = new Array();
+ it.stateDataFlags = 0;
+ it.stateDataBuffers = new Array();
+ it.numSubObjects = 0;
+ } else {
+ it.stateDatas = io.readArray(StateData.read);
+ it.stateDataBuffers = io.readArrayFlags(io -> io.readByte());
+ it.nameBuffer = io.readArray(io -> io.readByte());
+ it.stateDataFlags = 0;
+ it.numSubObjects = io.readInt32();
+ }
+
+ it.convexHulls = io.readArray((io) -> ConvexHull.read(io, version));
+ it.convexHullEmitStrings = io.readArray(io -> io.readByte());
+ it.hullIndices = io.readArrayAs((alt, that) -> alt, io -> io.readInt32(), io -> io.readUInt16());
+ it.hullPlaneIndices = io.readArrayAs((alt, that) -> true, io -> io.readUInt16(), io -> io.readUInt16());
+ it.hullEmitStringIndices = io.readArrayAs((alt, that) -> alt, io -> io.readInt32(), io -> io.readUInt16());
+ it.hullSurfaceIndices = io.readArrayAs((alt, that) -> alt, io -> io.readInt32(), io -> io.readUInt16());
+ it.polyListPlanes = io.readArrayAs((alt, that) -> true, io -> io.readUInt16(), io -> io.readUInt16());
+ it.polyListPoints = io.readArrayAs((alt, that) -> alt, io -> io.readInt32(), io -> io.readUInt16());
+ it.polyListStrings = io.readArray(io -> io.readByte());
+
+ it.coordBins = new Array();
+ for (i in 0...256) {
+ it.coordBins.push(CoordBin.read(io));
+ }
+
+ it.coordBinIndices = io.readArrayAs((a, b) -> true, io -> io.readUInt16(), io -> io.readUInt16());
+ it.coordBinMode = io.readInt32();
+
+ if (version.interiorVersion == 4) {
+ it.baseAmbientColor = [0, 0, 0, 255];
+ it.alarmAmbientColor = [0, 0, 0, 255];
+ it.extendedLightMapData = 0;
+ it.lightMapBorderSize = 0;
+ } else {
+ it.baseAmbientColor = io.readColorF();
+ it.alarmAmbientColor = io.readColorF();
+ if (version.interiorVersion >= 10) {
+ it.numStaticMeshes = io.readInt32();
+ }
+ if (version.interiorVersion >= 11) {
+ it.texNormals = io.readArray(Point3F.read);
+ it.texMatrices = io.readArray(TexMatrix.read);
+ it.texMatIndices = io.readArray(io -> io.readInt32());
+ } else {
+ io.readInt32();
+ io.readInt32();
+ io.readInt32();
+ }
+ it.extendedLightMapData = io.readInt32();
+ if (it.extendedLightMapData > 0) {
+ it.lightMapBorderSize = io.readInt32();
+ io.readInt32();
+ }
+ }
+
+ return it;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.detailLevel);
+ io.writeInt32(this.minPixels);
+ this.boundingBox.write(io);
+ this.boundingSphere.write(io);
+ io.writeByte(this.hasAlarmState);
+ io.writeInt32(this.numLightStateEntries);
+ io.writeArray(this.normals, (io, p) -> p.write(io));
+ io.writeArray(this.planes, (io, p) -> p.write(io));
+ io.writeArray(this.points, (io, p) -> p.write(io));
+ if (version.interiorVersion != 4) {
+ io.writeArray(this.pointVisibilities, (io, p) -> io.writeByte(p));
+ }
+ io.writeArray(this.texGenEQs, (io, p) -> p.write(io));
+ io.writeArray(this.bspNodes, (io, p) -> p.write(io, version));
+ io.writeArray(this.bspSolidLeaves, (io, p) -> p.write(io));
+ io.writeByte(this.materialListVersion);
+ io.writeArray(this.materialList, (io, p) -> io.writeStr(p));
+ io.writeArray(this.windings, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.windingIndices, (io, p) -> p.write(io));
+ if (version.interiorVersion >= 12)
+ io.writeArray(this.edges, (io, p) -> p.write(io, version));
+ io.writeArray(this.zones, (io, p) -> p.write(io, version));
+ io.writeArray(this.zoneSurfaces, (io, p) -> io.writeUInt16(p));
+ if (version.interiorVersion >= 12)
+ io.writeArray(this.zoneStaticMeshes, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.zonePortalList, (io, p) -> io.writeUInt16(p));
+ io.writeArray(this.portals, (io, p) -> p.write(io));
+ io.writeArray(this.surfaces, (io, p) -> p.write(io, version));
+ if (version.interiorVersion >= 2 && version.interiorVersion <= 5) {
+ io.writeArray(this.edges2, (io, p) -> p.write(io, version));
+ if (version.interiorVersion >= 4 && version.interiorVersion <= 5) {
+ io.writeArray(this.normals2, (io, p) -> p.write(io));
+ io.writeArray(this.normalIndices, (io, p) -> io.writeUInt16(p));
+ }
+ }
+ if (version.interiorVersion == 4) {
+ io.writeArray(this.normalLMapIndices, (io, p) -> io.writeByte(p));
+ } else if (version.interiorVersion >= 13) {
+ io.writeArray(this.normalLMapIndices, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.normalLMapIndices, (io, p) -> io.writeInt32(p));
+ } else {
+ io.writeArray(this.normalLMapIndices, (io, p) -> io.writeByte(p));
+ io.writeArray(this.normalLMapIndices, (io, p) -> io.writeByte(p));
+ }
+
+ io.writeArray(this.nullSurfaces, (io, p) -> p.write(io, version));
+ if (version.interiorVersion != 4) {
+ io.writeArray(this.lightMaps, (io, p) -> p.writeLightMap(io, version));
+ }
+ io.writeArray(this.solidLeafSurfaces, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.animatedLights, (io, p) -> p.write(io));
+ io.writeArray(this.lightStates, (io, p) -> p.write(io));
+ if (version.interiorVersion != 4) {
+ io.writeArray(this.stateDatas, (io, p) -> p.write(io));
+ io.writeArrayFlags(this.stateDataBuffers, this.stateDataFlags, (io, p) -> io.writeByte(p));
+ io.writeArray(this.nameBuffer, (io, p) -> io.writeByte(p));
+ io.writeInt32(this.numSubObjects);
+ }
+
+ io.writeArray(this.convexHulls, (io, p) -> p.write(io, version));
+ io.writeArray(this.convexHullEmitStrings, (io, p) -> io.writeByte(p));
+ io.writeArray(this.hullIndices, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.hullPlaneIndices, (io, p) -> io.writeInt16(p));
+ io.writeArray(this.hullEmitStringIndices, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.hullSurfaceIndices, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.polyListPlanes, (io, p) -> io.writeInt16(p));
+ io.writeArray(this.polyListPoints, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.polyListStrings, (io, p) -> io.writeByte(p));
+
+ for (i in 0...256) {
+ this.coordBins[i].write(io);
+ }
+
+ io.writeArray(this.coordBinIndices, (io, p) -> io.writeInt16(p));
+ io.writeInt32(this.coordBinMode);
+
+ if (version.interiorVersion != 4) {
+ io.writeColorF(this.baseAmbientColor);
+ io.writeColorF(this.alarmAmbientColor);
+ if (version.interiorVersion >= 10)
+ io.writeInt32(this.numStaticMeshes);
+ if (version.interiorVersion >= 11) {
+ io.writeArray(this.texNormals, (io, p) -> p.write(io));
+ io.writeArray(this.texNormals, (io, p) -> p.write(io));
+ io.writeArray(this.texMatIndices, (io, p) -> io.writeInt32(p));
+ } else {
+ io.writeInt32(0);
+ io.writeInt32(0);
+ io.writeInt32(0);
+ }
+ io.writeInt32(this.extendedLightMapData);
+ if (this.extendedLightMapData > 0) {
+ io.writeInt32(this.lightMapBorderSize);
+ io.writeInt32(0);
+ }
+ }
+ }
+}
diff --git a/src/dif/InteriorPathFollower.hx b/src/dif/InteriorPathFollower.hx
new file mode 100644
index 00000000..0cf6cd48
--- /dev/null
+++ b/src/dif/InteriorPathFollower.hx
@@ -0,0 +1,57 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import haxe.xml.Access;
+import haxe.ds.StringMap;
+import dif.math.Point3F;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class InteriorPathFollower {
+ public var name:String;
+ public var datablock:String;
+ public var interiorResIndex:Int;
+ public var offset:Point3F;
+ public var properties:StringMap;
+ public var triggerId:Array;
+ public var wayPoint:Array;
+ public var totalMS:Int;
+
+ public function new() {
+ this.name = "";
+ this.datablock = "";
+ this.interiorResIndex = 0;
+ this.offset = new Point3F();
+ this.properties = new StringMap();
+ this.triggerId = new Array();
+ this.wayPoint = new Array();
+ this.totalMS = 0;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new InteriorPathFollower();
+ ret.name = io.readStr();
+ ret.datablock = io.readStr();
+ ret.interiorResIndex = io.readInt32();
+ ret.offset = Point3F.read(io);
+ ret.properties = io.readDictionary();
+ ret.triggerId = io.readArray(io -> io.readInt32());
+ ret.wayPoint = io.readArray(WayPoint.read);
+ ret.totalMS = io.readInt32();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeStr(this.name);
+ io.writeStr(this.datablock);
+ io.writeInt32(this.interiorResIndex);
+ this.offset.write(io);
+ io.writeDictionary(this.properties);
+ io.writeArray(this.triggerId, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.wayPoint, (io, p) -> p.write(io));
+ io.writeInt32(this.totalMS);
+ }
+}
diff --git a/src/dif/LightMap.hx b/src/dif/LightMap.hx
new file mode 100644
index 00000000..c9b6bda5
--- /dev/null
+++ b/src/dif/LightMap.hx
@@ -0,0 +1,41 @@
+package dif;
+
+import haxe.io.Bytes;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import haxe.Int32;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class LightMap {
+ public var lightmap:Array;
+ public var lightdirmap:Array;
+ public var keepLightMap:Int;
+
+ public function new() {
+ this.lightmap = new Array();
+ this.lightdirmap = new Array();
+ this.keepLightMap = 0;
+ }
+
+ public static function read(io:BytesReader, version:Version) {
+ var ret = new LightMap();
+ ret.lightmap = io.readPNG();
+
+ if (version.interiorType != "mbg" && version.interiorType != "tge") {
+ ret.lightdirmap = io.readPNG();
+ }
+ ret.keepLightMap = io.readByte();
+ return ret;
+ }
+
+ public function writeLightMap(io:BytesWriter, version:Version) {
+ io.writePNG(this.lightmap);
+ if (version.interiorType != "mbg" && version.interiorType != "tge") {
+ io.writePNG(this.lightdirmap);
+ }
+ io.writeByte(this.keepLightMap);
+ }
+}
diff --git a/src/dif/LightState.hx b/src/dif/LightState.hx
new file mode 100644
index 00000000..2a5908cb
--- /dev/null
+++ b/src/dif/LightState.hx
@@ -0,0 +1,37 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import haxe.Int32;
+
+@:expose
+class LightState {
+ public var red:Int;
+ public var green:Int;
+ public var blue:Int;
+ public var activeTime:Int32;
+ public var dataIndex:Int32;
+ public var dataCount:Int;
+
+ public function new(red:Int, green:Int, blue:Int, activeTime:Int32, dataIndex:Int32, dataCount:Int) {
+ this.red = red;
+ this.green = green;
+ this.blue = blue;
+ this.activeTime = activeTime;
+ this.dataIndex = dataIndex;
+ this.dataCount = dataCount;
+ }
+
+ public static function read(io:BytesReader) {
+ return new LightState(io.readByte(), io.readByte(), io.readByte(), io.readInt32(), io.readInt32(), io.readInt16());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeByte(this.red);
+ io.writeByte(this.green);
+ io.writeByte(this.blue);
+ io.writeInt32(this.activeTime);
+ io.writeInt32(this.dataIndex);
+ io.writeInt16(this.dataCount);
+ }
+}
diff --git a/src/dif/NullSurface.hx b/src/dif/NullSurface.hx
new file mode 100644
index 00000000..bd4e7a06
--- /dev/null
+++ b/src/dif/NullSurface.hx
@@ -0,0 +1,43 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class NullSurface {
+ public var windingStart:Int;
+ public var planeIndex:Int;
+ public var surfaceFlags:Int;
+ public var windingCount:Int;
+
+ public function new() {
+ this.windingStart = 0;
+ this.planeIndex = 0;
+ this.surfaceFlags = 0;
+ this.windingCount = 0;
+ }
+
+ public static function read(io:BytesReader, version:Version) {
+ var ret = new NullSurface();
+ ret.windingStart = io.readInt32();
+ ret.planeIndex = io.readUInt16();
+ ret.surfaceFlags = io.readByte();
+ if (version.interiorVersion >= 13) {
+ ret.windingCount = io.readInt32();
+ } else {
+ ret.windingCount = io.readByte();
+ }
+ return ret;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.windingStart);
+ io.writeUInt16(this.planeIndex);
+ io.writeByte(this.surfaceFlags);
+ if (version.interiorVersion >= 13) {
+ io.writeInt32(this.windingCount);
+ } else {
+ io.writeByte(this.windingCount);
+ }
+ }
+}
diff --git a/src/dif/Plane.hx b/src/dif/Plane.hx
new file mode 100644
index 00000000..33501c89
--- /dev/null
+++ b/src/dif/Plane.hx
@@ -0,0 +1,24 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Plane {
+ public var normalIndex:Int;
+ public var planeDistance:Float;
+
+ public function new(normalIndex, planeDistance) {
+ this.normalIndex = normalIndex;
+ this.planeDistance = planeDistance;
+ }
+
+ public static function read(io:BytesReader) {
+ return new Plane(io.readInt16(), io.readFloat());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt16(this.normalIndex);
+ io.writeFloat(this.planeDistance);
+ }
+}
diff --git a/src/dif/Polyhedron.hx b/src/dif/Polyhedron.hx
new file mode 100644
index 00000000..92c7f998
--- /dev/null
+++ b/src/dif/Polyhedron.hx
@@ -0,0 +1,36 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.PlaneF;
+import dif.math.Point3F;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class Polyhedron {
+ public var pointList:Array;
+ public var planeList:Array;
+ public var edgeList:Array;
+
+ public function new() {
+ this.pointList = new Array();
+ this.planeList = new Array();
+ this.edgeList = new Array();
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new Polyhedron();
+ ret.pointList = io.readArray(Point3F.read);
+ ret.planeList = io.readArray(PlaneF.read);
+ ret.edgeList = io.readArray(PolyhedronEdge.read);
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeArray(this.pointList, (io, p) -> p.write(io));
+ io.writeArray(this.planeList, (io, p) -> p.write(io));
+ io.writeArray(this.edgeList, (io, p) -> p.write(io));
+ }
+}
diff --git a/src/dif/PolyhedronEdge.hx b/src/dif/PolyhedronEdge.hx
new file mode 100644
index 00000000..5cee287f
--- /dev/null
+++ b/src/dif/PolyhedronEdge.hx
@@ -0,0 +1,30 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class PolyhedronEdge {
+ public var pointIndex0:Int;
+ public var pointIndex1:Int;
+ public var faceIndex0:Int;
+ public var faceIndex1:Int;
+
+ public function new(faceIndex0, faceIndex1, pointIndex0, pointIndex1) {
+ this.pointIndex0 = pointIndex0;
+ this.pointIndex1 = pointIndex1;
+ this.faceIndex0 = faceIndex0;
+ this.faceIndex1 = faceIndex1;
+ }
+
+ public static function read(io:BytesReader) {
+ return new PolyhedronEdge(io.readInt32(), io.readInt32(), io.readInt32(), io.readInt32());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.faceIndex0);
+ io.writeInt32(this.faceIndex1);
+ io.writeInt32(this.pointIndex0);
+ io.writeInt32(this.pointIndex1);
+ }
+}
diff --git a/src/dif/Portal.hx b/src/dif/Portal.hx
new file mode 100644
index 00000000..b8a199a5
--- /dev/null
+++ b/src/dif/Portal.hx
@@ -0,0 +1,33 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Portal {
+ public var planeIndex:Int;
+ public var triFanCount:Int;
+ public var triFanStart:Int;
+ public var zoneFront:Int;
+ public var zoneBack:Int;
+
+ public function new(planeIndex, triFanCount, triFanStart, zoneFront, zoneBack) {
+ this.planeIndex = planeIndex;
+ this.triFanCount = triFanCount;
+ this.triFanStart = triFanStart;
+ this.zoneFront = zoneFront;
+ this.zoneBack = zoneBack;
+ }
+
+ public static function read(io:BytesReader) {
+ return new Portal(io.readUInt16(), io.readUInt16(), io.readInt32(), io.readUInt16(), io.readUInt16());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeUInt16(this.planeIndex);
+ io.writeUInt16(this.triFanCount);
+ io.writeInt32(this.triFanStart);
+ io.writeUInt16(this.zoneFront);
+ io.writeUInt16(this.zoneBack);
+ }
+}
diff --git a/src/dif/ReaderExtensions.hx b/src/dif/ReaderExtensions.hx
new file mode 100644
index 00000000..6d85cd94
--- /dev/null
+++ b/src/dif/ReaderExtensions.hx
@@ -0,0 +1,91 @@
+package dif;
+
+import dif.io.BytesReader;
+import haxe.ds.StringMap;
+
+class ReaderExtensions {
+ public static function readDictionary(io:BytesReader) {
+ var len = io.readInt32();
+ var dict = new StringMap();
+
+ for (i in 0...len) {
+ var name = io.readStr();
+ var value = io.readStr();
+ dict.set(name, value);
+ }
+
+ return dict;
+ }
+
+ public static function readArray(io:BytesReader, readMethod:BytesReader->V):Array {
+ var len = io.readInt32();
+ var arr = new Array();
+
+ for (i in 0...len) {
+ arr.push(readMethod(io));
+ }
+
+ return arr;
+ }
+
+ public static function readArrayAs(io:BytesReader, test:(Bool, Int) -> Bool, failMethod:BytesReader->V, passMethod:BytesReader->V) {
+ var length = io.readInt32();
+ var signed = false;
+ var param = 0;
+
+ if ((length & 0x80000000) > 0) {
+ length ^= 0x80000000;
+ signed = true;
+ param = io.readInt32();
+ }
+
+ var array = new Array();
+ for (i in 0...length) {
+ if (test(signed, param)) {
+ array.push(passMethod(io));
+ } else {
+ array.push(failMethod(io));
+ }
+ }
+
+ return array;
+ }
+
+ public static function readArrayFlags(io:BytesReader, readMethod:BytesReader->V) {
+ var length = io.readInt32();
+ var flags = io.readInt32();
+ var array = new Array();
+ for (i in 0...length) {
+ array.push(readMethod(io));
+ }
+ return array;
+ };
+
+ public static function readPNG(io:BytesReader) {
+ var footer = [0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82];
+
+ // I can't parse these, so I just read em all
+ var data = [];
+ while (true) {
+ data.push(io.readByte());
+
+ if (data.length >= 8) {
+ var match = true;
+ for (i in 0...8) {
+ if (data[i + (data.length - 8)] != footer[i]) {
+ match = false;
+ break;
+ }
+ }
+ if (match)
+ break;
+ }
+ }
+
+ return data;
+ };
+
+ public static function readColorF(io:BytesReader) {
+ return [io.readByte(), io.readByte(), io.readByte(), io.readByte()];
+ }
+}
diff --git a/src/dif/StateData.hx b/src/dif/StateData.hx
new file mode 100644
index 00000000..cdbc54bd
--- /dev/null
+++ b/src/dif/StateData.hx
@@ -0,0 +1,28 @@
+package dif;
+
+import haxe.Int32;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class StateData {
+ public var surfaceIndex:Int32;
+ public var mapIndex:Int32;
+ public var lightStateIndex:Int32;
+
+ public function new(surfaceIndex:Int32, mapIndex:Int32, lightStateIndex:Int32) {
+ this.surfaceIndex = surfaceIndex;
+ this.mapIndex = mapIndex;
+ this.lightStateIndex = lightStateIndex;
+ }
+
+ public static function read(io:BytesReader) {
+ return new StateData(io.readInt32(), io.readInt32(), io.readInt32());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.surfaceIndex);
+ io.writeInt32(this.mapIndex);
+ io.writeInt32(this.lightStateIndex);
+ }
+}
diff --git a/src/dif/Surface.hx b/src/dif/Surface.hx
new file mode 100644
index 00000000..e695d1ae
--- /dev/null
+++ b/src/dif/Surface.hx
@@ -0,0 +1,149 @@
+package dif;
+
+import haxe.Exception;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Surface {
+ public var windingStart:Int;
+ public var windingCount:Int;
+ public var planeIndex:Int;
+ public var textureIndex:Int;
+ public var texGenIndex:Int;
+ public var surfaceFlags:Int;
+ public var fanMask:Int;
+ public var lightMapFinalWord:Int;
+ public var lightMapTexGenXD:Float;
+ public var lightMapTexGenYD:Float;
+ public var lightCount:Int;
+ public var lightStateInfoStart:Int;
+ public var mapOffsetX:Int;
+ public var mapOffsetY:Int;
+ public var mapSizeX:Int;
+ public var mapSizeY:Int;
+ public var brushId:Int;
+
+ public function new() {
+ this.windingStart = 0;
+ this.windingCount = 0;
+ this.planeIndex = 0;
+ this.textureIndex = 0;
+ this.texGenIndex = 0;
+ this.surfaceFlags = 0;
+ this.fanMask = 0;
+ this.lightMapFinalWord = 0;
+ this.lightMapTexGenXD = 0;
+ this.lightMapTexGenYD = 0;
+ this.lightCount = 0;
+ this.lightStateInfoStart = 0;
+ this.mapOffsetX = 0;
+ this.mapOffsetY = 0;
+ this.mapSizeX = 0;
+ this.mapSizeY = 0;
+ this.brushId = 0;
+ }
+
+ public static function read(io:BytesReader, version:Version, interior:Interior) {
+ var ret = new Surface();
+ ret.windingStart = io.readInt32();
+
+ if (interior.windings.length <= ret.windingStart)
+ throw new Exception("DIF Type Error");
+
+ if (version.interiorVersion >= 13)
+ ret.windingCount = io.readInt32();
+ else
+ ret.windingCount = io.readByte();
+
+ if (ret.windingStart + ret.windingCount > interior.windings.length) {
+ throw new Exception("DIF Type Error");
+ }
+
+ ret.planeIndex = io.readInt16();
+ var flipped = (ret.planeIndex >> 15 != 0);
+ var planeIndexTemp = ret.planeIndex & ~0x8000;
+
+ if ((planeIndexTemp & ~0x8000) >= interior.planes.length) {
+ throw new Exception("DIF Type Error");
+ }
+
+ ret.textureIndex = io.readInt16();
+
+ if (ret.textureIndex >= interior.materialList.length) {
+ throw new Exception("DIF Type Error");
+ }
+
+ ret.texGenIndex = io.readInt32();
+
+ if (ret.texGenIndex >= interior.texGenEQs.length) {
+ throw new Exception("DIF Type Error");
+ }
+
+ ret.surfaceFlags = io.readByte();
+ ret.fanMask = io.readInt32();
+ ret.lightMapFinalWord = io.readInt16();
+ ret.lightMapTexGenXD = io.readFloat();
+ ret.lightMapTexGenYD = io.readFloat();
+ ret.lightCount = io.readInt16();
+ ret.lightStateInfoStart = io.readInt32();
+
+ if (version.interiorVersion >= 13) {
+ ret.mapOffsetX = io.readInt32();
+ ret.mapOffsetY = io.readInt32();
+ ret.mapSizeX = io.readInt32();
+ ret.mapSizeY = io.readInt32();
+ } else {
+ ret.mapOffsetX = io.readByte();
+ ret.mapOffsetY = io.readByte();
+ ret.mapSizeX = io.readByte();
+ ret.mapSizeY = io.readByte();
+ }
+
+ if (version.interiorType != "tge" && version.interiorType != "mbg") {
+ io.readByte();
+ if (version.interiorVersion >= 2 && version.interiorVersion <= 5)
+ ret.brushId = io.readInt32();
+ }
+
+ return ret;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.windingStart);
+
+ if (version.interiorVersion >= 13)
+ io.writeInt32(this.windingCount);
+ else
+ io.writeByte(this.windingCount);
+
+ io.writeInt16(this.planeIndex);
+ io.writeInt16(this.textureIndex);
+ io.writeInt32(this.texGenIndex);
+ io.writeByte(this.surfaceFlags);
+ io.writeInt32(this.fanMask);
+ io.writeInt16(this.lightMapFinalWord);
+ io.writeFloat(this.lightMapTexGenXD);
+ io.writeFloat(this.lightMapTexGenYD);
+ io.writeInt16(this.lightCount);
+ io.writeInt32(this.lightStateInfoStart);
+
+ if (version.interiorVersion >= 13) {
+ io.writeInt32(this.mapOffsetX);
+ io.writeInt32(this.mapOffsetY);
+ io.writeInt32(this.mapSizeX);
+ io.writeInt32(this.mapSizeY);
+ } else {
+ io.writeByte(this.mapOffsetX);
+ io.writeByte(this.mapOffsetY);
+ io.writeByte(this.mapSizeX);
+ io.writeByte(this.mapSizeY);
+ }
+
+ if (version.interiorType != "tge" && version.interiorType != "mbg") {
+ io.writeByte(0);
+ if (version.interiorVersion >= 2 && version.interiorVersion <= 5)
+ io.writeInt32(this.brushId);
+ }
+ }
+}
diff --git a/src/dif/TexGenEQ.hx b/src/dif/TexGenEQ.hx
new file mode 100644
index 00000000..546258ca
--- /dev/null
+++ b/src/dif/TexGenEQ.hx
@@ -0,0 +1,28 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.PlaneF;
+
+@:expose
+class TexGenEQ {
+ public var planeX:PlaneF;
+ public var planeY:PlaneF;
+
+ public function new() {
+ planeX = new PlaneF();
+ planeY = new PlaneF();
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new TexGenEQ();
+ ret.planeX = PlaneF.read(io);
+ ret.planeY = PlaneF.read(io);
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ this.planeX.write(io);
+ this.planeY.write(io);
+ }
+}
diff --git a/src/dif/TexMatrix.hx b/src/dif/TexMatrix.hx
new file mode 100644
index 00000000..1a1cd45d
--- /dev/null
+++ b/src/dif/TexMatrix.hx
@@ -0,0 +1,32 @@
+package dif;
+
+import haxe.Int32;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class TexMatrix {
+ public var t:Int32;
+ public var n:Int32;
+ public var b:Int32;
+
+ public function new() {
+ this.t = 0;
+ this.n = 0;
+ this.b = 0;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new TexMatrix();
+ ret.t = io.readInt32();
+ ret.n = io.readInt32();
+ ret.b = io.readInt32();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.t);
+ io.writeInt32(this.n);
+ io.writeInt32(this.b);
+ }
+}
diff --git a/src/dif/Trigger.hx b/src/dif/Trigger.hx
new file mode 100644
index 00000000..dabdb641
--- /dev/null
+++ b/src/dif/Trigger.hx
@@ -0,0 +1,44 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.Point3F;
+import haxe.ds.StringMap;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class Trigger {
+ public var name:String;
+ public var datablock:String;
+ public var properties:StringMap;
+ public var polyhedron:Polyhedron;
+ public var offset:Point3F;
+
+ public function new() {
+ this.name = "";
+ this.datablock = "";
+ this.offset = new Point3F();
+ this.properties = new StringMap();
+ this.polyhedron = new Polyhedron();
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new Trigger();
+ ret.name = io.readStr();
+ ret.datablock = io.readStr();
+ ret.properties = io.readDictionary();
+ ret.polyhedron = Polyhedron.read(io);
+ ret.offset = Point3F.read(io);
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeStr(this.name);
+ io.writeStr(this.datablock);
+ io.writeDictionary(this.properties);
+ this.polyhedron.write(io);
+ this.offset.write(io);
+ }
+}
diff --git a/src/dif/VehicleCollision.hx b/src/dif/VehicleCollision.hx
new file mode 100644
index 00000000..a5cda2fe
--- /dev/null
+++ b/src/dif/VehicleCollision.hx
@@ -0,0 +1,67 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.Point3F;
+
+using dif.ReaderExtensions;
+using dif.WriterExtensions;
+
+@:expose
+class VehicleCollision {
+ public var vehicleCollisionFileVersion:Int;
+ public var convexHulls:Array;
+ public var convexHullEmitStrings:Array;
+ public var hullIndices:Array;
+ public var hullPlaneIndices:Array;
+ public var hullEmitStringIndices:Array;
+ public var hullSurfaceIndices:Array;
+ public var polyListPlanes:Array;
+ public var polyListPoints:Array;
+ public var polyListStrings:Array;
+ public var nullSurfaces:Array;
+ public var points:Array;
+ public var planes:Array;
+ public var windings:Array;
+ public var windingIndices:Array;
+
+ public function new() {}
+
+ public static function read(io:BytesReader, version:Version) {
+ var ret = new VehicleCollision();
+ ret.vehicleCollisionFileVersion = io.readInt32();
+ ret.convexHulls = io.readArray((io) -> ConvexHull.read(io, version));
+ ret.convexHullEmitStrings = io.readArray(io -> io.readByte());
+ ret.hullIndices = io.readArray(io -> io.readInt32());
+ ret.hullPlaneIndices = io.readArray(io -> io.readInt16());
+ ret.hullEmitStringIndices = io.readArray(io -> io.readInt32());
+ ret.hullSurfaceIndices = io.readArray(io -> io.readInt32());
+ ret.polyListPlanes = io.readArray(io -> io.readInt16());
+ ret.polyListPoints = io.readArray(io -> io.readInt32());
+ ret.polyListStrings = io.readArray(io -> io.readByte());
+ ret.nullSurfaces = io.readArray(io -> NullSurface.read(io, new Version()));
+ ret.points = io.readArray(Point3F.read);
+ ret.planes = io.readArray(Plane.read);
+ ret.windings = io.readArray(io -> io.readInt32());
+ ret.windingIndices = io.readArray(WindingIndex.read);
+ return ret;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt32(this.vehicleCollisionFileVersion);
+ io.writeArray(this.convexHulls, (io, p) -> p.write(io, version));
+ io.writeArray(this.convexHullEmitStrings, (io, p) -> io.writeByte(p));
+ io.writeArray(this.hullIndices, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.hullPlaneIndices, (io, p) -> io.writeInt16(p));
+ io.writeArray(this.hullEmitStringIndices, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.hullSurfaceIndices, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.polyListPlanes, (io, p) -> io.writeInt16(p));
+ io.writeArray(this.polyListPoints, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.polyListStrings, (io, p) -> io.writeByte(p));
+ io.writeArray(this.nullSurfaces, (io, p) -> p.write(io, new Version()));
+ io.writeArray(this.points, (io, p) -> p.write(io));
+ io.writeArray(this.planes, (io, p) -> p.write(io));
+ io.writeArray(this.windings, (io, p) -> io.writeInt32(p));
+ io.writeArray(this.windingIndices, (io, p) -> p.write(io));
+ }
+}
diff --git a/src/dif/Version.hx b/src/dif/Version.hx
new file mode 100644
index 00000000..a1188e44
--- /dev/null
+++ b/src/dif/Version.hx
@@ -0,0 +1,14 @@
+package dif;
+
+@:expose
+class Version {
+ public var difVersion:Int;
+ public var interiorVersion:Int;
+ public var interiorType:String;
+
+ public function new() {
+ this.difVersion = 44;
+ this.interiorVersion = 0;
+ this.interiorType = "?";
+ }
+}
diff --git a/src/dif/WayPoint.hx b/src/dif/WayPoint.hx
new file mode 100644
index 00000000..0262ad43
--- /dev/null
+++ b/src/dif/WayPoint.hx
@@ -0,0 +1,32 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+import dif.math.QuatF;
+import dif.math.Point3F;
+
+@:expose
+class WayPoint {
+ public var position:Point3F;
+ public var rotation:QuatF;
+ public var msToNext:Int;
+ public var smoothingType:Int;
+
+ public function new(position, rotation, msToNext, smoothingType) {
+ this.position = position;
+ this.rotation = rotation;
+ this.msToNext = msToNext;
+ this.smoothingType = smoothingType;
+ }
+
+ public static function read(io:BytesReader) {
+ return new WayPoint(Point3F.read(io), QuatF.read(io), io.readInt32(), io.readInt32());
+ };
+
+ public function write(io:BytesWriter) {
+ this.position.write(io);
+ this.rotation.write(io);
+ io.writeInt32(this.msToNext);
+ io.writeInt32(this.smoothingType);
+ }
+}
diff --git a/src/dif/WindingIndex.hx b/src/dif/WindingIndex.hx
new file mode 100644
index 00000000..845f61af
--- /dev/null
+++ b/src/dif/WindingIndex.hx
@@ -0,0 +1,24 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class WindingIndex {
+ public var windingStart:Int;
+ public var windingCount:Int;
+
+ public function new(windingStart, windingCount) {
+ this.windingStart = windingStart;
+ this.windingCount = windingCount;
+ }
+
+ public static function read(io:BytesReader) {
+ return new WindingIndex(io.readInt32(), io.readInt32());
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeInt32(this.windingStart);
+ io.writeInt32(this.windingCount);
+ }
+}
diff --git a/src/dif/WriterExtensions.hx b/src/dif/WriterExtensions.hx
new file mode 100644
index 00000000..202e8481
--- /dev/null
+++ b/src/dif/WriterExtensions.hx
@@ -0,0 +1,49 @@
+package dif;
+
+import haxe.io.Bytes;
+import haxe.ds.StringMap;
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+class WriterExtensions {
+ public static function writeDictionary(io:BytesWriter, dict:StringMap) {
+ var len = 0;
+ for (key in dict.keys())
+ len++;
+
+ for (kvp in dict.keyValueIterator()) {
+ io.writeStr(kvp.key);
+ io.writeStr(kvp.value);
+ }
+ }
+
+ public static function writeArray(io:BytesWriter, arr:Array, writeMethod:(BytesWriter, V) -> Void) {
+ io.writeInt32(arr.length);
+ for (i in 0...arr.length) {
+ writeMethod(io, arr[i]);
+ }
+
+ return arr;
+ }
+
+ public static function writeArrayFlags(io:BytesWriter, arr:Array, flags:Int, writeMethod:(BytesWriter, V) -> Void) {
+ io.writeInt32(arr.length);
+ io.writeInt32(flags);
+ for (i in 0...arr.length) {
+ writeMethod(io, arr[i]);
+ }
+ };
+
+ public static function writePNG(io:BytesWriter, arr:Array) {
+ for (i in 0...arr.length) {
+ io.writeByte(arr[i]);
+ }
+ };
+
+ public static function writeColorF(io:BytesWriter, color:Array) {
+ io.writeByte(color[0]);
+ io.writeByte(color[1]);
+ io.writeByte(color[2]);
+ io.writeByte(color[3]);
+ }
+}
diff --git a/src/dif/Zone.hx b/src/dif/Zone.hx
new file mode 100644
index 00000000..4ad7dfb4
--- /dev/null
+++ b/src/dif/Zone.hx
@@ -0,0 +1,47 @@
+package dif;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Zone {
+ public var portalStart:Int;
+ public var portalCount:Int;
+ public var surfaceStart:Int;
+ public var surfaceCount:Int;
+ public var staticMeshStart:Int;
+ public var staticMeshCount:Int;
+
+ public function new() {
+ this.portalStart = 0;
+ this.portalCount = 0;
+ this.surfaceStart = 0;
+ this.surfaceCount = 0;
+ this.staticMeshStart = 0;
+ this.staticMeshCount = 0;
+ }
+
+ public static function read(io:BytesReader, version:Version) {
+ var ret = new Zone();
+ ret.portalStart = io.readUInt16();
+ ret.portalCount = io.readUInt16();
+ ret.surfaceStart = io.readInt32();
+ ret.surfaceCount = io.readInt32();
+ if (version.interiorVersion >= 12) {
+ ret.staticMeshStart = io.readInt32();
+ ret.staticMeshCount = io.readInt32();
+ }
+ return ret;
+ }
+
+ public function write(io:BytesWriter, version:Version) {
+ io.writeInt16(this.portalStart);
+ io.writeInt16(this.portalCount);
+ io.writeInt32(this.surfaceStart);
+ io.writeInt32(this.surfaceCount);
+ if (version.interiorVersion >= 12) {
+ io.writeInt32(this.staticMeshStart);
+ io.writeInt32(this.staticMeshCount);
+ }
+ }
+}
diff --git a/src/dif/io/BytesReader.hx b/src/dif/io/BytesReader.hx
new file mode 100644
index 00000000..e2ca0b45
--- /dev/null
+++ b/src/dif/io/BytesReader.hx
@@ -0,0 +1,60 @@
+package dif.io;
+
+import haxe.io.Bytes;
+
+class BytesReader {
+ var bytes:Bytes;
+ var position:Int;
+
+ public function new(bytes:Bytes) {
+ this.bytes = bytes;
+ this.position = 0;
+ }
+
+ public function readInt32() {
+ var b = bytes.getInt32(position);
+ position += 4;
+ return b;
+ }
+
+ public function readInt16() {
+ var b = bytes.getUInt16(position);
+ position += 2;
+ return b;
+ }
+
+ public function readUInt16() {
+ var b = bytes.getUInt16(position);
+ position += 2;
+ return b;
+ }
+
+ public function readByte() {
+ var b = bytes.get(position);
+ position += 1;
+ return b;
+ }
+
+ public function readStr() {
+ var len = this.readByte();
+ var str = "";
+ for (i in 0...len) {
+ str += String.fromCharCode(this.readByte());
+ }
+ return str;
+ }
+
+ public function readFloat() {
+ var b = bytes.getFloat(position);
+ position += 4;
+ return b;
+ }
+
+ public function tell() {
+ return position;
+ }
+
+ public function seek(pos) {
+ position = pos;
+ }
+}
diff --git a/src/dif/io/BytesWriter.hx b/src/dif/io/BytesWriter.hx
new file mode 100644
index 00000000..f6dbd77a
--- /dev/null
+++ b/src/dif/io/BytesWriter.hx
@@ -0,0 +1,50 @@
+package dif.io;
+
+import haxe.io.BytesData;
+import haxe.Int32;
+import haxe.io.BytesBuffer;
+import haxe.io.Bytes;
+
+class BytesWriter {
+ var bytes:BytesBuffer;
+
+ public function new() {
+ this.bytes = new BytesBuffer();
+ }
+
+ public function writeInt32(int:Int) {
+ this.bytes.addInt32(int);
+ }
+
+ public function writeUInt16(int:Int) {
+ var h = int >> 8;
+ var l = int & 0xFF;
+ this.bytes.addByte(h);
+ this.bytes.addByte(l);
+ }
+
+ public function writeInt16(int:Int) {
+ var h = int >> 8;
+ var l = int & 0xFF;
+ this.bytes.addByte(h);
+ this.bytes.addByte(l);
+ }
+
+ public function writeByte(int:Int) {
+ this.bytes.addByte(int);
+ }
+
+ public function writeStr(str:String) {
+ this.bytes.addByte(str.length);
+ for (c in 0...str.length)
+ this.bytes.addByte(str.charCodeAt(c));
+ }
+
+ public function writeFloat(f:Float) {
+ this.bytes.addFloat(f);
+ }
+
+ public function getBuffer() {
+ return bytes.getBytes();
+ }
+}
diff --git a/src/dif/math/Box3F.hx b/src/dif/math/Box3F.hx
new file mode 100644
index 00000000..8666e2f6
--- /dev/null
+++ b/src/dif/math/Box3F.hx
@@ -0,0 +1,104 @@
+package dif.math;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Box3F {
+ public var minX:Float;
+ public var minY:Float;
+ public var minZ:Float;
+ public var maxX:Float;
+ public var maxY:Float;
+ public var maxZ:Float;
+
+ public function new(minX = 0.0, minY = 0.0, minZ = 0.0, maxX = 0.0, maxY = 0.0, maxZ = 0.0) {
+ this.minX = minX;
+ this.minY = minY;
+ this.minZ = minZ;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ this.maxZ = maxZ;
+ }
+
+ public function center() {
+ return new Point3F(minX + maxX, minY + maxY, minZ + maxZ).scalarDiv(2);
+ }
+
+ public function Expand(point:Point3F) {
+ if (minX > point.x)
+ minX = point.x;
+ if (minY > point.y)
+ minY = point.y;
+ if (minZ > point.z)
+ minZ = point.z;
+
+ if (maxX < point.x)
+ maxX = point.x;
+ if (maxY < point.y)
+ maxY = point.y;
+ if (maxZ < point.z)
+ maxZ = point.z;
+ }
+
+ public static function PointBounds(point:Point3F, size:Point3F) {
+ var ret = new Box3F();
+ ret.minX = point.x;
+ ret.minY = point.y;
+ ret.minZ = point.z;
+ ret.maxX = point.x + size.x;
+ ret.maxY = point.y + size.y;
+ ret.maxZ = point.z + size.z;
+ return ret;
+ }
+
+ public function contains(p:Point3F) {
+ return (minX <= p.x && p.x <= maxX && minY <= p.y && p.y <= maxY && minZ <= p.z && p.z <= maxZ);
+ }
+
+ public function getClosestPoint(point:Point3F) {
+ var closest = new Point3F();
+ if (minX > point.x)
+ closest.x = minX;
+ else if (maxX < point.x)
+ closest.x = maxX;
+ else
+ closest.x = point.x;
+
+ if (minY > point.y)
+ closest.y = minY;
+ else if (maxY < point.y)
+ closest.y = maxY;
+ else
+ closest.y = point.y;
+
+ if (minZ > point.z)
+ closest.z = minZ;
+ else if (maxZ < point.z)
+ closest.z = maxZ;
+ else
+ closest.z = point.z;
+
+ return closest;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new Box3F();
+ ret.minX = io.readFloat();
+ ret.minY = io.readFloat();
+ ret.minZ = io.readFloat();
+ ret.maxX = io.readFloat();
+ ret.maxY = io.readFloat();
+ ret.maxZ = io.readFloat();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeFloat(this.minX);
+ io.writeFloat(this.minY);
+ io.writeFloat(this.minZ);
+ io.writeFloat(this.maxX);
+ io.writeFloat(this.maxY);
+ io.writeFloat(this.maxZ);
+ }
+}
diff --git a/src/dif/math/PlaneF.hx b/src/dif/math/PlaneF.hx
new file mode 100644
index 00000000..0a4d83ed
--- /dev/null
+++ b/src/dif/math/PlaneF.hx
@@ -0,0 +1,84 @@
+package dif.math;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class PlaneF {
+ public var x:Float;
+ public var y:Float;
+ public var z:Float;
+ public var d:Float;
+
+ public function new(x = 0.0, y = 0.0, z = 0.0, d = 0.0) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.d = d;
+ }
+
+ public function distance(p:Point3F) {
+ return this.x * p.x + this.y * p.y + this.z * p.z + this.d;
+ }
+
+ public function project(p:Point3F) {
+ var d = distance(p);
+ return new Point3F(p.x - d * this.x, p.y - d * this.y, p.z - d * this.z);
+ }
+
+ public function getNormal() {
+ return new Point3F(this.x, this.y, this.z);
+ }
+
+ public static function ThreePoints(a:Point3F, b:Point3F, c:Point3F) {
+ var v1 = a.sub(b);
+ var v2 = c.sub(b);
+ var res = v1.cross(v2);
+
+ var ret = new PlaneF();
+
+ var normal = res.normalized();
+ ret.x = normal.x;
+ ret.y = normal.y;
+ ret.z = normal.z;
+ ret.d = -b.dot(normal);
+ return ret;
+ }
+
+ public static function NormalD(normal:Point3F, d:Float) {
+ var ret = new PlaneF();
+
+ ret.x = normal.x;
+ ret.y = normal.y;
+ ret.z = normal.z;
+ ret.d = d;
+ return ret;
+ }
+
+ public static function PointNormal(pt:Point3F, n:Point3F) {
+ var ret = new PlaneF();
+
+ var normal = n.normalized();
+ ret.x = normal.x;
+ ret.y = normal.y;
+ ret.z = normal.z;
+ ret.d = -pt.dot(normal);
+ return ret;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new PlaneF();
+ ret.x = io.readFloat();
+ ret.y = io.readFloat();
+ ret.z = io.readFloat();
+ ret.d = io.readFloat();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeFloat(this.x);
+ io.writeFloat(this.y);
+ io.writeFloat(this.z);
+ io.writeFloat(this.d);
+ }
+}
diff --git a/src/dif/math/Point2F.hx b/src/dif/math/Point2F.hx
new file mode 100644
index 00000000..af367fdc
--- /dev/null
+++ b/src/dif/math/Point2F.hx
@@ -0,0 +1,15 @@
+package dif.math;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Point2F {
+ public var x:Float;
+ public var y:Float;
+
+ public function new(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+}
diff --git a/src/dif/math/Point3F.hx b/src/dif/math/Point3F.hx
new file mode 100644
index 00000000..58f9f0c8
--- /dev/null
+++ b/src/dif/math/Point3F.hx
@@ -0,0 +1,94 @@
+package dif.math;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Point3F {
+ public var x:Float;
+ public var y:Float;
+ public var z:Float;
+
+ public function new(x = 0.0, y = 0.0, z = 0.0) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ @:op([]) public function get(dim:Int) {
+ if (dim == 0)
+ return this.x;
+ if (dim == 1)
+ return this.y;
+ if (dim == 2)
+ return this.z;
+ return -1;
+ }
+
+ public function set(dim:Int, value:Float) {
+ if (dim == 0)
+ this.x = value;
+ if (dim == 1)
+ this.y = value;
+ if (dim == 2)
+ this.z = value;
+ }
+
+ @:op(A + B) public function add(rhs:Point3F) {
+ return new Point3F(this.x + rhs.x, this.y + rhs.y, this.z + rhs.z);
+ }
+
+ @:op(A - B) public function sub(rhs:Point3F) {
+ return new Point3F(this.x - rhs.x, this.y - rhs.y, this.z - rhs.z);
+ }
+
+ @:op(A * B) public function scalar(rhs:Float) {
+ return new Point3F(this.x * rhs, this.y * rhs, this.z * rhs);
+ }
+
+ @:op(A / B) public function scalarDiv(rhs:Float) {
+ return new Point3F(this.x / rhs, this.y / rhs, this.z / rhs);
+ }
+
+ public function dot(rhs:Point3F) {
+ return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;
+ }
+
+ public function cross(rhs:Point3F) {
+ return new Point3F(this.y * rhs.z - this.z * rhs.y, this.z * rhs.x - this.x * rhs.z, this.x * rhs.y - this.y * rhs.x);
+ }
+
+ public function length() {
+ return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+ }
+
+ public function lengthSq() {
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+ }
+
+ public function normalized() {
+ return this.length() != 0 ? this.scalarDiv(this.length()) : this;
+ }
+
+ public function equal(other:Point3F) {
+ return this.x == other.x && this.y == other.y && this.z == other.z;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new Point3F();
+ ret.x = io.readFloat();
+ ret.y = io.readFloat();
+ ret.z = io.readFloat();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeFloat(this.x);
+ io.writeFloat(this.y);
+ io.writeFloat(this.z);
+ }
+
+ public function copy() {
+ return new Point3F(this.x, this.y, this.z);
+ }
+}
diff --git a/src/dif/math/Point4F.hx b/src/dif/math/Point4F.hx
new file mode 100644
index 00000000..6df2ccbf
--- /dev/null
+++ b/src/dif/math/Point4F.hx
@@ -0,0 +1,35 @@
+package dif.math;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Point4F {
+ public var x:Float;
+ public var y:Float;
+ public var z:Float;
+ public var w:Float;
+
+ public function new() {
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ this.w = 0;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new Point4F();
+ ret.x = io.readFloat();
+ ret.y = io.readFloat();
+ ret.z = io.readFloat();
+ ret.w = io.readFloat();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeFloat(this.x);
+ io.writeFloat(this.y);
+ io.writeFloat(this.z);
+ io.writeFloat(this.w);
+ }
+}
diff --git a/src/dif/math/QuatF.hx b/src/dif/math/QuatF.hx
new file mode 100644
index 00000000..0092c85c
--- /dev/null
+++ b/src/dif/math/QuatF.hx
@@ -0,0 +1,35 @@
+package dif.math;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class QuatF {
+ public var x:Float;
+ public var y:Float;
+ public var z:Float;
+ public var w:Float;
+
+ public function new() {
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ this.w = 0;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new QuatF();
+ ret.x = io.readFloat();
+ ret.y = io.readFloat();
+ ret.z = io.readFloat();
+ ret.w = io.readFloat();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeFloat(this.x);
+ io.writeFloat(this.y);
+ io.writeFloat(this.z);
+ io.writeFloat(this.w);
+ }
+}
diff --git a/src/dif/math/SphereF.hx b/src/dif/math/SphereF.hx
new file mode 100644
index 00000000..c860c073
--- /dev/null
+++ b/src/dif/math/SphereF.hx
@@ -0,0 +1,35 @@
+package dif.math;
+
+import dif.io.BytesWriter;
+import dif.io.BytesReader;
+
+@:expose
+class Spheref {
+ public var originX:Float;
+ public var originY:Float;
+ public var originZ:Float;
+ public var radius:Float;
+
+ public function new() {
+ this.originX = 0;
+ this.originY = 0;
+ this.originZ = 0;
+ this.radius = 0;
+ }
+
+ public static function read(io:BytesReader) {
+ var ret = new Spheref();
+ ret.originX = io.readFloat();
+ ret.originY = io.readFloat();
+ ret.originZ = io.readFloat();
+ ret.radius = io.readFloat();
+ return ret;
+ }
+
+ public function write(io:BytesWriter) {
+ io.writeFloat(this.originX);
+ io.writeFloat(this.originY);
+ io.writeFloat(this.originZ);
+ io.writeFloat(this.radius);
+ }
+}
diff --git a/src/octree/IOctreeElement.hx b/src/octree/IOctreeElement.hx
new file mode 100644
index 00000000..50acb16c
--- /dev/null
+++ b/src/octree/IOctreeElement.hx
@@ -0,0 +1,8 @@
+package octree;
+
+import polygonal.ds.Prioritizable;
+
+interface IOctreeElement extends Prioritizable {
+ function getElementType():Int;
+ function setPriority(priority:Int):Void;
+}
diff --git a/src/octree/IOctreeObject.hx b/src/octree/IOctreeObject.hx
new file mode 100644
index 00000000..800d7aab
--- /dev/null
+++ b/src/octree/IOctreeObject.hx
@@ -0,0 +1,9 @@
+package octree;
+
+import h3d.Vector;
+import h3d.col.Bounds;
+
+interface IOctreeObject extends IOctreeElement {
+ var boundingBox:Bounds;
+ function isIntersectedByRay(rayOrigin:Vector, rayDirection:Vector, intersectionPoint:Vector):Bool;
+}
diff --git a/src/octree/Octree.hx b/src/octree/Octree.hx
new file mode 100644
index 00000000..ab896b6c
--- /dev/null
+++ b/src/octree/Octree.hx
@@ -0,0 +1,205 @@
+package octree;
+
+import h3d.Vector;
+import h3d.col.Bounds;
+
+class Octree {
+ public var root:OctreeNode;
+
+ /** A map of each object in the octree to the node that it's in. This accelerates removal drastically, as the lookup step can be skipped. */
+ public var objectToNode:Map;
+
+ public var tempBox = new Bounds();
+
+ public function new() {
+ this.root = new OctreeNode(this, 0);
+ // Init the octree to a 1x1x1 cube
+ this.root.min.set(0, 0, 0);
+ this.root.size = 1;
+ this.objectToNode = new Map();
+ }
+
+ public function insert(object:IOctreeObject) {
+ var node = this.objectToNode.get(object);
+ if (node != null)
+ return; // Don't insert if already contained in the tree
+ while (!this.root.largerThan(object) || !this.root.containsCenter(object)) {
+ // The root node does not fit the object; we need to grow the tree.
+ var a = !this.root.largerThan(object);
+ var b = !this.root.containsCenter(object);
+ if (this.root.depth == -32) {
+ return;
+ }
+ this.grow(object);
+ }
+ var emptyBefore = this.root.count == 0;
+ this.root.insert(object);
+ if (emptyBefore)
+ this.shrink(); // See if we can fit the octree better now that we actually have an element in it
+ }
+
+ public function remove(object:IOctreeObject) {
+ var node = this.objectToNode.get(object);
+ if (node == null)
+ return;
+ node.remove(object);
+ this.objectToNode.remove(object);
+ this.shrink(); // Try shrinking the octree
+ }
+
+ /** Updates an object in the tree whose bounding box has changed. */
+ public function update(object:IOctreeObject) {
+ this.remove(object);
+ this.insert(object);
+ }
+
+ /** Expand the octree towards an object that doesn't fit in it. */
+ public function grow(towards:IOctreeObject) {
+ // We wanna grow towards all the vertices of the object's bounding box that lie outside the octree, so we determine the average position of those vertices:
+ var averagePoint = new Vector();
+ var count = 0;
+ for (i in 0...8) {
+ var vec = new Vector();
+ vec.x = (i & 1) == 1 ? towards.boundingBox.xMin : towards.boundingBox.xMax;
+ vec.y = (i & 2) == 2 ? towards.boundingBox.yMin : towards.boundingBox.yMax;
+ vec.z = (i & 4) == 4 ? towards.boundingBox.zMin : towards.boundingBox.zMax;
+ if (!this.root.containsPoint(vec)) {
+ averagePoint.add(vec);
+ count++;
+ }
+ }
+ averagePoint.multiply(1 / count); // count should be greater than 0, because that's why we're growing in the first place.
+ // Determine the direction from the root center to the determined point
+ var rootCenter = this.root.min.clone().add(new Vector(this.root.size / 2, this.root.size / 2, this.root.size / 2));
+ var direction = averagePoint.sub(rootCenter); // Determine the "direction of growth"
+ // Create a new root. The current root will become a quadrant in this new root.
+ var newRoot = new OctreeNode(this, this.root.depth - 1);
+ newRoot.min = this.root.min.clone();
+ newRoot.size = this.root.size * 2;
+ if (direction.x < 0)
+ newRoot.min.x -= this.root.size;
+ if (direction.y < 0)
+ newRoot.min.y -= this.root.size;
+ if (direction.z < 0)
+ newRoot.min.z -= this.root.size;
+ if (this.root.count > 0) {
+ var octantIndex = ((direction.x < 0) ? 1 : 0) + ((direction.y < 0) ? 2 : 0) + ((direction.z < 0) ? 4 : 0);
+ newRoot.createOctants();
+ newRoot.octants[octantIndex] = this.root;
+ this.root.parent = newRoot;
+ newRoot.count = this.root.count;
+ newRoot.merge();
+ }
+ this.root = newRoot;
+ }
+
+ /** Tries to shrink the octree if large parts of the octree are empty. */
+ public function shrink() {
+ if (this.root.size < 1 || this.root.objects.length > 0)
+ return;
+ if (this.root.count == 0) {
+ // Reset to default empty octree
+ this.root.min.set(0, 0, 0);
+ this.root.size = 1;
+ this.root.depth = 0;
+ return;
+ }
+ if (this.root.octants == null)
+ return;
+ // Find the only non-empty octant
+ var nonEmptyOctant:OctreeNode = null;
+ for (i in 0...8) {
+ var octant = this.root.octants[i];
+ if (octant.count > 0) {
+ if (nonEmptyOctant != null)
+ return; // There are more than two non-empty octants -> don't shrink.
+ else
+ nonEmptyOctant = octant;
+ }
+ }
+ // Make the only non-empty octant the new root
+ this.root = nonEmptyOctant;
+ nonEmptyOctant.parent = null;
+ this.shrink();
+ }
+
+ /** Returns a list of all objects that intersect with the given ray, sorted by distance. */
+ public function raycast(rayOrigin:Vector, rayDirection:Vector) {
+ var intersections:Array = [];
+ this.root.raycast(rayOrigin, rayDirection, intersections);
+ intersections.sort((a, b) -> cast(a.distance - b.distance));
+ return intersections;
+ }
+
+ public function radiusSearch(point:Vector, maximumDistance:Float) {
+ function getClosestPoint(box:Bounds, point:Vector) {
+ var closest = new Vector();
+ if (box.xMin > point.x)
+ closest.x = box.xMin;
+ else if (box.xMax < point.x)
+ closest.x = box.xMax;
+ else
+ closest.x = point.x;
+
+ if (box.yMin > point.y)
+ closest.y = box.yMin;
+ else if (box.yMax < point.y)
+ closest.y = box.yMax;
+ else
+ closest.y = point.y;
+
+ if (box.zMin > point.z)
+ closest.z = box.zMin;
+ else if (box.zMax < point.z)
+ closest.z = box.zMax;
+ else
+ closest.z = point.z;
+
+ return closest;
+ }
+
+ var L = [];
+ var queue = new PriorityQueue();
+
+ var maxDistSq = maximumDistance * maximumDistance;
+ var closestPoint = this.root.getClosestPoint(point);
+ var distSq = closestPoint.distanceSq(point);
+
+ if (distSq > maximumDistance)
+ return L;
+
+ this.root.setPriority(cast(-distSq));
+ queue.enqueue(root, distSq);
+
+ while (queue.count > 0) {
+ var node = queue.dequeue();
+
+ switch (node.getElementType()) {
+ case 1:
+ var octant = cast(node, OctreeNode);
+ if (octant.objects != null) {
+ for (object in octant.objects) {
+ var dist = point.distanceSq(getClosestPoint(object.boundingBox, point));
+ if (dist < maxDistSq) {
+ object.setPriority(cast(-dist));
+ queue.enqueue(object, dist);
+ }
+ }
+ }
+ if (octant.octants != null) {
+ for (suboctant in octant.octants) {
+ var dist = point.distanceSq(suboctant.getClosestPoint(point));
+ if (dist < maxDistSq) {
+ suboctant.setPriority(cast(-dist));
+ queue.enqueue(suboctant, dist);
+ }
+ }
+ }
+
+ case 2:
+ L.push(cast(node, IOctreeObject));
+ }
+ }
+ return L;
+ }
+}
diff --git a/src/octree/OctreeIntersection.hx b/src/octree/OctreeIntersection.hx
new file mode 100644
index 00000000..78b80627
--- /dev/null
+++ b/src/octree/OctreeIntersection.hx
@@ -0,0 +1,11 @@
+package octree;
+
+import h3d.Vector;
+
+class OctreeIntersection {
+ public var object:IOctreeObject;
+ public var point:Vector;
+ public var distance:Float;
+
+ public function new() {}
+}
diff --git a/src/octree/OctreeNode.hx b/src/octree/OctreeNode.hx
new file mode 100644
index 00000000..d7d1f3d6
--- /dev/null
+++ b/src/octree/OctreeNode.hx
@@ -0,0 +1,221 @@
+package octree;
+
+import h3d.col.Ray;
+import h3d.col.Bounds;
+import h3d.col.Point;
+import h3d.Vector;
+
+class OctreeNode implements IOctreeElement {
+ public var octree:Octree;
+ public var parent:OctreeNode = null;
+
+ public var priority:Int;
+ public var position:Int;
+
+ /** The min corner of the bounding box. */
+ public var min = new Vector();
+
+ /** The size of the bounding box on all three axes. This forces the bounding box to be a cube. */
+ public var size:Int;
+
+ public var octants:Array = null;
+
+ /** A list of objects contained in this node. Note that the node doesn't need to be a leaf node for this set to be non-empty; since this is an octree of bounding boxes, some volumes cannot fit into an octant and therefore need to be stored in the node itself. */
+ public var objects = new Array();
+
+ /** The total number of objects in the subtree with this node as its root. */
+ public var count = 0;
+
+ public var depth:Int;
+
+ public function new(octree:Octree, depth:Int) {
+ this.octree = octree;
+ this.depth = depth;
+ }
+
+ public function insert(object:IOctreeObject) {
+ this.count++;
+ if (this.octants != null) {
+ // First we check if the object can fit into any of the octants (they all have the same size, so checking only one suffices)
+ if (this.octants[0].largerThan(object)) {
+ // Try to insert the object into one of the octants...
+ for (i in 0...8) {
+ var octant = this.octants[i];
+ if (octant.containsCenter(object)) {
+ octant.insert(object);
+ return;
+ }
+ }
+ }
+ // No octant fit the object, so add it to the list of objects instead
+ this.objects.push(object);
+ this.octree.objectToNode.set(object, this);
+ } else {
+ this.objects.push(object);
+ this.octree.objectToNode.set(object, this);
+ this.split(); // Try splitting this node
+ }
+ }
+
+ public function split() {
+ if (this.objects.length <= 8 || this.depth == 8)
+ return;
+ this.createOctants();
+ // Put the objects into the correct octants. Note that all objects that couldn't fit into any octant will remain in the set.
+ for (object in this.objects) {
+ if (this.octants[0].largerThan(object)) {
+ for (j in 0...8) {
+ var octant = this.octants[j];
+ if (octant.containsCenter(object)) {
+ octant.insert(object);
+ this.objects.remove(object);
+ }
+ }
+ }
+ }
+ // Try recursively splitting each octant
+ for (i in 0...8) {
+ this.octants[i].split();
+ }
+ }
+
+ public function createOctants() {
+ this.octants = [];
+ for (i in 0...8) {
+ var newNode = new OctreeNode(this.octree, this.depth + 1);
+ newNode.parent = this;
+ newNode.size = cast this.size / 2;
+ newNode.min.set(this.min.x
+ + newNode.size * ((i & 1) >> 0), // The x coordinate changes every index
+ this.min.y
+ + newNode.size * ((i & 2) >> 1), // The y coordinate changes every 2 indices
+ this.min.z
+ + newNode.size * ((i & 4) >> 2) // The z coordinate changes every 4 indices
+ );
+ this.octants.push(newNode);
+ }
+ }
+
+ // Note: The requirement for this method to be called is that `object` is contained directly in this node.
+ public function remove(object:IOctreeObject) {
+ this.objects.remove(object);
+ this.count--;
+ this.merge();
+ // Clean up all ancestors
+ var node = this.parent;
+ while (node != null) {
+ node.count--; // Reduce the count for all ancestor nodes up until the root
+ node.merge();
+ node = node.parent;
+ }
+ }
+
+ public function merge() {
+ if (this.count > 8 || !(this.octants == null))
+ return;
+ // Add all objects in the octants back to this node
+ for (i in 0...8) {
+ var octant = this.octants[i];
+ for (object in octant.objects) {
+ this.objects.push(object);
+ this.octree.objectToNode.set(object, this);
+ }
+ }
+ this.octants = null; // ...then devare the octants
+ }
+
+ public function largerThan(object:IOctreeObject) {
+ var box = object.boundingBox;
+ var bb = new Bounds();
+ bb.setMin(this.min.toPoint());
+ bb.xMax = bb.xMin + this.size;
+ bb.yMax = bb.yMin + this.size;
+ bb.zMax = bb.zMin + this.size;
+ return bb.containsBounds(box);
+ // return this.size > (box.xMax - box.xMin) && this.size > (box.yMax - box.yMin) && this.size > (box.zMax - box.zMin);
+ }
+
+ public function containsCenter(object:IOctreeObject) {
+ var box = object.boundingBox;
+ var x = box.xMin + (box.xMax - box.xMin) / 2;
+ var y = box.yMin + (box.yMax - box.yMin) / 2;
+ var z = box.zMin + (box.zMax - box.zMin) / 2;
+ return this.min.x <= x && x < (this.min.x + this.size) && this.min.y <= y && y < (this.min.y + this.size) && this.min.z <= z
+ && z < (this.min.z + this.size);
+ }
+
+ public function containsPoint(point:Vector) {
+ var x = point.x;
+ var y = point.y;
+ var z = point.z;
+ return this.min.x <= x && x < (this.min.x + this.size) && this.min.y <= y && y < (this.min.y + this.size) && this.min.z <= z
+ && z < (this.min.z + this.size);
+ }
+
+ public function raycast(rayOrigin:Vector, rayDirection:Vector, intersections:Array) {
+ // Construct the loose bounding box of this node (2x in size, with the regular bounding box in the center)
+ var looseBoundingBox = this.octree.tempBox;
+ looseBoundingBox.xMin += this.min.x + (-this.size / 2);
+ looseBoundingBox.yMin += this.min.y + (-this.size / 2);
+ looseBoundingBox.zMin += this.min.z + (-this.size / 2);
+ looseBoundingBox.xMax += this.min.x + (this.size * 3 / 2);
+ looseBoundingBox.yMax += this.min.y + (this.size * 3 / 2);
+ looseBoundingBox.zMax += this.min.z + (this.size * 3 / 2);
+ if (looseBoundingBox.rayIntersection(Ray.fromValues(rayOrigin.x, rayOrigin.y, rayOrigin.z, rayDirection.x, rayDirection.y, rayDirection.z),
+ true) == -1)
+ return; // The ray doesn't hit the node's loose bounding box; we can stop
+ var vec = new Vector();
+ // Test all objects for intersection
+ if (this.objects.length > 0)
+ for (object in this.objects) {
+ if (object.isIntersectedByRay(rayOrigin, rayDirection, vec)) {
+ var intersection:OctreeIntersection = new OctreeIntersection();
+ intersection.object = object;
+ intersection.point = vec;
+ intersection.distance = rayOrigin.distance(vec);
+ intersections.push(intersection);
+ vec = new Vector();
+ }
+ }
+ // Recurse into the octants
+ if (this.octants != null)
+ for (i in 0...8) {
+ var octant = this.octants[i];
+ octant.raycast(rayOrigin, rayDirection, intersections);
+ }
+ }
+
+ public function getClosestPoint(point:Vector) {
+ var closest = new Vector();
+ if (this.min.x > point.x)
+ closest.x = this.min.x;
+ else if (this.min.x + this.size < point.x)
+ closest.x = this.min.x + this.size;
+ else
+ closest.x = point.x;
+
+ if (this.min.y > point.y)
+ closest.y = this.min.y;
+ else if (this.min.y + this.size < point.y)
+ closest.y = this.min.y + this.size;
+ else
+ closest.y = point.y;
+
+ if (this.min.z > point.z)
+ closest.z = this.min.z;
+ else if (this.min.z + this.size < point.z)
+ closest.z = this.min.z + this.size;
+ else
+ closest.z = point.z;
+
+ return closest;
+ }
+
+ public function getElementType() {
+ return 1;
+ }
+
+ public function setPriority(priority:Int) {
+ this.priority = priority;
+ }
+}
diff --git a/src/octree/PriorityQueue.hx b/src/octree/PriorityQueue.hx
new file mode 100644
index 00000000..edd6c546
--- /dev/null
+++ b/src/octree/PriorityQueue.hx
@@ -0,0 +1,57 @@
+package octree;
+
+class PriorityQueue {
+ var first:PriorityQueueNode;
+
+ public var count:Int;
+
+ public function new() {
+ count = 0;
+ first = null;
+ }
+
+ public function enqueue(val:T, priority:Float) {
+ var node = new PriorityQueueNode(val, priority);
+ if (this.first == null) {
+ this.first = node;
+ } else {
+ if (this.first.priority >= priority) {
+ node.next = this.first;
+ this.first.prev = node;
+ this.first = node;
+ } else {
+ var n = this.first;
+ var end = false;
+ while (n.priority < node.priority) {
+ if (n.next == null) {
+ end = true;
+ break;
+ }
+ n = n.next;
+ }
+ if (!end) {
+ if (n.prev != null) {
+ n.prev.next = node;
+ node.prev = n.prev;
+ }
+ n.prev = node;
+ node.next = n;
+ } else {
+ n.next = node;
+ node.prev = n;
+ }
+ }
+ }
+
+ count++;
+ }
+
+ public function dequeue() {
+ var ret = this.first;
+ this.first = this.first.next;
+ if (this.first != null)
+ this.first.prev = null;
+ count--;
+ return ret.value;
+ }
+}
diff --git a/src/octree/PriorityQueueNode.hx b/src/octree/PriorityQueueNode.hx
new file mode 100644
index 00000000..5e5f0f8a
--- /dev/null
+++ b/src/octree/PriorityQueueNode.hx
@@ -0,0 +1,13 @@
+package octree;
+
+class PriorityQueueNode {
+ public var value:T;
+ public var priority:Float;
+ public var next:PriorityQueueNode;
+ public var prev:PriorityQueueNode;
+
+ public function new(value:T, priority:Float) {
+ this.value = value;
+ this.priority = priority;
+ }
+}
diff --git a/src/octreenarrowphase/IOctreeNode.hx b/src/octreenarrowphase/IOctreeNode.hx
new file mode 100644
index 00000000..e97967e6
--- /dev/null
+++ b/src/octreenarrowphase/IOctreeNode.hx
@@ -0,0 +1,7 @@
+package octreenarrowphase;
+
+import polygonal.ds.Prioritizable;
+
+interface IOctreeNode extends Prioritizable {
+ function getNodeType():Int;
+}
diff --git a/src/octreenarrowphase/Octree.hx b/src/octreenarrowphase/Octree.hx
new file mode 100644
index 00000000..d690ee1e
--- /dev/null
+++ b/src/octreenarrowphase/Octree.hx
@@ -0,0 +1,84 @@
+package octreenarrowphase;
+
+import polygonal.ds.PriorityQueue;
+import dif.math.Box3F;
+import dif.math.Point3F;
+
+class Octree {
+ var root:OctreeNode;
+
+ public function new(pts:Array>, binPoints:Int = 8) {
+ var pos = pts;
+
+ var min = new Point3F();
+ var max = new Point3F();
+
+ // Generate the bounding box
+ for (index => op in pos) {
+ var p = op.point;
+ if (p.x < min.x)
+ min.x = p.x;
+ if (p.y < min.y)
+ min.y = p.y;
+ if (p.z < min.z)
+ min.z = p.z;
+
+ if (p.x > max.x)
+ max.x = p.x;
+ if (p.y > max.y)
+ max.y = p.y;
+ if (p.z > max.z)
+ max.z = p.z;
+ }
+
+ root = new OctreeNode();
+ root.box = new Box3F(min.x, min.y, min.z, max.x, max.y, max.z);
+
+ // We use the insert method because its much faster doing this way
+ for (index => pt in pts)
+ root.insert(pt.point, pt.value);
+ }
+
+ public function find(pt:Point3F)
+ return root.find(pt);
+
+ public function remove(pt:Point3F)
+ return root.remove(pt);
+
+ public function insert(pt:Point3F, value:T)
+ return root.insert(pt, value);
+
+ public function knn(point:Point3F, number:Int) {
+ var queue = new PriorityQueue>();
+ root.priority = cast(-root.box.getClosestPoint(point).sub(point).lengthSq());
+ queue.enqueue(root);
+
+ var l = new Array>();
+
+ while (l.length < number && queue.size > 0) {
+ var node = queue.dequeue();
+
+ switch (node.getNodeType()) {
+ case 1:
+ var leaf:OctreeNode = cast node;
+ for (index => pt in leaf.points) {
+ pt.priority = cast(-pt.point.sub(point).lengthSq());
+ queue.enqueue(pt);
+ }
+
+ case 0:
+ var pt:OctreePoint = cast node;
+ l.push(pt);
+
+ case 2:
+ var n:OctreeNode = cast node;
+ for (subnode in n.nodes) {
+ subnode.priority = cast(-subnode.box.getClosestPoint(point).sub(point).lengthSq());
+ queue.enqueue(subnode);
+ }
+ }
+ }
+
+ return l;
+ }
+}
diff --git a/src/octreenarrowphase/OctreeNode.hx b/src/octreenarrowphase/OctreeNode.hx
new file mode 100644
index 00000000..284eb983
--- /dev/null
+++ b/src/octreenarrowphase/OctreeNode.hx
@@ -0,0 +1,168 @@
+package octreenarrowphase;
+
+import dif.math.Box3F;
+import dif.math.Point3F;
+
+class OctreeNode implements IOctreeNode {
+ public var nodes:Array>;
+
+ public var priority:Int;
+ public var position:Int;
+
+ var isLeaf:Bool;
+
+ public var points:Array>;
+
+ var center:Point3F;
+
+ public var box:Box3F;
+
+ public function new() {
+ this.isLeaf = true;
+ this.points = new Array>();
+ }
+
+ public function getCount() {
+ if (this.isLeaf) {
+ return this.points.length;
+ } else {
+ var res = 0;
+ for (index => value in nodes) {
+ res += value.getCount();
+ }
+ return res;
+ }
+ }
+
+ function getIsEmpty() {
+ if (this.isLeaf)
+ return this.getCount() == 0;
+ else {
+ for (index => value in nodes) {
+ if (!value.getIsEmpty())
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public function find(pt:Point3F) {
+ if (this.isLeaf) {
+ for (index => value in this.points) {
+ if (value.point.equal(pt))
+ return true;
+ }
+ return true;
+ } else {
+ var msk = 0;
+ msk |= (pt.x - center.x) < 0 ? 1 : 0;
+ msk |= (pt.y - center.y) < 0 ? 2 : 0;
+ msk |= (pt.z - center.z) < 0 ? 4 : 0;
+
+ return nodes[msk].find(pt);
+ }
+ }
+
+ public function remove(pt:Point3F) {
+ if (this.isLeaf) {
+ var found = false;
+ var idx = -1;
+ for (index => value in this.points) {
+ if (value.point.equal(pt)) {
+ found = true;
+ idx = index;
+ break;
+ }
+ }
+ if (found) {
+ return this.points.remove(this.points[idx]);
+ } else
+ return false;
+ } else {
+ var msk = 0;
+ msk |= (pt.x - center.x) < 0 ? 1 : 0;
+ msk |= (pt.y - center.y) < 0 ? 2 : 0;
+ msk |= (pt.z - center.z) < 0 ? 4 : 0;
+
+ var ret = nodes[msk].remove(pt);
+ this.merge();
+ return ret;
+ }
+ }
+
+ public function insert(pt:Point3F, value:T) {
+ if (this.isLeaf) {
+ this.points.push(new OctreePoint(pt, value));
+ subdivide();
+ } else {
+ var msk = 0;
+ msk |= (pt.x - center.x) < 0 ? 1 : 0;
+ msk |= (pt.y - center.y) < 0 ? 2 : 0;
+ msk |= (pt.z - center.z) < 0 ? 4 : 0;
+ nodes[msk].insert(pt, value);
+ }
+ }
+
+ function subdivide(binPoints:Int = 8) {
+ var min = new Point3F(box.minX, box.minY, box.minZ);
+ var max = new Point3F(box.maxX, box.maxY, box.maxZ);
+ center = min.add(max).scalarDiv(2);
+
+ if (points.length > binPoints) {
+ isLeaf = false;
+
+ var size = max.sub(min);
+
+ nodes = new Array>();
+ for (i in 0...8) {
+ nodes.push(new OctreeNode());
+ }
+
+ nodes[0].box = new Box3F(center.x, center.y, center.z, max.x, max.y, max.z);
+ nodes[1].box = new Box3F(center.x - (size.x / 2), center.y, center.z, max.x - (size.x / 2), max.y, max.z);
+ nodes[2].box = new Box3F(center.x, center.y - (size.y / 2), center.z, max.x, max.y - (size.y / 2), max.z);
+ nodes[3].box = new Box3F(center.x - (size.x / 2), center.y - (size.y / 2), center.z, max.x - (size.x / 2), max.y - (size.y / 2), max.z);
+ nodes[4].box = new Box3F(center.x, center.y, center.z - (size.z / 2), max.x, max.y, max.z - (size.z / 2));
+ nodes[5].box = new Box3F(center.x - (size.x / 2), center.y, center.z - (size.z / 2), max.x - (size.x / 2), max.y, max.z - (size.z / 2));
+ nodes[6].box = new Box3F(center.x, center.y - (size.y / 2), center.z - (size.z / 2), max.x, max.y - (size.y / 2), max.z - (size.z / 2));
+ nodes[7].box = new Box3F(min.x, min.y, min.z, max.x, max.y, max.z);
+
+ for (index => pt in points) {
+ var msk = 0;
+ msk |= (pt.point.x - center.x) < 0 ? 1 : 0;
+ msk |= (pt.point.y - center.y) < 0 ? 2 : 0;
+ msk |= (pt.point.z - center.z) < 0 ? 4 : 0;
+
+ if (!nodes[msk].find(pt.point))
+ nodes[msk].points.push(new OctreePoint(pt.point, pt.value));
+ }
+
+ points = null;
+
+ for (index => value in nodes) {
+ value.subdivide(binPoints);
+ }
+ } else {
+ isLeaf = true;
+ }
+ }
+
+ function merge() {
+ if (this.isLeaf) {
+ return;
+ } else {
+ if (this.getIsEmpty()) {
+ this.isLeaf = true;
+ this.nodes = null;
+ this.points = new Array>();
+ }
+ }
+ }
+
+ public function getNodeType() {
+ if (this.isLeaf)
+ return 1;
+ else
+ return 2;
+ }
+}
diff --git a/src/octreenarrowphase/OctreePoint.hx b/src/octreenarrowphase/OctreePoint.hx
new file mode 100644
index 00000000..6e9da413
--- /dev/null
+++ b/src/octreenarrowphase/OctreePoint.hx
@@ -0,0 +1,21 @@
+package octreenarrowphase;
+
+import dif.math.Point3F;
+
+class OctreePoint implements IOctreeNode {
+ public var point:Point3F;
+
+ public var priority:Int;
+ public var position:Int;
+
+ public var value:T;
+
+ public function new(point:Point3F, value:T) {
+ this.point = point;
+ this.value = value;
+ }
+
+ public function getNodeType() {
+ return 0;
+ }
+}