forked from LeenkxTeam/LNXSDK
Next patch
This commit is contained in:
@ -44,6 +44,7 @@ typedef TConfig = {
|
||||
@:optional var rp_supersample: Null<Float>;
|
||||
@:optional var rp_shadowmap_cube: Null<Int>; // size
|
||||
@:optional var rp_shadowmap_cascade: Null<Int>; // size for single cascade
|
||||
@:optional var rp_ssao: Null<Bool>;
|
||||
@:optional var rp_ssgi: Null<Bool>;
|
||||
@:optional var rp_ssr: Null<Bool>;
|
||||
@:optional var rp_ssrefr: Null<Bool>;
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package leenkx.network;
|
||||
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
import leenkx.network.WebSocketServer;
|
||||
import leenkx.network.WebSocketSecureServer;
|
||||
import leenkx.network.SocketImpl;
|
||||
#end
|
||||
#if sys
|
||||
import sys.ssl.Key;
|
||||
import sys.ssl.Certificate;
|
||||
import leenkx.network.SocketImpl;
|
||||
#end
|
||||
import leenkx.network.WebSocket;
|
||||
import leenkx.network.Types;
|
||||
@ -115,7 +117,7 @@ class Host extends Connect {
|
||||
public static var onErrorEvent: String = "Host.onError";
|
||||
public static var onCloseEvent: String = "Host.onClose";
|
||||
public static var object: Object = null;
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
public static var connections:Map<String, WebSocketServer<HostHandler>> = [];
|
||||
#else
|
||||
public static var connections = null;
|
||||
@ -131,14 +133,15 @@ class Host extends Connect {
|
||||
object = net_object;
|
||||
net_Url = "ws://" + net_Domain + ":" + net_Port;
|
||||
|
||||
#if sys
|
||||
if (connections[net_Url] != null) return;
|
||||
connections[net_Url] = new WebSocketServer<HostHandler>(net_Domain, net_Port, net_Max);
|
||||
#if (sys || kha_krom)
|
||||
if (connections[net_Url] == null) {
|
||||
connections[net_Url] = new WebSocketServer<HostHandler>(net_Domain, net_Port, net_Max);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
class HostHandler extends WebSocketHandler {
|
||||
|
||||
public function new(s: SocketImpl) {
|
||||
@ -217,7 +220,7 @@ class SecureHost extends Connect {
|
||||
public static var onCloseEvent: String = "SecureHost.onClose";
|
||||
public static var object: Object = null;
|
||||
public static var net_Url: String;
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
public static var connections:Map<String, WebSocketSecureServer<SecureHostHandler>> = [];
|
||||
#else
|
||||
public static var connections = null;
|
||||
@ -235,13 +238,18 @@ class SecureHost extends Connect {
|
||||
#if sys
|
||||
var cert = Certificate.loadFile(net_Cert);
|
||||
var key = Key.loadFile(net_Key);
|
||||
if (connections[net_Url] != null) return;
|
||||
connections[net_Url] = new WebSocketSecureServer<SecureHostHandler>(net_Domain, net_Port, cert, key, cert, net_Max);
|
||||
if (connections[net_Url] == null) {
|
||||
connections[net_Url] = new WebSocketSecureServer<SecureHostHandler>(net_Domain, net_Port, cert, key, cert, net_Max);
|
||||
}
|
||||
#elseif kha_krom
|
||||
if (connections[net_Url] == null) {
|
||||
connections[net_Url] = new WebSocketSecureServer<SecureHostHandler>(net_Domain, net_Port, net_Cert, net_Key, net_Cert, net_Max);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#if sys
|
||||
#if (sys || kha_krom)
|
||||
class SecureHostHandler extends WebSocketHandler {
|
||||
|
||||
public function new(s: SocketImpl) {
|
||||
@ -313,3 +321,4 @@ class SecureHostHandler extends WebSocketHandler {
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ package leenkx.network;
|
||||
|
||||
import leenkx.network.Types;
|
||||
import haxe.io.Bytes;
|
||||
import js.Browser;
|
||||
import iron.object.Object;
|
||||
import leenkx.system.Event;
|
||||
import leenkx.network.Buffer;
|
||||
@ -30,11 +29,13 @@ class Leenkx {
|
||||
public static var onAnnounceEvent: String = "Leenkx.onAnnounce";
|
||||
public static var onTorrentDoneEvent: String = "Leenkx.onTorrentDone";
|
||||
public static var connections:Map<String, leenkx.network.LeenkxSocket> = [];
|
||||
#if js
|
||||
public static var peers:js.lib.Map<String, String> = new js.lib.Map<String,String>();
|
||||
public static var data:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
public static var id:js.lib.Map<String, String> = new js.lib.Map<String,String>();
|
||||
public static var torrent:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
public static var file:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
public static var file:js.lib.Map<String, Dynamic> = new js.lib.Map<String,Dynamic>();
|
||||
#end
|
||||
public static var lxNew:Void->Void;
|
||||
|
||||
public function new(net_Url: String, net_object: Object) {
|
||||
@ -53,10 +54,6 @@ class Leenkx {
|
||||
}
|
||||
|
||||
if (object != null) {
|
||||
//var script = "const scope = '/';const sw = navigator.serviceWorker.register(`LeenkxFS.js`, { scope });";
|
||||
//js.Syntax.code('(1, eval)({0})', script);
|
||||
|
||||
|
||||
final loadEvent = Event.get(Leenkx.onLoadEvent);
|
||||
final openEvent = Event.get(Leenkx.onOpenEvent);
|
||||
final messageEvent = Event.get(Leenkx.onMessageEvent);
|
||||
@ -77,197 +74,204 @@ class Leenkx {
|
||||
final announceEvent = Event.get(Leenkx.onAnnounceEvent);
|
||||
final torrentDoneEvent = Event.get(Leenkx.onTorrentDoneEvent);
|
||||
|
||||
Leenkx.connections[net_Url].onopen = function() {
|
||||
if (openEvent != null) {
|
||||
for (e in openEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onmessage = function() {
|
||||
if (messageEvent != null) {
|
||||
for (e in messageEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onerror = function() {
|
||||
if (errorEvent != null) {
|
||||
for (e in errorEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onclose = function() {
|
||||
if (closeEvent != null) {
|
||||
for (e in closeEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onseen = function() {
|
||||
if (seenEvent != null) {
|
||||
for (e in seenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onserver = function() {
|
||||
if (serverEvent != null) {
|
||||
for (e in serverEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onconnections = function() {
|
||||
if (connectionsEvent != null) {
|
||||
for (e in connectionsEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onping = function() {
|
||||
if (pingEvent != null) {
|
||||
for (e in pingEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onleft = function() {
|
||||
if (leftEvent != null) {
|
||||
for (e in leftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontimeout = function() {
|
||||
if (timeoutEvent != null) {
|
||||
for (e in timeoutEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onrpc = function() {
|
||||
if (rpcEvent != null) {
|
||||
for (e in rpcEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onrpcresponse = function() {
|
||||
if (rpcresponseEvent != null) {
|
||||
for (e in rpcresponseEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onwireleft = function() {
|
||||
if (wireleftEvent != null) {
|
||||
for (e in wireleftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onwireseen = function() {
|
||||
if (wireseenEvent != null) {
|
||||
for (e in wireseenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontorrent = function() {
|
||||
if (torrentEvent != null) {
|
||||
for (e in torrentEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontracker = function() {
|
||||
if (trackerEvent != null) {
|
||||
for (e in trackerEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onannounce = function() {
|
||||
if (announceEvent != null) {
|
||||
for (e in announceEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].onload = function() {
|
||||
if (loadEvent != null) {
|
||||
for (e in loadEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Leenkx.connections[net_Url].ontorrentdone = function() {
|
||||
if (torrentDoneEvent != null) {
|
||||
for (e in torrentDoneEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var lnkx = Browser.document.createScriptElement();
|
||||
lnkx.type = "text/javascript";
|
||||
lnkx.src = "Leenkx.js";
|
||||
lnkx.onload = function() {
|
||||
|
||||
js.Syntax.code('(1, eval)({0})', 'lxNew =function(url){ var cx = new Leenkx(url); return cx;}');
|
||||
|
||||
Leenkx.connections[net_Url].onopen = function() {
|
||||
if (openEvent != null) {
|
||||
for (e in openEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
#if js
|
||||
var lnxjs:Dynamic = js.Lib.global;
|
||||
var connectionUrl = net_Url;
|
||||
|
||||
if (lnxjs.Leenkx != null) {
|
||||
if (lnxjs.lnxNew == null) {
|
||||
js.Syntax.code('globalThis.lnxNew = function(url) { return new Leenkx(url); }');
|
||||
}
|
||||
Leenkx.connections[net_Url].onmessage = function() {
|
||||
if (messageEvent != null) {
|
||||
for (e in messageEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
} else {
|
||||
kha.Assets.loadBlobFromPath("Leenkx.js", function(b: kha.Blob) {
|
||||
if (b != null) {
|
||||
js.Syntax.code("(1,eval)({0})", b.toString());
|
||||
if (lnxjs.Leenkx != null && lnxjs.lnxNew == null) {
|
||||
js.Syntax.code('globalThis.lnxNew = function(url) { return new Leenkx(url); }');
|
||||
}
|
||||
} else {
|
||||
trace("Warning: Leenkx.js blob is null - file may not be in assets");
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onerror = function() {
|
||||
if (errorEvent != null) {
|
||||
for (e in errorEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onclose = function() {
|
||||
if (closeEvent != null) {
|
||||
for (e in closeEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onseen = function() {
|
||||
if (seenEvent != null) {
|
||||
for (e in seenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onserver = function() {
|
||||
if (serverEvent != null) {
|
||||
for (e in serverEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onconnections = function() {
|
||||
if (connectionsEvent != null) {
|
||||
for (e in connectionsEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onping = function() {
|
||||
if (pingEvent != null) {
|
||||
for (e in pingEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onleft = function() {
|
||||
if (leftEvent != null) {
|
||||
for (e in leftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontimeout = function() {
|
||||
if (timeoutEvent != null) {
|
||||
for (e in timeoutEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onrpc = function() {
|
||||
if (rpcEvent != null) {
|
||||
for (e in rpcEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onrpcresponse = function() {
|
||||
if (rpcresponseEvent != null) {
|
||||
for (e in rpcresponseEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onwireleft = function() {
|
||||
if (wireleftEvent != null) {
|
||||
for (e in wireleftEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onwireseen = function() {
|
||||
if (wireseenEvent != null) {
|
||||
for (e in wireseenEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontorrent = function() {
|
||||
if (torrentEvent != null) {
|
||||
for (e in torrentEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontracker = function() {
|
||||
if (trackerEvent != null) {
|
||||
for (e in trackerEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onannounce = function() {
|
||||
if (announceEvent != null) {
|
||||
for (e in announceEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].onload = function() {
|
||||
if (loadEvent != null) {
|
||||
for (e in loadEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Leenkx.connections[net_Url].ontorrentdone = function() {
|
||||
if (torrentDoneEvent != null) {
|
||||
for (e in torrentDoneEvent) {
|
||||
if (e.mask == object.uid) {
|
||||
e.onEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Leenkx.connections[net_Url].onload();
|
||||
//var script = 'leenkx.network.Leenkx.connections.h["' + net_Url + '"].onload();';
|
||||
//js.Syntax.code('(1, eval)({0})', script);
|
||||
|
||||
}
|
||||
|
||||
lnkx.onerror = function(error, i) {
|
||||
trace("ERROR - " + error + " | " + i);
|
||||
}
|
||||
|
||||
Browser.document.head.appendChild(lnkx);
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
}, function(err: kha.AssetError) {
|
||||
trace("ERROR loading Leenkx.js: " + err.url + " - " + err.error);
|
||||
Leenkx.connections[connectionUrl].onload();
|
||||
});
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@ class Log {
|
||||
|
||||
public static var mask:Int = 0;
|
||||
|
||||
#if sys
|
||||
public static var logFn:Dynamic->Void = Sys.println;
|
||||
#if (sys || kha_krom)
|
||||
public static var logFn:Dynamic->Void = function(data:Dynamic) { trace(data); };
|
||||
#elseif js
|
||||
public static var logFn:Dynamic->Void = js.html.Console.log;
|
||||
#end
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
package leenkx.network;
|
||||
|
||||
#if kha_krom
|
||||
typedef SecureSocketImpl = leenkx.network.krom.KromSecureSocket;
|
||||
#else
|
||||
typedef SecureSocketImpl = sys.ssl.Socket;
|
||||
#end
|
||||
|
||||
@ -12,6 +12,10 @@ typedef SocketImpl = leenkx.network.cs.NonBlockingSocket;
|
||||
|
||||
typedef SocketImpl = leenkx.network.nodejs.NodeSocket;
|
||||
|
||||
#elseif kha_krom
|
||||
|
||||
typedef SocketImpl = leenkx.network.krom.KromSocket;
|
||||
|
||||
#else
|
||||
|
||||
typedef SocketImpl = sys.net.Socket;
|
||||
|
||||
@ -2,7 +2,128 @@ package leenkx.network;
|
||||
|
||||
import leenkx.network.Types;
|
||||
|
||||
#if js
|
||||
#if kha_krom
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
#if (haxe_ver < 4)
|
||||
typedef JsBuffer = js.html.ArrayBuffer;
|
||||
#else
|
||||
typedef JsBuffer = js.lib.ArrayBuffer;
|
||||
#end
|
||||
|
||||
@:native("WebSocket")
|
||||
extern class NativeWebSocket {
|
||||
var binaryType:String;
|
||||
var onopen:Dynamic;
|
||||
var onclose:Dynamic;
|
||||
var onerror:Dynamic;
|
||||
var onmessage:Dynamic;
|
||||
var readyState:Int;
|
||||
function new(url:String):Void;
|
||||
function close(?code:Int, ?reason:String):Void;
|
||||
function send(data:Dynamic):Void;
|
||||
}
|
||||
|
||||
class WebSocket {
|
||||
public var _protocol:String;
|
||||
public var _host:String;
|
||||
public var _port:Int = 0;
|
||||
public var _path:String;
|
||||
private var _url:String;
|
||||
|
||||
private var _ws:NativeWebSocket = null;
|
||||
|
||||
public var binaryType:Dynamic;
|
||||
|
||||
public var onopen:Void->Void;
|
||||
public var onclose:Void->Void;
|
||||
public var onerror:Dynamic->Void;
|
||||
public var onmessage:MessageType->Void;
|
||||
|
||||
public function new(url:String, immediateOpen:Bool = true) {
|
||||
_url = url;
|
||||
parseUrl(url);
|
||||
if (immediateOpen) {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
private function parseUrl(url:String):Void {
|
||||
var urlArr = url.split(":");
|
||||
if (urlArr.length < 2) return;
|
||||
_protocol = urlArr[0];
|
||||
var hostPart = urlArr[1];
|
||||
if (hostPart.substr(0, 2) == "//") hostPart = hostPart.substr(2);
|
||||
_host = hostPart;
|
||||
if (urlArr.length >= 3) {
|
||||
var portPathPart = urlArr[2];
|
||||
var slashIndex = portPathPart.indexOf("/");
|
||||
if (slashIndex >= 0) {
|
||||
_port = Std.parseInt(portPathPart.substr(0, slashIndex));
|
||||
_path = portPathPart.substr(slashIndex);
|
||||
} else {
|
||||
_port = Std.parseInt(portPathPart);
|
||||
_path = "/";
|
||||
}
|
||||
} else {
|
||||
_port = (_protocol == "wss") ? 443 : 80;
|
||||
_path = "/";
|
||||
}
|
||||
if (_port == null || _port == 0) _port = (_protocol == "wss") ? 443 : 80;
|
||||
}
|
||||
|
||||
public function open():Void {
|
||||
if (_ws != null) {
|
||||
throw "Socket already connected";
|
||||
}
|
||||
_ws = new NativeWebSocket(_url);
|
||||
_ws.binaryType = "arraybuffer";
|
||||
|
||||
_ws.onopen = function() {
|
||||
if (onopen != null) onopen();
|
||||
};
|
||||
_ws.onclose = function() {
|
||||
if (onclose != null) onclose();
|
||||
};
|
||||
_ws.onerror = function(err:Dynamic) {
|
||||
if (onerror != null) onerror(err);
|
||||
};
|
||||
_ws.onmessage = function(event:Dynamic) {
|
||||
if (onmessage != null) {
|
||||
if (Std.isOfType(event.data, JsBuffer)) {
|
||||
var buffer = new Buffer();
|
||||
buffer.writeBytes(Bytes.ofData(event.data));
|
||||
onmessage(MessageType.BytesMessage(buffer));
|
||||
} else {
|
||||
onmessage(MessageType.StrMessage(event.data));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function close():Void {
|
||||
if (_ws != null) {
|
||||
_ws.close();
|
||||
_ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function send(msg:Dynamic):Void {
|
||||
if (_ws == null) return;
|
||||
if (Std.isOfType(msg, Bytes)) {
|
||||
var bytes:Bytes = cast msg;
|
||||
_ws.send(bytes.getData());
|
||||
} else if (Std.isOfType(msg, Buffer)) {
|
||||
var buffer:Buffer = cast msg;
|
||||
_ws.send(buffer.readAllAvailableBytes().getData());
|
||||
} else {
|
||||
_ws.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elseif js
|
||||
|
||||
import haxe.Constraints.Function;
|
||||
import haxe.io.Bytes;
|
||||
|
||||
@ -7,13 +7,22 @@ class WebSocketHandler extends Handler {
|
||||
|
||||
public function new(socket:SocketImpl) {
|
||||
super(socket);
|
||||
#if kha_krom
|
||||
_creationTime = kha.Scheduler.time();
|
||||
#else
|
||||
_creationTime = Sys.time();
|
||||
#end
|
||||
_socket.setBlocking(false);
|
||||
Log.debug('New socket handler', id);
|
||||
}
|
||||
|
||||
public override function handle() {
|
||||
if (this.state == State.Handshake && Sys.time() - _creationTime > (MAX_WAIT_TIME / 1000)) {
|
||||
#if kha_krom
|
||||
var currentTime = kha.Scheduler.time();
|
||||
#else
|
||||
var currentTime = Sys.time();
|
||||
#end
|
||||
if (this.state == State.Handshake && currentTime - _creationTime > (MAX_WAIT_TIME / 1000)) {
|
||||
Log.info('No handshake detected in ${MAX_WAIT_TIME}ms, closing connection', id);
|
||||
this.close();
|
||||
return;
|
||||
|
||||
@ -2,8 +2,10 @@ package leenkx.network;
|
||||
|
||||
import haxe.Constraints;
|
||||
|
||||
#if !kha_krom
|
||||
import sys.ssl.Key;
|
||||
import sys.ssl.Certificate;
|
||||
#end
|
||||
|
||||
@:generic
|
||||
class WebSocketSecureServer
|
||||
@ -14,11 +16,21 @@ class WebSocketSecureServer
|
||||
#end
|
||||
extends WebSocketServer<T> {
|
||||
|
||||
#if kha_krom
|
||||
private var _cert:Dynamic;
|
||||
private var _key:Dynamic;
|
||||
private var _caChain:Dynamic;
|
||||
#else
|
||||
private var _cert:Certificate;
|
||||
private var _key:Key;
|
||||
private var _caChain:Certificate;
|
||||
#end
|
||||
|
||||
#if kha_krom
|
||||
public function new(host:String, port:Int, cert:Dynamic, key:Dynamic, caChain:Dynamic, maxConnections:Int = 1) {
|
||||
#else
|
||||
public function new(host:String, port:Int, cert:Certificate, key:Key, caChain:Certificate, maxConnections:Int = 1) {
|
||||
#end
|
||||
super(host, port, maxConnections);
|
||||
|
||||
_cert=cert;
|
||||
|
||||
@ -3,6 +3,9 @@ package leenkx.network;
|
||||
import haxe.Constraints;
|
||||
import haxe.MainLoop;
|
||||
import haxe.io.Error;
|
||||
#if kha_krom
|
||||
import leenkx.network.krom.KromSocket.KromHost;
|
||||
#end
|
||||
|
||||
@:generic
|
||||
class WebSocketServer
|
||||
@ -51,11 +54,20 @@ class WebSocketServer
|
||||
_stopServer = false;
|
||||
_serverSocket = createSocket();
|
||||
_serverSocket.setBlocking(false);
|
||||
#if kha_krom
|
||||
_serverSocket.bind(new KromHost(_host), _port);
|
||||
#else
|
||||
_serverSocket.bind(new sys.net.Host(_host), _port);
|
||||
#end
|
||||
_serverSocket.listen(_maxConnections);
|
||||
Log.info('Starting server - ${_host}:${_port} (maxConnections: ${_maxConnections})');
|
||||
|
||||
#if cs
|
||||
#if kha_krom
|
||||
kha.Scheduler.addTimeTask(function() {
|
||||
tick();
|
||||
}, 0, sleepAmount);
|
||||
|
||||
#elseif cs
|
||||
while (true) {
|
||||
var continueLoop = tick();
|
||||
if (continueLoop == false) {
|
||||
|
||||
62
leenkx/Sources/leenkx/network/krom/KromSecureSocket.hx
Normal file
62
leenkx/Sources/leenkx/network/krom/KromSecureSocket.hx
Normal file
@ -0,0 +1,62 @@
|
||||
package leenkx.network.krom;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesOutput;
|
||||
import haxe.io.Error;
|
||||
import leenkx.network.krom.KromSocket.KromHost;
|
||||
|
||||
@:native("krom_socket_enable_ssl") extern function krom_socket_enable_ssl(id:Int):Bool;
|
||||
|
||||
class KromSecureSocket extends KromSocket {
|
||||
private var _sslEnabled:Bool = false;
|
||||
private var _hostname:String = null;
|
||||
private var _certPath:String = null;
|
||||
private var _keyPath:String = null;
|
||||
private var _caPath:String = null;
|
||||
public var verifyCert:Bool = true;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
public function setHostname(hostname:String):Void {
|
||||
_hostname = hostname;
|
||||
}
|
||||
|
||||
public function setCA(ca:Dynamic):Void {
|
||||
if (ca != null) {
|
||||
_caPath = Std.string(ca);
|
||||
}
|
||||
}
|
||||
|
||||
public function setCertificate(cert:Dynamic, key:Dynamic):Void {
|
||||
if (cert != null) {
|
||||
_certPath = Std.string(cert);
|
||||
}
|
||||
if (key != null) {
|
||||
_keyPath = Std.string(key);
|
||||
}
|
||||
}
|
||||
|
||||
public function enableSSL():Bool {
|
||||
if (getSocketId() >= 0 && !_sslEnabled) {
|
||||
var result = krom_socket_enable_ssl(getSocketId());
|
||||
if (result) {
|
||||
_sslEnabled = true;
|
||||
} else {
|
||||
trace("SecureSocket: Failed to enable SSL for socket " + getSocketId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return _sslEnabled;
|
||||
}
|
||||
|
||||
override public function connect(host:KromHost, port:Int):Void {
|
||||
super.connect(host, port);
|
||||
enableSSL();
|
||||
}
|
||||
|
||||
public function isSSLEnabled():Bool {
|
||||
return _sslEnabled;
|
||||
}
|
||||
}
|
||||
249
leenkx/Sources/leenkx/network/krom/KromSocket.hx
Normal file
249
leenkx/Sources/leenkx/network/krom/KromSocket.hx
Normal file
@ -0,0 +1,249 @@
|
||||
package leenkx.network.krom;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesInput;
|
||||
import haxe.io.BytesOutput;
|
||||
import haxe.io.Error;
|
||||
|
||||
#if (haxe_ver < 4)
|
||||
typedef JsBuffer = js.html.ArrayBuffer;
|
||||
#else
|
||||
typedef JsBuffer = js.lib.ArrayBuffer;
|
||||
#end
|
||||
|
||||
@:native("krom_socket_create") extern function krom_socket_create():Int;
|
||||
@:native("krom_socket_close") extern function krom_socket_close(id:Int):Void;
|
||||
@:native("krom_socket_bind") extern function krom_socket_bind(id:Int, addr:String, port:Int):Bool;
|
||||
@:native("krom_socket_listen") extern function krom_socket_listen(id:Int, backlog:Int):Bool;
|
||||
@:native("krom_socket_accept") extern function krom_socket_accept(id:Int):Int;
|
||||
@:native("krom_socket_connect") extern function krom_socket_connect(id:Int, host:String, port:Int):Bool;
|
||||
@:native("krom_socket_send") extern function krom_socket_send(id:Int, data:JsBuffer):Int;
|
||||
@:native("krom_socket_recv") extern function krom_socket_recv(id:Int, maxLen:Int):Dynamic;
|
||||
@:native("krom_socket_set_blocking") extern function krom_socket_set_blocking(id:Int, blocking:Bool):Void;
|
||||
@:native("krom_socket_is_connected") extern function krom_socket_is_connected(id:Int):Bool;
|
||||
|
||||
class KromSocket {
|
||||
private var _socketId:Int = -1;
|
||||
private var _host:KromHost = null;
|
||||
private var _port:Int = 0;
|
||||
private var _blocking:Bool = true;
|
||||
|
||||
public var input:KromSocketInput = null;
|
||||
public var output:KromSocketOutput = null;
|
||||
|
||||
private static var _connections:Array<KromSocket> = [];
|
||||
private var _newConnections:Array<KromSocket> = [];
|
||||
|
||||
public function new() {
|
||||
_socketId = krom_socket_create();
|
||||
if (_socketId >= 0) {
|
||||
input = new KromSocketInput(this);
|
||||
output = new KromSocketOutput(this);
|
||||
}
|
||||
}
|
||||
|
||||
private function setSocketId(id:Int):Void {
|
||||
_socketId = id;
|
||||
if (_socketId >= 0) {
|
||||
input = new KromSocketInput(this);
|
||||
output = new KromSocketOutput(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSocketId():Int {
|
||||
return _socketId;
|
||||
}
|
||||
|
||||
public function bind(host:KromHost, port:Int):Void {
|
||||
_host = host;
|
||||
_port = port;
|
||||
if (_socketId >= 0) {
|
||||
if (!krom_socket_bind(_socketId, host.host, port)) {
|
||||
trace("Failed to bind to " + host.host + ":" + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function listen(connections:Int):Void {
|
||||
if (_host == null) {
|
||||
throw "You must bind the Socket to an address!";
|
||||
}
|
||||
if (_socketId >= 0) {
|
||||
if (!krom_socket_listen(_socketId, connections)) {
|
||||
trace("Failed to listen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function accept():KromSocket {
|
||||
if (_socketId < 0) {
|
||||
throw "Blocking";
|
||||
}
|
||||
|
||||
var clientId = krom_socket_accept(_socketId);
|
||||
if (clientId < 0) {
|
||||
throw "Blocking";
|
||||
}
|
||||
|
||||
var clientSocket = new KromSocket();
|
||||
clientSocket.setSocketId(clientId);
|
||||
_connections.push(clientSocket);
|
||||
return clientSocket;
|
||||
}
|
||||
|
||||
public function connect(host:KromHost, port:Int):Void {
|
||||
if (_socketId >= 0) {
|
||||
if (!krom_socket_connect(_socketId, host.host, port)) {
|
||||
throw "Connection failed to " + host.host + ":" + port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setBlocking(blocking:Bool):Void {
|
||||
_blocking = blocking;
|
||||
if (_socketId >= 0) {
|
||||
krom_socket_set_blocking(_socketId, blocking);
|
||||
}
|
||||
}
|
||||
|
||||
public function setTimeout(timeout:Int):Void {
|
||||
// TODO: Re-investigate timeout handling
|
||||
}
|
||||
|
||||
public function peer():{host:KromHost, port:Int} {
|
||||
return {host: _host != null ? _host : new KromHost("0.0.0.0"), port: _port};
|
||||
}
|
||||
|
||||
public function host():{host:KromHost, port:Int} {
|
||||
return {host: _host != null ? _host : new KromHost("0.0.0.0"), port: _port};
|
||||
}
|
||||
|
||||
public function close():Void {
|
||||
if (_socketId >= 0) {
|
||||
krom_socket_close(_socketId);
|
||||
_socketId = -1;
|
||||
}
|
||||
_connections.remove(this);
|
||||
}
|
||||
|
||||
public function sendRaw(data:Bytes):Int {
|
||||
var arrayBuffer:JsBuffer = data.getData();
|
||||
var exactBuffer:JsBuffer = untyped arrayBuffer.slice(0, data.length);
|
||||
return krom_socket_send(_socketId, exactBuffer);
|
||||
}
|
||||
|
||||
// throws "ConnectionClosed" if peer disconnected
|
||||
public function recvRaw(maxLength:Int):Bytes {
|
||||
if (_socketId < 0) return null;
|
||||
var result:Dynamic = krom_socket_recv(_socketId, maxLength);
|
||||
// V8 returns ArrayBuffer data, null would-block, or -2 connection closed
|
||||
if (result == -2) {
|
||||
_socketId = -1;
|
||||
throw "ConnectionClosed";
|
||||
}
|
||||
if (result == null) return null;
|
||||
return Bytes.ofData(result);
|
||||
}
|
||||
|
||||
public function isConnected():Bool {
|
||||
if (_socketId < 0) return false;
|
||||
return krom_socket_is_connected(_socketId);
|
||||
}
|
||||
|
||||
public static function select(read:Array<KromSocket>, write:Array<KromSocket>, others:Array<KromSocket>, ?timeout:Float):{read:Array<KromSocket>, write:Array<KromSocket>, others:Array<KromSocket>} {
|
||||
var readable:Array<KromSocket> = [];
|
||||
|
||||
if (read != null) {
|
||||
for (sock in read) {
|
||||
if (sock._socketId >= 0) {
|
||||
readable.push(sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readable.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
read: readable,
|
||||
write: write,
|
||||
others: others
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class KromSocketInput {
|
||||
private var _socket:KromSocket;
|
||||
public var hasData:Bool = false;
|
||||
private var _buffer:Bytes = null;
|
||||
private var _bufferPos:Int = 0;
|
||||
|
||||
public function new(socket:KromSocket) {
|
||||
_socket = socket;
|
||||
}
|
||||
|
||||
public function readBytes(buf:Bytes, pos:Int, len:Int):Int {
|
||||
var data = _socket.recvRaw(len);
|
||||
if (data == null || data.length == 0) {
|
||||
hasData = false;
|
||||
throw Error.Blocked;
|
||||
}
|
||||
hasData = true;
|
||||
var toRead = data.length < len ? data.length : len;
|
||||
buf.blit(pos, data, 0, toRead);
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public function read(nbytes:Int):Bytes {
|
||||
var buf = Bytes.alloc(nbytes);
|
||||
var read = readBytes(buf, 0, nbytes);
|
||||
if (read < nbytes) {
|
||||
return buf.sub(0, read);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
class KromSocketOutput {
|
||||
private var _socket:KromSocket;
|
||||
private var _buffer:BytesOutput;
|
||||
|
||||
public function new(socket:KromSocket) {
|
||||
_socket = socket;
|
||||
_buffer = new BytesOutput();
|
||||
}
|
||||
|
||||
public function write(data:Bytes):Void {
|
||||
_buffer.write(data);
|
||||
}
|
||||
|
||||
public function writeBytes(buf:Bytes, pos:Int, len:Int):Int {
|
||||
_buffer.writeBytes(buf, pos, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
public function flush():Void {
|
||||
var data = _buffer.getBytes();
|
||||
if (data.length > 0) {
|
||||
_socket.sendRaw(data);
|
||||
_buffer = new BytesOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KromHost {
|
||||
public var host:String;
|
||||
|
||||
public function new(hostname:String) {
|
||||
host = hostname;
|
||||
}
|
||||
|
||||
public static function resolve(hostname:String):String {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
@ -13,11 +13,15 @@ import iron.math.Vec2;
|
||||
import haxe.ds.Vector;
|
||||
import iron.object.Object;
|
||||
import iron.object.Animation;
|
||||
import iron.object.Animation.ActionSampler;
|
||||
#if lnx_skin
|
||||
import iron.object.BoneAnimation;
|
||||
#end
|
||||
import iron.object.ObjectAnimation;
|
||||
|
||||
class AnimationExtension {
|
||||
|
||||
#if lnx_skin
|
||||
public static function solveIKBlend(boneAnimation: BoneAnimation, actionMats: Array<Mat4>, effector: TObj, goal: Vec4, precision = 0.01, maxIterations = 100, chainLenght = 100, pole: Vec4 = null, rollAngle = 0.0, influence = 0.0, layerMask: Null<Int> = null, threshold: FastFloat = 0.1) {
|
||||
|
||||
var matsBlend = boneAnimation.initMatsEmpty();
|
||||
@ -31,7 +35,9 @@ class AnimationExtension {
|
||||
boneAnimation.solveIK(actionMats, effector, goal, precision, maxIterations, chainLenght, pole, rollAngle);
|
||||
boneAnimation.blendAction(matsBlend, actionMats, actionMats, influence, layerMask, threshold);
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_skin
|
||||
public static function solveTwoBoneIKBlend(boneAnimation: BoneAnimation, actionMats: Array<Mat4>, effector: TObj, goal: Vec4, pole: Vec4 = null, rollAngle = 0.0, influence = 0.0, layerMask: Null<Int> = null, threshold: FastFloat = 0.1) {
|
||||
|
||||
var matsBlend = boneAnimation.initMatsEmpty();
|
||||
@ -45,6 +51,7 @@ class AnimationExtension {
|
||||
boneAnimation.solveTwoBoneIK(actionMats, effector, goal, pole, rollAngle);
|
||||
boneAnimation.blendAction(matsBlend, actionMats, actionMats, influence, layerMask, threshold);
|
||||
}
|
||||
#end
|
||||
|
||||
static inline function sortWeights(vecs: Array<Vec2>, sampleVec: Vec2): Map<Int, Vec2> {
|
||||
var weightIndex: Array<WeightIndex> = [];
|
||||
@ -113,7 +120,9 @@ class AnimationExtension {
|
||||
|
||||
class OneShotOperator {
|
||||
|
||||
#if lnx_skin
|
||||
var boneAnimation: BoneAnimation;
|
||||
#end
|
||||
var objectAnimation: ObjectAnimation;
|
||||
var isArmature: Bool;
|
||||
var oneShotAction: ActionSampler;
|
||||
@ -136,12 +145,15 @@ class OneShotOperator {
|
||||
|
||||
var animation = animation;
|
||||
this.oneShotAction = oneShotAction;
|
||||
#if lnx_skin
|
||||
if(Std.isOfType(animation, BoneAnimation)) {
|
||||
boneAnimation = cast animation;
|
||||
tempMatsBone = boneAnimation.initMatsEmpty();
|
||||
this.isArmature = true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
#end
|
||||
{
|
||||
objectAnimation = cast animation;
|
||||
tempMatsObject = objectAnimation.initTransformMap();
|
||||
this.isArmature = false;
|
||||
@ -150,10 +162,13 @@ class OneShotOperator {
|
||||
}
|
||||
|
||||
function initOneShot() {
|
||||
#if lnx_skin
|
||||
if(isArmature) {
|
||||
totalFrames = boneAnimation.getTotalFrames(oneShotAction) - 1;
|
||||
}
|
||||
else {
|
||||
else
|
||||
#end
|
||||
{
|
||||
totalFrames = objectAnimation.getTotalFrames(oneShotAction) - 1;
|
||||
}
|
||||
blendFactor = 0.0;
|
||||
@ -257,7 +272,9 @@ class OneShotOperator {
|
||||
|
||||
class SwitchActionOperator {
|
||||
|
||||
#if lnx_skin
|
||||
var boneAnimation: BoneAnimation;
|
||||
#end
|
||||
var objectAnimation: ObjectAnimation;
|
||||
var isArmature: Bool;
|
||||
var boneLayer: Null<Int>;
|
||||
@ -268,12 +285,14 @@ class SwitchActionOperator {
|
||||
var tween: TAnim = null;
|
||||
|
||||
public function new(animation: Animation) {
|
||||
|
||||
#if lnx_skin
|
||||
if(Std.isOfType(animation, BoneAnimation)) {
|
||||
boneAnimation = cast animation;
|
||||
this.isArmature = true;
|
||||
}
|
||||
else {
|
||||
else
|
||||
#end
|
||||
{
|
||||
objectAnimation = cast animation;
|
||||
this.isArmature = false;
|
||||
}
|
||||
@ -338,6 +357,7 @@ class SwitchActionOperator {
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_skin
|
||||
class SimpleBiPedalIK {
|
||||
|
||||
var object: Object;
|
||||
@ -477,6 +497,7 @@ class SimpleBiPedalIK {
|
||||
}
|
||||
|
||||
}
|
||||
#end
|
||||
|
||||
@:enum abstract SelectAction(Int) from Int to Int {
|
||||
var action1 = 0;
|
||||
|
||||
56
leenkx/Sources/leenkx/renderpath/FSR1.hx
Normal file
56
leenkx/Sources/leenkx/renderpath/FSR1.hx
Normal file
@ -0,0 +1,56 @@
|
||||
package leenkx.renderpath;
|
||||
|
||||
import iron.RenderPath;
|
||||
|
||||
/**
|
||||
* AMD FidelityFX Super Resolution (FSR V1) - Spatial Upscaling
|
||||
* MIT License -
|
||||
* Quality sharpness values:
|
||||
* - Ultra_Quality: 0.0
|
||||
* - Quality: 0.25
|
||||
* - Balanced: 0.5
|
||||
* - Performance: 0.75
|
||||
*/
|
||||
class FSR1 {
|
||||
|
||||
static var path: RenderPath;
|
||||
|
||||
public static inline var ULTRA_QUALITY: Float = 0.0;
|
||||
public static inline var QUALITY: Float = 0.25;
|
||||
public static inline var BALANCED: Float = 0.5;
|
||||
public static inline var PERFORMANCE: Float = 0.75;
|
||||
|
||||
public static function init(_path: RenderPath) {
|
||||
path = _path;
|
||||
|
||||
#if rp_fsr1
|
||||
path.loadShader("shader_datas/fsr1_easu_pass/fsr1_easu_pass");
|
||||
path.loadShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
#end
|
||||
}
|
||||
|
||||
public static function getSharpnessFromPreset(preset: String): Float {
|
||||
return switch(preset) {
|
||||
case "Ultra_Quality": ULTRA_QUALITY;
|
||||
case "Quality": QUALITY;
|
||||
case "Balanced": BALANCED;
|
||||
case "Performance": PERFORMANCE;
|
||||
default: QUALITY;
|
||||
};
|
||||
}
|
||||
|
||||
public static function applyRCAS(inputTarget: String, outputTarget: String = null) {
|
||||
#if rp_fsr1
|
||||
if (path == null) return;
|
||||
|
||||
if (outputTarget != null) {
|
||||
path.setTarget(outputTarget);
|
||||
} else {
|
||||
path.setTarget("");
|
||||
}
|
||||
|
||||
path.bindTarget(inputTarget, "tex");
|
||||
path.drawShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
#end
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,13 @@ class Inc {
|
||||
static var spotIndex = 0;
|
||||
static var lastFrame = -1;
|
||||
|
||||
#if lnx_shadowmap_atlas
|
||||
static var tilesToRemove: Array<ShadowMapTile> = [];
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
static var tilesToChangeSize: Array<ShadowMapTile> = [];
|
||||
#end
|
||||
#end
|
||||
|
||||
#if ((rp_voxels != 'Off') && lnx_config)
|
||||
static var voxelsCreated = false;
|
||||
#end
|
||||
@ -149,12 +156,39 @@ class Inc {
|
||||
}
|
||||
|
||||
public static function bindShadowMapAtlas() {
|
||||
var hasAtlas = false;
|
||||
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
||||
path.bindTarget(atlas.target, atlas.target);
|
||||
hasAtlas = true;
|
||||
}
|
||||
|
||||
if (!hasAtlas) {
|
||||
#if lnx_shadowmap_atlas_single_map
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlas");
|
||||
#else
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlasSun");
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlasPoint");
|
||||
path.bindTarget("empty_shadowmap", "shadowMapAtlasSpot");
|
||||
#end
|
||||
}
|
||||
|
||||
#if rp_shadowmap_transparent
|
||||
var hasAtlasT = false;
|
||||
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlasesTransparent) {
|
||||
path.bindTarget(atlas.target, atlas.target);
|
||||
hasAtlasT = true;
|
||||
}
|
||||
|
||||
if (!hasAtlasT) {
|
||||
#if lnx_shadowmap_atlas_single_map
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasTransparent");
|
||||
#else
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasSunTransparent");
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasPointTransparent");
|
||||
path.bindTarget("empty_shadowmap_transparent", "shadowMapAtlasSpotTransparent");
|
||||
#end
|
||||
}
|
||||
#end
|
||||
}
|
||||
@ -223,9 +257,9 @@ class Inc {
|
||||
#end
|
||||
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlases) {
|
||||
var tilesToRemove = [];
|
||||
tilesToRemove.resize(0);
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
var tilesToChangeSize = [];
|
||||
tilesToChangeSize.resize(0);
|
||||
#end
|
||||
|
||||
var shadowmap = getShadowMapAtlas(atlas, false);
|
||||
@ -296,13 +330,14 @@ class Inc {
|
||||
path.currentFace = -1;
|
||||
}
|
||||
path.endStream();
|
||||
path.currentG = null;
|
||||
}
|
||||
|
||||
#if rp_shadowmap_transparent
|
||||
for (atlas in ShadowMapAtlas.shadowMapAtlasesTransparent) {
|
||||
var tilesToRemove = [];
|
||||
tilesToRemove.resize(0);
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
var tilesToChangeSize = [];
|
||||
tilesToChangeSize.resize(0);
|
||||
#end
|
||||
|
||||
var shadowmap = getShadowMapAtlas(atlas, true);
|
||||
@ -374,6 +409,8 @@ class Inc {
|
||||
}
|
||||
path.endStream();
|
||||
|
||||
path.currentG = null;
|
||||
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
for (tile in tilesToChangeSize) {
|
||||
tilesToRemove.push(tile);
|
||||
@ -382,9 +419,6 @@ class Inc {
|
||||
if (newTile != null)
|
||||
atlas.activeTiles.push(newTile);
|
||||
}
|
||||
// update point light data after changing size of tiles to avoid render issues
|
||||
updatePointLightAtlasData(false);
|
||||
// update point light data after changing size of tiles to avoid render issues
|
||||
updatePointLightAtlasData(true);
|
||||
#end
|
||||
|
||||
@ -554,8 +588,16 @@ class Inc {
|
||||
}
|
||||
if (superSample != config.rp_supersample) {
|
||||
superSample = config.rp_supersample;
|
||||
|
||||
var inVRPresent = false;
|
||||
#if (kha_webgl && lnx_vr)
|
||||
inVRPresent = kha.vr.VrInterface.instance != null &&
|
||||
kha.vr.VrInterface.instance.IsPresenting();
|
||||
#end
|
||||
|
||||
for (rt in path.renderTargets) {
|
||||
if (rt.raw.width == 0 && rt.raw.scale != null) {
|
||||
// VR present mode renderpath automatically overrides Inc.superSample to 4.0
|
||||
rt.raw.scale = getSuperSampling();
|
||||
}
|
||||
}
|
||||
@ -1372,6 +1414,10 @@ class ShadowMapAtlas {
|
||||
return;
|
||||
}
|
||||
|
||||
mainTile.forEachTileLinked(function(tile) {
|
||||
tile.isTransparent = transparent;
|
||||
});
|
||||
|
||||
atlas.activeTiles.push(mainTile);
|
||||
// notify the tile on light remove
|
||||
light.tileNotifyOnRemove = mainTile.notifyOnLightRemove;
|
||||
@ -1560,6 +1606,7 @@ class ShadowMapTile {
|
||||
public var size:Int;
|
||||
public var tiles:Array<ShadowMapTile> = [];
|
||||
public var linkedTile:ShadowMapTile = null;
|
||||
public var isTransparent:Bool = false; // track for tile
|
||||
|
||||
#if lnx_shadowmap_atlas_lod
|
||||
public var parentTile: ShadowMapTile = null;
|
||||
@ -1791,7 +1838,11 @@ class ShadowMapTile {
|
||||
public function freeTile(): Void {
|
||||
// prevent duplicates
|
||||
if (light != null && unlockLight) {
|
||||
light.lightInAtlas = false;
|
||||
if (isTransparent) {
|
||||
light.lightInAtlasTransparent = false;
|
||||
} else {
|
||||
light.lightInAtlas = false;
|
||||
}
|
||||
unlockLight = false;
|
||||
}
|
||||
|
||||
|
||||
@ -15,9 +15,9 @@ class RenderPathDeferred {
|
||||
static var bloomUpsampler: Upsampler;
|
||||
#end
|
||||
|
||||
#if (rp_ssgi == "SSGI")
|
||||
static var ssgitex = "singleb";
|
||||
static var ssgitexb = "singleb";
|
||||
#if rp_ssgi
|
||||
static var ssgitex = "ssgi_a";
|
||||
static var ssgitexb = "ssgi_b";
|
||||
#end
|
||||
|
||||
public static inline function setTargetMeshes() {
|
||||
@ -187,40 +187,32 @@ class RenderPathDeferred {
|
||||
path.loadShader("shader_datas/copy_pass/copy_pass");
|
||||
#end
|
||||
|
||||
#if ((rp_ssgi == "RTGI") || (rp_ssgi == "RTAO"))
|
||||
{
|
||||
path.loadShader("shader_datas/ssgi_pass/ssgi_pass");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
}
|
||||
#elseif (rp_ssgi == "SSAO")
|
||||
#if rp_ssao
|
||||
{
|
||||
path.loadShader("shader_datas/ssao_pass/ssao_pass");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
}
|
||||
#elseif (rp_ssgi == "SSGI")
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
path.loadShader("shader_datas/ssgi_pass/ssgi_pass");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
path.loadShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
path.loadShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_x");
|
||||
path.loadShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_y");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssgi != "Off")
|
||||
#if rp_ssao
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singlea";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
#end
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
#if rp_ssao_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
@ -230,11 +222,35 @@ class RenderPathDeferred {
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssao_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_a";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_b";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
@ -252,9 +268,6 @@ class RenderPathDeferred {
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half // Do we keep this ?
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
@ -264,37 +277,6 @@ class RenderPathDeferred {
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "volumetrica";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half // Do we keep this ?
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "volumetricb";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
#if rp_ssgi_half
|
||||
t.scale *= 0.5;
|
||||
#end
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
@ -381,6 +363,14 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_fsr1
|
||||
{
|
||||
path.loadShader("shader_datas/fsr1_easu_pass/fsr1_easu_pass");
|
||||
path.loadShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
path.loadShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_autoexposure
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
@ -401,7 +391,7 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssgi_half || rp_voxels != "Off") //we need half depth for resolve voxels shaders
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || rp_voxels != "Off") //we need half depth for resolve voxels shaders
|
||||
{
|
||||
path.loadShader("shader_datas/downsample_depth/downsample_depth");
|
||||
var t = new RenderTargetRaw();
|
||||
@ -423,6 +413,7 @@ class RenderPathDeferred {
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = Inc.getHdrFormat();
|
||||
t.scale = Inc.getSuperSampling();
|
||||
t.depth_buffer = "main";
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
@ -511,6 +502,31 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
// TODO: Re-investigate creating fallback empty shadowmaps for WebGL when no lights exist
|
||||
#if lnx_shadowmap_atlas
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "empty_shadowmap";
|
||||
t.width = 1;
|
||||
t.height = 1;
|
||||
t.format = "DEPTH16";
|
||||
path.createRenderTarget(t);
|
||||
path.setTarget("empty_shadowmap");
|
||||
path.clearTarget(null, 1.0);
|
||||
|
||||
#if rp_shadowmap_transparent
|
||||
var t2 = new RenderTargetRaw();
|
||||
t2.name = "empty_shadowmap_transparent";
|
||||
t2.width = 1;
|
||||
t2.height = 1;
|
||||
t2.format = "RGBA64";
|
||||
path.createRenderTarget(t2);
|
||||
path.setTarget("empty_shadowmap_transparent");
|
||||
path.clearTarget(0xffffffff, null);
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.loadShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
@ -536,7 +552,7 @@ class RenderPathDeferred {
|
||||
#if (rp_ssrefr || lnx_voxelgi_refract)
|
||||
{
|
||||
path.setTarget("gbuffer_refraction");
|
||||
path.clearTarget(0xffffff00);
|
||||
path.clearTarget(0xffff00ff);
|
||||
}
|
||||
#end
|
||||
|
||||
@ -585,7 +601,7 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
path.setTarget("half");
|
||||
path.bindTarget("_main", "texdepth");
|
||||
path.drawShader("shader_datas/downsample_depth/downsample_depth");
|
||||
@ -600,9 +616,9 @@ class RenderPathDeferred {
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (rp_ssgi == "SSAO")
|
||||
#if rp_ssao
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
if (leenkx.data.Config.raw.rp_ssao != false) {
|
||||
path.setTarget("singlea");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
@ -619,10 +635,12 @@ class RenderPathDeferred {
|
||||
path.drawShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
}
|
||||
}
|
||||
#elseif (rp_ssgi == "SSGI")
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
path.setTarget("singlea");
|
||||
path.setTarget("ssgi_a");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.bindTarget("gbuffer1", "gbuffer1");
|
||||
@ -631,29 +649,20 @@ class RenderPathDeferred {
|
||||
path.bindTarget("gbuffer_emission", "gbufferEmission");
|
||||
}
|
||||
#end
|
||||
#if rp_gbuffer2
|
||||
path.bindTarget("gbuffer2", "sveloc");
|
||||
#end
|
||||
#if rp_shadowmap
|
||||
{
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
path.drawShader("shader_datas/ssgi_pass/ssgi_pass");
|
||||
path.setTarget("singleb");
|
||||
path.bindTarget("singlea", "tex");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.drawShader("shader_datas/blur_edge_pass/blur_edge_pass_x");
|
||||
|
||||
path.setTarget("singlea");
|
||||
path.bindTarget("singleb", "tex");
|
||||
path.setTarget("ssgi_b");
|
||||
path.bindTarget("ssgi_a", "tex");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.drawShader("shader_datas/blur_edge_pass/blur_edge_pass_y");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_x");
|
||||
|
||||
path.setTarget("ssgi_a");
|
||||
path.bindTarget("ssgi_b", "tex");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.drawShader("shader_datas/ssgi_blur_pass/ssgi_blur_pass_y");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -740,9 +749,9 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssgi != "Off")
|
||||
#if rp_ssao
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
if (leenkx.data.Config.raw.rp_ssao != false) {
|
||||
path.bindTarget("singlea", "ssaotex");
|
||||
}
|
||||
else {
|
||||
@ -751,6 +760,14 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssgi != false) {
|
||||
path.bindTarget("ssgi_a", "ssgitex");
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
var voxelao_pass = false;
|
||||
#if (rp_voxels != "Off")
|
||||
if (leenkx.data.Config.raw.rp_gi != false)
|
||||
@ -848,24 +865,9 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
#if (rp_translucency && !rp_ssrefr)
|
||||
{
|
||||
path.setTarget("volumetrica");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
path.drawShader("shader_datas/volumetric_light/volumetric_light");
|
||||
|
||||
path.setTarget("volumetricb");
|
||||
path.bindTarget("volumetrica", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_pass/blur_bilat_pass_x");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("volumetricb", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_blend_pass/blur_bilat_blend_pass_y");
|
||||
Inc.drawTranslucency("tex");
|
||||
}
|
||||
#end
|
||||
|
||||
@ -937,12 +939,6 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_translucency && !rp_ssrefr)
|
||||
{
|
||||
Inc.drawTranslucency("tex");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssrefr
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
||||
@ -982,19 +978,55 @@ class RenderPathDeferred {
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex"); // scene with refractive objects
|
||||
path.bindTarget("refr", "tex1"); // background without refractive objects
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbufferD1", "gbufferD1");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
path.setTarget("volumetrica");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
path.drawShader("shader_datas/volumetric_light/volumetric_light");
|
||||
|
||||
path.setTarget("volumetricb");
|
||||
path.bindTarget("volumetrica", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_pass/blur_bilat_pass_x");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("volumetricb", "tex");
|
||||
path.drawShader("shader_datas/blur_bilat_blend_pass/blur_bilat_blend_pass_y");
|
||||
}
|
||||
#end
|
||||
|
||||
#if ((rp_motionblur == "Camera") || (rp_motionblur == "Object"))
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_motionblur != false) {
|
||||
@ -1029,22 +1061,6 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
// We are just about to enter compositing, add more custom passes here
|
||||
// #if rp_custom_pass
|
||||
// {
|
||||
// }
|
||||
// #end
|
||||
|
||||
// Begin compositor
|
||||
#if rp_autoexposure
|
||||
@ -1084,6 +1100,7 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
path.setTarget(target);
|
||||
path.clearTarget(0x00000000);
|
||||
|
||||
path.bindTarget("tex", "tex");
|
||||
#if rp_compositordepth
|
||||
@ -1167,12 +1184,58 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_fsr1
|
||||
{
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
path.setTarget("bufb");
|
||||
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufb";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#else
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget(target != "" ? target : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufa";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#end
|
||||
|
||||
path.setTarget(fsrDest);
|
||||
path.bindTarget(fsrSource, "tex");
|
||||
path.drawShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
{
|
||||
var finalTarget = "";
|
||||
path.setTarget(finalTarget);
|
||||
path.bindTarget(framebuffer, "tex");
|
||||
path.drawShader("shader_datas/supersample_resolve/supersample_resolve");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
// VR: Composite final output to XR framebuffer
|
||||
#if (kha_webgl && lnx_vr)
|
||||
if (iron.RenderPath.isVRPresenting()) {
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
var vrCompositeSource = framebuffer;
|
||||
#else
|
||||
var vrCompositeSource = "tex";
|
||||
#end
|
||||
if (vrCompositeSource == "") vrCompositeSource = "tex";
|
||||
|
||||
path.compositeToXR(vrCompositeSource);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
@ -29,7 +29,12 @@ class RenderPathForward {
|
||||
}
|
||||
#else
|
||||
{
|
||||
path.setTarget("");
|
||||
var isVR = iron.RenderPath.isVRPresenting() || iron.RenderPath.isVRSimulateMode();
|
||||
if (isVR) {
|
||||
path.setTarget("lbuffer0");
|
||||
} else {
|
||||
path.setTarget("");
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@ -251,25 +256,18 @@ class RenderPathForward {
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (rp_volumetriclight || rp_ssgi != "Off")
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
#if (rp_volumetriclight)
|
||||
path.loadShader("shader_datas/volumetric_light/volumetric_light");
|
||||
path.loadShader("shader_datas/blur_bilat_pass/blur_bilat_pass_x");
|
||||
path.loadShader("shader_datas/blur_bilat_blend_pass/blur_bilat_blend_pass_y");
|
||||
#end
|
||||
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singlea";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
#end
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
|
||||
@ -278,11 +276,51 @@ class RenderPathForward {
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
#if (rp_ssgi == "SSGI")
|
||||
t.format = "RGBA32";
|
||||
#else
|
||||
t.format = "R8";
|
||||
#end
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssao
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singlea";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "singleb";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "R8";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssgi
|
||||
{
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_a";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
|
||||
var t = new RenderTargetRaw();
|
||||
t.name = "ssgi_b";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "RGBA32";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
@ -308,7 +346,15 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
#if rp_fsr1
|
||||
{
|
||||
path.loadShader("shader_datas/fsr1_easu_pass/fsr1_easu_pass");
|
||||
path.loadShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
path.loadShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || (rp_voxels != "Off"))
|
||||
{
|
||||
path.loadShader("shader_datas/downsample_depth/downsample_depth");
|
||||
var t = new RenderTargetRaw();
|
||||
@ -422,14 +468,21 @@ class RenderPathForward {
|
||||
|
||||
#if (rp_ssrefr || lnx_voxelgi_refract)
|
||||
{
|
||||
path.setTarget("gbuffer_refraction"); // Only clear gbuffer0
|
||||
path.clearTarget(0xffffff00);
|
||||
path.setTarget("gbuffer_refraction");
|
||||
path.clearTarget(0xffff00ff);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_depthprepass
|
||||
{
|
||||
path.drawMeshes("depth");
|
||||
#if rp_stereo
|
||||
var isVR = iron.RenderPath.isVRPresenting() || iron.RenderPath.isVRSimulateMode();
|
||||
if (!isVR) {
|
||||
#end
|
||||
path.drawMeshes("depth");
|
||||
#if rp_stereo
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
@ -461,16 +514,23 @@ class RenderPathForward {
|
||||
#if rp_stereo
|
||||
{
|
||||
path.drawStereo(drawMeshes);
|
||||
if (iron.RenderPath.isVRPresenting()) {
|
||||
#if (kha_webgl && lnx_vr)
|
||||
// split-screen lbuffer0 to XR framebuffer
|
||||
path.compositeToXR("lbuffer0");
|
||||
#end
|
||||
return;
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
RenderPathCreator.drawMeshes();
|
||||
drawMeshes();
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_render_to_texture || rp_voxels != "Off")
|
||||
{
|
||||
#if (rp_ssr_half || rp_ssgi_half || rp_voxels != "Off")
|
||||
#if (rp_ssr_half || rp_ssao_half || rp_ssgi_half || rp_voxels != "Off")
|
||||
path.setTarget("half");
|
||||
path.bindTarget("_main", "texdepth");
|
||||
path.drawShader("shader_datas/downsample_depth/downsample_depth");
|
||||
@ -515,8 +575,7 @@ class RenderPathForward {
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
@ -525,6 +584,9 @@ class RenderPathForward {
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -595,7 +657,7 @@ class RenderPathForward {
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
@ -604,6 +666,9 @@ class RenderPathForward {
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -614,6 +679,18 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_volumetriclight
|
||||
{
|
||||
path.setTarget("singlea");
|
||||
@ -654,18 +731,6 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_chromatic_aberration
|
||||
{
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.drawShader("shader_datas/chromatic_aberration_pass/chromatic_aberration_pass");
|
||||
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
var framebuffer = "buf";
|
||||
#else
|
||||
@ -685,6 +750,7 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
path.setTarget(target);
|
||||
path.clearTarget(0x00000000);
|
||||
|
||||
#if rp_compositordepth
|
||||
{
|
||||
@ -732,6 +798,40 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_fsr1
|
||||
{
|
||||
// FSR1 RCAS sharpening pass applied after AA, expects sRGB [0-1] input
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
// SMAA outputs to framebuffer which needs an intermediate buffer
|
||||
path.setTarget("bufb");
|
||||
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufb";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#else
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget(target != "" ? target : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufa";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#end
|
||||
|
||||
path.setTarget(fsrDest);
|
||||
path.bindTarget(fsrSource, "tex");
|
||||
path.drawShader("shader_datas/fsr1_rcas_pass/fsr1_rcas_pass");
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
{
|
||||
var finalTarget = "";
|
||||
|
||||
@ -96,6 +96,25 @@ class Starter {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (js && lnx_jolt)
|
||||
function loadLibJolt(name: String) {
|
||||
kha.Assets.loadBlobFromPath(name, function(b: kha.Blob) {
|
||||
js.Syntax.code("(1,eval)({0})", b.toString());
|
||||
#if kha_krom
|
||||
js.Syntax.code("Jolt({print:function(s){iron.log(s);},instantiateWasm:function(imports,successCallback) {
|
||||
var wasmbin = Krom.loadBlob('jolt.wasm.wasm');
|
||||
var module = new WebAssembly.Module(wasmbin);
|
||||
var inst = new WebAssembly.Instance(module,imports);
|
||||
successCallback(inst);
|
||||
return inst.exports;
|
||||
}}).then(function(m){ Jolt=m; tasks--; start();})");
|
||||
#else
|
||||
js.Syntax.code("Jolt({print:function(s){iron.log(s);},locateFile:function(f){return 'jolt.wasm.wasm';}}).then(function(m){ Jolt=m; tasks--; start();})");
|
||||
#end
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
#if (js && lnx_navigation)
|
||||
function loadLib(name: String) {
|
||||
kha.Assets.loadBlobFromPath(name, function(b: kha.Blob) {
|
||||
@ -126,6 +145,11 @@ class Starter {
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (js && lnx_jolt)
|
||||
tasks++;
|
||||
loadLibJolt("jolt.wasm.js");
|
||||
#end
|
||||
|
||||
#if (js && lnx_navigation)
|
||||
tasks++;
|
||||
#if kha_krom
|
||||
|
||||
@ -7,13 +7,11 @@ class KinematicCharacterController extends iron.Trait { public function new() {
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef KinematicCharacterController = leenkx.trait.physics.bullet.KinematicCharacterController;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef KinematicCharacterController = leenkx.trait.physics.jolt.KinematicCharacterController;
|
||||
#else
|
||||
|
||||
typedef KinematicCharacterController = leenkx.trait.physics.oimo.KinematicCharacterController;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
@ -21,6 +21,8 @@ class PhysicsCache {
|
||||
|
||||
#if lnx_bullet
|
||||
var rb = object.getTrait(leenkx.trait.physics.bullet.RigidBody);
|
||||
#elseif lnx_jolt
|
||||
var rb = object.getTrait(leenkx.trait.physics.jolt.RigidBody);
|
||||
#else
|
||||
var rb = object.getTrait(leenkx.trait.physics.oimo.RigidBody);
|
||||
#end
|
||||
@ -42,6 +44,9 @@ class PhysicsCache {
|
||||
#if lnx_bullet
|
||||
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
|
||||
return leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
|
||||
#elseif lnx_jolt
|
||||
if (leenkx.trait.physics.jolt.PhysicsWorld.active == null) return null;
|
||||
return leenkx.trait.physics.jolt.PhysicsWorld.active.getContacts(rb);
|
||||
#else
|
||||
if (leenkx.trait.physics.oimo.PhysicsWorld.active == null) return null;
|
||||
return leenkx.trait.physics.oimo.PhysicsWorld.active.getContacts(rb);
|
||||
@ -61,6 +66,9 @@ class PhysicsCache {
|
||||
#if lnx_bullet
|
||||
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
|
||||
var contacts = leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
|
||||
#elseif lnx_jolt
|
||||
if (leenkx.trait.physics.jolt.PhysicsWorld.active == null) return null;
|
||||
var contacts = leenkx.trait.physics.jolt.PhysicsWorld.active.getContacts(rb);
|
||||
#else
|
||||
if (leenkx.trait.physics.oimo.PhysicsWorld.active == null) return null;
|
||||
var contacts = leenkx.trait.physics.oimo.PhysicsWorld.active.getContacts(rb);
|
||||
|
||||
@ -10,6 +10,9 @@ class PhysicsConstraint extends iron.Trait { public function new() { super(); }
|
||||
#if lnx_bullet
|
||||
typedef PhysicsConstraint = leenkx.trait.physics.bullet.PhysicsConstraint;
|
||||
typedef ConstraintAxis = leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintAxis;
|
||||
#elseif lnx_jolt
|
||||
typedef PhysicsConstraint = leenkx.trait.physics.jolt.PhysicsConstraint;
|
||||
typedef ConstraintAxis = leenkx.trait.physics.jolt.PhysicsConstraint.ConstraintType;
|
||||
#else
|
||||
typedef PhysicsConstraint = leenkx.trait.physics.oimo.PhysicsConstraint;
|
||||
typedef ConstraintAxis = leenkx.trait.physics.oimo.PhysicsConstraint.ConstraintAxis;
|
||||
|
||||
@ -7,13 +7,11 @@ class PhysicsHook extends iron.Trait { public function new() { super(); } }
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef PhysicsHook = leenkx.trait.physics.bullet.PhysicsHook;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef PhysicsHook = leenkx.trait.physics.jolt.PhysicsHook;
|
||||
#else
|
||||
|
||||
typedef PhysicsHook = leenkx.trait.physics.oimo.PhysicsHook;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
@ -10,6 +10,9 @@ class PhysicsWorld extends iron.Trait { public function new() { super(); } }
|
||||
#if lnx_bullet
|
||||
typedef PhysicsWorld = leenkx.trait.physics.bullet.PhysicsWorld;
|
||||
typedef Hit = leenkx.trait.physics.bullet.PhysicsWorld.Hit;
|
||||
#elseif lnx_jolt
|
||||
typedef PhysicsWorld = leenkx.trait.physics.jolt.PhysicsWorld;
|
||||
typedef Hit = leenkx.trait.physics.jolt.PhysicsWorld.Hit;
|
||||
#else
|
||||
typedef PhysicsWorld = leenkx.trait.physics.oimo.PhysicsWorld;
|
||||
typedef Hit = leenkx.trait.physics.oimo.PhysicsWorld.Hit;
|
||||
|
||||
@ -8,15 +8,14 @@ class RigidBody extends iron.Trait { public function new() { super(); } }
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef RigidBody = leenkx.trait.physics.bullet.RigidBody;
|
||||
typedef Shape = leenkx.trait.physics.bullet.RigidBody.Shape;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef RigidBody = leenkx.trait.physics.jolt.RigidBody;
|
||||
typedef Shape = leenkx.trait.physics.jolt.RigidBody.Shape;
|
||||
#else
|
||||
|
||||
typedef RigidBody = leenkx.trait.physics.oimo.RigidBody;
|
||||
typedef Shape = leenkx.trait.physics.oimo.RigidBody.Shape;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package leenkx.trait.physics;
|
||||
|
||||
import iron.Trait;
|
||||
|
||||
#if (!lnx_physics_soft)
|
||||
|
||||
class SoftBody extends Trait { public function new() { super(); } }
|
||||
@ -7,13 +9,11 @@ class SoftBody extends Trait { public function new() { super(); } }
|
||||
#else
|
||||
|
||||
#if lnx_bullet
|
||||
|
||||
typedef SoftBody = leenkx.trait.physics.bullet.SoftBody;
|
||||
|
||||
#elseif lnx_jolt
|
||||
typedef SoftBody = leenkx.trait.physics.jolt.SoftBody;
|
||||
#else
|
||||
|
||||
typedef SoftBody = leenkx.trait.physics.oimo.SoftBody;
|
||||
|
||||
#end
|
||||
|
||||
#end
|
||||
|
||||
376
leenkx/Sources/leenkx/trait/physics/jolt/DebugDrawHelper.hx
Normal file
376
leenkx/Sources/leenkx/trait/physics/jolt/DebugDrawHelper.hx
Normal file
@ -0,0 +1,376 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.System;
|
||||
import iron.math.Vec4;
|
||||
|
||||
#if lnx_ui
|
||||
import leenkx.ui.Canvas;
|
||||
#end
|
||||
|
||||
using StringTools;
|
||||
|
||||
enum abstract DebugDrawMode(Int) from Int to Int {
|
||||
var NoDebug = 0;
|
||||
var DrawWireframe = 1;
|
||||
var DrawAabb = 2;
|
||||
var DrawContactPoints = 4;
|
||||
var DrawConstraints = 8;
|
||||
var DrawConstraintLimits = 16;
|
||||
var DrawRayCast = 32;
|
||||
var DrawAll = 63;
|
||||
|
||||
@:op(A | B) static function or(lhs:DebugDrawMode, rhs:DebugDrawMode):DebugDrawMode;
|
||||
@:op(A & B) static function and(lhs:DebugDrawMode, rhs:DebugDrawMode):DebugDrawMode;
|
||||
}
|
||||
|
||||
class DebugDrawHelper {
|
||||
static inline var contactPointSizePx = 4;
|
||||
static inline var contactPointNormalColor = 0xffffffff;
|
||||
|
||||
final rayCastColor:Vec4 = new Vec4(0.0, 1.0, 0.0);
|
||||
final rayCastHitColor:Vec4 = new Vec4(1.0, 0.0, 0.0);
|
||||
final rayCastHitPointColor:Vec4 = new Vec4(1.0, 1.0, 0.0);
|
||||
final wireframeColor:Vec4 = new Vec4(0.0, 1.0, 0.0);
|
||||
final aabbColor:Vec4 = new Vec4(1.0, 1.0, 0.0);
|
||||
final constraintColor:Vec4 = new Vec4(0.0, 0.5, 1.0);
|
||||
|
||||
final physicsWorld:PhysicsWorld;
|
||||
final lines:Array<LineData> = [];
|
||||
final texts:Array<TextData> = [];
|
||||
var font:kha.Font = null;
|
||||
|
||||
var rayCasts:Array<TRayCastData> = [];
|
||||
var debugDrawMode:DebugDrawMode = NoDebug;
|
||||
|
||||
public function new(physicsWorld:PhysicsWorld, debugDrawMode:DebugDrawMode) {
|
||||
this.physicsWorld = physicsWorld;
|
||||
this.debugDrawMode = debugDrawMode;
|
||||
|
||||
#if lnx_ui
|
||||
iron.data.Data.getFont(Canvas.defaultFontName, function(defaultFont:kha.Font) {
|
||||
font = defaultFont;
|
||||
});
|
||||
#end
|
||||
|
||||
iron.App.notifyOnRender2D(onRender);
|
||||
if (debugDrawMode & DrawRayCast != 0) {
|
||||
iron.App.notifyOnFixedUpdate(function() {
|
||||
rayCasts.resize(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function drawLine(fromX:Float, fromY:Float, fromZ:Float, toX:Float, toY:Float, toZ:Float, r:Float, g:Float, b:Float) {
|
||||
final fromScreenSpace = worldToScreenFast(new Vec4(fromX, fromY, fromZ, 1.0));
|
||||
final toScreenSpace = worldToScreenFast(new Vec4(toX, toY, toZ, 1.0));
|
||||
|
||||
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(r, g, b, 1.0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function drawLineVec(from:Vec4, to:Vec4, color:Vec4) {
|
||||
drawLine(from.x, from.y, from.z, to.x, to.y, to.z, color.x, color.y, color.z);
|
||||
}
|
||||
|
||||
public function drawContactPoint(pointX:Float, pointY:Float, pointZ:Float, normalX:Float, normalY:Float, normalZ:Float, distance:Float, r:Float, g:Float, b:Float) {
|
||||
final contactPointScreenSpace = worldToScreenFast(new Vec4(pointX, pointY, pointZ, 1.0));
|
||||
final toScreenSpace = worldToScreenFast(new Vec4(pointX + normalX * distance, pointY + normalY * distance, pointZ + normalZ * distance, 1.0));
|
||||
|
||||
if (contactPointScreenSpace.w == 1) {
|
||||
final color = kha.Color.fromFloats(r, g, b, 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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function rayCast(rayCastData:TRayCastData) {
|
||||
rayCasts.push(rayCastData);
|
||||
}
|
||||
|
||||
function drawRayCast(f:Vec4, t:Vec4, hit:Bool) {
|
||||
final from = worldToScreenFast(f.clone());
|
||||
final to = worldToScreenFast(t.clone());
|
||||
var c:kha.Color;
|
||||
|
||||
if (from.w == 1 && to.w == 1) {
|
||||
if (hit)
|
||||
c = kha.Color.fromFloats(rayCastHitColor.x, rayCastHitColor.y, rayCastHitColor.z);
|
||||
else
|
||||
c = kha.Color.fromFloats(rayCastColor.x, rayCastColor.y, rayCastColor.z);
|
||||
|
||||
lines.push({
|
||||
fromX: from.x,
|
||||
fromY: from.y,
|
||||
toX: to.x,
|
||||
toY: to.y,
|
||||
color: c
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function drawHitPoint(hp:Vec4) {
|
||||
final hitPoint = worldToScreenFast(hp.clone());
|
||||
final c = kha.Color.fromFloats(rayCastHitPointColor.x, rayCastHitPointColor.y, rayCastHitPointColor.z);
|
||||
|
||||
if (hitPoint.w == 1) {
|
||||
lines.push({
|
||||
fromX: hitPoint.x - contactPointSizePx,
|
||||
fromY: hitPoint.y - contactPointSizePx,
|
||||
toX: hitPoint.x + contactPointSizePx,
|
||||
toY: hitPoint.y + contactPointSizePx,
|
||||
color: c
|
||||
});
|
||||
|
||||
lines.push({
|
||||
fromX: hitPoint.x - contactPointSizePx,
|
||||
fromY: hitPoint.y + contactPointSizePx,
|
||||
toX: hitPoint.x + contactPointSizePx,
|
||||
toY: hitPoint.y - contactPointSizePx,
|
||||
color: c
|
||||
});
|
||||
|
||||
if (font != null) {
|
||||
texts.push({
|
||||
x: hitPoint.x,
|
||||
y: hitPoint.y,
|
||||
color: c,
|
||||
text: 'RAYCAST HIT'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function drawBox(center:Vec4, halfExtents:Vec4, color:Vec4) {
|
||||
var c = center;
|
||||
var h = halfExtents;
|
||||
|
||||
// Bottom face
|
||||
drawLine(c.x - h.x, c.y - h.y, c.z - h.z, c.x + h.x, c.y - h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y - h.y, c.z - h.z, c.x + h.x, c.y + h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y + h.y, c.z - h.z, c.x - h.x, c.y + h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x - h.x, c.y + h.y, c.z - h.z, c.x - h.x, c.y - h.y, c.z - h.z, color.x, color.y, color.z);
|
||||
|
||||
// Top face
|
||||
drawLine(c.x - h.x, c.y - h.y, c.z + h.z, c.x + h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y - h.y, c.z + h.z, c.x + h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y + h.y, c.z + h.z, c.x - h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x - h.x, c.y + h.y, c.z + h.z, c.x - h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
|
||||
// Vertical edges
|
||||
drawLine(c.x - h.x, c.y - h.y, c.z - h.z, c.x - h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y - h.y, c.z - h.z, c.x + h.x, c.y - h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x + h.x, c.y + h.y, c.z - h.z, c.x + h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
drawLine(c.x - h.x, c.y + h.y, c.z - h.z, c.x - h.x, c.y + h.y, c.z + h.z, color.x, color.y, color.z);
|
||||
}
|
||||
|
||||
public function drawSphere(center:Vec4, radius:Float, color:Vec4) {
|
||||
final segments = 16;
|
||||
final step = Math.PI * 2 / segments;
|
||||
|
||||
// XY circle
|
||||
for (i in 0...segments) {
|
||||
var angle1 = i * step;
|
||||
var angle2 = (i + 1) * step;
|
||||
drawLine(
|
||||
center.x + Math.cos(angle1) * radius, center.y + Math.sin(angle1) * radius, center.z,
|
||||
center.x + Math.cos(angle2) * radius, center.y + Math.sin(angle2) * radius, center.z,
|
||||
color.x, color.y, color.z
|
||||
);
|
||||
}
|
||||
|
||||
// XZ circle
|
||||
for (i in 0...segments) {
|
||||
var angle1 = i * step;
|
||||
var angle2 = (i + 1) * step;
|
||||
drawLine(
|
||||
center.x + Math.cos(angle1) * radius, center.y, center.z + Math.sin(angle1) * radius,
|
||||
center.x + Math.cos(angle2) * radius, center.y, center.z + Math.sin(angle2) * radius,
|
||||
color.x, color.y, color.z
|
||||
);
|
||||
}
|
||||
|
||||
// YZ circle
|
||||
for (i in 0...segments) {
|
||||
var angle1 = i * step;
|
||||
var angle2 = (i + 1) * step;
|
||||
drawLine(
|
||||
center.x, center.y + Math.cos(angle1) * radius, center.z + Math.sin(angle1) * radius,
|
||||
center.x, center.y + Math.cos(angle2) * radius, center.z + Math.sin(angle2) * radius,
|
||||
color.x, color.y, color.z
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function setDebugMode(debugDrawMode:DebugDrawMode) {
|
||||
this.debugDrawMode = debugDrawMode;
|
||||
}
|
||||
|
||||
public function getDebugMode():DebugDrawMode {
|
||||
return debugDrawMode;
|
||||
}
|
||||
|
||||
function drawBodyWireframe(body:RigidBody) {
|
||||
if (body == null || body.object == null)
|
||||
return;
|
||||
|
||||
var transform = body.object.transform;
|
||||
var pos = transform.world.getLoc();
|
||||
var dim = transform.dim;
|
||||
var halfExtents = new Vec4(dim.x * 0.5, dim.y * 0.5, dim.z * 0.5);
|
||||
|
||||
drawBox(pos, halfExtents, wireframeColor);
|
||||
}
|
||||
|
||||
function drawBodyAabb(body:RigidBody) {
|
||||
if (body == null || body.object == null)
|
||||
return;
|
||||
|
||||
var transform = body.object.transform;
|
||||
var pos = transform.world.getLoc();
|
||||
var dim = transform.dim;
|
||||
var halfExtents = new Vec4(dim.x * 0.5, dim.y * 0.5, dim.z * 0.5);
|
||||
|
||||
drawBox(pos, halfExtents, aabbColor);
|
||||
}
|
||||
|
||||
function drawConstraintDebug(constraint:PhysicsConstraint) {
|
||||
if (constraint == null || constraint.body1 == null || constraint.body2 == null)
|
||||
return;
|
||||
|
||||
var pos1 = constraint.body1.object.transform.world.getLoc();
|
||||
var pos2 = constraint.body2.object.transform.world.getLoc();
|
||||
|
||||
drawLineVec(pos1, pos2, constraintColor);
|
||||
}
|
||||
|
||||
function onRender(g:kha.graphics2.Graphics) {
|
||||
if (getDebugMode() == NoDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw physics debug info
|
||||
if (debugDrawMode & DrawWireframe != 0 || debugDrawMode & DrawAabb != 0) {
|
||||
for (body in physicsWorld.rbMap) {
|
||||
if (debugDrawMode & DrawWireframe != 0) {
|
||||
drawBodyWireframe(body);
|
||||
}
|
||||
if (debugDrawMode & DrawAabb != 0) {
|
||||
drawBodyAabb(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debugDrawMode & DrawConstraints != 0) {
|
||||
for (constraint in physicsWorld.constraints) {
|
||||
drawConstraintDebug(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (debugDrawMode & DrawRayCast != 0) {
|
||||
for (rayCastData in rayCasts) {
|
||||
if (rayCastData.hasHit) {
|
||||
drawRayCast(rayCastData.from, rayCastData.hitPoint, true);
|
||||
drawHitPoint(rayCastData.hitPoint);
|
||||
} else {
|
||||
drawRayCast(rayCastData.from, rayCastData.to, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@:structInit
|
||||
typedef TRayCastData = {
|
||||
var from:Vec4;
|
||||
var to:Vec4;
|
||||
var hasHit:Bool;
|
||||
@:optional var hitPoint:Vec4;
|
||||
@:optional var hitNormal:Vec4;
|
||||
}
|
||||
|
||||
#end
|
||||
@ -0,0 +1,380 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
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;
|
||||
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;
|
||||
var bodyScaleY:Float;
|
||||
var bodyScaleZ:Float;
|
||||
var currentScaleX:Float;
|
||||
var currentScaleY:Float;
|
||||
var currentScaleZ:Float;
|
||||
var jumpSpeed:Float;
|
||||
|
||||
public var body:jolt.Jt.Body;
|
||||
public var bodyId:jolt.Jt.BodyID;
|
||||
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:jolt.Jt.Vec3;
|
||||
static var quat1:jolt.Jt.Quat;
|
||||
static var quat = new Quat();
|
||||
|
||||
var walkDirection:Vec4 = new Vec4();
|
||||
var gravityEnabled = true;
|
||||
var gravityFactor = 1.0;
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
|
||||
transform = object.transform;
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
if (physics == null) {
|
||||
new PhysicsWorld();
|
||||
physics = PhysicsWorld.active;
|
||||
}
|
||||
|
||||
#if js
|
||||
// Check if Jolt is initialized - defer if not
|
||||
if (!physics.physicsReady) {
|
||||
haxe.Timer.delay(init, 16);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
ready = true;
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
quat1 = new jolt.Jt.Quat(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
var joltShape:jolt.Jt.Shape = createShape();
|
||||
|
||||
var pos = transform.world.getLoc();
|
||||
var rot = new iron.math.Quat();
|
||||
rot.fromMat(transform.world);
|
||||
|
||||
// Jolt uses RVec3 for world positions
|
||||
var jPos = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
var jRot = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
var settings = new jolt.Jt.BodyCreationSettings(joltShape, jPos, jRot, 1, 1);
|
||||
|
||||
// Use kinematic body for character controller
|
||||
settings.mFriction = friction;
|
||||
settings.mRestitution = restitution;
|
||||
|
||||
body = physics.bodyInterface.CreateBody(settings);
|
||||
bodyId = body.GetID();
|
||||
|
||||
#if hl
|
||||
settings.delete();
|
||||
jPos.delete();
|
||||
jRot.delete();
|
||||
#end
|
||||
|
||||
physics.bodyInterface.AddBody(bodyId, 1);
|
||||
|
||||
bodyScaleX = currentScaleX = transform.scale.x;
|
||||
bodyScaleY = currentScaleY = transform.scale.y;
|
||||
bodyScaleZ = currentScaleZ = transform.scale.z;
|
||||
|
||||
id = nextId;
|
||||
nextId++;
|
||||
|
||||
if (onReady != null)
|
||||
onReady();
|
||||
}
|
||||
|
||||
function createShape():jolt.Jt.Shape {
|
||||
var t = transform;
|
||||
if (shape == ControllerShape.Box) {
|
||||
var halfExtent = new jolt.Jt.Vec3(withMargin(t.dim.x / 2), withMargin(t.dim.y / 2), withMargin(t.dim.z / 2));
|
||||
return new jolt.Jt.BoxShape(halfExtent);
|
||||
} else if (shape == ControllerShape.Sphere) {
|
||||
var width = Math.max(t.dim.x, Math.max(t.dim.y, t.dim.z));
|
||||
return new jolt.Jt.SphereShape(withMargin(width / 2));
|
||||
} else if (shape == ControllerShape.Cylinder) {
|
||||
var radius = Math.max(t.dim.x, t.dim.y) / 2;
|
||||
var halfHeight = t.dim.z / 2;
|
||||
return new jolt.Jt.CylinderShape(withMargin(halfHeight), withMargin(radius));
|
||||
} else if (shape == ControllerShape.Capsule) {
|
||||
var r = t.dim.x / 2;
|
||||
var halfHeight = (t.dim.z - r * 2) / 2;
|
||||
if (halfHeight < 0.01) halfHeight = 0.01;
|
||||
return new jolt.Jt.CapsuleShape(withMargin(halfHeight), withMargin(r));
|
||||
} else if (shape == ControllerShape.Cone) {
|
||||
var radius = Math.max(t.dim.x, t.dim.y) / 2;
|
||||
var halfHeight = t.dim.z / 2;
|
||||
return new jolt.Jt.CylinderShape(withMargin(halfHeight), withMargin(radius));
|
||||
}
|
||||
// Default capsule
|
||||
var r = t.dim.x / 2;
|
||||
var halfHeight = (t.dim.z - r * 2) / 2;
|
||||
if (halfHeight < 0.01) halfHeight = 0.01;
|
||||
return new jolt.Jt.CapsuleShape(withMargin(halfHeight), withMargin(r));
|
||||
}
|
||||
|
||||
function lateUpdate() {
|
||||
if (!ready)
|
||||
return;
|
||||
if (object.animation != null || animated) {
|
||||
syncTransform();
|
||||
} else {
|
||||
var p = physics.bodyInterface.GetPosition(bodyId);
|
||||
var q = physics.bodyInterface.GetRotation(bodyId);
|
||||
|
||||
#if js
|
||||
transform.loc.set(cast p.GetX(), cast p.GetY(), cast p.GetZ());
|
||||
#else
|
||||
transform.loc.set(p.GetX(), p.GetY(), p.GetZ());
|
||||
#end
|
||||
transform.rot.set(q.GetX(), q.GetY(), q.GetZ(), q.GetW());
|
||||
|
||||
#if hl
|
||||
p.delete();
|
||||
q.delete();
|
||||
#end
|
||||
|
||||
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 {
|
||||
// Simple ground check - could be improved with raycast
|
||||
return onGround();
|
||||
}
|
||||
|
||||
public function onGround():Bool {
|
||||
// Perform downward raycast to check ground
|
||||
var pos = transform.world.getLoc();
|
||||
var from = new Vec4(pos.x, pos.y, pos.z);
|
||||
var to = new Vec4(pos.x, pos.y, pos.z - 0.2);
|
||||
var hit = physics.rayCast(from, to);
|
||||
return hit != null;
|
||||
}
|
||||
|
||||
public function setJumpSpeed(jumpSpeed:Float) {
|
||||
this.jumpSpeed = jumpSpeed;
|
||||
}
|
||||
|
||||
public function setFallSpeed(fallSpeed:Float) {
|
||||
// Jolt handles this through gravity
|
||||
}
|
||||
|
||||
public function setMaxSlope(slopeRadians:Float) {
|
||||
// Would need CharacterVirtual for proper slope handling
|
||||
}
|
||||
|
||||
public function getMaxSlope():Float {
|
||||
return Math.PI / 4; // 45 degrees default
|
||||
}
|
||||
|
||||
public function setMaxJumpHeight(maxJumpHeight:Float) {
|
||||
// Calculate jump speed from height: v = sqrt(2 * g * h)
|
||||
var g = physics.getGravity().length();
|
||||
jumpSpeed = Math.sqrt(2 * g * maxJumpHeight);
|
||||
}
|
||||
|
||||
public function setWalkDirection(dir:Vec4) {
|
||||
walkDirection.setFrom(dir);
|
||||
var vel = new jolt.Jt.Vec3(dir.x, dir.y, dir.z);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function setUpInterpolate(value:Bool) {
|
||||
// Not directly applicable in Jolt kinematic body
|
||||
}
|
||||
|
||||
public function jump() {
|
||||
var currentVel = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var vel = new jolt.Jt.Vec3(currentVel.GetX(), currentVel.GetY(), jumpSpeed);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl currentVel.delete(); vel.delete(); #end
|
||||
}
|
||||
|
||||
public function removeFromWorld() {
|
||||
if (physics != null && ready) {
|
||||
physics.bodyInterface.RemoveBody(bodyId);
|
||||
physics.bodyInterface.DestroyBody(bodyId);
|
||||
}
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
physics.bodyInterface.ActivateBody(bodyId);
|
||||
}
|
||||
|
||||
public function disableGravity() {
|
||||
gravityEnabled = false;
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, 0.0);
|
||||
}
|
||||
|
||||
public function enableGravity() {
|
||||
gravityEnabled = true;
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, gravityFactor);
|
||||
}
|
||||
|
||||
public function setGravity(f:Float) {
|
||||
gravityFactor = f / 9.81; // Normalize
|
||||
if (gravityEnabled) {
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, gravityFactor);
|
||||
}
|
||||
}
|
||||
|
||||
public function setActivationState(newState:Int) {
|
||||
if (newState == ControllerActivationState.NoDeactivation) {
|
||||
// Keep active - Jolt handles this differently
|
||||
activate();
|
||||
}
|
||||
}
|
||||
|
||||
public function setFriction(f:Float) {
|
||||
physics.bodyInterface.SetFriction(bodyId, f);
|
||||
this.friction = f;
|
||||
}
|
||||
|
||||
public function syncTransform() {
|
||||
var t = transform;
|
||||
t.buildMatrix();
|
||||
var pos = t.world.getLoc();
|
||||
var rot = new iron.math.Quat();
|
||||
rot.fromMat(t.world);
|
||||
|
||||
// Jolt uses RVec3 for world positions
|
||||
var p = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
var q = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
physics.bodyInterface.SetPosition(bodyId, p, 0);
|
||||
physics.bodyInterface.SetRotation(bodyId, q, 0);
|
||||
#if hl p.delete(); q.delete(); #end
|
||||
|
||||
activate();
|
||||
}
|
||||
|
||||
public function getLinearVelocity():Vec4 {
|
||||
var vel = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var result = new Vec4(vel.GetX(), vel.GetY(), vel.GetZ());
|
||||
#if hl vel.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setLinearVelocity(velocity:Vec4) {
|
||||
var vel = new jolt.Jt.Vec3(velocity.x, velocity.y, velocity.z);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getPosition():Vec4 {
|
||||
var pos = physics.bodyInterface.GetPosition(bodyId);
|
||||
var result = new Vec4(pos.GetX(), pos.GetY(), pos.GetZ());
|
||||
#if hl pos.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setPosition(position:Vec4) {
|
||||
var p = new jolt.Jt.RVec3(position.x, position.y, position.z);
|
||||
physics.bodyInterface.SetPosition(bodyId, p, 0);
|
||||
#if hl p.delete(); #end
|
||||
}
|
||||
|
||||
public function warp(position:Vec4) {
|
||||
setPosition(position);
|
||||
var zeroVel = new jolt.Jt.Vec3(0, 0, 0);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, zeroVel);
|
||||
#if hl zeroVel.delete(); #end
|
||||
}
|
||||
|
||||
public function move(direction:Vec4, speed:Float) {
|
||||
var moveVel = new Vec4(direction.x * speed, direction.y * speed, direction.z * speed);
|
||||
|
||||
// Preserve vertical velocity for jumping/falling
|
||||
var currentVel = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var vel = new jolt.Jt.Vec3(moveVel.x, moveVel.y, currentVel.GetZ());
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl currentVel.delete(); vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getGroundState():Int {
|
||||
if (onGround()) {
|
||||
return 0; // OnGround
|
||||
}
|
||||
return 3; // InAir
|
||||
}
|
||||
|
||||
public function isSupported():Bool {
|
||||
return onGround();
|
||||
}
|
||||
}
|
||||
|
||||
@: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
|
||||
405
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsConstraint.hx
Normal file
405
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsConstraint.hx
Normal file
@ -0,0 +1,405 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Mat4;
|
||||
import iron.object.Object;
|
||||
|
||||
@: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 Distance = 7;
|
||||
}
|
||||
|
||||
class PhysicsConstraint extends Trait {
|
||||
public var id:Int;
|
||||
public var physics:PhysicsWorld;
|
||||
public var body1:RigidBody;
|
||||
public var body2:RigidBody;
|
||||
public var type:ConstraintType;
|
||||
public var con:jolt.Jt.Constraint;
|
||||
public var conReady:Bool = false;
|
||||
public var disableCollisions:Bool;
|
||||
|
||||
var body1Obj:Object;
|
||||
var body2Obj:Object;
|
||||
var limits:Array<Float>;
|
||||
var breakingThreshold:Float;
|
||||
|
||||
static var nextId = 0;
|
||||
|
||||
public function new(body1:Object, body2:Object, type:ConstraintType, disableCollisions:Bool = false, breakingThreshold:Float = 0.0,
|
||||
limits:Array<Float> = null) {
|
||||
super();
|
||||
|
||||
this.type = type;
|
||||
this.disableCollisions = disableCollisions;
|
||||
this.breakingThreshold = breakingThreshold;
|
||||
this.limits = limits;
|
||||
this.id = nextId++;
|
||||
this.body1Obj = body1;
|
||||
this.body2Obj = body2;
|
||||
|
||||
notifyOnInit(function() {
|
||||
this.body1 = body1.getTrait(RigidBody);
|
||||
this.body2 = body2.getTrait(RigidBody);
|
||||
tryInit();
|
||||
});
|
||||
|
||||
notifyOnRemove(removeFromWorld);
|
||||
}
|
||||
|
||||
function tryInit() {
|
||||
if (this.body1 != null && this.body1.ready && this.body2 != null && this.body2.ready) {
|
||||
init();
|
||||
} else if (this.body1 != null || this.body2 != null) {
|
||||
// Bodies exist but not ready yet, retry next frame
|
||||
iron.App.notifyOnUpdate(retryInit);
|
||||
}
|
||||
}
|
||||
|
||||
function retryInit() {
|
||||
iron.App.removeUpdate(retryInit);
|
||||
tryInit();
|
||||
}
|
||||
|
||||
function init() {
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
// Compute constraint frames in each body's local space (exactly matches Bullet approach)
|
||||
var t = object.transform; // pivot object
|
||||
var t1 = body1Obj.transform; // body1 object
|
||||
var t2 = body2Obj.transform; // body2 object
|
||||
|
||||
// Frame In A: pivot transform in body1's local space
|
||||
var frameT = t.world.clone();
|
||||
var frameInA = t1.world.clone();
|
||||
frameInA.getInverse(frameInA);
|
||||
frameT.multmat(frameInA);
|
||||
frameInA = frameT.clone();
|
||||
|
||||
// Frame In B: pivot transform in body2's local space
|
||||
frameT = t.world.clone();
|
||||
var frameInB = t2.world.clone();
|
||||
frameInB.getInverse(frameInB);
|
||||
frameT.multmat(frameInB);
|
||||
frameInB = frameT.clone();
|
||||
|
||||
// Decompose frames to get local positions and orientations
|
||||
var locA = new Vec4();
|
||||
var rotA = new Quat();
|
||||
var sclA = new Vec4();
|
||||
frameInA.decompose(locA, rotA, sclA);
|
||||
|
||||
var locB = new Vec4();
|
||||
var rotB = new Quat();
|
||||
var sclB = new Vec4();
|
||||
frameInB.decompose(locB, rotB, sclB);
|
||||
|
||||
// Extract local axes from each frame (normalized to remove scale)
|
||||
var rightA = frameInA.right().normalize();
|
||||
var upA = frameInA.up().normalize();
|
||||
var rightB = frameInB.right().normalize();
|
||||
var upB = frameInB.up().normalize();
|
||||
|
||||
// Create Jolt vectors for body1 local frame
|
||||
var jPt1 = new jolt.Jt.RVec3(locA.x, locA.y, locA.z);
|
||||
var jAxX1 = new jolt.Jt.Vec3(rightA.x, rightA.y, rightA.z);
|
||||
var jAxY1 = new jolt.Jt.Vec3(upA.x, upA.y, upA.z);
|
||||
|
||||
// Create Jolt vectors for body2 local frame
|
||||
var jPt2 = new jolt.Jt.RVec3(locB.x, locB.y, locB.z);
|
||||
var jAxX2 = new jolt.Jt.Vec3(rightB.x, rightB.y, rightB.z);
|
||||
var jAxY2 = new jolt.Jt.Vec3(upB.x, upB.y, upB.z);
|
||||
|
||||
switch (type) {
|
||||
case Fixed:
|
||||
var settings = new jolt.Jt.FixedConstraintSettings();
|
||||
settings.mSpace = 0; // LocalToBodyCOM
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Point:
|
||||
var settings = new jolt.Jt.PointConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Hinge:
|
||||
var settings = new jolt.Jt.HingeConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mHingeAxis1 = jAxY1;
|
||||
settings.mHingeAxis2 = jAxY2;
|
||||
settings.mNormalAxis1 = jAxX1;
|
||||
settings.mNormalAxis2 = jAxX2;
|
||||
if (limits != null && limits.length >= 3 && limits[0] != 0) {
|
||||
settings.mLimitsMin = limits[1];
|
||||
settings.mLimitsMax = limits[2];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Slider:
|
||||
var settings = new jolt.Jt.SliderConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mSliderAxis1 = jAxX1;
|
||||
settings.mSliderAxis2 = jAxX2;
|
||||
settings.mNormalAxis1 = jAxY1;
|
||||
settings.mNormalAxis2 = jAxY2;
|
||||
if (limits != null && limits.length >= 3 && limits[0] != 0) {
|
||||
settings.mLimitsMin = limits[1];
|
||||
settings.mLimitsMax = limits[2];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Distance:
|
||||
var settings = new jolt.Jt.DistanceConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
if (limits != null && limits.length >= 2) {
|
||||
settings.mMinDistance = limits[0];
|
||||
settings.mMaxDistance = limits[1];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Piston:
|
||||
var settings = new jolt.Jt.SliderConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mSliderAxis1 = jAxY1;
|
||||
settings.mSliderAxis2 = jAxY2;
|
||||
settings.mNormalAxis1 = jAxX1;
|
||||
settings.mNormalAxis2 = jAxX2;
|
||||
if (limits != null && limits.length >= 3 && limits[0] != 0) {
|
||||
settings.mLimitsMin = limits[1];
|
||||
settings.mLimitsMax = limits[2];
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case Generic:
|
||||
var settings = new jolt.Jt.SixDOFConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPosition1 = jPt1;
|
||||
settings.mPosition2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
if (limits != null) {
|
||||
applySixDOFLimits(settings);
|
||||
} else {
|
||||
for (i in 0...6) settings.MakeFreeAxis(i);
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
case GenericSpring:
|
||||
var settings = new jolt.Jt.SixDOFConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mPosition1 = jPt1;
|
||||
settings.mPosition2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
if (limits != null) {
|
||||
applySixDOFLimits(settings);
|
||||
} else {
|
||||
for (i in 0...6) settings.MakeFreeAxis(i);
|
||||
}
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
|
||||
default:
|
||||
var settings = new jolt.Jt.FixedConstraintSettings();
|
||||
settings.mSpace = 0;
|
||||
settings.mAutoDetectPoint = false;
|
||||
settings.mPoint1 = jPt1;
|
||||
settings.mPoint2 = jPt2;
|
||||
settings.mAxisX1 = jAxX1;
|
||||
settings.mAxisY1 = jAxY1;
|
||||
settings.mAxisX2 = jAxX2;
|
||||
settings.mAxisY2 = jAxY2;
|
||||
con = settings.Create(body1.body, body2.body);
|
||||
#if hl settings.delete(); #end
|
||||
}
|
||||
|
||||
// Clean up temporary Jolt objects
|
||||
#if hl
|
||||
jPt1.delete();
|
||||
jPt2.delete();
|
||||
jAxX1.delete();
|
||||
jAxY1.delete();
|
||||
jAxX2.delete();
|
||||
jAxY2.delete();
|
||||
#end
|
||||
|
||||
conReady = true;
|
||||
physics.addPhysicsConstraint(this);
|
||||
}
|
||||
|
||||
function applySixDOFLimits(settings:jolt.Jt.SixDOFConstraintSettings) {
|
||||
// Linear X (limits[0..2]): limits[0]=enabled, limits[1]=lower, limits[2]=upper
|
||||
if (limits.length > 2 && limits[0] != 0) {
|
||||
if (limits[1] > limits[2])
|
||||
settings.MakeFreeAxis(0);
|
||||
else
|
||||
settings.SetLimitedAxis(0, limits[1], limits[2]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(0);
|
||||
}
|
||||
// Linear Y (limits[3..5])
|
||||
if (limits.length > 5 && limits[3] != 0) {
|
||||
if (limits[4] > limits[5])
|
||||
settings.MakeFreeAxis(1);
|
||||
else
|
||||
settings.SetLimitedAxis(1, limits[4], limits[5]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(1);
|
||||
}
|
||||
// Linear Z (limits[6..8])
|
||||
if (limits.length > 8 && limits[6] != 0) {
|
||||
if (limits[7] > limits[8])
|
||||
settings.MakeFreeAxis(2);
|
||||
else
|
||||
settings.SetLimitedAxis(2, limits[7], limits[8]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(2);
|
||||
}
|
||||
// Angular X (limits[9..11])
|
||||
if (limits.length > 11 && limits[9] != 0) {
|
||||
if (limits[10] > limits[11])
|
||||
settings.MakeFreeAxis(3);
|
||||
else
|
||||
settings.SetLimitedAxis(3, limits[10], limits[11]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(3);
|
||||
}
|
||||
// Angular Y (limits[12..14])
|
||||
if (limits.length > 14 && limits[12] != 0) {
|
||||
if (limits[13] > limits[14])
|
||||
settings.MakeFreeAxis(4);
|
||||
else
|
||||
settings.SetLimitedAxis(4, limits[13], limits[14]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(4);
|
||||
}
|
||||
// Angular Z (limits[15..17])
|
||||
if (limits.length > 17 && limits[15] != 0) {
|
||||
if (limits[16] > limits[17])
|
||||
settings.MakeFreeAxis(5);
|
||||
else
|
||||
settings.SetLimitedAxis(5, limits[16], limits[17]);
|
||||
} else {
|
||||
settings.MakeFreeAxis(5);
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromWorld() {
|
||||
if (physics != null) {
|
||||
physics.removePhysicsConstraint(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
conReady = false;
|
||||
}
|
||||
|
||||
public function setEnabled(enabled:Bool) {
|
||||
if (conReady) {
|
||||
con.SetEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEnabled():Bool {
|
||||
return conReady ? con.GetEnabled() : false;
|
||||
}
|
||||
|
||||
// Bullet-compatible limit setting methods
|
||||
public function setHingeConstraintLimits(angLimit:Bool, lowerAngLimit:Float, upperAngLimit:Float) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
limits[0] = angLimit ? 1 : 0;
|
||||
limits[1] = lowerAngLimit * (Math.PI / 180);
|
||||
limits[2] = upperAngLimit * (Math.PI / 180);
|
||||
}
|
||||
|
||||
public function setSliderConstraintLimits(linLimit:Bool, lowerLinLimit:Float, upperLinLimit:Float) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
limits[0] = linLimit ? 1 : 0;
|
||||
limits[1] = lowerLinLimit;
|
||||
limits[2] = upperLinLimit;
|
||||
}
|
||||
|
||||
public function setPistonConstraintLimits(linLimit:Bool, lowerLinLimit:Float, upperLinLimit:Float, angLimit:Bool, lowerAngLimit:Float, upperAngLimit:Float) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
limits[0] = linLimit ? 1 : 0;
|
||||
limits[1] = lowerLinLimit;
|
||||
limits[2] = upperLinLimit;
|
||||
limits[3] = angLimit ? 1 : 0;
|
||||
limits[4] = lowerAngLimit * (Math.PI / 180);
|
||||
limits[5] = upperAngLimit * (Math.PI / 180);
|
||||
}
|
||||
|
||||
public function setGenericConstraintLimits(setLimit:Bool = false, lowerLimit:Float = 1.0, upperLimit:Float = -1.0, axis:ConstraintAxis = X, isAngular:Bool = false) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
var i = switch (axis) {
|
||||
case X: 0;
|
||||
case Y: 3;
|
||||
case Z: 6;
|
||||
};
|
||||
var j = isAngular ? 9 : 0;
|
||||
var radian = isAngular ? (Math.PI / 180) : 1;
|
||||
limits[i + j] = setLimit ? 1 : 0;
|
||||
limits[i + j + 1] = lowerLimit * radian;
|
||||
limits[i + j + 2] = upperLimit * radian;
|
||||
}
|
||||
|
||||
public function setSpringParams(setSpring:Bool = false, stiffness:Float = 10.0, damping:Float = 0.5, axis:ConstraintAxis = X, isAngular:Bool = false) {
|
||||
if (limits == null) limits = [for (i in 0...36) 0.0];
|
||||
var i = switch (axis) {
|
||||
case X: 18;
|
||||
case Y: 21;
|
||||
case Z: 24;
|
||||
};
|
||||
var j = isAngular ? 9 : 0;
|
||||
limits[i + j] = setSpring ? 1 : 0;
|
||||
limits[i + j + 1] = stiffness;
|
||||
limits[i + j + 2] = damping;
|
||||
}
|
||||
}
|
||||
|
||||
@:enum abstract ConstraintAxis(Int) from Int to Int {
|
||||
var X = 0;
|
||||
var Y = 1;
|
||||
var Z = 2;
|
||||
}
|
||||
|
||||
#end
|
||||
122
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsHook.hx
Normal file
122
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsHook.hx
Normal file
@ -0,0 +1,122 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
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;
|
||||
|
||||
class PhysicsHook extends Trait {
|
||||
|
||||
var target:Object;
|
||||
var targetName:String;
|
||||
var targetTransform:Transform;
|
||||
var verts:Array<Float>;
|
||||
|
||||
var hookBodyId:jolt.Jt.BodyID = null;
|
||||
var constraintId:Int = -1;
|
||||
|
||||
static var nullvec = true;
|
||||
static var vec1:jolt.Jt.Vec3;
|
||||
static var quat1:jolt.Jt.Quat;
|
||||
static var quat = new Quat();
|
||||
|
||||
public function new(targetName:String, verts:Array<Float>) {
|
||||
super();
|
||||
|
||||
this.targetName = targetName;
|
||||
this.verts = verts;
|
||||
|
||||
iron.Scene.active.notifyOnInit(function() {
|
||||
notifyOnInit(init);
|
||||
notifyOnUpdate(update);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
quat1 = new jolt.Jt.Quat(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
target = targetName != "" ? iron.Scene.active.getChild(targetName) : null;
|
||||
targetTransform = target != null ? target.transform : iron.Scene.global.transform;
|
||||
|
||||
var physics = PhysicsWorld.active;
|
||||
if (physics == null)
|
||||
return;
|
||||
|
||||
#if lnx_physics_soft
|
||||
var sb:SoftBody = object.getTrait(SoftBody);
|
||||
if (sb != null && sb.ready) {
|
||||
// For soft body hooks, pin vertices near the target
|
||||
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;
|
||||
|
||||
// Find and pin matching vertices
|
||||
for (i in 0...@:privateAccess sb.particles.length) {
|
||||
var p = @:privateAccess sb.particles[i];
|
||||
if (Math.abs(p.position.x - x) < 0.01 && Math.abs(p.position.y - y) < 0.01 && Math.abs(p.position.z - z) < 0.01) {
|
||||
sb.pinVertex(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
// Rigid body hook using fixed constraint
|
||||
var rb1:RigidBody = object.getTrait(RigidBody);
|
||||
if (rb1 != null && rb1.ready) {
|
||||
var settings = new jolt.Jt.FixedConstraintSettings();
|
||||
settings.mAutoDetectPoint = true;
|
||||
var constraint = settings.Create(rb1.body, rb1.body);
|
||||
physics.physicsSystem.AddConstraint(constraint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rigid body or soft body not initialized yet
|
||||
notifyOnInit(init);
|
||||
}
|
||||
|
||||
function update() {
|
||||
#if lnx_physics_soft
|
||||
// Soft body hook - update pinned vertex positions to follow target
|
||||
var sb:SoftBody = object.getTrait(SoftBody);
|
||||
if (sb != null && sb.ready) {
|
||||
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;
|
||||
|
||||
// Update pinned vertex positions to target
|
||||
for (i in 0...@:privateAccess sb.particles.length) {
|
||||
var p = @:privateAccess sb.particles[i];
|
||||
if (p.pinned) {
|
||||
// Move pinned vertex with target
|
||||
var dx = targetTransform.worldx() - targetTransform.loc.x;
|
||||
var dy = targetTransform.worldy() - targetTransform.loc.y;
|
||||
var dz = targetTransform.worldz() - targetTransform.loc.z;
|
||||
p.position.x = x + dx;
|
||||
p.position.y = y + dy;
|
||||
p.position.z = z + dz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
448
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsWorld.hx
Normal file
448
leenkx/Sources/leenkx/trait/physics/jolt/PhysicsWorld.hx
Normal file
@ -0,0 +1,448 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.system.Time;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.RayCaster;
|
||||
import leenkx.trait.physics.PhysicsCache;
|
||||
|
||||
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;
|
||||
|
||||
public var physicsSystem:jolt.Jt.PhysicsSystem;
|
||||
public var bodyInterface:jolt.Jt.BodyInterface;
|
||||
public var physicsReady:Bool = false;
|
||||
var broadPhaseOptimized:Bool = 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 constraints:Array<PhysicsConstraint> = [];
|
||||
public var timeScale = 1.0;
|
||||
var maxSteps = 1;
|
||||
public var solverIterations = 10;
|
||||
public var hitPointWorld = new Vec4();
|
||||
public var hitNormalWorld = new Vec4();
|
||||
|
||||
// Debug drawing
|
||||
var debugDrawHelper:DebugDrawHelper = null;
|
||||
var debugDrawMode:DebugDrawHelper.DebugDrawMode = DebugDrawHelper.DebugDrawMode.NoDebug;
|
||||
|
||||
// Jolt-specific helpers
|
||||
static var nullvec = true;
|
||||
static var vec1:jolt.Jt.Vec3 = null;
|
||||
static var vec2:jolt.Jt.Vec3 = null;
|
||||
static var quat1:jolt.Jt.Quat = null;
|
||||
|
||||
#if js
|
||||
var joltInterface:jolt.Jt.JoltInterface;
|
||||
static var joltReady = false;
|
||||
static var joltModule:Dynamic = null;
|
||||
static var pendingWorlds:Array<PhysicsWorld> = [];
|
||||
#end
|
||||
|
||||
#if lnx_debug
|
||||
public static var physTime = 0.0;
|
||||
#end
|
||||
|
||||
#if hl
|
||||
@:hlNative("jolt", "Init")
|
||||
static function hlJoltInit():Void {}
|
||||
|
||||
@:hlNative("jolt", "Shutdown")
|
||||
static function hlJoltShutdown():Void {}
|
||||
#end
|
||||
|
||||
public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, fixedStep = 1 / 60) {
|
||||
super();
|
||||
|
||||
if (active != null && !sceneRemoved)
|
||||
return;
|
||||
sceneRemoved = false;
|
||||
|
||||
this.timeScale = timeScale;
|
||||
this.maxSteps = maxSteps;
|
||||
this.solverIterations = solverIterations;
|
||||
Time.initFixedStep(fixedStep);
|
||||
|
||||
contacts = [];
|
||||
rbMap = new Map();
|
||||
conMap = new Map();
|
||||
active = this;
|
||||
|
||||
#if js
|
||||
// Check if Jolt is initialized
|
||||
if (!joltReady) {
|
||||
pendingWorlds.push(this);
|
||||
initJolt();
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
initPhysicsWorld();
|
||||
}
|
||||
|
||||
#if js
|
||||
static var joltRetryRegistered = false;
|
||||
static function initJolt() {
|
||||
// Check if Jolt global exists (loaded via asset)
|
||||
var jolt:Dynamic = untyped __js__("typeof Jolt !== 'undefined' ? Jolt : null");
|
||||
if (jolt != null) {
|
||||
joltModule = jolt;
|
||||
joltReady = true;
|
||||
joltRetryRegistered = false;
|
||||
// Initialize pending worlds
|
||||
for (world in pendingWorlds) {
|
||||
world.initPhysicsWorld();
|
||||
}
|
||||
pendingWorlds = [];
|
||||
} else if (!joltRetryRegistered) {
|
||||
// Jolt not loaded yet, retry on next frame (NOT notifyOnInit which fires same-frame)
|
||||
joltRetryRegistered = true;
|
||||
var retryFn:Void->Void = null;
|
||||
retryFn = function() {
|
||||
iron.App.removeUpdate(retryFn);
|
||||
joltRetryRegistered = false;
|
||||
initJolt();
|
||||
};
|
||||
iron.App.notifyOnUpdate(retryFn);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
function initPhysicsWorld() {
|
||||
#if hl
|
||||
// Must initialize Jolt allocator before ANY Jolt object creation
|
||||
hlJoltInit();
|
||||
#end
|
||||
|
||||
if (nullvec) {
|
||||
nullvec = false;
|
||||
vec1 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
vec2 = new jolt.Jt.Vec3(0, 0, 0);
|
||||
quat1 = new jolt.Jt.Quat(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
if (!physicsReady) {
|
||||
createPhysics();
|
||||
} else if (active != null && active != this) {
|
||||
physicsSystem = active.physicsSystem;
|
||||
bodyInterface = active.bodyInterface;
|
||||
physicsReady = true;
|
||||
}
|
||||
|
||||
_fixedUpdate = [fixedUpdate];
|
||||
@:privateAccess iron.App.traitFixedUpdates.insert(0, fixedUpdate);
|
||||
|
||||
iron.Scene.active.notifyOnRemove(function() {
|
||||
sceneRemoved = true;
|
||||
PhysicsCache.clearCache();
|
||||
});
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
for (rb in active.rbMap)
|
||||
removeRigidBody(rb);
|
||||
}
|
||||
|
||||
function createPhysics() {
|
||||
#if hl
|
||||
// HashLink initialization - uses native jolt library
|
||||
physicsSystem = new jolt.Jt.PhysicsSystem();
|
||||
physicsSystem.Init(10240, 0, 65536, 10240);
|
||||
bodyInterface = physicsSystem.GetBodyInterface();
|
||||
#elseif js
|
||||
// JavaScript/WASM initialization via JoltSettings + JoltInterface
|
||||
var settings = new jolt.Jt.JoltSettings();
|
||||
settings.mMaxBodies = 10240;
|
||||
settings.mMaxBodyPairs = 65536;
|
||||
settings.mMaxContactConstraints = 10240;
|
||||
|
||||
// Create layer interfaces
|
||||
var broadPhaseLayer = new jolt.Jt.BroadPhaseLayerInterfaceTable(2, 2);
|
||||
broadPhaseLayer.MapObjectToBroadPhaseLayer(0, new jolt.Jt.BroadPhaseLayer(0));
|
||||
broadPhaseLayer.MapObjectToBroadPhaseLayer(1, new jolt.Jt.BroadPhaseLayer(1));
|
||||
|
||||
var objectLayerPair = new jolt.Jt.ObjectLayerPairFilterTable(2);
|
||||
objectLayerPair.EnableCollision(0, 0);
|
||||
objectLayerPair.EnableCollision(0, 1);
|
||||
objectLayerPair.EnableCollision(1, 1);
|
||||
|
||||
var objectVsBroadPhase = new jolt.Jt.ObjectVsBroadPhaseLayerFilterTable(broadPhaseLayer, 2, objectLayerPair, 2);
|
||||
|
||||
settings.mBroadPhaseLayerInterface = broadPhaseLayer;
|
||||
settings.mObjectVsBroadPhaseLayerFilter = objectVsBroadPhase;
|
||||
settings.mObjectLayerPairFilter = objectLayerPair;
|
||||
|
||||
joltInterface = new jolt.Jt.JoltInterface(settings);
|
||||
physicsSystem = joltInterface.GetPhysicsSystem();
|
||||
bodyInterface = physicsSystem.GetBodyInterface();
|
||||
#end
|
||||
|
||||
physicsReady = true;
|
||||
broadPhaseOptimized = false;
|
||||
|
||||
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]);
|
||||
setGravity(gravity);
|
||||
}
|
||||
|
||||
public function setGravity(v:Vec4) {
|
||||
vec1.Set(v.x, v.y, v.z);
|
||||
physicsSystem.SetGravity(vec1);
|
||||
}
|
||||
|
||||
public function getGravity():Vec4 {
|
||||
var g = physicsSystem.GetGravity();
|
||||
var result = new Vec4(g.GetX(), g.GetY(), g.GetZ());
|
||||
#if hl g.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function addRigidBody(body:RigidBody, activate:Bool = true) {
|
||||
bodyInterface.AddBody(body.bodyId, activate ? 1 : 0);
|
||||
rbMap.set(body.id, body);
|
||||
}
|
||||
|
||||
public function addPhysicsConstraint(constraint:PhysicsConstraint) {
|
||||
if (constraint.conReady) {
|
||||
physicsSystem.AddConstraint(constraint.con);
|
||||
}
|
||||
conMap.set(constraint.id, constraint);
|
||||
constraints.push(constraint);
|
||||
}
|
||||
|
||||
public function removeRigidBody(body:RigidBody) {
|
||||
if (body.destroyed)
|
||||
return;
|
||||
body.destroyed = true;
|
||||
bodyInterface.RemoveBody(body.bodyId);
|
||||
bodyInterface.DestroyBody(body.bodyId);
|
||||
rbMap.remove(body.id);
|
||||
}
|
||||
|
||||
public function removePhysicsConstraint(constraint:PhysicsConstraint) {
|
||||
if (constraint.conReady) {
|
||||
physicsSystem.RemoveConstraint(constraint.con);
|
||||
}
|
||||
conMap.remove(constraint.id);
|
||||
constraints.remove(constraint);
|
||||
constraint.delete();
|
||||
}
|
||||
|
||||
public function getContacts(body:RigidBody):Array<RigidBody> {
|
||||
if (contacts.length == 0)
|
||||
return null;
|
||||
var res:Array<RigidBody> = [];
|
||||
for (c in contacts) {
|
||||
var rb:RigidBody = null;
|
||||
if (c.a == body.id)
|
||||
rb = rbMap.get(c.b);
|
||||
else if (c.b == body.id)
|
||||
rb = rbMap.get(c.a);
|
||||
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 (c in contacts) {
|
||||
if (c.a == body.id || c.b == body.id)
|
||||
res.push(c);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public function findBody(id:Int):RigidBody {
|
||||
return rbMap.get(id);
|
||||
}
|
||||
|
||||
function fixedUpdate() {
|
||||
var t = Time.fixedStep * timeScale * Time.scale;
|
||||
if (t == 0.0)
|
||||
return;
|
||||
|
||||
PhysicsCache.clearContactsCache();
|
||||
|
||||
#if lnx_debug
|
||||
var startTime = kha.Scheduler.realTime();
|
||||
#end
|
||||
|
||||
if (preUpdates != null)
|
||||
for (f in preUpdates)
|
||||
f();
|
||||
|
||||
if (!broadPhaseOptimized) {
|
||||
physicsSystem.OptimizeBroadPhase();
|
||||
broadPhaseOptimized = true;
|
||||
}
|
||||
|
||||
var currMaxSteps = t < (Time.fixedStep * (maxSteps / 10)) ? maxSteps : 1;
|
||||
|
||||
#if js
|
||||
joltInterface.Step(t, currMaxSteps);
|
||||
#elseif hl
|
||||
physicsSystem.Update(t, currMaxSteps);
|
||||
#end
|
||||
|
||||
for (rb in rbMap)
|
||||
@:privateAccess rb.physicsUpdate();
|
||||
|
||||
#if lnx_debug
|
||||
physTime = kha.Scheduler.realTime() - startTime;
|
||||
#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);
|
||||
return hit != null ? hit.rb : null;
|
||||
}
|
||||
|
||||
public function rayCast(from:Vec4, to:Vec4, group:Int = 0x00000001, mask = 0xFFFFFFFF):Hit {
|
||||
var dirX = to.x - from.x;
|
||||
var dirY = to.y - from.y;
|
||||
var dirZ = to.z - from.z;
|
||||
|
||||
var origin = new jolt.Jt.RVec3(from.x, from.y, from.z);
|
||||
var direction = new jolt.Jt.Vec3(dirX, dirY, dirZ);
|
||||
var ray = new jolt.Jt.RRayCast(origin, direction);
|
||||
var result = new jolt.Jt.RayCastResult();
|
||||
|
||||
var narrowPhase = physicsSystem.GetNarrowPhaseQuery();
|
||||
var didHit = narrowPhase.CastRay(ray, result);
|
||||
|
||||
if (didHit) {
|
||||
var bodyId = result.mBodyID;
|
||||
var fraction = result.mFraction;
|
||||
|
||||
hitPointWorld.set(from.x + dirX * fraction, from.y + dirY * fraction, from.z + dirZ * fraction);
|
||||
|
||||
// Find rigid body by ID
|
||||
for (rb in rbMap) {
|
||||
if (rb.bodyId.GetIndex() == bodyId.GetIndex()) {
|
||||
#if hl
|
||||
origin.delete();
|
||||
direction.delete();
|
||||
ray.delete();
|
||||
result.delete();
|
||||
#end
|
||||
return new Hit(rb, hitPointWorld.clone(), hitNormalWorld.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if hl
|
||||
origin.delete();
|
||||
direction.delete();
|
||||
ray.delete();
|
||||
result.delete();
|
||||
#end
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function notifyOnPreUpdate(f:Void->Void) {
|
||||
if (preUpdates == null)
|
||||
preUpdates = [];
|
||||
preUpdates.push(f);
|
||||
}
|
||||
|
||||
public function removePreUpdate(f:Void->Void) {
|
||||
preUpdates.remove(f);
|
||||
}
|
||||
|
||||
public var convexHitPointWorld = new Vec4();
|
||||
public var convexHitNormalWorld = new Vec4();
|
||||
|
||||
public function convexSweepTest(rb:RigidBody, from:Vec4, to:Vec4, rotation:iron.math.Quat, group:Int = 0x00000001, mask = 0xFFFFFFFF):ConvexHit {
|
||||
// Jolt shape cast implementation
|
||||
// Note: Full shape cast requires additional Jolt bindings
|
||||
// For now, use raycast as approximation
|
||||
var hit = rayCast(from, to, group, mask);
|
||||
if (hit != null) {
|
||||
var fraction = hit.pos.sub(from).length() / to.sub(from).length();
|
||||
convexHitPointWorld = hit.pos.clone();
|
||||
convexHitNormalWorld = hit.normal.clone();
|
||||
return new ConvexHit(convexHitPointWorld, convexHitNormalWorld, fraction);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setDebugDrawMode(mode:DebugDrawHelper.DebugDrawMode) {
|
||||
debugDrawMode = mode;
|
||||
if (mode != DebugDrawHelper.DebugDrawMode.NoDebug && debugDrawHelper == null) {
|
||||
debugDrawHelper = new DebugDrawHelper(this, mode);
|
||||
} else if (debugDrawHelper != null) {
|
||||
debugDrawHelper.setDebugMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public function getDebugDrawMode():DebugDrawHelper.DebugDrawMode {
|
||||
return debugDrawMode;
|
||||
}
|
||||
|
||||
public function debugDrawRayCast(from:Vec4, to:Vec4, hasHit:Bool, ?hitPoint:Vec4, ?hitNormal:Vec4) {
|
||||
if (debugDrawHelper != null && (debugDrawMode & DebugDrawHelper.DebugDrawMode.DrawRayCast) != 0) {
|
||||
debugDrawHelper.rayCast({
|
||||
from: from,
|
||||
to: to,
|
||||
hasHit: hasHit,
|
||||
hitPoint: hitPoint,
|
||||
hitNormal: hitNormal
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
746
leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx
Normal file
746
leenkx/Sources/leenkx/trait/physics/jolt/RigidBody.hx
Normal file
@ -0,0 +1,746 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.object.Transform;
|
||||
import iron.object.MeshObject;
|
||||
|
||||
@: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;
|
||||
}
|
||||
|
||||
class RigidBody extends Trait {
|
||||
public var physics:PhysicsWorld;
|
||||
public var transform:Transform = null;
|
||||
|
||||
public var body:jolt.Jt.Body;
|
||||
public var bodyId:jolt.Jt.BodyID;
|
||||
|
||||
public var id:Int;
|
||||
public var destroyed = false;
|
||||
public var ready = false;
|
||||
|
||||
public var shape:Shape;
|
||||
public var mass:Float;
|
||||
public var friction:Float;
|
||||
public var restitution:Float;
|
||||
public var group:Int;
|
||||
public var mask:Int;
|
||||
public var linearDamping:Float;
|
||||
public var angularDamping:Float;
|
||||
public var animated:Bool;
|
||||
public var staticObj:Bool;
|
||||
public var trigger:Bool;
|
||||
var lockTranslationX:Bool;
|
||||
var lockTranslationY:Bool;
|
||||
var lockTranslationZ:Bool;
|
||||
var lockRotationX:Bool;
|
||||
var lockRotationY:Bool;
|
||||
var lockRotationZ:Bool;
|
||||
var ccd:Bool;
|
||||
var useDeactivation:Bool;
|
||||
|
||||
static var nextId = 0;
|
||||
|
||||
// Native bindings for complex shape creation (HL only)
|
||||
#if hl
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_new")
|
||||
static function hlConvexHullShapeSettings_new():Dynamic { return null; }
|
||||
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_delete")
|
||||
static function hlConvexHullShapeSettings_delete(settings:Dynamic):Void {}
|
||||
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_AddPoint")
|
||||
static function hlConvexHullShapeSettings_AddPoint(settings:Dynamic, x:Float, y:Float, z:Float):Void {}
|
||||
|
||||
@:hlNative("jolt", "ConvexHullShapeSettings_Create")
|
||||
static function hlConvexHullShapeSettings_Create(settings:Dynamic):Dynamic { return null; }
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_new")
|
||||
static function hlMeshShapeSettings_new():Dynamic { return null; }
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_delete")
|
||||
static function hlMeshShapeSettings_delete(settings:Dynamic):Void {}
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_AddVertex")
|
||||
static function hlMeshShapeSettings_AddVertex(settings:Dynamic, x:Float, y:Float, z:Float):Void {}
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_AddTriangle")
|
||||
static function hlMeshShapeSettings_AddTriangle(settings:Dynamic, i0:Int, i1:Int, i2:Int):Void {}
|
||||
|
||||
@:hlNative("jolt", "MeshShapeSettings_Create")
|
||||
static function hlMeshShapeSettings_Create(settings:Dynamic):Dynamic { return null; }
|
||||
#end
|
||||
|
||||
public function new(shape:Shape = Box, mass:Float = 1.0, friction:Float = 0.5, restitution:Float = 0.0, group:Int = 1, mask:Int = 1,
|
||||
params:RigidBodyParams = null, flags:RigidBodyFlags = null) {
|
||||
super();
|
||||
|
||||
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,
|
||||
linearVelocityMin: 0.0,
|
||||
linearVelocityMax: 0.0,
|
||||
angularVelocityMin: 0.0,
|
||||
angularVelocityMax: 0.0,
|
||||
lockTranslationX: false,
|
||||
lockTranslationY: false,
|
||||
lockTranslationZ: false,
|
||||
lockRotationX: false,
|
||||
lockRotationY: false,
|
||||
lockRotationZ: false
|
||||
};
|
||||
|
||||
if (flags == null) flags = {
|
||||
animated: false,
|
||||
trigger: false,
|
||||
ccd: false,
|
||||
interpolate: false,
|
||||
staticObj: false,
|
||||
useDeactivation: true
|
||||
};
|
||||
|
||||
this.shape = shape;
|
||||
this.mass = mass;
|
||||
this.friction = friction;
|
||||
this.restitution = restitution;
|
||||
this.group = group;
|
||||
this.mask = mask;
|
||||
this.linearDamping = params.linearDamping;
|
||||
this.angularDamping = params.angularDamping;
|
||||
this.animated = flags.animated;
|
||||
this.trigger = flags.trigger;
|
||||
this.ccd = flags.ccd;
|
||||
this.staticObj = flags.staticObj || mass == 0.0;
|
||||
this.lockTranslationX = params.lockTranslationX;
|
||||
this.lockTranslationY = params.lockTranslationY;
|
||||
this.lockTranslationZ = params.lockTranslationZ;
|
||||
this.lockRotationX = params.lockRotationX;
|
||||
this.lockRotationY = params.lockRotationY;
|
||||
this.lockRotationZ = params.lockRotationZ;
|
||||
this.useDeactivation = flags.useDeactivation;
|
||||
this.id = nextId++;
|
||||
|
||||
notifyOnAdd(init);
|
||||
notifyOnRemove(removeFromWorld);
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (ready)
|
||||
return;
|
||||
|
||||
transform = object.transform;
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
if (physics == null) {
|
||||
new PhysicsWorld();
|
||||
physics = PhysicsWorld.active;
|
||||
}
|
||||
|
||||
#if js
|
||||
// Check if Jolt is initialized - defer if not
|
||||
if (!physics.physicsReady) {
|
||||
// Jolt not ready yet, retry after delay
|
||||
haxe.Timer.delay(init, 16);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
ready = true;
|
||||
|
||||
var t = transform;
|
||||
t.buildMatrix();
|
||||
var pos = t.world.getLoc();
|
||||
var rot = new Quat();
|
||||
rot.fromMat(t.world);
|
||||
|
||||
// Create shape based on type - use transform.dim like Bullet does
|
||||
var joltShape = createShape(t);
|
||||
|
||||
// Determine motion type (0=Static, 1=Kinematic, 2=Dynamic)
|
||||
var motionType:Int = staticObj ? 0 : (animated ? 1 : 2);
|
||||
|
||||
// Jolt uses RVec3 for world positions
|
||||
var jPos = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
var jRot = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
if (staticObj || animated) mass = 0;
|
||||
|
||||
var settings = new jolt.Jt.BodyCreationSettings(joltShape, jPos, jRot, motionType, staticObj ? 0 : 1);
|
||||
settings.mFriction = friction;
|
||||
settings.mRestitution = restitution;
|
||||
settings.mIsSensor = trigger;
|
||||
|
||||
settings.mLinearDamping = linearDamping;
|
||||
settings.mAngularDamping = angularDamping;
|
||||
|
||||
// Match Bullet's deactivation: useDeactivation=false → DISABLE_DEACTIVATION
|
||||
if (!useDeactivation) {
|
||||
settings.mAllowSleeping = false;
|
||||
}
|
||||
|
||||
// Set mass to match Bullet (CalculateInertia = 1: use provided mass, compute inertia from shape)
|
||||
// Use explicit MassProperties object to avoid chained property access issues in HL
|
||||
if (mass > 0) {
|
||||
settings.mOverrideMassProperties = 1;
|
||||
var mp = new jolt.Jt.MassProperties();
|
||||
mp.mMass = mass;
|
||||
settings.mMassPropertiesOverride = mp;
|
||||
#if hl
|
||||
mp.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
// Set allowed DOFs (matching Bullet's linear/angular factors + lock properties)
|
||||
var dofs = 0x3F; // All DOFs by default (0x3F = TranslationX|Y|Z|RotationX|Y|Z)
|
||||
if (lockTranslationX) dofs &= ~0x01;
|
||||
if (lockTranslationY) dofs &= ~0x02;
|
||||
if (lockTranslationZ) dofs &= ~0x04;
|
||||
if (lockRotationX) dofs &= ~0x08;
|
||||
if (lockRotationY) dofs &= ~0x10;
|
||||
if (lockRotationZ) dofs &= ~0x20;
|
||||
if (dofs != 0x3F) settings.mAllowedDOFs = dofs;
|
||||
|
||||
// CCD for fast-moving objects
|
||||
if (ccd) settings.mMotionQuality = 1; // LinearCast
|
||||
|
||||
body = physics.bodyInterface.CreateBody(settings);
|
||||
bodyId = body.GetID();
|
||||
|
||||
#if hl
|
||||
settings.delete();
|
||||
jPos.delete();
|
||||
jRot.delete();
|
||||
#end
|
||||
|
||||
// Add to world (activate dynamic bodies, matching Bullet behavior)
|
||||
physics.addRigidBody(this, !staticObj);
|
||||
|
||||
// Initialize cached position from body creation position
|
||||
currentPosX = pos.x;
|
||||
currentPosY = pos.y;
|
||||
currentPosZ = pos.z;
|
||||
currentRotX = rot.x;
|
||||
currentRotY = rot.y;
|
||||
currentRotZ = rot.z;
|
||||
currentRotW = rot.w;
|
||||
|
||||
// Register visual update callback for non-animated bodies (matching Bullet)
|
||||
if (!animated) notifyOnUpdate(update);
|
||||
}
|
||||
|
||||
function createShape(t:Transform):jolt.Jt.Shape {
|
||||
// Use transform.dim (mesh bounding box) for shape dimensions, matching Bullet
|
||||
var dimX = t.dim.x;
|
||||
var dimY = t.dim.y;
|
||||
var dimZ = t.dim.z;
|
||||
|
||||
return switch (shape) {
|
||||
case Box:
|
||||
var halfExtent = new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2);
|
||||
cast new jolt.Jt.BoxShape(halfExtent);
|
||||
case Sphere:
|
||||
var radius = dimX / 2;
|
||||
cast new jolt.Jt.SphereShape(radius);
|
||||
case Capsule:
|
||||
var radius = dimX / 2;
|
||||
var halfHeight = dimZ / 2 - radius;
|
||||
if (halfHeight < 0) halfHeight = 0.01;
|
||||
cast new jolt.Jt.CapsuleShape(halfHeight, radius);
|
||||
case Cylinder:
|
||||
var radius = Math.max(dimX, dimY) / 2;
|
||||
var halfHeight = dimZ / 2;
|
||||
cast new jolt.Jt.CylinderShape(halfHeight, radius);
|
||||
case Cone:
|
||||
var radius = Math.max(dimX, dimY) / 2;
|
||||
var halfHeight = dimZ / 2;
|
||||
cast new jolt.Jt.CylinderShape(halfHeight, radius);
|
||||
case ConvexHull:
|
||||
#if hl
|
||||
createConvexHullShape(t.scale);
|
||||
#else
|
||||
createConvexHullShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
case Mesh:
|
||||
// Jolt MeshShape only works for static bodies (unlike Bullet's GImpact)
|
||||
// Dynamic mesh bodies must use ConvexHullShape instead
|
||||
if (staticObj) {
|
||||
#if hl
|
||||
createMeshShape(t.scale);
|
||||
#else
|
||||
createMeshShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
} else {
|
||||
#if hl
|
||||
createConvexHullShape(t.scale);
|
||||
#else
|
||||
createConvexHullShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
}
|
||||
case Terrain:
|
||||
#if hl
|
||||
createTerrainShape(t.scale);
|
||||
#else
|
||||
createMeshShapeJS(t.scale, dimX, dimY, dimZ);
|
||||
#end
|
||||
default:
|
||||
var halfExtent = new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2);
|
||||
cast new jolt.Jt.BoxShape(halfExtent);
|
||||
};
|
||||
}
|
||||
|
||||
function createConvexHullShape(scale:Vec4):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var scalePos = mo.data.scalePos;
|
||||
|
||||
#if hl
|
||||
var settings = hlConvexHullShapeSettings_new();
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
for (i in 0...numVerts) {
|
||||
var x = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
hlConvexHullShapeSettings_AddPoint(settings, x, y, z);
|
||||
}
|
||||
var shape = hlConvexHullShapeSettings_Create(settings);
|
||||
hlConvexHullShapeSettings_delete(settings);
|
||||
if (shape == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
return cast shape;
|
||||
#else
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
#end
|
||||
}
|
||||
|
||||
function createMeshShape(scale:Vec4):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var indices = mo.data.geom.indices;
|
||||
var scalePos = mo.data.scalePos;
|
||||
|
||||
#if hl
|
||||
var settings = hlMeshShapeSettings_new();
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
|
||||
for (i in 0...numVerts) {
|
||||
var x = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
hlMeshShapeSettings_AddVertex(settings, x, y, z);
|
||||
}
|
||||
|
||||
for (indexArray in indices) {
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
hlMeshShapeSettings_AddTriangle(settings, indexArray[i * 3], indexArray[i * 3 + 1], indexArray[i * 3 + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
var shape = hlMeshShapeSettings_Create(settings);
|
||||
hlMeshShapeSettings_delete(settings);
|
||||
if (shape == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
return cast shape;
|
||||
#else
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
#end
|
||||
}
|
||||
|
||||
function createTerrainShape(scale:Vec4):jolt.Jt.Shape {
|
||||
// Terrain/HeightField shape - requires height data from object
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
}
|
||||
|
||||
#if js
|
||||
// For JS, use HeightFieldShapeSettings or fallback to mesh
|
||||
// Terrain meshes are typically treated as mesh shapes in Jolt
|
||||
return createMeshShape(scale);
|
||||
#elseif hl
|
||||
// For HashLink, terrain is also best represented as mesh shape
|
||||
// HeightFieldShape requires specific grid data which terrain meshes may not have
|
||||
return createMeshShape(scale);
|
||||
#else
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5));
|
||||
#end
|
||||
}
|
||||
|
||||
#if js
|
||||
function createConvexHullShapeJS(scale:Vec4, dimX:Float, dimY:Float, dimZ:Float):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var scalePos = mo.data.scalePos;
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
|
||||
var settings:Dynamic = untyped __js__("new Jolt.ConvexHullShapeSettings()");
|
||||
var points:Dynamic = untyped settings.mPoints;
|
||||
points.clear();
|
||||
for (i in 0...numVerts) {
|
||||
var x:Float = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y:Float = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z:Float = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
var pt:Dynamic = untyped __js__("new Jolt.Vec3({0}, {1}, {2})", x, y, z);
|
||||
untyped points.push_back(pt);
|
||||
}
|
||||
|
||||
var result:Dynamic = untyped settings.Create();
|
||||
if (untyped result.HasError()) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
return cast untyped result.Get();
|
||||
}
|
||||
|
||||
function createMeshShapeJS(scale:Vec4, dimX:Float, dimY:Float, dimZ:Float):jolt.Jt.Shape {
|
||||
var mo = cast(object, MeshObject);
|
||||
if (mo == null || mo.data == null || mo.data.geom == null) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
|
||||
var positions = mo.data.geom.positions.values;
|
||||
var indices = mo.data.geom.indices;
|
||||
var scalePos = mo.data.scalePos;
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
|
||||
var settings:Dynamic = untyped __js__("new Jolt.MeshShapeSettings()");
|
||||
var verts:Dynamic = untyped settings.mTriangleVertices;
|
||||
var tris:Dynamic = untyped settings.mIndexedTriangles;
|
||||
verts.clear();
|
||||
tris.clear();
|
||||
|
||||
for (i in 0...numVerts) {
|
||||
var x:Float = (positions[i * 4] / 32767) * scalePos * scale.x;
|
||||
var y:Float = (positions[i * 4 + 1] / 32767) * scalePos * scale.y;
|
||||
var z:Float = (positions[i * 4 + 2] / 32767) * scalePos * scale.z;
|
||||
var v:Dynamic = untyped __js__("new Jolt.Float3({0}, {1}, {2})", x, y, z);
|
||||
untyped verts.push_back(v);
|
||||
}
|
||||
|
||||
for (indexArray in indices) {
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
var tri:Dynamic = untyped __js__("new Jolt.IndexedTriangle()");
|
||||
untyped tri.set_mIdx(0, indexArray[i * 3]);
|
||||
untyped tri.set_mIdx(1, indexArray[i * 3 + 1]);
|
||||
untyped tri.set_mIdx(2, indexArray[i * 3 + 2]);
|
||||
untyped tri.set_mMaterialIndex(0);
|
||||
untyped tris.push_back(tri);
|
||||
}
|
||||
}
|
||||
|
||||
var result:Dynamic = untyped settings.Create();
|
||||
if (untyped result.HasError()) {
|
||||
return cast new jolt.Jt.BoxShape(new jolt.Jt.Vec3(dimX / 2, dimY / 2, dimZ / 2));
|
||||
}
|
||||
return cast untyped result.Get();
|
||||
}
|
||||
#end
|
||||
|
||||
// Cached physics state for visual interpolation (matching Bullet pattern)
|
||||
var currentPosX:Float = 0;
|
||||
var currentPosY:Float = 0;
|
||||
var currentPosZ:Float = 0;
|
||||
var currentRotX:Float = 0;
|
||||
var currentRotY:Float = 0;
|
||||
var currentRotZ:Float = 0;
|
||||
var currentRotW:Float = 1;
|
||||
|
||||
public function physicsUpdate() {
|
||||
if (!ready)
|
||||
return;
|
||||
|
||||
if (staticObj)
|
||||
return;
|
||||
|
||||
if (animated) {
|
||||
syncTransform();
|
||||
return;
|
||||
}
|
||||
|
||||
var active = physics.bodyInterface.IsActive(bodyId);
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
// Read position and rotation from Jolt into cached state
|
||||
var p = physics.bodyInterface.GetPosition(bodyId);
|
||||
var q = physics.bodyInterface.GetRotation(bodyId);
|
||||
|
||||
#if js
|
||||
currentPosX = cast p.GetX();
|
||||
currentPosY = cast p.GetY();
|
||||
currentPosZ = cast p.GetZ();
|
||||
currentRotX = cast q.GetX();
|
||||
currentRotY = cast q.GetY();
|
||||
currentRotZ = cast q.GetZ();
|
||||
currentRotW = cast q.GetW();
|
||||
// JS: getter return values use internal WASM wrappers - do NOT destroy
|
||||
#else
|
||||
currentPosX = p.GetX();
|
||||
currentPosY = p.GetY();
|
||||
currentPosZ = p.GetZ();
|
||||
currentRotX = q.GetX();
|
||||
currentRotY = q.GetY();
|
||||
currentRotZ = q.GetZ();
|
||||
currentRotW = q.GetW();
|
||||
p.delete();
|
||||
q.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
function update() {
|
||||
transform.loc.set(currentPosX, currentPosY, currentPosZ);
|
||||
transform.rot.set(currentRotX, currentRotY, currentRotZ, currentRotW);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
function removeFromWorld() {
|
||||
if (physics != null) {
|
||||
physics.removeRigidBody(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
// Cleanup handled by physics world
|
||||
}
|
||||
|
||||
// Physics methods
|
||||
public function applyForce(force:Vec4, ?loc:Vec4) {
|
||||
activate();
|
||||
if (loc == null) {
|
||||
var f = new jolt.Jt.Vec3(force.x, force.y, force.z);
|
||||
physics.bodyInterface.AddForce(bodyId, f);
|
||||
#if hl f.delete(); #end
|
||||
} else {
|
||||
var f = new jolt.Jt.Vec3(force.x, force.y, force.z);
|
||||
var l = new jolt.Jt.RVec3(loc.x, loc.y, loc.z);
|
||||
physics.bodyInterface.AddForceAtPosition(bodyId, f, l);
|
||||
#if hl f.delete(); l.delete(); #end
|
||||
}
|
||||
}
|
||||
|
||||
public function applyImpulse(impulse:Vec4, ?loc:Vec4) {
|
||||
activate();
|
||||
if (loc == null) {
|
||||
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
|
||||
physics.bodyInterface.AddImpulse(bodyId, i);
|
||||
#if hl i.delete(); #end
|
||||
} else {
|
||||
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
|
||||
var l = new jolt.Jt.RVec3(loc.x, loc.y, loc.z);
|
||||
physics.bodyInterface.AddImpulseAtPosition(bodyId, i, l);
|
||||
#if hl i.delete(); l.delete(); #end
|
||||
}
|
||||
}
|
||||
|
||||
public function applyTorque(torque:Vec4) {
|
||||
activate();
|
||||
var t = new jolt.Jt.Vec3(torque.x, torque.y, torque.z);
|
||||
physics.bodyInterface.AddTorque(bodyId, t);
|
||||
#if hl t.delete(); #end
|
||||
}
|
||||
|
||||
public function applyTorqueImpulse(impulse:Vec4) {
|
||||
activate();
|
||||
var i = new jolt.Jt.Vec3(impulse.x, impulse.y, impulse.z);
|
||||
physics.bodyInterface.AddAngularImpulse(bodyId, i);
|
||||
#if hl i.delete(); #end
|
||||
}
|
||||
|
||||
public function setLinearVelocity(v:Vec4) {
|
||||
var vel = new jolt.Jt.Vec3(v.x, v.y, v.z);
|
||||
physics.bodyInterface.SetLinearVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getLinearVelocity():Vec4 {
|
||||
var v = physics.bodyInterface.GetLinearVelocity(bodyId);
|
||||
var result = new Vec4(v.GetX(), v.GetY(), v.GetZ());
|
||||
#if hl v.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setAngularVelocity(v:Vec4) {
|
||||
var vel = new jolt.Jt.Vec3(v.x, v.y, v.z);
|
||||
physics.bodyInterface.SetAngularVelocity(bodyId, vel);
|
||||
#if hl vel.delete(); #end
|
||||
}
|
||||
|
||||
public function getAngularVelocity():Vec4 {
|
||||
var v = physics.bodyInterface.GetAngularVelocity(bodyId);
|
||||
var result = new Vec4(v.GetX(), v.GetY(), v.GetZ());
|
||||
#if hl v.delete(); #end
|
||||
return result;
|
||||
}
|
||||
|
||||
public function setFriction(f:Float) {
|
||||
friction = f;
|
||||
physics.bodyInterface.SetFriction(bodyId, f);
|
||||
}
|
||||
|
||||
public function setRestitution(r:Float) {
|
||||
restitution = r;
|
||||
physics.bodyInterface.SetRestitution(bodyId, r);
|
||||
}
|
||||
|
||||
public function setGravityFactor(f:Float) {
|
||||
physics.bodyInterface.SetGravityFactor(bodyId, f);
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
physics.bodyInterface.ActivateBody(bodyId);
|
||||
}
|
||||
|
||||
public function disableSimulation() {
|
||||
physics.bodyInterface.DeactivateBody(bodyId);
|
||||
}
|
||||
|
||||
public function setPosition(pos:Vec4) {
|
||||
var p = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
physics.bodyInterface.SetPosition(bodyId, p, 0);
|
||||
#if hl p.delete(); #end
|
||||
}
|
||||
|
||||
public function setRotation(rot:Quat) {
|
||||
var q = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
physics.bodyInterface.SetRotation(bodyId, q, 0);
|
||||
#if hl q.delete(); #end
|
||||
}
|
||||
|
||||
public function syncTransform() {
|
||||
var t = transform;
|
||||
var pos = t.world.getLoc();
|
||||
var rot = new Quat();
|
||||
rot.fromMat(t.world);
|
||||
setPosition(pos);
|
||||
setRotation(rot);
|
||||
}
|
||||
|
||||
public function isActive():Bool {
|
||||
return physics.bodyInterface.IsActive(bodyId);
|
||||
}
|
||||
|
||||
public function disableGravity() {
|
||||
setGravityFactor(0.0);
|
||||
}
|
||||
|
||||
public function enableGravity() {
|
||||
setGravityFactor(1.0);
|
||||
}
|
||||
|
||||
public function getPointVelocity(x:Float, y:Float, z:Float):Vec4 {
|
||||
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 disableCollision() {
|
||||
// In Jolt, use SetIsSensor to disable contact response
|
||||
body.SetIsSensor(true);
|
||||
}
|
||||
|
||||
public function enableCollision() {
|
||||
body.SetIsSensor(false);
|
||||
}
|
||||
|
||||
public function notifyOnContact(f:RigidBody->Void) {
|
||||
if (onContact == null)
|
||||
onContact = [];
|
||||
onContact.push(f);
|
||||
}
|
||||
|
||||
public function removeContact(f:RigidBody->Void) {
|
||||
if (onContact != null)
|
||||
onContact.remove(f);
|
||||
}
|
||||
|
||||
public var onContact:Array<RigidBody->Void> = null;
|
||||
public var onReady:Void->Void = null;
|
||||
|
||||
public function notifyOnReady(f:Void->Void) {
|
||||
onReady = f;
|
||||
if (ready)
|
||||
onReady();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
var linearVelocityMin:Float;
|
||||
var linearVelocityMax:Float;
|
||||
var angularVelocityMin:Float;
|
||||
var angularVelocityMax:Float;
|
||||
var lockTranslationX:Bool;
|
||||
var lockTranslationY:Bool;
|
||||
var lockTranslationZ:Bool;
|
||||
var lockRotationX:Bool;
|
||||
var lockRotationY:Bool;
|
||||
var lockRotationZ:Bool;
|
||||
}
|
||||
|
||||
typedef RigidBodyFlags = {
|
||||
var animated:Bool;
|
||||
var trigger:Bool;
|
||||
var ccd:Bool;
|
||||
var interpolate:Bool;
|
||||
var staticObj:Bool;
|
||||
var useDeactivation:Bool;
|
||||
}
|
||||
|
||||
#end
|
||||
528
leenkx/Sources/leenkx/trait/physics/jolt/SoftBody.hx
Normal file
528
leenkx/Sources/leenkx/trait/physics/jolt/SoftBody.hx
Normal file
@ -0,0 +1,528 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.math.Vec4;
|
||||
import iron.object.MeshObject;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.Time;
|
||||
import kha.arrays.ByteArray;
|
||||
|
||||
@:enum abstract SoftShape(Int) from Int {
|
||||
var Cloth = 0;
|
||||
var Volume = 1;
|
||||
}
|
||||
|
||||
// Soft body particle for spring-mass simulation
|
||||
class SoftParticle {
|
||||
public var position:Vec4;
|
||||
public var velocity:Vec4;
|
||||
public var acceleration:Vec4;
|
||||
public var invMass:Float;
|
||||
public var pinned:Bool;
|
||||
|
||||
public function new(x:Float, y:Float, z:Float, mass:Float) {
|
||||
position = new Vec4(x, y, z);
|
||||
velocity = new Vec4(0, 0, 0);
|
||||
acceleration = new Vec4(0, 0, 0);
|
||||
invMass = mass > 0 ? 1.0 / mass : 0;
|
||||
pinned = false;
|
||||
}
|
||||
|
||||
public function applyForce(force:Vec4) {
|
||||
acceleration.x += force.x * invMass;
|
||||
acceleration.y += force.y * invMass;
|
||||
acceleration.z += force.z * invMass;
|
||||
}
|
||||
|
||||
public function integrate(dt:Float) {
|
||||
if (pinned || invMass == 0)
|
||||
return;
|
||||
|
||||
velocity.x += acceleration.x * dt;
|
||||
velocity.y += acceleration.y * dt;
|
||||
velocity.z += acceleration.z * dt;
|
||||
|
||||
// Damping
|
||||
velocity.x *= 0.99;
|
||||
velocity.y *= 0.99;
|
||||
velocity.z *= 0.99;
|
||||
|
||||
position.x += velocity.x * dt;
|
||||
position.y += velocity.y * dt;
|
||||
position.z += velocity.z * dt;
|
||||
|
||||
acceleration.set(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Spring constraint between particles
|
||||
class SoftSpring {
|
||||
public var p1:Int;
|
||||
public var p2:Int;
|
||||
public var restLength:Float;
|
||||
public var stiffness:Float;
|
||||
|
||||
public function new(p1:Int, p2:Int, restLength:Float, stiffness:Float) {
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
this.restLength = restLength;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
}
|
||||
|
||||
class SoftBody extends Trait {
|
||||
|
||||
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;
|
||||
|
||||
// Spring-mass simulation
|
||||
var particles:Array<SoftParticle> = [];
|
||||
var springs:Array<SoftSpring> = [];
|
||||
var gravity:Vec4;
|
||||
var iterations = 3;
|
||||
|
||||
// Mesh data for vertex updates
|
||||
var meshObject:MeshObject;
|
||||
|
||||
// Vertex deduplication: maps unique vertex index → list of raw vertex indices
|
||||
// Meshes split vertices for normals/UVs, but soft body particles must be unique
|
||||
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;
|
||||
|
||||
notifyOnInit(init);
|
||||
}
|
||||
|
||||
function init() {
|
||||
var mo = cast(object, MeshObject);
|
||||
new MeshData(mo.data.raw, function(data) {
|
||||
mo.setData(data);
|
||||
initSoftBody();
|
||||
});
|
||||
}
|
||||
|
||||
function retryInit() {
|
||||
iron.App.removeUpdate(retryInit);
|
||||
initSoftBody();
|
||||
}
|
||||
|
||||
function initSoftBody() {
|
||||
if (ready)
|
||||
return;
|
||||
|
||||
if (PhysicsWorld.active == null || !PhysicsWorld.active.physicsReady) {
|
||||
iron.App.notifyOnUpdate(retryInit);
|
||||
return;
|
||||
}
|
||||
|
||||
ready = true;
|
||||
|
||||
if (physics == null)
|
||||
physics = PhysicsWorld.active;
|
||||
|
||||
meshObject = cast(object, MeshObject);
|
||||
meshObject.frustumCulling = false;
|
||||
var geom = meshObject.data.geom;
|
||||
var rawData = meshObject.data.raw;
|
||||
|
||||
// Get gravity from physics world
|
||||
var g = physics.getGravity();
|
||||
gravity = new Vec4(g.x, g.y, g.z);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Build vertex deduplication map from vertex_map (matching Bullet SoftBody pattern)
|
||||
// vertex_map[rawVertexBufferIdx] = uniqueVertexIdx
|
||||
// vertex_map.length = number of raw vertices in vertex buffer (NOT same as geom.indices length)
|
||||
// vertexIndexMap: uniqueVertexIdx → [rawVertexBufferIdx, ...]
|
||||
vertexIndexMap = new Map();
|
||||
var hasVertexMap = false;
|
||||
for (ind in rawData.index_arrays) {
|
||||
if (ind.vertex_map != null) {
|
||||
hasVertexMap = true;
|
||||
for (rawIdx in 0...ind.vertex_map.length) {
|
||||
var uniqueIdx = ind.vertex_map[rawIdx];
|
||||
var mapping = vertexIndexMap.get(uniqueIdx);
|
||||
if (mapping == null) {
|
||||
vertexIndexMap.set(uniqueIdx, [rawIdx]);
|
||||
} else {
|
||||
if (!mapping.contains(rawIdx))
|
||||
mapping.push(rawIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var positions = geom.positions.values;
|
||||
var scalePos = meshObject.data.scalePos;
|
||||
|
||||
if (hasVertexMap) {
|
||||
// Create particles only for unique vertices
|
||||
var numUnique = 0;
|
||||
for (_ in vertexIndexMap.keys())
|
||||
numUnique++;
|
||||
|
||||
for (key in 0...numUnique) {
|
||||
var rawIndices = vertexIndexMap.get(key);
|
||||
if (rawIndices == null || rawIndices.length == 0)
|
||||
continue;
|
||||
var ri = rawIndices[0];
|
||||
var x = (positions[ri * 4] / 32767) * scalePos;
|
||||
var y = (positions[ri * 4 + 1] / 32767) * scalePos;
|
||||
var z = (positions[ri * 4 + 2] / 32767) * scalePos;
|
||||
|
||||
// Apply object rotation and scale
|
||||
var vt = new Vec4(x, y, z);
|
||||
vt.applyQuat(object.transform.rot);
|
||||
vt.x *= object.transform.scale.x;
|
||||
vt.y *= object.transform.scale.y;
|
||||
vt.z *= object.transform.scale.z;
|
||||
vt.addf(object.transform.worldx(), object.transform.worldy(), object.transform.worldz());
|
||||
|
||||
var particle = new SoftParticle(vt.x, vt.y, vt.z, mass / numUnique);
|
||||
particles.push(particle);
|
||||
}
|
||||
|
||||
// Create springs from triangle connectivity
|
||||
// geom.indices[ia] = triangle index list of raw vertex buffer indices
|
||||
// vertex_map[rawIdx] = unique vertex index for that raw vertex
|
||||
var createdSprings:Map<String, Bool> = new Map();
|
||||
var indexArrayIdx = 0;
|
||||
for (ind in rawData.index_arrays) {
|
||||
if (ind.vertex_map == null) {
|
||||
indexArrayIdx++;
|
||||
continue;
|
||||
}
|
||||
var indexArray = geom.indices[indexArrayIdx];
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
var r0 = indexArray[i * 3];
|
||||
var r1 = indexArray[i * 3 + 1];
|
||||
var r2 = indexArray[i * 3 + 2];
|
||||
// Map raw vertex buffer indices → unique vertex indices
|
||||
var u0 = ind.vertex_map[r0];
|
||||
var u1 = ind.vertex_map[r1];
|
||||
var u2 = ind.vertex_map[r2];
|
||||
|
||||
createSpring(u0, u1, createdSprings);
|
||||
createSpring(u1, u2, createdSprings);
|
||||
createSpring(u2, u0, createdSprings);
|
||||
}
|
||||
indexArrayIdx++;
|
||||
}
|
||||
} else {
|
||||
// Fallback: no vertex_map, treat each vertex as unique
|
||||
var numVerts = Std.int(positions.length / 4);
|
||||
vertexIndexMap = new Map();
|
||||
for (i in 0...numVerts) {
|
||||
vertexIndexMap.set(i, [i]);
|
||||
|
||||
var x = (positions[i * 4] / 32767) * scalePos;
|
||||
var y = (positions[i * 4 + 1] / 32767) * scalePos;
|
||||
var z = (positions[i * 4 + 2] / 32767) * scalePos;
|
||||
|
||||
var vt = new Vec4(x, y, z);
|
||||
vt.applyQuat(object.transform.rot);
|
||||
vt.x *= object.transform.scale.x;
|
||||
vt.y *= object.transform.scale.y;
|
||||
vt.z *= object.transform.scale.z;
|
||||
vt.addf(object.transform.worldx(), object.transform.worldy(), object.transform.worldz());
|
||||
|
||||
var particle = new SoftParticle(vt.x, vt.y, vt.z, mass / numVerts);
|
||||
particles.push(particle);
|
||||
}
|
||||
|
||||
var createdSprings:Map<String, Bool> = new Map();
|
||||
for (indexArray in geom.indices) {
|
||||
var numTris = Std.int(indexArray.length / 3);
|
||||
for (i in 0...numTris) {
|
||||
createSpring(indexArray[i * 3], indexArray[i * 3 + 1], createdSprings);
|
||||
createSpring(indexArray[i * 3 + 1], indexArray[i * 3 + 2], createdSprings);
|
||||
createSpring(indexArray[i * 3 + 2], indexArray[i * 3], createdSprings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pin top edge vertices for cloth simulation
|
||||
if (shape == SoftShape.Cloth) {
|
||||
var minX = Math.POSITIVE_INFINITY, maxX = Math.NEGATIVE_INFINITY;
|
||||
var minY = Math.POSITIVE_INFINITY, maxY = Math.NEGATIVE_INFINITY;
|
||||
var minZ = Math.POSITIVE_INFINITY, maxZ = Math.NEGATIVE_INFINITY;
|
||||
for (p in particles) {
|
||||
if (p.position.x < minX) minX = p.position.x;
|
||||
if (p.position.x > maxX) maxX = p.position.x;
|
||||
if (p.position.y < minY) minY = p.position.y;
|
||||
if (p.position.y > maxY) maxY = p.position.y;
|
||||
if (p.position.z < minZ) minZ = p.position.z;
|
||||
if (p.position.z > maxZ) maxZ = p.position.z;
|
||||
}
|
||||
var extX = maxX - minX;
|
||||
var extY = maxY - minY;
|
||||
var extZ = maxZ - minZ;
|
||||
|
||||
if (extZ > 0.01) {
|
||||
var threshold = maxZ - extZ * 0.05;
|
||||
for (p in particles) {
|
||||
if (p.position.z >= threshold)
|
||||
p.pinned = true;
|
||||
}
|
||||
} else if (extY > 0.01) {
|
||||
var threshold = maxY - extY * 0.05;
|
||||
for (p in particles) {
|
||||
if (p.position.y >= threshold)
|
||||
p.pinned = true;
|
||||
}
|
||||
} else if (extX > 0.01) {
|
||||
var threshold = maxX - extX * 0.05;
|
||||
for (p in particles) {
|
||||
if (p.position.x >= threshold)
|
||||
p.pinned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyOnRemove(removeFromWorld);
|
||||
notifyOnUpdate(update);
|
||||
}
|
||||
|
||||
function createSpring(i0:Int, i1:Int, createdSprings:Map<String, Bool>) {
|
||||
if (i0 == i1)
|
||||
return;
|
||||
var key = i0 < i1 ? '${i0}_${i1}' : '${i1}_${i0}';
|
||||
if (createdSprings.exists(key))
|
||||
return;
|
||||
createdSprings.set(key, true);
|
||||
|
||||
if (i0 >= particles.length || i1 >= particles.length)
|
||||
return;
|
||||
|
||||
var p0 = particles[i0];
|
||||
var p1 = particles[i1];
|
||||
var dx = p1.position.x - p0.position.x;
|
||||
var dy = p1.position.y - p0.position.y;
|
||||
var dz = p1.position.z - p0.position.z;
|
||||
var restLength = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (restLength < 0.0001)
|
||||
return;
|
||||
|
||||
springs.push(new SoftSpring(i0, i1, restLength, 1.0 - bend));
|
||||
}
|
||||
|
||||
function update() {
|
||||
var dt = Time.delta;
|
||||
if (dt <= 0 || dt > 0.1)
|
||||
return;
|
||||
|
||||
// Apply gravity as acceleration (m/s^2, independent of mass)
|
||||
for (p in particles) {
|
||||
if (!p.pinned) {
|
||||
p.acceleration.x += gravity.x;
|
||||
p.acceleration.y += gravity.y;
|
||||
p.acceleration.z += gravity.z;
|
||||
}
|
||||
}
|
||||
|
||||
// Integrate particles
|
||||
for (p in particles) {
|
||||
p.integrate(dt);
|
||||
}
|
||||
|
||||
// Solve spring constraints
|
||||
for (iter in 0...iterations) {
|
||||
for (spring in springs) {
|
||||
solveSpring(spring);
|
||||
}
|
||||
}
|
||||
|
||||
// Ground collision
|
||||
for (p in particles) {
|
||||
if (p.position.z < 0) {
|
||||
p.position.z = 0;
|
||||
p.velocity.z = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update mesh vertices
|
||||
updateMeshVertices();
|
||||
}
|
||||
|
||||
function solveSpring(spring:SoftSpring) {
|
||||
var p1 = particles[spring.p1];
|
||||
var p2 = particles[spring.p2];
|
||||
|
||||
var dx = p2.position.x - p1.position.x;
|
||||
var dy = p2.position.y - p1.position.y;
|
||||
var dz = p2.position.z - p1.position.z;
|
||||
var dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist < 0.0001)
|
||||
return;
|
||||
|
||||
var diff = (dist - spring.restLength) / dist;
|
||||
var stiffness = spring.stiffness;
|
||||
var correction = diff * stiffness * 0.5;
|
||||
|
||||
if (!p1.pinned && p1.invMass > 0) {
|
||||
p1.position.x += dx * correction;
|
||||
p1.position.y += dy * correction;
|
||||
p1.position.z += dz * correction;
|
||||
}
|
||||
|
||||
if (!p2.pinned && p2.invMass > 0) {
|
||||
p2.position.x -= dx * correction;
|
||||
p2.position.y -= dy * correction;
|
||||
p2.position.z -= dz * correction;
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshVertices() {
|
||||
var geom = meshObject.data.geom;
|
||||
var numNodes = particles.length;
|
||||
|
||||
// Compute mean position (center of mass) for object placement
|
||||
vertOffsetX = 0.0;
|
||||
vertOffsetY = 0.0;
|
||||
vertOffsetZ = 0.0;
|
||||
for (p in particles) {
|
||||
vertOffsetX += p.position.x;
|
||||
vertOffsetY += p.position.y;
|
||||
vertOffsetZ += p.position.z;
|
||||
}
|
||||
vertOffsetX /= numNodes;
|
||||
vertOffsetY /= numNodes;
|
||||
vertOffsetZ /= numNodes;
|
||||
|
||||
// Set object transform to center of mass
|
||||
meshObject.transform.scale.set(1, 1, 1);
|
||||
meshObject.transform.loc.set(vertOffsetX, vertOffsetY, vertOffsetZ);
|
||||
meshObject.transform.rot.set(0, 0, 0, 1);
|
||||
|
||||
// Compute scalePos to fit all vertices
|
||||
var scalePos = 1.0;
|
||||
for (p in particles) {
|
||||
var mx = Math.abs((p.position.x - vertOffsetX) * 2);
|
||||
var my = Math.abs((p.position.y - vertOffsetY) * 2);
|
||||
var mz = Math.abs((p.position.z - vertOffsetZ) * 2);
|
||||
if (mx > scalePos) scalePos = mx;
|
||||
if (my > scalePos) scalePos = my;
|
||||
if (mz > scalePos) scalePos = mz;
|
||||
}
|
||||
|
||||
meshObject.data.scalePos = scalePos;
|
||||
meshObject.transform.scaleWorld = scalePos;
|
||||
meshObject.transform.buildMatrix();
|
||||
|
||||
var invScalePos = 1.0 / scalePos;
|
||||
|
||||
// Lock vertex buffer(s) for GPU upload
|
||||
#if lnx_deinterleaved
|
||||
var v:ByteArray = geom.vertexBuffers[0].buffer.lock();
|
||||
#else
|
||||
var v:ByteArray = geom.vertexBuffer.lock();
|
||||
var vbPos = geom.vertexBufferMap.get("pos");
|
||||
var v2 = vbPos != null ? vbPos.lock() : null;
|
||||
var l = geom.structLength;
|
||||
#end
|
||||
|
||||
// Write each unique particle position to all its raw vertex copies
|
||||
for (uniqueIdx in 0...numNodes) {
|
||||
var p = particles[uniqueIdx];
|
||||
var indices = vertexIndexMap.get(uniqueIdx);
|
||||
if (indices == null)
|
||||
continue;
|
||||
|
||||
var mx = p.position.x - vertOffsetX;
|
||||
var my = p.position.y - vertOffsetY;
|
||||
var mz = p.position.z - vertOffsetZ;
|
||||
|
||||
var sx = Std.int(mx * 32767 * invScalePos);
|
||||
var sy = Std.int(my * 32767 * invScalePos);
|
||||
var sz = Std.int(mz * 32767 * invScalePos);
|
||||
|
||||
for (idx in indices) {
|
||||
#if lnx_deinterleaved
|
||||
v.setInt16(idx * 8, sx);
|
||||
v.setInt16(idx * 8 + 2, sy);
|
||||
v.setInt16(idx * 8 + 4, sz);
|
||||
#else
|
||||
var vertIndex = idx * l * 2;
|
||||
v.setInt16(vertIndex, sx);
|
||||
v.setInt16(vertIndex + 2, sy);
|
||||
v.setInt16(vertIndex + 4, sz);
|
||||
if (v2 != null) {
|
||||
v2.setInt16(idx * 8, sx);
|
||||
v2.setInt16(idx * 8 + 2, sy);
|
||||
v2.setInt16(idx * 8 + 4, sz);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock triggers GPU upload
|
||||
#if lnx_deinterleaved
|
||||
geom.vertexBuffers[0].buffer.unlock();
|
||||
#else
|
||||
geom.vertexBuffer.unlock();
|
||||
if (vbPos != null) vbPos.unlock();
|
||||
#end
|
||||
}
|
||||
|
||||
public function pinVertex(index:Int) {
|
||||
if (index >= 0 && index < particles.length) {
|
||||
particles[index].pinned = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function unpinVertex(index:Int) {
|
||||
if (index >= 0 && index < particles.length) {
|
||||
particles[index].pinned = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function applyForceToVertex(index:Int, force:Vec4) {
|
||||
if (index >= 0 && index < particles.length) {
|
||||
particles[index].applyForce(force);
|
||||
}
|
||||
}
|
||||
|
||||
public function applyWindForce(direction:Vec4, strength:Float) {
|
||||
var wind = new Vec4(direction.x * strength, direction.y * strength, direction.z * strength);
|
||||
for (p in particles) {
|
||||
if (!p.pinned) {
|
||||
p.applyForce(wind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromWorld() {
|
||||
particles = [];
|
||||
springs = [];
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
276
leenkx/Sources/leenkx/trait/physics/jolt/VehicleBody.hx
Normal file
276
leenkx/Sources/leenkx/trait/physics/jolt/VehicleBody.hx
Normal file
@ -0,0 +1,276 @@
|
||||
package leenkx.trait.physics.jolt;
|
||||
|
||||
#if lnx_jolt
|
||||
|
||||
import iron.Trait;
|
||||
import iron.object.Object;
|
||||
import iron.object.CameraObject;
|
||||
import iron.object.Transform;
|
||||
import iron.system.Time;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class VehicleBody extends Trait {
|
||||
|
||||
@prop var wheel0Name:String = "Wheel0";
|
||||
@prop var wheel1Name:String = "Wheel1";
|
||||
@prop var wheel2Name:String = "Wheel2";
|
||||
@prop var wheel3Name:String = "Wheel3";
|
||||
|
||||
@prop var chassisMass:Float = 1500.0;
|
||||
@prop var maxEngineForce:Float = 3000.0;
|
||||
@prop var maxBrakingForce:Float = 500.0;
|
||||
@prop var maxSteeringAngle:Float = 0.5;
|
||||
@prop var suspensionMinLength:Float = 0.1;
|
||||
@prop var suspensionMaxLength:Float = 0.5;
|
||||
@prop var wheelRadius:Float = 0.3;
|
||||
|
||||
var physics:PhysicsWorld;
|
||||
var transform:Transform;
|
||||
var camera:CameraObject;
|
||||
|
||||
var wheels:Array<Object> = [];
|
||||
var wheelInfos:Array<VehicleWheel> = [];
|
||||
var chassisBody:RigidBody;
|
||||
var chassisBodyId:jolt.Jt.BodyID;
|
||||
var chassisReady:Bool = false;
|
||||
|
||||
var engineForce = 0.0;
|
||||
var brakingForce = 0.0;
|
||||
var vehicleSteering = 0.0;
|
||||
var currentSpeed = 0.0;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
iron.Scene.active.notifyOnInit(init);
|
||||
}
|
||||
|
||||
function init() {
|
||||
physics = PhysicsWorld.active;
|
||||
transform = object.transform;
|
||||
camera = iron.Scene.active.camera;
|
||||
|
||||
// Collect wheel objects
|
||||
for (n in [wheel0Name, wheel1Name, wheel2Name, wheel3Name]) {
|
||||
var wheel = iron.Scene.active.root.getChild(n);
|
||||
if (wheel != null) {
|
||||
wheels.push(wheel);
|
||||
wheelInfos.push(new VehicleWheel(wheelInfos.length, wheel.transform, transform));
|
||||
}
|
||||
}
|
||||
|
||||
// Create chassis rigid body
|
||||
var pos = transform.world.getLoc();
|
||||
var rot = new iron.math.Quat();
|
||||
rot.fromMat(transform.world);
|
||||
|
||||
var halfExtent = new jolt.Jt.Vec3(transform.dim.x / 2, transform.dim.y / 2, transform.dim.z / 4);
|
||||
var chassisShape = new jolt.Jt.BoxShape(halfExtent);
|
||||
|
||||
var jPos = new jolt.Jt.RVec3(pos.x, pos.y, pos.z);
|
||||
var jRot = new jolt.Jt.Quat(rot.x, rot.y, rot.z, rot.w);
|
||||
|
||||
var settings = new jolt.Jt.BodyCreationSettings(chassisShape, jPos, jRot, 2, 1);
|
||||
settings.mFriction = 0.6;
|
||||
settings.mRestitution = 0.1;
|
||||
settings.mLinearDamping = 0.1;
|
||||
settings.mAngularDamping = 0.5;
|
||||
settings.mGravityFactor = 1.0;
|
||||
|
||||
var body = physics.bodyInterface.CreateBody(settings);
|
||||
chassisBodyId = body.GetID();
|
||||
|
||||
#if hl
|
||||
settings.delete();
|
||||
jPos.delete();
|
||||
jRot.delete();
|
||||
#end
|
||||
|
||||
physics.bodyInterface.AddBody(chassisBodyId, 1);
|
||||
chassisReady = true;
|
||||
|
||||
notifyOnUpdate(update);
|
||||
notifyOnRemove(onRemove);
|
||||
}
|
||||
|
||||
function onRemove() {
|
||||
if (chassisReady) {
|
||||
physics.bodyInterface.RemoveBody(chassisBodyId);
|
||||
physics.bodyInterface.DestroyBody(chassisBodyId);
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
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");
|
||||
|
||||
// Engine force
|
||||
if (forward) {
|
||||
engineForce = maxEngineForce;
|
||||
brakingForce = 0;
|
||||
} else if (backward) {
|
||||
engineForce = -maxEngineForce * 0.5;
|
||||
brakingForce = 0;
|
||||
} else if (brake) {
|
||||
engineForce = 0;
|
||||
brakingForce = maxBrakingForce;
|
||||
} else {
|
||||
engineForce = 0;
|
||||
brakingForce = 20; // Rolling resistance
|
||||
}
|
||||
|
||||
// Steering with smooth interpolation
|
||||
var steerSpeed = 2.0 * Time.step;
|
||||
if (left) {
|
||||
vehicleSteering = Math.min(vehicleSteering + steerSpeed, maxSteeringAngle);
|
||||
} else if (right) {
|
||||
vehicleSteering = Math.max(vehicleSteering - steerSpeed, -maxSteeringAngle);
|
||||
} else {
|
||||
// Return to center
|
||||
if (vehicleSteering > 0) {
|
||||
vehicleSteering = Math.max(0, vehicleSteering - steerSpeed * 2);
|
||||
} else if (vehicleSteering < 0) {
|
||||
vehicleSteering = Math.min(0, vehicleSteering + steerSpeed * 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (chassisReady) {
|
||||
// Get current velocity to calculate speed
|
||||
var vel = physics.bodyInterface.GetLinearVelocity(chassisBodyId);
|
||||
currentSpeed = Math.sqrt(vel.GetX() * vel.GetX() + vel.GetY() * vel.GetY() + vel.GetZ() * vel.GetZ());
|
||||
|
||||
// Get chassis orientation
|
||||
var chassisRot = physics.bodyInterface.GetRotation(chassisBodyId);
|
||||
var chassisPos = physics.bodyInterface.GetPosition(chassisBodyId);
|
||||
|
||||
// Calculate forward direction from chassis rotation
|
||||
var forwardX = 2.0 * (chassisRot.GetX() * chassisRot.GetZ() + chassisRot.GetW() * chassisRot.GetY());
|
||||
var forwardY = 2.0 * (chassisRot.GetY() * chassisRot.GetZ() - chassisRot.GetW() * chassisRot.GetX());
|
||||
var forwardZ = 1.0 - 2.0 * (chassisRot.GetX() * chassisRot.GetX() + chassisRot.GetY() * chassisRot.GetY());
|
||||
|
||||
// Apply engine force
|
||||
var force = new jolt.Jt.Vec3(forwardX * engineForce, forwardY * engineForce, forwardZ * engineForce);
|
||||
physics.bodyInterface.AddForce(chassisBodyId, force);
|
||||
#if hl force.delete(); #end
|
||||
|
||||
// Apply steering torque
|
||||
if (Math.abs(vehicleSteering) > 0.01 && currentSpeed > 0.5) {
|
||||
var steerTorque = vehicleSteering * currentSpeed * 50;
|
||||
var upTorque = new jolt.Jt.Vec3(0, 0, steerTorque);
|
||||
physics.bodyInterface.AddTorque(chassisBodyId, upTorque);
|
||||
#if hl upTorque.delete(); #end
|
||||
}
|
||||
|
||||
// Apply braking
|
||||
if (brakingForce > 0 && currentSpeed > 0.1) {
|
||||
var brakeVec = new jolt.Jt.Vec3(-vel.GetX() * brakingForce * 0.01, -vel.GetY() * brakingForce * 0.01, 0);
|
||||
physics.bodyInterface.AddForce(chassisBodyId, brakeVec);
|
||||
#if hl brakeVec.delete(); #end
|
||||
}
|
||||
|
||||
// Update object transform from physics
|
||||
transform.loc.set(chassisPos.GetX(), chassisPos.GetY(), chassisPos.GetZ());
|
||||
transform.rot.set(chassisRot.GetX(), chassisRot.GetY(), chassisRot.GetZ(), chassisRot.GetW());
|
||||
transform.buildMatrix();
|
||||
|
||||
// Update wheel transforms with suspension simulation
|
||||
for (i in 0...wheels.length) {
|
||||
if (wheels[i] != null && i < wheelInfos.length) {
|
||||
updateWheelTransform(i, chassisPos, chassisRot);
|
||||
}
|
||||
}
|
||||
|
||||
// Free temporary native objects (after all usage)
|
||||
#if hl
|
||||
vel.delete();
|
||||
chassisRot.delete();
|
||||
chassisPos.delete();
|
||||
#end
|
||||
}
|
||||
|
||||
// Update camera
|
||||
if (camera != null && camera.parent != null) {
|
||||
camera.parent.transform.buildMatrix();
|
||||
}
|
||||
if (camera != null) {
|
||||
camera.buildMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
function updateWheelTransform(wheelIndex:Int, chassisPos:jolt.Jt.RVec3, chassisRot:jolt.Jt.Quat) {
|
||||
var wheel = wheels[wheelIndex];
|
||||
var info = wheelInfos[wheelIndex];
|
||||
|
||||
// Get wheel connection point in local space
|
||||
var connPoint = info.getConnectionPoint();
|
||||
|
||||
// Simple suspension raycast
|
||||
var worldX = chassisPos.GetX() + connPoint.x;
|
||||
var worldY = chassisPos.GetY() + connPoint.y;
|
||||
var worldZ = chassisPos.GetZ() - suspensionMaxLength;
|
||||
|
||||
// Position wheel
|
||||
wheel.transform.loc.set(worldX, worldY, worldZ + info.wheelRadius);
|
||||
|
||||
// Rotate front wheels for steering
|
||||
if (info.isFrontWheel) {
|
||||
wheel.transform.rot.initRotateZ(vehicleSteering);
|
||||
}
|
||||
|
||||
wheel.transform.buildMatrix();
|
||||
}
|
||||
|
||||
public function getSpeed():Float {
|
||||
return currentSpeed;
|
||||
}
|
||||
|
||||
public function getSteeringAngle():Float {
|
||||
return vehicleSteering;
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
class VehicleWheel {
|
||||
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;
|
||||
|
||||
isFrontWheel = id < 2;
|
||||
}
|
||||
|
||||
public function getConnectionPoint():Vec4 {
|
||||
return new Vec4(locX, locY, locZ);
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
Reference in New Issue
Block a user