Update Files

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

View File

@ -0,0 +1,27 @@
package leenkx.trait;
import iron.Trait;
import iron.system.Input;
import iron.math.Vec4;
class ArcBall extends Trait {
@prop
public var axis = new Vec4(0, 0, 1);
public function new() {
super();
notifyOnUpdate(update);
}
function update() {
if (Input.occupied) return;
var mouse = Input.getMouse();
if (mouse.down()) {
object.transform.rotate(axis, -mouse.movementX / 100);
object.transform.rotate(object.transform.world.right(), -mouse.movementY / 100);
}
}
}

View File

@ -0,0 +1,77 @@
package leenkx.trait;
import iron.object.Object;
import iron.object.Animation;
import iron.Trait;
import iron.system.Input;
import iron.math.Vec4;
import iron.math.Quat;
class Character extends Trait {
var currentAction: String;
var animation: Animation;
var speed = 0.0;
var loc: Vec4 = new Vec4();
var lastLoc: Vec4 = null;
var framesIdle = 0; // Number of frames character did not move
@:prop
var actionIdle: String = "idle";
@:prop
var actionMove: String = "run";
public function new() {
super();
currentAction = actionIdle;
notifyOnInit(init);
}
function init() {
animation = object.animation;
// Try first child if we are running from armature
if (animation == null) {
if (object.children.length > 0) {
animation = object.children[0].animation;
}
}
if (animation == null) return;
notifyOnUpdate(update);
}
function update() {
// Get current position
var tr = object.transform;
loc.set(tr.worldx(), tr.worldy(), tr.worldz());
// Set previous position to current position if there is no previous position
if (lastLoc == null) lastLoc = new Vec4(loc.x, loc.y, loc.z);
// Check if character moved compared from last position
speed = Vec4.distance(loc, lastLoc);
// Update previous position to current position
// in preparation for next check
lastLoc.setFrom(loc);
if (speed == 0) framesIdle++;
else framesIdle = 0;
// If state is idle and character is in movement, play move walk animation
if (currentAction == actionIdle && framesIdle == 0) {
currentAction = actionMove;
if (actionMove != null) animation.play(actionMove);
}
else if (currentAction == actionMove && framesIdle > 2) { // Otherwise if state is walking and character is idle, play idle animation
currentAction = actionIdle;
if (actionIdle != null) animation.play(actionIdle);
}
}
}

View File

@ -0,0 +1,40 @@
package leenkx.trait;
import iron.object.MeshObject;
/**
* Trait to enable GPU instancing of Mesh objects
*/
class CustomParticle extends iron.Trait {
@prop
var ParticleCount: Int = 100;
public function new() {
super();
notifyOnInit(function() {
var partCount = ParticleCount;
setupSimpleInstanced(partCount);
});
}
function setupSimpleInstanced(count: Int){
if(object.raw.type == 'mesh_object')
{
var meshObjGeom = cast(object, MeshObject).data.geom;
meshObjGeom.instanced = true;
meshObjGeom.instanceCount = count;
}
}
public function updateParticleCount(count: Int){
if(object.raw.type == 'mesh_object')
{
var meshObjGeom = cast(object, MeshObject).data.geom;
meshObjGeom.instanced = true;
meshObjGeom.instanceCount = count;
}
}
}

View File

@ -0,0 +1,87 @@
package leenkx.trait;
import iron.math.Vec4;
import iron.system.Input;
import iron.object.Object;
import iron.object.CameraObject;
import leenkx.trait.physics.PhysicsWorld;
import leenkx.trait.internal.CameraController;
class FirstPersonController extends CameraController {
#if (!lnx_physics)
public function new() { super(); }
#else
var head: Object;
static inline var rotationSpeed = 2.0;
public function new() {
super();
iron.Scene.active.notifyOnInit(init);
}
function init() {
head = object.getChildOfType(CameraObject);
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
notifyOnUpdate(update);
notifyOnRemove(removed);
}
var xVec = Vec4.xAxis();
var zVec = Vec4.zAxis();
function preUpdate() {
if (Input.occupied || !body.ready) return;
var mouse = Input.getMouse();
var kb = Input.getKeyboard();
if (mouse.started() && !mouse.locked) mouse.lock();
else if (kb.started("escape") && mouse.locked) mouse.unlock();
if (mouse.locked || mouse.down()) {
head.transform.rotate(xVec, -mouse.movementY / 250 * rotationSpeed);
transform.rotate(zVec, -mouse.movementX / 250 * rotationSpeed);
body.syncTransform();
}
}
function removed() {
PhysicsWorld.active.removePreUpdate(preUpdate);
}
var dir = new Vec4();
function update() {
if (!body.ready) return;
if (jump) {
body.applyImpulse(new Vec4(0, 0, 16));
jump = false;
}
// Move
dir.set(0, 0, 0);
if (moveForward) dir.add(transform.look());
if (moveBackward) dir.add(transform.look().mult(-1));
if (moveLeft) dir.add(transform.right().mult(-1));
if (moveRight) dir.add(transform.right());
// Push down
var btvec = body.getLinearVelocity();
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
if (moveForward || moveBackward || moveLeft || moveRight) {
var dirN = dir.normalize();
dirN.mult(6);
body.activate();
body.setLinearVelocity(dirN.x, dirN.y, btvec.z - 1.0);
}
// Keep vertical
body.setAngularFactor(0, 0, 0);
camera.buildMatrix();
}
#end
}

View File

@ -0,0 +1,59 @@
package leenkx.trait;
import iron.Scene;
import iron.object.Object;
/**
This trait is to be used with a camera mounted on a camera boom with offset.
1. Place the camera as a child to another object, for example an 'Empty'.
2. Place this trait on the 'Empty' object.
3. Set the name of the target object to be followed by the camera.
**/
class FollowCamera extends iron.Trait {
@prop
var target: String;
@prop
var lerp: Bool = true;
@prop
var lerpSpeed: Float = 0.1;
var targetObj: Object;
var disabled = false;
public function new() {
super();
notifyOnInit(function() {
targetObj = Scene.active.getChild(target);
if (targetObj == null) {
disabled = true;
trace("FollowCamera error, unable to set target object");
}
if (Std.isOfType(object, iron.object.CameraObject)) {
disabled = true;
trace("FollowCamera error, this trait should not be placed directly on a camera objet. It should be placed on another object such as an Empty. The camera should be placed as a child to the Empty object with offset, creating a camera boom.");
}
});
notifyOnLateUpdate(function() {
if (!disabled) {
if (targetObj != null) {
if (lerp) {
object.transform.loc.lerp(object.transform.world.getLoc(), targetObj.transform.world.getLoc(), lerpSpeed);
}
else {
object.transform.loc = targetObj.transform.world.getLoc();
}
object.transform.buildMatrix();
}
else {
targetObj = Scene.active.getChild(target);
}
}
});
}
}

View File

@ -0,0 +1,81 @@
package leenkx.trait;
import iron.Trait;
import iron.math.Vec4;
import iron.math.Quat;
import iron.system.Tween;
class NavAgent extends Trait {
@prop
public var speed: Float = 5;
@prop
public var turnDuration: Float = 0.4;
@prop
public var heightOffset: Float = 0.0;
var path: Array<Vec4> = null;
var index = 0;
var rotAnim: TAnim = null;
var locAnim: TAnim = null;
public var tickPos: Null<Void -> Void>;
public var tickRot: Null<Void -> Void>;
public function new() {
super();
notifyOnRemove(stopTween);
}
public function setPath(path: Array<Vec4>) {
stopTween();
this.path = path;
index = 1;
notifyOnUpdate(update);
go();
}
function stopTween() {
if (rotAnim != null) Tween.stop(rotAnim);
if (locAnim != null) Tween.stop(locAnim);
}
public function stop() {
stopTween();
path = null;
}
function shortAngle(from: Float, to: Float): Float {
if (from < 0) from += Math.PI * 2;
if (to < 0) to += Math.PI * 2;
var delta = Math.abs(from - to);
if (delta > Math.PI) to = Math.PI * 2 - delta;
return to;
}
var orient = new Vec4();
function go() {
if (path == null || index >= path.length) return;
var p = path[index];
var dist = Vec4.distance(object.transform.loc, p);
orient.subvecs(p, object.transform.loc).normalize;
var targetAngle = Math.atan2(orient.y, orient.x) + Math.PI / 2;
locAnim = Tween.to({ target: object.transform.loc, props: { x: p.x, y: p.y, z: p.z + heightOffset }, duration: dist / speed, tick: tickPos, done: function() {
index++;
if (index < path.length) go();
else removeUpdate(update);
}});
var q = new Quat();
rotAnim = Tween.to({ target: object.transform, props: { rot: q.fromEuler(0, 0, targetAngle) }, tick: tickRot, duration: turnDuration});
}
function update() {
object.transform.buildMatrix();
}
}

View File

@ -0,0 +1,146 @@
package leenkx.trait;
#if lnx_navigation
import iron.math.Quat;
import iron.math.Vec4;
import leenkx.trait.navigation.Navigation;
#end
import iron.Trait;
class NavCrowd extends Trait {
#if !lnx_navigation
public function new() { super(); }
#else
// Position offset for the agent.
@prop
public var offset: Vec4 = new Vec4();
// Radius of the agent.
@prop
public var radius: Float = 1.0;
// Height of the agent.
@prop
public var height: Float = 1.0;
// Should the agent turn.
@prop
var turn: Bool = true;
// Turn rate in range (0, 1).
// 0 = No turn.
// 1 = Instant turn without interpolation.
@prop
public var turnSpeed: Float = 0.1;
// Threshold to avoid turning at low velocities which might causer jittering.
@prop
public var turnVelocityThreshold: Float = 0.1;
// Maximum speed of the crowd agent
@prop
public var maxSpeed: Float = 5.0;
// Maximum acceleration of the agent
@prop
public var maxAcceleration: Float = 100.0;
// How separated should the agents be. Effective when crowd separation flag is enabled.
@prop
public var separationWeight: Float = 1.0;
// Distance to check for collisions. Typically should be greater than agent radius.
@prop
public var collisionQueryRange: Float = 2.0;
// Anticipate turns and modify agent path
@prop
public var anticipateTurns: Bool = false;
@prop
public var obstacleAvoidance: Bool = false;
@prop
public var crowdSeparation: Bool = false;
@prop
public var optimizeVisibility: Bool = false;
@prop
public var optimizeTopology: Bool = false;
public var agentReady(default, null) = false;
var activeNavMesh: NavMesh = null;
var agentID = -1;
static inline var EPSILON = 0.0001;
public function new() {
super();
notifyOnUpdate(addAgent);
notifyOnRemove(removeAgent);
}
function addAgent() {
if(Navigation.active.navMeshes.length < 1) return;
if(! Navigation.active.navMeshes[0].ready) return;
activeNavMesh = Navigation.active.navMeshes[0];
var flags: Int = 0;
if(anticipateTurns) flags += 1;
if(obstacleAvoidance) flags += 2;
if(crowdSeparation) flags += 4;
if(optimizeVisibility) flags += 8;
if(optimizeTopology) flags += 16;
var position = object.transform.world.getLoc();
agentID = activeNavMesh.addCrowdAgent(this, position, radius, height, maxAcceleration, maxSpeed, flags, separationWeight, collisionQueryRange);
notifyOnUpdate(updateCrowdAgent);
removeUpdate(addAgent);
agentReady = true;
}
public function crowdAgentGoto(position: Vec4) {
if(!agentReady) return;
activeNavMesh.crowdAgentGoto(agentID, position);
}
public function crowdAgentTeleport(position: Vec4) {
if(!agentReady) return;
activeNavMesh.crowdAgentTeleport(agentID, position);
}
function removeAgent() {
activeNavMesh.removeCrowdAgent(agentID);
}
function updateCrowdAgent() {
if(!agentReady) return;
var pos = activeNavMesh.crowdGetAgentPosition(agentID);
pos.add(offset);
object.transform.loc.setFrom(pos);
if(turn) turnAgent();
object.transform.buildMatrix();
}
function turnAgent() {
var vel = activeNavMesh.crowdGetAgentVelocity(agentID);
vel.z = 0;
if(vel.length() < turnVelocityThreshold) return;
vel.normalize();
var targetRot = new Quat().fromTo(new Vec4(1, 0, 0, 1), vel);
var currentRot = new Quat().setFrom(object.transform.rot);
var res = new Quat().lerp(currentRot, targetRot, turnSpeed);
object.transform.rot = res;
}
#end
}

View File

