Update Files

This commit is contained in:
2025-01-22 16:18:30 +01:00
parent ed4603cf95
commit a36294b518
16718 changed files with 2960346 additions and 0 deletions

View File

@ -0,0 +1,224 @@
package leenkx.trait.physics.bullet;
import bullet.Bt.Vector3;
import kha.FastFloat;
import kha.System;
import iron.math.Vec4;
#if lnx_ui
import leenkx.ui.Canvas;
#end
using StringTools;
class DebugDrawHelper {
static inline var contactPointSizePx = 4;
static inline var contactPointNormalColor = 0xffffffff;
static inline var contactPointDrawLifetime = true;
final physicsWorld: PhysicsWorld;
final lines: Array<LineData> = [];
final texts: Array<TextData> = [];
var font: kha.Font = null;
var debugMode: PhysicsWorld.DebugDrawMode = NoDebug;
public function new(physicsWorld: PhysicsWorld) {
this.physicsWorld = physicsWorld;
#if lnx_ui
iron.data.Data.getFont(Canvas.defaultFontName, function(defaultFont: kha.Font) {
font = defaultFont;
});
#end
iron.App.notifyOnRender2D(onRender);
}
public function drawLine(from: bullet.Bt.Vector3, to: bullet.Bt.Vector3, color: bullet.Bt.Vector3) {
#if js
// https://github.com/InfiniteLee/ammo-debug-drawer/pull/1/files
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html#pointers-and-comparisons
from = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", from);
to = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", to);
color = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", color);
#end
final fromScreenSpace = worldToScreenFast(new Vec4(from.x(), from.y(), from.z(), 1.0));
final toScreenSpace = worldToScreenFast(new Vec4(to.x(), to.y(), to.z(), 1.0));
// For now don't draw lines if any point is outside of clip space z,
// investigate how to clamp lines to clip space borders
if (fromScreenSpace.w == 1 && toScreenSpace.w == 1) {
lines.push({
fromX: fromScreenSpace.x,
fromY: fromScreenSpace.y,
toX: toScreenSpace.x,
toY: toScreenSpace.y,
color: kha.Color.fromFloats(color.x(), color.y(), color.z(), 1.0)
});
}
}
public function drawContactPoint(pointOnB: Vector3, normalOnB: Vector3, distance: kha.FastFloat, lifeTime: Int, color: Vector3) {
#if js
pointOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", pointOnB);
normalOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", normalOnB);
color = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", color);
#end
final contactPointScreenSpace = worldToScreenFast(new Vec4(pointOnB.x(), pointOnB.y(), pointOnB.z(), 1.0));
final toScreenSpace = worldToScreenFast(new Vec4(pointOnB.x() + normalOnB.x() * distance, pointOnB.y() + normalOnB.y() * distance, pointOnB.z() + normalOnB.z() * distance, 1.0));
if (contactPointScreenSpace.w == 1) {
final color = kha.Color.fromFloats(color.x(), color.y(), color.z(), 1.0);
lines.push({
fromX: contactPointScreenSpace.x - contactPointSizePx,
fromY: contactPointScreenSpace.y - contactPointSizePx,
toX: contactPointScreenSpace.x + contactPointSizePx,
toY: contactPointScreenSpace.y + contactPointSizePx,
color: color
});
lines.push({
fromX: contactPointScreenSpace.x - contactPointSizePx,
fromY: contactPointScreenSpace.y + contactPointSizePx,
toX: contactPointScreenSpace.x + contactPointSizePx,
toY: contactPointScreenSpace.y - contactPointSizePx,
color: color
});
if (toScreenSpace.w == 1) {
lines.push({
fromX: contactPointScreenSpace.x,
fromY: contactPointScreenSpace.y,
toX: toScreenSpace.x,
toY: toScreenSpace.y,
color: contactPointNormalColor
});
}
if (contactPointDrawLifetime && font != null) {
texts.push({
x: contactPointScreenSpace.x,
y: contactPointScreenSpace.y,
color: color,
text: Std.string(lifeTime), // lifeTime: number of frames the contact point existed
});
}
}
}
public function reportErrorWarning(warningString: bullet.Bt.BulletString) {
trace(warningString.toHaxeString().trim());
}
public function draw3dText(location: Vector3, textString: bullet.Bt.BulletString) {
if (font == null) {
return;
}
#if js
location = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", location);
#end
final locationScreenSpace = worldToScreenFast(new Vec4(location.x(), location.y(), location.z(), 1.0));
texts.push({
x: locationScreenSpace.x,
y: locationScreenSpace.y,
color: kha.Color.fromFloats(0.0, 0.0, 0.0, 1.0),
text: textString.toHaxeString()
});
}
public function setDebugMode(debugMode: PhysicsWorld.DebugDrawMode) {
this.debugMode = debugMode;
}
public function getDebugMode(): PhysicsWorld.DebugDrawMode {
#if js
return debugMode;
#elseif hl
return physicsWorld.getDebugDrawMode();
#else
return NoDebug;
#end
}
function onRender(g: kha.graphics2.Graphics) {
if (getDebugMode() == NoDebug) {
return;
}
// It might be a bit unusual to call this method in a render callback
// instead of the update loop (after all it doesn't draw anything but
// will cause Bullet to call the btIDebugDraw callbacks), but this way
// we can ensure that--within a frame--the function will not be called
// before some user-specific physics update, which would result in a
// one-frame drawing delay... Ideally we would ensure that debugDrawWorld()
// is called when all other (late) update callbacks are already executed...
physicsWorld.world.debugDrawWorld();
g.opacity = 1.0;
for (line in lines) {
g.color = line.color;
g.drawLine(line.fromX, line.fromY, line.toX, line.toY, 1.0);
}
lines.resize(0);
if (font != null) {
g.font = font;
g.fontSize = 12;
for (text in texts) {
g.color = text.color;
g.drawString(text.text, text.x, text.y);
}
texts.resize(0);
}
}
/**
Transform a world coordinate vector into screen space and store the
result in the input vector's x and y coordinates. The w coordinate is
set to 0 if the input vector is outside the active camera's far and near
planes, and 1 otherwise.
**/
inline function worldToScreenFast(loc: Vec4): Vec4 {
final cam = iron.Scene.active.camera;
loc.w = 1.0;
loc.applyproj(cam.VP);
if (loc.z < -1 || loc.z > 1) {
loc.w = 0.0;
}
else {
loc.x = (loc.x + 1) * 0.5 * System.windowWidth();
loc.y = (1 - loc.y) * 0.5 * System.windowHeight();
loc.w = 1.0;
}
return loc;
}
}
@:structInit
class LineData {
public var fromX: FastFloat;
public var fromY: FastFloat;
public var toX: FastFloat;
public var toY: FastFloat;
public var color: kha.Color;
}
@:structInit
class TextData {
public var x: FastFloat;
public var y: FastFloat;
public var color: kha.Color;
public var text: String;
}

View File

@ -0,0 +1,364 @@
package leenkx.trait.physics.bullet;
#if lnx_bullet
import iron.Trait;
import iron.math.Vec4;
import iron.math.Quat;
import iron.object.Transform;
import iron.object.MeshObject;
class KinematicCharacterController extends Trait {
var shape: ControllerShape;
var shapeConvex: bullet.Bt.ConvexShape;
var shapeConvexHull: bullet.Bt.ConvexHullShape;
var isConvexHull = false;
public var physics: PhysicsWorld;
public var transform: Transform = null;
public var mass: Float;
public var friction: Float;
public var restitution: Float;
public var collisionMargin: Float;
public var animated: Bool;
public var group = 1;
var bodyScaleX: Float; // Transform scale at creation time
var bodyScaleY: Float;
var bodyScaleZ: Float;
var currentScaleX: Float;
var currentScaleY: Float;
var currentScaleZ: Float;
var jumpSpeed: Float;
public var body: bullet.Bt.PairCachingGhostObject = null;
public var character: bullet.Bt.KinematicCharacterController = null;
public var ready = false;
static var nextId = 0;
public var id = 0;
public var onReady: Void->Void = null;
static var nullvec = true;
static var vec1: bullet.Bt.Vector3;
static var quat1: bullet.Bt.Quaternion;
static var trans1: bullet.Bt.Transform;
static var quat = new Quat();
static inline var CF_CHARACTER_OBJECT = 16;
public function new(mass = 1.0, shape = ControllerShape.Capsule, jumpSpeed = 8.0, friction = 0.5, restitution = 0.0,
collisionMargin = 0.0, animated = false, group = 1) {
super();
if (nullvec) {
nullvec = false;
vec1 = new bullet.Bt.Vector3(0, 0, 0);
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 0);
trans1 = new bullet.Bt.Transform();
}
this.mass = mass;
this.jumpSpeed = jumpSpeed;
this.shape = shape;
this.friction = friction;
this.restitution = restitution;
this.collisionMargin = collisionMargin;
this.animated = animated;
this.group = group;
notifyOnAdd(init);
notifyOnLateUpdate(lateUpdate);
notifyOnRemove(removeFromWorld);
}
inline function withMargin(f: Float): Float {
return f + f * collisionMargin;
}
public function notifyOnReady(f: Void->Void) {
onReady = f;
if (ready) onReady();
}
public function init() {
if (ready) return;
ready = true;
transform = object.transform;
physics = leenkx.trait.physics.PhysicsWorld.active;
shapeConvex = null;
shapeConvexHull = null;
isConvexHull = false;
if (shape == ControllerShape.Box) {
vec1.setX(withMargin(transform.dim.x / 2));
vec1.setY(withMargin(transform.dim.y / 2));
vec1.setZ(withMargin(transform.dim.z / 2));
shapeConvex = new bullet.Bt.BoxShape(vec1);
}
else if (shape == ControllerShape.Sphere) {
var width = transform.dim.x;
if (transform.dim.y > width) width = transform.dim.y;
if (transform.dim.z > width) width = transform.dim.z;
shapeConvex = new bullet.Bt.SphereShape(withMargin(width / 2));
}
else if (shape == ControllerShape.ConvexHull && mass > 0) {
shapeConvexHull = new bullet.Bt.ConvexHullShape();
isConvexHull = true;
addPointsToConvexHull(shapeConvexHull, transform.scale, collisionMargin);
}
else if (shape == ControllerShape.Cone) {
shapeConvex = new bullet.Bt.ConeShapeZ(
withMargin(transform.dim.x / 2), // Radius
withMargin(transform.dim.z)); // Height
}
else if (shape == ControllerShape.Cylinder) {
vec1.setX(withMargin(transform.dim.x / 2));
vec1.setY(withMargin(transform.dim.y / 2));
vec1.setZ(withMargin(transform.dim.z / 2));
shapeConvex = new bullet.Bt.CylinderShapeZ(vec1);
}
else if (shape == ControllerShape.Capsule) {
var r = transform.dim.x / 2;
shapeConvex = new bullet.Bt.CapsuleShapeZ(
withMargin(r), // Radius
withMargin(transform.dim.z - r * 2)); // Height between 2 sphere centers
}
trans1.setIdentity();
vec1.setX(transform.worldx());
vec1.setY(transform.worldy());
vec1.setZ(transform.worldz());
trans1.setOrigin(vec1);
quat.fromMat(transform.world);
quat1.setX(quat.x);
quat1.setY(quat.y);
quat1.setZ(quat.z);
quat1.setW(quat.w);
trans1.setRotation(quat1);
body = new bullet.Bt.PairCachingGhostObject();
body.setCollisionShape(isConvexHull ? shapeConvexHull : shapeConvex);
body.setCollisionFlags(CF_CHARACTER_OBJECT);
body.setWorldTransform(trans1);
body.setFriction(friction);
body.setRollingFriction(friction);
body.setRestitution(restitution);
#if js
character = new bullet.Bt.KinematicCharacterController(body, isConvexHull ? shapeConvexHull : shapeConvex, 0.5, 2);
#elseif cpp
character = new bullet.Bt.KinematicCharacterController.create(body, isConvexHull ? shapeConvexHull : shapeConvex, 0.5, bullet.Bt.Vector3(0.0, 0.0, 1.0));
#end
character.setJumpSpeed(jumpSpeed);
character.setUseGhostSweepTest(true);
setActivationState(ControllerActivationState.NoDeactivation);
bodyScaleX = currentScaleX = transform.scale.x;
bodyScaleY = currentScaleY = transform.scale.y;
bodyScaleZ = currentScaleZ = transform.scale.z;
id = nextId;
nextId++;
#if js
untyped body.userIndex = id;
#elseif cpp
body.setUserIndex(id);
#end
// physics.addKinematicCharacterController(this);
if (onReady != null) onReady();
}
function lateUpdate() {
if (!ready) return;
if (object.animation != null || animated) {
syncTransform();
}
else {
var trans = body.getWorldTransform();
var p = trans.getOrigin();
var q = trans.getRotation();
transform.loc.set(p.x(), p.y(), p.z());
transform.rot.set(q.x(), q.y(), q.z(), q.w());
if (object.parent != null) {
var ptransform = object.parent.transform;
transform.loc.x -= ptransform.worldx();
transform.loc.y -= ptransform.worldy();
transform.loc.z -= ptransform.worldz();
}
transform.buildMatrix();
}
}
public function canJump(): Bool {
return character.canJump();
}
public function onGround(): Bool {
return character.onGround();
}
public function setJumpSpeed(jumpSpeed: Float) {
character.setJumpSpeed(jumpSpeed);
}
public function setFallSpeed(fallSpeed: Float) {
character.setFallSpeed(fallSpeed);
}
public function setMaxSlope(slopeRadians: Float) {
return character.setMaxSlope(slopeRadians);
}
public function getMaxSlope(): Float {
return character.getMaxSlope();
}
public function setMaxJumpHeight(maxJumpHeight: Float) {
character.setMaxJumpHeight(maxJumpHeight);
}
public function setWalkDirection(walkDirection: Vec4) {
vec1.setX(walkDirection.x);
vec1.setY(walkDirection.y);
vec1.setZ(walkDirection.z);
character.setWalkDirection(vec1);
}
public function setUpInterpolate(value: Bool) {
character.setUpInterpolate(value);
}
#if js
public function jump(): Void{
character.jump();
}
#elseif cpp
public function jump(v: Vec4): Void{
vec1.setX(v.x);
vec1.setY(v.y);
vec1.setZ(v.z);
character.jump(vec1);
}
#end
public function removeFromWorld() {
// if (physics != null) physics.removeKinematicCharacterController(this);
}
public function activate() {
body.activate(false);
}
public function disableGravity() {
#if js
character.setGravity(0.0);
#elseif cpp
vec1.setX(0);
vec1.setY(0);
vec1.setZ(0);
character.setGravity(vec1);
#end
}
public function enableGravity() {
#if js
character.setGravity(Math.abs(physics.world.getGravity().z()) * 3.0); // 9.8 * 3.0 in cpp source code
#elseif cpp
vec1.setX(physics.world.getGravity().x() * 3.0);
vec1.setY(physics.world.getGravity().y() * 3.0);
vec1.setZ(physics.world.getGravity().z() * 3.0);
character.setGravity(vec1);
#end
}
#if js
public function setGravity(f: Float) {
character.setGravity(f);
}
#elseif cpp
public function setGravity(v: Vec4) {
vec1.setX(v.x);
vec1.setY(v.y);
vec1.setZ(v.z);
character.setGravity(vec1);
}
#end
public function setActivationState(newState: Int) {
body.setActivationState(newState);
}
public function setFriction(f: Float) {
body.setFriction(f);
body.setRollingFriction(f);
this.friction = f;
}
public function syncTransform() {
var t = transform;
t.buildMatrix();
vec1.setX(t.worldx());
vec1.setY(t.worldy());
vec1.setZ(t.worldz());
trans1.setOrigin(vec1);
quat.fromMat(t.world);
quat1.setX(quat.x);
quat1.setY(quat.y);
quat1.setZ(quat.z);
quat1.setW(quat.w);
trans1.setRotation(quat1);
//body.setCenterOfMassTransform(trans); // ?
if (currentScaleX != t.scale.x || currentScaleY != t.scale.y || currentScaleZ != t.scale.z) setScale(t.scale);
activate();
}
function setScale(v: Vec4) {
currentScaleX = v.x;
currentScaleY = v.y;
currentScaleZ = v.z;
vec1.setX(bodyScaleX * v.x);
vec1.setY(bodyScaleY * v.y);
vec1.setZ(bodyScaleZ * v.z);
if (isConvexHull) shapeConvexHull.setLocalScaling(vec1);
else shapeConvex.setLocalScaling(vec1);
physics.world.updateSingleAabb(body);
}
function addPointsToConvexHull(shape: bullet.Bt.ConvexHullShape, scale: Vec4, margin: Float) {
var positions = cast(object, MeshObject).data.geom.positions.values;
var sx = scale.x * (1.0 - margin);
var sy = scale.y * (1.0 - margin);
var sz = scale.z * (1.0 - margin);
for (i in 0...Std.int(positions.length / 4)) {
vec1.setX(positions[i * 3] * sx);
vec1.setY(positions[i * 3 + 1] * sy);
vec1.setZ(positions[i * 3 + 2] * sz);
shape.addPoint(vec1, true);
}
}
}
@:enum abstract ControllerShape(Int) from Int to Int {
var Box = 0;
var Sphere = 1;
var ConvexHull = 2;
var Cone = 3;
var Cylinder = 4;
var Capsule = 5;
}
@:enum abstract ControllerActivationState(Int) from Int to Int {
var Active = 1;
var NoDeactivation = 4;
var NoSimulation = 5;
}
#end

