add files

This commit is contained in:
RandomityGuy 2021-05-28 16:58:28 +05:30
commit 13e8bc70ff
67 changed files with 5041 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
interiors
*.hl
*.js
*.js.map
.vscode

8
compile.hxml Normal file
View file

@ -0,0 +1,8 @@
-cp src
-lib headbutt
-lib heaps
-lib hlsdl
-lib polygonal-ds
-hl marblegame.hl
--main Main
-debug

22
index.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE>
<html>
<head>
<meta charset="utf-8"/>
<title>Hello Heaps</title>
<style>
body {
margin: 0;
padding: 0;
background-color: black;
}
canvas#webgl {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="webgl"></canvas>
<script type="text/javascript" src="marblegame.js"></script>
</body>
</html>

43
src/CameraController.hx Normal file
View file

@ -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);
}
}

252
src/DifBuilder.hx Normal file
View file

@ -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<String, Array<DifBuilderTriangle>>();
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;
}
}

20
src/InteriorGeometry.hx Normal file
View file

@ -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();
}
}

89
src/Main.hx Normal file
View file

@ -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();
}
}

406
src/Marble.hx Normal file
View file

@ -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<CollisionInfo>) {
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<CollisionInfo>) {
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<CollisionInfo>) {
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());
}
}

571
src/collision/Collision.hx Normal file
View file

@ -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<Float> {
// 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;
}
}

View file

@ -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<CollisionSurface>;
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;
}
}

View file

@ -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() {}
}

View file

@ -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();
}
}

View file

@ -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<Vector>;
public var normals:Array<Vector>;
public var indices:Array<Int>;
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;
}
}

View file

@ -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);
}
}

27
src/dif/AISpecialNode.hx Normal file
View file

@ -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);
}
}

34
src/dif/AnimatedLight.hx Normal file
View file

@ -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);
}
}

107
src/dif/BSPNode.hx Normal file
View file

@ -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);
}
}
}

24
src/dif/BSPSolidLeaf.hx Normal file
View file

@ -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);
}
}

86
src/dif/ConvexHull.hx Normal file
View file

@ -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);
}
}
}

28
src/dif/CoordBin.hx Normal file
View file

@ -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);
}
}

149
src/dif/Dif.hx Normal file
View file

@ -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<Interior>;
public var subObjects:Array<Interior>;
public var triggers:Array<Trigger>;
public var interiorPathfollowers:Array<InteriorPathFollower>;
public var forceFields:Array<ForceField>;
public var aiSpecialNodes:Array<AISpecialNode>;
public var vehicleCollision:VehicleCollision = null;
public var gameEntities:Array<GameEntity> = 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<cs.types.UInt8>) {
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);
}
}

31
src/dif/Edge.hx Normal file
View file

@ -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);
}
}

48
src/dif/Edge2.hx Normal file
View file

@ -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);
}
}
}

39
src/dif/FFSurface.hx Normal file
View file

@ -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);
}
}

63
src/dif/ForceField.hx Normal file
View file

@ -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<String>;
public var boundingBox:Box3F;
public var boundingSphere:Spheref;
public var normals:Array<Point3F>;
public var planes:Array<Plane>;
public var bspNodes:Array<BSPNode>;
public var bspSolidLeaves:Array<BSPSolidLeaf>;
public var windings:Array<Int>;
public var surfaces:Array<FFSurface>;
public var solidLeafSurfaces:Array<Int>;
public var color:Array<Int>;
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);
}
}

42
src/dif/GameEntity.hx Normal file
View file

@ -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<String>;
public function new() {
this.datablock = "";
this.gameClass = "";
this.position = new Point3F();
this.properties = new StringMap<String>();
}
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);
}
}

328
src/dif/Interior.hx Normal file
View file

@ -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<Point3F>;
public var planes:Array<Plane>;
public var points:Array<Point3F>;
public var pointVisibilities:Array<Int>;
public var texGenEQs:Array<TexGenEQ>;
public var bspNodes:Array<BSPNode>;
public var bspSolidLeaves:Array<BSPSolidLeaf>;
public var materialListVersion:Int;
public var materialList:Array<String>;
public var windings:Array<Int>;
public var windingIndices:Array<WindingIndex>;
public var edges:Array<Edge>;
public var zones:Array<Zone>;
public var zoneSurfaces:Array<Int>;
public var zoneStaticMeshes:Array<Int>;
public var zonePortalList:Array<Int>;
public var portals:Array<Portal>;
public var surfaces:Array<Surface>;
public var edges2:Array<Edge2>;
public var normals2:Array<Point3F>;
public var normalIndices:Array<Int>;
public var normalLMapIndices:Array<Int>;
public var alarmLMapIndices:Array<Int>;
public var nullSurfaces:Array<NullSurface>;
public var lightMaps:Array<LightMap>;
public var solidLeafSurfaces:Array<Int>;
public var animatedLights:Array<AnimatedLight>;
public var lightStates:Array<LightState>;
public var stateDatas:Array<StateData>;
public var stateDataFlags:Int;
public var stateDataBuffers:Array<Int>;
public var nameBuffer:Array<Int>;
public var numSubObjects:Int;
public var convexHulls:Array<ConvexHull>;
public var convexHullEmitStrings:Array<Int>;
public var hullIndices:Array<Int>;
public var hullPlaneIndices:Array<Int>;
public var hullEmitStringIndices:Array<Int>;
public var hullSurfaceIndices:Array<Int>;
public var polyListPlanes:Array<Int>;
public var polyListPoints:Array<Int>;
public var polyListStrings:Array<Int>;
public var coordBins:Array<CoordBin>;
public var coordBinIndices:Array<Int>;
public var coordBinMode:Int;
public var baseAmbientColor:Array<Int>;
public var alarmAmbientColor:Array<Int>;
public var numStaticMeshes:Int;
public var texNormals:Array<Point3F>;
public var texMatrices:Array<TexMatrix>;
public var texMatIndices:Array<Int>;
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<Int>();
} 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<Int>();
} 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<LightMap>();
}
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<StateData>();
it.stateDataFlags = 0;
it.stateDataBuffers = new Array<Int>();
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<CoordBin>();
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);
}
}
}
}