@ -0,0 +1,505 @@
package leenkx.trait;
#if lnx_navigation
import recast.Recast.RecastConfigHelper;
import haxe.ds.Vector;
import leenkx.trait.navigation.DebugDrawHelper;
import kha.arrays.Float32Array;
import iron.object.Object;
import leenkx.trait.navigation.Navigation;
import iron.data.Data;
import iron.math.Vec4;
import iron.system.Time;
import kha.arrays.Uint32Array;
import iron.object.MeshObject;
import iron.data.Geometry;
import iron.data.MeshData;
#end
import iron.Trait;
class NavMesh extends Trait {
#if !lnx_navigation
public function new() { super(); }
#else
@prop
public var debugDraw:Bool = false;
// recast config:
/// Also use immidiate children for nav mesh construction.
@prop
public var combineImmidiateChildren:Bool = true;
/// The width of the field along the x-axis. [Limit: >= 0] [Units: vx]
@prop
public var width:Int = 0;
/// The height of the field along the z-axis. [Limit: >= 0] [Units: vx]
@prop
public var height:Int = 0;
/// Enable for tiled mesh and using temporary obstacles. `tileSize` will be ignored if set to false.
@prop
public var tiledMesh:Bool = true;
/// The width/height size of tile's on the xz-plane. [Limit: >= 0] [Units: vx]
@prop
public var tileSize:Int = 50;
/// The size of the non-navigable border around the heightfield. [Limit: >=0] [Units: vx]
@prop
public var borderSize:Int = 0;
/// The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu]
@prop
public var cellSize:Float = 0.2;
/// The y-axis cell size to use for fields. [Limit: > 0] [Units: wu]
@prop
public var cellHeight:Float = 0.3;
/// The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu]
//public var bmin:haxe.ds.Vector<Float>;
/// The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu]
//public var bmax:haxe.ds.Vector<Float>;
/// The maximum slope that is considered walkable. [Limits: 0 <= value < 90] [Units: Degrees]
@prop
public var walkableSlopeAngle:Float = 45.0;
/// Minimum floor to 'ceiling' height that will still allow the floor area to
/// be considered walkable. [Limit: >= 3] [Units: vx]
@prop
public var walkableHeight:Int = 3;
/// Maximum ledge height that is considered to still be traversable. [Limit: >=0] [Units: vx]
@prop
public var walkableClimb:Int = 2;
/// The distance to erode/shrink the walkable area of the heightfield away from
/// obstructions. [Limit: >=0] [Units: vx]
@prop
public var walkableRadius:Int = 1;
/// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx]
@prop
public var maxEdgeLen:Int = 12;
/// The maximum distance a simplfied contour's border edges should deviate
/// the original raw contour. [Limit: >=0] [Units: vx]
@prop
public var maxSimplificationError:Float = 1.3;
/// The minimum number of cells allowed to form isolated island areas. [Limit: >=0] [Units: vx]
@prop
public var minRegionArea:Int = 8;
/// Any regions with a span count smaller than this value will, if possible,
/// be merged with larger regions. [Limit: >=0] [Units: vx]
@prop
public var mergeRegionArea:Int = 20;
/// The maximum number of vertices allowed for polygons generated during the
/// contour to polygon conversion process. [Limit: >= 3]
@prop
public var maxVertsPerPoly:Int = 6;
/// Sets the sampling distance to use when generating the detail mesh.
/// (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu]
@prop
public var detailSampleDist:Float = 6;
/// The maximum distance the detail mesh surface should deviate from heightfield
/// data. (For height detail only.) [Limit: >=0] [Units: wu]
@prop
public var detailSampleMaxError:Float = 1;
// maximum number of crowd agents
@prop
public var maxCrowdAgents: Int = 50;
// maximum radius of crowd agents
@prop
public var maxCrowdAgentRadius: Float = 3.0;
var recastNavMesh: recast.Recast.NavMesh = null;
var recastCrowd: recast.Recast.Crowd = null;
public var ready(default, null) = false;
var crowdAgentMap: Map<Int, NavCrowd> = new Map();
var tempObstacleCounter = 0;
var tempObstacleMap: Map<Int, NavObstacle> = new Map();
var recastObstacleMap: Map<Int, recast.Recast.DtObstacleRef> = new Map();
public var navMeshDebugColor: kha.Color = Green;
var v:Vec4 = new Vec4();
public function new() {
super();
notifyOnInit(initNavMesh);
notifyOnUpdate(updateNavMesh);
}
function initNavMesh() {
if (ready) return;
Navigation.active.navMeshes.push(this);
if(debugDraw) Navigation.active.debugDrawHelper.setDebugMode(DrawWireframe);
recastNavMesh = new recast.Recast.NavMesh();
var recastConfig = new recast.Recast.RcConfig();
recastConfig.width = width;
recastConfig.height = height;
if(tiledMesh) {
recastConfig.tileSize = tileSize;
}
else {
recastConfig.tileSize = 0;
}
recastConfig.borderSize = borderSize;
recastConfig.cs = cellSize;
recastConfig.ch = cellHeight;
recastConfig.walkableSlopeAngle = walkableSlopeAngle;
recastConfig.walkableHeight = walkableHeight;
recastConfig.walkableClimb = walkableClimb;
recastConfig.walkableRadius = walkableRadius;
recastConfig.maxEdgeLen = maxEdgeLen;
recastConfig.maxSimplificationError = maxSimplificationError;
recastConfig.minRegionArea = minRegionArea;
recastConfig.mergeRegionArea = mergeRegionArea;
recastConfig.maxVertsPerPoly = maxVertsPerPoly;
recastConfig.detailSampleDist = detailSampleDist;
recastConfig.detailSampleMaxError = detailSampleMaxError;
var positionsArray = new Array<Float>();
var indexArray = new Array<Int>();
var currentIndexOffset = 0;
var reducedMeshData = getVerticesIndicesFromMesh(object, currentIndexOffset);
currentIndexOffset = reducedMeshData.maxIndex + 1;
positionsArray = positionsArray.concat(reducedMeshData.positions.toArray());
indexArray = indexArray.concat(reducedMeshData.indices.toArray());
if(combineImmidiateChildren) {
for(child in object.children) {
if(child.raw.type != "mesh_object") continue;
reducedMeshData = getVerticesIndicesFromMesh(child, currentIndexOffset);
currentIndexOffset = reducedMeshData.maxIndex + 1;
positionsArray = positionsArray.concat(reducedMeshData.positions.toArray());
indexArray = indexArray.concat(reducedMeshData.indices.toArray());
}
}
#if js
var positionsVector = Vector.fromArrayCopy(positionsArray);
var vecindVector = Vector.fromArrayCopy(indexArray);
recastNavMesh.build(positionsVector, positionsVector.length, vecindVector, vecindVector.length, recastConfig);
#else
var posLength = positionsArray.length;
var positionsVector = new recast.Recast.RcFloatArray(posLength);
for(i in 0...posLength) {
positionsVector.set(i, positionsArray[i]);
}
var indexLength = indexArray.length;
var vecindVector = new recast.Recast.RcIntArray(indexLength);
for(i in 0...indexLength) {
vecindVector.set(i, indexArray[i]);
}
recastNavMesh.build(positionsVector.raw, posLength, vecindVector.raw, indexLength, recastConfig);
#end
notifyOnUpdate(updateNavMesh);
ready = true;
}
public function reconstructNavMesh() {
removeUpdate(updateNavMesh);
if(recastCrowd != null) {
for(agent in crowdAgentMap.keys()) {
removeCrowdAgent(agent);
}
recastCrowd.destroy();
}
for(obstacle in tempObstacleMap.keys()) {
removeTempObstacle(obstacle);
}
ready = false;
initNavMesh();
}
public function updateNavMesh() {
if(!ready) return;
recastNavMesh.update();
}
public function findPath(from: Vec4, to: Vec4, done: Array<Vec4>->Void) {
if (!ready) return;
var start = RecastConversions.recastVec3FromVec4(from);
var end = RecastConversions.recastVec3FromVec4(to);
var navPath = recastNavMesh.computePath(start, end);
var pathVec = new Array<Vec4>();
for(i in 0...navPath.getPointCount()) {
pathVec.push(RecastConversions.vec4FromRecastVec3(navPath.getPoint(i)));
}
done(pathVec);
}
public function getRandomPointAround(position: Vec4, radius: Float):Vec4 {
if (!ready) return null;
var randomPoint = recastNavMesh.getRandomPointAround(RecastConversions.recastVec3FromVec4(position), radius);
return RecastConversions.vec4FromRecastVec3(randomPoint);
}
public function initCrowd(maxAgents: Int, maxAgentRadius: Float) {
if (!ready) return;
recastCrowd = new recast.Recast.Crowd(maxAgents, maxAgentRadius, recastNavMesh.getNavMesh());
notifyOnUpdate(crowdUpdate);
}
public function addCrowdAgent(agent: NavCrowd, position: Vec4, radius: Float, height: Float, maxAcceleration: Float, maxSpeed: Float, updateFlags: Int = 7, separationWeight: Float = 1.0, collisionQueryRange: Float = 1.0): Int {
if(!ready) return -1;
if(recastCrowd == null) initCrowd(maxCrowdAgents, maxCrowdAgentRadius);
var crowdAgentParams = new recast.Recast.DtCrowdAgentParams();
crowdAgentParams.radius = radius;
crowdAgentParams.height = height;
crowdAgentParams.maxAcceleration = maxAcceleration;
crowdAgentParams.maxSpeed = maxSpeed;
crowdAgentParams.separationWeight = separationWeight;
crowdAgentParams.collisionQueryRange = collisionQueryRange;
crowdAgentParams.updateFlags = updateFlags;
crowdAgentParams.pathOptimizationRange=0;
var crowdAgentID = recastCrowd.addAgent(RecastConversions.recastVec3FromVec4(position), crowdAgentParams);
crowdAgentMap.set(crowdAgentID, agent);
return crowdAgentID;
}
public function removeCrowdAgent(agentID: Int) {
if(!ready) return;
if(recastCrowd == null) return;
crowdAgentMap.remove(agentID);
recastCrowd.removeAgent(agentID);
}
public function crowdUpdate() {
if(!ready) return;
if(recastCrowd == null) return;
recastCrowd.update(Time.delta);
}
public function crowdGetAgentPosition(agentID: Int):Vec4 {
if(!ready) return null;
if(recastCrowd == null) return null;
var pos = recastCrowd.getAgentPosition(agentID);
var armPos = RecastConversions.vec4FromRecastVec3(pos);
#if hl
pos.delete();
#end
return armPos;
}
public function crowdGetAgentVelocity(agentID: Int):Vec4 {
if(!ready) return null;
if(recastCrowd == null) return null;
var vel = recastCrowd.getAgentVelocity(agentID);
var armVel = RecastConversions.vec4FromRecastVec3(vel);
#if hl
vel.delete();
#end
return armVel;
}
public function crowdAgentGoto(agentID: Int, destination: Vec4) {
if(!ready) return;
if(recastCrowd == null) return;
recastCrowd.agentGoto(agentID, RecastConversions.recastVec3FromVec4(destination));
}
public function crowdAgentTeleport(agentID: Int, destination: Vec4) {
if(!ready) return;
if(recastCrowd == null) return;
recastCrowd.agentTeleport(agentID, RecastConversions.recastVec3FromVec4(destination));
}
public function addCylinderObstacle(navObstacle: NavObstacle, position: Vec4, radius: Float, height: Float): Int {
if(!ready) return -1;
if(!tiledMesh) return -1;
var pos = RecastConversions.recastVec3FromVec4(position);
var obstacle = recastNavMesh.addCylinderObstacle(pos, radius, height);
var obstacleID = tempObstacleCounter;
tempObstacleMap.set(obstacleID, navObstacle);
recastObstacleMap.set(obstacleID, obstacle);
tempObstacleCounter++;
return obstacleID;
}
public function addBoxObstacle(navObstacle: NavObstacle, position: Vec4, dimensions: Vec4, angle: Float): Int {
if(!ready) return -1;
if(!tiledMesh) return -1;
var pos = RecastConversions.recastVec3FromVec4(position);
var dim = RecastConversions.recastVec3FromVec4(dimensions);
var obstacle = recastNavMesh.addBoxObstacle(pos, dim, angle);
var obstacleID = tempObstacleCounter;
tempObstacleMap.set(obstacleID, navObstacle);
recastObstacleMap.set(obstacleID, obstacle);
tempObstacleCounter++;
return obstacleID;
}
public function removeTempObstacle(obstacleID: Int) {
if(!ready) return;
if(!tiledMesh) return;
tempObstacleMap.remove(obstacleID);
var obstacleRef = recastObstacleMap.get(obstacleID);
recastNavMesh.removeObstacle(obstacleRef);
recastObstacleMap.remove(obstacleID);
}
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>):Map<Int, Array<Int>> {
var 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);
}
}
return vertexIndexMap;
}
function getVerticesIndicesFromMesh(object: Object, indexOffset:Int = 0): MeshData {
var vertexIndexMap: Map<Int, Array<Int>>;
var mo = cast(object, MeshObject);
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 null;
vertexMap.push(ind.vertex_map);
}
var vecind = fromU32(geom.indices);
var vertexMapArray = fromU32(vertexMap);
vertexIndexMap = generateVertexIndexMap(vecind, vertexMapArray);
// Parented object - 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 * object.parent.transform.scale.x;
v.y *= object.transform.scale.y * object.parent.transform.scale.y;
v.z *= object.transform.scale.z * object.parent.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 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];
// Y and Z are interchanged in Recast
positionsVector.set(key * 3 , positions[i * 3 ]);
positionsVector.set(key * 3 + 1, positions[i * 3 + 2]);
positionsVector.set(key * 3 + 2, positions[i * 3 + 1]);
}
var vecindVector: haxe.ds.Vector<Int> = new haxe.ds.Vector<Int>(vertexMapArray.length);
var maxIndex = 0;
for (i in 0...vecindVector.length){
var idx = vertexMapArray.get(i);
var idxOffset = idx + indexOffset;
vecindVector.set(i, idxOffset);
if(maxIndex < idxOffset) maxIndex = idxOffset;
}
return { positions: positionsVector, indices: vecindVector, maxIndex: maxIndex};
}
public function drawDebugMesh(helper: DebugDrawHelper) {
var recastDebugNavMesh = recastNavMesh.getDebugNavMesh();
var triangleCount = recastDebugNavMesh.getTriangleCount();
for(index in 0...triangleCount) {
var triangle = recastDebugNavMesh.getTriangle(index);
var point0 = RecastConversions.vec4FromRecastVec3(triangle.getPoint(0));
var point1 = RecastConversions.vec4FromRecastVec3(triangle.getPoint(1));
var point2 = RecastConversions.vec4FromRecastVec3(triangle.getPoint(2));
helper.drawLine(point0, point1, navMeshDebugColor);
helper.drawLine(point1, point2, navMeshDebugColor);
helper.drawLine(point2, point0, navMeshDebugColor);
}
#if hl
recastDebugNavMesh.delete();
#end
}
#end
}
typedef MeshData = {
var positions: haxe.ds.Vector<Float>;
var indices: haxe.ds.Vector<Int>;
var maxIndex:Int;
}

View File

@ -0,0 +1,60 @@
package leenkx.trait;
#if lnx_navigation
import iron.math.Vec4;
import leenkx.trait.navigation.Navigation;
#end
import iron.Trait;
class NavObstacle extends Trait {
#if !lnx_navigation
public function new() { super(); }
#else
@prop
public var radius: Float = 1.0;
@prop
public var height: Float = 1.0;
var obstacleID: Int = -1;
public var obstacleReady (default, null) = false;
var activeNavMesh: NavMesh = null;
var initialPosition: Vec4 = new Vec4();
public function new() {
super();
notifyOnUpdate(addObstacle);
notifyOnRemove(removeObstacle);
}
function addObstacle() {
if(Navigation.active.navMeshes.length < 1) return;
if(! Navigation.active.navMeshes[0].ready) return;
activeNavMesh = Navigation.active.navMeshes[0];
initialPosition = object.transform.world.getLoc();
obstacleID = activeNavMesh.addCylinderObstacle(this, initialPosition, radius, height);
notifyOnUpdate(updateObstaclePosition);
removeUpdate(addObstacle);
obstacleReady = true;
}
function removeObstacle() {
activeNavMesh.removeTempObstacle(obstacleID);
}
function updateObstaclePosition() {
object.transform.loc.setFrom(initialPosition);
object.transform.buildMatrix();
}
#end
}

View File