View File

@ -0,0 +1,476 @@
package leenkx.trait.physics.bullet;
import iron.math.Vec4;
import iron.Scene;
import iron.object.Object;
#if lnx_bullet
import Math;
import iron.math.Quat;
import leenkx.trait.physics.RigidBody;
import leenkx.trait.physics.PhysicsWorld;
/**
* A trait to add Bullet physics constraints
**/
class PhysicsConstraint extends iron.Trait {
static var nextId:Int = 0;
public var id:Int = 0;
var physics: PhysicsWorld;
var body1: Object;
var body2: Object;
var type: ConstraintType;
public var disableCollisions: Bool;
var breakingThreshold: Float;
var limits: Array<Float>;
public var con: bullet.Bt.TypedConstraint = null;
static var nullvec = true;
static var vec1: bullet.Bt.Vector3;
static var vec2: bullet.Bt.Vector3;
static var vec3: bullet.Bt.Vector3;
static var trans1: bullet.Bt.Transform;
static var trans2: bullet.Bt.Transform;
static var transt: bullet.Bt.Transform;
/**
* Function to initialize physics constraint trait.
*
* @param object Pivot object to which this constraint trait will be added. The constraint limits are applied along the local axes of this object. This object need not
* be a Rigid Body. Typically an `Empty` object may be used. Moving/rotating/parenting this pivot object has no effect once the constraint trait is added. Removing
* the pivot object removes the constraint.
*
* @param body1 First rigid body to be constrained. This rigid body may be constrained by other constraints.
*
* @param body2 Second rigid body to be constrained. This rigid body may be constrained by other constraints.
*
* @param type Type of the constraint.
*
* @param disableCollisions Disable collisions between constrained objects.
*
* @param breakingThreshold Break the constraint if stress on this constraint exceeds this value. Set to 0 to make un-breakable.
*
* @param limits Constraint limits. This may be set before adding the trait to pivot object using the set limits functions.
*
**/
public function new(body1: Object, body2: Object, type: ConstraintType, disableCollisions: Bool, breakingThreshold: Float, limits: Array<Float> = null) {
super();
if (nullvec) {
nullvec = false;
vec1 = new bullet.Bt.Vector3(0, 0, 0);
vec2 = new bullet.Bt.Vector3(0, 0, 0);
vec3 = new bullet.Bt.Vector3(0, 0, 0);
trans1 = new bullet.Bt.Transform();
trans2 = new bullet.Bt.Transform();
}
this.body1 = body1;
this.body2 = body2;
this.type = type;
this.disableCollisions = disableCollisions;
this.breakingThreshold = breakingThreshold;
if(limits == null) limits = [for(i in 0...36) 0];
this.limits = limits;
notifyOnInit(init);
}
function init() {
physics = PhysicsWorld.active;
var target1 = body1;
var target2 = body2;
if (target1 == null || target2 == null) return;//no objects selected
var rb1: RigidBody = target1.getTrait(RigidBody);
var rb2: RigidBody = target2.getTrait(RigidBody);
if (rb1 != null && rb1.ready && rb2 != null && rb2.ready) {//Check if rigid bodies are ready
var t = object.transform;
var t1 = target1.transform;
var t2 = target2.transform;
var frameT = t.world.clone();//Transform of pivot in world space
var frameInA = t1.world.clone();//Transform of rb1 in world space
frameInA.getInverse(frameInA);//Inverse Transform of rb1 in world space
frameT.multmat(frameInA);//Transform of pivot object in rb1 space
frameInA = frameT.clone();//Frame In A
frameT = t.world.clone();//Transform of pivot in world space
var frameInB = t2.world.clone();//Transform of rb2 in world space
frameInB.getInverse(frameInB);//Inverse Transform of rb2 in world space
frameT.multmat(frameInB);//Transform of pivot object in rb2 space
frameInB = frameT.clone();//Frame In B
var loc = new Vec4();
var rot = new Quat();
var scl = new Vec4();
frameInA.decompose(loc,rot,scl);
trans1.setIdentity();
vec1.setX(loc.x);
vec1.setY(loc.y);
vec1.setZ(loc.z);
trans1.setOrigin(vec1);
trans1.setRotation(new bullet.Bt.Quaternion(rot.x, rot.y, rot.z, rot.w));
frameInB.decompose(loc,rot,scl);
trans2.setIdentity();
vec2.setX(loc.x);
vec2.setY(loc.y);
vec2.setZ(loc.z);
trans2.setOrigin(vec2);
trans2.setRotation(new bullet.Bt.Quaternion(rot.x, rot.y, rot.z, rot.w));
if (type == Generic || type == Fixed) {
#if hl
var c = new bullet.Bt.Generic6DofConstraint(rb1.body, rb2.body, trans1, trans2, false);
#else
var c = bullet.Bt.Generic6DofConstraint.new2(rb1.body, rb2.body, trans1, trans2, false);
#end
if (type == Fixed) {
vec1.setX(0);
vec1.setY(0);
vec1.setZ(0);
c.setLinearLowerLimit(vec1);
c.setLinearUpperLimit(vec1);
c.setAngularLowerLimit(vec1);
c.setAngularUpperLimit(vec1);
}
else if (type == ConstraintType.Generic) {
if (limits[0] == 0) {
limits[1] = 1.0;
limits[2] = -1.0;
}
if (limits[3] == 0) {
limits[4] = 1.0;
limits[5] = -1.0;
}
if (limits[6] == 0) {
limits[7] = 1.0;
limits[8] = -1.0;
}
if (limits[9] == 0) {
limits[10] = 1.0;
limits[11] = -1.0;
}
if (limits[12] == 0) {
limits[13] = 1.0;
limits[14] = -1.0;
}
if (limits[15] == 0) {
limits[16] = 1.0;
limits[17] = -1.0;
}
vec1.setX(limits[1]);
vec1.setY(limits[4]);
vec1.setZ(limits[7]);
c.setLinearLowerLimit(vec1);
vec1.setX(limits[2]);
vec1.setY(limits[5]);
vec1.setZ(limits[8]);
c.setLinearUpperLimit(vec1);
vec1.setX(limits[10]);
vec1.setY(limits[13]);
vec1.setZ(limits[16]);
c.setAngularLowerLimit(vec1);
vec1.setX(limits[11]);
vec1.setY(limits[14]);
vec1.setZ(limits[17]);
c.setAngularUpperLimit(vec1);
}
con = cast c;
}
else if (type == ConstraintType.GenericSpring){
var c = new bullet.Bt.Generic6DofSpringConstraint(rb1.body, rb2.body, trans1, trans2, false);
if (limits[0] == 0) {
limits[1] = 1.0;
limits[2] = -1.0;
}
if (limits[3] == 0) {
limits[4] = 1.0;
limits[5] = -1.0;
}
if (limits[6] == 0) {
limits[7] = 1.0;
limits[8] = -1.0;
}
if (limits[9] == 0) {
limits[10] = 1.0;
limits[11] = -1.0;
}
if (limits[12] == 0) {
limits[13] = 1.0;
limits[14] = -1.0;
}
if (limits[15] == 0) {
limits[16] = 1.0;
limits[17] = -1.0;
}
vec1.setX(limits[1]);
vec1.setY(limits[4]);
vec1.setZ(limits[7]);
c.setLinearLowerLimit(vec1);
vec1.setX(limits[2]);
vec1.setY(limits[5]);
vec1.setZ(limits[8]);
c.setLinearUpperLimit(vec1);
vec1.setX(limits[10]);
vec1.setY(limits[13]);
vec1.setZ(limits[16]);
c.setAngularLowerLimit(vec1);
vec1.setX(limits[11]);
vec1.setY(limits[14]);
vec1.setZ(limits[17]);
c.setAngularUpperLimit(vec1);
if (limits[18] != 0) {
c.enableSpring(0, true);
c.setStiffness(0, limits[19]);
c.setDamping(0, limits[20]);
}
else {
c.enableSpring(0, false);
}
if (limits[21] != 0) {
c.enableSpring(1, true);
c.setStiffness(1, limits[22]);
c.setDamping(1, limits[23]);
}
else {
c.enableSpring(1, false);
}
if (limits[24] != 0) {
c.enableSpring(2, true);
c.setStiffness(2, limits[25]);
c.setDamping(2, limits[26]);
}
else {
c.enableSpring(2, false);
}
if (limits[27] != 0) {
c.enableSpring(3, true);
c.setStiffness(3, limits[28]);
c.setDamping(3, limits[29]);
}
else {
c.enableSpring(3, false);
}
if (limits[30] != 0) {
c.enableSpring(4, true);
c.setStiffness(4, limits[31]);
c.setDamping(4, limits[32]);
}
else {
c.enableSpring(4, false);
}
if (limits[33] != 0) {
c.enableSpring(5, true);
c.setStiffness(5, limits[34]);
c.setDamping(5, limits[35]);
}
else {
c.enableSpring(5, false);
}
con = cast c;
}
else if (type == ConstraintType.Point){
var c = new bullet.Bt.Point2PointConstraint(rb1.body, rb2.body, vec1, vec2);
con = cast c;
}
else if (type == ConstraintType.Hinge) {
var axis = vec3;
var _softness: Float = 0.9;
var _biasFactor: Float = 0.3;
var _relaxationFactor: Float = 1.0;
axis.setX(t.up().x);
axis.setY(t.up().y);
axis.setZ(t.up().z);
var c = new bullet.Bt.HingeConstraint(rb1.body, rb2.body, vec1, vec2, axis, axis, false);
if (limits[0] != 0) {
c.setLimit(limits[1], limits[2], _softness, _biasFactor, _relaxationFactor);
}
con = cast c;
}
else if (type == ConstraintType.Slider) {
var c = new bullet.Bt.SliderConstraint(rb1.body, rb2.body, trans1, trans2, true);
if (limits[0] != 0) {
c.setLowerLinLimit(limits[1]);
c.setUpperLinLimit(limits[2]);
}
con = cast c;
}
else if (type == ConstraintType.Piston) {
var c = new bullet.Bt.SliderConstraint(rb1.body, rb2.body, trans1, trans2, true);
if (limits[0] != 0) {
c.setLowerLinLimit(limits[1]);
c.setUpperLinLimit(limits[2]);
}
if (limits[3] != 0) {
c.setLowerAngLimit(limits[4]);
c.setUpperAngLimit(limits[5]);
}
else {
c.setLowerAngLimit(1);
c.setUpperAngLimit(-1);
}
con = cast c;
}
if (breakingThreshold > 0) con.setBreakingImpulseThreshold(breakingThreshold);
physics.addPhysicsConstraint(this);
id = nextId;
nextId++;
notifyOnRemove(removeFromWorld);
}
else this.remove(); // Rigid body not initialized yet. Remove trait without adding constraint
}
public function removeFromWorld() {
physics.removePhysicsConstraint(this);
}
/**
* Function to set constraint limits when using Hinge constraint. May be used after initalizing this trait but before adding it
* to the pivot object
**/
public function setHingeConstraintLimits(angLimit: Bool, lowerAngLimit: Float, upperAngLimit: Float) {
angLimit? limits[0] = 1 : limits[0] = 0;
limits[1] = lowerAngLimit * (Math.PI/ 180);
limits[2] = upperAngLimit * (Math.PI/ 180);
}
/**
* Function to set constraint limits when using Slider constraint. May be used after initalizing this trait but before adding it
* to the pivot object
**/
public function setSliderConstraintLimits(linLimit: Bool, lowerLinLimit: Float, upperLinLimit: Float) {
linLimit? limits[0] = 1 : limits[0] = 0;
limits[1] = lowerLinLimit;
limits[2] = upperLinLimit;
}
/**
* Function to set constraint limits when using Piston constraint. May be used after initalizing this trait but before adding it
* to the pivot object
**/
public function setPistonConstraintLimits(linLimit: Bool, lowerLinLimit: Float, upperLinLimit: Float, angLimit: Bool, lowerAngLimit: Float, upperAngLimit: Float) {
linLimit? limits[0] = 1 : limits[0] = 0;
limits[1] = lowerLinLimit;
limits[2] = upperLinLimit;
angLimit? limits[3] = 1 : limits[3] = 0;
limits[4] = lowerAngLimit * (Math.PI/ 180);
limits[5] = upperAngLimit * (Math.PI/ 180);
}
/**
* Function to set customized constraint limits when using Generic/ Generic Spring constraint. May be used after initalizing this trait but before adding it
* to the pivot object. Multiple constarints may be set by calling this function with different parameters.
**/
public function setGenericConstraintLimits(setLimit: Bool = false, lowerLimit: Float = 1.0, upperLimit: Float = -1.0, axis: ConstraintAxis = X, isAngular: Bool = false) {
var i = 0;
var j = 0;
var radian = (Math.PI/ 180);
switch (axis){
case X:
i = 0;
case Y:
i = 3;
case Z:
i = 6;
}
isAngular? j = 9 : j = 0;
isAngular? radian = (Math.PI/ 180) : radian = 1;
setLimit? limits[i + j] = 1 : 0;
limits[i + j + 1] = lowerLimit * radian;
limits[i + j + 2] = upperLimit * radian;
}
/**
* Function to set customized spring parameters when using Generic/ Generic Spring constraint. May be used after initalizing this trait but before adding it
* to the pivot object. Multiple parameters to different axes may be set by calling this function with different parameters.
**/
public function setSpringParams(setSpring: Bool = false, stiffness: Float = 10.0, damping: Float = 0.5, axis: ConstraintAxis = X, isAngular: Bool = false) {
var i = 0;
var j = 0;
switch (axis){
case X:
i = 18;
case Y:
i = 21;
case Z:
i = 24;
}
isAngular? j = 9 : j = 0;
setSpring? limits[i + j] = 1 : 0;
limits[i + j + 1] = stiffness;
limits[i + j + 2] = damping;
}
public function delete() {
#if js
bullet.Bt.Ammo.destroy(con);
#else
con.delete();
#end
}
}
@:enum abstract ConstraintType(Int) from Int to Int {
var Fixed = 0;
var Point = 1;
var Hinge = 2;
var Slider = 3;
var Piston = 4;
var Generic = 5;
var GenericSpring = 6;
var Motor = 7;
}
@:enum abstract ConstraintAxis(Int) from Int to Int {
var X = 0;
var Y = 1;
var Z = 2;
}
#end