View file

@ -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<String>;
public var triggerId:Array<Int>;
public var wayPoint:Array<WayPoint>;
public var totalMS:Int;
public function new() {
this.name = "";
this.datablock = "";
this.interiorResIndex = 0;
this.offset = new Point3F();
this.properties = new StringMap<String>();
this.triggerId = new Array<Int>();
this.wayPoint = new Array<WayPoint>();
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);
}
}

41
src/dif/LightMap.hx Normal file
View file

@ -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<Int>;
public var lightdirmap:Array<Int>;
public var keepLightMap:Int;
public function new() {
this.lightmap = new Array<Int>();
this.lightdirmap = new Array<Int>();
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);
}
}

37
src/dif/LightState.hx Normal file
View file

@ -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);
}
}

43
src/dif/NullSurface.hx Normal file
View file

@ -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);
}
}
}

24
src/dif/Plane.hx Normal file
View file

@ -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);
}
}

36
src/dif/Polyhedron.hx Normal file
View file

@ -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<Point3F>;
public var planeList:Array<PlaneF>;
public var edgeList:Array<PolyhedronEdge>;
public function new() {
this.pointList = new Array<Point3F>();
this.planeList = new Array<PlaneF>();
this.edgeList = new Array<PolyhedronEdge>();
}
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));
}
}

30
src/dif/PolyhedronEdge.hx Normal file
View file

@ -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);
}
}

33
src/dif/Portal.hx Normal file
View file

@ -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);
}
}

View file

@ -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<String>();
for (i in 0...len) {
var name = io.readStr();
var value = io.readStr();
dict.set(name, value);
}
return dict;
}
public static function readArray<V>(io:BytesReader, readMethod:BytesReader->V):Array<V> {
var len = io.readInt32();
var arr = new Array<V>();
for (i in 0...len) {
arr.push(readMethod(io));
}
return arr;
}
public static function readArrayAs<V>(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<V>();
for (i in 0...length) {
if (test(signed, param)) {
array.push(passMethod(io));
} else {
array.push(failMethod(io));
}
}
return array;
}
public static function readArrayFlags<V>(io:BytesReader, readMethod:BytesReader->V) {
var length = io.readInt32();
var flags = io.readInt32();
var array = new Array<V>();
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()];
}
}

28
src/dif/StateData.hx Normal file
View file

@ -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);
}
}

149
src/dif/Surface.hx Normal file
View file

@ -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);
}
}
}

28
src/dif/TexGenEQ.hx Normal file
View file

@ -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);
}
}

32
src/dif/TexMatrix.hx Normal file
View file

@ -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);
}
}

44
src/dif/Trigger.hx Normal file
View file

@ -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<String>;
public var polyhedron:Polyhedron;
public var offset:Point3F;
public function new() {
this.name = "";
this.datablock = "";
this.offset = new Point3F();
this.properties = new StringMap<String>();
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);
}
}

View file

@ -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<ConvexHull>;
public var convexHullEmitStrings:Array<Int>;
public var hullIndices:Array<Int>;
public var hullPlaneIndices:Array<Int>;
public var hullEmitStringIndices:Array<Int>;
public var hullSurfaceIndices:Array<Int>;
public var polyListPlanes:Array<Int>;
public var polyListPoints:Array<Int>;
public var polyListStrings:Array<Int>;
public var nullSurfaces:Array<NullSurface>;
public var points:Array<Point3F>;
public var planes:Array<Plane>;
public var windings:Array<Int>;
public var windingIndices:Array<WindingIndex>;
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));
}
}

14
src/dif/Version.hx Normal file
View file

@ -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 = "?";
}
}

32
src/dif/WayPoint.hx Normal file
View file

@ -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);
}
}

24
src/dif/WindingIndex.hx Normal file
View file

@ -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);
}
}

View file

