mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2026-02-06 14:25:55 +00:00
add files
This commit is contained in:
commit
13e8bc70ff
67 changed files with 5041 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
interiors
|
||||
*.hl
|
||||
*.js
|
||||
*.js.map
|
||||
.vscode
|
||||
8
compile.hxml
Normal file
8
compile.hxml
Normal 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
22
index.html
Normal 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
43
src/CameraController.hx
Normal 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
252
src/DifBuilder.hx
Normal 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
20
src/InteriorGeometry.hx
Normal 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
89
src/Main.hx
Normal 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
406
src/Marble.hx
Normal 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
571
src/collision/Collision.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
222
src/collision/CollisionEntity.hx
Normal file
222
src/collision/CollisionEntity.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
15
src/collision/CollisionInfo.hx
Normal file
15
src/collision/CollisionInfo.hx
Normal 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() {}
|
||||
}
|
||||
67
src/collision/CollisionPacket.hx
Normal file
67
src/collision/CollisionPacket.hx
Normal 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();
|
||||
}
|
||||
}
|
||||
63
src/collision/CollisionSurface.hx
Normal file
63
src/collision/CollisionSurface.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
30
src/collision/CollisionWorld.hx
Normal file
30
src/collision/CollisionWorld.hx
Normal 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
27
src/dif/AISpecialNode.hx
Normal 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
34
src/dif/AnimatedLight.hx
Normal 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
107
src/dif/BSPNode.hx
Normal 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
24
src/dif/BSPSolidLeaf.hx
Normal 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
86
src/dif/ConvexHull.hx
Normal 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
28
src/dif/CoordBin.hx
Normal 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
149
src/dif/Dif.hx
Normal 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
31
src/dif/Edge.hx
Normal 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
48
src/dif/Edge2.hx
Normal 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
39
src/dif/FFSurface.hx
Normal 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
63
src/dif/ForceField.hx
Normal 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
42
src/dif/GameEntity.hx
Normal 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
328
src/dif/Interior.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/dif/InteriorPathFollower.hx
Normal file
57
src/dif/InteriorPathFollower.hx
Normal 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
41
src/dif/LightMap.hx
Normal 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
37
src/dif/LightState.hx
Normal 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
43
src/dif/NullSurface.hx
Normal 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
24
src/dif/Plane.hx
Normal 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
36
src/dif/Polyhedron.hx
Normal 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
30
src/dif/PolyhedronEdge.hx
Normal 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
33
src/dif/Portal.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
91
src/dif/ReaderExtensions.hx
Normal file
91
src/dif/ReaderExtensions.hx
Normal 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
28
src/dif/StateData.hx
Normal 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
149
src/dif/Surface.hx
Normal 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
28
src/dif/TexGenEQ.hx
Normal 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
32
src/dif/TexMatrix.hx
Normal 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
44
src/dif/Trigger.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
67
src/dif/VehicleCollision.hx
Normal file
67
src/dif/VehicleCollision.hx
Normal 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
14
src/dif/Version.hx
Normal 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
32
src/dif/WayPoint.hx
Normal 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
24
src/dif/WindingIndex.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
49
src/dif/WriterExtensions.hx
Normal file
49
src/dif/WriterExtensions.hx
Normal 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
47
src/dif/Zone.hx
Normal 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
60
src/dif/io/BytesReader.hx
Normal 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
50
src/dif/io/BytesWriter.hx
Normal 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
104
src/dif/math/Box3F.hx
Normal 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
84
src/dif/math/PlaneF.hx
Normal 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
15
src/dif/math/Point2F.hx
Normal 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
94
src/dif/math/Point3F.hx
Normal 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
35
src/dif/math/Point4F.hx
Normal 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
35
src/dif/math/QuatF.hx
Normal 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
35
src/dif/math/SphereF.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
8
src/octree/IOctreeElement.hx
Normal file
8
src/octree/IOctreeElement.hx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package octree;
|
||||
|
||||
import polygonal.ds.Prioritizable;
|
||||
|
||||
interface IOctreeElement extends Prioritizable {
|
||||
function getElementType():Int;
|
||||
function setPriority(priority:Int):Void;
|
||||
}
|
||||
9
src/octree/IOctreeObject.hx
Normal file
9
src/octree/IOctreeObject.hx
Normal 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
205
src/octree/Octree.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
11
src/octree/OctreeIntersection.hx
Normal file
11
src/octree/OctreeIntersection.hx
Normal 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
221
src/octree/OctreeNode.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
57
src/octree/PriorityQueue.hx
Normal file
57
src/octree/PriorityQueue.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
13
src/octree/PriorityQueueNode.hx
Normal file
13
src/octree/PriorityQueueNode.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
7
src/octreenarrowphase/IOctreeNode.hx
Normal file
7
src/octreenarrowphase/IOctreeNode.hx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package octreenarrowphase;
|
||||
|
||||
import polygonal.ds.Prioritizable;
|
||||
|
||||
interface IOctreeNode<T> extends Prioritizable {
|
||||
function getNodeType():Int;
|
||||
}
|
||||
84
src/octreenarrowphase/Octree.hx
Normal file
84
src/octreenarrowphase/Octree.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
168
src/octreenarrowphase/OctreeNode.hx
Normal file
168
src/octreenarrowphase/OctreeNode.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
21
src/octreenarrowphase/OctreePoint.hx
Normal file
21
src/octreenarrowphase/OctreePoint.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue