forked from LeenkxTeam/LNXSDK
Update Files
This commit is contained in:
224
leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx
Normal file
224
leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx
Normal 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;
|
||||
}
|
||||
@ -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
|
||||
476
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx
Normal file
476
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx
Normal 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
|
||||
@ -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
|
||||
187
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx
Normal file
187
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx
Normal 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
|
||||
673
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx
Normal file
673
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx
Normal 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
|
||||
695
leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx
Normal file
695
leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx
Normal 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
|
||||
431
leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx
Normal file
431
leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx
Normal 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
|
||||
Reference in New Issue
Block a user