View File

@ -0,0 +1,60 @@
package leenkx.trait.physics.bullet;
import iron.Scene;
import iron.object.Object;
#if lnx_bullet
/**
* A helper trait to add physics constraints when exporting via Blender.
* This trait will be automatically removed once the constraint is added. Note that this trait
* uses object names instead of object reference.
**/
class PhysicsConstraintExportHelper extends iron.Trait {
var body1: String;
var body2: String;
var type: Int;
var disableCollisions: Bool;
var breakingThreshold: Float;
var limits: Array<Float>;
var constraintAdded: Bool = false;
var relativeConstraint: Bool = false;
public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, relatieConstraint: Bool = false, limits: Array<Float> = null) {
super();
this.body1 = body1;
this.body2 = body2;
this.type = type;
this.disableCollisions = disableCollisions;
this.breakingThreshold = breakingThreshold;
this.relativeConstraint = relatieConstraint;
this.limits = limits;
notifyOnInit(init);
notifyOnUpdate(update);
}
function init() {
var target1;
var target2;
if(relativeConstraint) {
target1 = object.parent.getChild(body1);
target2 = object.parent.getChild(body2);
}
else {
target1 = Scene.active.getChild(body1);
target2 = Scene.active.getChild(body2);
}
object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits));
constraintAdded = true;
}
function update() {
if(constraintAdded) this.remove();
}
}
#end

View File

@ -0,0 +1,187 @@
package leenkx.trait.physics.bullet;
#if lnx_bullet
import iron.math.Vec4;
import iron.math.Mat4;
import iron.math.Quat;
import iron.Trait;
import iron.object.Object;
import iron.object.MeshObject;
import iron.object.Transform;
import iron.data.MeshData;
import iron.data.SceneFormat;
import leenkx.trait.physics.RigidBody;
import leenkx.trait.physics.PhysicsWorld;
class PhysicsHook extends Trait {
var target: Object;
var targetName: String;
var targetTransform: Transform;
var verts: Array<Float>;
var constraint: bullet.Bt.Generic6DofConstraint = null;
#if lnx_physics_soft
var hookRB: bullet.Bt.RigidBody = null;
#end
static var nullvec = true;
static var vec1: bullet.Bt.Vector3;
static var quat1: bullet.Bt.Quaternion;
static var trans1: bullet.Bt.Transform;
static var trans2: bullet.Bt.Transform;
static var quat = new Quat();
public function new(targetName: String, verts: Array<Float>) {
super();
if (nullvec) {
nullvec = false;
vec1 = new bullet.Bt.Vector3(0, 0, 0);
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 0);
trans1 = new bullet.Bt.Transform();
trans2 = new bullet.Bt.Transform();
}
this.targetName = targetName;
this.verts = verts;
iron.Scene.active.notifyOnInit(function() {
notifyOnInit(init);
notifyOnUpdate(update);
});
}
function init() {
// Hook to empty axes
target = targetName != "" ? iron.Scene.active.getChild(targetName) : null;
targetTransform = target != null ? target.transform : iron.Scene.global.transform;
var physics = PhysicsWorld.active;
// Soft body hook
#if lnx_physics_soft
var sb: SoftBody = object.getTrait(SoftBody);
if (sb != null && sb.ready) {
// Place static rigid body at target location
trans1.setIdentity();
vec1.setX(targetTransform.loc.x);
vec1.setY(targetTransform.loc.y);
vec1.setZ(targetTransform.loc.z);
trans1.setOrigin(vec1);
quat1.setX(targetTransform.rot.x);
quat1.setY(targetTransform.rot.y);
quat1.setZ(targetTransform.rot.z);
quat1.setW(targetTransform.rot.w);
trans1.setRotation(quat1);
var centerOfMassOffset = trans2;
centerOfMassOffset.setIdentity();
var mass = 0.0;
var motionState = new bullet.Bt.DefaultMotionState(trans1, centerOfMassOffset);
var inertia = vec1;
inertia.setX(0);
inertia.setY(0);
inertia.setZ(0);
var shape = new bullet.Bt.SphereShape(0.01);
shape.calculateLocalInertia(mass, inertia);
var bodyCI = new bullet.Bt.RigidBodyConstructionInfo(mass, motionState, shape, inertia);
hookRB = new bullet.Bt.RigidBody(bodyCI);
#if js
var nodes = sb.body.get_m_nodes();
#else
var nodes = sb.body.m_nodes;
#end
for (i in 0...nodes.size()) {
var node = nodes.at(i);
#if js
var nodePos = node.get_m_x();
#else
var nodePos = node.m_x;
#end
// Find nodes at marked vertex group locations
var numVerts = Std.int(verts.length / 3);
for (j in 0...numVerts) {
var x = verts[j * 3] + sb.vertOffsetX + sb.object.transform.loc.x;
var y = verts[j * 3 + 1] + sb.vertOffsetY + sb.object.transform.loc.y;
var z = verts[j * 3 + 2] + sb.vertOffsetZ + sb.object.transform.loc.z;
// Anchor node to rigid body
if (Math.abs(nodePos.x() - x) < 0.01 && Math.abs(nodePos.y() - y) < 0.01 && Math.abs(nodePos.z() - z) < 0.01) {
sb.body.appendAnchor(i, hookRB, false, 1.0 / numVerts);
}
}
}
return;
}
#end
// Rigid body hook
var rb1: RigidBody = object.getTrait(RigidBody);
if (rb1 != null && rb1.ready) {
trans1.setIdentity();
vec1.setX(targetTransform.worldx() - object.transform.worldx());
vec1.setY(targetTransform.worldy() - object.transform.worldy());
vec1.setZ(targetTransform.worldz() - object.transform.worldz());
trans1.setOrigin(vec1);
constraint = new bullet.Bt.Generic6DofConstraint(rb1.body, trans1, false);
vec1.setX(0);
vec1.setY(0);
vec1.setZ(0);
constraint.setLinearLowerLimit(vec1);
constraint.setLinearUpperLimit(vec1);
vec1.setX(-10);
vec1.setY(-10);
vec1.setZ(-10);
constraint.setAngularLowerLimit(vec1);
vec1.setX(10);
vec1.setY(10);
vec1.setZ(10);
constraint.setAngularUpperLimit(vec1);
physics.world.addConstraint(constraint, false);
return;
}
// Rigid body or soft body not initialized yet
notifyOnInit(init);
}
function update() {
#if lnx_physics_soft
if (hookRB != null) {
trans1.setIdentity();
vec1.setX(targetTransform.world.getLoc().x);
vec1.setY(targetTransform.world.getLoc().y);
vec1.setZ(targetTransform.world.getLoc().z);
trans1.setOrigin(vec1);
quat.fromMat(targetTransform.world);
quat1.setX(quat.x);
quat1.setY(quat.y);
quat1.setZ(quat.z);
quat1.setW(quat.w);
trans1.setRotation(quat1);
hookRB.setWorldTransform(trans1);
}
#end
// if (constraint != null) {
// vec1.setX(targetTransform.worldx() - object.transform.worldx());
// vec1.setY(targetTransform.worldy() - object.transform.worldy());
// vec1.setZ(targetTransform.worldz() - object.transform.worldz());
// var pivot = vec1;
// #if js
// constraint.getFrameOffsetA().setOrigin(pivot);
// #elseif cpp
// constraint.setFrameOffsetAOrigin(pivot);
// #end
// }
}
}
#end

