forked from LeenkxTeam/LNXSDK
Update Files
This commit is contained in:
27
leenkx/Sources/leenkx/trait/ArcBall.hx
Normal file
27
leenkx/Sources/leenkx/trait/ArcBall.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
77
leenkx/Sources/leenkx/trait/Character.hx
Normal file
77
leenkx/Sources/leenkx/trait/Character.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
40
leenkx/Sources/leenkx/trait/CustomParticle.hx
Normal file
40
leenkx/Sources/leenkx/trait/CustomParticle.hx
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
87
leenkx/Sources/leenkx/trait/FirstPersonController.hx
Normal file
87
leenkx/Sources/leenkx/trait/FirstPersonController.hx
Normal 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
|
||||
}
|
59
leenkx/Sources/leenkx/trait/FollowCamera.hx
Normal file
59
leenkx/Sources/leenkx/trait/FollowCamera.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
81
leenkx/Sources/leenkx/trait/NavAgent.hx
Normal file
81
leenkx/Sources/leenkx/trait/NavAgent.hx
Normal 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();
|
||||
}
|
||||
}
|
146
leenkx/Sources/leenkx/trait/NavCrowd.hx
Normal file
146
leenkx/Sources/leenkx/trait/NavCrowd.hx
Normal 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
|
||||
}
|
505
leenkx/Sources/leenkx/trait/NavMesh.hx
Normal file
505
leenkx/Sources/leenkx/trait/NavMesh.hx
Normal 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;
|
||||
}
|
60
leenkx/Sources/leenkx/trait/NavObstacle.hx
Normal file
60
leenkx/Sources/leenkx/trait/NavObstacle.hx
Normal 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
|
||||
}
|
812
leenkx/Sources/leenkx/trait/PhysicsBreak.hx
Normal file
812
leenkx/Sources/leenkx/trait/PhysicsBreak.hx
Normal 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();
|
||||
}
|
||||
}
|
132
leenkx/Sources/leenkx/trait/PhysicsDrag.hx
Normal file
132
leenkx/Sources/leenkx/trait/PhysicsDrag.hx
Normal 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
|
||||
}
|
73
leenkx/Sources/leenkx/trait/SimpleMoveObject.hx
Normal file
73
leenkx/Sources/leenkx/trait/SimpleMoveObject.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
72
leenkx/Sources/leenkx/trait/SimpleRotateObject.hx
Normal file
72
leenkx/Sources/leenkx/trait/SimpleRotateObject.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
92
leenkx/Sources/leenkx/trait/SimpleScaleObject.hx
Normal file
92
leenkx/Sources/leenkx/trait/SimpleScaleObject.hx
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
108
leenkx/Sources/leenkx/trait/ThirdPersonController.hx
Normal file
108
leenkx/Sources/leenkx/trait/ThirdPersonController.hx
Normal 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
|
||||
}
|
265
leenkx/Sources/leenkx/trait/VehicleBody.hx
Normal file
265
leenkx/Sources/leenkx/trait/VehicleBody.hx
Normal 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
|
||||
}
|
139
leenkx/Sources/leenkx/trait/VirtualGamepad.hx
Normal file
139
leenkx/Sources/leenkx/trait/VirtualGamepad.hx
Normal 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;
|
||||
}
|
||||
}
|
150
leenkx/Sources/leenkx/trait/WalkNavigation.hx
Normal file
150
leenkx/Sources/leenkx/trait/WalkNavigation.hx
Normal 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
|
||||
}
|
19
leenkx/Sources/leenkx/trait/internal/Bridge.hx
Normal file
19
leenkx/Sources/leenkx/trait/internal/Bridge.hx
Normal 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
|
60
leenkx/Sources/leenkx/trait/internal/CameraController.hx
Normal file
60
leenkx/Sources/leenkx/trait/internal/CameraController.hx
Normal 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
|
||||
}
|
217
leenkx/Sources/leenkx/trait/internal/CanvasScript.hx
Normal file
217
leenkx/Sources/leenkx/trait/internal/CanvasScript.hx
Normal 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
|
||||
}
|
1076
leenkx/Sources/leenkx/trait/internal/DebugConsole.hx
Normal file
1076
leenkx/Sources/leenkx/trait/internal/DebugConsole.hx
Normal file
File diff suppressed because it is too large
Load Diff
215
leenkx/Sources/leenkx/trait/internal/DebugDraw.hx
Normal file
215
leenkx/Sources/leenkx/trait/internal/DebugDraw.hx
Normal 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
|
195
leenkx/Sources/leenkx/trait/internal/LivePatch.hx
Normal file
195
leenkx/Sources/leenkx/trait/internal/LivePatch.hx
Normal 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
|
||||
}
|
11
leenkx/Sources/leenkx/trait/internal/LoadingScreen.hx
Normal file
11
leenkx/Sources/leenkx/trait/internal/LoadingScreen.hx
Normal 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);
|
||||
}
|
||||
}
|
90
leenkx/Sources/leenkx/trait/internal/MovieTexture.hx
Normal file
90
leenkx/Sources/leenkx/trait/internal/MovieTexture.hx
Normal 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);
|
||||
}
|
||||
}
|
34
leenkx/Sources/leenkx/trait/internal/TerrainPhysics.hx
Normal file
34
leenkx/Sources/leenkx/trait/internal/TerrainPhysics.hx
Normal 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
|
372
leenkx/Sources/leenkx/trait/internal/UniformsManager.hx
Normal file
372
leenkx/Sources/leenkx/trait/internal/UniformsManager.hx
Normal 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;
|
||||
}
|
91
leenkx/Sources/leenkx/trait/internal/WasmScript.hx
Normal file
91
leenkx/Sources/leenkx/trait/internal/WasmScript.hx
Normal 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
|
44
leenkx/Sources/leenkx/trait/internal/wasm_api.h
Normal file
44
leenkx/Sources/leenkx/trait/internal/wasm_api.h
Normal 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;
|
||||
}
|
||||
*/
|
102
leenkx/Sources/leenkx/trait/navigation/DebugDrawHelper.hx
Normal file
102
leenkx/Sources/leenkx/trait/navigation/DebugDrawHelper.hx
Normal 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
|
47
leenkx/Sources/leenkx/trait/navigation/Navigation.hx
Normal file
47
leenkx/Sources/leenkx/trait/navigation/Navigation.hx
Normal 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
|
@ -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
|
19
leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx
Normal file
19
leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx
Normal 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
|
19
leenkx/Sources/leenkx/trait/physics/PhysicsHook.hx
Normal file
19
leenkx/Sources/leenkx/trait/physics/PhysicsHook.hx
Normal 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
|
19
leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx
Normal file
19
leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx
Normal 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
|
19
leenkx/Sources/leenkx/trait/physics/RigidBody.hx
Normal file
19
leenkx/Sources/leenkx/trait/physics/RigidBody.hx
Normal 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
|
19
leenkx/Sources/leenkx/trait/physics/SoftBody.hx
Normal file
19
leenkx/Sources/leenkx/trait/physics/SoftBody.hx
Normal 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
|
224
leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx
Normal file
224
leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx
Normal file
@ -0,0 +1,224 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
import bullet.Bt.Vector3;
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.System;
|
||||
|
||||
import iron.math.Vec4;
|
||||
|
||||
#if lnx_ui
|
||||
import leenkx.ui.Canvas;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
class DebugDrawHelper {
|
||||
static inline var contactPointSizePx = 4;
|
||||
static inline var contactPointNormalColor = 0xffffffff;
|
||||
static inline var contactPointDrawLifetime = true;
|
||||
|
||||
final physicsWorld: PhysicsWorld;
|
||||
final lines: Array<LineData> = [];
|
||||
final texts: Array<TextData> = [];
|
||||
var font: kha.Font = null;
|
||||
|
||||
var debugMode: PhysicsWorld.DebugDrawMode = NoDebug;
|
||||
|
||||
public function new(physicsWorld: PhysicsWorld) {
|
||||
this.physicsWorld = physicsWorld;
|
||||
|
||||
#if lnx_ui
|
||||
iron.data.Data.getFont(Canvas.defaultFontName, function(defaultFont: kha.Font) {
|
||||
font = defaultFont;
|
||||
});
|
||||
#end
|
||||
|
||||
iron.App.notifyOnRender2D(onRender);
|
||||
}
|
||||
|
||||
public function drawLine(from: bullet.Bt.Vector3, to: bullet.Bt.Vector3, color: bullet.Bt.Vector3) {
|
||||
#if js
|
||||
// https://github.com/InfiniteLee/ammo-debug-drawer/pull/1/files
|
||||
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html#pointers-and-comparisons
|
||||
from = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", from);
|
||||
to = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", to);
|
||||
color = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", color);
|
||||
#end
|
||||
|
||||
final fromScreenSpace = worldToScreenFast(new Vec4(from.x(), from.y(), from.z(), 1.0));
|
||||
final toScreenSpace = worldToScreenFast(new Vec4(to.x(), to.y(), to.z(), 1.0));
|
||||
|
||||
// For now don't draw lines if any point is outside of clip space z,
|
||||
// investigate how to clamp lines to clip space borders
|
||||
if (fromScreenSpace.w == 1 && toScreenSpace.w == 1) {
|
||||
lines.push({
|
||||
fromX: fromScreenSpace.x,
|
||||
fromY: fromScreenSpace.y,
|
||||
toX: toScreenSpace.x,
|
||||
toY: toScreenSpace.y,
|
||||
color: kha.Color.fromFloats(color.x(), color.y(), color.z(), 1.0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function drawContactPoint(pointOnB: Vector3, normalOnB: Vector3, distance: kha.FastFloat, lifeTime: Int, color: Vector3) {
|
||||
#if js
|
||||
pointOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", pointOnB);
|
||||
normalOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", normalOnB);
|
||||
color = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", color);
|
||||
#end
|
||||
|
||||
final contactPointScreenSpace = worldToScreenFast(new Vec4(pointOnB.x(), pointOnB.y(), pointOnB.z(), 1.0));
|
||||
final toScreenSpace = worldToScreenFast(new Vec4(pointOnB.x() + normalOnB.x() * distance, pointOnB.y() + normalOnB.y() * distance, pointOnB.z() + normalOnB.z() * distance, 1.0));
|
||||
|
||||
if (contactPointScreenSpace.w == 1) {
|
||||
final color = kha.Color.fromFloats(color.x(), color.y(), color.z(), 1.0);
|
||||
|
||||
lines.push({
|
||||
fromX: contactPointScreenSpace.x - contactPointSizePx,
|
||||
fromY: contactPointScreenSpace.y - contactPointSizePx,
|
||||
toX: contactPointScreenSpace.x + contactPointSizePx,
|
||||
toY: contactPointScreenSpace.y + contactPointSizePx,
|
||||
color: color
|
||||
});
|
||||
|
||||
lines.push({
|
||||
fromX: contactPointScreenSpace.x - contactPointSizePx,
|
||||
fromY: contactPointScreenSpace.y + contactPointSizePx,
|
||||
toX: contactPointScreenSpace.x + contactPointSizePx,
|
||||
toY: contactPointScreenSpace.y - contactPointSizePx,
|
||||
color: color
|
||||
});
|
||||
|
||||
if (toScreenSpace.w == 1) {
|
||||
lines.push({
|
||||
fromX: contactPointScreenSpace.x,
|
||||
fromY: contactPointScreenSpace.y,
|
||||
toX: toScreenSpace.x,
|
||||
toY: toScreenSpace.y,
|
||||
color: contactPointNormalColor
|
||||
});
|
||||
}
|
||||
|
||||
if (contactPointDrawLifetime && font != null) {
|
||||
texts.push({
|
||||
x: contactPointScreenSpace.x,
|
||||
y: contactPointScreenSpace.y,
|
||||
color: color,
|
||||
text: Std.string(lifeTime), // lifeTime: number of frames the contact point existed
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function reportErrorWarning(warningString: bullet.Bt.BulletString) {
|
||||
trace(warningString.toHaxeString().trim());
|
||||
}
|
||||
|
||||
public function draw3dText(location: Vector3, textString: bullet.Bt.BulletString) {
|
||||
if (font == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if js
|
||||
location = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", location);
|
||||
#end
|
||||
|
||||
final locationScreenSpace = worldToScreenFast(new Vec4(location.x(), location.y(), location.z(), 1.0));
|
||||
|
||||
texts.push({
|
||||
x: locationScreenSpace.x,
|
||||
y: locationScreenSpace.y,
|
||||
color: kha.Color.fromFloats(0.0, 0.0, 0.0, 1.0),
|
||||
text: textString.toHaxeString()
|
||||
});
|
||||
}
|
||||
|
||||
public function setDebugMode(debugMode: PhysicsWorld.DebugDrawMode) {
|
||||
this.debugMode = debugMode;
|
||||
}
|
||||
|
||||
public function getDebugMode(): PhysicsWorld.DebugDrawMode {
|
||||
#if js
|
||||
return debugMode;
|
||||
#elseif hl
|
||||
return physicsWorld.getDebugDrawMode();
|
||||
#else
|
||||
return NoDebug;
|
||||
#end
|
||||
}
|
||||
|
||||
function onRender(g: kha.graphics2.Graphics) {
|
||||
if (getDebugMode() == NoDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It might be a bit unusual to call this method in a render callback
|
||||
// instead of the update loop (after all it doesn't draw anything but
|
||||
// will cause Bullet to call the btIDebugDraw callbacks), but this way
|
||||
// we can ensure that--within a frame--the function will not be called
|
||||
// before some user-specific physics update, which would result in a
|
||||
// one-frame drawing delay... Ideally we would ensure that debugDrawWorld()
|
||||
// is called when all other (late) update callbacks are already executed...
|
||||
physicsWorld.world.debugDrawWorld();
|
||||
|
||||
g.opacity = 1.0;
|
||||
|
||||
for (line in lines) {
|
||||
g.color = line.color;
|
||||
g.drawLine(line.fromX, line.fromY, line.toX, line.toY, 1.0);
|
||||
}
|
||||
lines.resize(0);
|
||||
|
||||
if (font != null) {
|
||||
g.font = font;
|
||||
g.fontSize = 12;
|
||||
for (text in texts) {
|
||||
g.color = text.color;
|
||||
g.drawString(text.text, text.x, text.y);
|
||||
}
|
||||
texts.resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Transform a world coordinate vector into screen space and store the
|
||||
result in the input vector's x and y coordinates. The w coordinate is
|
||||
set to 0 if the input vector is outside the active camera's far and near
|
||||
planes, and 1 otherwise.
|
||||
**/
|
||||
inline function worldToScreenFast(loc: Vec4): Vec4 {
|
||||
final cam = iron.Scene.active.camera;
|
||||
loc.w = 1.0;
|
||||
loc.applyproj(cam.VP);
|
||||
|
||||
if (loc.z < -1 || loc.z > 1) {
|
||||
loc.w = 0.0;
|
||||
}
|
||||
else {
|
||||
loc.x = (loc.x + 1) * 0.5 * System.windowWidth();
|
||||
loc.y = (1 - loc.y) * 0.5 * System.windowHeight();
|
||||
loc.w = 1.0;
|
||||
}
|
||||
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
|
||||
@:structInit
|
||||
class LineData {
|
||||
public var fromX: FastFloat;
|
||||
public var fromY: FastFloat;
|
||||
public var toX: FastFloat;
|
||||
public var toY: FastFloat;
|
||||
public var color: kha.Color;
|
||||
}
|
||||
|
||||
@:structInit
|
||||
class TextData {
|
||||
public var x: FastFloat;
|
||||
public var y: FastFloat;
|
||||
public var color: kha.Color;
|
||||
public var text: String;
|
||||
}
|
@ -0,0 +1,364 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.object.Transform;
|
||||
import iron.object.MeshObject;
|
||||
|
||||
class KinematicCharacterController extends Trait {
|
||||
|
||||
var shape: ControllerShape;
|
||||
var shapeConvex: bullet.Bt.ConvexShape;
|
||||
var shapeConvexHull: bullet.Bt.ConvexHullShape;
|
||||
var isConvexHull = false;
|
||||
|
||||
public var physics: PhysicsWorld;
|
||||
public var transform: Transform = null;
|
||||
public var mass: Float;
|
||||
public var friction: Float;
|
||||
public var restitution: Float;
|
||||
public var collisionMargin: Float;
|
||||
public var animated: Bool;
|
||||
public var group = 1;
|
||||
var bodyScaleX: Float; // Transform scale at creation time
|
||||
var bodyScaleY: Float;
|
||||
var bodyScaleZ: Float;
|
||||
var currentScaleX: Float;
|
||||
var currentScaleY: Float;
|
||||
var currentScaleZ: Float;
|
||||
var jumpSpeed: Float;
|
||||
|
||||
public var body: bullet.Bt.PairCachingGhostObject = null;
|
||||
public var character: bullet.Bt.KinematicCharacterController = null;
|
||||
public var ready = false;
|
||||
static var nextId = 0;
|
||||
public var id = 0;
|
||||
public var onReady: Void->Void = null;
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1: bullet.Bt.Vector3;
|
||||
static var quat1: bullet.Bt.Quaternion;
|
||||
static var trans1: bullet.Bt.Transform;
|
||||
static var quat = new Quat();
|
||||
|
||||
static inline var CF_CHARACTER_OBJECT = 16;
|
||||
|
||||
public function new(mass = 1.0, shape = ControllerShape.Capsule, jumpSpeed = 8.0, friction = 0.5, restitution = 0.0,
|
||||
collisionMargin = 0.0, animated = false, group = 1) {
|
||||
super();
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 0);
|
||||
trans1 = new bullet.Bt.Transform();
|
||||
}
|
||||
|
||||
this.mass = mass;
|
||||
this.jumpSpeed = jumpSpeed;
|
||||
this.shape = shape;
|
||||
this.friction = friction;
|
||||
this.restitution = restitution;
|
||||
this.collisionMargin = collisionMargin;
|
||||
this.animated = animated;
|
||||
this.group = group;
|
||||
|
||||
notifyOnAdd(init);
|
||||
notifyOnLateUpdate(lateUpdate);
|
||||
notifyOnRemove(removeFromWorld);
|
||||
}
|
||||
|
||||
inline function withMargin(f: Float): Float {
|
||||
return f + f * collisionMargin;
|
||||
}
|
||||
|
||||
public function notifyOnReady(f: Void->Void) {
|
||||
onReady = f;
|
||||
if (ready) onReady();
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if (ready) return;
|
||||
ready = true;
|
||||
|
||||
transform = object.transform;
|
||||
physics = leenkx.trait.physics.PhysicsWorld.active;
|
||||
|
||||
shapeConvex = null;
|
||||
shapeConvexHull = null;
|
||||
isConvexHull = false;
|
||||
|
||||
if (shape == ControllerShape.Box) {
|
||||
vec1.setX(withMargin(transform.dim.x / 2));
|
||||
vec1.setY(withMargin(transform.dim.y / 2));
|
||||
vec1.setZ(withMargin(transform.dim.z / 2));
|
||||
shapeConvex = new bullet.Bt.BoxShape(vec1);
|
||||
}
|
||||
else if (shape == ControllerShape.Sphere) {
|
||||
var width = transform.dim.x;
|
||||
if (transform.dim.y > width) width = transform.dim.y;
|
||||
if (transform.dim.z > width) width = transform.dim.z;
|
||||
shapeConvex = new bullet.Bt.SphereShape(withMargin(width / 2));
|
||||
}
|
||||
else if (shape == ControllerShape.ConvexHull && mass > 0) {
|
||||
shapeConvexHull = new bullet.Bt.ConvexHullShape();
|
||||
isConvexHull = true;
|
||||
addPointsToConvexHull(shapeConvexHull, transform.scale, collisionMargin);
|
||||
}
|
||||
else if (shape == ControllerShape.Cone) {
|
||||
shapeConvex = new bullet.Bt.ConeShapeZ(
|
||||
withMargin(transform.dim.x / 2), // Radius
|
||||
withMargin(transform.dim.z)); // Height
|
||||
}
|
||||
else if (shape == ControllerShape.Cylinder) {
|
||||
vec1.setX(withMargin(transform.dim.x / 2));
|
||||
vec1.setY(withMargin(transform.dim.y / 2));
|
||||
vec1.setZ(withMargin(transform.dim.z / 2));
|
||||
shapeConvex = new bullet.Bt.CylinderShapeZ(vec1);
|
||||
}
|
||||
else if (shape == ControllerShape.Capsule) {
|
||||
var r = transform.dim.x / 2;
|
||||
shapeConvex = new bullet.Bt.CapsuleShapeZ(
|
||||
withMargin(r), // Radius
|
||||
withMargin(transform.dim.z - r * 2)); // Height between 2 sphere centers
|
||||
}
|
||||
|
||||
trans1.setIdentity();
|
||||
vec1.setX(transform.worldx());
|
||||
vec1.setY(transform.worldy());
|
||||
vec1.setZ(transform.worldz());
|
||||
trans1.setOrigin(vec1);
|
||||
|
||||
quat.fromMat(transform.world);
|
||||
quat1.setX(quat.x);
|
||||
quat1.setY(quat.y);
|
||||
quat1.setZ(quat.z);
|
||||
quat1.setW(quat.w);
|
||||
trans1.setRotation(quat1);
|
||||
|
||||
body = new bullet.Bt.PairCachingGhostObject();
|
||||
body.setCollisionShape(isConvexHull ? shapeConvexHull : shapeConvex);
|
||||
body.setCollisionFlags(CF_CHARACTER_OBJECT);
|
||||
body.setWorldTransform(trans1);
|
||||
body.setFriction(friction);
|
||||
body.setRollingFriction(friction);
|
||||
body.setRestitution(restitution);
|
||||
#if js
|
||||
character = new bullet.Bt.KinematicCharacterController(body, isConvexHull ? shapeConvexHull : shapeConvex, 0.5, 2);
|
||||
#elseif cpp
|
||||
character = new bullet.Bt.KinematicCharacterController.create(body, isConvexHull ? shapeConvexHull : shapeConvex, 0.5, bullet.Bt.Vector3(0.0, 0.0, 1.0));
|
||||
#end
|
||||
character.setJumpSpeed(jumpSpeed);
|
||||
character.setUseGhostSweepTest(true);
|
||||
|
||||
setActivationState(ControllerActivationState.NoDeactivation);
|
||||
|
||||
bodyScaleX = currentScaleX = transform.scale.x;
|
||||
bodyScaleY = currentScaleY = transform.scale.y;
|
||||
bodyScaleZ = currentScaleZ = transform.scale.z;
|
||||
|
||||
id = nextId;
|
||||
nextId++;
|
||||
|
||||
#if js
|
||||
untyped body.userIndex = id;
|
||||
#elseif cpp
|
||||
body.setUserIndex(id);
|
||||
#end
|
||||
|
||||
// physics.addKinematicCharacterController(this);
|
||||
|
||||
if (onReady != null) onReady();
|
||||
}
|
||||
|
||||
function lateUpdate() {
|
||||
if (!ready) return;
|
||||
if (object.animation != null || animated) {
|
||||
syncTransform();
|
||||
}
|
||||
else {
|
||||
var trans = body.getWorldTransform();
|
||||
var p = trans.getOrigin();
|
||||
var q = trans.getRotation();
|
||||
transform.loc.set(p.x(), p.y(), p.z());
|
||||
transform.rot.set(q.x(), q.y(), q.z(), q.w());
|
||||
if (object.parent != null) {
|
||||
var ptransform = object.parent.transform;
|
||||
transform.loc.x -= ptransform.worldx();
|
||||
transform.loc.y -= ptransform.worldy();
|
||||
transform.loc.z -= ptransform.worldz();
|
||||
}
|
||||
transform.buildMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
public function canJump(): Bool {
|
||||
return character.canJump();
|
||||
}
|
||||
|
||||
public function onGround(): Bool {
|
||||
return character.onGround();
|
||||
}
|
||||
|
||||
public function setJumpSpeed(jumpSpeed: Float) {
|
||||
character.setJumpSpeed(jumpSpeed);
|
||||
}
|
||||
|
||||
public function setFallSpeed(fallSpeed: Float) {
|
||||
character.setFallSpeed(fallSpeed);
|
||||
}
|
||||
|
||||
public function setMaxSlope(slopeRadians: Float) {
|
||||
return character.setMaxSlope(slopeRadians);
|
||||
}
|
||||
|
||||
public function getMaxSlope(): Float {
|
||||
return character.getMaxSlope();
|
||||
}
|
||||
|
||||
public function setMaxJumpHeight(maxJumpHeight: Float) {
|
||||
character.setMaxJumpHeight(maxJumpHeight);
|
||||
}
|
||||
|
||||
public function setWalkDirection(walkDirection: Vec4) {
|
||||
vec1.setX(walkDirection.x);
|
||||
vec1.setY(walkDirection.y);
|
||||
vec1.setZ(walkDirection.z);
|
||||
character.setWalkDirection(vec1);
|
||||
}
|
||||
|
||||
public function setUpInterpolate(value: Bool) {
|
||||
character.setUpInterpolate(value);
|
||||
}
|
||||
|
||||
#if js
|
||||
public function jump(): Void{
|
||||
character.jump();
|
||||
}
|
||||
#elseif cpp
|
||||
public function jump(v: Vec4): Void{
|
||||
vec1.setX(v.x);
|
||||
vec1.setY(v.y);
|
||||
vec1.setZ(v.z);
|
||||
character.jump(vec1);
|
||||
}
|
||||
#end
|
||||
|
||||
public function removeFromWorld() {
|
||||
// if (physics != null) physics.removeKinematicCharacterController(this);
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
body.activate(false);
|
||||
}
|
||||
|
||||
public function disableGravity() {
|
||||
#if js
|
||||
character.setGravity(0.0);
|
||||
#elseif cpp
|
||||
vec1.setX(0);
|
||||
vec1.setY(0);
|
||||
vec1.setZ(0);
|
||||
character.setGravity(vec1);
|
||||
#end
|
||||
}
|
||||
|
||||
public function enableGravity() {
|
||||
#if js
|
||||
character.setGravity(Math.abs(physics.world.getGravity().z()) * 3.0); // 9.8 * 3.0 in cpp source code
|
||||
#elseif cpp
|
||||
vec1.setX(physics.world.getGravity().x() * 3.0);
|
||||
vec1.setY(physics.world.getGravity().y() * 3.0);
|
||||
vec1.setZ(physics.world.getGravity().z() * 3.0);
|
||||
character.setGravity(vec1);
|
||||
#end
|
||||
}
|
||||
|
||||
#if js
|
||||
public function setGravity(f: Float) {
|
||||
character.setGravity(f);
|
||||
}
|
||||
#elseif cpp
|
||||
public function setGravity(v: Vec4) {
|
||||
vec1.setX(v.x);
|
||||
vec1.setY(v.y);
|
||||
vec1.setZ(v.z);
|
||||
character.setGravity(vec1);
|
||||
}
|
||||
#end
|
||||
|
||||
public function setActivationState(newState: Int) {
|
||||
body.setActivationState(newState);
|
||||
}
|
||||
|
||||
public function setFriction(f: Float) {
|
||||
body.setFriction(f);
|
||||
body.setRollingFriction(f);
|
||||
this.friction = f;
|
||||
}
|
||||
|
||||
public function syncTransform() {
|
||||
var t = transform;
|
||||
t.buildMatrix();
|
||||
vec1.setX(t.worldx());
|
||||
vec1.setY(t.worldy());
|
||||
vec1.setZ(t.worldz());
|
||||
trans1.setOrigin(vec1);
|
||||
quat.fromMat(t.world);
|
||||
quat1.setX(quat.x);
|
||||
quat1.setY(quat.y);
|
||||
quat1.setZ(quat.z);
|
||||
quat1.setW(quat.w);
|
||||
trans1.setRotation(quat1);
|
||||
//body.setCenterOfMassTransform(trans); // ?
|
||||
if (currentScaleX != t.scale.x || currentScaleY != t.scale.y || currentScaleZ != t.scale.z) setScale(t.scale);
|
||||
activate();
|
||||
}
|
||||
|
||||
function setScale(v: Vec4) {
|
||||
currentScaleX = v.x;
|
||||
currentScaleY = v.y;
|
||||
currentScaleZ = v.z;
|
||||
vec1.setX(bodyScaleX * v.x);
|
||||
vec1.setY(bodyScaleY * v.y);
|
||||
vec1.setZ(bodyScaleZ * v.z);
|
||||
if (isConvexHull) shapeConvexHull.setLocalScaling(vec1);
|
||||
else shapeConvex.setLocalScaling(vec1);
|
||||
physics.world.updateSingleAabb(body);
|
||||
}
|
||||
|
||||
function addPointsToConvexHull(shape: bullet.Bt.ConvexHullShape, scale: Vec4, margin: Float) {
|
||||
var positions = cast(object, MeshObject).data.geom.positions.values;
|
||||
|
||||
var sx = scale.x * (1.0 - margin);
|
||||
var sy = scale.y * (1.0 - margin);
|
||||
var sz = scale.z * (1.0 - margin);
|
||||
|
||||
for (i in 0...Std.int(positions.length / 4)) {
|
||||
vec1.setX(positions[i * 3] * sx);
|
||||
vec1.setY(positions[i * 3 + 1] * sy);
|
||||
vec1.setZ(positions[i * 3 + 2] * sz);
|
||||
shape.addPoint(vec1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:enum abstract ControllerShape(Int) from Int to Int {
|
||||
var Box = 0;
|
||||
var Sphere = 1;
|
||||
var ConvexHull = 2;
|
||||
var Cone = 3;
|
||||
var Cylinder = 4;
|
||||
var Capsule = 5;
|
||||
}
|
||||
|
||||
@:enum abstract ControllerActivationState(Int) from Int to Int {
|
||||
var Active = 1;
|
||||
var NoDeactivation = 4;
|
||||
var NoSimulation = 5;
|
||||
}
|
||||
|
||||
#end
|
476
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx
Normal file
476
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsConstraint.hx
Normal file
@ -0,0 +1,476 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
import iron.math.Vec4;
|
||||
import iron.Scene;
|
||||
import iron.object.Object;
|
||||
#if lnx_bullet
|
||||
import Math;
|
||||
import iron.math.Quat;
|
||||
import leenkx.trait.physics.RigidBody;
|
||||
import leenkx.trait.physics.PhysicsWorld;
|
||||
|
||||
/**
|
||||
* A trait to add Bullet physics constraints
|
||||
**/
|
||||
class PhysicsConstraint extends iron.Trait {
|
||||
|
||||
static var nextId:Int = 0;
|
||||
public var id:Int = 0;
|
||||
|
||||
var physics: PhysicsWorld;
|
||||
var body1: Object;
|
||||
var body2: Object;
|
||||
var type: ConstraintType;
|
||||
public var disableCollisions: Bool;
|
||||
var breakingThreshold: Float;
|
||||
var limits: Array<Float>;
|
||||
public var con: bullet.Bt.TypedConstraint = null;
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1: bullet.Bt.Vector3;
|
||||
static var vec2: bullet.Bt.Vector3;
|
||||
static var vec3: bullet.Bt.Vector3;
|
||||
static var trans1: bullet.Bt.Transform;
|
||||
static var trans2: bullet.Bt.Transform;
|
||||
static var transt: bullet.Bt.Transform;
|
||||
|
||||
/**
|
||||
* Function to initialize physics constraint trait.
|
||||
*
|
||||
* @param object Pivot object to which this constraint trait will be added. The constraint limits are applied along the local axes of this object. This object need not
|
||||
* be a Rigid Body. Typically an `Empty` object may be used. Moving/rotating/parenting this pivot object has no effect once the constraint trait is added. Removing
|
||||
* the pivot object removes the constraint.
|
||||
*
|
||||
* @param body1 First rigid body to be constrained. This rigid body may be constrained by other constraints.
|
||||
*
|
||||
* @param body2 Second rigid body to be constrained. This rigid body may be constrained by other constraints.
|
||||
*
|
||||
* @param type Type of the constraint.
|
||||
*
|
||||
* @param disableCollisions Disable collisions between constrained objects.
|
||||
*
|
||||
* @param breakingThreshold Break the constraint if stress on this constraint exceeds this value. Set to 0 to make un-breakable.
|
||||
*
|
||||
* @param limits Constraint limits. This may be set before adding the trait to pivot object using the set limits functions.
|
||||
*
|
||||
**/
|
||||
public function new(body1: Object, body2: Object, type: ConstraintType, disableCollisions: Bool, breakingThreshold: Float, limits: Array<Float> = null) {
|
||||
super();
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
vec2 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
vec3 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
trans1 = new bullet.Bt.Transform();
|
||||
trans2 = new bullet.Bt.Transform();
|
||||
}
|
||||
|
||||
this.body1 = body1;
|
||||
this.body2 = body2;
|
||||
this.type = type;
|
||||
this.disableCollisions = disableCollisions;
|
||||
this.breakingThreshold = breakingThreshold;
|
||||
if(limits == null) limits = [for(i in 0...36) 0];
|
||||
this.limits = limits;
|
||||
|
||||
notifyOnInit(init);
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
physics = PhysicsWorld.active;
|
||||
var target1 = body1;
|
||||
var target2 = body2;
|
||||
|
||||
if (target1 == null || target2 == null) return;//no objects selected
|
||||
|
||||
var rb1: RigidBody = target1.getTrait(RigidBody);
|
||||
var rb2: RigidBody = target2.getTrait(RigidBody);
|
||||
|
||||
if (rb1 != null && rb1.ready && rb2 != null && rb2.ready) {//Check if rigid bodies are ready
|
||||
|
||||
var t = object.transform;
|
||||
var t1 = target1.transform;
|
||||
var t2 = target2.transform;
|
||||
|
||||
var frameT = t.world.clone();//Transform of pivot in world space
|
||||
|
||||
var frameInA = t1.world.clone();//Transform of rb1 in world space
|
||||
frameInA.getInverse(frameInA);//Inverse Transform of rb1 in world space
|
||||
frameT.multmat(frameInA);//Transform of pivot object in rb1 space
|
||||
frameInA = frameT.clone();//Frame In A
|
||||
|
||||
frameT = t.world.clone();//Transform of pivot in world space
|
||||
|
||||
var frameInB = t2.world.clone();//Transform of rb2 in world space
|
||||
frameInB.getInverse(frameInB);//Inverse Transform of rb2 in world space
|
||||
frameT.multmat(frameInB);//Transform of pivot object in rb2 space
|
||||
frameInB = frameT.clone();//Frame In B
|
||||
|
||||
var loc = new Vec4();
|
||||
var rot = new Quat();
|
||||
var scl = new Vec4();
|
||||
|
||||
frameInA.decompose(loc,rot,scl);
|
||||
trans1.setIdentity();
|
||||
vec1.setX(loc.x);
|
||||
vec1.setY(loc.y);
|
||||
vec1.setZ(loc.z);
|
||||
trans1.setOrigin(vec1);
|
||||
trans1.setRotation(new bullet.Bt.Quaternion(rot.x, rot.y, rot.z, rot.w));
|
||||
|
||||
frameInB.decompose(loc,rot,scl);
|
||||
trans2.setIdentity();
|
||||
vec2.setX(loc.x);
|
||||
vec2.setY(loc.y);
|
||||
vec2.setZ(loc.z);
|
||||
trans2.setOrigin(vec2);
|
||||
trans2.setRotation(new bullet.Bt.Quaternion(rot.x, rot.y, rot.z, rot.w));
|
||||
|
||||
if (type == Generic || type == Fixed) {
|
||||
#if hl
|
||||
var c = new bullet.Bt.Generic6DofConstraint(rb1.body, rb2.body, trans1, trans2, false);
|
||||
#else
|
||||
var c = bullet.Bt.Generic6DofConstraint.new2(rb1.body, rb2.body, trans1, trans2, false);
|
||||
#end
|
||||
if (type == Fixed) {
|
||||
vec1.setX(0);
|
||||
vec1.setY(0);
|
||||
vec1.setZ(0);
|
||||
c.setLinearLowerLimit(vec1);
|
||||
c.setLinearUpperLimit(vec1);
|
||||
c.setAngularLowerLimit(vec1);
|
||||
c.setAngularUpperLimit(vec1);
|
||||
}
|
||||
else if (type == ConstraintType.Generic) {
|
||||
if (limits[0] == 0) {
|
||||
limits[1] = 1.0;
|
||||
limits[2] = -1.0;
|
||||
}
|
||||
if (limits[3] == 0) {
|
||||
limits[4] = 1.0;
|
||||
limits[5] = -1.0;
|
||||
}
|
||||
if (limits[6] == 0) {
|
||||
limits[7] = 1.0;
|
||||
limits[8] = -1.0;
|
||||
}
|
||||
if (limits[9] == 0) {
|
||||
limits[10] = 1.0;
|
||||
limits[11] = -1.0;
|
||||
}
|
||||
if (limits[12] == 0) {
|
||||
limits[13] = 1.0;
|
||||
limits[14] = -1.0;
|
||||
}
|
||||
if (limits[15] == 0) {
|
||||
limits[16] = 1.0;
|
||||
limits[17] = -1.0;
|
||||
}
|
||||
vec1.setX(limits[1]);
|
||||
vec1.setY(limits[4]);
|
||||
vec1.setZ(limits[7]);
|
||||
c.setLinearLowerLimit(vec1);
|
||||
vec1.setX(limits[2]);
|
||||
vec1.setY(limits[5]);
|
||||
vec1.setZ(limits[8]);
|
||||
c.setLinearUpperLimit(vec1);
|
||||
vec1.setX(limits[10]);
|
||||
vec1.setY(limits[13]);
|
||||
vec1.setZ(limits[16]);
|
||||
c.setAngularLowerLimit(vec1);
|
||||
vec1.setX(limits[11]);
|
||||
vec1.setY(limits[14]);
|
||||
vec1.setZ(limits[17]);
|
||||
c.setAngularUpperLimit(vec1);
|
||||
}
|
||||
con = cast c;
|
||||
}
|
||||
else if (type == ConstraintType.GenericSpring){
|
||||
var c = new bullet.Bt.Generic6DofSpringConstraint(rb1.body, rb2.body, trans1, trans2, false);
|
||||
|
||||
if (limits[0] == 0) {
|
||||
limits[1] = 1.0;
|
||||
limits[2] = -1.0;
|
||||
}
|
||||
if (limits[3] == 0) {
|
||||
limits[4] = 1.0;
|
||||
limits[5] = -1.0;
|
||||
}
|
||||
if (limits[6] == 0) {
|
||||
limits[7] = 1.0;
|
||||
limits[8] = -1.0;
|
||||
}
|
||||
if (limits[9] == 0) {
|
||||
limits[10] = 1.0;
|
||||
limits[11] = -1.0;
|
||||
}
|
||||
if (limits[12] == 0) {
|
||||
limits[13] = 1.0;
|
||||
limits[14] = -1.0;
|
||||
}
|
||||
if (limits[15] == 0) {
|
||||
limits[16] = 1.0;
|
||||
limits[17] = -1.0;
|
||||
}
|
||||
vec1.setX(limits[1]);
|
||||
vec1.setY(limits[4]);
|
||||
vec1.setZ(limits[7]);
|
||||
c.setLinearLowerLimit(vec1);
|
||||
vec1.setX(limits[2]);
|
||||
vec1.setY(limits[5]);
|
||||
vec1.setZ(limits[8]);
|
||||
c.setLinearUpperLimit(vec1);
|
||||
vec1.setX(limits[10]);
|
||||
vec1.setY(limits[13]);
|
||||
vec1.setZ(limits[16]);
|
||||
c.setAngularLowerLimit(vec1);
|
||||
vec1.setX(limits[11]);
|
||||
vec1.setY(limits[14]);
|
||||
vec1.setZ(limits[17]);
|
||||
c.setAngularUpperLimit(vec1);
|
||||
if (limits[18] != 0) {
|
||||
c.enableSpring(0, true);
|
||||
c.setStiffness(0, limits[19]);
|
||||
c.setDamping(0, limits[20]);
|
||||
}
|
||||
else {
|
||||
c.enableSpring(0, false);
|
||||
}
|
||||
if (limits[21] != 0) {
|
||||
c.enableSpring(1, true);
|
||||
c.setStiffness(1, limits[22]);
|
||||
c.setDamping(1, limits[23]);
|
||||
}
|
||||
else {
|
||||
c.enableSpring(1, false);
|
||||
}
|
||||
if (limits[24] != 0) {
|
||||
c.enableSpring(2, true);
|
||||
c.setStiffness(2, limits[25]);
|
||||
c.setDamping(2, limits[26]);
|
||||
}
|
||||
else {
|
||||
c.enableSpring(2, false);
|
||||
}
|
||||
if (limits[27] != 0) {
|
||||
c.enableSpring(3, true);
|
||||
c.setStiffness(3, limits[28]);
|
||||
c.setDamping(3, limits[29]);
|
||||
}
|
||||
else {
|
||||
c.enableSpring(3, false);
|
||||
}
|
||||
if (limits[30] != 0) {
|
||||
c.enableSpring(4, true);
|
||||
c.setStiffness(4, limits[31]);
|
||||
c.setDamping(4, limits[32]);
|
||||
}
|
||||
else {
|
||||
c.enableSpring(4, false);
|
||||
}
|
||||
if (limits[33] != 0) {
|
||||
c.enableSpring(5, true);
|
||||
c.setStiffness(5, limits[34]);
|
||||
c.setDamping(5, limits[35]);
|
||||
}
|
||||
else {
|
||||
c.enableSpring(5, false);
|
||||
}
|
||||
con = cast c;
|
||||
|
||||
}
|
||||
else if (type == ConstraintType.Point){
|
||||
var c = new bullet.Bt.Point2PointConstraint(rb1.body, rb2.body, vec1, vec2);
|
||||
con = cast c;
|
||||
}
|
||||
else if (type == ConstraintType.Hinge) {
|
||||
var axis = vec3;
|
||||
var _softness: Float = 0.9;
|
||||
var _biasFactor: Float = 0.3;
|
||||
var _relaxationFactor: Float = 1.0;
|
||||
|
||||
axis.setX(t.up().x);
|
||||
axis.setY(t.up().y);
|
||||
axis.setZ(t.up().z);
|
||||
|
||||
var c = new bullet.Bt.HingeConstraint(rb1.body, rb2.body, vec1, vec2, axis, axis, false);
|
||||
|
||||
if (limits[0] != 0) {
|
||||
c.setLimit(limits[1], limits[2], _softness, _biasFactor, _relaxationFactor);
|
||||
}
|
||||
|
||||
con = cast c;
|
||||
}
|
||||
else if (type == ConstraintType.Slider) {
|
||||
var c = new bullet.Bt.SliderConstraint(rb1.body, rb2.body, trans1, trans2, true);
|
||||
|
||||
if (limits[0] != 0) {
|
||||
c.setLowerLinLimit(limits[1]);
|
||||
c.setUpperLinLimit(limits[2]);
|
||||
}
|
||||
|
||||
con = cast c;
|
||||
}
|
||||
else if (type == ConstraintType.Piston) {
|
||||
var c = new bullet.Bt.SliderConstraint(rb1.body, rb2.body, trans1, trans2, true);
|
||||
|
||||
if (limits[0] != 0) {
|
||||
c.setLowerLinLimit(limits[1]);
|
||||
c.setUpperLinLimit(limits[2]);
|
||||
}
|
||||
if (limits[3] != 0) {
|
||||
c.setLowerAngLimit(limits[4]);
|
||||
c.setUpperAngLimit(limits[5]);
|
||||
}
|
||||
else {
|
||||
c.setLowerAngLimit(1);
|
||||
c.setUpperAngLimit(-1);
|
||||
|
||||
}
|
||||
con = cast c;
|
||||
}
|
||||
|
||||
if (breakingThreshold > 0) con.setBreakingImpulseThreshold(breakingThreshold);
|
||||
|
||||
physics.addPhysicsConstraint(this);
|
||||
|
||||
id = nextId;
|
||||
nextId++;
|
||||
|
||||
|
||||
notifyOnRemove(removeFromWorld);
|
||||
}
|
||||
else this.remove(); // Rigid body not initialized yet. Remove trait without adding constraint
|
||||
}
|
||||
|
||||
public function removeFromWorld() {
|
||||
physics.removePhysicsConstraint(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set constraint limits when using Hinge constraint. May be used after initalizing this trait but before adding it
|
||||
* to the pivot object
|
||||
**/
|
||||
public function setHingeConstraintLimits(angLimit: Bool, lowerAngLimit: Float, upperAngLimit: Float) {
|
||||
|
||||
angLimit? limits[0] = 1 : limits[0] = 0;
|
||||
|
||||
limits[1] = lowerAngLimit * (Math.PI/ 180);
|
||||
limits[2] = upperAngLimit * (Math.PI/ 180);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set constraint limits when using Slider constraint. May be used after initalizing this trait but before adding it
|
||||
* to the pivot object
|
||||
**/
|
||||
public function setSliderConstraintLimits(linLimit: Bool, lowerLinLimit: Float, upperLinLimit: Float) {
|
||||
|
||||
linLimit? limits[0] = 1 : limits[0] = 0;
|
||||
|
||||
limits[1] = lowerLinLimit;
|
||||
limits[2] = upperLinLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set constraint limits when using Piston constraint. May be used after initalizing this trait but before adding it
|
||||
* to the pivot object
|
||||
**/
|
||||
public function setPistonConstraintLimits(linLimit: Bool, lowerLinLimit: Float, upperLinLimit: Float, angLimit: Bool, lowerAngLimit: Float, upperAngLimit: Float) {
|
||||
|
||||
linLimit? limits[0] = 1 : limits[0] = 0;
|
||||
|
||||
limits[1] = lowerLinLimit;
|
||||
limits[2] = upperLinLimit;
|
||||
|
||||
angLimit? limits[3] = 1 : limits[3] = 0;
|
||||
|
||||
limits[4] = lowerAngLimit * (Math.PI/ 180);
|
||||
limits[5] = upperAngLimit * (Math.PI/ 180);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set customized constraint limits when using Generic/ Generic Spring constraint. May be used after initalizing this trait but before adding it
|
||||
* to the pivot object. Multiple constarints may be set by calling this function with different parameters.
|
||||
**/
|
||||
public function setGenericConstraintLimits(setLimit: Bool = false, lowerLimit: Float = 1.0, upperLimit: Float = -1.0, axis: ConstraintAxis = X, isAngular: Bool = false) {
|
||||
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
var radian = (Math.PI/ 180);
|
||||
|
||||
switch (axis){
|
||||
case X:
|
||||
i = 0;
|
||||
case Y:
|
||||
i = 3;
|
||||
case Z:
|
||||
i = 6;
|
||||
}
|
||||
|
||||
isAngular? j = 9 : j = 0;
|
||||
|
||||
isAngular? radian = (Math.PI/ 180) : radian = 1;
|
||||
|
||||
setLimit? limits[i + j] = 1 : 0;
|
||||
limits[i + j + 1] = lowerLimit * radian;
|
||||
limits[i + j + 2] = upperLimit * radian;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set customized spring parameters when using Generic/ Generic Spring constraint. May be used after initalizing this trait but before adding it
|
||||
* to the pivot object. Multiple parameters to different axes may be set by calling this function with different parameters.
|
||||
**/
|
||||
public function setSpringParams(setSpring: Bool = false, stiffness: Float = 10.0, damping: Float = 0.5, axis: ConstraintAxis = X, isAngular: Bool = false) {
|
||||
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
|
||||
switch (axis){
|
||||
case X:
|
||||
i = 18;
|
||||
case Y:
|
||||
i = 21;
|
||||
case Z:
|
||||
i = 24;
|
||||
}
|
||||
|
||||
isAngular? j = 9 : j = 0;
|
||||
|
||||
setSpring? limits[i + j] = 1 : 0;
|
||||
limits[i + j + 1] = stiffness;
|
||||
limits[i + j + 2] = damping;
|
||||
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(con);
|
||||
#else
|
||||
con.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@:enum abstract ConstraintType(Int) from Int to Int {
|
||||
var Fixed = 0;
|
||||
var Point = 1;
|
||||
var Hinge = 2;
|
||||
var Slider = 3;
|
||||
var Piston = 4;
|
||||
var Generic = 5;
|
||||
var GenericSpring = 6;
|
||||
var Motor = 7;
|
||||
}
|
||||
|
||||
@:enum abstract ConstraintAxis(Int) from Int to Int {
|
||||
var X = 0;
|
||||
var Y = 1;
|
||||
var Z = 2;
|
||||
}
|
||||
|
||||
#end
|
@ -0,0 +1,60 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
import iron.Scene;
|
||||
import iron.object.Object;
|
||||
#if lnx_bullet
|
||||
|
||||
/**
|
||||
* A helper trait to add physics constraints when exporting via Blender.
|
||||
* This trait will be automatically removed once the constraint is added. Note that this trait
|
||||
* uses object names instead of object reference.
|
||||
**/
|
||||
class PhysicsConstraintExportHelper extends iron.Trait {
|
||||
|
||||
var body1: String;
|
||||
var body2: String;
|
||||
var type: Int;
|
||||
var disableCollisions: Bool;
|
||||
var breakingThreshold: Float;
|
||||
var limits: Array<Float>;
|
||||
var constraintAdded: Bool = false;
|
||||
var relativeConstraint: Bool = false;
|
||||
|
||||
public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, relatieConstraint: Bool = false, limits: Array<Float> = null) {
|
||||
super();
|
||||
|
||||
this.body1 = body1;
|
||||
this.body2 = body2;
|
||||
this.type = type;
|
||||
this.disableCollisions = disableCollisions;
|
||||
this.breakingThreshold = breakingThreshold;
|
||||
this.relativeConstraint = relatieConstraint;
|
||||
this.limits = limits;
|
||||
notifyOnInit(init);
|
||||
notifyOnUpdate(update);
|
||||
}
|
||||
|
||||
function init() {
|
||||
var target1;
|
||||
var target2;
|
||||
|
||||
if(relativeConstraint) {
|
||||
|
||||
target1 = object.parent.getChild(body1);
|
||||
target2 = object.parent.getChild(body2);
|
||||
}
|
||||
else {
|
||||
|
||||
target1 = Scene.active.getChild(body1);
|
||||
target2 = Scene.active.getChild(body2);
|
||||
}
|
||||
object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits));
|
||||
constraintAdded = true;
|
||||
}
|
||||
|
||||
function update() {
|
||||
if(constraintAdded) this.remove();
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
187
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx
Normal file
187
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsHook.hx
Normal file
@ -0,0 +1,187 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.Trait;
|
||||
import iron.object.Object;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Transform;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.SceneFormat;
|
||||
import leenkx.trait.physics.RigidBody;
|
||||
import leenkx.trait.physics.PhysicsWorld;
|
||||
|
||||
class PhysicsHook extends Trait {
|
||||
|
||||
var target: Object;
|
||||
var targetName: String;
|
||||
var targetTransform: Transform;
|
||||
var verts: Array<Float>;
|
||||
|
||||
var constraint: bullet.Bt.Generic6DofConstraint = null;
|
||||
|
||||
#if lnx_physics_soft
|
||||
var hookRB: bullet.Bt.RigidBody = null;
|
||||
#end
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1: bullet.Bt.Vector3;
|
||||
static var quat1: bullet.Bt.Quaternion;
|
||||
static var trans1: bullet.Bt.Transform;
|
||||
static var trans2: bullet.Bt.Transform;
|
||||
static var quat = new Quat();
|
||||
|
||||
public function new(targetName: String, verts: Array<Float>) {
|
||||
super();
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 0);
|
||||
trans1 = new bullet.Bt.Transform();
|
||||
trans2 = new bullet.Bt.Transform();
|
||||
}
|
||||
|
||||
this.targetName = targetName;
|
||||
this.verts = verts;
|
||||
|
||||
iron.Scene.active.notifyOnInit(function() {
|
||||
notifyOnInit(init);
|
||||
notifyOnUpdate(update);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Hook to empty axes
|
||||
target = targetName != "" ? iron.Scene.active.getChild(targetName) : null;
|
||||
targetTransform = target != null ? target.transform : iron.Scene.global.transform;
|
||||
|
||||
var physics = PhysicsWorld.active;
|
||||
|
||||
// Soft body hook
|
||||
#if lnx_physics_soft
|
||||
var sb: SoftBody = object.getTrait(SoftBody);
|
||||
if (sb != null && sb.ready) {
|
||||
|
||||
// Place static rigid body at target location
|
||||
trans1.setIdentity();
|
||||
vec1.setX(targetTransform.loc.x);
|
||||
vec1.setY(targetTransform.loc.y);
|
||||
vec1.setZ(targetTransform.loc.z);
|
||||
trans1.setOrigin(vec1);
|
||||
quat1.setX(targetTransform.rot.x);
|
||||
quat1.setY(targetTransform.rot.y);
|
||||
quat1.setZ(targetTransform.rot.z);
|
||||
quat1.setW(targetTransform.rot.w);
|
||||
trans1.setRotation(quat1);
|
||||
var centerOfMassOffset = trans2;
|
||||
centerOfMassOffset.setIdentity();
|
||||
var mass = 0.0;
|
||||
var motionState = new bullet.Bt.DefaultMotionState(trans1, centerOfMassOffset);
|
||||
var inertia = vec1;
|
||||
inertia.setX(0);
|
||||
inertia.setY(0);
|
||||
inertia.setZ(0);
|
||||
var shape = new bullet.Bt.SphereShape(0.01);
|
||||
shape.calculateLocalInertia(mass, inertia);
|
||||
var bodyCI = new bullet.Bt.RigidBodyConstructionInfo(mass, motionState, shape, inertia);
|
||||
hookRB = new bullet.Bt.RigidBody(bodyCI);
|
||||
|
||||
#if js
|
||||
var nodes = sb.body.get_m_nodes();
|
||||
#else
|
||||
var nodes = sb.body.m_nodes;
|
||||
#end
|
||||
|
||||
for (i in 0...nodes.size()) {
|
||||
var node = nodes.at(i);
|
||||
#if js
|
||||
var nodePos = node.get_m_x();
|
||||
#else
|
||||
var nodePos = node.m_x;
|
||||
#end
|
||||
|
||||
// Find nodes at marked vertex group locations
|
||||
var numVerts = Std.int(verts.length / 3);
|
||||
for (j in 0...numVerts) {
|
||||
var x = verts[j * 3] + sb.vertOffsetX + sb.object.transform.loc.x;
|
||||
var y = verts[j * 3 + 1] + sb.vertOffsetY + sb.object.transform.loc.y;
|
||||
var z = verts[j * 3 + 2] + sb.vertOffsetZ + sb.object.transform.loc.z;
|
||||
|
||||
// Anchor node to rigid body
|
||||
if (Math.abs(nodePos.x() - x) < 0.01 && Math.abs(nodePos.y() - y) < 0.01 && Math.abs(nodePos.z() - z) < 0.01) {
|
||||
sb.body.appendAnchor(i, hookRB, false, 1.0 / numVerts);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
// Rigid body hook
|
||||
var rb1: RigidBody = object.getTrait(RigidBody);
|
||||
if (rb1 != null && rb1.ready) {
|
||||
trans1.setIdentity();
|
||||
vec1.setX(targetTransform.worldx() - object.transform.worldx());
|
||||
vec1.setY(targetTransform.worldy() - object.transform.worldy());
|
||||
vec1.setZ(targetTransform.worldz() - object.transform.worldz());
|
||||
trans1.setOrigin(vec1);
|
||||
constraint = new bullet.Bt.Generic6DofConstraint(rb1.body, trans1, false);
|
||||
vec1.setX(0);
|
||||
vec1.setY(0);
|
||||
vec1.setZ(0);
|
||||
constraint.setLinearLowerLimit(vec1);
|
||||
constraint.setLinearUpperLimit(vec1);
|
||||
vec1.setX(-10);
|
||||
vec1.setY(-10);
|
||||
vec1.setZ(-10);
|
||||
constraint.setAngularLowerLimit(vec1);
|
||||
vec1.setX(10);
|
||||
vec1.setY(10);
|
||||
vec1.setZ(10);
|
||||
constraint.setAngularUpperLimit(vec1);
|
||||
physics.world.addConstraint(constraint, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rigid body or soft body not initialized yet
|
||||
notifyOnInit(init);
|
||||
}
|
||||
|
||||
function update() {
|
||||
#if lnx_physics_soft
|
||||
if (hookRB != null) {
|
||||
trans1.setIdentity();
|
||||
vec1.setX(targetTransform.world.getLoc().x);
|
||||
vec1.setY(targetTransform.world.getLoc().y);
|
||||
vec1.setZ(targetTransform.world.getLoc().z);
|
||||
trans1.setOrigin(vec1);
|
||||
quat.fromMat(targetTransform.world);
|
||||
quat1.setX(quat.x);
|
||||
quat1.setY(quat.y);
|
||||
quat1.setZ(quat.z);
|
||||
quat1.setW(quat.w);
|
||||
trans1.setRotation(quat1);
|
||||
hookRB.setWorldTransform(trans1);
|
||||
}
|
||||
#end
|
||||
|
||||
// if (constraint != null) {
|
||||
// vec1.setX(targetTransform.worldx() - object.transform.worldx());
|
||||
// vec1.setY(targetTransform.worldy() - object.transform.worldy());
|
||||
// vec1.setZ(targetTransform.worldz() - object.transform.worldz());
|
||||
// var pivot = vec1;
|
||||
// #if js
|
||||
// constraint.getFrameOffsetA().setOrigin(pivot);
|
||||
// #elseif cpp
|
||||
// constraint.setFrameOffsetAOrigin(pivot);
|
||||
// #end
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#end
|
673
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx
Normal file
673
leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx
Normal file
@ -0,0 +1,673 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
import iron.Trait;
|
||||
import iron.system.Time;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.RayCaster;
|
||||
|
||||
class Hit {
|
||||
|
||||
public var rb: RigidBody;
|
||||
public var pos: Vec4;
|
||||
public var normal: Vec4;
|
||||
public function new(rb: RigidBody, pos: Vec4, normal: Vec4){
|
||||
this.rb = rb;
|
||||
this.pos = pos;
|
||||
this.normal = normal;
|
||||
}
|
||||
}
|
||||
|
||||
class ConvexHit {
|
||||
public var pos: Vec4;
|
||||
public var normal: Vec4;
|
||||
public var hitFraction: Float;
|
||||
public function new(pos: Vec4, normal: Vec4, hitFraction: Float){
|
||||
this.pos = pos;
|
||||
this.normal = normal;
|
||||
this.hitFraction = hitFraction;
|
||||
}
|
||||
}
|
||||
|
||||
class ContactPair {
|
||||
|
||||
public var a: Int;
|
||||
public var b: Int;
|
||||
public var posA: Vec4;
|
||||
public var posB: Vec4;
|
||||
public var normOnB: Vec4;
|
||||
public var impulse: Float;
|
||||
public var distance: Float;
|
||||
public function new(a: Int, b: Int) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
}
|
||||
|
||||
class PhysicsWorld extends Trait {
|
||||
|
||||
public static var active: PhysicsWorld = null;
|
||||
static var sceneRemoved = false;
|
||||
|
||||
#if lnx_physics_soft
|
||||
public var world: bullet.Bt.SoftRigidDynamicsWorld;
|
||||
#else
|
||||
public var world: bullet.Bt.DiscreteDynamicsWorld;
|
||||
#end
|
||||
|
||||
var dispatcher: bullet.Bt.CollisionDispatcher;
|
||||
var gimpactRegistered = false;
|
||||
var contacts: Array<ContactPair>;
|
||||
var preUpdates: Array<Void->Void> = null;
|
||||
public var rbMap: Map<Int, RigidBody>;
|
||||
public var conMap: Map<Int, PhysicsConstraint>;
|
||||
public var timeScale = 1.0;
|
||||
var maxSteps = 1;
|
||||
public var solverIterations = 10;
|
||||
public var hitPointWorld = new Vec4();
|
||||
public var hitNormalWorld = new Vec4();
|
||||
public var convexHitPointWorld = new Vec4();
|
||||
public var convexHitNormalWorld = new Vec4();
|
||||
var pairCache: Bool = false;
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1: bullet.Bt.Vector3 = null;
|
||||
static var vec2: bullet.Bt.Vector3 = null;
|
||||
static var quat1: bullet.Bt.Quaternion = null;
|
||||
static var transform1: bullet.Bt.Transform = null;
|
||||
static var transform2: bullet.Bt.Transform = null;
|
||||
|
||||
var debugDrawHelper: DebugDrawHelper = null;
|
||||
|
||||
#if lnx_debug
|
||||
public static var physTime = 0.0;
|
||||
#end
|
||||
|
||||
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug) {
|
||||
super();
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
vec2 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
transform1 = new bullet.Bt.Transform();
|
||||
transform2 = new bullet.Bt.Transform();
|
||||
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 1.0);
|
||||
}
|
||||
|
||||
// Scene spawn
|
||||
if (active != null && !sceneRemoved) return;
|
||||
sceneRemoved = false;
|
||||
|
||||
this.timeScale = timeScale;
|
||||
this.maxSteps = maxSteps;
|
||||
this.solverIterations = solverIterations;
|
||||
|
||||
// First scene
|
||||
if (active == null) {
|
||||
createPhysics();
|
||||
}
|
||||
else { // Scene switch
|
||||
world = active.world;
|
||||
dispatcher = active.dispatcher;
|
||||
gimpactRegistered = active.gimpactRegistered;
|
||||
}
|
||||
|
||||
contacts = [];
|
||||
rbMap = new Map();
|
||||
conMap = new Map();
|
||||
active = this;
|
||||
|
||||
// Ensure physics are updated first in the lateUpdate list
|
||||
_lateUpdate = [lateUpdate];
|
||||
@:privateAccess iron.App.traitLateUpdates.insert(0, lateUpdate);
|
||||
|
||||
setDebugDrawMode(debugDrawMode);
|
||||
|
||||
iron.Scene.active.notifyOnRemove(function() {
|
||||
sceneRemoved = true;
|
||||
});
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
for (rb in active.rbMap) removeRigidBody(rb);
|
||||
}
|
||||
|
||||
function createPhysics() {
|
||||
var broadphase = new bullet.Bt.DbvtBroadphase();
|
||||
|
||||
#if lnx_physics_soft
|
||||
var collisionConfiguration = new bullet.Bt.SoftBodyRigidBodyCollisionConfiguration();
|
||||
#else
|
||||
var collisionConfiguration = new bullet.Bt.DefaultCollisionConfiguration();
|
||||
#end
|
||||
|
||||
dispatcher = new bullet.Bt.CollisionDispatcher(collisionConfiguration);
|
||||
var solver = new bullet.Bt.SequentialImpulseConstraintSolver();
|
||||
|
||||
var g = iron.Scene.active.raw.gravity;
|
||||
var gravity = g == null ? new Vec4(0, 0, -9.81) : new Vec4(g[0], g[1], g[2]);
|
||||
|
||||
#if lnx_physics_soft
|
||||
var softSolver = new bullet.Bt.DefaultSoftBodySolver();
|
||||
world = new bullet.Bt.SoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softSolver);
|
||||
vec1.setX(gravity.x);
|
||||
vec1.setY(gravity.y);
|
||||
vec1.setZ(gravity.z);
|
||||
#if js
|
||||
world.getWorldInfo().set_m_gravity(vec1);
|
||||
#else
|
||||
world.getWorldInfo().m_gravity = vec1;
|
||||
#end
|
||||
#else
|
||||
world = new bullet.Bt.DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
|
||||
#end
|
||||
|
||||
setGravity(gravity);
|
||||
}
|
||||
|
||||
public function setGravity(v: Vec4) {
|
||||
vec1.setValue(v.x, v.y, v.z);
|
||||
world.setGravity(vec1);
|
||||
}
|
||||
|
||||
public function getGravity(): Vec4{
|
||||
var g = world.getGravity();
|
||||
return (new Vec4(g.x(), g.y(), g.z()));
|
||||
}
|
||||
|
||||
public function addRigidBody(body: RigidBody) {
|
||||
#if js
|
||||
world.addRigidBodyToGroup(body.body, body.group, body.mask);
|
||||
#else
|
||||
world.addRigidBody(body.body, body.group, body.mask);
|
||||
#end
|
||||
rbMap.set(body.id, body);
|
||||
}
|
||||
|
||||
public function addPhysicsConstraint(constraint: PhysicsConstraint) {
|
||||
world.addConstraint(constraint.con, constraint.disableCollisions);
|
||||
conMap.set(constraint.id, constraint);
|
||||
}
|
||||
|
||||
public function removeRigidBody(body: RigidBody) {
|
||||
if (body.destroyed) return;
|
||||
body.destroyed = true;
|
||||
if (world != null) world.removeRigidBody(body.body);
|
||||
rbMap.remove(body.id);
|
||||
body.delete();
|
||||
}
|
||||
|
||||
public function removePhysicsConstraint(constraint: PhysicsConstraint) {
|
||||
if(world != null) world.removeConstraint(constraint.con);
|
||||
conMap.remove(constraint.id);
|
||||
constraint.delete();
|
||||
}
|
||||
|
||||
// public function addKinematicCharacterController(controller:KinematicCharacterController) {
|
||||
// if (!pairCache){ // Only create PairCache if needed
|
||||
// world.getPairCache().setInternalGhostPairCallback(BtGhostPairCallbackPointer.create());
|
||||
// pairCache = true;
|
||||
// }
|
||||
// world.addAction(controller.character);
|
||||
// world.addCollisionObjectToGroup(controller.body, controller.group, controller.group);
|
||||
// }
|
||||
|
||||
// public function removeKinematicCharacterController(controller:KinematicCharacterController) {
|
||||
// if (world != null) {
|
||||
// world.removeCollisionObject(controller.body);
|
||||
// world.removeAction(controller.character);
|
||||
// }
|
||||
// #if js
|
||||
// bullet.Bt.Ammo.destroy(controller.body);
|
||||
// #else
|
||||
// var cbody = controller.body;
|
||||
// untyped __cpp__("delete cbody");
|
||||
// #end
|
||||
// }
|
||||
|
||||
/**
|
||||
Used to get intersecting rigid bodies with the passed in RigidBody as reference. Often used when checking for object collisions.
|
||||
@param body The passed in RigidBody to be checked for intersecting rigid bodies.
|
||||
@return `Array<RigidBody>`
|
||||
**/
|
||||
public function getContacts(body: RigidBody): Array<RigidBody> {
|
||||
if (contacts.length == 0) return null;
|
||||
var res: Array<RigidBody> = [];
|
||||
for (i in 0...contacts.length) {
|
||||
var c = contacts[i];
|
||||
var rb = null;
|
||||
|
||||
#if js
|
||||
if (c.a == untyped body.body.userIndex) rb = rbMap.get(c.b);
|
||||
else if (c.b == untyped body.body.userIndex) rb = rbMap.get(c.a);
|
||||
|
||||
#else
|
||||
var ob: bullet.Bt.CollisionObject = body.body;
|
||||
if (c.a == ob.getUserIndex()) rb = rbMap.get(c.b);
|
||||
else if (c.b == ob.getUserIndex()) rb = rbMap.get(c.a);
|
||||
#end
|
||||
|
||||
if (rb != null && res.indexOf(rb) == -1) res.push(rb);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public function getContactPairs(body: RigidBody): Array<ContactPair> {
|
||||
if (contacts.length == 0) return null;
|
||||
var res: Array<ContactPair> = [];
|
||||
for (i in 0...contacts.length) {
|
||||
var c = contacts[i];
|
||||
#if js
|
||||
|
||||
if (c.a == untyped body.body.userIndex) res.push(c);
|
||||
else if (c.b == untyped body.body.userIndex) res.push(c);
|
||||
|
||||
#else
|
||||
|
||||
var ob: bullet.Bt.CollisionObject = body.body;
|
||||
if (c.a == ob.getUserIndex()) res.push(c);
|
||||
else if (c.b == ob.getUserIndex()) res.push(c);
|
||||
|
||||
#end
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public function findBody(id: Int): RigidBody{
|
||||
var rb = rbMap.get(id);
|
||||
return rb;
|
||||
}
|
||||
|
||||
function lateUpdate() {
|
||||
var t = Time.delta * timeScale;
|
||||
if (t == 0.0) return; // Simulation paused
|
||||
|
||||
#if lnx_debug
|
||||
var startTime = kha.Scheduler.realTime();
|
||||
#end
|
||||
|
||||
if (preUpdates != null) for (f in preUpdates) f();
|
||||
|
||||
//Bullet physics fixed timescale
|
||||
var fixedTime = 1.0 / 60;
|
||||
|
||||
//This condition must be satisfied to not loose time
|
||||
var currMaxSteps = t < (fixedTime * maxSteps) ? maxSteps : 1;
|
||||
|
||||
world.stepSimulation(t, currMaxSteps, fixedTime);
|
||||
updateContacts();
|
||||
|
||||
for (rb in rbMap) @:privateAccess rb.physicsUpdate();
|
||||
|
||||
#if lnx_debug
|
||||
physTime = kha.Scheduler.realTime() - startTime;
|
||||
#end
|
||||
}
|
||||
|
||||
function updateContacts() {
|
||||
contacts.resize(0);
|
||||
|
||||
var disp: bullet.Bt.Dispatcher = dispatcher;
|
||||
var numManifolds = disp.getNumManifolds();
|
||||
|
||||
for (i in 0...numManifolds) {
|
||||
var contactManifold = disp.getManifoldByIndexInternal(i);
|
||||
#if js
|
||||
var body0 = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(contactManifold.getBody0());
|
||||
var body1 = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(contactManifold.getBody1());
|
||||
#else
|
||||
var body0: bullet.Bt.CollisionObject = contactManifold.getBody0();
|
||||
var body1: bullet.Bt.CollisionObject = contactManifold.getBody1();
|
||||
#end
|
||||
|
||||
var numContacts = contactManifold.getNumContacts();
|
||||
for (j in 0...numContacts) {
|
||||
|
||||
var pt = contactManifold.getContactPoint(j);
|
||||
var posA: bullet.Bt.Vector3 = null;
|
||||
var posB: bullet.Bt.Vector3 = null;
|
||||
var nor: bullet.Bt.Vector3 = null;
|
||||
var cp: ContactPair = null;
|
||||
|
||||
#if js
|
||||
posA = pt.get_m_positionWorldOnA();
|
||||
posB = pt.get_m_positionWorldOnB();
|
||||
nor = pt.get_m_normalWorldOnB();
|
||||
cp = new ContactPair(untyped body0.userIndex, untyped body1.userIndex);
|
||||
#else
|
||||
posA = pt.m_positionWorldOnA;
|
||||
posB = pt.m_positionWorldOnB;
|
||||
nor = pt.m_normalWorldOnB;
|
||||
cp = new ContactPair(body0.getUserIndex(), body1.getUserIndex());
|
||||
#end
|
||||
|
||||
cp.posA = new Vec4(posA.x(), posA.y(), posA.z());
|
||||
cp.posB = new Vec4(posB.x(), posB.y(), posB.z());
|
||||
cp.normOnB = new Vec4(nor.x(), nor.y(), nor.z());
|
||||
cp.impulse = pt.getAppliedImpulse();
|
||||
cp.distance = pt.getDistance();
|
||||
contacts.push(cp);
|
||||
|
||||
#if hl
|
||||
pt.delete();
|
||||
posA.delete();
|
||||
posB.delete();
|
||||
nor.delete();
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function pickClosest(inputX: Float, inputY: Float, group: Int = 0x00000001, mask = 0xFFFFFFFF): RigidBody {
|
||||
var camera = iron.Scene.active.camera;
|
||||
var start = new Vec4();
|
||||
var end = new Vec4();
|
||||
RayCaster.getDirection(start, end, inputX, inputY, camera);
|
||||
var hit = rayCast(camera.transform.world.getLoc(), end, group, mask);
|
||||
var rb = (hit != null) ? hit.rb : null;
|
||||
return rb;
|
||||
}
|
||||
|
||||
public function rayCast(from: Vec4, to: Vec4, group: Int = 0x00000001, mask = 0xFFFFFFFF): Hit {
|
||||
var rayFrom = vec1;
|
||||
var rayTo = vec2;
|
||||
rayFrom.setValue(from.x, from.y, from.z);
|
||||
rayTo.setValue(to.x, to.y, to.z);
|
||||
|
||||
var rayCallback = new bullet.Bt.ClosestRayResultCallback(rayFrom, rayTo);
|
||||
#if js
|
||||
rayCallback.set_m_collisionFilterGroup(group);
|
||||
rayCallback.set_m_collisionFilterMask(mask);
|
||||
#elseif (cpp || hl)
|
||||
rayCallback.m_collisionFilterGroup = group;
|
||||
rayCallback.m_collisionFilterMask = mask;
|
||||
#end
|
||||
var worldDyn: bullet.Bt.DynamicsWorld = world;
|
||||
var worldCol: bullet.Bt.CollisionWorld = worldDyn;
|
||||
worldCol.rayTest(rayFrom, rayTo, rayCallback);
|
||||
var rb: RigidBody = null;
|
||||
var hitInfo: Hit = null;
|
||||
|
||||
var rc: bullet.Bt.RayResultCallback = rayCallback;
|
||||
if (rc.hasHit()) {
|
||||
#if js
|
||||
var co = rayCallback.get_m_collisionObject();
|
||||
var body = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(co);
|
||||
var hit = rayCallback.get_m_hitPointWorld();
|
||||
hitPointWorld.set(hit.x(), hit.y(), hit.z());
|
||||
var norm = rayCallback.get_m_hitNormalWorld();
|
||||
hitNormalWorld.set(norm.x(), norm.y(), norm.z());
|
||||
rb = rbMap.get(untyped body.userIndex);
|
||||
hitInfo = new Hit(rb, hitPointWorld, hitNormalWorld);
|
||||
#elseif (cpp || hl)
|
||||
var hit = rayCallback.m_hitPointWorld;
|
||||
hitPointWorld.set(hit.x(), hit.y(), hit.z());
|
||||
var norm = rayCallback.m_hitNormalWorld;
|
||||
hitNormalWorld.set(norm.x(), norm.y(), norm.z());
|
||||
rb = rbMap.get(rayCallback.m_collisionObject.getUserIndex());
|
||||
hitInfo = new Hit(rb, hitPointWorld, hitNormalWorld);
|
||||
#end
|
||||
}
|
||||
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(rayCallback);
|
||||
#else
|
||||
rayCallback.delete();
|
||||
#end
|
||||
|
||||
return hitInfo;
|
||||
}
|
||||
|
||||
public function convexSweepTest(rb: RigidBody, from: Vec4, to: Vec4, rotation: Quat, group: Int = 0x00000001, mask = 0xFFFFFFFF): ConvexHit {
|
||||
var transformFrom = transform1;
|
||||
var transformTo = transform2;
|
||||
transformFrom.setIdentity();
|
||||
transformTo.setIdentity();
|
||||
|
||||
vec1.setValue(from.x, from.y, from.z);
|
||||
transformFrom.setOrigin(vec1);
|
||||
quat1.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
|
||||
transformFrom.setRotation(quat1);
|
||||
|
||||
vec2.setValue(to.x, to.y, to.z);
|
||||
transformTo.setOrigin(vec2);
|
||||
quat1.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
|
||||
transformFrom.setRotation(quat1);
|
||||
|
||||
var convexCallback = new bullet.Bt.ClosestConvexResultCallback(vec1, vec2);
|
||||
#if js
|
||||
convexCallback.set_m_collisionFilterGroup(group);
|
||||
convexCallback.set_m_collisionFilterMask(mask);
|
||||
#elseif (cpp || hl)
|
||||
convexCallback.m_collisionFilterGroup = group;
|
||||
convexCallback.m_collisionFilterMask = mask;
|
||||
#end
|
||||
var worldDyn: bullet.Bt.DynamicsWorld = world;
|
||||
var worldCol: bullet.Bt.CollisionWorld = worldDyn;
|
||||
var bodyColl: bullet.Bt.ConvexShape = cast rb.body.getCollisionShape();
|
||||
worldCol.convexSweepTest(bodyColl, transformFrom, transformTo, convexCallback, 0.0);
|
||||
|
||||
var hitInfo: ConvexHit = null;
|
||||
|
||||
var cc: bullet.Bt.ClosestConvexResultCallback = convexCallback;
|
||||
if (cc.hasHit()) {
|
||||
#if js
|
||||
var hit = convexCallback.get_m_hitPointWorld();
|
||||
convexHitPointWorld.set(hit.x(), hit.y(), hit.z());
|
||||
var norm = convexCallback.get_m_hitNormalWorld();
|
||||
convexHitNormalWorld.set(norm.x(), norm.y(), norm.z());
|
||||
var hitFraction = convexCallback.get_m_closestHitFraction();
|
||||
#elseif (cpp || hl)
|
||||
var hit = convexCallback.m_hitPointWorld;
|
||||
convexHitPointWorld.set(hit.x(), hit.y(), hit.z());
|
||||
var norm = convexCallback.m_hitNormalWorld;
|
||||
convexHitNormalWorld.set(norm.x(), norm.y(), norm.z());
|
||||
var hitFraction = convexCallback.m_closestHitFraction;
|
||||
#end
|
||||
hitInfo = new ConvexHit(convexHitPointWorld, convexHitNormalWorld, hitFraction);
|
||||
}
|
||||
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(convexCallback);
|
||||
#else
|
||||
convexCallback.delete();
|
||||
#end
|
||||
|
||||
return hitInfo;
|
||||
}
|
||||
|
||||
public function notifyOnPreUpdate(f: Void->Void) {
|
||||
if (preUpdates == null) preUpdates = [];
|
||||
preUpdates.push(f);
|
||||
}
|
||||
|
||||
public function removePreUpdate(f: Void->Void) {
|
||||
preUpdates.remove(f);
|
||||
}
|
||||
|
||||
public function setDebugDrawMode(debugDrawMode: DebugDrawMode) {
|
||||
if (debugDrawHelper == null) {
|
||||
if (debugDrawMode == NoDebug) {
|
||||
return;
|
||||
}
|
||||
initDebugDrawing();
|
||||
}
|
||||
|
||||
#if js
|
||||
world.getDebugDrawer().setDebugMode(debugDrawMode);
|
||||
#elseif hl
|
||||
hlDebugDrawer_setDebugMode(debugDrawMode);
|
||||
#end
|
||||
}
|
||||
|
||||
public inline function getDebugDrawMode(): DebugDrawMode {
|
||||
if (debugDrawHelper == null) {
|
||||
return NoDebug;
|
||||
}
|
||||
|
||||
#if js
|
||||
return world.getDebugDrawer().getDebugMode();
|
||||
#elseif hl
|
||||
return hlDebugDrawer_getDebugMode();
|
||||
#else
|
||||
return NoDebug;
|
||||
#end
|
||||
}
|
||||
|
||||
function initDebugDrawing() {
|
||||
debugDrawHelper = new DebugDrawHelper(this);
|
||||
|
||||
#if js
|
||||
final drawer = new bullet.Bt.DebugDrawer();
|
||||
|
||||
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html?highlight=jsimplementation#sub-classing-c-base-classes-in-javascript-jsimplementation
|
||||
drawer.drawLine = debugDrawHelper.drawLine;
|
||||
drawer.drawContactPoint = debugDrawHelper.drawContactPoint;
|
||||
drawer.reportErrorWarning = debugDrawHelper.reportErrorWarning;
|
||||
drawer.draw3dText = debugDrawHelper.draw3dText;
|
||||
|
||||
// From the Leenkx API perspective this is not required,
|
||||
// but Ammo requires it and will result in a black screen if not set
|
||||
drawer.setDebugMode = debugDrawHelper.setDebugMode;
|
||||
drawer.getDebugMode = debugDrawHelper.getDebugMode;
|
||||
|
||||
world.setDebugDrawer(drawer);
|
||||
#elseif hl
|
||||
hlDebugDrawer_setDrawLine(debugDrawHelper.drawLine);
|
||||
hlDebugDrawer_setDrawContactPoint(debugDrawHelper.drawContactPoint);
|
||||
hlDebugDrawer_setReportErrorWarning(debugDrawHelper.reportErrorWarning);
|
||||
hlDebugDrawer_setDraw3dText(debugDrawHelper.draw3dText);
|
||||
|
||||
hlDebugDrawer_worldSetGlobalDebugDrawer(world);
|
||||
#end
|
||||
}
|
||||
|
||||
#if hl
|
||||
@:hlNative("bullet", "debugDrawer_worldSetGlobalDebugDrawer")
|
||||
public static function hlDebugDrawer_worldSetGlobalDebugDrawer(world: #if lnx_physics_soft bullet.Bt.SoftRigidDynamicsWorld #else bullet.Bt.DiscreteDynamicsWorld #end) {}
|
||||
|
||||
@:hlNative("bullet", "debugDrawer_setDebugMode")
|
||||
public static function hlDebugDrawer_setDebugMode(debugMode: Int) {}
|
||||
|
||||
@:hlNative("bullet", "debugDrawer_getDebugMode")
|
||||
public static function hlDebugDrawer_getDebugMode(): Int { return 0; }
|
||||
|
||||
@:hlNative("bullet", "debugDrawer_setDrawLine")
|
||||
public static function hlDebugDrawer_setDrawLine(func: bullet.Bt.Vector3->bullet.Bt.Vector3->bullet.Bt.Vector3->Void) {}
|
||||
|
||||
@:hlNative("bullet", "debugDrawer_setDrawContactPoint")
|
||||
public static function hlDebugDrawer_setDrawContactPoint(func: bullet.Bt.Vector3->bullet.Bt.Vector3->kha.FastFloat->Int->bullet.Bt.Vector3->Void) {}
|
||||
|
||||
@:hlNative("bullet", "debugDrawer_setReportErrorWarning")
|
||||
public static function hlDebugDrawer_setReportErrorWarning(func: hl.Bytes->Void) {}
|
||||
|
||||
@:hlNative("bullet", "debugDrawer_setDraw3dText")
|
||||
public static function hlDebugDrawer_setDraw3dText(func: bullet.Bt.Vector3->hl.Bytes->Void) {}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Debug flags for Bullet physics, despite the name not solely related to debug drawing.
|
||||
You can combine multiple flags with bitwise operations.
|
||||
|
||||
Taken from Bullet's `btIDebugDraw::DebugDrawModes` enum.
|
||||
Please note that the descriptions of the individual flags are a result of inspecting the Bullet sources and thus might contain inaccuracies.
|
||||
|
||||
@see `leenkx.trait.physics.PhysicsWorld.getDebugDrawMode()`
|
||||
@see `leenkx.trait.physics.PhysicsWorld.setDebugDrawMode()`
|
||||
**/
|
||||
// Not all of the flags below are actually used in the library core, some of them are only used
|
||||
// in individual Bullet example projects. The intention of the original authors is unknown,
|
||||
// so whether those flags are actually meant to get their purpose from the implementing application
|
||||
// and not from the library remains a mystery...
|
||||
enum abstract DebugDrawMode(Int) from Int to Int {
|
||||
/** All debug flags off. **/
|
||||
var NoDebug = 0;
|
||||
|
||||
/** Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations. **/
|
||||
var DrawWireframe = 1;
|
||||
|
||||
/** Draw axis-aligned minimum bounding boxes (AABBs) of the physics collider meshes. **/
|
||||
var DrawAABB = 1 << 1;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
// Only used in a Bullet physics example at the moment:
|
||||
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/ExampleBrowser/GL_ShapeDrawer.cpp#L616-L644
|
||||
var DrawFeaturesText = 1 << 2;
|
||||
|
||||
/** Visualize contact points of multiple colliders. **/
|
||||
var DrawContactPoints = 1 << 3;
|
||||
|
||||
/**
|
||||
Globally disable sleeping/deactivation of dynamic colliders.
|
||||
**/
|
||||
var NoDeactivation = 1 << 4;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
// Not used in the library core, in some Bullet examples this flag is used to print application-specific help text (e.g. keyboard shortcuts) to the screen, e.g.:
|
||||
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/ForkLift/ForkLiftDemo.cpp#L586
|
||||
var NoHelpText = 1 << 5;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
// Not used in the library core, appears to be the opposite of NoHelpText (not sure why there are two flags required for this...)
|
||||
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/FractureDemo/FractureDemo.cpp#L189
|
||||
var DrawText = 1 << 6;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
// Not even used in official Bullet examples, probably obsolete.
|
||||
// Related to btQuickprof.h: https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=1285#p4743
|
||||
// Probably replaced by define: https://github.com/bulletphysics/bullet3/commit/d051e2eacb01a948c7b53e24fd3d9942ce64bdcc
|
||||
var ProfileTimings = 1 << 7;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
// Not even used in official Bullet examples, might be obsolete.
|
||||
var EnableSatComparison = 1 << 8;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
var DisableBulletLCP = 1 << 9;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
var EnableCCD = 1 << 10;
|
||||
|
||||
/** Draw axis gizmos for important constraint points. **/
|
||||
var DrawConstraints = 1 << 11;
|
||||
|
||||
/** Draw additional constraint information such as distance or angle limits. **/
|
||||
var DrawConstraintLimits = 1 << 12;
|
||||
|
||||
/** Not used in Leenkx. **/
|
||||
// Only used in a Bullet physics example at the moment:
|
||||
// https://github.com/bulletphysics/bullet3/blob/39b8de74df93721add193e5b3d9ebee579faebf8/examples/ExampleBrowser/GL_ShapeDrawer.cpp#L258
|
||||
// We could use it in the future to toggle depth testing for lines, i.e. draw actual 3D lines if not set and Kha's g2 lines if set.
|
||||
var FastWireframe = 1 << 13;
|
||||
|
||||
/**
|
||||
Draw the normal vectors of the triangles of the physics collider meshes.
|
||||
This only works for `Mesh` collision shapes.
|
||||
**/
|
||||
// Outside of Leenkx this works for a few more collision shapes
|
||||
var DrawNormals = 1 << 14;
|
||||
|
||||
/**
|
||||
Draw a small axis gizmo at the origin of the collision shape.
|
||||
Only works if `DrawWireframe` is enabled as well.
|
||||
**/
|
||||
var DrawFrames = 1 << 15;
|
||||
|
||||
@:op(~A) public inline function bitwiseNegate(): DebugDrawMode {
|
||||
return ~this;
|
||||
}
|
||||
|
||||
@:op(A & B) public inline function bitwiseAND(other: DebugDrawMode): DebugDrawMode {
|
||||
return this & other;
|
||||
}
|
||||
|
||||
@:op(A | B) public inline function bitwiseOR(other: DebugDrawMode): DebugDrawMode {
|
||||
return this | other;
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
695
leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx
Normal file
695
leenkx/Sources/leenkx/trait/physics/bullet/RigidBody.hx
Normal file
@ -0,0 +1,695 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.object.Transform;
|
||||
import iron.object.MeshObject;
|
||||
import iron.data.MeshData;
|
||||
|
||||
/**
|
||||
RigidBody is used to allow objects to interact with Physics in your game including collisions and gravity.
|
||||
RigidBody can also be used with the getContacts method to detect collisions and run appropriate code.
|
||||
The Bullet physics engine is used for these calculations.
|
||||
**/
|
||||
@:access(leenkx.trait.physics.bullet.PhysicsWorld)
|
||||
class RigidBody extends iron.Trait {
|
||||
|
||||
var shape: Shape;
|
||||
public var physics: PhysicsWorld;
|
||||
public var transform: Transform = null;
|
||||
public var mass: Float;
|
||||
public var friction: Float;
|
||||
public var angularFriction: Float;
|
||||
public var restitution: Float;
|
||||
public var collisionMargin: Float;
|
||||
public var linearDamping: Float;
|
||||
public var angularDamping: Float;
|
||||
public var animated: Bool;
|
||||
public var staticObj: Bool;
|
||||
public var destroyed = false;
|
||||
var linearFactors: Array<Float>;
|
||||
var angularFactors: Array<Float>;
|
||||
var useDeactivation: Bool;
|
||||
var deactivationParams: Array<Float>;
|
||||
var ccd = false; // Continuous collision detection
|
||||
public var group = 1;
|
||||
public var mask = 1;
|
||||
var trigger = false;
|
||||
var bodyScaleX: Float; // Transform scale at creation time
|
||||
var bodyScaleY: Float;
|
||||
var bodyScaleZ: Float;
|
||||
var currentScaleX: Float;
|
||||
var currentScaleY: Float;
|
||||
var currentScaleZ: Float;
|
||||
var meshInterface: bullet.Bt.TriangleMesh;
|
||||
|
||||
public var body: bullet.Bt.RigidBody = null;
|
||||
public var motionState: bullet.Bt.MotionState;
|
||||
public var btshape: bullet.Bt.CollisionShape;
|
||||
public var ready = false;
|
||||
static var nextId = 0;
|
||||
public var id = 0;
|
||||
public var onReady: Void->Void = null;
|
||||
public var onContact: Array<RigidBody->Void> = null;
|
||||
public var heightData: haxe.io.Bytes = null;
|
||||
#if js
|
||||
static var ammoArray: Int = -1;
|
||||
#end
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1: bullet.Bt.Vector3;
|
||||
static var vec2: bullet.Bt.Vector3;
|
||||
static var vec3: bullet.Bt.Vector3;
|
||||
static var quat1: bullet.Bt.Quaternion;
|
||||
static var trans1: bullet.Bt.Transform;
|
||||
static var trans2: bullet.Bt.Transform;
|
||||
static var quat = new Quat();
|
||||
|
||||
static var CF_STATIC_OBJECT = 1;
|
||||
static var CF_KINEMATIC_OBJECT = 2;
|
||||
static var CF_NO_CONTACT_RESPONSE = 4;
|
||||
static var CF_CHARACTER_OBJECT = 16;
|
||||
|
||||
static var convexHullCache = new Map<MeshData, bullet.Bt.ConvexHullShape>();
|
||||
static var triangleMeshCache = new Map<MeshData, bullet.Bt.TriangleMesh>();
|
||||
static var usersCache = new Map<MeshData, Int>();
|
||||
|
||||
public function new(shape = Shape.Box, mass = 1.0, friction = 0.5, restitution = 0.0, group = 1, mask = 1,
|
||||
params: RigidBodyParams = null, flags: RigidBodyFlags = null) {
|
||||
super();
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
vec2 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
vec3 = new bullet.Bt.Vector3(0, 0, 0);
|
||||
quat1 = new bullet.Bt.Quaternion(0, 0, 0, 0);
|
||||
trans1 = new bullet.Bt.Transform();
|
||||
trans2 = new bullet.Bt.Transform();
|
||||
}
|
||||
|
||||
this.shape = shape;
|
||||
this.mass = mass;
|
||||
this.friction = friction;
|
||||
this.restitution = restitution;
|
||||
this.group = group;
|
||||
this.mask = mask;
|
||||
|
||||
if (params == null) params = {
|
||||
linearDamping: 0.04,
|
||||
angularDamping: 0.1,
|
||||
angularFriction: 0.1,
|
||||
linearFactorsX: 1.0,
|
||||
linearFactorsY: 1.0,
|
||||
linearFactorsZ: 1.0,
|
||||
angularFactorsX: 1.0,
|
||||
angularFactorsY: 1.0,
|
||||
angularFactorsZ: 1.0,
|
||||
collisionMargin: 0.0,
|
||||
linearDeactivationThreshold: 0.0,
|
||||
angularDeactivationThrshold: 0.0,
|
||||
deactivationTime: 0.0
|
||||
};
|
||||
|
||||
if (flags == null) flags = {
|
||||
animated: false,
|
||||
trigger: false,
|
||||
ccd: false,
|
||||
staticObj: false,
|
||||
useDeactivation: true
|
||||
};
|
||||
|
||||
this.linearDamping = params.linearDamping;
|
||||
this.angularDamping = params.angularDamping;
|
||||
this.angularFriction = params.angularFriction;
|
||||
this.linearFactors = [params.linearFactorsX, params.linearFactorsY, params.linearFactorsZ];
|
||||
this.angularFactors = [params.angularFactorsX, params.angularFactorsY, params.angularFactorsZ];
|
||||
this.collisionMargin = params.collisionMargin;
|
||||
this.deactivationParams = [params.linearDeactivationThreshold, params.angularDeactivationThrshold, params.deactivationTime];
|
||||
this.animated = flags.animated;
|
||||
this.trigger = flags.trigger;
|
||||
this.ccd = flags.ccd;
|
||||
this.staticObj = flags.staticObj;
|
||||
this.useDeactivation = flags.useDeactivation;
|
||||
|
||||
notifyOnAdd(init);
|
||||
}
|
||||
|
||||
inline function withMargin(f: Float) {
|
||||
return f + f * collisionMargin;
|
||||
}
|
||||
|
||||
public function notifyOnReady(f: Void->Void) {
|
||||
onReady = f;
|
||||
if (ready) onReady();
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if (ready) return;
|
||||
ready = true;
|
||||
|
||||
if (!Std.isOfType(object, MeshObject)) return; // No mesh data
|
||||
|
||||
transform = object.transform;
|
||||
physics = leenkx.trait.physics.PhysicsWorld.active;
|
||||
|
||||
if (shape == Shape.Box) {
|
||||
vec1.setX(withMargin(transform.dim.x / 2));
|
||||
vec1.setY(withMargin(transform.dim.y / 2));
|
||||
vec1.setZ(withMargin(transform.dim.z / 2));
|
||||
btshape = new bullet.Bt.BoxShape(vec1);
|
||||
}
|
||||
else if (shape == Shape.Sphere) {
|
||||
btshape = new bullet.Bt.SphereShape(withMargin(transform.dim.x / 2));
|
||||
}
|
||||
else if (shape == Shape.ConvexHull) {
|
||||
var shapeConvex = fillConvexHull(transform.scale, collisionMargin);
|
||||
btshape = shapeConvex;
|
||||
}
|
||||
else if (shape == Shape.Cone) {
|
||||
var coneZ = new bullet.Bt.ConeShapeZ(
|
||||
withMargin(transform.dim.x / 2), // Radius
|
||||
withMargin(transform.dim.z)); // Height
|
||||
var cone: bullet.Bt.ConeShape = coneZ;
|
||||
btshape = cone;
|
||||
}
|
||||
else if (shape == Shape.Cylinder) {
|
||||
vec1.setX(withMargin(transform.dim.x / 2));
|
||||
vec1.setY(withMargin(transform.dim.y / 2));
|
||||
vec1.setZ(withMargin(transform.dim.z / 2));
|
||||
var cylZ = new bullet.Bt.CylinderShapeZ(vec1);
|
||||
var cyl: bullet.Bt.CylinderShape = cylZ;
|
||||
btshape = cyl;
|
||||
}
|
||||
else if (shape == Shape.Capsule) {
|
||||
var r = transform.dim.x / 2;
|
||||
var capsZ = new bullet.Bt.CapsuleShapeZ(
|
||||
withMargin(r), // Radius
|
||||
withMargin(transform.dim.z - r * 2)); // Height between 2 sphere centers
|
||||
var caps: bullet.Bt.CapsuleShape = capsZ;
|
||||
btshape = caps;
|
||||
}
|
||||
else if (shape == Shape.Mesh) {
|
||||
meshInterface = fillTriangleMesh(transform.scale);
|
||||
if (mass > 0) {
|
||||
var shapeGImpact = new bullet.Bt.GImpactMeshShape(meshInterface);
|
||||
shapeGImpact.updateBound();
|
||||
var shapeConcave: bullet.Bt.ConcaveShape = shapeGImpact;
|
||||
btshape = shapeConcave;
|
||||
if (!physics.gimpactRegistered) {
|
||||
#if js
|
||||
new bullet.Bt.GImpactCollisionAlgorithm().registerAlgorithm(physics.dispatcher);
|
||||
#else
|
||||
shapeGImpact.registerAlgorithm(physics.dispatcher);
|
||||
#end
|
||||
physics.gimpactRegistered = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var shapeBvh = new bullet.Bt.BvhTriangleMeshShape(meshInterface, true, true);
|
||||
var shapeTri: bullet.Bt.TriangleMeshShape = shapeBvh;
|
||||
var shapeConcave: bullet.Bt.ConcaveShape = shapeTri;
|
||||
btshape = shapeConcave;
|
||||
}
|
||||
}
|
||||
else if (shape == Shape.Terrain) {
|
||||
#if js
|
||||
var length = heightData.length;
|
||||
if (ammoArray == -1) {
|
||||
ammoArray = bullet.Bt.Ammo._malloc(length);
|
||||
}
|
||||
// From texture bytes
|
||||
for (i in 0...length) {
|
||||
bullet.Bt.Ammo.HEAPU8[ammoArray + i] = heightData.get(i);
|
||||
}
|
||||
var slice = Std.int(Math.sqrt(length)); // Assuming square terrain data
|
||||
var axis = 2; // z
|
||||
var dataType = 5; // u8
|
||||
btshape = new bullet.Bt.HeightfieldTerrainShape(slice, slice, ammoArray, 1 / 255, 0, 1, axis, dataType, false);
|
||||
vec1.setX(transform.dim.x / slice);
|
||||
vec1.setY(transform.dim.y / slice);
|
||||
vec1.setZ(transform.dim.z);
|
||||
btshape.setLocalScaling(vec1);
|
||||
#end
|
||||
}
|
||||
|
||||
trans1.setIdentity();
|
||||
vec1.setX(transform.worldx());
|
||||
vec1.setY(transform.worldy());
|
||||
vec1.setZ(transform.worldz());
|
||||
trans1.setOrigin(vec1);
|
||||
quat.fromMat(transform.world);
|
||||
quat1.setValue(quat.x, quat.y, quat.z, quat.w);
|
||||
trans1.setRotation(quat1);
|
||||
|
||||
var centerOfMassOffset = trans2;
|
||||
centerOfMassOffset.setIdentity();
|
||||
motionState = new bullet.Bt.DefaultMotionState(trans1, centerOfMassOffset);
|
||||
|
||||
vec1.setX(0);
|
||||
vec1.setY(0);
|
||||
vec1.setZ(0);
|
||||
var inertia = vec1;
|
||||
|
||||
if (staticObj || animated) mass = 0;
|
||||
if (mass > 0) btshape.calculateLocalInertia(mass, inertia);
|
||||
var bodyCI = new bullet.Bt.RigidBodyConstructionInfo(mass, motionState, btshape, inertia);
|
||||
body = new bullet.Bt.RigidBody(bodyCI);
|
||||
|
||||
var bodyColl: bullet.Bt.CollisionObject = body;
|
||||
bodyColl.setFriction(friction);
|
||||
bodyColl.setRollingFriction(angularFriction);
|
||||
bodyColl.setRestitution(restitution);
|
||||
|
||||
if ( useDeactivation) {
|
||||
setDeactivationParams(deactivationParams[0], deactivationParams[1], deactivationParams[2]);
|
||||
}
|
||||
else {
|
||||
setActivationState(bullet.Bt.CollisionObjectActivationState.DISABLE_DEACTIVATION);
|
||||
}
|
||||
|
||||
if (linearDamping != 0.04 || angularDamping != 0.1) {
|
||||
body.setDamping(linearDamping, angularDamping);
|
||||
}
|
||||
|
||||
if (linearFactors != null) {
|
||||
setLinearFactor(linearFactors[0], linearFactors[1], linearFactors[2]);
|
||||
}
|
||||
|
||||
if (angularFactors != null) {
|
||||
setAngularFactor(angularFactors[0], angularFactors[1], angularFactors[2]);
|
||||
}
|
||||
|
||||
if (trigger) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
|
||||
if (animated) {
|
||||
bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_KINEMATIC_OBJECT);
|
||||
bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() & ~CF_STATIC_OBJECT);
|
||||
}
|
||||
if (staticObj && !animated) bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_STATIC_OBJECT);
|
||||
|
||||
if (ccd) setCcd(transform.radius);
|
||||
|
||||
bodyScaleX = currentScaleX = transform.scale.x;
|
||||
bodyScaleY = currentScaleY = transform.scale.y;
|
||||
bodyScaleZ = currentScaleZ = transform.scale.z;
|
||||
|
||||
id = nextId;
|
||||
nextId++;
|
||||
|
||||
#if js
|
||||
//body.setUserIndex(nextId);
|
||||
untyped body.userIndex = id;
|
||||
#else
|
||||
bodyColl.setUserIndex(id);
|
||||
#end
|
||||
|
||||
physics.addRigidBody(this);
|
||||
notifyOnRemove(removeFromWorld);
|
||||
|
||||
if (onReady != null) onReady();
|
||||
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(bodyCI);
|
||||
#else
|
||||
bodyCI.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
function physicsUpdate() {
|
||||
if (!ready) return;
|
||||
if (animated) {
|
||||
syncTransform();
|
||||
}
|
||||
else {
|
||||
var trans = body.getWorldTransform();
|
||||
var p = trans.getOrigin();
|
||||
var q = trans.getRotation();
|
||||
|
||||
transform.loc.set(p.x(), p.y(), p.z());
|
||||
transform.rot.set(q.x(), q.y(), q.z(), q.w());
|
||||
if (object.parent != null) {
|
||||
var ptransform = object.parent.transform;
|
||||
transform.loc.x -= ptransform.worldx();
|
||||
transform.loc.y -= ptransform.worldy();
|
||||
transform.loc.z -= ptransform.worldz();
|
||||
}
|
||||
transform.clearDelta();
|
||||
transform.buildMatrix();
|
||||
|
||||
#if hl
|
||||
p.delete();
|
||||
q.delete();
|
||||
trans.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
if (onContact != null) {
|
||||
var rbs = physics.getContacts(this);
|
||||
if (rbs != null) for (rb in rbs) for (f in onContact) f(rb);
|
||||
}
|
||||
}
|
||||
|
||||
public function disableCollision() {
|
||||
var bodyColl: bullet.Bt.CollisionObject = body;
|
||||
bodyColl.setCollisionFlags(bodyColl.getCollisionFlags() | CF_NO_CONTACT_RESPONSE);
|
||||
}
|
||||
|
||||
public function enableCollision() {
|
||||
var bodyColl: bullet.Bt.CollisionObject = body;
|
||||
bodyColl.setCollisionFlags(~bodyColl.getCollisionFlags() & CF_NO_CONTACT_RESPONSE);
|
||||
}
|
||||
|
||||
public function removeFromWorld() {
|
||||
if (physics != null) physics.removeRigidBody(this);
|
||||
}
|
||||
|
||||
public function isActive() : Bool {
|
||||
return body.isActive();
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
var bodyColl: bullet.Bt.CollisionObject = body;
|
||||
bodyColl.activate(false);
|
||||
}
|
||||
|
||||
public function disableGravity() {
|
||||
vec1.setValue(0, 0, 0);
|
||||
body.setGravity(vec1);
|
||||
}
|
||||
|
||||
public function enableGravity() {
|
||||
body.setGravity(physics.world.getGravity());
|
||||
}
|
||||
|
||||
public function setGravity(v: Vec4) {
|
||||
vec1.setValue(v.x, v.y, v.z);
|
||||
body.setGravity(vec1);
|
||||
}
|
||||
|
||||
public function setActivationState(newState: Int) {
|
||||
var bodyColl: bullet.Bt.CollisionObject = body;
|
||||
bodyColl.setActivationState(newState);
|
||||
}
|
||||
|
||||
public function setDeactivationParams(linearThreshold: Float, angularThreshold: Float, time: Float) {
|
||||
body.setSleepingThresholds(linearThreshold, angularThreshold);
|
||||
// body.setDeactivationTime(time); // not available in ammo
|
||||
}
|
||||
|
||||
public function setUpDeactivation(useDeactivation: Bool, linearThreshold: Float, angularThreshold: Float, time: Float) {
|
||||
this.useDeactivation = useDeactivation;
|
||||
this.deactivationParams[0] = linearThreshold;
|
||||
this.deactivationParams[1] = angularThreshold;
|
||||
this.deactivationParams[2] = time;
|
||||
}
|
||||
|
||||
public function isTriggerObject(isTrigger: Bool) {
|
||||
this.trigger = isTrigger;
|
||||
}
|
||||
|
||||
public function applyForce(force: Vec4, loc: Vec4 = null) {
|
||||
activate();
|
||||
vec1.setValue(force.x, force.y, force.z);
|
||||
if (loc == null) {
|
||||
body.applyCentralForce(vec1);
|
||||
}
|
||||
else {
|
||||
vec2.setValue(loc.x, loc.y, loc.z);
|
||||
body.applyForce(vec1, vec2);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyImpulse(impulse: Vec4, loc: Vec4 = null) {
|
||||
activate();
|
||||
vec1.setValue(impulse.x, impulse.y, impulse.z);
|
||||
if (loc == null) {
|
||||
body.applyCentralImpulse(vec1);
|
||||
}
|
||||
else {
|
||||
vec2.setValue(loc.x, loc.y, loc.z);
|
||||
body.applyImpulse(vec1, vec2);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyTorque(torque: Vec4) {
|
||||
activate();
|
||||
vec1.setValue(torque.x, torque.y, torque.z);
|
||||
body.applyTorque(vec1);
|
||||
}
|
||||
|
||||
public function applyTorqueImpulse(torque: Vec4) {
|
||||
activate();
|
||||
vec1.setValue(torque.x, torque.y, torque.z);
|
||||
body.applyTorqueImpulse(vec1);
|
||||
}
|
||||
|
||||
public function setLinearFactor(x: Float, y: Float, z: Float) {
|
||||
vec1.setValue(x, y, z);
|
||||
body.setLinearFactor(vec1);
|
||||
}
|
||||
|
||||
public function setAngularFactor(x: Float, y: Float, z: Float) {
|
||||
vec1.setValue(x, y, z);
|
||||
body.setAngularFactor(vec1);
|
||||
}
|
||||
|
||||
public function getLinearVelocity(): Vec4 {
|
||||
var v = body.getLinearVelocity();
|
||||
return new Vec4(v.x(), v.y(), v.z());
|
||||
}
|
||||
|
||||
public function setLinearVelocity(x: Float, y: Float, z: Float) {
|
||||
vec1.setValue(x, y, z);
|
||||
body.setLinearVelocity(vec1);
|
||||
}
|
||||
|
||||
public function getAngularVelocity(): Vec4 {
|
||||
var v = body.getAngularVelocity();
|
||||
return new Vec4(v.x(), v.y(), v.z());
|
||||
}
|
||||
|
||||
public function setAngularVelocity(x: Float, y: Float, z: Float) {
|
||||
vec1.setValue(x, y, z);
|
||||
body.setAngularVelocity(vec1);
|
||||
}
|
||||
|
||||
public function getPointVelocity(x: Float, y: Float, z: Float) {
|
||||
var linear = getLinearVelocity();
|
||||
|
||||
var relativePoint = new Vec4(x, y, z).sub(transform.world.getLoc());
|
||||
var angular = getAngularVelocity().cross(relativePoint);
|
||||
|
||||
return linear.add(angular);
|
||||
}
|
||||
|
||||
public function setFriction(f: Float) {
|
||||
var bodyColl: bullet.Bt.CollisionObject = body;
|
||||
bodyColl.setFriction(f);
|
||||
// bodyColl.setRollingFriction(f);
|
||||
this.friction = f;
|
||||
}
|
||||
|
||||
public function notifyOnContact(f: RigidBody->Void) {
|
||||
if (onContact == null) onContact = [];
|
||||
onContact.push(f);
|
||||
}
|
||||
|
||||
public function removeContact(f: RigidBody->Void) {
|
||||
onContact.remove(f);
|
||||
}
|
||||
|
||||
function setScale(v: Vec4) {
|
||||
currentScaleX = v.x;
|
||||
currentScaleY = v.y;
|
||||
currentScaleZ = v.z;
|
||||
vec1.setX(v.x / bodyScaleX);
|
||||
vec1.setY(v.y / bodyScaleY);
|
||||
vec1.setZ(v.z / bodyScaleZ);
|
||||
btshape.setLocalScaling(vec1);
|
||||
var worldDyn: bullet.Bt.DynamicsWorld = physics.world;
|
||||
var worldCol: bullet.Bt.CollisionWorld = worldDyn;
|
||||
worldCol.updateSingleAabb(body);
|
||||
}
|
||||
|
||||
public function syncTransform() {
|
||||
var t = transform;
|
||||
t.buildMatrix();
|
||||
vec1.setValue(t.worldx(), t.worldy(), t.worldz());
|
||||
trans1.setOrigin(vec1);
|
||||
quat.fromMat(t.world);
|
||||
quat1.setValue(quat.x, quat.y, quat.z, quat.w);
|
||||
trans1.setRotation(quat1);
|
||||
if (animated) body.getMotionState().setWorldTransform(trans1);
|
||||
else body.setCenterOfMassTransform(trans1);
|
||||
if (currentScaleX != t.scale.x || currentScaleY != t.scale.y || currentScaleZ != t.scale.z) setScale(t.scale);
|
||||
activate();
|
||||
}
|
||||
|
||||
// Continuous collision detection
|
||||
public function setCcd(sphereRadius: Float, motionThreshold = 1e-7) {
|
||||
var bodyColl: bullet.Bt.CollisionObject = body;
|
||||
bodyColl.setCcdSweptSphereRadius(sphereRadius);
|
||||
bodyColl.setCcdMotionThreshold(motionThreshold);
|
||||
}
|
||||
|
||||
function fillConvexHull(scale: Vec4, margin: kha.FastFloat): bullet.Bt.ConvexHullShape {
|
||||
// Check whether shape already exists
|
||||
var data = cast(object, MeshObject).data;
|
||||
var shape = convexHullCache.get(data);
|
||||
if (shape != null) {
|
||||
usersCache.set(data, usersCache.get(data) + 1);
|
||||
return shape;
|
||||
}
|
||||
|
||||
shape = new bullet.Bt.ConvexHullShape();
|
||||
convexHullCache.set(data, shape);
|
||||
usersCache.set(data, 1);
|
||||
|
||||
var positions = data.geom.positions.values;
|
||||
|
||||
var sx: kha.FastFloat = scale.x * (1.0 - margin) * (1 / 32767);
|
||||
var sy: kha.FastFloat = scale.y * (1.0 - margin) * (1 / 32767);
|
||||
var sz: kha.FastFloat = scale.z * (1.0 - margin) * (1 / 32767);
|
||||
|
||||
if (data.raw.scale_pos != null) {
|
||||
sx *= data.raw.scale_pos;
|
||||
sy *= data.raw.scale_pos;
|
||||
sz *= data.raw.scale_pos;
|
||||
}
|
||||
|
||||
for (i in 0...Std.int(positions.length / 4)) {
|
||||
vec1.setX(positions[i * 4 ] * sx);
|
||||
vec1.setY(positions[i * 4 + 1] * sy);
|
||||
vec1.setZ(positions[i * 4 + 2] * sz);
|
||||
shape.addPoint(vec1, true);
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
function fillTriangleMesh(scale: Vec4): bullet.Bt.TriangleMesh {
|
||||
// Check whether shape already exists
|
||||
var data = cast(object, MeshObject).data;
|
||||
var triangleMesh = triangleMeshCache.get(data);
|
||||
if (triangleMesh != null) {
|
||||
usersCache.set(data, usersCache.get(data) + 1);
|
||||
return triangleMesh;
|
||||
}
|
||||
|
||||
triangleMesh = new bullet.Bt.TriangleMesh(true, true);
|
||||
triangleMeshCache.set(data, triangleMesh);
|
||||
usersCache.set(data, 1);
|
||||
|
||||
var positions = data.geom.positions.values;
|
||||
var indices = data.geom.indices;
|
||||
|
||||
var sx: kha.FastFloat = scale.x * (1 / 32767);
|
||||
var sy: kha.FastFloat = scale.y * (1 / 32767);
|
||||
var sz: kha.FastFloat = scale.z * (1 / 32767);
|
||||
|
||||
if (data.raw.scale_pos != null) {
|
||||
sx *= data.raw.scale_pos;
|
||||
sy *= data.raw.scale_pos;
|
||||
sz *= data.raw.scale_pos;
|
||||
}
|
||||
|
||||
for (ar in indices) {
|
||||
for (i in 0...Std.int(ar.length / 3)) {
|
||||
vec1.setX(positions[ar[i * 3 ] * 4 ] * sx);
|
||||
vec1.setY(positions[ar[i * 3 ] * 4 + 1] * sy);
|
||||
vec1.setZ(positions[ar[i * 3 ] * 4 + 2] * sz);
|
||||
vec2.setX(positions[ar[i * 3 + 1] * 4 ] * sx);
|
||||
vec2.setY(positions[ar[i * 3 + 1] * 4 + 1] * sy);
|
||||
vec2.setZ(positions[ar[i * 3 + 1] * 4 + 2] * sz);
|
||||
vec3.setX(positions[ar[i * 3 + 2] * 4 ] * sx);
|
||||
vec3.setY(positions[ar[i * 3 + 2] * 4 + 1] * sy);
|
||||
vec3.setZ(positions[ar[i * 3 + 2] * 4 + 2] * sz);
|
||||
triangleMesh.addTriangle(vec1, vec2, vec3);
|
||||
}
|
||||
}
|
||||
return triangleMesh;
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(motionState);
|
||||
bullet.Bt.Ammo.destroy(body);
|
||||
#else
|
||||
motionState.delete();
|
||||
body.delete();
|
||||
#end
|
||||
|
||||
// Delete shape if no other user is found
|
||||
if (shape == Shape.ConvexHull || shape == Shape.Mesh) {
|
||||
var data = cast(object, MeshObject).data;
|
||||
var i = usersCache.get(data) - 1;
|
||||
usersCache.set(data, i);
|
||||
if(shape == Shape.Mesh) deleteShape();
|
||||
if (i <= 0) {
|
||||
if(shape == Shape.ConvexHull)
|
||||
{
|
||||
deleteShape();
|
||||
convexHullCache.remove(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
triangleMeshCache.remove(data);
|
||||
if(meshInterface != null)
|
||||
{
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(meshInterface);
|
||||
#else
|
||||
meshInterface.delete();
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else deleteShape();
|
||||
}
|
||||
|
||||
inline function deleteShape() {
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(btshape);
|
||||
#else
|
||||
btshape.delete();
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
@:enum abstract Shape(Int) from Int to Int {
|
||||
var Box = 0;
|
||||
var Sphere = 1;
|
||||
var ConvexHull = 2;
|
||||
var Mesh = 3;
|
||||
var Cone = 4;
|
||||
var Cylinder = 5;
|
||||
var Capsule = 6;
|
||||
var Terrain = 7;
|
||||
}
|
||||
|
||||
typedef RigidBodyParams = {
|
||||
var linearDamping: Float;
|
||||
var angularDamping: Float;
|
||||
var angularFriction: Float;
|
||||
var linearFactorsX: Float;
|
||||
var linearFactorsY: Float;
|
||||
var linearFactorsZ: Float;
|
||||
var angularFactorsX: Float;
|
||||
var angularFactorsY: Float;
|
||||
var angularFactorsZ: Float;
|
||||
var collisionMargin: Float;
|
||||
var linearDeactivationThreshold: Float;
|
||||
var angularDeactivationThrshold: Float;
|
||||
var deactivationTime: Float;
|
||||
}
|
||||
|
||||
typedef RigidBodyFlags = {
|
||||
var animated: Bool;
|
||||
var trigger: Bool;
|
||||
var ccd: Bool;
|
||||
var staticObj: Bool;
|
||||
var useDeactivation: Bool;
|
||||
}
|
||||
#end
|
431
leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx
Normal file
431
leenkx/Sources/leenkx/trait/physics/bullet/SoftBody.hx
Normal file
@ -0,0 +1,431 @@
|
||||
package leenkx.trait.physics.bullet;
|
||||
|
||||
#if lnx_bullet
|
||||
import iron.Scene;
|
||||
import haxe.ds.Vector;
|
||||
import kha.arrays.ByteArray;
|
||||
import bullet.Bt.Vector3;
|
||||
import bullet.Bt.CollisionObjectActivationState;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.Trait;
|
||||
import iron.object.MeshObject;
|
||||
import iron.data.Geometry;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.SceneFormat;
|
||||
import kha.arrays.Uint32Array;
|
||||
#if lnx_physics_soft
|
||||
import leenkx.trait.physics.RigidBody;
|
||||
import leenkx.trait.physics.PhysicsWorld;
|
||||
#end
|
||||
|
||||
class SoftBody extends Trait {
|
||||
#if (!lnx_physics_soft)
|
||||
public function new() { super(); }
|
||||
#else
|
||||
|
||||
static var physics: PhysicsWorld = null;
|
||||
|
||||
public var ready = false;
|
||||
var shape: SoftShape;
|
||||
var bend: Float;
|
||||
var mass: Float;
|
||||
var margin: Float;
|
||||
|
||||
public var vertOffsetX = 0.0;
|
||||
public var vertOffsetY = 0.0;
|
||||
public var vertOffsetZ = 0.0;
|
||||
|
||||
public var body: bullet.Bt.SoftBody;
|
||||
|
||||
static var helpers: bullet.Bt.SoftBodyHelpers;
|
||||
static var helpersCreated = false;
|
||||
static var worldInfo: bullet.Bt.SoftBodyWorldInfo;
|
||||
|
||||
var vertexIndexMap: Map<Int, Array<Int>>;
|
||||
|
||||
public function new(shape = SoftShape.Cloth, bend = 0.5, mass = 1.0, margin = 0.04) {
|
||||
super();
|
||||
this.shape = shape;
|
||||
this.bend = bend;
|
||||
this.mass = mass;
|
||||
this.margin = margin;
|
||||
|
||||
//notifyOnAdd(init);
|
||||
//The above line works as well, but the object transforms are not set
|
||||
//properly, so the positions are not accurate
|
||||
notifyOnInit(init);
|
||||
}
|
||||
|
||||
function fromI16(ar: kha.arrays.Int16Array, scalePos: Float): haxe.ds.Vector<Float> {
|
||||
var vals = new haxe.ds.Vector<Float>(Std.int(ar.length / 4) * 3);
|
||||
for (i in 0...Std.int(vals.length / 3)) {
|
||||
vals[i * 3 ] = (ar[i * 4 ] / 32767) * scalePos;
|
||||
vals[i * 3 + 1] = (ar[i * 4 + 1] / 32767) * scalePos;
|
||||
vals[i * 3 + 2] = (ar[i * 4 + 2] / 32767) * scalePos;
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
function fromU32(ars: Array<kha.arrays.Uint32Array>): haxe.ds.Vector<Int> {
|
||||
var len = 0;
|
||||
for (ar in ars) len += ar.length;
|
||||
var vals = new haxe.ds.Vector<Int>(len);
|
||||
var i = 0;
|
||||
for (ar in ars) {
|
||||
for (j in 0...ar.length) {
|
||||
vals[i] = ar[j];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
function generateVertexIndexMap(ind: haxe.ds.Vector<Int>, vert: haxe.ds.Vector<Int>) {
|
||||
if (vertexIndexMap == null) vertexIndexMap = new Map();
|
||||
for (i in 0...ind.length) {
|
||||
var currentVertex = vert[i];
|
||||
var currentIndex = ind[i];
|
||||
|
||||
var mapping = vertexIndexMap.get(currentVertex);
|
||||
if (mapping == null) {
|
||||
vertexIndexMap.set(currentVertex, [currentIndex]);
|
||||
}
|
||||
else {
|
||||
if(! mapping.contains(currentIndex)) mapping.push(currentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
var mo = cast(object, MeshObject);
|
||||
//Set new mesh data for this object
|
||||
new MeshData(mo.data.raw, function (data) {
|
||||
mo.setData(data);
|
||||
//Init soft body after setting new data
|
||||
initSoftBody();
|
||||
//If the above line is commented out, the program becomes unresponsive with white screen
|
||||
//and no errors.
|
||||
});
|
||||
}
|
||||
|
||||
var v = new Vec4();
|
||||
function initSoftBody() {
|
||||
if (ready) return;
|
||||
ready = true;
|
||||
|
||||
if (physics == null) physics = leenkx.trait.physics.PhysicsWorld.active;
|
||||
|
||||
var mo = cast(object, MeshObject);
|
||||
mo.frustumCulling = false;
|
||||
var geom = mo.data.geom;
|
||||
var rawData = mo.data.raw;
|
||||
var vertexMap: Array<Uint32Array> = [];
|
||||
for (ind in rawData.index_arrays) {
|
||||
if (ind.vertex_map == null) return;
|
||||
vertexMap.push(ind.vertex_map);
|
||||
}
|
||||
|
||||
var vecind = fromU32(geom.indices);
|
||||
var vertexMapArray = fromU32(vertexMap);
|
||||
|
||||
generateVertexIndexMap(vecind, vertexMapArray);
|
||||
|
||||
// Parented soft body - clear parent location
|
||||
if (object.parent != null && object.parent.name != "") {
|
||||
object.transform.loc.x += object.parent.transform.worldx();
|
||||
object.transform.loc.y += object.parent.transform.worldy();
|
||||
object.transform.loc.z += object.parent.transform.worldz();
|
||||
object.transform.localOnly = true;
|
||||
object.transform.buildMatrix();
|
||||
}
|
||||
|
||||
var positions = fromI16(geom.positions.values, mo.data.scalePos);
|
||||
for (i in 0...Std.int(positions.length / 3)) {
|
||||
v.set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
|
||||
v.applyQuat(object.transform.rot);
|
||||
v.x *= object.transform.scale.x;
|
||||
v.y *= object.transform.scale.y;
|
||||
v.z *= object.transform.scale.z;
|
||||
v.addf(object.transform.worldx(), object.transform.worldy(), object.transform.worldz());
|
||||
positions[i * 3 ] = v.x;
|
||||
positions[i * 3 + 1] = v.y;
|
||||
positions[i * 3 + 2] = v.z;
|
||||
}
|
||||
|
||||
var numtri = 0;
|
||||
for (ar in geom.indices) numtri += Std.int(ar.length / 3);
|
||||
|
||||
if (!helpersCreated) {
|
||||
helpers = new bullet.Bt.SoftBodyHelpers();
|
||||
worldInfo = physics.world.getWorldInfo();
|
||||
helpersCreated = true;
|
||||
#if hl
|
||||
//world info is passed as value and not as a reference, need to set gravity again in HL.
|
||||
worldInfo.m_gravity = physics.world.getGravity();
|
||||
#end
|
||||
}
|
||||
|
||||
var vertsLength = 0;
|
||||
for (key in vertexIndexMap.keys()) vertsLength++;
|
||||
var positionsVector: haxe.ds.Vector<Float> = new haxe.ds.Vector<Float>(vertsLength * 3);
|
||||
for (key in 0...vertsLength){
|
||||
var i = vertexIndexMap.get(key)[0];
|
||||
positionsVector.set(key * 3 , positions[i * 3 ]);
|
||||
positionsVector.set(key * 3 + 1, positions[i * 3 + 1]);
|
||||
positionsVector.set(key * 3 + 2, positions[i * 3 + 2]);
|
||||
}
|
||||
|
||||
var indexMax: Int = 0;
|
||||
var vecindVector: haxe.ds.Vector<Int> = new haxe.ds.Vector<Int>(vertexMapArray.length);
|
||||
for (i in 0...vecindVector.length){
|
||||
var idx = vertexMapArray.get(i);
|
||||
vecindVector.set(i, idx);
|
||||
indexMax = indexMax > idx ? indexMax : idx;
|
||||
}
|
||||
|
||||
#if js
|
||||
body = helpers.CreateFromTriMesh(worldInfo, positionsVector, vecindVector, numtri);
|
||||
#else
|
||||
//Create helper float array
|
||||
var floatArray = new bullet.Bt.FloatArray(positionsVector.length);
|
||||
for (i in 0...positionsVector.length){
|
||||
floatArray.set(i, positionsVector[i]);
|
||||
}
|
||||
//Create helper int array
|
||||
var intArray = new bullet.Bt.IntArray(vecindVector.length);
|
||||
for (i in 0...vecindVector.length){
|
||||
intArray.set(i, vecindVector[i]);
|
||||
}
|
||||
//Create soft body
|
||||
body = helpers.CreateFromTriMesh(worldInfo, floatArray.raw, intArray.raw, numtri, false);
|
||||
|
||||
floatArray.delete();
|
||||
intArray.delete();
|
||||
#end
|
||||
// body.generateClusters(4);
|
||||
|
||||
#if js
|
||||
var cfg = body.get_m_cfg();
|
||||
cfg.set_viterations(physics.solverIterations);
|
||||
cfg.set_piterations(physics.solverIterations);
|
||||
// cfg.set_collisions(0x0001 + 0x0020 + 0x0040); // self collision
|
||||
cfg.set_collisions(0x11); // Soft-rigid, soft-soft
|
||||
if (shape == SoftShape.Volume) {
|
||||
cfg.set_kDF(0.1);
|
||||
cfg.set_kDP(0.01);
|
||||
cfg.set_kPR(bend);
|
||||
}
|
||||
#else
|
||||
//Not passed as refernece
|
||||
var cfg = body.m_cfg;
|
||||
cfg.viterations = physics.solverIterations;
|
||||
cfg.piterations = physics.solverIterations;
|
||||
// body.m_cfg.collisions = 0x0001 + 0x0020 + 0x0040;
|
||||
cfg.collisions = 0x11; // Soft-rigid, soft-soft
|
||||
if (shape == SoftShape.Volume) {
|
||||
cfg.kDF = 0.1;
|
||||
cfg.kDP = 0.01;
|
||||
cfg.kPR = bend;
|
||||
}
|
||||
//Set config again in HL
|
||||
body.m_cfg = cfg;
|
||||
#end
|
||||
|
||||
body.setTotalMass(mass, false);
|
||||
body.getCollisionShape().setMargin(margin);
|
||||
|
||||
physics.world.addSoftBody(body, 1, -1);
|
||||
body.setActivationState(CollisionObjectActivationState.DISABLE_DEACTIVATION);
|
||||
|
||||
#if hl
|
||||
cfg.delete();
|
||||
#end
|
||||
|
||||
notifyOnRemove(removeFromWorld);
|
||||
notifyOnUpdate(update);
|
||||
}
|
||||
|
||||
var va = new Vec4();
|
||||
var vb = new Vec4();
|
||||
var vc = new Vec4();
|
||||
var cb = new Vec4();
|
||||
var ab = new Vec4();
|
||||
function update() {
|
||||
var mo = cast(object, MeshObject);
|
||||
var geom = mo.data.geom;
|
||||
#if lnx_deinterleaved
|
||||
var v: ByteArray = geom.vertexBuffers[0].buffer.lock();
|
||||
var n: ByteArray = geom.vertexBuffers[1].buffer.lock();
|
||||
#else
|
||||
var v:ByteArray = geom.vertexBuffer.lock();
|
||||
var vbPos = geom.vertexBufferMap.get("pos");
|
||||
var v2 = vbPos != null ? vbPos.lock() : null; // For shadows
|
||||
var l = geom.structLength;
|
||||
#end
|
||||
|
||||
#if js
|
||||
var nodes = body.get_m_nodes();
|
||||
#else
|
||||
var nodes = body.m_nodes;
|
||||
#end
|
||||
var numNodes = nodes.size();
|
||||
|
||||
//Finding the mean position of vertices in world space
|
||||
vertOffsetX = 0.0;
|
||||
vertOffsetY = 0.0;
|
||||
vertOffsetZ = 0.0;
|
||||
for (i in 0...numNodes) {
|
||||
var node = nodes.at(i);
|
||||
#if js
|
||||
var nodePos = node.get_m_x();
|
||||
#else
|
||||
var nodePos = node.m_x;
|
||||
#end
|
||||
var mx = nodePos.x();
|
||||
var my = nodePos.y();
|
||||
var mz = nodePos.z();
|
||||
vertOffsetX += mx;
|
||||
vertOffsetY += my;
|
||||
vertOffsetZ += mz;
|
||||
|
||||
#if hl
|
||||
node.delete();
|
||||
nodePos.delete();
|
||||
#end
|
||||
}
|
||||
vertOffsetX /= numNodes;
|
||||
vertOffsetY /= numNodes;
|
||||
vertOffsetZ /= numNodes;
|
||||
|
||||
//Setting the mean position as object local location
|
||||
mo.transform.scale.set(1, 1, 1);
|
||||
mo.transform.loc.set(vertOffsetX, vertOffsetY, vertOffsetZ);
|
||||
mo.transform.rot.set(0, 0, 0, 1);
|
||||
|
||||
//Checking maximum dimension for scalePos
|
||||
var scalePos = 1.0;
|
||||
for (i in 0...numNodes) {
|
||||
var node = nodes.at(i);
|
||||
#if js
|
||||
var nodePos = node.get_m_x();
|
||||
#else
|
||||
var nodePos = node.m_x;
|
||||
#end
|
||||
var mx = nodePos.x() - vertOffsetX;
|
||||
var my = nodePos.y() - vertOffsetY;
|
||||
var mz = nodePos.z() - vertOffsetZ;
|
||||
if (Math.abs(mx * 2) > scalePos) scalePos = Math.abs(mx * 2);
|
||||
if (Math.abs(my * 2) > scalePos) scalePos = Math.abs(my * 2);
|
||||
if (Math.abs(mz * 2) > scalePos) scalePos = Math.abs(mz * 2);
|
||||
|
||||
#if hl
|
||||
node.delete();
|
||||
nodePos.delete();
|
||||
#end
|
||||
}
|
||||
//Set scalePos and buildMatrix
|
||||
mo.data.scalePos = scalePos;
|
||||
mo.transform.scaleWorld = scalePos;
|
||||
mo.transform.buildMatrix();
|
||||
|
||||
//Set vertices with location offset
|
||||
for (i in 0...nodes.size()) {
|
||||
var node = nodes.at(i);
|
||||
var indices = vertexIndexMap.get(i);
|
||||
#if js
|
||||
var nodePos = node.get_m_x();
|
||||
var nodeNor = node.get_m_n();
|
||||
#else
|
||||
var nodePos = node.m_x;
|
||||
var nodeNor = node.m_n;
|
||||
#end
|
||||
var mx = nodePos.x() - vertOffsetX;
|
||||
var my = nodePos.y() - vertOffsetY;
|
||||
var mz = nodePos.z() - vertOffsetZ;
|
||||
|
||||
var nx = nodeNor.x();
|
||||
var ny = nodeNor.y();
|
||||
var nz = nodeNor.z();
|
||||
|
||||
for (idx in indices){
|
||||
#if lnx_deinterleaved
|
||||
v.setInt16(idx * 8 , Std.int(mx * 32767 * (1 / scalePos)));
|
||||
v.setInt16(idx * 8 + 2, Std.int(my * 32767 * (1 / scalePos)));
|
||||
v.setInt16(idx * 8 + 4, Std.int(mz * 32767 * (1 / scalePos)));
|
||||
n.setInt16(idx * 4 , Std.int(nx * 32767));
|
||||
n.setInt16(idx * 4 + 2, Std.int(ny * 32767));
|
||||
v.setInt16(idx * 8 + 6, Std.int(nz * 32767));
|
||||
#else
|
||||
var vertIndex = idx * l * 2;
|
||||
v.setInt16(vertIndex , Std.int(mx * 32767 * (1 / scalePos)));
|
||||
v.setInt16(vertIndex + 2, Std.int(my * 32767 * (1 / scalePos)));
|
||||
v.setInt16(vertIndex + 4, Std.int(mz * 32767 * (1 / scalePos)));
|
||||
if (vbPos != null) {
|
||||
v2.setInt16(idx * 8 , v.getInt16(vertIndex ));
|
||||
v2.setInt16(idx * 8 + 2, v.getInt16(vertIndex + 2));
|
||||
v2.setInt16(idx * 8 + 4, v.getInt16(vertIndex + 4));
|
||||
}
|
||||
v.setInt16(vertIndex + 6, Std.int(nx * 32767));
|
||||
v.setInt16(vertIndex + 8, Std.int(ny * 32767));
|
||||
v.setInt16(vertIndex + 10, Std.int(nz * 32767));
|
||||
#end
|
||||
}
|
||||
|
||||
#if hl
|
||||
node.delete();
|
||||
nodePos.delete();
|
||||
nodeNor.delete();
|
||||
#end
|
||||
}
|
||||
// for (i in 0...Std.int(geom.indices[0].length / 3)) {
|
||||
// var a = geom.indices[0][i * 3];
|
||||
// var b = geom.indices[0][i * 3 + 1];
|
||||
// var c = geom.indices[0][i * 3 + 2];
|
||||
// va.set(v.get(a * l), v.get(a * l + 1), v.get(a * l + 2));
|
||||
// vb.set(v.get(b * l), v.get(b * l + 1), v.get(b * l + 2));
|
||||
// vc.set(v.get(c * l), v.get(c * l + 1), v.get(c * l + 2));
|
||||
// cb.subvecs(vc, vb);
|
||||
// ab.subvecs(va, vb);
|
||||
// cb.cross(ab);
|
||||
// cb.normalize();
|
||||
// v.set(a * l + 3, cb.x);
|
||||
// v.set(a * l + 4, cb.y);
|
||||
// v.set(a * l + 5, cb.z);
|
||||
// v.set(b * l + 3, cb.x);
|
||||
// v.set(b * l + 4, cb.y);
|
||||
// v.set(b * l + 5, cb.z);
|
||||
// v.set(c * l + 3, cb.x);
|
||||
// v.set(c * l + 4, cb.y);
|
||||
// v.set(c * l + 5, cb.z);
|
||||
// }
|
||||
#if lnx_deinterleaved
|
||||
geom.vertexBuffers[0].buffer.unlock();
|
||||
geom.vertexBuffers[1].buffer.unlock();
|
||||
#else
|
||||
geom.vertexBuffer.unlock();
|
||||
if (vbPos != null) vbPos.unlock();
|
||||
#end
|
||||
#if hl
|
||||
nodes.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
function removeFromWorld() {
|
||||
physics.world.removeSoftBody(body);
|
||||
#if js
|
||||
bullet.Bt.Ammo.destroy(body);
|
||||
#else
|
||||
body.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
||||
|
||||
@:enum abstract SoftShape(Int) from Int {
|
||||
var Cloth = 0;
|
||||
var Volume = 1;
|
||||
}
|
||||
|
||||
#end
|
Reference in New Issue
Block a user