@ -0,0 +1,812 @@
package leenkx.trait;
import iron.math.Vec4;
import iron.math.Mat4;
import iron.Trait;
import iron.object.MeshObject;
import iron.data.MeshData;
import iron.data.SceneFormat;
#if lnx_bullet
import leenkx.trait.physics.bullet.RigidBody;
import leenkx.trait.physics.PhysicsWorld;
#end
class PhysicsBreak extends Trait {
#if (!lnx_bullet)
public function new() { super(); }
#else
static var physics: PhysicsWorld = null;
static var breaker: ConvexBreaker = null;
var body: RigidBody;
public function new() {
super();
if (breaker == null) breaker = new ConvexBreaker();
notifyOnInit(init);
}
function init() {
if (physics == null) physics = leenkx.trait.physics.PhysicsWorld.active;
body = object.getTrait(RigidBody);
breaker.initBreakableObject(cast object, body.mass, body.friction, new Vec4(), new Vec4(), true);
notifyOnUpdate(update);
}
function update() {
var ar = physics.getContactPairs(body);
if (ar != null) {
var maxImpulse = 0.0;
var impactPoint: Vec4 = null;
var impactNormal: Vec4 = null;
for (p in ar) {
if (maxImpulse < p.impulse) {
maxImpulse = p.impulse;
impactPoint = p.posB;
impactNormal = p.normOnB;
}
}
var fractureImpulse = 4.0;
if (maxImpulse > fractureImpulse) {
var radialIter = 1;
var randIter = 1;
var debris = breaker.subdivideByImpact(cast object, impactPoint, impactNormal, radialIter, randIter);
// var numObjects = debris.length;
for (o in debris) {
var ud = breaker.userDataMap.get(cast o);
var params: RigidBodyParams = {
linearDamping: 0.04,
angularDamping: 0.1,
angularFriction: 0.1,
linearFactorsX: 1.0,
linearFactorsY: 1.0,
linearFactorsZ: 1.0,
angularFactorsX: 1.0,
angularFactorsY: 1.0,
angularFactorsZ: 1.0,
collisionMargin: 0.04,
linearDeactivationThreshold: 0.0,
angularDeactivationThrshold: 0.0,
deactivationTime: 0.0
};
o.addTrait(new RigidBody(Shape.ConvexHull, ud.mass, ud.friction, 0, 1, params));
if (cast(o, MeshObject).data.geom.positions.values.length < 600) {
o.addTrait(new PhysicsBreak());
}
}
object.remove();
}
}
}
#end
}
// Based on work by yomboprime https://github.com/yomboprime
// This class can be used to subdivide a convex geometry object into pieces
class ConvexBreaker {
var minSizeForBreak: Float;
var smallDelta: Float;
var tempLine: Line3;
var tempPlane: Plane;
var tempPlane2: Plane;
var tempCM1: Vec4;
var tempCM2: Vec4;
var tempVec4: Vec4;
var tempVec42: Vec4;
var tempVec43: Vec4;
var tempCutResult: CutResult;
var segments: Array<Bool>;
public var userDataMap: Map<MeshObject, UserData>;
// minSizeForBreak Min size a debris can have to break
// smallDelta Max distance to consider that a point belongs to a plane
public function new(minSizeForBreak = 1.4, smallDelta = 0.0001) {
this.minSizeForBreak = minSizeForBreak;
this.smallDelta = smallDelta;
tempLine = new Line3();
tempPlane = new Plane();
tempPlane2 = new Plane();
tempCM1 = new Vec4();
tempCM2 = new Vec4();
tempVec4 = new Vec4();
tempVec42 = new Vec4();
tempVec43 = new Vec4();
tempCutResult = new CutResult();
segments = new Array<Bool>();
var n = 30 * 30;
for (i in 0...n) segments.push(false);
userDataMap = new Map();
}
public function initBreakableObject(object: MeshObject, mass: Float, friction: Float, velocity: Vec4, angularVelocity: Vec4, breakable: Bool) {
var ar = object.data.geom.positions.values;
var scalePos = object.data.scalePos;
// Create vertices mark
var sc = object.transform.scale;
var vertices = new Array<Vec4>();
for (i in 0...Std.int(ar.length / 4)) {
// Use w component as mark
vertices.push(new Vec4(
ar[i * 4 ] * sc.x * (1 / 32767) * scalePos,
ar[i * 4 + 1] * sc.y * (1 / 32767) * scalePos,
ar[i * 4 + 2] * sc.z * (1 / 32767) * scalePos,
0
));
}
var ind = object.data.geom.indices[0];
var faces = new Array<Face3>();
for (i in 0...Std.int(ind.length / 3)) {
var a = ind[i * 3];
var b = ind[i * 3 + 1];
var c = ind[i * 3 + 2];
// Merge duplis
for (f in faces) {
if (vertices[a].equals(vertices[f.a])) a = f.a;
else if (vertices[a].equals(vertices[f.b])) a = f.b;
else if (vertices[a].equals(vertices[f.c])) a = f.c;
if (vertices[b].equals(vertices[f.a])) b = f.a;
else if (vertices[b].equals(vertices[f.b])) b = f.b;
else if (vertices[b].equals(vertices[f.c])) b = f.c;
if (vertices[c].equals(vertices[f.a])) c = f.a;
else if (vertices[c].equals(vertices[f.b])) c = f.b;
else if (vertices[c].equals(vertices[f.c])) c = f.c;
}
faces.push(new Face3(a, b, c));
}
// Reorder vertices
var verts = new Array<Vec4>();
var map = new Map<Int, Int>();
var i = 0;
function orderVert(fi: Int): Int {
var val = map.get(fi);
if (val == null) {
verts.push(vertices[fi]);
map.set(fi, i);
i++;
return i - 1;
}
else return val;
}
for (f in faces) {
f.a = orderVert(f.a);
f.b = orderVert(f.b);
f.c = orderVert(f.c);
}
var userData = new UserData();
userData.mass = mass;
userData.friction = friction;
userData.velocity = velocity.clone();
userData.angularVelocity = angularVelocity.clone();
userData.breakable = breakable;
userData.vertices = verts;
userData.faces = faces;
userDataMap.set(object, userData);
}
// maxRadialIterations Iterations for radial cuts
// maxRandomIterations Max random iterations for not-radial cuts
public function subdivideByImpact(object: MeshObject, pointOfImpact: Vec4, normal: Vec4, maxRadialIterations: Int, maxRandomIterations: Int): Array<MeshObject> {
var debris: Array<MeshObject> = [];
tempVec4.addvecs(pointOfImpact, normal);
tempPlane.setFromCoplanarPoints(pointOfImpact, object.transform.loc, tempVec4);
var maxTotalIterations = maxRandomIterations + maxRadialIterations;
var scope = this;
function subdivideRadial(subObject: MeshObject, startAngle: Float, endAngle: Float, numIterations: Int) {
if (Math.random() < numIterations * 0.05 || numIterations > maxTotalIterations) {
debris.push(subObject);
return;
}
var angle = Math.PI;
if (numIterations == 0) {
tempPlane2.normal.setFrom(tempPlane.normal);
tempPlane2.constant = tempPlane.constant;
}
else {
if (numIterations <= maxRadialIterations) {
angle = (endAngle - startAngle) * (0.2 + 0.6 * Math.random()) + startAngle;
// Rotate tempPlane2 at impact point around normal axis and the angle
scope.tempVec42.setFrom(object.transform.loc).sub(pointOfImpact).applyAxisAngle(normal, angle).add(pointOfImpact);
tempPlane2.setFromCoplanarPoints(pointOfImpact, scope.tempVec4, scope.tempVec42);
}
else {
angle = ((0.5 * (numIterations & 1)) + 0.2 * (2 - Math.random())) * Math.PI;
// Rotate tempPlane2 at object position around normal axis and the angle
scope.tempVec42.setFrom(pointOfImpact).sub(subObject.transform.loc).applyAxisAngle(normal, angle).add(subObject.transform.loc);
scope.tempVec43.setFrom(normal).add(subObject.transform.loc);
tempPlane2.setFromCoplanarPoints(subObject.transform.loc, scope.tempVec43, scope.tempVec42);
}
}
// Perform the cut
scope.cutByPlane(subObject, tempPlane2, scope.tempCutResult);
var object1 = scope.tempCutResult.object1;
var object2 = scope.tempCutResult.object2;
if (object1 != null) subdivideRadial(object1, startAngle, angle, numIterations + 1);
if (object2 != null) subdivideRadial(object2, angle, endAngle, numIterations + 1);
// Object was subdivided into debris
iron.Scene.active.meshes.remove(subObject);
}
subdivideRadial(object, 0, 2 * Math.PI, 0);
return debris;
}
function transformFreeVector(v: Vec4, m: Mat4): Vec4 {
// Vector interpreted as a free vector
// Mat4 orthogonal matrix (matrix without scale)
var x = v.x, y = v.y, z = v.z;
v.x = m._00 * x + m._10 * y + m._20 * z;
v.y = m._01 * x + m._11 * y + m._21 * z;
v.z = m._02 * x + m._12 * y + m._22 * z;
return v;
}
function transformFreeVectorInverse(v: Vec4, m: Mat4): Vec4 {
// Vector interpreted as a free vector
// Mat4 orthogonal matrix (matrix without scale)
var x = v.x, y = v.y, z = v.z;
v.x = m._00 * x + m._01 * y + m._02 * z;
v.y = m._10 * x + m._11 * y + m._12 * z;
v.z = m._20 * x + m._21 * y + m._22 * z;
return v;
}
function transformTiedVectorInverse(v: Vec4, m: Mat4): Vec4 {
// Vector interpreted as a tied (ordinary) vector
// Mat4 orthogonal matrix (matrix without scale)
var x = v.x, y = v.y, z = v.z;
v.x = m._00 * x + m._01 * y + m._02 * z - m._30;
v.y = m._10 * x + m._11 * y + m._12 * z - m._31;
v.z = m._20 * x + m._21 * y + m._22 * z - m._32;
return v;
};
function transformPlaneToLocalSpace(plane: Plane, m: Mat4, resultPlane: Plane) {
resultPlane.normal.setFrom(plane.normal);
resultPlane.constant = plane.constant;
var v1 = new Vec4();
var referencePoint = transformTiedVectorInverse(plane.coplanarPoint(v1), m);
transformFreeVectorInverse(resultPlane.normal, m);
// Recalculate constant
resultPlane.constant = -referencePoint.dot(resultPlane.normal);
}
// Returns breakable objects, the resulting 2 pieces of the cut
// object2 can be null if the plane doesn't cut the object
// object1 can be null only in case of error
// Returned value is number of pieces, 0 for error
function cutByPlane(object: MeshObject, plane: Plane, output: CutResult): Int {
var userData = userDataMap.get(object);
var points: Array<Vec4> = userData.vertices;
var faces: Array<Face3> = userData.faces;
var numPoints = points.length;
var points1 = [];
var points2 = [];
var delta = smallDelta;
// Reset vertices mark
for (i in 0...numPoints) points[i].w = 0;
// Reset segments mark
var numPointPairs = numPoints * numPoints;
for (i in 0...numPointPairs) this.segments[i] = false;
// Iterate through the faces to mark edges shared by coplanar faces
for (i in 0...faces.length - 1) {
var face1 = faces[i];
for (j in (i + 1)...faces.length) {
var face2 = faces[j];
var coplanar = 1 - face1.normal.dot(face2.normal) < delta;
if (coplanar) {
var a1 = face1.a;
var b1 = face1.b;
var c1 = face1.c;
var a2 = face2.a;
var b2 = face2.b;
var c2 = face2.c;
if (a1 == a2 || a1 == b2 || a1 == c2) {
if (b1 == a2 || b1 == b2 || b1 == c2) {
this.segments[a1 * numPoints + b1] = true;
this.segments[b1 * numPoints + a1] = true;
}
else {
this.segments[c1 * numPoints + a1] = true;
this.segments[a1 * numPoints + c1] = true;
}
}
else if (b1 == a2 || b1 == b2 || b1 == c2) {
this.segments[c1 * numPoints + b1] = true;
this.segments[b1 * numPoints + c1] = true;
}
}
}
}
// Transform the plane to object local space
var localPlane = this.tempPlane;
object.transform.buildMatrix();
transformPlaneToLocalSpace(plane, object.transform.world, localPlane);
// Iterate through the faces adding points to both pieces
for (i in 0...faces.length) {
var face = faces[i];
for (segment in 0...3) {
var i0 = segment == 0 ? face.a : (segment == 1 ? face.b : face.c);
var i1 = segment == 0 ? face.b : (segment == 1 ? face.c : face.a);
var segmentState = this.segments[i0 * numPoints + i1];
// The segment already has been processed in another face
if (segmentState) continue;
// Mark segment as processed (also inverted segment)
this.segments[i0 * numPoints + i1] = true;
this.segments[i1 * numPoints + i0] = true;
var p0 = points[i0];
var p1 = points[i1];
if (p0.w == 0) {
var d = localPlane.distanceToPoint(p0);
// mark: 1 for negative side, 2 for positive side, 3 for coplanar point
if (d > delta) {
p0.w = 2;
points2.push(p0);
}
else if (d < -delta) {
p0.w = 1;
points1.push(p0);
}
else {
p0.w = 3;
points1.push(p0);
var p02 = p0.clone();
p02.w = 3;
points2.push(p02);
}
}
if (p1.w == 0) {
var d = localPlane.distanceToPoint(p1);
// mark: 1 for negative side, 2 for positive side, 3 for coplanar point
if (d > delta) {
p1.w = 2;
points2.push(p1);
}
else if (d < -delta) {
p1.w = 1;
points1.push(p1);
}
else {
p1.w = 3;
points1.push(p1);
var p1_2 = p1.clone();
p1_2.w = 3;
points2.push(p1_2);
}
}
var mark0 = p0.w;
var mark1 = p1.w;
if ((mark0 == 1 && mark1 == 2 ) || ( mark0 == 2 && mark1 == 1)) {
// Intersection of segment with the plane
tempLine.start.setFrom(p0);
tempLine.end.setFrom(p1);
var intersection = localPlane.intersectLine(tempLine);
if (intersection == null) return 0;
intersection.w = 1;
points1.push(intersection);
var intersection_2 = intersection.clone();
intersection_2.w = 2;
points2.push(intersection_2);
}
}
}
// Calculate debris mass (very fast and imprecise):
var newMass = userData.mass * 0.5;
// Calculate debris Center of Mass (again fast and imprecise)
tempCM1.set(0, 0, 0);
var radius1 = 0.0;
var numPoints1 = points1.length;
if (numPoints1 > 0) {
for (i in 0...numPoints1) {
tempCM1.add(points1[i]);
}
tempCM1.mult(1.0 / numPoints1);
for (i in 0...numPoints1) {
var p = points1[i];
p.sub(tempCM1);
radius1 = Math.max(Math.max(radius1, p.x), Math.max(p.y, p.z));
}
tempCM1.add(object.transform.loc);
}
tempCM2.set(0, 0, 0);
var radius2 = 0.0;
var numPoints2 = points2.length;
if (numPoints2 > 0) {
for (i in 0...numPoints2) {
tempCM2.add(points2[i]);
}
tempCM2.mult(1.0 / numPoints2);
for (i in 0...numPoints2) {
var p = points2[i];
p.sub(tempCM2);
radius2 = Math.max(Math.max(radius2, p.x), Math.max(p.y, p.z));
}
tempCM2.add(object.transform.loc);
}
var object1 = null;
var object2 = null;
var numObjects = 0;
if (numPoints1 > 4) {
var data1 = makeMeshData(points1);
object1 = new MeshObject(data1, object.materials);
object1.transform.loc.setFrom(tempCM1);
object1.transform.rot.setFrom(object.transform.rot);
object1.transform.buildMatrix();
initBreakableObject(object1, newMass, userData.friction, userData.velocity, userData.angularVelocity, 2 * radius1 > minSizeForBreak);
numObjects++;
}
if (numPoints2 > 4) {
var data2 = makeMeshData(points2);
object2 = new MeshObject(data2, object.materials);
object2.transform.loc.setFrom(tempCM2);
object2.transform.rot.setFrom(object.transform.rot);
object2.transform.buildMatrix();
initBreakableObject(object2, newMass, userData.friction, userData.velocity, userData.angularVelocity, 2 * radius2 > minSizeForBreak);
numObjects++;
}
output.object1 = object1;
output.object2 = object2;
return numObjects;
}
static var meshIndex = 0;
function makeMeshData(points: Array<Vec4>): MeshData {
while (points.length > 50) points.pop();
var cm = new ConvexHull(points);
var maxdim = 1.0;
var pa = new Array<Float>();
var na = new Array<Float>();
for (p in cm.vertices) {
pa.push(p.x);
pa.push(p.y);
pa.push(p.z);
na.push(0.0);
na.push(0.0);
na.push(0.0);
var ax = Math.abs(p.x);
var ay = Math.abs(p.y);
var az = Math.abs(p.z);
if (ax > maxdim) maxdim = ax;
if (ay > maxdim) maxdim = ay;
if (az > maxdim) maxdim = az;
}
maxdim *= 2;
var ind = new Array<Int>();
function addFlatNormal(normal: Vec4, fi: Int) {
if (na[fi * 3] != 0.0 || na[fi * 3 + 1] != 0.0 || na[fi * 3 + 2] != 0.0) {
pa.push(pa[fi * 3 ]);
pa.push(pa[fi * 3 + 1]);
pa.push(pa[fi * 3 + 2]);
na.push(normal.x);
na.push(normal.y);
na.push(normal.z);
ind.push(Std.int(pa.length / 3 - 1));
}
else {
na[fi * 3 ] = normal.x;
na[fi * 3 + 1] = normal.y;
na[fi * 3 + 2] = normal.z;
ind.push(fi);
}
}
for (f in cm.face3s) {
// Duplicate vertex for flat normals
addFlatNormal(f.normal, f.a);
addFlatNormal(f.normal, f.b);
addFlatNormal(f.normal, f.c);
}
// TODO:
var n = Std.int(pa.length / 3);
var paa = new kha.arrays.Int16Array(n * 4);
var naa = new kha.arrays.Int16Array(n * 2);
var invdim = 1 / maxdim;
for (i in 0...n) {
paa.set(i * 4 , Std.int(pa[i * 3 ] * 32767 * invdim));
paa.set(i * 4 + 1, Std.int(pa[i * 3 + 1] * 32767 * invdim));
paa.set(i * 4 + 2, Std.int(pa[i * 3 + 2] * 32767 * invdim));
naa.set(i * 2 , Std.int(na[i * 3 ] * 32767 * invdim));
naa.set(i * 2 + 1, Std.int(na[i * 3 + 1] * 32767 * invdim));
paa.set(i * 4 + 3, Std.int(na[i * 3 + 2] * 32767 * invdim));
}
var inda = new kha.arrays.Uint32Array(ind.length);
for (i in 0...ind.length) inda.set(i, ind[i]);
var pos: TVertexArray = { attrib: "pos", values: paa, data: "short4norm" };
var nor: TVertexArray = { attrib: "nor", values: naa, data: "short2norm" };
var indices: TIndexArray = { material: 0, values: inda };
var rawmesh: TMeshData = {
name: "TempMesh" + (meshIndex++),
vertex_arrays: [pos, nor],
index_arrays: [indices],
scale_pos: maxdim
};
// Synchronous on Krom
var md = new MeshData(rawmesh, function(d: MeshData) {});
md.geom.calculateAABB();
return md;
}
}
class UserData {
public var mass: Float;
public var friction: Float;
public var velocity: Vec4;
public var angularVelocity: Vec4;
public var breakable: Bool;
public var vertices: Array<Vec4>;
public var faces: Array<Face3>;
public function new() {}
}
class CutResult {
public var object1: MeshObject = null;
public var object2: MeshObject = null;
public function new() {}
}
class Line3 {
public var start: Vec4;
public var end: Vec4;
public function new() {
start = new Vec4();
end = new Vec4();
}
public function delta(result: Vec4): Vec4 {
result.subvecs(end, start);
return result;
}
}
class Plane {
public var normal = new Vec4(1.0, 0.0, 0.0);
public var constant = 0.0;
public function new() {}
public function distanceToPoint(point: Vec4): Float {
return normal.dot(point) + constant;
}
public function setFromCoplanarPoints(a: Vec4, b: Vec4, c: Vec4): Plane {
var v1 = new Vec4();
var v2 = new Vec4();
var normal = v1.subvecs(c, b).cross(v2.subvecs(a, b)).normalize();
set(normal, a);
return this;
}
public function set(normal: Vec4, point: Vec4): Plane {
this.normal.setFrom(normal);
constant = -point.dot(this.normal);
return this;
}
public function coplanarPoint(result: Vec4): Vec4 {
return result.setFrom(normal).mult(-constant);
}
public function intersectLine(line: Line3): Vec4 {
var v1 = new Vec4();
var result = new Vec4();
var direction = line.delta(v1);
var denominator = normal.dot(direction);
if (denominator == 0) {
// line is coplanar, return origin
if (distanceToPoint(line.start) == 0) {
return result.setFrom(line.start);
}
// Unsure if this is the correct method to handle this case.
return null;
}
var t = -(line.start.dot(this.normal) + constant) / denominator;
if (t < 0 || t > 1) return null;
return result.setFrom(direction).mult(t).add(line.start);
}
}
// Based on work by qiao https://github.com/qiao
// This is a convex hull generator using the incremental method
// The complexity is O(n^2) where n is the number of vertices
class ConvexHull {
var faces = [[0, 1, 2], [0, 2, 1]];
public var face3s = new Array<Face3>();
public var vertices = new Array<Vec4>();
public function new(vertices: Array<Vec4>) {
for (i in 3...vertices.length) addPoint(i, vertices);
// Push vertices into array, skipping those inside the hull
// Map from old vertex id to new id
var id = 0;
var newId = new Array<Int>();
for (i in 0...vertices.length) newId.push(-1);
for (i in 0...faces.length) {
var face = faces[i];
for (j in 0...3) {
if (newId[face[j]] == -1) {
newId[face[j]] = id++;
this.vertices.push(vertices[face[j]]);
}
face[j] = newId[face[j]];
}
}
for (i in 0...faces.length) {
face3s.push(new Face3(faces[i][0], faces[i][1], faces[i][2]));
}
computeFaceNormals();
}
var cb = new Vec4();
var ab = new Vec4();
function computeFaceNormals() {
for (f in 0...face3s.length) {
var face = face3s[f];
var va = vertices[face.a];
var vb = vertices[face.b];
var vc = vertices[face.c];
cb.subvecs(vc, vb);
ab.subvecs(va, vb);
cb.cross(ab);
cb.normalize();
face.normal.setFrom(cb);
}
}
function addPoint(vertexId: Int, vertices: Array<Vec4>) {
var vertex = vertices[vertexId].clone();
var mag = vertex.length();
vertex.x += mag * randomOffset();
vertex.y += mag * randomOffset();
vertex.z += mag * randomOffset();
var hole: Array<Array<Int>> = [];
var f = 0;
while (f < faces.length) {
var face = faces[f];
// For each face, if the vertex can see it,
// then we try to add the face's edges into the hole
if (visible(face, vertex, vertices)) {
for (e in 0...3) {
var edge = [face[e], face[(e + 1) % 3]];
var boundary = true;
// Remove duplicated edges
for (h in 0...hole.length) {
if (equalEdge(hole[h], edge)) {
hole[h] = hole[hole.length - 1];
hole.pop();
boundary = false;
break;
}
}
if (boundary) hole.push(edge);
}
faces[f] = faces[faces.length - 1];
faces.pop();
}
else {
f++;
}
}
// Construct the new faces formed by the edges of the hole and the vertex
for (h in 0...hole.length) {
faces.push([hole[h][0], hole[h][1], vertexId]);
}
}
// Whether the face is visible from the vertex
function visible(face: Array<Int>, vertex: Vec4, vertices: Array<Vec4>): Bool {
var va = vertices[face[0]];
var vb = vertices[face[1]];
var vc = vertices[face[2]];
var n = normal(va, vb, vc);
var dist = n.dot(va); // Distance from face to origin
return n.dot(vertex) >= dist;
}
function normal(va: Vec4, vb: Vec4, vc: Vec4): Vec4 {
var cb = new Vec4();
var ab = new Vec4();
cb.subvecs(vc, vb);
ab.subvecs(va, vb);
cb.cross(ab);
cb.normalize();
return cb;
}
function equalEdge(ea: Array<Int>, eb: Array<Int>): Bool {
return ea[0] == eb[1] && ea[1] == eb[0];
}
function randomOffset(): Float {
return (Math.random() - 0.5) * 2 * 1e-6;
}
}
class Face3 {
public var a: Int;
public var b: Int;
public var c: Int;
public var normal: Vec4;
public function new(a: Int, b: Int, c: Int) {
this.a = a;
this.b = b;
this.c = c;
normal = new Vec4();
}
}