View File

@ -0,0 +1,673 @@
package leenkx.trait.physics.bullet;
#if lnx_bullet
import iron.Trait;
import iron.system.Time;
import iron.math.Vec4;
import iron.math.Quat;
import iron.math.RayCaster;
class Hit {
public var rb: RigidBody;
public var pos: Vec4;
public var normal: Vec4;
public function new(rb: RigidBody, pos: Vec4, normal: Vec4){
this.rb = rb;
this.pos = pos;
this.normal = normal;
}
}
class ConvexHit {
public var pos: Vec4;
public var normal: Vec4;
public var hitFraction: Float;
public function new(pos: Vec4, normal: Vec4, hitFraction: Float){
this.pos = pos;
this.normal = normal;
this.hitFraction = hitFraction;
}
}
class ContactPair {
public var a: Int;
public var b: Int;
public var posA: Vec4;
public var posB: Vec4;
public var normOnB: Vec4;
public var impulse: Float;
public var distance: Float;
public function new(a: Int, b: Int) {
this.a = a;
this.b = b;
}
}
class PhysicsWorld extends Trait {
public static var active: PhysicsWorld = null;
static var sceneRemoved = false;
#if lnx_physics_soft
public var world: bullet.Bt.SoftRigidDynamicsWorld;
#else
public var world: bullet.Bt.DiscreteDynamicsWorld;
#end
var dispatcher: bullet.Bt.CollisionDispatcher;
var gimpactRegistered = false;
var contacts: Array<ContactPair>;
var preUpdates: Array<Void->Void> = null;
public var rbMap: Map<Int, RigidBody>;
public var conMap: Map<Int, PhysicsConstraint>;
public var timeScale = 1.0;
var maxSteps = 1;
public var solverIterations = 10;
public var hitPointWorld = new Vec4();
public var hitNormalWorld = new Vec4();
public var convexHitPointWorld = new Vec4();
public var convexHitNormalWorld = new Vec4();
var pairCache: Bool = false;
static var nullvec = true;
static var vec1: bullet.Bt.Vector3 = null;
static var vec2: bullet.Bt.Vector3 = null;
static var quat1: bullet.Bt.Quaternion = null;
static var transform1: bullet.Bt.Transform = null;
static var transform2: bullet.Bt.Transform = null;
var debugDrawHelper: DebugDrawHelper = null;
#if lnx_debug
public static var physTime = 0.0;
#end
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug) {
super();
if (nullvec) {
nullvec = false;
vec1 = new bullet.Bt.Vector3(0, 0, 0);
vec2 = new bullet.Bt.Vector3(0, 0, 0);
transform1 = new bullet.Bt.Transform();
transform2 = new bullet.Bt.Transform();
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 1.0);
}
// Scene spawn
if (active != null && !sceneRemoved) return;
sceneRemoved = false;
this.timeScale = timeScale;
this.maxSteps = maxSteps;
this.solverIterations = solverIterations;
// First scene
if (active == null) {
createPhysics();
}
else { // Scene switch
world = active.world;
dispatcher = active.dispatcher;
gimpactRegistered = active.gimpactRegistered;
}
contacts = [];
rbMap = new Map();
conMap = new Map();
active = this;
// Ensure physics are updated first in the lateUpdate list
_lateUpdate = [lateUpdate];
@:privateAccess iron.App.traitLateUpdates.insert(0, lateUpdate);
setDebugDrawMode(debugDrawMode);
iron.Scene.active.notifyOnRemove(function() {
sceneRemoved = true;
});
}
public function reset() {
for (rb in active.rbMap) removeRigidBody(rb);
}
function createPhysics() {
var broadphase = new bullet.Bt.DbvtBroadphase();
#if lnx_physics_soft
var collisionConfiguration = new bullet.Bt.SoftBodyRigidBodyCollisionConfiguration();
#else
var collisionConfiguration = new bullet.Bt.DefaultCollisionConfiguration();
#end
dispatcher = new bullet.Bt.CollisionDispatcher(collisionConfiguration);
var solver = new bullet.Bt.SequentialImpulseConstraintSolver();
var g = iron.Scene.active.raw.gravity;
var gravity = g == null ? new Vec4(0, 0, -9.81) : new Vec4(g[0], g[1], g[2]);
#if lnx_physics_soft
var softSolver = new bullet.Bt.DefaultSoftBodySolver();
world = new bullet.Bt.SoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softSolver);
vec1.setX(gravity.x);
vec1.setY(gravity.y);
vec1.setZ(gravity.z);
#if js
world.getWorldInfo().set_m_gravity(vec1);
#else
world.getWorldInfo().m_gravity = vec1;
#end
#else
world = new bullet.Bt.DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
#end
setGravity(gravity);
}
public function setGravity(v: Vec4) {
vec1.setValue(v.x, v.y, v.z);
world.setGravity(vec1);
}
public function getGravity(): Vec4{
var g = world.getGravity();
return (new Vec4(g.x(), g.y(), g.z()));
}
public function addRigidBody(body: RigidBody) {
#if js
world.addRigidBodyToGroup(body.body, body.group, body.mask);
#else
world.addRigidBody(body.body, body.group, body.mask);
#end
rbMap.set(body.id, body);
}
public function addPhysicsConstraint(constraint: PhysicsConstraint) {
world.addConstraint(constraint.con, constraint.disableCollisions);
conMap.set(constraint.id, constraint);
}
public function removeRigidBody(body: RigidBody) {
if (body.destroyed) return;
body.destroyed = true;
if (world != null) world.removeRigidBody(body.body);
rbMap.remove(body.id);
body.delete();
}
public function removePhysicsConstraint(constraint: PhysicsConstraint) {
if(world != null) world.removeConstraint(constraint.con);
conMap.remove(constraint.id);
constraint.delete();
}
// public function addKinematicCharacterController(controller:KinematicCharacterController) {
// if (!pairCache){ // Only create PairCache if needed
// world.getPairCache().setInternalGhostPairCallback(BtGhostPairCallbackPointer.create());
// pairCache = true;
// }
// world.addAction(controller.character);
// world.addCollisionObjectToGroup(controller.body, controller.group, controller.group);
// }
// public function removeKinematicCharacterController(controller:KinematicCharacterController) {
// if (world != null) {
// world.removeCollisionObject(controller.body);
// world.removeAction(controller.character);
// }
// #if js
// bullet.Bt.Ammo.destroy(controller.body);
// #else
// var cbody = controller.body;
// untyped __cpp__("delete cbody");
// #end
// }
/**
Used to get intersecting rigid bodies with the passed in RigidBody as reference. Often used when checking for object collisions.
@param body The passed in RigidBody to be checked for intersecting rigid bodies.
@return `Array<RigidBody>`
**/
public function getContacts(body: RigidBody): Array<RigidBody> {
if (contacts.length == 0) return null;
var res: Array<RigidBody> = [];
for (i in 0...contacts.length) {
var c = contacts[i];
var rb = null;
#if js
if (c.a == untyped body.body.userIndex) rb = rbMap.get(c.b);
else if (c.b == untyped body.body.userIndex) rb = rbMap.get(c.a);
#else
var ob: bullet.Bt.CollisionObject = body.body;
if (c.a == ob.getUserIndex()) rb = rbMap.get(c.b);
else if (c.b == ob.getUserIndex()) rb = rbMap.get(c.a);
#end
if (rb != null && res.indexOf(rb) == -1) res.push(rb);
}
return res;
}
public function getContactPairs(body: RigidBody): Array<ContactPair> {
if (contacts.length == 0) return null;
var res: Array<ContactPair> = [];
for (i in 0...contacts.length) {
var c = contacts[i];
#if js
if (c.a == untyped body.body.userIndex) res.push(c);
else if (c.b == untyped body.body.userIndex) res.push(c);
#else
var ob: bullet.Bt.CollisionObject = body.body;
if (c.a == ob.getUserIndex()) res.push(c);
else if (c.b == ob.getUserIndex()) res.push(c);
#end
}
return res;
}
public function findBody(id: Int): RigidBody{
var rb = rbMap.get(id);
return rb;
}
function lateUpdate() {
var t = Time.delta * timeScale;
if (t == 0.0) return; // Simulation paused
#if lnx_debug
var startTime = kha.Scheduler.realTime();
#end
if (preUpdates != null) for (f in preUpdates) f();
//Bullet physics fixed timescale
var fixedTime = 1.0 / 60;
//This condition must be satisfied to not loose time
var currMaxSteps = t < (fixedTime * maxSteps) ? maxSteps : 1;
world.stepSimulation(t, currMaxSteps, fixedTime);
updateContacts();
for (rb in rbMap) @:privateAccess rb.physicsUpdate();
#if lnx_debug
physTime = kha.Scheduler.realTime() - startTime;
#end
}
function updateContacts() {
contacts.resize(0);
var disp: bullet.Bt.Dispatcher = dispatcher;
var numManifolds = disp.getNumManifolds();
for (i in 0...numManifolds) {
var contactManifold = disp.getManifoldByIndexInternal(i);
#if js
var body0 = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(contactManifold.getBody0());
var body1 = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(contactManifold.getBody1());
#else
var body0: bullet.Bt.CollisionObject = contactManifold.getBody0();
var body1: bullet.Bt.CollisionObject = contactManifold.getBody1();
#end
var numContacts = contactManifold.getNumContacts();
for (j in 0...numContacts) {
var pt = contactManifold.getContactPoint(j);
var posA: bullet.Bt.Vector3 = null;
var posB: bullet.Bt.Vector3 = null;
var nor: bullet.Bt.Vector3 = null;
var cp: ContactPair = null;
#if js
posA = pt.get_m_positionWorldOnA();
posB = pt.get_m_positionWorldOnB();
nor = pt.get_m_normalWorldOnB();
cp = new ContactPair(untyped body0.userIndex, untyped body1.userIndex);
#else
posA = pt.m_positionWorldOnA;
posB = pt.m_positionWorldOnB;
nor = pt.m_normalWorldOnB;
cp = new ContactPair(body0.getUserIndex(), body1.getUserIndex());
#end
cp.posA = new Vec4(posA.x(), posA.y(), posA.z());
cp.posB = new Vec4(posB.x(), posB.y(), posB.z());
cp.normOnB = new Vec4(nor.x(), nor.y(), nor.z());
cp.impulse = pt.getAppliedImpulse();
cp.distance = pt.getDistance();
contacts.push(cp);
#if hl
pt.delete();
posA.delete();
posB.delete();
nor.delete();
#end
}
}
}
public function pickClosest(inputX: Float, inputY: Float, group: Int = 0x00000001, mask = 0xFFFFFFFF): RigidBody {
var camera = iron.Scene.active.camera;
var start = new Vec4();
var end = new Vec4();
RayCaster.getDirection(start, end, inputX, inputY, camera);
var hit = rayCast(camera.transform.world.getLoc(), end, group, mask);
var rb = (hit != null) ? hit.rb : null;
return rb;
}
public function rayCast(from: Vec4, to: Vec4, group: Int = 0x00000001, mask = 0xFFFFFFFF): Hit {
var rayFrom = vec1;
var rayTo = vec2;
rayFrom.setValue(from.x, from.y, from.z);
rayTo.setValue(to.x, to.y, to.z);
var rayCallback = new bullet.Bt.ClosestRayResultCallback(rayFrom, rayTo);
#if js
rayCallback.set_m_collisionFilterGroup(group);
rayCallback.set_m_collisionFilterMask(mask);
#elseif (cpp || hl)
rayCallback.m_collisionFilterGroup = group;
rayCallback.m_collisionFilterMask = mask;
#end
var worldDyn: bullet.Bt.DynamicsWorld = world;
var worldCol: bullet.Bt.CollisionWorld = worldDyn;
worldCol.rayTest(rayFrom, rayTo, rayCallback);
var rb: RigidBody = null;
var hitInfo: Hit = null;
var rc: bullet.Bt.RayResultCallback = rayCallback;
if (rc.hasHit()) {
#if js
var co = rayCallback.get_m_collisionObject();
var body = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(co);
var hit = rayCallback.get_m_hitPointWorld();
hitPointWorld.set(hit.x(), hit.y(), hit.z());
var norm = rayCallback.get_m_hitNormalWorld();
hitNormalWorld.set(norm.x(), norm.y(), norm.z());
rb = rbMap.get(untyped body.userIndex);
hitInfo = new Hit(rb, hitPointWorld, hitNormalWorld);
#elseif (cpp || hl)
var hit = rayCallback.m_hitPointWorld;
hitPointWorld.set(hit.x(), hit.y(), hit.z());
var norm = rayCallback.m_hitNormalWorld;
hitNormalWorld.set(norm.x(), norm.y(), norm.z());
rb = rbMap.get(rayCallback.m_collisionObject.getUserIndex());
hitInfo = new Hit(rb, hitPointWorld, hitNormalWorld);
#end
}
#if js
bullet.Bt.Ammo.destroy(rayCallback);
#else
rayCallback.delete();
#end
return hitInfo;
}
public function convexSweepTest(rb: RigidBody, from: Vec4, to: Vec4, rotation: Quat, group: Int = 0x00000001, mask = 0xFFFFFFFF): ConvexHit {
var transformFrom = transform1;
var transformTo = transform2;
transformFrom.setIdentity();
transformTo.setIdentity();
vec1.setValue(from.x, from.y, from.z);
transformFrom.setOrigin(vec1);
quat1.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
transformFrom.setRotation(quat1);
vec2.setValue(to.x, to.y, to.z);
transformTo.setOrigin(vec2);
quat1.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
transformFrom.setRotation(quat1);
var convexCallback = new bullet.Bt.ClosestConvexResultCallback(vec1, vec2);
#if js
convexCallback.set_m_collisionFilterGroup(group);
convexCallback.set_m_collisionFilterMask(mask);
#elseif (cpp || hl)
convexCallback.m_collisionFilterGroup = group;
convexCallback.m_collisionFilterMask = mask;
#end
var worldDyn: bullet.Bt.DynamicsWorld = world;
var worldCol: bullet.Bt.CollisionWorld = worldDyn;
var bodyColl: bullet.Bt.ConvexShape = cast rb.body.getCollisionShape();
worldCol.convexSweepTest(bodyColl, transformFrom, transformTo, convexCallback, 0.0);
var hitInfo: ConvexHit = null;
var cc: bullet.Bt.ClosestConvexResultCallback = convexCallback;
if (cc.hasHit()) {
#if js
var hit = convexCallback.get_m_hitPointWorld();
convexHitPointWorld.set(hit.x(), hit.y(), hit.z());
var norm = convexCallback.get_m_hitNormalWorld();
convexHitNormalWorld.set(norm.x(), norm.y(), norm.z());
var hitFraction = convexCallback.get_m_closestHitFraction();
#elseif (cpp || hl)
var hit = convexCallback.m_hitPointWorld;
convexHitPointWorld.set(hit.x(), hit.y(), hit.z());
var norm = convexCallback.m_hitNormalWorld;
convexHitNormalWorld.set(norm.x(), norm.y(), norm.z());
var hitFraction = convexCallback.m_closestHitFraction;
#end
hitInfo = new ConvexHit(convexHitPointWorld, convexHitNormalWorld, hitFraction);
}
#if js
bullet.Bt.Ammo.destroy(convexCallback);
#else
convexCallback.delete();
#end
return hitInfo;
}
public function notifyOnPreUpdate(f: Void->Void) {
if (preUpdates == null) preUpdates = [];
preUpdates.push(f);
}
public function removePreUpdate(f: Void->Void) {
preUpdates.remove(f);
}
public function setDebugDrawMode(debugDrawMode: DebugDrawMode) {
if (debugDrawHelper == null) {
if (debugDrawMode == NoDebug) {
return;
}
initDebugDrawing();
}
#if js
world.getDebugDrawer().setDebugMode(debugDrawMode);
#elseif hl
hlDebugDrawer_setDebugMode(debugDrawMode);
#end
}
public inline function getDebugDrawMode(): DebugDrawMode {
if (debugDrawHelper == null) {
return NoDebug;
}
#if js
return world.getDebugDrawer().getDebugMode();
#elseif hl
return hlDebugDrawer_getDebugMode();
#else
return NoDebug;
#end
}
function initDebugDrawing() {
debugDrawHelper = new DebugDrawHelper(this);
#if js
final drawer = new bullet.Bt.DebugDrawer();
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html?highlight=jsimplementation#sub-classing-c-base-classes-in-javascript-jsimplementation
drawer.drawLine = debugDrawHelper.drawLine;
drawer.drawContactPoint = debugDrawHelper.drawContactPoint;
drawer.reportErrorWarning = debugDrawHelper.reportErrorWarning;
drawer.draw3dText = debugDrawHelper.draw3dText;
// From the Leenkx API perspective this is not required,
// but Ammo requires it and will result in a black screen if not set
drawer.setDebugMode = debugDrawHelper.setDebugMode;
drawer.getDebugMode = debugDrawHelper.getDebugMode;
world.setDebugDrawer(drawer);
#elseif hl
hlDebugDrawer_setDrawLine(debugDrawHelper.drawLine);
hlDebugDrawer_setDrawContactPoint(debugDrawHelper.drawContactPoint);
hlDebugDrawer_setReportErrorWarning(debugDrawHelper.reportErrorWarning);
hlDebugDrawer_setDraw3dText(debugDrawHelper.draw3dText);
hlDebugDrawer_worldSetGlobalDebugDrawer(world);
#end
}
#if hl
@:hlNative("bullet", "debugDrawer_worldSetGlobalDebugDrawer")
public static function hlDebugDrawer_worldSetGlobalDebugDrawer(world: #if lnx_physics_soft bullet.Bt.SoftRigidDynamicsWorld #else bullet.Bt.DiscreteDynamicsWorld #end) {}
@:hlNative("bullet", "debugDrawer_setDebugMode")
public static function hlDebugDrawer_setDebugMode(debugMode: Int) {}
@:hlNative("bullet", "debugDrawer_getDebugMode")
public static function hlDebugDrawer_getDebugMode(): Int { return 0; }
@:hlNative("bullet", "debugDrawer_setDrawLine")
public static function hlDebugDrawer_setDrawLine(func: bullet.Bt.Vector3->bullet.Bt.Vector3->bullet.Bt.Vector3->Void) {}
@:hlNative("bullet", "debugDrawer_setDrawContactPoint")
public static function hlDebugDrawer_setDrawContactPoint(func: bullet.Bt.Vector3->bullet.Bt.Vector3->kha.FastFloat->Int->bullet.Bt.Vector3->Void) {}
@:hlNative("bullet", "debugDrawer_setReportErrorWarning")
public static function hlDebugDrawer_setReportErrorWarning(func: hl.Bytes->Void) {}
@:hlNative("bullet", "debugDrawer_setDraw3dText")
public static function hlDebugDrawer_setDraw3dText(func: bullet.Bt.Vector3->hl.Bytes->Void) {}
#end
}
/**
Debug flags for Bullet physics, despite the name not solely related to debug drawing.
You can combine multiple flags with bitwise operations.
Taken from Bullet's `btIDebugDraw::DebugDrawModes` enum.
Please note that the descriptions of the individual flags are a result of inspecting the Bullet sources and thus might contain inaccuracies.
@see `leenkx.trait.physics.PhysicsWorld.getDebugDrawMode()`
@see `leenkx.trait.physics.PhysicsWorld.setDebugDrawMode()`
**/
// Not all of the flags below are actually used in the library core, some of them are only used
// in individual Bullet example projects. The intention of the original authors is unknown,
// so whether those flags are actually meant to get their purpose from the implementing application
// and not from the library remains a mystery...
enum abstract DebugDrawMode(Int) from Int to Int {
/** All debug flags off. **/
var NoDebug = 0;
/** Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations. **/
var DrawWireframe = 1;
/** Draw axis-aligned minimum bounding boxes (AABBs) of the physics collider meshes. **/
var DrawAABB = 1 << 1;
/** Not used in Leenkx. **/
// Only used in a Bullet physics example at the moment:
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/ExampleBrowser/GL_ShapeDrawer.cpp#L616-L644
var DrawFeaturesText = 1 << 2;
/** Visualize contact points of multiple colliders. **/
var DrawContactPoints = 1 << 3;
/**
Globally disable sleeping/deactivation of dynamic colliders.
**/
var NoDeactivation = 1 << 4;
/** Not used in Leenkx. **/
// Not used in the library core, in some Bullet examples this flag is used to print application-specific help text (e.g. keyboard shortcuts) to the screen, e.g.:
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/ForkLift/ForkLiftDemo.cpp#L586
var NoHelpText = 1 << 5;
/** Not used in Leenkx. **/
// Not used in the library core, appears to be the opposite of NoHelpText (not sure why there are two flags required for this...)
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/FractureDemo/FractureDemo.cpp#L189
var DrawText = 1 << 6;
/** Not used in Leenkx. **/
// Not even used in official Bullet examples, probably obsolete.
// Related to btQuickprof.h: https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=1285#p4743
// Probably replaced by define: https://github.com/bulletphysics/bullet3/commit/d051e2eacb01a948c7b53e24fd3d9942ce64bdcc
var ProfileTimings = 1 << 7;
/** Not used in Leenkx. **/
// Not even used in official Bullet examples, might be obsolete.
var EnableSatComparison = 1 << 8;
/** Not used in Leenkx. **/
var DisableBulletLCP = 1 << 9;
/** Not used in Leenkx. **/
var EnableCCD = 1 << 10;
/** Draw axis gizmos for important constraint points. **/
var DrawConstraints = 1 << 11;
/** Draw additional constraint information such as distance or angle limits. **/
var DrawConstraintLimits = 1 << 12;
/** Not used in Leenkx. **/
// Only used in a Bullet physics example at the moment:
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/ExampleBrowser/GL_ShapeDrawer.cpp#L258
// We could use it in the future to toggle depth testing for lines, i.e. draw actual 3D lines if not set and Kha's g2 lines if set.
var FastWireframe = 1 << 13;
/**
Draw the normal vectors of the triangles of the physics collider meshes.
This only works for `Mesh` collision shapes.
**/
// Outside of Leenkx this works for a few more collision shapes
var DrawNormals = 1 << 14;
/**
Draw a small axis gizmo at the origin of the collision shape.
Only works if `DrawWireframe` is enabled as well.
**/
var DrawFrames = 1 << 15;
@:op(~A) public inline function bitwiseNegate(): DebugDrawMode {
return ~this;
}
@:op(A & B) public inline function bitwiseAND(other: DebugDrawMode): DebugDrawMode {
return this & other;
}
@:op(A | B) public inline function bitwiseOR(other: DebugDrawMode): DebugDrawMode {
return this | other;
}
}
#end