@ -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<String>) {
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<V>(io:BytesWriter, arr:Array<V>, writeMethod:(BytesWriter, V) -> Void) {
io.writeInt32(arr.length);
for (i in 0...arr.length) {
writeMethod(io, arr[i]);
}
return arr;
}
public static function writeArrayFlags<V>(io:BytesWriter, arr:Array<V>, 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<Int>) {
for (i in 0...arr.length) {
io.writeByte(arr[i]);
}
};
public static function writeColorF(io:BytesWriter, color:Array<Int>) {
io.writeByte(color[0]);
io.writeByte(color[1]);
io.writeByte(color[2]);
io.writeByte(color[3]);
}
}

47
src/dif/Zone.hx Normal file
View file

@ -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);
}
}
}

60
src/dif/io/BytesReader.hx Normal file
View file

@ -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;
}
}

50
src/dif/io/BytesWriter.hx Normal file
View file

@ -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();
}
}

104
src/dif/math/Box3F.hx Normal file
View file

@ -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);
}
}

84
src/dif/math/PlaneF.hx Normal file
View file

@ -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);
}
}

15
src/dif/math/Point2F.hx Normal file
View file

@ -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;
}
}

94
src/dif/math/Point3F.hx Normal file
View file

@ -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);
}
}

35
src/dif/math/Point4F.hx Normal file
View file

@ -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);
}
}

35
src/dif/math/QuatF.hx Normal file
View file

@ -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);
}
}

35
src/dif/math/SphereF.hx Normal file
View file

@ -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);
}
}

View file

@ -0,0 +1,8 @@
package octree;
import polygonal.ds.Prioritizable;
interface IOctreeElement extends Prioritizable {
function getElementType():Int;
function setPriority(priority:Int):Void;
}

View file

@ -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;
}

205
src/octree/Octree.hx Normal file
View file

@ -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<IOctreeObject, OctreeNode>;
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<OctreeIntersection> = [];
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<IOctreeElement>();
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;
}
}

View file

@ -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() {}
}

221
src/octree/OctreeNode.hx Normal file
View file

@ -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<OctreeNode> = 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<IOctreeObject>();
/** 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<OctreeIntersection>) {
// 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;
}
}

View file

@ -0,0 +1,57 @@
package octree;
class PriorityQueue<T> {
var first:PriorityQueueNode<T>;
public var count:Int;
public function new() {
count = 0;
first = null;
}
public function enqueue(val:T, priority:Float) {
var node = new PriorityQueueNode<T>(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;
}
}

View file

@ -0,0 +1,13 @@
package octree;
class PriorityQueueNode<T> {
public var value:T;
public var priority:Float;
public var next:PriorityQueueNode<T>;
public var prev:PriorityQueueNode<T>;
public function new(value:T, priority:Float) {
this.value = value;
this.priority = priority;
}
}

View file

@ -0,0 +1,7 @@
package octreenarrowphase;
import polygonal.ds.Prioritizable;
interface IOctreeNode<T> extends Prioritizable {
function getNodeType():Int;
}

View file

@ -0,0 +1,84 @@
package octreenarrowphase;
import polygonal.ds.PriorityQueue;
import dif.math.Box3F;
import dif.math.Point3F;
class Octree<T> {
var root:OctreeNode<T>;
public function new(pts:Array<OctreePoint<T>>, 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<IOctreeNode<T>>();
root.priority = cast(-root.box.getClosestPoint(point).sub(point).lengthSq());
queue.enqueue(root);
var l = new Array<OctreePoint<T>>();
while (l.length < number && queue.size > 0) {
var node = queue.dequeue();
switch (node.getNodeType()) {
case 1:
var leaf:OctreeNode<T> = cast node;
for (index => pt in leaf.points) {
pt.priority = cast(-pt.point.sub(point).lengthSq());
queue.enqueue(pt);
}
case 0:
var pt:OctreePoint<T> = cast node;
l.push(pt);
case 2:
var n:OctreeNode<T> = cast node;
for (subnode in n.nodes) {
subnode.priority = cast(-subnode.box.getClosestPoint(point).sub(point).lengthSq());
queue.enqueue(subnode);
}
}
}
return l;
}
}

View file

@ -0,0 +1,168 @@
package octreenarrowphase;
import dif.math.Box3F;
import dif.math.Point3F;
class OctreeNode<T> implements IOctreeNode<T> {
public var nodes:Array<OctreeNode<T>>;
public var priority:Int;
public var position:Int;
var isLeaf:Bool;
public var points:Array<OctreePoint<T>>;
var center:Point3F;
public var box:Box3F;
public function new() {
this.isLeaf = true;
this.points = new Array<OctreePoint<T>>();
}
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<OctreeNode<T>>();
for (i in 0...8) {
nodes.push(new OctreeNode<T>());
}
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<OctreePoint<T>>();
}
}
}
public function getNodeType() {
if (this.isLeaf)
return 1;
else
return 2;
}
}

View file

@ -0,0 +1,21 @@
package octreenarrowphase;
import dif.math.Point3F;
class OctreePoint<T> implements IOctreeNode<T> {
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;
}
}