View File

@ -0,0 +1,132 @@
package leenkx.trait;
import iron.Trait;
import iron.system.Input;
import iron.math.Vec3;
import iron.math.Vec4;
import iron.math.Mat4;
import iron.math.RayCaster;
import leenkx.trait.physics.RigidBody;
import leenkx.trait.physics.PhysicsWorld;
class PhysicsDrag extends Trait {
#if (!lnx_bullet)
public function new() { super(); }
#else
@prop public var linearLowerLimit = new Vec3(0,0,0);
@prop public var linearUpperLimit = new Vec3(0,0,0);
@prop public var angularLowerLimit = new Vec3(-10,-10,-10);
@prop public var angularUpperLimit = new Vec3(10,10,10);
var pickConstraint: bullet.Bt.Generic6DofConstraint = null;
var pickDist: Float;
var pickedBody: RigidBody = null;
var rayFrom: bullet.Bt.Vector3;
var rayTo: bullet.Bt.Vector3;
static var v = new Vec4();
static var m = Mat4.identity();
static var first = true;
public function new() {
super();
if (first) {
first = false;
notifyOnUpdate(update);
}
}
function update() {
var physics = PhysicsWorld.active;
if (pickedBody != null) pickedBody.activate();
var mouse = Input.getMouse();
if (mouse.started()) {
var b = physics.pickClosest(mouse.x, mouse.y);
if (b != null && b.mass > 0 && !b.body.isKinematicObject() && b.object.getTrait(PhysicsDrag) != null) {
setRays();
pickedBody = b;
m.getInverse(b.object.transform.world);
var hit = physics.hitPointWorld;
v.setFrom(hit);
v.applymat4(m);
var localPivot = new bullet.Bt.Vector3(v.x, v.y, v.z);
var tr = new bullet.Bt.Transform();
tr.setIdentity();
tr.setOrigin(localPivot);
pickConstraint = new bullet.Bt.Generic6DofConstraint(b.body, tr, false);
pickConstraint.setLinearLowerLimit(new bullet.Bt.Vector3(linearLowerLimit.x, linearLowerLimit.y, linearLowerLimit.z));
pickConstraint.setLinearUpperLimit(new bullet.Bt.Vector3(linearUpperLimit.x, linearUpperLimit.y, linearUpperLimit.z));
pickConstraint.setAngularLowerLimit(new bullet.Bt.Vector3(angularLowerLimit.x, angularLowerLimit.y, angularLowerLimit.z));
pickConstraint.setAngularUpperLimit(new bullet.Bt.Vector3(angularUpperLimit.x, angularUpperLimit.y, angularUpperLimit.z));
physics.world.addConstraint(pickConstraint, false);
/*pickConstraint.setParam(4, 0.8, 0);
pickConstraint.setParam(4, 0.8, 1);
pickConstraint.setParam(4, 0.8, 2);
pickConstraint.setParam(4, 0.8, 3);
pickConstraint.setParam(4, 0.8, 4);
pickConstraint.setParam(4, 0.8, 5);
pickConstraint.setParam(1, 0.1, 0);
pickConstraint.setParam(1, 0.1, 1);
pickConstraint.setParam(1, 0.1, 2);
pickConstraint.setParam(1, 0.1, 3);
pickConstraint.setParam(1, 0.1, 4);
pickConstraint.setParam(1, 0.1, 5);*/
pickDist = v.set(hit.x - rayFrom.x(), hit.y - rayFrom.y(), hit.z - rayFrom.z()).length();
Input.occupied = true;
}
}
else if (mouse.released()) {
if (pickConstraint != null) {
physics.world.removeConstraint(pickConstraint);
pickConstraint = null;
pickedBody = null;
}
Input.occupied = false;
}
else if (mouse.down()) {
if (pickConstraint != null) {
setRays();
// Keep it at the same picking distance
var dir = new bullet.Bt.Vector3(rayTo.x() - rayFrom.x(), rayTo.y() - rayFrom.y(), rayTo.z() - rayFrom.z());
dir.normalize();
dir.setX(dir.x() * pickDist);
dir.setY(dir.y() * pickDist);
dir.setZ(dir.z() * pickDist);
var newPivotB = new bullet.Bt.Vector3(rayFrom.x() + dir.x(), rayFrom.y() + dir.y(), rayFrom.z() + dir.z());
#if (js || hl)
pickConstraint.getFrameOffsetA().setOrigin(newPivotB);
#elseif cpp
pickConstraint.setFrameOffsetAOrigin(newPivotB);
#end
}
}
}
static var start = new Vec4();
static var end = new Vec4();
inline function setRays() {
var mouse = Input.getMouse();
var camera = iron.Scene.active.camera;
var v = camera.transform.world.getLoc();
rayFrom = new bullet.Bt.Vector3(v.x, v.y, v.z);
RayCaster.getDirection(start, end, mouse.x, mouse.y, camera);
rayTo = new bullet.Bt.Vector3(end.x, end.y, end.z);
}
#end
}

View File

@ -0,0 +1,73 @@
package leenkx.trait;
import iron.math.Vec4;
import iron.system.Input;
import leenkx.trait.physics.RigidBody;
/**
Simple script to move an object around using the keyboard with WSAD+QE.
Can be used for testing and debuging.
**/
class SimpleMoveObject extends iron.Trait {
@prop
var speed: Float = 0.1;
var keyboard: Keyboard;
var rb: RigidBody;
public function new() {
super();
notifyOnInit(function() {
rb = object.getTrait(RigidBody);
keyboard = Input.getKeyboard();
});
notifyOnUpdate(function() {
var move = new Vec4(0, 0, 0);
if (keyboard.down("d")) {
move.x += speed;
}
if (keyboard.down("a")) {
move.x -= speed;
}
if (keyboard.down("w")) {
move.y += speed;
}
if (keyboard.down("s")) {
move.y -= speed;
}
if (keyboard.down("q")) {
move.z += speed;
}
if (keyboard.down("e")) {
move.z -= speed;
}
if (!move.equals(new Vec4(0, 0, 0))) {
moveObject(move);
}
});
}
function moveObject(vec: Vec4){
if (rb != null) {
#if lnx_physics
rb.setLinearVelocity(0, 0, 0);
rb.setAngularVelocity(0, 0, 0);
rb.transform.translate(vec.x, vec.y, vec.z);
rb.syncTransform();
#end
}
else {
object.transform.translate(vec.x, vec.y, vec.z);
}
}
}

View File

@ -0,0 +1,72 @@
package leenkx.trait;
import iron.math.Vec4;
import iron.system.Input;
import leenkx.trait.physics.RigidBody;
/**
Simple script to rotate an object around using the keyboard with RT(x), FG(y), VB(z).
Can be used for testing and debuging.
**/
class SimpleRotateObject extends iron.Trait {
@prop
var speed: Float = 0.01;
var keyboard: Keyboard;
var rb: RigidBody;
public function new() {
super();
notifyOnInit(function() {
rb = object.getTrait(RigidBody);
keyboard = Input.getKeyboard();
});
notifyOnUpdate(function() {
var rotate = new Vec4(0, 0, 0);
if (keyboard.down("r")) {
rotate.x += 1;
}
if (keyboard.down("t")) {
rotate.x -= 1;
}
if (keyboard.down("f")) {
rotate.y += 1;
}
if (keyboard.down("g")) {
rotate.y -= 1;
}
if (keyboard.down("v")) {
rotate.z += 1;
}
if (keyboard.down("b")) {
rotate.z -= 1;
}
if (!rotate.equals(new Vec4(0, 0, 0))) {
rotateObject(rotate);
}
});
}
function rotateObject(vec: Vec4){
if (rb != null) {
#if lnx_physics
rb.setAngularVelocity(0, 0, 0);
rb.transform.rotate(vec, speed);
rb.syncTransform();
#end
}
else {
object.transform.rotate(vec, speed);
}
}
}