View File

@ -0,0 +1,695 @@
package leenkx.trait.physics.bullet;
#if lnx_bullet
import iron.math.Vec4;
import iron.math.Quat;
import iron.object.Transform;
import iron.object.MeshObject;
import iron.data.MeshData;
/**
RigidBody is used to allow objects to interact with Physics in your game including collisions and gravity.
RigidBody can also be used with the getContacts method to detect collisions and run appropriate code.
The Bullet physics engine is used for these calculations.
**/
@:access(leenkx.trait.physics.bullet.PhysicsWorld)
class RigidBody extends iron.Trait {
var shape: Shape;
public var physics: PhysicsWorld;
public var transform: Transform = null;
public var mass: Float;
public var friction: Float;
public var angularFriction: Float;
public var restitution: Float;
public var collisionMargin: Float;
public var linearDamping: Float;
public var angularDamping: Float;
public var animated: Bool;
public var staticObj: Bool;
public var destroyed = false;
var linearFactors: Array<Float>;
var angularFactors: Array<Float>;
var useDeactivation: Bool;
var deactivationParams: Array<Float>;
var ccd = false; // Continuous collision detection
public var group = 1;
public var mask = 1;
var trigger = false;
var bodyScaleX: Float; // Transform scale at creation time
var bodyScaleY: Float;
var bodyScaleZ: Float;
var currentScaleX: Float;
var currentScaleY: Float;
var currentScaleZ: Float;
var meshInterface: bullet.Bt.TriangleMesh;
public var body: bullet.Bt.RigidBody = null;
public var motionState: bullet.Bt.MotionState;
public var btshape: bullet.Bt.CollisionShape;
public var ready = false;
static var nextId = 0;
public var id = 0;
public var onReady: Void->Void = null;
public var onContact: Array<RigidBody->Void> = null;
public var heightData: haxe.io.Bytes = null;
#if js
static var ammoArray: Int = -1;
#end
static var nullvec = true;
static var vec1: bullet.Bt.Vector3;
static var vec2: bullet.Bt.Vector3;
static var vec3: bullet.Bt.Vector3;
static var quat1: bullet.Bt.Quaternion;
static var trans1: bullet.Bt.Transform;
static var trans2: bullet.Bt.Transform;
static var quat = new Quat();
static var CF_STATIC_OBJECT = 1;
static var CF_KINEMATIC_OBJECT = 2;
static var CF_NO_CONTACT_RESPONSE = 4;
static var CF_CHARACTER_OBJECT = 16;
static var convexHullCache = new Map<MeshData, bullet.Bt.ConvexHullShape>();
static var triangleMeshCache = new Map<MeshData, bullet.Bt.TriangleMesh>();
static var usersCache = new Map<MeshData, Int>();
public function new(shape = Shape.Box, mass = 1.0, friction = 0.5, restitution = 0.0, group = 1, mask = 1,
params: RigidBodyParams = null, flags: RigidBodyFlags = null) {
super();
if (nullvec) {
nullvec = false;
vec1 = new bullet.Bt.Vector3(0, 0, 0);
vec2 = new bullet.Bt.Vector3(0, 0, 0);
vec3 = new bullet.Bt.Vector3(0, 0, 0);
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 0);
trans1 = new bullet.Bt.Transform();
trans2 = new bullet.Bt.Transform();
}
this.shape = shape;
this.mass = mass;
this.friction = friction;
this.restitution = restitution;
this.group = group;
this.mask = mask;
if (params == null) params = {
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.0,
linearDeactivationThreshold: 0.0,
angularDeactivationThrshold: 0.0,
deactivationTime: 0.0
};
if (flags == null) flags = {
animated: false,
trigger: false,
ccd: false,
staticObj: false,
useDeactivation: true
};
this.linearDamping = params.linearDamping;
this.angularDamping = params.angularDamping;
this.angularFriction = params.angularFriction;
this.linearFactors = [params.linearFactorsX, params.linearFactorsY, params.linearFactorsZ];
this.angularFactors = [params.angularFactorsX, params.angularFactorsY, params.angularFactorsZ];
this.collisionMargin = params.collisionMargin;
this.deactivationParams = [params.linearDeactivationThreshold, params.angularDeactivationThrshold, params.deactivationTime];
this.animated = flags.animated;
this.trigger = flags.trigger;
this.ccd = flags.ccd;
this.staticObj = flags.staticObj;
this.useDeactivation = flags.useDeactivation;
notifyOnAdd(init);
}
inline function withMargin(f: Float) {
return f + f * collisionMargin;
}
public function notifyOnReady(f: Void->Void) {
onReady = f;
if (ready) onReady();
}
public function init() {
if (ready) return;
ready = true;
if (!Std.isOfType(object, MeshObject)) return; // No mesh data
transform = object.transform;
physics = leenkx.trait.physics.PhysicsWorld.active;
if (shape == Shape.Box) {
vec1.setX(withMargin(transform.dim.x / 2));
vec1.setY(withMargin(transform.dim.y / 2));
vec1.setZ(withMargin(transform.dim.z / 2));
btshape = new bullet.Bt.BoxShape(vec1);
}
else if (shape == Shape.Sphere) {
btshape = new bullet.Bt.SphereShape(withMargin(transform.dim.x / 2));
}
else if (shape == Shape.ConvexHull) {
var shapeConvex = fillConvexHull(transform.scale, collisionMargin);
btshape = shapeConvex;
}
else if (shape == Shape.Cone) {
var coneZ = new bullet.Bt.ConeShapeZ(
withMargin(transform.dim.x / 2), // Radius
withMargin(transform.dim.z)); // Height
var cone: bullet.Bt.ConeShape = coneZ;
btshape = cone;
}
else if (shape == Shape.Cylinder) {
vec1.setX(withMargin(transform.dim.x / 2));
vec1.setY(withMargin(transform.dim.y / 2));
vec1.setZ(withMargin(transform.dim.z / 2));
var cylZ = new bullet.Bt.CylinderShapeZ(vec1);
var cyl: bullet.Bt.CylinderShape = cylZ;
btshape = cyl;
}
else if (shape == Shape.Capsule) {
var r = transform.dim.x / 2;
var capsZ = new bullet.Bt.CapsuleShapeZ(
withMargin(r), // Radius
withMargin(transform.dim.z - r * 2)); // Height between 2 sphere centers
var caps: bullet.Bt.CapsuleShape = capsZ;
btshape = caps;
}
else if (shape == Shape.Mesh) {
meshInterface = fillTriangleMesh(transform.scale);
if (mass > 0) {
var shapeGImpact = new bullet.Bt.GImpactMeshShape(meshInterface);
shapeGImpact.updateBound();
var shapeConcave: bullet.Bt.ConcaveShape = shapeGImpact;
btshape = shapeConcave;
if (!physics.gimpactRegistered) {
#if js
new bullet.Bt.GImpactCollisionAlgorithm().registerAlgorithm(physics.dispatcher);
#else
shapeGImpact.registerAlgorithm(physics.dispatcher);
#end
physics.gimpactRegistered = true;
}
}
else {
var shapeBvh = new bullet.Bt.BvhTriangleMeshShape(meshInterface, true, true);
var shapeTri: bullet.Bt.TriangleMeshShape = shapeBvh;
var shapeConcave: bullet.Bt.ConcaveShape = shapeTri;
btshape = shapeConcave;
}
}
else if (shape == Shape.Terrain) {
#if js
var length = heightData.length;
if (ammoArray == -1) {
ammoArray = bullet.Bt.Ammo._malloc(length);
}
// From texture bytes
for (i in 0...length) {
bullet.Bt.Ammo.HEAPU8[ammoArray + i] = heightData.get(i);
}
var slice = Std.int(Math.sqrt(length)); // Assuming square terrain data
var axis = 2; // z
var dataType = 5; // u8
btshape = new bullet.Bt.HeightfieldTerrainShape(slice, slice, ammoArray, 1 / 255, 0, 1, axis, dataType, false);
vec1.setX(transform.dim.x / slice);
vec1.setY(transform.dim.y / slice);
vec1.setZ(transform.dim.z);
btshape.setLocalScaling(vec1);
#end
}
trans1.setIdentity();
vec1.setX(transform.worldx());
vec1.setY(transform.worldy());
vec1.setZ(transform.worldz());
trans1.setOrigin(vec1);
quat.fromMat(transform.world);
quat1.setValue(quat.x, quat.y, quat.z, quat.w);
trans1.setRotation(quat1);
var centerOfMassOffset = trans2;
centerOfMassOffset.setIdentity();
motionState = new bullet.Bt.DefaultMotionState(trans1, centerOfMassOffset);
vec1.setX(0);
vec1.setY(0);
vec1.setZ(0);
var inertia = vec1;
if (staticObj || animated) mass = 0;
if (mass > 0) btshape.calculateLocalInertia(mass, inertia);
var bodyCI = new bullet.Bt.RigidBodyConstructionInfo(mass, motionState, btshape, inertia);
body = new bullet.Bt.RigidBody(bodyCI);
var bodyColl: bullet.Bt.CollisionObject = body;
bodyColl.setFriction(friction);
bodyColl.setRollingFriction(angularFriction);
bodyColl.setRestitution(restitution);
if ( useDeactivation) {
setDeactivationParams(deactivationParams[0], deactivationParams[1], deactivationParams[2]);
}
else {
setActivationState(bullet.Bt.CollisionObjectActivationState.DISABLE_DEACTIVATION);
}
if (linearDamping != 0.04 || angularDamping != 0.1) {
body.setDamping(linearDamping, angularDamping);
}
if (linearFactors != null) {
setLinearFactor(linearFactors[0], linearFactors[1], linearFactors[2]);
}
if (angularFactors != null) {
setAngularFactor(angularFactors[0], angularFactors[1], angularFactors[2]);
}
if (trigger) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
if (animated) {
bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_KINEMATIC_OBJECT);
bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() & ~CF_STATIC_OBJECT);
}
if (staticObj && !animated) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_STATIC_OBJECT);
if (ccd) setCcd(transform.radius);
bodyScaleX = currentScaleX = transform.scale.x;
bodyScaleY = currentScaleY = transform.scale.y;
bodyScaleZ = currentScaleZ = transform.scale.z;
id = nextId;
nextId++;
#if js
//body.setUserIndex(nextId);
untyped body.userIndex = id;
#else
bodyColl.setUserIndex(id);
#end
physics.addRigidBody(this);
notifyOnRemove(removeFromWorld);
if (onReady != null) onReady();
#if js
bullet.Bt.Ammo.destroy(bodyCI);
#else
bodyCI.delete();
#end
}
function physicsUpdate() {
if (!ready) return;
if (animated) {
syncTransform();
}
else {
var trans = body.getWorldTransform();
var p = trans.getOrigin();
var q = trans.getRotation();
transform.loc.set(p.x(), p.y(), p.z());
transform.rot.set(q.x(), q.y(), q.z(), q.w());
if (object.parent != null) {
var ptransform = object.parent.transform;
transform.loc.x -= ptransform.worldx();
transform.loc.y -= ptransform.worldy();
transform.loc.z -= ptransform.worldz();
}
transform.clearDelta();
transform.buildMatrix();
#if hl
p.delete();
q.delete();
trans.delete();
#end
}
if (onContact != null) {
var rbs = physics.getContacts(this);
if (rbs != null) for (rb in rbs) for (f in onContact) f(rb);
}
}
public function disableCollision() {
var bodyColl: bullet.Bt.CollisionObject = body;
bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
}
public function enableCollision() {
var bodyColl: bullet.Bt.CollisionObject = body;
bodyColl.setCollisionFlags(~bodyColl.getCollisionFlags() & CF_NO_CONTACT_RESPONSE);
}
public function removeFromWorld() {
if (physics != null) physics.removeRigidBody(this);
}
public function isActive() : Bool {
return body.isActive();
}
public function activate() {
var bodyColl: bullet.Bt.CollisionObject = body;
bodyColl.activate(false);
}
public function disableGravity() {
vec1.setValue(0, 0, 0);
body.setGravity(vec1);
}
public function enableGravity() {
body.setGravity(physics.world.getGravity());
}
public function setGravity(v: Vec4) {
vec1.setValue(v.x, v.y, v.z);
body.setGravity(vec1);
}
public function setActivationState(newState: Int) {
var bodyColl: bullet.Bt.CollisionObject = body;
bodyColl.setActivationState(newState);
}
public function setDeactivationParams(linearThreshold: Float, angularThreshold: Float, time: Float) {
body.setSleepingThresholds(linearThreshold, angularThreshold);
// body.setDeactivationTime(time); // not available in ammo
}
public function setUpDeactivation(useDeactivation: Bool, linearThreshold: Float, angularThreshold: Float, time: Float) {
this.useDeactivation = useDeactivation;
this.deactivationParams[0] = linearThreshold;
this.deactivationParams[1] = angularThreshold;
this.deactivationParams[2] = time;
}
public function isTriggerObject(isTrigger: Bool) {
this.trigger = isTrigger;
}
public function applyForce(force: Vec4, loc: Vec4 = null) {
activate();
vec1.setValue(force.x, force.y, force.z);
if (loc == null) {
body.applyCentralForce(vec1);
}
else {
vec2.setValue(loc.x, loc.y, loc.z);
body.applyForce(vec1, vec2);
}
}
public function applyImpulse(impulse: Vec4, loc: Vec4 = null) {
activate();
vec1.setValue(impulse.x, impulse.y, impulse.z);
if (loc == null) {
body.applyCentralImpulse(vec1);
}
else {
vec2.setValue(loc.x, loc.y, loc.z);
body.applyImpulse(vec1, vec2);
}
}
public function applyTorque(torque: Vec4) {
activate();
vec1.setValue(torque.x, torque.y, torque.z);
body.applyTorque(vec1);
}
public function applyTorqueImpulse(torque: Vec4) {
activate();
vec1.setValue(torque.x, torque.y, torque.z);
body.applyTorqueImpulse(vec1);
}
public function setLinearFactor(x: Float, y: Float, z: Float) {
vec1.setValue(x, y, z);
body.setLinearFactor(vec1);
}
public function setAngularFactor(x: Float, y: Float, z: Float) {
vec1.setValue(x, y, z);
body.setAngularFactor(vec1);
}
public function getLinearVelocity(): Vec4 {
var v = body.getLinearVelocity();
return new Vec4(v.x(), v.y(), v.z());
}
public function setLinearVelocity(x: Float, y: Float, z: Float) {
vec1.setValue(x, y, z);
body.setLinearVelocity(vec1);
}
public function getAngularVelocity(): Vec4 {
var v = body.getAngularVelocity();
return new Vec4(v.x(), v.y(), v.z());
}
public function setAngularVelocity(x: Float, y: Float, z: Float) {
vec1.setValue(x, y, z);
body.setAngularVelocity(vec1);
}
public function getPointVelocity(x: Float, y: Float, z: Float) {
var linear = getLinearVelocity();
var relativePoint = new Vec4(x, y, z).sub(transform.world.getLoc());
var angular = getAngularVelocity().cross(relativePoint);
return linear.add(angular);
}
public function setFriction(f: Float) {
var bodyColl: bullet.Bt.CollisionObject = body;
bodyColl.setFriction(f);
// bodyColl.setRollingFriction(f);
this.friction = f;
}
public function notifyOnContact(f: RigidBody->Void) {
if (onContact == null) onContact = [];
onContact.push(f);
}
public function removeContact(f: RigidBody->Void) {
onContact.remove(f);
}
function setScale(v: Vec4) {
currentScaleX = v.x;
currentScaleY = v.y;
currentScaleZ = v.z;
vec1.setX(v.x / bodyScaleX);
vec1.setY(v.y / bodyScaleY);
vec1.setZ(v.z / bodyScaleZ);
btshape.setLocalScaling(vec1);
var worldDyn: bullet.Bt.DynamicsWorld = physics.world;
var worldCol: bullet.Bt.CollisionWorld = worldDyn;
worldCol.updateSingleAabb(body);
}
public function syncTransform() {
var t = transform;
t.buildMatrix();
vec1.setValue(t.worldx(), t.worldy(), t.worldz());
trans1.setOrigin(vec1);
quat.fromMat(t.world);
quat1.setValue(quat.x, quat.y, quat.z, quat.w);
trans1.setRotation(quat1);
if (animated) body.getMotionState().setWorldTransform(trans1);
else body.setCenterOfMassTransform(trans1);
if (currentScaleX != t.scale.x || currentScaleY != t.scale.y || currentScaleZ != t.scale.z) setScale(t.scale);
activate();
}
// Continuous collision detection
public function setCcd(sphereRadius: Float, motionThreshold = 1e-7) {
var bodyColl: bullet.Bt.CollisionObject = body;
bodyColl.setCcdSweptSphereRadius(sphereRadius);
bodyColl.setCcdMotionThreshold(motionThreshold);
}
function fillConvexHull(scale: Vec4, margin: kha.FastFloat): bullet.Bt.ConvexHullShape {
// Check whether shape already exists
var data = cast(object, MeshObject).data;
var shape = convexHullCache.get(data);
if (shape != null) {
usersCache.set(data, usersCache.get(data) + 1);
return shape;
}
shape = new bullet.Bt.ConvexHullShape();
convexHullCache.set(data, shape);
usersCache.set(data, 1);
var positions = data.geom.positions.values;
var sx: kha.FastFloat = scale.x * (1.0 - margin) * (1 / 32767);
var sy: kha.FastFloat = scale.y * (1.0 - margin) * (1 / 32767);
var sz: kha.FastFloat = scale.z * (1.0 - margin) * (1 / 32767);
if (data.raw.scale_pos != null) {
sx *= data.raw.scale_pos;
sy *= data.raw.scale_pos;
sz *= data.raw.scale_pos;
}
for (i in 0...Std.int(positions.length / 4)) {
vec1.setX(positions[i * 4 ] * sx);
vec1.setY(positions[i * 4 + 1] * sy);
vec1.setZ(positions[i * 4 + 2] * sz);
shape.addPoint(vec1, true);
}
return shape;
}
function fillTriangleMesh(scale: Vec4): bullet.Bt.TriangleMesh {
// Check whether shape already exists
var data = cast(object, MeshObject).data;
var triangleMesh = triangleMeshCache.get(data);
if (triangleMesh != null) {
usersCache.set(data, usersCache.get(data) + 1);
return triangleMesh;
}
triangleMesh = new bullet.Bt.TriangleMesh(true, true);
triangleMeshCache.set(data, triangleMesh);
usersCache.set(data, 1);
var positions = data.geom.positions.values;
var indices = data.geom.indices;
var sx: kha.FastFloat = scale.x * (1 / 32767);
var sy: kha.FastFloat = scale.y * (1 / 32767);
var sz: kha.FastFloat = scale.z * (1 / 32767);
if (data.raw.scale_pos != null) {
sx *= data.raw.scale_pos;
sy *= data.raw.scale_pos;
sz *= data.raw.scale_pos;
}
for (ar in indices) {
for (i in 0...Std.int(ar.length / 3)) {
vec1.setX(positions[ar[i * 3 ] * 4 ] * sx);
vec1.setY(positions[ar[i * 3 ] * 4 + 1] * sy);
vec1.setZ(positions[ar[i * 3 ] * 4 + 2] * sz);
vec2.setX(positions[ar[i * 3 + 1] * 4 ] * sx);
vec2.setY(positions[ar[i * 3 + 1] * 4 + 1] * sy);
vec2.setZ(positions[ar[i * 3 + 1] * 4 + 2] * sz);
vec3.setX(positions[ar[i * 3 + 2] * 4 ] * sx);
vec3.setY(positions[ar[i * 3 + 2] * 4 + 1] * sy);
vec3.setZ(positions[ar[i * 3 + 2] * 4 + 2] * sz);
triangleMesh.addTriangle(vec1, vec2, vec3);
}
}
return triangleMesh;
}
public function delete() {
#if js
bullet.Bt.Ammo.destroy(motionState);
bullet.Bt.Ammo.destroy(body);
#else
motionState.delete();
body.delete();
#end
// Delete shape if no other user is found
if (shape == Shape.ConvexHull || shape == Shape.Mesh) {
var data = cast(object, MeshObject).data;
var i = usersCache.get(data) - 1;
usersCache.set(data, i);
if(shape == Shape.Mesh) deleteShape();
if (i <= 0) {
if(shape == Shape.ConvexHull)
{
deleteShape();
convexHullCache.remove(data);
}
else
{
triangleMeshCache.remove(data);
if(meshInterface != null)
{
#if js
bullet.Bt.Ammo.destroy(meshInterface);
#else
meshInterface.delete();
#end
}
}
}
}
else deleteShape();
}
inline function deleteShape() {
#if js
bullet.Bt.Ammo.destroy(btshape);
#else
btshape.delete();
#end
}
}
@:enum abstract Shape(Int) from Int to Int {
var Box = 0;
var Sphere = 1;
var ConvexHull = 2;
var Mesh = 3;
var Cone = 4;
var Cylinder = 5;
var Capsule = 6;
var Terrain = 7;
}
typedef RigidBodyParams = {
var linearDamping: Float;
var angularDamping: Float;
var angularFriction: Float;
var linearFactorsX: Float;
var linearFactorsY: Float;
var linearFactorsZ: Float;
var angularFactorsX: Float;
var angularFactorsY: Float;
var angularFactorsZ: Float;
var collisionMargin: Float;
var linearDeactivationThreshold: Float;
var angularDeactivationThrshold: Float;
var deactivationTime: Float;
}
typedef RigidBodyFlags = {
var animated: Bool;
var trigger: Bool;
var ccd: Bool;
var staticObj: Bool;
var useDeactivation: Bool;
}
#end

