Files
LNXSDK/leenkx/Sources/leenkx/trait/PhysicsBreak.hx

205 lines
5.3 KiB
Haxe

package leenkx.trait;
import iron.math.Vec4;
import iron.math.Mat4;
import iron.Trait;
import iron.object.Object;
import iron.object.MeshObject;
import iron.data.MeshData;
import iron.data.SceneFormat;
#if lnx_physics
import leenkx.trait.physics.RigidBody;
import leenkx.trait.physics.PhysicsWorld;
#end
import leenkx.object.MeshDataExtension;
import leenkx.object.BreakerExtension;
import kha.arrays.Float32Array;
import haxe.ds.Vector;
class PhysicsBreak extends Trait {
#if (!lnx_physics)
public function new() { super(); }
#else
// Track all debris for cleanup on scene change
static var allDebris: Array<MeshObject> = [];
static var sceneCallbackRegistered = false;
var breaker: ConvexBreaker;
var physics: PhysicsWorld;
var body: RigidBody;
@prop
var fractureImpulse: Float = 0.1;
@prop
var fractureIterative: Bool = true;
@prop
var traitName: String = '';
@prop
var subdivideByPlane: Bool = false;
@prop
var plane: Vec4 = new Vec4(1, 1, 1);
@prop
var randomPlane: Bool = true;
@prop
var scaleUV: Float = 0.3;
@prop
var flatShading: Bool = true;
public function new() {
super();
breaker = new ConvexBreaker(0.1);
@:privateAccess breaker.scaleUV = scaleUV;
@:privateAccess breaker.flatShading = flatShading;
notifyOnInit(init);
}
function init() {
physics = leenkx.trait.physics.PhysicsWorld.active;
if (physics == null) return;
body = object.getTrait(RigidBody);
breaker.initBreakableObject(cast object, body.mass, body.friction, new Vec4(), new Vec4(), true);
// Register scene removal callback once
if (!sceneCallbackRegistered) {
sceneCallbackRegistered = true;
iron.Scene.active.notifyOnRemove(cleanupAllDebris);
}
notifyOnUpdate(update);
}
static function cleanupAllDebris() {
// Make a copy since remove() modifies the array
var toRemove = allDebris.copy();
for (debris in toRemove) {
if (debris != null) debris.remove();
}
allDebris = [];
sceneCallbackRegistered = false;
}
function update() {
if (body == null || !body.ready || physics == null) return;
var ar = physics.getContactPairs(body);
if (ar != null) {
var maxImpulse: Float = 0.0;
var impactPoint: Vec4 = null;
var impactNormal: Vec4 = null;
for (p in ar) {
if (maxImpulse < p.impulse) {
maxImpulse = p.impulse;
impactPoint = p.posB;
#if lnx_bullet
impactNormal = p.normOnB;
#elseif lnx_oimo
impactNormal = p.nor;
#else
impactNormal = p.normOnB;
#end
}
}
if (maxImpulse > fractureImpulse) {
var radialIter = 1;
var randIter = 1;
var debris = subdivideByPlane ? breaker.subdivideByPlane(cast object, plane, randomPlane) :
breaker.subdivideByImpact(cast object, impactPoint, impactNormal, radialIter, randIter);
for (o in debris) {
var obj: Object = cast o;
obj.name = o.data.raw.name;
obj.setParent(iron.Scene.active.root);
var dims = new kha.arrays.Float32Array(3);
dims[0] = o.data.geom.aabb.x;
dims[1] = o.data.geom.aabb.y;
dims[2] = o.data.geom.aabb.z;
obj.raw = cast {
type: "mesh_object",
name: obj.name,
data_ref: obj.name,
dimensions: dims
};
if (traitName == '')
for (t in object.traits) {
var tc = Type.getClass(t);
if (tc == null || tc == leenkx.trait.physics.RigidBody ||
tc == leenkx.trait.PhysicsBreak || Type.resolveClass("leenx.trait.PhysicsBreak") != null) continue;
var nt = Type.createInstance(tc, []);
for (f in Type.getInstanceFields(tc)) {
var v = Reflect.field(t, f);
if (v != null && !Reflect.isFunction(v)) Reflect.setField(nt, f, v);
}
obj.addTrait(nt);
}
else {
var cname = Type.resolveClass(Main.projectPackage + "." + traitName);
if (cname == null) cname = Type.resolveClass(Main.projectPackage + ".node." + traitName);
var trait = Type.createInstance(cname, []);
obj.addTrait(trait);
obj.addTrait(new leenkx.trait.internal.UniformsManager());
}
var ud = breaker.userDataMap.get(cast o);
if (ud == null) continue;
var params: RigidBodyParams = {
linearDamping: 0.04,
angularDamping: 0.1,
angularFriction: 0.1,
linearFactorsX: 1.0,
linearFactorsY: 1.0,
linearFactorsZ: 1.0,
angularFactorsX: 1.0,
angularFactorsY: 1.0,
angularFactorsZ: 1.0,
collisionMargin: 0.04,
linearDeactivationThreshold: 0.0,
angularDeactivationThreshold: 0.0,
deactivationTime: 0.0,
linearVelocityMin: 0.0,
linearVelocityMax: 0.0,
angularVelocityMin: 0.0,
angularVelocityMax: 0.0,
lockTranslationX: false,
lockTranslationY: false,
lockTranslationZ: false,
lockRotationX: false,
lockRotationY: false,
lockRotationZ: false
};
obj.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params));
if (fractureIterative && cast(o, MeshObject).data.geom.positions.values.length > 100) {
var pb = new PhysicsBreak();
pb.fractureImpulse = fractureImpulse / 2;
pb.traitName = traitName;
obj.addTrait(pb);
}
// Track debris for cleanup on scene change
allDebris.push(cast o);
}
// Remove self from update before removing object
remove();
object.remove();
}
}
}
#end
}