View File

@ -0,0 +1,92 @@
package leenkx.trait;
import iron.math.Vec4;
import iron.system.Input;
import leenkx.trait.physics.RigidBody;
import leenkx.trait.physics.KinematicCharacterController;
/**
Simple script to scale an object around using the keyboard.
All axis can be scaled at once using the Z and X keys.
Individual axis can be scaled using YU(x), HJ(y), NM(z).
Can be used for testing and debuging.
**/
class SimpleScaleObject extends iron.Trait {
@prop
var speed: Float = 0.1;
var keyboard: Keyboard;
var rb: RigidBody;
var character: KinematicCharacterController;
public function new() {
super();
notifyOnInit(function() {
rb = object.getTrait(RigidBody);
character = object.getTrait(KinematicCharacterController);
keyboard = Input.getKeyboard();
});
notifyOnUpdate(function() {
var scale = new Vec4(0, 0, 0);
if (keyboard.down("y")) {
scale.x += speed;
}
if (keyboard.down("u")) {
scale.x -= speed;
}
if (keyboard.down("h")) {
scale.y += speed;
}
if (keyboard.down("j")) {
scale.y -= speed;
}
if (keyboard.down("n")) {
scale.z += speed;
}
if (keyboard.down("m")) {
scale.z -= speed;
}
if (keyboard.down("z")) {
scale.set(speed, speed, speed);
}
if (keyboard.down("x")) {
scale.set(-speed, -speed, -speed);
}
if (!scale.equals(new Vec4(0, 0, 0))) {
scaleObject(scale);
}
});
}
function scaleObject(vec: Vec4){
var s = object.transform.scale;
if (rb != null) {
#if lnx_physics
rb.transform.scale.set(s.x + vec.x, s.y + vec.y, s.z + vec.z);
rb.syncTransform();
#end
}
else if (character != null) {
#if lnx_physics
character.transform.scale.set(s.x + vec.x, s.y + vec.y, s.z + vec.z);
character.syncTransform();
#end
}
else {
object.transform.scale.set(s.x + vec.x, s.y + vec.y, s.z + vec.z);
object.transform.buildMatrix();
}
}
}

View File

@ -0,0 +1,108 @@
package leenkx.trait;
import iron.math.Vec4;
import iron.system.Input;
import iron.object.Object;
import leenkx.trait.physics.PhysicsWorld;
import leenkx.trait.internal.CameraController;
class ThirdPersonController extends CameraController {
#if (!lnx_physics)
public function new() { super(); }
#else
static inline var rotationSpeed = 1.0;
var animObject: String;
var idleAction: String;
var runAction: String;
var currentAction: String;
var arm: Object;
public function new(animObject = "", idle = "idle", run = "run") {
super();
this.animObject = animObject;
this.idleAction = idle;
this.runAction = run;
currentAction = idleAction;
iron.Scene.active.notifyOnInit(init);
}
function findAnimation(o: Object): Object {
if (o.animation != null) return o;
for (c in o.children) {
var co = findAnimation(c);
if (co != null) return co;
}
return null;
}
function init() {
if (animObject == "") arm = findAnimation(object);
else arm = object.getChild(animObject);
PhysicsWorld.active.notifyOnPreUpdate(preUpdate);
notifyOnUpdate(update);
notifyOnRemove(removed);
}
var xVec = Vec4.xAxis();
var zVec = Vec4.zAxis();
function preUpdate() {
if (Input.occupied || !body.ready) return;
var mouse = Input.getMouse();
if (mouse.down()) {
camera.transform.rotate(xVec, mouse.movementY / 250 * rotationSpeed);
transform.rotate(zVec, -mouse.movementX / 250 * rotationSpeed);
camera.buildMatrix();
body.syncTransform();
}
}
function removed() {
PhysicsWorld.active.removePreUpdate(preUpdate);
}
var dir = new Vec4();
function update() {
if (!body.ready) return;
if (jump) body.applyImpulse(new Vec4(0, 0, 20));
// Move
dir.set(0, 0, 0);
if (moveForward) dir.add(transform.look());
if (moveBackward) dir.add(transform.look().mult(-1));
if (moveLeft) dir.add(transform.right().mult(-1));
if (moveRight) dir.add(transform.right());
// Push down
var btvec = body.getLinearVelocity();
body.setLinearVelocity(0.0, 0.0, btvec.z - 1.0);
if (moveForward || moveBackward || moveLeft || moveRight) {
if (currentAction != runAction) {
arm.animation.play(runAction, null, 0.2);
currentAction = runAction;
}
dir.mult(-4 * 0.7);
body.activate();
body.setLinearVelocity(dir.x, dir.y, btvec.z - 1.0);
}
else {
if (currentAction != idleAction) {
arm.animation.play(idleAction, null, 0.2);
currentAction = idleAction;
}
}
// Keep vertical
body.setAngularFactor(0, 0, 0);
camera.buildMatrix();
}
#end
}

View File

@ -0,0 +1,265 @@
package leenkx.trait;
import iron.Trait;
import iron.object.Object;
import iron.object.CameraObject;
import iron.object.Transform;
import iron.system.Time;
import leenkx.trait.physics.PhysicsWorld;
class VehicleBody extends Trait {
#if (!lnx_bullet)
public function new() { super(); }
#else
@prop var wheel0Name: String = "Wheel0";
@prop var wheel1Name: String = "Wheel1";
@prop var wheel2Name: String = "Wheel2";
@prop var wheel3Name: String = "Wheel3";
var physics: PhysicsWorld;
var transform: Transform;
var camera: CameraObject;
var wheels: Array<Object> = [];
var vehicle: bullet.Bt.RaycastVehicle = null;
var carChassis: bullet.Bt.RigidBody;
var chassis_mass = 600.0;
var wheelFriction = 1000;
var suspensionStiffness = 20.0;
var suspensionDamping = 2.3;
var suspensionCompression = 4.4;
var suspensionRestLength = 0.3;
var rollInfluence = 0.1;
var maxEngineForce = 3000.0;
var maxBreakingForce = 500.0;
var engineForce = 0.0;
var breakingForce = 0.0;
var vehicleSteering = 0.0;
public function new() {
super();
iron.Scene.active.notifyOnInit(init);
}
function init() {
physics = leenkx.trait.physics.PhysicsWorld.active;
transform = object.transform;
camera = iron.Scene.active.camera;
for (n in [wheel0Name, wheel1Name, wheel2Name, wheel3Name]) {
wheels.push(iron.Scene.active.root.getChild(n));
}
var wheelDirectionCS0 = new bullet.Bt.Vector3(0, 0, -1);
var wheelAxleCS = new bullet.Bt.Vector3(1, 0, 0);
var chassisShape = new bullet.Bt.BoxShape(new bullet.Bt.Vector3(
transform.dim.x / 2,
transform.dim.y / 2,
transform.dim.z / 2));
var compound = new bullet.Bt.CompoundShape();
var localTrans = new bullet.Bt.Transform();
localTrans.setIdentity();
localTrans.setOrigin(new bullet.Bt.Vector3(0, 0, 1));
compound.addChildShape(localTrans, chassisShape);
carChassis = createRigidBody(chassis_mass, compound);
// Create vehicle
var tuning = new bullet.Bt.VehicleTuning();
var vehicleRayCaster = new bullet.Bt.DefaultVehicleRaycaster(physics.world);
vehicle = new bullet.Bt.RaycastVehicle(tuning, carChassis, vehicleRayCaster);
// Never deactivate the vehicle
carChassis.setActivationState(bullet.Bt.CollisionObjectActivationState.DISABLE_DEACTIVATION);
// Choose coordinate system
var rightIndex = 0;
var upIndex = 2;
var forwardIndex = 1;
vehicle.setCoordinateSystem(rightIndex, upIndex, forwardIndex);
// Add wheels
for (i in 0...wheels.length) {
var vehicleWheel = new VehicleWheel(i, wheels[i].transform, object.transform);
vehicle.addWheel(
vehicleWheel.getConnectionPoint(),
wheelDirectionCS0,
wheelAxleCS,
suspensionRestLength,
vehicleWheel.wheelRadius,
tuning,
vehicleWheel.isFrontWheel);
}
// Setup wheels
for (i in 0...vehicle.getNumWheels()){
var wheel = vehicle.getWheelInfo(i);
wheel.m_suspensionStiffness = suspensionStiffness;
wheel.m_wheelsDampingRelaxation = suspensionDamping;
wheel.m_wheelsDampingCompression = suspensionCompression;
wheel.m_frictionSlip = wheelFriction;
wheel.m_rollInfluence = rollInfluence;
}
physics.world.addAction(vehicle);
notifyOnUpdate(update);
}
function update() {
if (vehicle == null) return;
var keyboard = iron.system.Input.getKeyboard();
var forward = keyboard.down(keyUp);
var backward = keyboard.down(keyDown);
var left = keyboard.down(keyLeft);
var right = keyboard.down(keyRight);
var brake = keyboard.down("space");
if (forward) {
engineForce = maxEngineForce;
}
else if (backward) {
engineForce = -maxEngineForce;
}
else if (brake) {
breakingForce = 100;
}
else {
engineForce = 0;
breakingForce = 20;
}
if (left) {
if (vehicleSteering < 0.3) vehicleSteering += Time.step;
}
else if (right) {
if (vehicleSteering > -0.3) vehicleSteering -= Time.step;
}
else if (vehicleSteering != 0) {
var step = Math.abs(vehicleSteering) < Time.step ? Math.abs(vehicleSteering) : Time.step;
if (vehicleSteering > 0) vehicleSteering -= step;
else vehicleSteering += step;
}
vehicle.applyEngineForce(engineForce, 2);
vehicle.setBrake(breakingForce, 2);
vehicle.applyEngineForce(engineForce, 3);
vehicle.setBrake(breakingForce, 3);
vehicle.setSteeringValue(vehicleSteering, 0);
vehicle.setSteeringValue(vehicleSteering, 1);
for (i in 0...vehicle.getNumWheels()) {
// Synchronize the wheels with the chassis worldtransform
vehicle.updateWheelTransform(i, true);
// Update wheels transforms
var trans = vehicle.getWheelTransformWS(i);
var p = trans.getOrigin();
var q = trans.getRotation();
wheels[i].transform.localOnly = true;
wheels[i].transform.loc.set(p.x(), p.y(), p.z());
wheels[i].transform.rot.set(q.x(), q.y(), q.z(), q.w());
wheels[i].transform.dirty = true;
}
var trans = carChassis.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());
var up = transform.world.up();
transform.loc.add(up);
transform.dirty = true;
// TODO: fix parent matrix update
if (camera.parent != null) camera.parent.transform.buildMatrix();
camera.buildMatrix();
}
function createRigidBody(mass: Float, shape: bullet.Bt.CompoundShape): bullet.Bt.RigidBody {
var localInertia = new bullet.Bt.Vector3(0, 0, 0);
shape.calculateLocalInertia(mass, localInertia);
var centerOfMassOffset = new bullet.Bt.Transform();
centerOfMassOffset.setIdentity();
var startTransform = new bullet.Bt.Transform();
startTransform.setIdentity();
startTransform.setOrigin(new bullet.Bt.Vector3(
transform.loc.x,
transform.loc.y,
transform.loc.z));
startTransform.setRotation(new bullet.Bt.Quaternion(
transform.rot.x,
transform.rot.y,
transform.rot.z,
transform.rot.w));
var myMotionState = new bullet.Bt.DefaultMotionState(startTransform, centerOfMassOffset);
var cInfo = new bullet.Bt.RigidBodyConstructionInfo(mass, myMotionState, shape, localInertia);
var body = new bullet.Bt.RigidBody(cInfo);
body.setLinearVelocity(new bullet.Bt.Vector3(0, 0, 0));
body.setAngularVelocity(new bullet.Bt.Vector3(0, 0, 0));
physics.world.addRigidBody(body);
return body;
}
#if lnx_azerty
static inline var keyUp = "z";
static inline var keyDown = "s";
static inline var keyLeft = "q";
static inline var keyRight = "d";
static inline var keyStrafeUp = "e";
static inline var keyStrafeDown = "a";
#else
static inline var keyUp = "w";
static inline var keyDown = "s";
static inline var keyLeft = "a";
static inline var keyRight = "d";
static inline var keyStrafeUp = "e";
static inline var keyStrafeDown = "q";
#end
#end
}
class VehicleWheel {
#if (!lnx_bullet)
public function new() {}
#else
public var isFrontWheel: Bool;
public var wheelRadius: Float;
public var wheelWidth: Float;
var locX: Float;
var locY: Float;
var locZ: Float;
public function new(id: Int, transform: Transform, vehicleTransform: Transform) {
wheelRadius = transform.dim.z / 2;
wheelWidth = transform.dim.x > transform.dim.y ? transform.dim.y : transform.dim.x;
locX = transform.loc.x;
locY = transform.loc.y;
locZ = vehicleTransform.dim.z / 2 + transform.loc.z;
}
public function getConnectionPoint(): bullet.Bt.Vector3 {
return new bullet.Bt.Vector3(locX, locY, locZ);
}
#end
}

View File

@ -0,0 +1,139 @@
package leenkx.trait;
import iron.Trait;
import iron.system.Input;
import iron.math.Vec2;
@:access(iron.system.Input)
@:access(iron.system.Gamepad)
class VirtualGamepad extends Trait {
var gamepad: Gamepad;
var leftPadX = 0;
var leftPadY = 0;
var rightPadX = 0;
var rightPadY = 0;
var leftStickX = 0;
var leftStickXLast = 0;
var leftStickY = 0;
var leftStickYLast = 0;
var rightStickX = 0;
var rightStickXLast = 0;
var rightStickY = 0;
var rightStickYLast = 0;
var leftLocked = false;
var rightLocked = false;
@prop
public var radius = 100; // Radius
@prop
public var offset = 40; // Offset
public function new() {
super();
notifyOnInit(function() {
gamepad = new Gamepad(0, true);
Input.gamepads.push(gamepad);
notifyOnUpdate(update);
notifyOnRender2D(render2D);
});
}
function update() {
var r = radius;
var o = offset;
leftPadX = r + o;
rightPadX = iron.App.w() - r - o;
leftPadY = rightPadY = iron.App.h() - r - o;
var mouse = Input.getMouse();
if (mouse.started() && Vec2.distancef(mouse.x, mouse.y, leftPadX, leftPadY) <= r) {
leftLocked = true;
}
else if (mouse.released()) {
leftLocked = false;
}
if (mouse.started() && Vec2.distancef(mouse.x, mouse.y, rightPadX, rightPadY) <= r) {
rightLocked = true;
}
else if (mouse.released()) {
rightLocked = false;
}
if (leftLocked) {
leftStickX = Std.int(mouse.x - leftPadX);
leftStickY = Std.int(mouse.y - leftPadY);
var l = Math.sqrt(leftStickX * leftStickX + leftStickY * leftStickY);
if (l > r) {
var x = r * (leftStickX / Math.sqrt(leftStickX * leftStickX + leftStickY * leftStickY));
var y = r * (leftStickY / Math.sqrt(leftStickX * leftStickX + leftStickY * leftStickY));
leftStickX = Std.int(x);
leftStickY = Std.int(y);
}
}
else {
leftStickX = 0;
leftStickY = 0;
}
if (rightLocked) {
rightStickX = Std.int(mouse.x - rightPadX);
rightStickY = Std.int(mouse.y - rightPadY);
var l = Math.sqrt(rightStickX * rightStickX + rightStickY * rightStickY);
if (l > r) {
var x = r * (rightStickX / Math.sqrt(rightStickX * rightStickX + rightStickY * rightStickY));
var y = r * (rightStickY / Math.sqrt(rightStickX * rightStickX + rightStickY * rightStickY));
rightStickX = Std.int(x);
rightStickY = Std.int(y);
}
}
else {
rightStickX = 0;
rightStickY = 0;
}
if (leftStickX != leftStickXLast) {
gamepad.axisListener(0, leftStickX / r);
}
if (leftStickY != leftStickYLast) {
gamepad.axisListener(1, leftStickY / r);
}
if (rightStickX != rightStickXLast) {
gamepad.axisListener(2, rightStickX / r);
}
if (rightStickY != rightStickYLast) {
gamepad.axisListener(3, rightStickY / r);
}
leftStickXLast = leftStickX;
leftStickYLast = leftStickY;
rightStickXLast = rightStickX;
rightStickYLast = rightStickY;
}
function render2D(g: kha.graphics2.Graphics) {
var r = radius;
g.color = 0xffaaaaaa;
zui.GraphicsExtension.fillCircle(g, leftPadX, leftPadY, r);
zui.GraphicsExtension.fillCircle(g, rightPadX, rightPadY, r);
var r2 = Std.int(r / 2.2);
g.color = 0xffffff44;
zui.GraphicsExtension.fillCircle(g, leftPadX + leftStickX, leftPadY + leftStickY, r2);
zui.GraphicsExtension.fillCircle(g, rightPadX + rightStickX, rightPadY + rightStickY, r2);
g.color = 0xffffffff;
}
}