View File

@ -0,0 +1,431 @@
package leenkx.trait.physics.bullet;
#if lnx_bullet
import iron.Scene;
import haxe.ds.Vector;
import kha.arrays.ByteArray;
import bullet.Bt.Vector3;
import bullet.Bt.CollisionObjectActivationState;
import iron.math.Vec4;
import iron.math.Mat4;
import iron.Trait;
import iron.object.MeshObject;
import iron.data.Geometry;
import iron.data.MeshData;
import iron.data.SceneFormat;
import kha.arrays.Uint32Array;
#if lnx_physics_soft
import leenkx.trait.physics.RigidBody;
import leenkx.trait.physics.PhysicsWorld;
#end
class SoftBody extends Trait {
#if (!lnx_physics_soft)
public function new() { super(); }
#else
static var physics: PhysicsWorld = null;
public var ready = false;
var shape: SoftShape;
var bend: Float;
var mass: Float;
var margin: Float;
public var vertOffsetX = 0.0;
public var vertOffsetY = 0.0;
public var vertOffsetZ = 0.0;
public var body: bullet.Bt.SoftBody;
static var helpers: bullet.Bt.SoftBodyHelpers;
static var helpersCreated = false;
static var worldInfo: bullet.Bt.SoftBodyWorldInfo;
var vertexIndexMap: Map<Int, Array<Int>>;
public function new(shape = SoftShape.Cloth, bend = 0.5, mass = 1.0, margin = 0.04) {
super();
this.shape = shape;
this.bend = bend;
this.mass = mass;
this.margin = margin;
//notifyOnAdd(init);
//The above line works as well, but the object transforms are not set
//properly, so the positions are not accurate
notifyOnInit(init);
}
function fromI16(ar: kha.arrays.Int16Array, scalePos: Float): haxe.ds.Vector<Float> {
var vals = new haxe.ds.Vector<Float>(Std.int(ar.length / 4) * 3);
for (i in 0...Std.int(vals.length / 3)) {
vals[i * 3 ] = (ar[i * 4 ] / 32767) * scalePos;
vals[i * 3 + 1] = (ar[i * 4 + 1] / 32767) * scalePos;
vals[i * 3 + 2] = (ar[i * 4 + 2] / 32767) * scalePos;
}
return vals;
}
function fromU32(ars: Array<kha.arrays.Uint32Array>): haxe.ds.Vector<Int> {
var len = 0;
for (ar in ars) len += ar.length;
var vals = new haxe.ds.Vector<Int>(len);
var i = 0;
for (ar in ars) {
for (j in 0...ar.length) {
vals[i] = ar[j];
i++;
}
}
return vals;
}
function generateVertexIndexMap(ind: haxe.ds.Vector<Int>, vert: haxe.ds.Vector<Int>) {
if (vertexIndexMap == null) vertexIndexMap = new Map();
for (i in 0...ind.length) {
var currentVertex = vert[i];
var currentIndex = ind[i];
var mapping = vertexIndexMap.get(currentVertex);
if (mapping == null) {
vertexIndexMap.set(currentVertex, [currentIndex]);
}
else {
if(! mapping.contains(currentIndex)) mapping.push(currentIndex);
}
}
}
function init() {
var mo = cast(object, MeshObject);
//Set new mesh data for this object
new MeshData(mo.data.raw, function (data) {
mo.setData(data);
//Init soft body after setting new data
initSoftBody();
//If the above line is commented out, the program becomes unresponsive with white screen
//and no errors.
});
}
var v = new Vec4();
function initSoftBody() {
if (ready) return;
ready = true;
if (physics == null) physics = leenkx.trait.physics.PhysicsWorld.active;
var mo = cast(object, MeshObject);
mo.frustumCulling = false;
var geom = mo.data.geom;
var rawData = mo.data.raw;
var vertexMap: Array<Uint32Array> = [];
for (ind in rawData.index_arrays) {
if (ind.vertex_map == null) return;
vertexMap.push(ind.vertex_map);
}
var vecind = fromU32(geom.indices);
var vertexMapArray = fromU32(vertexMap);
generateVertexIndexMap(vecind, vertexMapArray);
// Parented soft body - clear parent location
if (object.parent != null && object.parent.name != "") {
object.transform.loc.x += object.parent.transform.worldx();
object.transform.loc.y += object.parent.transform.worldy();
object.transform.loc.z += object.parent.transform.worldz();
object.transform.localOnly = true;
object.transform.buildMatrix();
}
var positions = fromI16(geom.positions.values, mo.data.scalePos);
for (i in 0...Std.int(positions.length / 3)) {
v.set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
v.applyQuat(object.transform.rot);
v.x *= object.transform.scale.x;
v.y *= object.transform.scale.y;
v.z *= object.transform.scale.z;
v.addf(object.transform.worldx(), object.transform.worldy(), object.transform.worldz());
positions[i * 3 ] = v.x;
positions[i * 3 + 1] = v.y;
positions[i * 3 + 2] = v.z;
}
var numtri = 0;
for (ar in geom.indices) numtri += Std.int(ar.length / 3);
if (!helpersCreated) {
helpers = new bullet.Bt.SoftBodyHelpers();
worldInfo = physics.world.getWorldInfo();
helpersCreated = true;
#if hl
//world info is passed as value and not as a reference, need to set gravity again in HL.
worldInfo.m_gravity = physics.world.getGravity();
#end
}
var vertsLength = 0;
for (key in vertexIndexMap.keys()) vertsLength++;
var positionsVector: haxe.ds.Vector<Float> = new haxe.ds.Vector<Float>(vertsLength * 3);
for (key in 0...vertsLength){
var i = vertexIndexMap.get(key)[0];
positionsVector.set(key * 3 , positions[i * 3 ]);
positionsVector.set(key * 3 + 1, positions[i * 3 + 1]);
positionsVector.set(key * 3 + 2, positions[i * 3 + 2]);
}
var indexMax: Int = 0;
var vecindVector: haxe.ds.Vector<Int> = new haxe.ds.Vector<Int>(vertexMapArray.length);
for (i in 0...vecindVector.length){
var idx = vertexMapArray.get(i);
vecindVector.set(i, idx);
indexMax = indexMax > idx ? indexMax : idx;
}
#if js
body = helpers.CreateFromTriMesh(worldInfo, positionsVector, vecindVector, numtri);
#else
//Create helper float array
var floatArray = new bullet.Bt.FloatArray(positionsVector.length);
for (i in 0...positionsVector.length){
floatArray.set(i, positionsVector[i]);
}
//Create helper int array
var intArray = new bullet.Bt.IntArray(vecindVector.length);
for (i in 0...vecindVector.length){
intArray.set(i, vecindVector[i]);
}
//Create soft body
body = helpers.CreateFromTriMesh(worldInfo, floatArray.raw, intArray.raw, numtri, false);
floatArray.delete();
intArray.delete();
#end
// body.generateClusters(4);
#if js
var cfg = body.get_m_cfg();
cfg.set_viterations(physics.solverIterations);
cfg.set_piterations(physics.solverIterations);
// cfg.set_collisions(0x0001 + 0x0020 + 0x0040); // self collision
cfg.set_collisions(0x11); // Soft-rigid, soft-soft
if (shape == SoftShape.Volume) {
cfg.set_kDF(0.1);
cfg.set_kDP(0.01);
cfg.set_kPR(bend);
}
#else
//Not passed as refernece
var cfg = body.m_cfg;
cfg.viterations = physics.solverIterations;
cfg.piterations = physics.solverIterations;
// body.m_cfg.collisions = 0x0001 + 0x0020 + 0x0040;
cfg.collisions = 0x11; // Soft-rigid, soft-soft
if (shape == SoftShape.Volume) {
cfg.kDF = 0.1;
cfg.kDP = 0.01;
cfg.kPR = bend;
}
//Set config again in HL
body.m_cfg = cfg;
#end
body.setTotalMass(mass, false);
body.getCollisionShape().setMargin(margin);
physics.world.addSoftBody(body, 1, -1);
body.setActivationState(CollisionObjectActivationState.DISABLE_DEACTIVATION);
#if hl
cfg.delete();
#end
notifyOnRemove(removeFromWorld);
notifyOnUpdate(update);
}
var va = new Vec4();
var vb = new Vec4();
var vc = new Vec4();
var cb = new Vec4();
var ab = new Vec4();
function update() {
var mo = cast(object, MeshObject);
var geom = mo.data.geom;
#if lnx_deinterleaved
var v: ByteArray = geom.vertexBuffers[0].buffer.lock();
var n: ByteArray = geom.vertexBuffers[1].buffer.lock();
#else
var v:ByteArray = geom.vertexBuffer.lock();
var vbPos = geom.vertexBufferMap.get("pos");
var v2 = vbPos != null ? vbPos.lock() : null; // For shadows
var l = geom.structLength;
#end
#if js
var nodes = body.get_m_nodes();
#else
var nodes = body.m_nodes;
#end
var numNodes = nodes.size();
//Finding the mean position of vertices in world space
vertOffsetX = 0.0;
vertOffsetY = 0.0;
vertOffsetZ = 0.0;
for (i in 0...numNodes) {
var node = nodes.at(i);
#if js
var nodePos = node.get_m_x();
#else
var nodePos = node.m_x;
#end
var mx = nodePos.x();
var my = nodePos.y();
var mz = nodePos.z();
vertOffsetX += mx;
vertOffsetY += my;
vertOffsetZ += mz;
#if hl
node.delete();
nodePos.delete();
#end
}
vertOffsetX /= numNodes;
vertOffsetY /= numNodes;
vertOffsetZ /= numNodes;
//Setting the mean position as object local location
mo.transform.scale.set(1, 1, 1);
mo.transform.loc.set(vertOffsetX, vertOffsetY, vertOffsetZ);
mo.transform.rot.set(0, 0, 0, 1);
//Checking maximum dimension for scalePos
var scalePos = 1.0;
for (i in 0...numNodes) {
var node = nodes.at(i);
#if js
var nodePos = node.get_m_x();
#else
var nodePos = node.m_x;
#end
var mx = nodePos.x() - vertOffsetX;
var my = nodePos.y() - vertOffsetY;
var mz = nodePos.z() - vertOffsetZ;
if (Math.abs(mx * 2) > scalePos) scalePos = Math.abs(mx * 2);
if (Math.abs(my * 2) > scalePos) scalePos = Math.abs(my * 2);
if (Math.abs(mz * 2) > scalePos) scalePos = Math.abs(mz * 2);
#if hl
node.delete();
nodePos.delete();
#end
}
//Set scalePos and buildMatrix
mo.data.scalePos = scalePos;
mo.transform.scaleWorld = scalePos;
mo.transform.buildMatrix();
//Set vertices with location offset
for (i in 0...nodes.size()) {
var node = nodes.at(i);
var indices = vertexIndexMap.get(i);
#if js
var nodePos = node.get_m_x();
var nodeNor = node.get_m_n();
#else
var nodePos = node.m_x;
var nodeNor = node.m_n;
#end
var mx = nodePos.x() - vertOffsetX;
var my = nodePos.y() - vertOffsetY;
var mz = nodePos.z() - vertOffsetZ;
var nx = nodeNor.x();
var ny = nodeNor.y();
var nz = nodeNor.z();
for (idx in indices){
#if lnx_deinterleaved
v.setInt16(idx * 8 , Std.int(mx * 32767 * (1 / scalePos)));
v.setInt16(idx * 8 + 2, Std.int(my * 32767 * (1 / scalePos)));
v.setInt16(idx * 8 + 4, Std.int(mz * 32767 * (1 / scalePos)));
n.setInt16(idx * 4 , Std.int(nx * 32767));
n.setInt16(idx * 4 + 2, Std.int(ny * 32767));
v.setInt16(idx * 8 + 6, Std.int(nz * 32767));
#else
var vertIndex = idx * l * 2;
v.setInt16(vertIndex , Std.int(mx * 32767 * (1 / scalePos)));
v.setInt16(vertIndex + 2, Std.int(my * 32767 * (1 / scalePos)));
v.setInt16(vertIndex + 4, Std.int(mz * 32767 * (1 / scalePos)));
if (vbPos != null) {
v2.setInt16(idx * 8 , v.getInt16(vertIndex ));
v2.setInt16(idx * 8 + 2, v.getInt16(vertIndex + 2));
v2.setInt16(idx * 8 + 4, v.getInt16(vertIndex + 4));
}
v.setInt16(vertIndex + 6, Std.int(nx * 32767));
v.setInt16(vertIndex + 8, Std.int(ny * 32767));
v.setInt16(vertIndex + 10, Std.int(nz * 32767));
#end
}
#if hl
node.delete();
nodePos.delete();
nodeNor.delete();
#end
}
// for (i in 0...Std.int(geom.indices[0].length / 3)) {
// var a = geom.indices[0][i * 3];
// var b = geom.indices[0][i * 3 + 1];
// var c = geom.indices[0][i * 3 + 2];
// va.set(v.get(a * l), v.get(a * l + 1), v.get(a * l + 2));
// vb.set(v.get(b * l), v.get(b * l + 1), v.get(b * l + 2));
// vc.set(v.get(c * l), v.get(c * l + 1), v.get(c * l + 2));
// cb.subvecs(vc, vb);
// ab.subvecs(va, vb);
// cb.cross(ab);
// cb.normalize();
// v.set(a * l + 3, cb.x);
// v.set(a * l + 4, cb.y);
// v.set(a * l + 5, cb.z);
// v.set(b * l + 3, cb.x);
// v.set(b * l + 4, cb.y);
// v.set(b * l + 5, cb.z);
// v.set(c * l + 3, cb.x);
// v.set(c * l + 4, cb.y);
// v.set(c * l + 5, cb.z);
// }
#if lnx_deinterleaved
geom.vertexBuffers[0].buffer.unlock();
geom.vertexBuffers[1].buffer.unlock();
#else
geom.vertexBuffer.unlock();
if (vbPos != null) vbPos.unlock();
#end
#if hl
nodes.delete();
#end
}
function removeFromWorld() {
physics.world.removeSoftBody(body);
#if js
bullet.Bt.Ammo.destroy(body);
#else
body.delete();
#end
}
#end
}
@:enum abstract SoftShape(Int) from Int {
var Cloth = 0;
var Volume = 1;
}
#end