View File

@ -0,0 +1,150 @@
package leenkx.trait;
import iron.Trait;
import iron.system.Input;
import iron.system.Time;
import iron.object.CameraObject;
import iron.math.Vec4;
class WalkNavigation extends Trait {
public static var enabled = true;
var speed = 5.0;
var dir = new Vec4();
var xvec = new Vec4();
var yvec = new Vec4();
var ease = 1.0;
var camera: CameraObject;
var keyboard: Keyboard;
var gamepad: Gamepad;
var mouse: Mouse;
public function new() {
super();
notifyOnInit(init);
}
#if lnx_debug @:keep #end
function init() {
keyboard = Input.getKeyboard();
gamepad = Input.getGamepad();
mouse = Input.getMouse();
try {
camera = cast(object, CameraObject);
}
catch (msg: String) {
trace("Error occurred: " + msg + "\nWalkNavigation trait should be used with a camera object.");
}
if (camera != null){
notifyOnUpdate(update);
}
}
function update() {
if (!enabled || Input.occupied) return;
var moveForward = keyboard.down(keyUp) || keyboard.down("up");
var moveBackward = keyboard.down(keyDown) || keyboard.down("down");
var strafeLeft = keyboard.down(keyLeft) || keyboard.down("left");
var strafeRight = keyboard.down(keyRight) || keyboard.down("right");
var strafeUp = keyboard.down(keyStrafeUp);
var strafeDown = keyboard.down(keyStrafeDown);
var fast = keyboard.down("shift") ? 2.0 : (keyboard.down("alt") ? 0.5 : 1.0);
if (gamepad != null) {
var leftStickY = Math.abs(gamepad.leftStick.y) > 0.05;
var leftStickX = Math.abs(gamepad.leftStick.x) > 0.05;
var r1 = gamepad.down("r1") > 0.0;
var l1 = gamepad.down("l1") > 0.0;
var rightStickX = Math.abs(gamepad.rightStick.x) > 0.1;
var rightStickY = Math.abs(gamepad.rightStick.y) > 0.1;
if (leftStickY || leftStickX || r1 || l1 || rightStickX || rightStickY) {
dir.set(0, 0, 0);
if (leftStickY) {
yvec.setFrom(camera.look());
yvec.mult(gamepad.leftStick.y);
dir.add(yvec);
}
if (leftStickX) {
xvec.setFrom(camera.right());
xvec.mult(gamepad.leftStick.x);
dir.add(xvec);
}
if (r1) dir.addf(0, 0, 1);
if (l1) dir.addf(0, 0, -1);
var d = Time.delta * speed * fast;
camera.transform.move(dir, d);
if (rightStickX) {
camera.transform.rotate(Vec4.zAxis(), -gamepad.rightStick.x / 15.0);
}
if (rightStickY) {
camera.transform.rotate(camera.right(), gamepad.rightStick.y / 15.0);
}
}
}
if (moveForward || moveBackward || strafeRight || strafeLeft || strafeUp || strafeDown) {
ease += Time.delta * 15;
if (ease > 1.0) ease = 1.0;
dir.set(0, 0, 0);
if (moveForward) dir.addf(camera.look().x, camera.look().y, camera.look().z);
if (moveBackward) dir.addf(-camera.look().x, -camera.look().y, -camera.look().z);
if (strafeRight) dir.addf(camera.right().x, camera.right().y, camera.right().z);
if (strafeLeft) dir.addf(-camera.right().x, -camera.right().y, -camera.right().z);
#if lnx_yaxisup
if (strafeUp) dir.addf(0, 1, 0);
if (strafeDown) dir.addf(0, -1, 0);
#else
if (strafeUp) dir.addf(0, 0, 1);
if (strafeDown) dir.addf(0, 0, -1);
#end
}
else {
ease -= Time.delta * 20.0 * ease;
if (ease < 0.0) ease = 0.0;
}
if (mouse.wheelDelta < 0) {
speed *= 1.1;
} else if (mouse.wheelDelta > 0) {
speed *= 0.9;
if (speed < 0.5) speed = 0.5;
}
var d = Time.delta * speed * fast * ease;
if (d > 0.0) camera.transform.move(dir, d);
if (mouse.down()) {
#if lnx_yaxisup
camera.transform.rotate(Vec4.yAxis(), -mouse.movementX / 200);
#else
camera.transform.rotate(Vec4.zAxis(), -mouse.movementX / 200);
#end
camera.transform.rotate(camera.right(), -mouse.movementY / 200);
}
}
#if lnx_azerty
static inline var keyUp = "z";
static inline var keyDown = "s";
static inline var keyLeft = "q";
static inline var keyRight = "d";
static inline var keyStrafeUp = "e";
static inline var keyStrafeDown = "a";
#else
static inline var keyUp = "w";
static inline var keyDown = "s";
static inline var keyLeft = "a";
static inline var keyRight = "d";
static inline var keyStrafeUp = "e";
static inline var keyStrafeDown = "q";
#end
}

View File

@ -0,0 +1,19 @@
package leenkx.trait.internal;
#if js
@:expose("iron")
class Bridge {
public static var App = iron.App;
public static var Scene = iron.Scene;
public static var Time = iron.system.Time;
public static var Input = iron.system.Input;
public static var Object = iron.object.Object;
public static var Data = iron.data.Data;
public static var Vec4 = iron.math.Vec4;
public static var Quat = iron.math.Quat;
public static function log(s: String) { trace(s); };
}
#end

View File

@ -0,0 +1,60 @@
package leenkx.trait.internal;
import iron.Trait;
import iron.system.Input;
import iron.object.Transform;
import iron.object.CameraObject;
import leenkx.trait.physics.RigidBody;
class CameraController extends Trait {
#if (!lnx_physics)
public function new() { super(); }
#else
var transform: Transform;
var body: RigidBody;
var camera: CameraObject;
var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;
var jump = false;
public function new() {
super();
iron.Scene.active.notifyOnInit(function() {
transform = object.transform;
body = object.getTrait(RigidBody);
camera = cast(object.getChildOfType(CameraObject), CameraObject);
});
notifyOnUpdate(function() {
var keyboard = Input.getKeyboard();
moveForward = keyboard.down(keyUp);
moveRight = keyboard.down(keyRight);
moveBackward = keyboard.down(keyDown);
moveLeft = keyboard.down(keyLeft);
jump = keyboard.started("space");
});
}
#if lnx_azerty
static inline var keyUp = "z";
static inline var keyDown = "s";
static inline var keyLeft = "q";
static inline var keyRight = "d";
static inline var keyStrafeUp = "e";
static inline var keyStrafeDown = "a";
#else
static inline var keyUp = "w";
static inline var keyDown = "s";
static inline var keyLeft = "a";
static inline var keyRight = "d";
static inline var keyStrafeUp = "e";
static inline var keyStrafeDown = "q";
#end
#end
}

View File

@ -0,0 +1,217 @@
package leenkx.trait.internal;
import iron.Trait;
#if lnx_ui
import iron.Scene;
import zui.Zui;
import leenkx.ui.Canvas;
#end
class CanvasScript extends Trait {
public var cnvName: String;
#if lnx_ui
var cui: Zui;
var canvas: TCanvas = null;
public var ready(get, null): Bool;
function get_ready(): Bool { return canvas != null; }
var onReadyFuncs: Array<Void->Void> = null;
/**
Create new CanvasScript from canvas
@param canvasName Name of the canvas
@param font font file (Optional)
**/
public function new(canvasName: String, font: String = Canvas.defaultFontName) {
super();
cnvName = canvasName;
iron.data.Data.getBlob(canvasName + ".json", function(blob: kha.Blob) {
iron.data.Data.getBlob("_themes.json", function(tBlob: kha.Blob) {
if (@:privateAccess tBlob.get_length() != 0) {
Canvas.themes = haxe.Json.parse(tBlob.toString());
}
else {
trace("\"_themes.json\" is empty! Using default theme instead.");
}
if (Canvas.themes.length == 0) {
Canvas.themes.push(leenkx.ui.Themes.light);
}
iron.data.Data.getFont(font, function(defaultFont: kha.Font) {
var c: TCanvas = Canvas.parseCanvasFromBlob(blob);
if (c.theme == null) c.theme = Canvas.themes[0].NAME;
cui = new Zui({font: defaultFont, theme: Canvas.getTheme(c.theme)});
if (c.assets == null || c.assets.length == 0) canvas = c;
else { // Load canvas assets
var loaded = 0;
for (asset in c.assets) {
var file = asset.name;
if (Canvas.isFontAsset(file)) {
iron.data.Data.getFont(file, function(f: kha.Font) {
Canvas.assetMap.set(asset.id, f);
if (++loaded >= c.assets.length) canvas = c;
});
} else {
iron.data.Data.getImage(file, function(image: kha.Image) {
Canvas.assetMap.set(asset.id, image);
if (++loaded >= c.assets.length) canvas = c;
});
}
}
}
});
});
});
notifyOnRender2D(function(g: kha.graphics2.Graphics) {
if (canvas == null) return;
setCanvasDimensions(kha.System.windowWidth(), kha.System.windowHeight());
var events = Canvas.draw(cui, canvas, g);
for (e in events) {
var all = leenkx.system.Event.get(e);
if (all != null) for (entry in all) entry.onEvent();
}
if (onReadyFuncs != null) {
for (f in onReadyFuncs) {
f();
}
onReadyFuncs.resize(0);
}
});
}
/**
Run the given callback function `f` when the canvas is loaded and ready.
@see https://github.com/leenkx3d/leenkx/wiki/traits#canvas-trait-events
**/
public function notifyOnReady(f: Void->Void) {
if (onReadyFuncs == null) onReadyFuncs = [];
onReadyFuncs.push(f);
}
/**
Returns an element of the canvas.
@param name The name of the element
@return TElement
**/
public function getElement(name: String): TElement {
for (e in canvas.elements) if (e.name == name) return e;
return null;
}
/**
Returns an array of the elements of the canvas.
@return Array<TElement>
**/
public function getElements(): Array<TElement> {
return canvas.elements;
}
/**
Returns the canvas object of this trait.
@return TCanvas
**/
public function getCanvas(): Null<TCanvas> {
return canvas;
}
/**
Set the UI scale factor.
**/
public inline function setUiScale(factor: Float) {
cui.setScale(factor);
}
/**
Get the UI scale factor.
**/
public inline function getUiScale(): Float {
return cui.ops.scaleFactor;
}
@:deprecated("Please use setCanvasVisible() instead")
public inline function setCanvasVisibility(visible: Bool) {
setCanvasVisible(visible);
}
/**
Set whether the active canvas is visible.
Note that elements of invisible canvases are not rendered and computed,
so it is not possible to interact with those elements on the screen.
**/
public inline function setCanvasVisible(visible: Bool) {
canvas.visible = visible;
}
/**
Get whether the active canvas is visible.
**/
public inline function getCanvasVisible(): Bool {
return canvas.visible;
}
/**
Set dimensions of canvas
@param x Width
@param y Height
**/
public function setCanvasDimensions(x: Int, y: Int){
canvas.width = x;
canvas.height = y;
}
/**
Set font size of the canvas
@param fontSize Size of font to be setted
**/
public function setCanvasFontSize(fontSize: Int) {
cui.t.FONT_SIZE = fontSize;
cui.setScale(cui.ops.scaleFactor);
}
public function getCanvasFontSize(): Int {
return cui.t.FONT_SIZE;
}
public function setCanvasInputTextFocus(e: Handle, focus: Bool) {
if (focus == true){
@:privateAccess cui.startTextEdit(e);
} else {
@:privateAccess cui.deselectText();
}
}
// Contains data
@:access(leenkx.ui.Canvas)
@:access(zui.Handle)
public function getHandle(name: String): Handle {
// Consider this a temporary solution
return Canvas.h.children[getElement(name).id];
}
public static function getActiveCanvas(): CanvasScript {
var activeCanvas = Scene.active.getTrait(CanvasScript);
if (activeCanvas == null) activeCanvas = Scene.active.camera.getTrait(CanvasScript);
assert(Error, activeCanvas != null, "Could not find a canvas trait on the active scene or camera");
return activeCanvas;
}
#else
public function new(canvasName: String) { super(); cnvName = canvasName; }
#end
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,215 @@
package leenkx.trait.internal;
#if lnx_debug
import kha.graphics4.PipelineState;
import kha.graphics4.VertexStructure;
import kha.graphics4.VertexBuffer;
import kha.graphics4.IndexBuffer;
import kha.graphics4.VertexData;
import kha.graphics4.Usage;
import kha.graphics4.ConstantLocation;
import kha.graphics4.CompareMode;
import kha.graphics4.CullMode;
import iron.math.Vec4;
import iron.math.Mat4;
using leenkx.object.TransformExtension;
class DebugDraw {
static var inst: DebugDraw = null;
public var color: kha.Color = 0xffff0000;
public var strength = 0.02;
var vertexBuffer: VertexBuffer;
var indexBuffer: IndexBuffer;
var pipeline: PipelineState;
var vp: Mat4;
var vpID: ConstantLocation;
var vbData: kha.arrays.Float32Array;
var ibData: kha.arrays.Uint32Array;
static inline var maxLines = 300;
static inline var maxVertices = maxLines * 4;
static inline var maxIndices = maxLines * 6;
var lines = 0;
function new() {
inst = this;
var structure = new VertexStructure();
structure.add("pos", VertexData.Float3);
structure.add("col", VertexData.Float3);
pipeline = new PipelineState();
pipeline.inputLayout = [structure];
#if lnx_deferred
pipeline.fragmentShader = kha.Shaders.line_deferred_frag;
#else
pipeline.fragmentShader = kha.Shaders.line_frag;
#end
pipeline.vertexShader = kha.Shaders.line_vert;
pipeline.depthWrite = true;
pipeline.depthMode = CompareMode.Less;
pipeline.cullMode = CullMode.None;
pipeline.compile();
vpID = pipeline.getConstantLocation("ViewProjection");
vp = Mat4.identity();
vertexBuffer = new VertexBuffer(maxVertices, structure, Usage.DynamicUsage);
indexBuffer = new IndexBuffer(maxIndices, Usage.DynamicUsage);
}
static var g: kha.graphics4.Graphics;
public static function notifyOnRender(f: DebugDraw->Void) {
if (inst == null) inst = new DebugDraw();
iron.RenderPath.notifyOnContext("mesh", function(g4: kha.graphics4.Graphics, i: Int, len: Int) {
g = g4;
if (i == 0) inst.begin();
f(inst);
if (i == len - 1) inst.end();
});
}
static var objPosition: Vec4;
static var vx = new Vec4();
static var vy = new Vec4();
static var vz = new Vec4();
public function bounds(transform: iron.object.Transform) {
objPosition = transform.getWorldPosition();
var dx = transform.dim.x / 2;
var dy = transform.dim.y / 2;
var dz = transform.dim.z / 2;
var up = transform.world.up();
var look = transform.world.look();
var right = transform.world.right();
up.normalize();
look.normalize();
right.normalize();
vx.setFrom(right);
vx.mult(dx);
vy.setFrom(look);
vy.mult(dy);
vz.setFrom(up);
vz.mult(dz);
lineb(-1, -1, -1, 1, -1, -1);
lineb(-1, 1, -1, 1, 1, -1);
lineb(-1, -1, 1, 1, -1, 1);
lineb(-1, 1, 1, 1, 1, 1);
lineb(-1, -1, -1, -1, 1, -1);
lineb(-1, -1, 1, -1, 1, 1);
lineb( 1, -1, -1, 1, 1, -1);
lineb( 1, -1, 1, 1, 1, 1);
lineb(-1, -1, -1, -1, -1, 1);
lineb(-1, 1, -1, -1, 1, 1);
lineb( 1, -1, -1, 1, -1, 1);
lineb( 1, 1, -1, 1, 1, 1);
}
static var v1 = new Vec4();
static var v2 = new Vec4();
static var t = new Vec4();
function lineb(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {
v1.setFrom(objPosition);
t.setFrom(vx); t.mult(a); v1.add(t);
t.setFrom(vy); t.mult(b); v1.add(t);
t.setFrom(vz); t.mult(c); v1.add(t);
v2.setFrom(objPosition);
t.setFrom(vx); t.mult(d); v2.add(t);
t.setFrom(vy); t.mult(e); v2.add(t);
t.setFrom(vz); t.mult(f); v2.add(t);
linev(v1, v2);
}
public inline function linev(v1: Vec4, v2: Vec4) {
line(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z);
}
static var midPoint = new Vec4();
static var midLine = new Vec4();
static var corner1 = new Vec4();
static var corner2 = new Vec4();
static var corner3 = new Vec4();
static var corner4 = new Vec4();
static var cameraLook = new Vec4();
public function line(x1: Float, y1: Float, z1: Float, x2: Float, y2: Float, z2: Float) {
if (lines >= maxLines) { end(); begin(); }
midPoint.set(x1 + x2, y1 + y2, z1 + z2);
midPoint.mult(0.5);
midLine.set(x1, y1, z1);
midLine.sub(midPoint);
var camera = iron.Scene.active.camera;
cameraLook = camera.transform.getWorldPosition();
cameraLook.sub(midPoint);
var lineWidth = cameraLook.cross(midLine);
lineWidth.normalize();
lineWidth.mult(strength);
corner1.set(x1, y1, z1).add(lineWidth);
corner2.set(x1, y1, z1).sub(lineWidth);
corner3.set(x2, y2, z2).sub(lineWidth);
corner4.set(x2, y2, z2).add(lineWidth);
var i = lines * 24; // 4 * 6 (structure len)
addVbData(i, [corner1.x, corner1.y, corner1.z, color.R, color.G, color.B]);
i += 6;
addVbData(i, [corner2.x, corner2.y, corner2.z, color.R, color.G, color.B]);
i += 6;
addVbData(i, [corner3.x, corner3.y, corner3.z, color.R, color.G, color.B]);
i += 6;
addVbData(i, [corner4.x, corner4.y, corner4.z, color.R, color.G, color.B]);
i = lines * 6;
ibData[i ] = lines * 4;
ibData[i + 1] = lines * 4 + 1;
ibData[i + 2] = lines * 4 + 2;
ibData[i + 3] = lines * 4 + 2;
ibData[i + 4] = lines * 4 + 3;
ibData[i + 5] = lines * 4;
lines++;
}
function begin() {
lines = 0;
vbData = vertexBuffer.lock();
ibData = indexBuffer.lock();
}
function end() {
vertexBuffer.unlock();
indexBuffer.unlock();
g.setVertexBuffer(vertexBuffer);
g.setIndexBuffer(indexBuffer);
g.setPipeline(pipeline);
var camera = iron.Scene.active.camera;
vp.setFrom(camera.V);
vp.multmat(camera.P);
g.setMatrix(vpID, vp.self);
g.drawIndexedVertices(0, lines * 6);
}
inline function addVbData(i: Int, data: Array<Float>) {
for (offset in 0...6) {
vbData.set(i + offset, data[offset]);
}
}
}
#end

View File

@ -0,0 +1,195 @@
package leenkx.trait.internal;
import leenkx.logicnode.LogicNode;
import leenkx.logicnode.LogicTree;
#if lnx_patch @:expose("LivePatch") #end
@:access(leenkx.logicnode.LogicNode)
@:access(leenkx.logicnode.LogicNodeLink)
class LivePatch extends iron.Trait {
#if !lnx_patch
public function new() { super(); }
#else
static var patchId = 0;
public function new() {
super();
notifyOnUpdate(update);
}
function update() {
kha.Assets.loadBlobFromPath("krom.patch", function(b: kha.Blob) {
if (b.length == 0) return;
var lines = b.toString().split("\n");
var id = Std.parseInt(lines[0]);
if (id > patchId) {
patchId = id;
js.Lib.eval(lines[1]);
}
});
}
public static function patchCreateNodeLink(treeName: String, fromNodeName: String, toNodeName: String, fromIndex: Int, toIndex: Int) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var fromNode = tree.nodes[fromNodeName];
var toNode = tree.nodes[toNodeName];
if (fromNode == null || toNode == null) return;
LogicNode.addLink(fromNode, toNode, fromIndex, toIndex);
}
}
public static function patchSetNodeLinks(treeName: String, nodeName: String, inputDatas: Array<Dynamic>, outputDatas: Array<Array<Dynamic>>) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
node.clearInputs();
node.clearOutputs();
for (inputData in inputDatas) {
var fromNode: LogicNode;
var fromIndex: Int;
if (inputData.isLinked) {
fromNode = tree.nodes[inputData.fromNode];
if (fromNode == null) continue;
fromIndex = inputData.fromIndex;
}
else {
fromNode = LogicNode.createSocketDefaultNode(node.tree, inputData.socketType, inputData.socketValue);
fromIndex = 0;
}
LogicNode.addLink(fromNode, node, fromIndex, inputData.toIndex);
}
for (outputData in outputDatas) {
for (linkData in outputData) {
var toNode: LogicNode;
var toIndex: Int;
if (linkData.isLinked) {
toNode = tree.nodes[linkData.toNode];
if (toNode == null) continue;
toIndex = linkData.toIndex;
}
else {
toNode = LogicNode.createSocketDefaultNode(node.tree, linkData.socketType, linkData.socketValue);
toIndex = 0;
}
LogicNode.addLink(node, toNode, linkData.fromIndex, toIndex);
}
}
}
}
public static function patchUpdateNodeProp(treeName: String, nodeName: String, propName: String, value: Dynamic) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
Reflect.setField(node, propName, value);
}
}
public static function patchUpdateNodeInputVal(treeName: String, nodeName: String, socketIndex: Int, value: Dynamic) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
node.inputs[socketIndex].set(value);
}
}
public static function patchNodeDelete(treeName: String, nodeName: String) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
node.clearOutputs();
node.clearInputs();
tree.nodes.remove(nodeName);
}
}
public static function patchNodeCreate(treeName: String, nodeName: String, nodeType: String, propDatas: Array<Array<Dynamic>>, inputDatas: Array<Array<Dynamic>>, outputDatas: Array<Array<Dynamic>>) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
// No further constructor parameters required here, all variable nodes
// use optional further parameters and all values are set later in this
// function.
var newNode: LogicNode = Type.createInstance(Type.resolveClass(nodeType), [tree]);
newNode.name = nodeName;
tree.nodes[nodeName] = newNode;
for (propData in propDatas) {
Reflect.setField(newNode, propData[0], propData[1]);
}
var i = 0;
for (inputData in inputDatas) {
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
}
i = 0;
for (outputData in outputDatas) {
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
}
}
}
public static function patchNodeCopy(treeName: String, nodeName: String, newNodeName: String, copyProps: Array<String>, inputDatas: Array<Array<Dynamic>>, outputDatas: Array<Array<Dynamic>>) {
if (!LogicTree.nodeTrees.exists(treeName)) return;
var trees = LogicTree.nodeTrees[treeName];
for (tree in trees) {
var node = tree.nodes[nodeName];
if (node == null) return;
// No further constructor parameters required here, all variable nodes
// use optional further parameters and all values are set later in this
// function.
var newNode: LogicNode = Type.createInstance(Type.getClass(node), [tree]);
newNode.name = newNodeName;
tree.nodes[newNodeName] = newNode;
for (propName in copyProps) {
Reflect.setField(newNode, propName, Reflect.field(node, propName));
}
var i = 0;
for (inputData in inputDatas) {
LogicNode.addLink(LogicNode.createSocketDefaultNode(newNode.tree, inputData[0], inputData[1]), newNode, 0, i++);
}
i = 0;
for (outputData in outputDatas) {
LogicNode.addLink(newNode, LogicNode.createSocketDefaultNode(newNode.tree, outputData[0], outputData[1]), i++, 0);
}
}
}
#end
}

View File

@ -0,0 +1,11 @@
package leenkx.trait.internal;
// To create a custom loading screen copy this file to blend_root/Sources/arm/LoadingScreen.hx
class LoadingScreen {
public static function render(g: kha.graphics2.Graphics, assetsLoaded: Int, assetsTotal: Int) {
g.color = 0xffcf2b43;
g.fillRect(0, iron.App.h() - 6, iron.App.w() / assetsTotal * assetsLoaded, 6);
}
}

View File

@ -0,0 +1,90 @@
package leenkx.trait.internal;
import kha.Image;
import kha.Video;
import iron.Trait;
import iron.object.MeshObject;
/**
Replaces the diffuse texture of the first material of the trait's object
with a video texture.
@see https://github.com/leenkx3d/leenkx_examples/tree/master/material_movie
**/
class MovieTexture extends Trait {
/**
Caches all render targets used by this trait for re-use when having
multiple videos of the same size. The lookup only takes place on trait
initialization.
Map layout: `[width => [height => image]]`
**/
static var imageCache: Map<Int, Map<Int, Image>> = new Map();
var video: Video;
var image: Image;
var videoName: String;
function pow(pow: Int): Int {
var ret = 1;
for (i in 0...pow) ret *= 2;
return ret;
}
function getPower2(i: Int): Int {
var power = 0;
while (true) {
var res = pow(power);
if (res >= i) return res;
power++;
}
}
public function new(videoName: String) {
super();
this.videoName = videoName;
notifyOnInit(init);
}
function init() {
iron.data.Data.getVideo(videoName, function(vid: kha.Video) {
video = vid;
video.play(true);
var w = getPower2(video.width());
var h = getPower2(video.height());
// Lazily fill the outer map
var hMap: Map<Int, Image> = imageCache[w];
if (hMap == null) {
imageCache[w] = new Map<Int, Image>();
}
image = imageCache[w][h];
if (image == null) {
imageCache[w][h] = image = Image.createRenderTarget(w, h);
}
var o = cast(object, MeshObject);
o.materials[0].contexts[0].textures[0] = image; // Override diffuse texture
notifyOnRender2D(render);
});
}
function render(g: kha.graphics2.Graphics) {
g.end();
var g2 = image.g2;
g2.begin(true, 0xff000000);
g2.color = 0xffffffff;
g2.drawVideo(video, 0, 0, image.width, image.height);
g2.end();
g.begin(false);
}
}

View File

@ -0,0 +1,34 @@
package leenkx.trait.internal;
import iron.Trait;
import leenkx.trait.physics.RigidBody;
#if lnx_terrain
class TerrainPhysics extends Trait {
public function new() {
super();
notifyOnInit(init);
}
function init() {
var stream = iron.Scene.active.terrainStream;
stream.notifyOnReady(function() {
for (sector in stream.sectors) {
// Heightmap to bytes
var tex = stream.heightTextures[sector.uid];
var p = tex.getPixels();
var b = haxe.io.Bytes.alloc(tex.width * tex.height);
for (i in 0...b.length) b.set(i, p.get(i * 4));
// Shape.Terrain, mass
var rb = new RigidBody(7, 0);
rb.heightData = b;
sector.addTrait(rb);
}
});
}
}
#end

View File

@ -0,0 +1,372 @@
package leenkx.trait.internal;
import iron.object.DecalObject;
import iron.object.MeshObject;
import iron.Trait;
import kha.Image;
import iron.math.Vec4;
import iron.data.MaterialData;
import iron.Scene;
import iron.object.Object;
import iron.object.Uniforms;
class UniformsManager extends Trait{
static var floatsRegistered = false;
static var floatsMap = new Map<Object, Map<MaterialData, Map<String, Null<kha.FastFloat>>>>();
static var vectorsRegistered = false;
static var vectorsMap = new Map<Object, Map<MaterialData, Map<String, Vec4>>>();
static var texturesRegistered = false;
static var texturesMap = new Map<Object, Map<MaterialData, Map<String, kha.Image>>>();
static var sceneRemoveInitalized = false;
public var uniformExists = false;
public function new() {
super();
notifyOnAdd(init);
notifyOnRemove(removeObject);
if (!sceneRemoveInitalized) {
Scene.active.notifyOnRemove(removeScene);
}
}
function init() {
if (Std.isOfType(object, MeshObject)) {
var materials = cast(object, MeshObject).materials;
for (material in materials) {
var exists = registerShaderUniforms(material);
if (exists) {
uniformExists = true;
}
}
}
#if rp_decals
if (Std.isOfType(object, DecalObject)) {
var material = cast(object, DecalObject).material;
var exists = registerShaderUniforms(material);
if (exists) {
uniformExists = true;
}
}
#end
}
static function removeScene() {
removeObjectFromAllMaps(Scene.active.root);
}
function removeObject() {
removeObjectFromAllMaps(object);
}
// Helper method to register float, vec3 and texture getter functions
static function register(type: UniformType) {
switch (type) {
case Float:
if (!floatsRegistered) {
floatsRegistered = true;
Uniforms.externalFloatLinks.push(floatLink);
}
case Vector:
if (!vectorsRegistered) {
vectorsRegistered = true;
Uniforms.externalVec3Links.push(vec3Link);
}
case Texture:
if (!texturesRegistered) {
texturesRegistered = true;
Uniforms.externalTextureLinks.push(textureLink);
}
}
}
// Register and map shader uniforms if it is an leenkx shader parameter
public static function registerShaderUniforms(material: MaterialData) : Bool {
var uniformExist = false;
if (!floatsMap.exists(Scene.active.root)) floatsMap.set(Scene.active.root, null);
if (!vectorsMap.exists(Scene.active.root)) vectorsMap.set(Scene.active.root, null);
if (!texturesMap.exists(Scene.active.root)) texturesMap.set(Scene.active.root, null);
for (context in material.shader.raw.contexts) { // For each context in shader
for (constant in context.constants) { // For each constant in the context
if (constant.is_lnx_parameter) { // Check if leenkx parameter
uniformExist = true;
var object = Scene.active.root; // Map default uniforms to scene root
switch (constant.type) {
case "float":
var link = constant.link;
var value = constant.floatValue;
setFloatValue(material, object, link, value);
register(Float);
case "vec3":
var vec = new Vec4();
vec.x = constant.vec3Value.get(0);
vec.y = constant.vec3Value.get(1);
vec.z = constant.vec3Value.get(2);
setVec3Value(material, object, constant.link, vec);
register(Vector);
}
}
}
for (texture in context.texture_units) {
if (texture.is_lnx_parameter) { // Check if leenkx parameter
uniformExist = true;
var object = Scene.active.root; // Map default texture to scene root
if (texture.default_image_file == null) {
setTextureValue(material, object, texture.link, null);
}
else {
iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) {
setTextureValue(material, object, texture.link, image);
});
}
register(Texture);
}
}
}
return uniformExist;
}
// Method to set map Object -> Material -> Link -> FLoat
public static function setFloatValue(material: MaterialData, object: Object, link: String, value: Null<kha.FastFloat>) {
if (object == null || material == null || link == null) return;
var map = floatsMap;
var matMap = map.get(object);
if (matMap == null) {
matMap = new Map();
map.set(object, matMap);
}
var entry = matMap.get(material);
if (entry == null) {
entry = new Map();
matMap.set(material, entry);
}
entry.set(link, value); // parameter name, value
}
// Method to set map Object -> Material -> Link -> Vec3
public static function setVec3Value(material: MaterialData, object: Object, link: String, value: Vec4) {
if (object == null || material == null || link == null) return;
var map = vectorsMap;
var matMap = map.get(object);
if (matMap == null) {
matMap = new Map();
map.set(object, matMap);
}
var entry = matMap.get(material);
if (entry == null) {
entry = new Map();
matMap.set(material, entry);
}
entry.set(link, value); // parameter name, value
}
// Method to set map Object -> Material -> Link -> Texture
public static function setTextureValue(material: MaterialData, object: Object, link: String, value: kha.Image) {
if (object == null || material == null || link == null) return;
var map = texturesMap;
var matMap = map.get(object);
if (matMap == null) {
matMap = new Map();
map.set(object, matMap);
}
var entry = matMap.get(material);
if (entry == null) {
entry = new Map();
matMap.set(material, entry);
}
entry.set(link, value); // parameter name, value
}
// Method to get object specific material parameter float value
public static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
if (object == null || mat == null) return null;
// First check if float exists per object
var res = getObjectFloatLink(object, mat, link);
if (res == null) {
// If not defined per object, use default scene root
res = getObjectFloatLink(Scene.active.root, mat, link);
}
return res;
}
// Get float link
static function getObjectFloatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
var material = floatsMap.get(object);
if (material == null) return null;
var entry = material.get(mat);
if (entry == null) return null;
return entry.get(link);
}
// Method to get object specific material parameter vector value
public static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
if (object == null || mat == null) return null;
// First check if vector exists per object
var res = getObjectVec3Link(object, mat, link);
if (res == null) {
// If not defined per object, use default scene root
res = getObjectVec3Link(Scene.active.root, mat, link);
}
return res;
}
// Get vector link
static function getObjectVec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
var material = vectorsMap.get(object);
if (material == null) return null;
var entry = material.get(mat);
if (entry == null) return null;
return entry.get(link);
}
// Method to get object specific material parameter texture value
public static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
if (object == null || mat == null) return null;
// First check if texture exists per object
var res = getObjectTextureLink(object, mat, link);
if (res == null) {
// If not defined per object, use default scene root
res = getObjectTextureLink(Scene.active.root, mat, link);
}
return res;
}
// Get texture link
static function getObjectTextureLink(object: Object, mat: MaterialData, link: String): kha.Image {
var material = texturesMap.get(object);
if (material == null) return null;
var entry = material.get(mat);
if (entry == null) return null;
return entry.get(link);
}
// Returns complete map of float value material paramets
public static function getFloatsMap():Map<Object, Map<MaterialData, Map<String, Null<kha.FastFloat>>>>{
return floatsMap;
}
// Returns complete map of vec3 value material paramets
public static function getVectorsMap():Map<Object, Map<MaterialData, Map<String, Vec4>>>{
return vectorsMap;
}
// Returns complete map of texture value material paramets
public static function getTexturesMap():Map<Object, Map<MaterialData, Map<String, kha.Image>>>{
return texturesMap;
}
// Remove all object specific material paramenter keys
public static function removeObjectFromAllMaps(object: Object) {
floatsMap.remove(object);
vectorsMap.remove(object);
texturesMap.remove(object);
}
// Remove object specific material paramenter keys
public static function removeObjectFromMap(object: Object, type: UniformType) {
switch (type) {
case Float: floatsMap.remove(object);
case Vector: vectorsMap.remove(object);
case Texture: texturesMap.remove(object);
}
}
public static function removeFloatValue(object: Object, mat:MaterialData, link: String) {
var material = floatsMap.get(object);
if (material == null) return;
var entry = material.get(mat);
if (entry == null) return;
entry.remove(link);
if (!entry.keys().hasNext()) material.remove(mat);
if (!material.keys().hasNext()) floatsMap.remove(object);
}
public static function removeVectorValue(object: Object, mat:MaterialData, link: String) {
var material = vectorsMap.get(object);
if (material == null) return;
var entry = material.get(mat);
if (entry == null) return;
entry.remove(link);
if (!entry.keys().hasNext()) material.remove(mat);
if (!material.keys().hasNext()) vectorsMap.remove(object);
}
public static function removeTextureValue(object: Object, mat:MaterialData, link: String) {
var material = texturesMap.get(object);
if (material == null) return;
var entry = material.get(mat);
if (entry == null) return;
entry.remove(link);
if (!entry.keys().hasNext()) material.remove(mat);
if (!material.keys().hasNext()) texturesMap.remove(object);
}
}
enum abstract UniformType(Int) from Int to Int {
var Float = 0;
var Vector = 1;
var Texture = 2;
}

View File

@ -0,0 +1,91 @@
package leenkx.trait.internal;
#if js
import iron.data.Data;
import iron.data.Wasm;
import iron.system.Input;
import iron.system.Time;
class WasmScript extends iron.Trait {
var wasm: Wasm;
public var wasmName: String;
var objectMap: Map<Int, iron.object.Object> = new Map();
public function new(handle: String) {
super();
wasmName = handle;
// Leenkx API exposed to WebAssembly
// TODO: static
/*static*/ var imports = {
env: {
trace: function(i: Int) { trace(wasm.getString(i)); },
tracef: function(f: Float) { trace(f); },
tracei: function(i: Int) { trace(i); },
notify_on_update: function(i: Int) { notifyOnUpdate(wasm.exports.update); },
remove_update: function(i: Int) { removeUpdate(wasm.exports.update); },
get_object: function(name: Int) {
var s = wasm.getString(name);
var o = iron.Scene.active.getChild(s);
if (o == null) return -1;
objectMap.set(o.uid, o);
return o.uid;
},
set_transform: function(object: Int, x: Float, y: Float, z: Float, rx: Float, ry: Float, rz: Float, sx: Float, sy: Float, sz: Float) {
var o = objectMap.get(object);
if (o == null) return;
var t = o.transform;
t.loc.set(x, y, z);
t.scale.set(sx, sy, sz);
t.setRotation(rx, ry, rz);
},
set_location: function(object: Int, x: Float, y: Float, z: Float) {
var o = objectMap.get(object);
if (o == null) return;
var t = o.transform;
t.loc.set(x, y, z);
},
set_scale: function(object: Int, x: Float, y: Float, z: Float) {
var o = objectMap.get(object);
if (o == null) return;
var t = o.transform;
t.scale.set(x, y, z);
},
set_rotation: function(object: Int, x: Float, y: Float, z: Float) {
var o = objectMap.get(object);
if (o == null) return;
var t = o.transform;
t.setRotation(x, y, z);
},
mouse_x: function() { return Input.getMouse().x; },
mouse_y: function() { return Input.getMouse().y; },
mouse_started: function(button: Int) { return Input.getMouse().started(); },
mouse_down: function(button: Int) { return Input.getMouse().down(); },
mouse_released: function(button: Int) { return Input.getMouse().released(); },
key_started: function(key: Int) { return Input.getKeyboard().started(Keyboard.keyCode(cast key)); },
key_down: function(key: Int) { return Input.getKeyboard().down(Keyboard.keyCode(cast key)); },
key_released: function(key: Int) { return Input.getKeyboard().released(Keyboard.keyCode(cast key)); },
time_real: function() { return Time.time(); },
time_delta: function() { return Time.delta; },
js_eval: function(fn: Int) { js.Lib.eval(wasm.getString(fn)); },
js_call_object: function(object: Int, fn: Int) { Reflect.callMethod(objectMap.get(object), Reflect.field(objectMap.get(object), wasm.getString(fn)), null); },
js_call_static: function(path: Int, fn: Int) { var cpath = wasm.getString(path); var ctype = Type.resolveClass(cpath); Reflect.callMethod(ctype, Reflect.field(ctype, wasm.getString(fn)), []); },
},
};
Data.getBlob(handle + ".wasm", function(b: kha.Blob) {
wasm = Wasm.instance(b, imports);
wasm.exports.main();
});
}
}
#end

View File

@ -0,0 +1,44 @@
/* C API */
void trace(const char* s);
void tracef(float f);
void tracei(int i);
typedef void(*func_update)(void);
void notify_on_update(func_update f);
void remove_update(func_update f);
int get_object(const char* name);
void set_transform(int object, float x, float y, float z, float rx, float ry, float rz, float sx, float sy, float sz);
void set_location(int object, float x, float y, float z);
void set_scale(int object, float x, float y, float z);
void set_rotation(int object, float x, float y, float z);
int mouse_x(void);
int mouse_y(void);
int mouse_started(int button);
int mouse_down(int button);
int mouse_released(int button);
int key_started(int key); // kha.input.KeyCode
int key_down(int key);
int key_released(int key);
float time_real(void);
float time_delta(void);
void js_eval(const char* fn);
void js_call_object(int object, const char* fn);
void js_call_static(const char* path, const char* fn);
/* C template
#define WASM_EXPORT __attribute__((visibility("default")))
void logs(const char* s);
WASM_EXPORT
int main() {
logs("Hello, world!");
return 0;
}
*/

View File

@ -0,0 +1,102 @@
package leenkx.trait.navigation;
import kha.FastFloat;
import kha.System;
import iron.math.Vec4;
#if lnx_ui
import leenkx.ui.Canvas;
#end
#if lnx_navigation
class DebugDrawHelper {
final navigation: Navigation;
final lines: Array<LineData> = [];
var debugMode: Navigation.DebugDrawMode = NoDebug;
public function new(navigation: Navigation) {
this.navigation = navigation;
iron.App.notifyOnRender2D(onRender);
}
public function drawLine(from: Vec4, to: Vec4, color: kha.Color) {
final fromScreenSpace = worldToScreenFast(new Vec4().setFrom(from));
final toScreenSpace = worldToScreenFast(new Vec4().setFrom(to));
// 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: color
});
}
}
public function setDebugMode(debugMode: Navigation.DebugDrawMode) {
this.debugMode = debugMode;
}
public function getDebugMode(): Navigation.DebugDrawMode {
return debugMode;
}
function onRender(g: kha.graphics2.Graphics) {
if (getDebugMode() == NoDebug) {
return;
}
for(navMesh in Navigation.active.navMeshes) {
navMesh.drawDebugMesh(this);
}
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);
}
/**
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;
}
#end

View File

@ -0,0 +1,47 @@
package leenkx.trait.navigation;
#if lnx_navigation
import iron.math.Vec4;
import leenkx.trait.NavMesh;
class Navigation extends iron.Trait {
public static var active: Navigation = null;
public var navMeshes: Array<NavMesh> = [];
//public var recast: Recast;
public var debugDrawHelper: DebugDrawHelper = null;
public function new() {
super();
active = this;
initDebugDrawing();
}
function initDebugDrawing() {
debugDrawHelper = new DebugDrawHelper(this);
}
}
enum abstract DebugDrawMode(Int) from Int to Int {
/** All debug flags off. **/
var NoDebug = 0;
/** Draw wireframes of the NavMesh. **/
var DrawWireframe = 1;
}
class RecastConversions {
public static function recastVec3FromVec4(vec: Vec4): recast.Recast.Vec3{
return new recast.Recast.Vec3(vec.x, vec.z, vec.y);
}
public static function vec4FromRecastVec3(vec: recast.Recast.Vec3): Vec4 {
return new Vec4(vec.x, vec.z, vec.y);
}
}
#end

View File

@ -0,0 +1,15 @@
package leenkx.trait.physics;
#if (!lnx_physics)
class KinematicCharacterController extends iron.Trait { public function new() { super(); } }
#else
#if lnx_bullet
typedef KinematicCharacterController = leenkx.trait.physics.bullet.KinematicCharacterController;
#end
#end

View File

@ -0,0 +1,19 @@
package leenkx.trait.physics;
#if (!lnx_physics)
class PhysicsConstraint extends iron.Trait { public function new() { super(); } }
#else
#if lnx_bullet
typedef PhysicsConstraint = leenkx.trait.physics.bullet.PhysicsConstraint;
#else
typedef PhysicsConstraint = leenkx.trait.physics.oimo.PhysicsConstraint;
#end
#end

View File

@ -0,0 +1,19 @@
package leenkx.trait.physics;
#if (!lnx_physics)
class PhysicsHook extends iron.Trait { public function new() { super(); } }
#else
#if lnx_bullet
typedef PhysicsHook = leenkx.trait.physics.bullet.PhysicsHook;
#else
typedef PhysicsHook = leenkx.trait.physics.oimo.PhysicsHook;
#end
#end

View File

@ -0,0 +1,19 @@
package leenkx.trait.physics;
#if (!lnx_physics)
class PhysicsWorld extends iron.Trait { public function new() { super(); } }
#else
#if lnx_bullet
typedef PhysicsWorld = leenkx.trait.physics.bullet.PhysicsWorld;
#else
typedef PhysicsWorld = leenkx.trait.physics.oimo.PhysicsWorld;
#end
#end

View File

@ -0,0 +1,19 @@
package leenkx.trait.physics;
#if (!lnx_physics)
class RigidBody extends iron.Trait { public function new() { super(); } }
#else
#if lnx_bullet
typedef RigidBody = leenkx.trait.physics.bullet.RigidBody;
#else
typedef RigidBody = leenkx.trait.physics.oimo.RigidBody;
#end
#end

View File

@ -0,0 +1,19 @@
package leenkx.trait.physics;
#if (!lnx_physics_soft)
class SoftBody extends Trait { public function new() { super(); } }
#else
#if lnx_bullet
typedef SoftBody = leenkx.trait.physics.bullet.SoftBody;
#else
typedef SoftBody = leenkx.trait.physics.oimo.SoftBody;
#end
#end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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