forked from LeenkxTeam/LNXSDK
Update Files
This commit is contained in:
208
leenkx/Sources/iron/App.hx
Normal file
208
leenkx/Sources/iron/App.hx
Normal file
@ -0,0 +1,208 @@
|
||||
package iron;
|
||||
|
||||
class App {
|
||||
|
||||
public static dynamic function w(): Int { return kha.System.windowWidth(); }
|
||||
public static dynamic function h(): Int { return kha.System.windowHeight(); }
|
||||
public static dynamic function x(): Int { return 0; }
|
||||
public static dynamic function y(): Int { return 0; }
|
||||
|
||||
static var onResets: Array<Void->Void> = null;
|
||||
static var onEndFrames: Array<Void->Void> = null;
|
||||
static var traitInits: Array<Void->Void> = [];
|
||||
static var traitUpdates: Array<Void->Void> = [];
|
||||
static var traitLateUpdates: Array<Void->Void> = [];
|
||||
static var traitRenders: Array<kha.graphics4.Graphics->Void> = [];
|
||||
static var traitRenders2D: Array<kha.graphics2.Graphics->Void> = [];
|
||||
public static var framebuffer: kha.Framebuffer;
|
||||
public static var pauseUpdates = false;
|
||||
|
||||
#if lnx_debug
|
||||
static var startTime: Float;
|
||||
public static var updateTime: Float;
|
||||
public static var renderPathTime: Float;
|
||||
public static var endFrameCallbacks: Array<Void->Void> = [];
|
||||
#end
|
||||
static var lastw = -1;
|
||||
static var lasth = -1;
|
||||
public static var onResize: Void->Void = null;
|
||||
|
||||
public static function init(done: Void->Void) {
|
||||
new App(done);
|
||||
}
|
||||
|
||||
function new(done: Void->Void) {
|
||||
done();
|
||||
kha.System.notifyOnFrames(render);
|
||||
kha.Scheduler.addTimeTask(update, 0, iron.system.Time.delta);
|
||||
}
|
||||
|
||||
public static function reset() {
|
||||
traitInits = [];
|
||||
traitUpdates = [];
|
||||
traitLateUpdates = [];
|
||||
traitRenders = [];
|
||||
traitRenders2D = [];
|
||||
if (onResets != null) for (f in onResets) f();
|
||||
}
|
||||
|
||||
static function update() {
|
||||
if (Scene.active == null || !Scene.active.ready) return;
|
||||
if (pauseUpdates) return;
|
||||
|
||||
#if lnx_debug
|
||||
startTime = kha.Scheduler.realTime();
|
||||
#end
|
||||
|
||||
Scene.active.updateFrame();
|
||||
|
||||
var i = 0;
|
||||
var l = traitUpdates.length;
|
||||
while (i < l) {
|
||||
if (traitInits.length > 0) {
|
||||
for (f in traitInits) {
|
||||
traitInits.length > 0 ? f() : break;
|
||||
}
|
||||
traitInits.splice(0, traitInits.length);
|
||||
}
|
||||
traitUpdates[i]();
|
||||
// Account for removed traits
|
||||
l <= traitUpdates.length ? i++ : l = traitUpdates.length;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
l = traitLateUpdates.length;
|
||||
while (i < l) {
|
||||
traitLateUpdates[i]();
|
||||
l <= traitLateUpdates.length ? i++ : l = traitLateUpdates.length;
|
||||
}
|
||||
|
||||
if (onEndFrames != null) for (f in onEndFrames) f();
|
||||
|
||||
#if lnx_debug
|
||||
iron.object.Animation.endFrame();
|
||||
for (cb in endFrameCallbacks) cb();
|
||||
updateTime = kha.Scheduler.realTime() - startTime;
|
||||
#end
|
||||
|
||||
// Rebuild projection on window resize
|
||||
if (lastw == -1) {
|
||||
lastw = App.w();
|
||||
lasth = App.h();
|
||||
}
|
||||
if (lastw != App.w() || lasth != App.h()) {
|
||||
if (onResize != null) onResize();
|
||||
else {
|
||||
if (Scene.active != null && Scene.active.camera != null) {
|
||||
Scene.active.camera.buildProjection();
|
||||
}
|
||||
}
|
||||
}
|
||||
lastw = App.w();
|
||||
lasth = App.h();
|
||||
}
|
||||
|
||||
static function render(frames: Array<kha.Framebuffer>) {
|
||||
var frame = frames[0];
|
||||
framebuffer = frame;
|
||||
|
||||
iron.system.Time.update();
|
||||
|
||||
if (Scene.active == null || !Scene.active.ready) {
|
||||
render2D(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
startTime = kha.Scheduler.realTime();
|
||||
#end
|
||||
|
||||
if (traitInits.length > 0) {
|
||||
for (f in traitInits) {
|
||||
traitInits.length > 0 ? f() : break;
|
||||
}
|
||||
traitInits.splice(0, traitInits.length);
|
||||
}
|
||||
|
||||
Scene.active.renderFrame(frame.g4);
|
||||
|
||||
for (f in traitRenders) {
|
||||
traitRenders.length > 0 ? f(frame.g4) : break;
|
||||
}
|
||||
|
||||
render2D(frame);
|
||||
|
||||
#if lnx_debug
|
||||
renderPathTime = kha.Scheduler.realTime() - startTime;
|
||||
#end
|
||||
}
|
||||
|
||||
static function render2D(frame: kha.Framebuffer) {
|
||||
if (traitRenders2D.length > 0) {
|
||||
frame.g2.begin(false);
|
||||
for (f in traitRenders2D) {
|
||||
traitRenders2D.length > 0 ? f(frame.g2) : break;
|
||||
}
|
||||
frame.g2.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Hooks
|
||||
public static function notifyOnInit(f: Void->Void) {
|
||||
traitInits.push(f);
|
||||
}
|
||||
|
||||
public static function removeInit(f: Void->Void) {
|
||||
traitInits.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnUpdate(f: Void->Void) {
|
||||
traitUpdates.push(f);
|
||||
}
|
||||
|
||||
public static function removeUpdate(f: Void->Void) {
|
||||
traitUpdates.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnLateUpdate(f: Void->Void) {
|
||||
traitLateUpdates.push(f);
|
||||
}
|
||||
|
||||
public static function removeLateUpdate(f: Void->Void) {
|
||||
traitLateUpdates.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnRender(f: kha.graphics4.Graphics->Void) {
|
||||
traitRenders.push(f);
|
||||
}
|
||||
|
||||
public static function removeRender(f: kha.graphics4.Graphics->Void) {
|
||||
traitRenders.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
traitRenders2D.push(f);
|
||||
}
|
||||
|
||||
public static function removeRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
traitRenders2D.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnReset(f: Void->Void) {
|
||||
if (onResets == null) onResets = [];
|
||||
onResets.push(f);
|
||||
}
|
||||
|
||||
public static function removeReset(f: Void->Void) {
|
||||
onResets.remove(f);
|
||||
}
|
||||
|
||||
public static function notifyOnEndFrame(f: Void->Void) {
|
||||
if (onEndFrames == null) onEndFrames = [];
|
||||
onEndFrames.push(f);
|
||||
}
|
||||
|
||||
public static function removeEndFrame(f: Void->Void) {
|
||||
onEndFrames.remove(f);
|
||||
}
|
||||
}
|
||||
887
leenkx/Sources/iron/RenderPath.hx
Normal file
887
leenkx/Sources/iron/RenderPath.hx
Normal file
@ -0,0 +1,887 @@
|
||||
package iron;
|
||||
|
||||
import kha.Image;
|
||||
import kha.Color;
|
||||
import kha.Scheduler;
|
||||
import kha.graphics4.Graphics;
|
||||
import kha.graphics4.CubeMap;
|
||||
import kha.graphics4.DepthStencilFormat;
|
||||
import kha.graphics4.TextureFormat;
|
||||
import iron.system.Time;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.ShaderData;
|
||||
import iron.data.ConstData;
|
||||
import iron.data.Data;
|
||||
import iron.object.Object;
|
||||
import iron.object.LightObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Uniforms;
|
||||
import iron.object.Clipmap;
|
||||
|
||||
class RenderPath {
|
||||
|
||||
public static var active: RenderPath;
|
||||
|
||||
public var frameScissor = false;
|
||||
public var frameScissorX = 0;
|
||||
public var frameScissorY = 0;
|
||||
public var frameScissorW = 0;
|
||||
public var frameScissorH = 0;
|
||||
public var frameTime = 0.0;
|
||||
public var frame = 0;
|
||||
public var currentTarget: RenderTarget = null;
|
||||
public var currentFace: Int;
|
||||
public var light: LightObject = null;
|
||||
public var sun: LightObject = null;
|
||||
public var point: LightObject = null;
|
||||
#if rp_probes
|
||||
public var currentProbeIndex = 0;
|
||||
#end
|
||||
public var isProbePlanar = false;
|
||||
public var isProbeCube = false;
|
||||
public var isProbe = false;
|
||||
public var currentG: Graphics = null;
|
||||
public var frameG: Graphics;
|
||||
public var drawOrder = DrawOrder.Distance;
|
||||
public var paused = false;
|
||||
public var ready(get, null): Bool;
|
||||
function get_ready(): Bool { return loading == 0; }
|
||||
public var commands: Void->Void = null;
|
||||
public var setupDepthTexture: Void->Void = null;
|
||||
public var renderTargets: Map<String, RenderTarget> = new Map();
|
||||
public var depthToRenderTarget: Map<String, RenderTarget> = new Map();
|
||||
public var currentW: Int;
|
||||
public var currentH: Int;
|
||||
public var currentD: Int;
|
||||
var lastW = 0;
|
||||
var lastH = 0;
|
||||
var bindParams: Array<String>;
|
||||
var meshesSorted: Bool;
|
||||
var scissorSet = false;
|
||||
var viewportScaled = false;
|
||||
var lastFrameTime = 0.0;
|
||||
var loading = 0;
|
||||
var cachedShaderContexts: Map<String, CachedShaderContext> = new Map();
|
||||
var depthBuffers: Array<{name: String, format: String}> = [];
|
||||
var additionalTargets: Array<kha.Canvas>;
|
||||
|
||||
#if (rp_voxels != "Off")
|
||||
public static var pre_clear = true;
|
||||
public static var res_pre_clear = true;
|
||||
public static var clipmapLevel = 0;
|
||||
public static var clipmaps:Array<Clipmap>;
|
||||
|
||||
public static inline function getVoxelRes(): Int {
|
||||
#if (rp_voxelgi_resolution == 512)
|
||||
return 512;
|
||||
#elseif (rp_voxelgi_resolution == 256)
|
||||
return 256;
|
||||
#elseif (rp_voxelgi_resolution == 128)
|
||||
return 128;
|
||||
#elseif (rp_voxelgi_resolution == 64)
|
||||
return 64;
|
||||
#elseif (rp_voxelgi_resolution == 32)
|
||||
return 32;
|
||||
#elseif (rp_voxelgi_resolution == 16)
|
||||
return 16;
|
||||
#else
|
||||
return 0;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function getVoxelResZ(): Float {
|
||||
#if (rp_voxelgi_resolution_z == 1.0)
|
||||
return 1.0;
|
||||
#elseif (rp_voxelgi_resolution_z == 0.5)
|
||||
return 0.5;
|
||||
#elseif (rp_voxelgi_resolution_z == 0.25)
|
||||
return 0.25;
|
||||
#elseif (rp_voxelgi_resolution_z == 0.125)
|
||||
return 0.125;
|
||||
#else
|
||||
return 0.0;
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_debug
|
||||
public static var drawCalls = 0;
|
||||
public static var batchBuckets = 0;
|
||||
public static var batchCalls = 0;
|
||||
public static var culled = 0;
|
||||
public static var numTrisMesh = 0;
|
||||
public static var numTrisShadow = 0;
|
||||
#end
|
||||
|
||||
public static function setActive(renderPath: RenderPath) {
|
||||
active = renderPath;
|
||||
}
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function renderFrame(g: Graphics) {
|
||||
if (!ready || paused || iron.App.w() == 0 || iron.App.h() == 0) return;
|
||||
|
||||
if (lastW > 0 && (lastW != iron.App.w() || lastH != iron.App.h())) resize();
|
||||
lastW = iron.App.w();
|
||||
lastH = iron.App.h();
|
||||
|
||||
frameTime = Time.time() - lastFrameTime;
|
||||
lastFrameTime = Time.time();
|
||||
|
||||
#if lnx_debug
|
||||
drawCalls = 0;
|
||||
batchBuckets = 0;
|
||||
batchCalls = 0;
|
||||
culled = 0;
|
||||
numTrisMesh = 0;
|
||||
numTrisShadow = 0;
|
||||
#end
|
||||
|
||||
#if (rp_voxels != "Off")
|
||||
clipmapLevel = (clipmapLevel + 1) % Main.voxelgiClipmapCount;
|
||||
var clipmap = clipmaps[clipmapLevel];
|
||||
|
||||
clipmap.voxelSize = clipmaps[0].voxelSize * Math.pow(2.0, clipmapLevel);
|
||||
|
||||
var texelSize = 2.0 * clipmap.voxelSize;
|
||||
var camera = iron.Scene.active.camera;
|
||||
var center = new iron.math.Vec3(
|
||||
Math.floor(camera.transform.worldx() / texelSize) * texelSize,
|
||||
Math.floor(camera.transform.worldy() / texelSize) * texelSize,
|
||||
Math.floor(camera.transform.worldz() / texelSize) * texelSize
|
||||
);
|
||||
|
||||
clipmap.offset_prev.x = Std.int((clipmap.center.x - center.x) / texelSize);
|
||||
clipmap.offset_prev.y = Std.int((clipmap.center.y - center.y) / texelSize);
|
||||
clipmap.offset_prev.z = Std.int((clipmap.center.z - center.z) / texelSize);
|
||||
clipmap.center = center;
|
||||
|
||||
var res = getVoxelRes();
|
||||
var resZ = getVoxelResZ();
|
||||
var extents = new iron.math.Vec3(clipmap.voxelSize * res, clipmap.voxelSize * res, clipmap.voxelSize * resZ);
|
||||
if (clipmap.extents.x != extents.x || clipmap.extents.y != extents.y || clipmap.extents.z != extents.z)
|
||||
{
|
||||
pre_clear = true;
|
||||
}
|
||||
clipmap.extents = extents;
|
||||
#end
|
||||
|
||||
// Render to screen or probe
|
||||
var cam = Scene.active.camera;
|
||||
isProbePlanar = cam != null && cam.renderTarget != null;
|
||||
isProbeCube = cam != null && cam.renderTargetCube != null;
|
||||
isProbe = isProbePlanar || isProbeCube;
|
||||
|
||||
if (isProbePlanar) frameG = cam.renderTarget.g4;
|
||||
else if (isProbeCube) frameG = cam.renderTargetCube.g4;
|
||||
else frameG = g;
|
||||
|
||||
currentW = iron.App.w();
|
||||
currentH = iron.App.h();
|
||||
currentD = 1;
|
||||
currentFace = -1;
|
||||
meshesSorted = false;
|
||||
|
||||
for (l in Scene.active.lights) {
|
||||
if (l.visible) l.buildMatrix(Scene.active.camera);
|
||||
if (l.data.raw.type == "sun") sun = l;
|
||||
else point = l;
|
||||
}
|
||||
light = Scene.active.lights[0];
|
||||
|
||||
commands();
|
||||
|
||||
if (!isProbe) frame++;
|
||||
}
|
||||
|
||||
public function setTarget(target: String, additional: Array<String> = null, viewportScale = 1.0) {
|
||||
if (target == "") { // Framebuffer
|
||||
currentD = 1;
|
||||
currentTarget = null;
|
||||
currentFace = -1;
|
||||
if (isProbeCube) {
|
||||
currentW = Scene.active.camera.renderTargetCube.width;
|
||||
currentH = Scene.active.camera.renderTargetCube.height;
|
||||
begin(frameG, Scene.active.camera.currentFace);
|
||||
}
|
||||
else { // Screen, planar probe
|
||||
currentW = iron.App.w();
|
||||
currentH = iron.App.h();
|
||||
if (frameScissor) setFrameScissor();
|
||||
begin(frameG);
|
||||
if (!isProbe) {
|
||||
setCurrentViewport(iron.App.w(), iron.App.h());
|
||||
setCurrentScissor(iron.App.w(), iron.App.h());
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Render target
|
||||
var rt = renderTargets.get(target);
|
||||
currentTarget = rt;
|
||||
var additionalImages: Array<kha.Canvas> = null;
|
||||
if (additional != null) {
|
||||
additionalImages = [];
|
||||
for (s in additional) {
|
||||
var t = renderTargets.get(s);
|
||||
additionalImages.push(t.image);
|
||||
}
|
||||
}
|
||||
var targetG = rt.isCubeMap ? rt.cubeMap.g4 : rt.image.g4;
|
||||
currentW = rt.isCubeMap ? rt.cubeMap.width : rt.image.width;
|
||||
currentH = rt.isCubeMap ? rt.cubeMap.height : rt.image.height;
|
||||
if (rt.is3D) currentD = rt.image.depth;
|
||||
begin(targetG, additionalImages, currentFace);
|
||||
}
|
||||
if (viewportScale != 1.0) {
|
||||
viewportScaled = true;
|
||||
var viewW = Std.int(currentW * viewportScale);
|
||||
var viewH = Std.int(currentH * viewportScale);
|
||||
currentG.viewport(0, viewH, viewW, viewH);
|
||||
currentG.scissor(0, viewH, viewW, viewH);
|
||||
}
|
||||
else if (viewportScaled) { // Reset viewport
|
||||
viewportScaled = false;
|
||||
setCurrentViewport(currentW, currentH);
|
||||
setCurrentScissor(currentW, currentH);
|
||||
}
|
||||
bindParams = null;
|
||||
}
|
||||
|
||||
public function setDepthFrom(target: String, from: String) {
|
||||
var rt = renderTargets.get(target);
|
||||
rt.image.setDepthStencilFrom(renderTargets.get(from).image);
|
||||
}
|
||||
|
||||
inline function begin(g: Graphics, additionalRenderTargets: Array<kha.Canvas> = null, face = -1) {
|
||||
if (currentG != null) end();
|
||||
currentG = g;
|
||||
additionalTargets = additionalRenderTargets;
|
||||
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
|
||||
}
|
||||
|
||||
inline function end() {
|
||||
if (scissorSet) {
|
||||
currentG.disableScissor();
|
||||
scissorSet = false;
|
||||
}
|
||||
currentG.end();
|
||||
currentG = null;
|
||||
bindParams = null;
|
||||
}
|
||||
|
||||
public function setCurrentViewportWithOffset(viewW:Int, viewH:Int, offsetX: Int, offsetY: Int) {
|
||||
currentG.viewport(iron.App.x() + offsetX, currentH - viewH + iron.App.y() - offsetY, viewW, viewH);
|
||||
}
|
||||
|
||||
public function setCurrentViewport(viewW: Int, viewH: Int) {
|
||||
currentG.viewport(iron.App.x(), currentH - (viewH - iron.App.y()), viewW, viewH);
|
||||
}
|
||||
|
||||
public function setCurrentScissor(viewW: Int, viewH: Int) {
|
||||
currentG.scissor(iron.App.x(), currentH - (viewH - iron.App.y()), viewW, viewH);
|
||||
scissorSet = true;
|
||||
}
|
||||
|
||||
public function setFrameScissor() {
|
||||
frameG.scissor(frameScissorX, currentH - (frameScissorH - frameScissorY), frameScissorW, frameScissorH);
|
||||
}
|
||||
|
||||
public function setViewport(viewW: Int, viewH: Int) {
|
||||
setCurrentViewport(viewW, viewH);
|
||||
setCurrentScissor(viewW, viewH);
|
||||
}
|
||||
|
||||
public function clearTarget(colorFlag: Null<Int> = null, depthFlag: Null<Float> = null) {
|
||||
if (colorFlag == -1) { // -1 == 0xffffffff
|
||||
if (Scene.active.world != null) {
|
||||
colorFlag = Scene.active.world.raw.background_color;
|
||||
}
|
||||
else if (Scene.active.camera != null) {
|
||||
var cc = Scene.active.camera.data.raw.clear_color;
|
||||
if (cc != null) colorFlag = kha.Color.fromFloats(cc[0], cc[1], cc[2]);
|
||||
}
|
||||
}
|
||||
currentG.clear(colorFlag, depthFlag, null);
|
||||
}
|
||||
|
||||
public function clearImage(target: String, color: Int) {
|
||||
var rt = renderTargets.get(target);
|
||||
rt.image.clear(0, 0, 0, rt.image.width, rt.image.height, rt.image.depth, color);
|
||||
}
|
||||
|
||||
public function generateMipmaps(target: String) {
|
||||
var rt = renderTargets.get(target);
|
||||
rt.image.generateMipmaps(1000);
|
||||
}
|
||||
|
||||
static inline function boolToInt(b: Bool): Int {
|
||||
return b ? 1 : 0;
|
||||
}
|
||||
|
||||
public static function sortMeshesDistance(meshes: Array<MeshObject>) {
|
||||
meshes.sort(function(a, b): Int {
|
||||
#if rp_depth_texture
|
||||
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
|
||||
if (depthDiff != 0) return depthDiff;
|
||||
#end
|
||||
|
||||
return a.cameraDistance >= b.cameraDistance ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
public static function sortMeshesShader(meshes: Array<MeshObject>) {
|
||||
meshes.sort(function(a, b): Int {
|
||||
#if rp_depth_texture
|
||||
var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead);
|
||||
if (depthDiff != 0) return depthDiff;
|
||||
#end
|
||||
|
||||
return a.materials[0].name >= b.materials[0].name ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
public function drawMeshes(context: String) {
|
||||
var isShadows = context == "shadowmap";
|
||||
if (isShadows) {
|
||||
// Disabled shadow casting for this light
|
||||
if (light == null || !light.data.raw.cast_shadow || !light.visible || light.data.raw.strength == 0) return;
|
||||
}
|
||||
// Single face attached
|
||||
if (currentFace >= 0 && light != null) light.setCubeFace(currentFace, Scene.active.camera);
|
||||
|
||||
var drawn = false;
|
||||
|
||||
#if lnx_csm
|
||||
if (isShadows && light.data.raw.type == "sun") {
|
||||
var step = currentH; // Atlas with tiles on x axis
|
||||
for (i in 0...LightObject.cascadeCount) {
|
||||
light.setCascade(Scene.active.camera, i);
|
||||
currentG.viewport(i * step, 0, step, step);
|
||||
submitDraw(context);
|
||||
}
|
||||
drawn = true;
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_clusters
|
||||
if (context == "mesh") LightObject.updateClusters(Scene.active.camera);
|
||||
#end
|
||||
|
||||
if (!drawn) submitDraw(context);
|
||||
|
||||
#if lnx_debug
|
||||
// Callbacks to specific context
|
||||
if (contextEvents != null) {
|
||||
var ar = contextEvents.get(context);
|
||||
if (ar != null) for (i in 0...ar.length) ar[i](currentG, i, ar.length);
|
||||
}
|
||||
#end
|
||||
|
||||
end();
|
||||
}
|
||||
|
||||
@:access(iron.object.MeshObject)
|
||||
function submitDraw(context: String) {
|
||||
var camera = Scene.active.camera;
|
||||
var meshes = Scene.active.meshes;
|
||||
MeshObject.lastPipeline = null;
|
||||
|
||||
if (!meshesSorted && camera != null) { // Order max once per frame for now
|
||||
var camX = camera.transform.worldx();
|
||||
var camY = camera.transform.worldy();
|
||||
var camZ = camera.transform.worldz();
|
||||
for (mesh in meshes) {
|
||||
mesh.computeCameraDistance(camX, camY, camZ);
|
||||
mesh.computeDepthRead();
|
||||
}
|
||||
#if lnx_batch
|
||||
sortMeshesDistance(Scene.active.meshBatch.nonBatched);
|
||||
#else
|
||||
drawOrder == DrawOrder.Shader ? sortMeshesShader(meshes) : sortMeshesDistance(meshes);
|
||||
#end
|
||||
meshesSorted = true;
|
||||
}
|
||||
|
||||
#if lnx_batch
|
||||
Scene.active.meshBatch.render(currentG, context, bindParams);
|
||||
#else
|
||||
inline meshRenderLoop(currentG, context, bindParams, meshes);
|
||||
#end
|
||||
}
|
||||
|
||||
static inline function meshRenderLoop(g: Graphics, context: String, _bindParams: Array<String>, _meshes: Array<MeshObject>) {
|
||||
var isReadingDepth = false;
|
||||
|
||||
for (m in _meshes) {
|
||||
#if rp_depth_texture
|
||||
// First mesh that reads depth
|
||||
if (!isReadingDepth && m.depthRead) {
|
||||
if (context == "mesh") {
|
||||
// Copy the depth buffer so that we can read from it while writing
|
||||
active.setupDepthTexture();
|
||||
}
|
||||
#if rp_depthprepass
|
||||
else if (context == "depth") {
|
||||
// Don't render in depth prepass
|
||||
break;
|
||||
}
|
||||
#end
|
||||
|
||||
isReadingDepth = true;
|
||||
}
|
||||
#end
|
||||
|
||||
m.render(g, context, _bindParams);
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
static var contextEvents: Map<String, Array<Graphics->Int->Int->Void>> = null;
|
||||
public static function notifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
|
||||
if (contextEvents == null) contextEvents = new Map();
|
||||
var ar = contextEvents.get(name);
|
||||
if (ar == null) {
|
||||
ar = [];
|
||||
contextEvents.set(name, ar);
|
||||
}
|
||||
ar.push(onContext);
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_decals
|
||||
public function drawDecals(context: String) {
|
||||
if (ConstData.boxVB == null) ConstData.createBoxData();
|
||||
for (decal in Scene.active.decals) {
|
||||
decal.render(currentG, context, bindParams);
|
||||
}
|
||||
end();
|
||||
}
|
||||
#end
|
||||
|
||||
public function drawSkydome(handle: String) {
|
||||
if (ConstData.skydomeVB == null) ConstData.createSkydomeData();
|
||||
var cc: CachedShaderContext = cachedShaderContexts.get(handle);
|
||||
if (cc.context == null) return; // World data not specified
|
||||
currentG.setPipeline(cc.context.pipeState);
|
||||
Uniforms.setContextConstants(currentG, cc.context, bindParams);
|
||||
Uniforms.setObjectConstants(currentG, cc.context, null); // External hosek
|
||||
#if lnx_deinterleaved
|
||||
currentG.setVertexBuffers(ConstData.skydomeVB);
|
||||
#else
|
||||
currentG.setVertexBuffer(ConstData.skydomeVB);
|
||||
#end
|
||||
currentG.setIndexBuffer(ConstData.skydomeIB);
|
||||
currentG.drawIndexedVertices();
|
||||
end();
|
||||
}
|
||||
|
||||
#if rp_probes
|
||||
public function drawVolume(object: Object, handle: String) {
|
||||
if (ConstData.boxVB == null) ConstData.createBoxData();
|
||||
var cc: CachedShaderContext = cachedShaderContexts.get(handle);
|
||||
currentG.setPipeline(cc.context.pipeState);
|
||||
Uniforms.setContextConstants(currentG, cc.context, bindParams);
|
||||
Uniforms.setObjectConstants(currentG, cc.context, object);
|
||||
currentG.setVertexBuffer(ConstData.boxVB);
|
||||
currentG.setIndexBuffer(ConstData.boxIB);
|
||||
currentG.drawIndexedVertices();
|
||||
end();
|
||||
}
|
||||
#end
|
||||
|
||||
public function bindTarget(target: String, uniform: String) {
|
||||
if (bindParams != null) {
|
||||
bindParams.push(target);
|
||||
bindParams.push(uniform);
|
||||
}
|
||||
else bindParams = [target, uniform];
|
||||
}
|
||||
|
||||
// Full-screen triangle
|
||||
public function drawShader(handle: String) {
|
||||
// file/data_name/context
|
||||
var cc: CachedShaderContext = cachedShaderContexts.get(handle);
|
||||
if (ConstData.screenAlignedVB == null) ConstData.createScreenAlignedData();
|
||||
currentG.setPipeline(cc.context.pipeState);
|
||||
Uniforms.setContextConstants(currentG, cc.context, bindParams);
|
||||
Uniforms.setObjectConstants(currentG, cc.context, null);
|
||||
currentG.setVertexBuffer(ConstData.screenAlignedVB);
|
||||
currentG.setIndexBuffer(ConstData.screenAlignedIB);
|
||||
currentG.drawIndexedVertices();
|
||||
|
||||
end();
|
||||
}
|
||||
|
||||
public function getComputeShader(handle: String): kha.compute.Shader {
|
||||
return Reflect.field(kha.Shaders, handle + "_comp");
|
||||
}
|
||||
|
||||
#if (kha_krom && lnx_vr)
|
||||
public function drawStereo(drawMeshes: Int->Void) {
|
||||
for (eye in 0...2) {
|
||||
Krom.vrBeginRender(eye);
|
||||
drawMeshes(eye);
|
||||
Krom.vrEndRender(eye);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
public function loadShader(handle: String) {
|
||||
loading++;
|
||||
var cc: CachedShaderContext = cachedShaderContexts.get(handle);
|
||||
if (cc != null) {
|
||||
loading--;
|
||||
return;
|
||||
}
|
||||
|
||||
cc = new CachedShaderContext();
|
||||
cachedShaderContexts.set(handle, cc);
|
||||
|
||||
// file/data_name/context
|
||||
var shaderPath = handle.split("/");
|
||||
|
||||
#if lnx_json
|
||||
shaderPath[0] += ".json";
|
||||
#end
|
||||
|
||||
Data.getShader(shaderPath[0], shaderPath[1], function(res: ShaderData) {
|
||||
cc.context = res.getContext(shaderPath[2]);
|
||||
loading--;
|
||||
});
|
||||
}
|
||||
|
||||
public function unloadShader(handle: String) {
|
||||
cachedShaderContexts.remove(handle);
|
||||
|
||||
// file/data_name/context
|
||||
var shaderPath = handle.split("/");
|
||||
// Todo: Handle context overrides (see Data.getShader())
|
||||
Data.cachedShaders.remove(shaderPath[1]);
|
||||
}
|
||||
|
||||
public function unload() {
|
||||
for (rt in renderTargets) rt.unload();
|
||||
}
|
||||
|
||||
public function resize() {
|
||||
if (kha.System.windowWidth() == 0 || kha.System.windowHeight() == 0) return;
|
||||
|
||||
// Make sure depth buffer is attached to single target only and gets released once
|
||||
for (rt in renderTargets) {
|
||||
if (rt == null ||
|
||||
rt.raw.width > 0 ||
|
||||
rt.depthStencilFrom == "" ||
|
||||
rt == depthToRenderTarget.get(rt.depthStencilFrom)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var nodepth: RenderTarget = null;
|
||||
for (rt2 in renderTargets) {
|
||||
if (rt2 == null ||
|
||||
rt2.raw.width > 0 ||
|
||||
rt2.depthStencilFrom != "" ||
|
||||
depthToRenderTarget.get(rt2.raw.depth_buffer) != null ||
|
||||
rt2.raw.is_image == true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nodepth = rt2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (nodepth != null) {
|
||||
rt.image.setDepthStencilFrom(nodepth.image);
|
||||
}
|
||||
}
|
||||
|
||||
// Resize textures
|
||||
for (rt in renderTargets) {
|
||||
if (rt != null && rt.raw.width == 0) {
|
||||
App.notifyOnInit(rt.image.unload);
|
||||
rt.image = createImage(rt.raw, rt.depthStencil);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach depth buffers
|
||||
for (rt in renderTargets) {
|
||||
if (rt != null && rt.depthStencilFrom != "") {
|
||||
rt.image.setDepthStencilFrom(depthToRenderTarget.get(rt.depthStencilFrom).image);
|
||||
}
|
||||
}
|
||||
|
||||
#if (rp_voxels != "Off")
|
||||
res_pre_clear = true;
|
||||
#end
|
||||
}
|
||||
|
||||
public function createRenderTarget(t: RenderTargetRaw): RenderTarget {
|
||||
var rt = createTarget(t);
|
||||
renderTargets.set(t.name, rt);
|
||||
return rt;
|
||||
}
|
||||
|
||||
public function createDepthBuffer(name: String, format: String = null) {
|
||||
depthBuffers.push({ name: name, format: format });
|
||||
}
|
||||
|
||||
function createTarget(t: RenderTargetRaw): RenderTarget {
|
||||
var rt = new RenderTarget(t);
|
||||
// With depth buffer
|
||||
if (t.depth_buffer != null) {
|
||||
rt.hasDepth = true;
|
||||
var depthTarget = depthToRenderTarget.get(t.depth_buffer);
|
||||
|
||||
if (depthTarget == null) { // Create new one
|
||||
for (db in depthBuffers) {
|
||||
if (db.name == t.depth_buffer) {
|
||||
depthToRenderTarget.set(db.name, rt);
|
||||
rt.depthStencil = getDepthStencilFormat(db.format);
|
||||
rt.image = createImage(t, rt.depthStencil);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Reuse
|
||||
rt.depthStencil = DepthStencilFormat.NoDepthAndStencil;
|
||||
rt.depthStencilFrom = t.depth_buffer;
|
||||
rt.image = createImage(t, rt.depthStencil);
|
||||
rt.image.setDepthStencilFrom(depthTarget.image);
|
||||
}
|
||||
}
|
||||
else { // No depth buffer
|
||||
rt.hasDepth = false;
|
||||
if (t.depth != null && t.depth > 1) rt.is3D = true;
|
||||
if (t.is_cubemap) {
|
||||
rt.isCubeMap = true;
|
||||
rt.depthStencil = DepthStencilFormat.NoDepthAndStencil;
|
||||
rt.cubeMap = createCubeMap(t, rt.depthStencil);
|
||||
}
|
||||
else {
|
||||
rt.depthStencil = DepthStencilFormat.NoDepthAndStencil;
|
||||
rt.image = createImage(t, rt.depthStencil);
|
||||
}
|
||||
}
|
||||
return rt;
|
||||
}
|
||||
|
||||
function createImage(t: RenderTargetRaw, depthStencil: DepthStencilFormat): Image {
|
||||
var width = t.width == 0 ? iron.App.w() : t.width;
|
||||
var height = t.height == 0 ? iron.App.h() : t.height;
|
||||
var depth = t.depth != null ? t.depth : 0;
|
||||
if (t.displayp != null) { // 1080p/..
|
||||
if (width > height) {
|
||||
width = Std.int(width * (t.displayp / height));
|
||||
height = t.displayp;
|
||||
}
|
||||
else {
|
||||
height = Std.int(height * (t.displayp / width));
|
||||
width = t.displayp;
|
||||
}
|
||||
}
|
||||
if (t.scale != null) {
|
||||
width = Std.int(width * t.scale);
|
||||
height = Std.int(height * t.scale);
|
||||
depth = Std.int(depth * t.scale);
|
||||
}
|
||||
if (width < 1) width = 1;
|
||||
if (height < 1) height = 1;
|
||||
if (t.depth != null && t.depth > 1) { // 3D texture
|
||||
// Image only
|
||||
var img = Image.create3D(width, height, depth,
|
||||
t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32);
|
||||
if (t.mipmaps)
|
||||
img.generateMipmaps(1000); // Allocate mipmaps
|
||||
return img;
|
||||
}
|
||||
else { // 2D texture
|
||||
if (t.is_image != null && t.is_image) { // Image
|
||||
var img = Image.create(width, height,
|
||||
t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32);
|
||||
if (t.mipmaps)
|
||||
img.generateMipmaps(1000); // Allocate mipmaps
|
||||
return img;
|
||||
}
|
||||
else { // Render target
|
||||
return Image.createRenderTarget(width, height,
|
||||
t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32,
|
||||
depthStencil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createCubeMap(t: RenderTargetRaw, depthStencil: DepthStencilFormat): CubeMap {
|
||||
return CubeMap.createRenderTarget(t.width,
|
||||
t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32,
|
||||
depthStencil);
|
||||
}
|
||||
|
||||
inline function getTextureFormat(s: String): TextureFormat {
|
||||
switch (s) {
|
||||
case "RGBA32": return TextureFormat.RGBA32;
|
||||
case "RGBA64": return TextureFormat.RGBA64;
|
||||
case "RGBA128": return TextureFormat.RGBA128;
|
||||
case "DEPTH16": return TextureFormat.DEPTH16;
|
||||
case "R32": return TextureFormat.A32;
|
||||
case "R16": return TextureFormat.A16;
|
||||
case "R8": return TextureFormat.L8;
|
||||
default: return TextureFormat.RGBA32;
|
||||
}
|
||||
}
|
||||
|
||||
inline function getDepthStencilFormat(s: String): DepthStencilFormat {
|
||||
if (s == null || s == "") return DepthStencilFormat.DepthOnly;
|
||||
switch (s) {
|
||||
case "DEPTH24": return DepthStencilFormat.DepthOnly;
|
||||
case "DEPTH16": return DepthStencilFormat.Depth16;
|
||||
default: return DepthStencilFormat.DepthOnly;
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_shadowmap_atlas
|
||||
// Allow setting a target with manual end() calling, this is to render multiple times to the same image (atlas)
|
||||
// TODO: allow manual end() calling in existing functions to prevent duplicated code
|
||||
public function setTargetStream(target:String, additional:Array<String> = null, viewportScale = 1.0) {
|
||||
if (target == "") { // Framebuffer
|
||||
currentD = 1;
|
||||
currentTarget = null;
|
||||
currentFace = -1;
|
||||
if (isProbeCube) {
|
||||
currentW = Scene.active.camera.renderTargetCube.width;
|
||||
currentH = Scene.active.camera.renderTargetCube.height;
|
||||
beginStream(frameG, Scene.active.camera.currentFace);
|
||||
}
|
||||
else { // Screen, planar probe
|
||||
currentW = iron.App.w();
|
||||
currentH = iron.App.h();
|
||||
if (frameScissor) {
|
||||
setFrameScissor();
|
||||
}
|
||||
beginStream(frameG);
|
||||
if (!isProbe) {
|
||||
setCurrentViewport(iron.App.w(), iron.App.h());
|
||||
setCurrentScissor(iron.App.w(), iron.App.h());
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Render target
|
||||
var rt = renderTargets.get(target);
|
||||
currentTarget = rt;
|
||||
var additionalImages:Array<kha.Canvas> = null;
|
||||
if (additional != null) {
|
||||
additionalImages = [];
|
||||
for (s in additional) {
|
||||
var t = renderTargets.get(s);
|
||||
additionalImages.push(t.image);
|
||||
}
|
||||
}
|
||||
var targetG = rt.isCubeMap ? rt.cubeMap.g4 : rt.image.g4;
|
||||
currentW = rt.isCubeMap ? rt.cubeMap.width : rt.image.width;
|
||||
currentH = rt.isCubeMap ? rt.cubeMap.height : rt.image.height;
|
||||
if (rt.is3D) {
|
||||
currentD = rt.image.depth;
|
||||
}
|
||||
beginStream(targetG, additionalImages, currentFace);
|
||||
}
|
||||
if (viewportScale != 1.0) {
|
||||
viewportScaled = true;
|
||||
var viewW = Std.int(currentW * viewportScale);
|
||||
var viewH = Std.int(currentH * viewportScale);
|
||||
currentG.viewport(0, viewH, viewW, viewH);
|
||||
currentG.scissor(0, viewH, viewW, viewH);
|
||||
}
|
||||
else if (viewportScaled) { // Reset viewport
|
||||
viewportScaled = false;
|
||||
setCurrentViewport(currentW, currentH);
|
||||
setCurrentScissor(currentW, currentH);
|
||||
}
|
||||
bindParams = null;
|
||||
}
|
||||
|
||||
inline function beginStream(g:Graphics, additionalRenderTargets:Array<kha.Canvas> = null, face = -1) {
|
||||
currentG = g;
|
||||
additionalTargets = additionalRenderTargets;
|
||||
face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets);
|
||||
}
|
||||
|
||||
public function endStream() {
|
||||
if (scissorSet) {
|
||||
currentG.disableScissor();
|
||||
scissorSet = false;
|
||||
}
|
||||
currentG.end();
|
||||
currentG = null;
|
||||
bindParams = null;
|
||||
}
|
||||
|
||||
public function drawMeshesStream(context:String) {
|
||||
// Single face attached
|
||||
if (currentFace >= 0 && light != null) {
|
||||
light.setCubeFace(currentFace, Scene.active.camera);
|
||||
}
|
||||
|
||||
#if lnx_clusters
|
||||
if (context == "mesh") {
|
||||
LightObject.updateClusters(Scene.active.camera);
|
||||
}
|
||||
#end
|
||||
|
||||
submitDraw(context);
|
||||
|
||||
#if lnx_debug
|
||||
// Callbacks to specific context
|
||||
if (contextEvents != null) {
|
||||
var ar = contextEvents.get(context);
|
||||
if (ar != null) {
|
||||
for (i in 0...ar.length) {
|
||||
ar[i](currentG, i, ar.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
#end // lnx_shadowmap_atlas
|
||||
}
|
||||
|
||||
class RenderTargetRaw {
|
||||
public var name: String;
|
||||
public var width: Int;
|
||||
public var height: Int;
|
||||
public var format: String = null;
|
||||
public var scale: Null<Float> = null;
|
||||
public var displayp: Null<Int> = null; // Set to 1080p/...
|
||||
public var depth_buffer: String = null; // 2D texture
|
||||
public var mipmaps: Null<Bool> = null;
|
||||
public var depth: Null<Int> = null; // 3D texture
|
||||
public var is_image: Null<Bool> = null; // Image
|
||||
public var is_cubemap: Null<Bool> = null; // Cubemap
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
class RenderTarget {
|
||||
public var raw: RenderTargetRaw;
|
||||
public var depthStencil: DepthStencilFormat;
|
||||
public var depthStencilFrom = "";
|
||||
public var image: Image = null; // RT or image
|
||||
public var cubeMap: CubeMap = null;
|
||||
public var hasDepth = false;
|
||||
public var is3D = false; // sampler2D / sampler3D
|
||||
public var isCubeMap = false;
|
||||
public function new(raw: RenderTargetRaw) { this.raw = raw; }
|
||||
public function unload() {
|
||||
if (image != null) image.unload();
|
||||
if (cubeMap != null) cubeMap.unload();
|
||||
}
|
||||
}
|
||||
|
||||
class CachedShaderContext {
|
||||
public var context: ShaderContext;
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
@:enum abstract DrawOrder(Int) from Int {
|
||||
var Distance = 0; // Early-z
|
||||
var Shader = 1; // Less state changes
|
||||
// var Mix = 2; // Distance buckets sorted by shader
|
||||
}
|
||||
996
leenkx/Sources/iron/Scene.hx
Normal file
996
leenkx/Sources/iron/Scene.hx
Normal file
@ -0,0 +1,996 @@
|
||||
package iron;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import kha.graphics4.TextureFormat;
|
||||
import iron.Trait;
|
||||
import iron.object.Transform;
|
||||
import iron.object.Constraint;
|
||||
import iron.object.Animation;
|
||||
import iron.object.Object;
|
||||
import iron.object.CameraObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.LightObject;
|
||||
import iron.object.SpeakerObject;
|
||||
import iron.object.DecalObject;
|
||||
import iron.object.ProbeObject;
|
||||
import iron.object.Tilesheet;
|
||||
import iron.data.CameraData;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.LightData;
|
||||
import iron.data.ProbeData;
|
||||
import iron.data.WorldData;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.Armature;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.TerrainStream;
|
||||
import iron.data.SceneStream;
|
||||
import iron.data.MeshBatch;
|
||||
import iron.system.Time;
|
||||
using StringTools;
|
||||
|
||||
class Scene {
|
||||
|
||||
public static var active: Scene = null;
|
||||
public static var global: Object = null;
|
||||
static var uidCounter = 0;
|
||||
public var uid: Int;
|
||||
public var raw: TSceneFormat;
|
||||
public var root: Object;
|
||||
public var sceneParent: Object;
|
||||
public var camera: CameraObject;
|
||||
public var world: WorldData;
|
||||
|
||||
#if lnx_batch
|
||||
public var meshBatch: MeshBatch = null;
|
||||
#end
|
||||
#if lnx_stream
|
||||
public var sceneStream: SceneStream = null;
|
||||
#end
|
||||
#if lnx_terrain
|
||||
public var terrainStream: TerrainStream = null;
|
||||
#end
|
||||
#if rp_decals
|
||||
public var decals: Array<DecalObject>;
|
||||
#end
|
||||
#if rp_probes
|
||||
public var probes: Array<ProbeObject>;
|
||||
#end
|
||||
public var meshes: Array<MeshObject>;
|
||||
public var lights: Array<LightObject>;
|
||||
public var cameras: Array<CameraObject>;
|
||||
#if lnx_audio
|
||||
public var speakers: Array<SpeakerObject>;
|
||||
#end
|
||||
public var empties: Array<Object>;
|
||||
public var animations: Array<Animation>;
|
||||
public var tilesheets: Array<Tilesheet>;
|
||||
#if lnx_skin
|
||||
public var armatures: Array<Armature>;
|
||||
#end
|
||||
var groups: Map<String, Array<Object>> = null;
|
||||
|
||||
public var embedded: Map<String, kha.Image>;
|
||||
|
||||
public var ready: Bool; // Async in progress
|
||||
|
||||
public var traitInits: Array<Void->Void> = [];
|
||||
public var traitRemoves: Array<Void->Void> = [];
|
||||
|
||||
var initializing: Bool; // Is the scene in its initialization phase?
|
||||
|
||||
public function new() {
|
||||
uid = uidCounter++;
|
||||
#if lnx_batch
|
||||
meshBatch = new MeshBatch();
|
||||
#end
|
||||
#if lnx_stream
|
||||
sceneStream = new SceneStream();
|
||||
#end
|
||||
#if rp_decals
|
||||
decals = [];
|
||||
#end
|
||||
#if rp_probes
|
||||
probes = [];
|
||||
#end
|
||||
meshes = [];
|
||||
lights = [];
|
||||
cameras = [];
|
||||
#if lnx_audio
|
||||
speakers = [];
|
||||
#end
|
||||
empties = [];
|
||||
animations = [];
|
||||
tilesheets = [];
|
||||
#if lnx_skin
|
||||
armatures = [];
|
||||
#end
|
||||
embedded = new Map();
|
||||
root = new Object();
|
||||
root.name = "Root";
|
||||
traitInits = [];
|
||||
traitRemoves = [];
|
||||
initializing = true;
|
||||
if (global == null) global = new Object();
|
||||
}
|
||||
|
||||
public static function create(format: TSceneFormat, done: Object->Void) {
|
||||
active = new Scene();
|
||||
active.ready = false;
|
||||
active.raw = format;
|
||||
|
||||
Data.getWorld(format.name, format.world_ref, function(world: WorldData) {
|
||||
active.world = world;
|
||||
|
||||
// Startup scene
|
||||
active.addScene(format.name, null, function(sceneObject: Object) {
|
||||
for (object in sceneObject.getChildren(true)) {
|
||||
createTraits(object.raw.traits, object);
|
||||
}
|
||||
|
||||
#if lnx_terrain
|
||||
if (format.terrain_ref != null) {
|
||||
active.terrainStream = new TerrainStream(format.terrain_datas[0]);
|
||||
}
|
||||
#end
|
||||
|
||||
if (active.cameras.length == 0) {
|
||||
trace('No camera found for scene "' + format.name + '"');
|
||||
}
|
||||
|
||||
active.camera = active.getCamera(format.camera_ref);
|
||||
active.sceneParent = sceneObject;
|
||||
|
||||
active.ready = true;
|
||||
|
||||
for (f in active.traitInits) f();
|
||||
active.traitInits = [];
|
||||
|
||||
active.initializing = false;
|
||||
done(sceneObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#if lnx_patch
|
||||
public static var getRenderPath: Void->RenderPath;
|
||||
public static function patch() {
|
||||
Data.deleteAll();
|
||||
var cameraTransform = Scene.active.camera.transform;
|
||||
Scene.setActive(Scene.active.raw.name, function(o: Object) {
|
||||
RenderPath.setActive(getRenderPath());
|
||||
Scene.active.camera.transform = cameraTransform;
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
public function remove() {
|
||||
for (f in traitRemoves) f();
|
||||
#if lnx_batch
|
||||
if (meshBatch != null) meshBatch.remove();
|
||||
#end
|
||||
#if lnx_stream
|
||||
if (sceneStream != null) sceneStream.remove();
|
||||
#end
|
||||
#if lnx_terrain
|
||||
if (terrainStream != null) terrainStream.remove();
|
||||
#end
|
||||
#if rp_decals
|
||||
for (o in decals) o.remove();
|
||||
#end
|
||||
#if rp_probes
|
||||
for (o in probes) o.remove();
|
||||
#end
|
||||
for (o in meshes) o.remove();
|
||||
for (o in lights) o.remove();
|
||||
for (o in cameras) o.remove();
|
||||
#if lnx_audio
|
||||
for (o in speakers) o.remove();
|
||||
#end
|
||||
for (o in empties) o.remove();
|
||||
groups = null;
|
||||
root.remove();
|
||||
}
|
||||
|
||||
static var framePassed = true;
|
||||
public static function setActive(sceneName: String, done: Object->Void = null) {
|
||||
if (!framePassed) return;
|
||||
framePassed = false;
|
||||
|
||||
// Defer unloading the world shader until the new world shader is loaded
|
||||
// to prevent errors due to a missing world shader inbetween
|
||||
var removeWorldShader: String = null;
|
||||
|
||||
if (Scene.active != null) {
|
||||
#if (rp_background == "World")
|
||||
if (Scene.active.raw.world_ref != null) {
|
||||
removeWorldShader = "shader_datas/World_" + Scene.active.raw.world_ref + "/World_" + Scene.active.raw.world_ref;
|
||||
}
|
||||
#end
|
||||
Scene.active.remove();
|
||||
}
|
||||
|
||||
Data.getSceneRaw(sceneName, function(format: TSceneFormat) {
|
||||
Scene.create(format, function(o: Object) {
|
||||
if (done != null) done(o);
|
||||
|
||||
#if (rp_background == "World")
|
||||
if (removeWorldShader != null) {
|
||||
RenderPath.active.unloadShader(removeWorldShader);
|
||||
}
|
||||
if (format.world_ref != null) {
|
||||
RenderPath.active.loadShader("shader_datas/World_" + format.world_ref + "/World_" + format.world_ref);
|
||||
}
|
||||
#end
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function updateFrame() {
|
||||
if (!ready) return;
|
||||
#if lnx_stream
|
||||
sceneStream.update(active.camera);
|
||||
#end
|
||||
#if lnx_terrain
|
||||
if (terrainStream != null) terrainStream.update(active.camera);
|
||||
#end
|
||||
for (anim in animations) anim.update(Time.delta);
|
||||
for (e in empties) if (e != null && e.parent != null) e.transform.update();
|
||||
}
|
||||
|
||||
public function renderFrame(g: kha.graphics4.Graphics) {
|
||||
if (!ready || RenderPath.active == null) return;
|
||||
framePassed = true;
|
||||
|
||||
for (tilesheet in tilesheets) {
|
||||
tilesheet.update();
|
||||
}
|
||||
|
||||
// Render probes
|
||||
#if rp_probes
|
||||
var activeCamera = camera;
|
||||
for (probe in probes) {
|
||||
camera = probe.camera;
|
||||
probe.render(g, activeCamera);
|
||||
}
|
||||
camera = activeCamera;
|
||||
#end
|
||||
|
||||
// Render active camera
|
||||
camera != null ? camera.renderFrame(g) : RenderPath.active.renderFrame(g);
|
||||
}
|
||||
|
||||
// Objects
|
||||
public function addObject(parent: Object = null): Object {
|
||||
var object = new Object();
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the children of the scene.
|
||||
*
|
||||
* If 'recursive' is set to `false`, only direct children will be included
|
||||
* in the returned array. If `recursive` is `true`, children of children and
|
||||
* so on will be included too.
|
||||
*
|
||||
* @param recursive = `false` Include children of children
|
||||
* @return `Array<Object>`
|
||||
*/
|
||||
public function getChildren(?recursive = false): Array<Object> {
|
||||
return root.getChildren(recursive);
|
||||
}
|
||||
|
||||
public function getChild(name: String): Object {
|
||||
return root.getChild(name);
|
||||
}
|
||||
|
||||
public function getTrait(c: Class<Trait>): Dynamic {
|
||||
return root.children.length > 0 ? root.children[0].getTrait(c) : null;
|
||||
}
|
||||
|
||||
public function getMesh(name: String): MeshObject {
|
||||
for (m in meshes) if (m.name == name) return m;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getLight(name: String): LightObject {
|
||||
for (l in lights) if (l.name == name) return l;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getCamera(name: String): CameraObject {
|
||||
for (c in cameras) if (c.name == name) return c;
|
||||
return null;
|
||||
}
|
||||
|
||||
#if lnx_audio
|
||||
public function getSpeaker(name: String): SpeakerObject {
|
||||
for (s in speakers) if (s.name == name) return s;
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
public function getEmpty(name: String): Object {
|
||||
for (e in empties) if (e.name == name) return e;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getGroup(name: String): Array<Object> {
|
||||
if (groups == null) groups = new Map();
|
||||
var g = groups.get(name);
|
||||
if (g == null) {
|
||||
g = [];
|
||||
groups.set(name, g);
|
||||
var refs = getGroupObjectRefs(name, active.raw);
|
||||
if (refs == null) return g;
|
||||
for (ref in refs) {
|
||||
var c = getChild(ref);
|
||||
if (c != null) g.push(c);
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
public function addMeshObject(data: MeshData, materials: Vector<MaterialData>, parent: Object = null): MeshObject {
|
||||
var object = new MeshObject(data, materials);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
|
||||
public function addLightObject(data: LightData, parent: Object = null): LightObject {
|
||||
var object = new LightObject(data);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
|
||||
#if rp_probes
|
||||
public function addProbeObject(data: ProbeData, parent: Object = null): ProbeObject {
|
||||
var object = new ProbeObject(data);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
#end
|
||||
|
||||
public function addCameraObject(data: CameraData, parent: Object = null): CameraObject {
|
||||
var object = new CameraObject(data);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
|
||||
#if lnx_audio
|
||||
public function addSpeakerObject(data: TSpeakerData, parent: Object = null): SpeakerObject {
|
||||
var object = new SpeakerObject(data);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_decals
|
||||
public function addDecalObject(material: MaterialData, parent: Object = null): DecalObject {
|
||||
var object = new DecalObject(material);
|
||||
parent != null ? object.setParent(parent) : object.setParent(root);
|
||||
return object;
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_stream
|
||||
var objectsTraversed = 0;
|
||||
#end
|
||||
public function addScene(sceneName: String, parent: Object, done: Object->Void) {
|
||||
if (parent == null) {
|
||||
parent = addObject();
|
||||
parent.name = sceneName;
|
||||
}
|
||||
Data.getSceneRaw(sceneName, function(format: TSceneFormat) {
|
||||
loadEmbeddedData(format.embedded_datas, function() { // Additional scene assets
|
||||
#if lnx_stream
|
||||
objectsTraversed = 0;
|
||||
#else
|
||||
var objectsTraversed = 0;
|
||||
#end
|
||||
|
||||
var objectsCount = getObjectsCount(format.objects);
|
||||
function traverseObjects(parent: Object, objects: Array<TObj>, parentObject: TObj, done: Void->Void) {
|
||||
if (objects == null) return;
|
||||
for (i in 0...objects.length) {
|
||||
var o = objects[i];
|
||||
if (o.spawn != null && o.spawn == false) {
|
||||
if (++objectsTraversed == objectsCount) done();
|
||||
continue; // Do not auto-create this object
|
||||
}
|
||||
|
||||
createObject(o, format, parent, parentObject, function(object: Object) {
|
||||
traverseObjects(object, o.children, o, done);
|
||||
if (++objectsTraversed == objectsCount) done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (format.objects == null || format.objects.length == 0) {
|
||||
createTraits(format.traits, parent); // Scene traits
|
||||
done(parent);
|
||||
}
|
||||
else {
|
||||
traverseObjects(parent, format.objects, null, function() { // Scene objects
|
||||
createTraits(format.traits, parent); // Scene traits
|
||||
done(parent);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getObjectsCount(objects: Array<TObj>, discardNoSpawn = true): Int {
|
||||
if (objects == null) return 0;
|
||||
var result = objects.length;
|
||||
for (o in objects) {
|
||||
if (discardNoSpawn && o.spawn != null && o.spawn == false) continue; // Do not count children of non-spawned objects
|
||||
if (o.children != null) result += getObjectsCount(o.children);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Spawn a new object instance in the scene.
|
||||
@param name The name of the object as defined in Blender.
|
||||
@param parent The parent object this new object should be attached to (optional, use `null` to add to the scene without a parent).
|
||||
@param done Function to run after the spawn is completed (optional). Useful to change properties of the object after spawning.
|
||||
@param spawnChildren Also spawn the children of the newly spawned object (optional, default is `true`).
|
||||
@param srcRaw If not `null`, spawn the object from the given scene data instead of using the scene this function is called on. Useful to spawn objects from other scenes.
|
||||
**/
|
||||
public function spawnObject(name: String, parent: Null<Object>, done: Null<Object->Void>, spawnChildren = true, srcRaw: Null<TSceneFormat> = null) {
|
||||
if (srcRaw == null) srcRaw = raw;
|
||||
var objectsTraversed = 0;
|
||||
var obj = getRawObjectByName(srcRaw, name);
|
||||
var objectsCount = spawnChildren ? getObjectsCount([obj], false) : 1;
|
||||
var rootId = -1;
|
||||
function spawnObjectTree(obj: TObj, parent: Object, parentObject: TObj, done: Object->Void) {
|
||||
createObject(obj, srcRaw, parent, parentObject, function(object: Object) {
|
||||
if (rootId == -1) {
|
||||
rootId = object.uid;
|
||||
}
|
||||
if (spawnChildren && obj.children != null) {
|
||||
for (child in obj.children) spawnObjectTree(child, object, obj, done);
|
||||
}
|
||||
if (++objectsTraversed == objectsCount && done != null) {
|
||||
// Retrieve the originally spawned object from the current
|
||||
// child object to ensure done() is called with the right
|
||||
// object
|
||||
while (object.uid != rootId) {
|
||||
object = object.parent;
|
||||
}
|
||||
done(object);
|
||||
}
|
||||
});
|
||||
}
|
||||
spawnObjectTree(obj, parent, null, done);
|
||||
}
|
||||
|
||||
public function parseObject(sceneName: String, objectName: String, parent: Object, done: Object->Void) {
|
||||
Data.getSceneRaw(sceneName, function(format: TSceneFormat) {
|
||||
var o: TObj = getRawObjectByName(format, objectName);
|
||||
if (o == null) done(null);
|
||||
createObject(o, format, parent, null, done);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an object in scene data format ('TObj') based on its name.
|
||||
Returns 'null' if the object does not exist.
|
||||
@param format The raw scene data
|
||||
@param name The name of the object
|
||||
@return TObj
|
||||
**/
|
||||
public static function getRawObjectByName(format: TSceneFormat, name: String): TObj {
|
||||
return traverseObjs(format.objects, name);
|
||||
}
|
||||
|
||||
/**
|
||||
Searches the given 'TObj' array for an object with the given name and returns that object.
|
||||
@param children The array in which to search
|
||||
@param name The name of the object
|
||||
@return TObj
|
||||
**/
|
||||
static function traverseObjs(children: Array<TObj>, name: String): TObj {
|
||||
for (o in children) {
|
||||
if (o.name == name) return o;
|
||||
if (o.children != null) {
|
||||
var res = traverseObjs(o.children, name);
|
||||
if (res != null) return res;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function createObject(o: TObj, format: TSceneFormat, parent: Object, parentObject: TObj, done: Object->Void) {
|
||||
var sceneName = format.name;
|
||||
|
||||
if (o.type == "camera_object") {
|
||||
Data.getCamera(sceneName, o.data_ref, function(b: CameraData) {
|
||||
var object = addCameraObject(b, parent);
|
||||
returnObject(object, o, done);
|
||||
});
|
||||
}
|
||||
else if (o.type == "light_object") {
|
||||
Data.getLight(sceneName, o.data_ref, function(b: LightData) {
|
||||
var object = addLightObject(b, parent);
|
||||
returnObject(object, o, done);
|
||||
});
|
||||
}
|
||||
#if rp_probes
|
||||
else if (o.type == "probe_object") {
|
||||
Data.getProbe(sceneName, o.data_ref, function(b: ProbeData) {
|
||||
var object = addProbeObject(b, parent);
|
||||
returnObject(object, o, done);
|
||||
});
|
||||
}
|
||||
#end
|
||||
else if (o.type == "mesh_object") {
|
||||
if (o.material_refs == null || o.material_refs.length == 0) {
|
||||
createMeshObject(o, format, parent, parentObject, null, done);
|
||||
}
|
||||
else {
|
||||
// Materials
|
||||
var materials = new Vector<MaterialData>(o.material_refs.length);
|
||||
var materialsLoaded = 0;
|
||||
for (i in 0...o.material_refs.length) {
|
||||
var ref = o.material_refs[i];
|
||||
Data.getMaterial(sceneName, ref, function(mat: MaterialData) {
|
||||
materials[i] = mat;
|
||||
materialsLoaded++;
|
||||
if (materialsLoaded == o.material_refs.length) {
|
||||
createMeshObject(o, format, parent, parentObject, materials, done);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#if lnx_audio
|
||||
else if (o.type == "speaker_object") {
|
||||
var object = addSpeakerObject(Data.getSpeakerRawByName(format.speaker_datas, o.data_ref), parent);
|
||||
returnObject(object, o, done);
|
||||
}
|
||||
#end
|
||||
#if rp_decals
|
||||
else if (o.type == "decal_object") {
|
||||
if (o.material_refs != null && o.material_refs.length > 0) {
|
||||
Data.getMaterial(sceneName, o.material_refs[0], function(material: MaterialData) {
|
||||
var object = addDecalObject(material, parent);
|
||||
returnObject(object, o, done);
|
||||
});
|
||||
}
|
||||
else {
|
||||
var object = addDecalObject(null, parent);
|
||||
returnObject(object, o, done);
|
||||
}
|
||||
}
|
||||
#end
|
||||
else if (o.type == "object") {
|
||||
var object = addObject(parent);
|
||||
returnObject(object, o, function(ro: Object) {
|
||||
if (o.group_ref != null) { // Instantiate group objects
|
||||
spawnGroup(format, o.group_ref, ro, () -> done(ro), () -> done(ro) /* also call done when failed to ensure loading progress */);
|
||||
}
|
||||
else done(ro);
|
||||
});
|
||||
}
|
||||
else done(null);
|
||||
}
|
||||
|
||||
function spawnGroup(format: TSceneFormat, groupRef: String, groupOwner: Object, done: Void->Void, ?failed: Void->Void) {
|
||||
var spawned = 0;
|
||||
var object_refs = getGroupObjectRefs(groupRef, format);
|
||||
|
||||
if (object_refs == null) { // Group doesn't exist
|
||||
trace('Failed to spawn group "$groupRef", group doesn\'t exist');
|
||||
if (failed != null) failed();
|
||||
}
|
||||
else if (object_refs.length == 0) {
|
||||
done();
|
||||
}
|
||||
else {
|
||||
for (object_ref in object_refs) {
|
||||
// Spawn top-level collection objects and their children
|
||||
spawnObject(object_ref, groupOwner, function(spawnedObject: Object) {
|
||||
// Apply collection/group instance offset to all
|
||||
// top-level parents of that group
|
||||
if (!isObjectInGroup(groupRef, spawnedObject.parent, format)) {
|
||||
for (group in format.groups) {
|
||||
if (group.name == groupRef) {
|
||||
spawnedObject.transform.applyParent();
|
||||
spawnedObject.transform.translate(
|
||||
-group.instance_offset[0],
|
||||
-group.instance_offset[1],
|
||||
-group.instance_offset[2]
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (++spawned == object_refs.length) {
|
||||
groupOwner.transform.reset();
|
||||
done();
|
||||
}
|
||||
}, true, format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all object names of the given group.
|
||||
Returns `null` if the group does not exist.
|
||||
@param group_ref The name of the group
|
||||
@param format The raw scene data
|
||||
@return `Array<String>`
|
||||
**/
|
||||
function getGroupObjectRefs(group_ref: String, format: TSceneFormat): Array<String> {
|
||||
for (g in format.groups) if (g.name == group_ref) return g.object_refs;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all objects in scene data format (`TObj`) of the given group.
|
||||
If the group does not exist or is empty, an empty array is returned.
|
||||
@param groupRef The name of the group
|
||||
@param format The raw scene data
|
||||
@return `Array<TObj>`
|
||||
**/
|
||||
function getGroupObjectsRaw(groupRef: String, format: TSceneFormat): Array<TObj> {
|
||||
var objectRefs = getGroupObjectRefs(groupRef, format);
|
||||
var objects: Array<TObj> = new Array<TObj>();
|
||||
|
||||
if (objectRefs == null) return objects;
|
||||
|
||||
for (objRef in objectRefs) {
|
||||
var rawObj = getRawObjectByName(format, objRef);
|
||||
objects.push(rawObj);
|
||||
|
||||
var childRefs = getChildObjectsRaw(rawObj);
|
||||
objects = objects.concat(childRefs);
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all child objects of the given raw object in scene data format ('TObj').
|
||||
If the object has no children, an empty array is returned.
|
||||
@param rawObj The object
|
||||
@param recursive (Optional) If 'true', return also children of children and so on...
|
||||
@return `Array<TObj>`
|
||||
**/
|
||||
function getChildObjectsRaw(rawObj: TObj, ?recursive:Bool = true): Array<TObj> {
|
||||
var children = rawObj.children;
|
||||
if (children == null) return new Array<TObj>();
|
||||
children = children.copy();
|
||||
|
||||
if (recursive) {
|
||||
for (child in rawObj.children) {
|
||||
var childRefs = getChildObjectsRaw(child);
|
||||
children = children.concat(childRefs);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if an object is an element of the given group.
|
||||
@param groupRef The name of the group
|
||||
@param object The object
|
||||
@param format The raw scene data
|
||||
@return Bool
|
||||
**/
|
||||
function isObjectInGroup(groupRef: String, object: Object, format: TSceneFormat): Bool {
|
||||
for (obj in getGroupObjectsRaw(groupRef, format)) {
|
||||
if (obj.name == object.name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if lnx_stream
|
||||
function childCount(o: TObj): Int {
|
||||
var i = o.children.length;
|
||||
if (o.children != null) for (c in o.children) i += childCount(c);
|
||||
return i;
|
||||
}
|
||||
|
||||
function streamMeshObject(object_file: String, data_ref: String, sceneName: String, armature: Armature, materials: Vector<MaterialData>, parent: Object, parentObj: TObj, o: TObj, done: Object->Void) {
|
||||
sceneStream.add(object_file, data_ref, sceneName, armature, materials, parent, parentObj, o);
|
||||
returnObject(null, null, done);
|
||||
}
|
||||
#end
|
||||
|
||||
function isLod(raw: TObj): Bool {
|
||||
return raw != null && raw.lods != null && raw.lods.length > 0;
|
||||
}
|
||||
|
||||
function createMeshObject(o: TObj, format: TSceneFormat, parent: Object, parentObject: TObj, materials: Vector<MaterialData>, done: Object->Void) {
|
||||
// Mesh reference
|
||||
var ref = o.data_ref.split("/");
|
||||
var object_file = "";
|
||||
var data_ref = "";
|
||||
var sceneName = format.name;
|
||||
if (ref.length == 2) { // File reference
|
||||
object_file = ref[0];
|
||||
data_ref = ref[1];
|
||||
}
|
||||
else { // Local mesh data
|
||||
object_file = sceneName;
|
||||
data_ref = o.data_ref;
|
||||
}
|
||||
|
||||
// Bone objects are stored in armature parent
|
||||
#if lnx_skin
|
||||
if (parentObject != null && parentObject.bone_actions != null) {
|
||||
var bactions: Array<TSceneFormat> = [];
|
||||
for (ref in parentObject.bone_actions) {
|
||||
Data.getSceneRaw(ref, function(action: TSceneFormat) {
|
||||
bactions.push(action);
|
||||
if (bactions.length == parentObject.bone_actions.length) {
|
||||
var armature: Armature = null;
|
||||
// Check if armature exists
|
||||
for (a in armatures) {
|
||||
if (a.uid == parent.uid) {
|
||||
armature = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Create new one
|
||||
if (armature == null) {
|
||||
armature = new Armature(parent.uid, parent.name, bactions);
|
||||
armatures.push(armature);
|
||||
}
|
||||
#if lnx_stream
|
||||
streamMeshObject(
|
||||
#else
|
||||
returnMeshObject(
|
||||
#end
|
||||
object_file, data_ref, sceneName, armature, materials, parent, parentObject, o, done);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else { #end // lnx_skin
|
||||
#if lnx_stream
|
||||
streamMeshObject(
|
||||
#else
|
||||
returnMeshObject(
|
||||
#end
|
||||
object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
|
||||
#if lnx_skin
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public function returnMeshObject(object_file: String, data_ref: String, sceneName: String, armature: #if lnx_skin Armature #else Null<Int> #end, materials: Vector<MaterialData>, parent: Object, parentObject: TObj, o: TObj, done: Object->Void) {
|
||||
Data.getMesh(object_file, data_ref, function(mesh: MeshData) {
|
||||
var object = addMeshObject(mesh, materials, parent);
|
||||
#if lnx_batch
|
||||
var lod = isLod(o) || (parent != null && isLod(parent.raw));
|
||||
object.batch(lod);
|
||||
#end
|
||||
|
||||
// Attach particle systems
|
||||
#if lnx_particles
|
||||
if (o.particle_refs != null) {
|
||||
for (ref in o.particle_refs) cast(object, MeshObject).setupParticleSystem(sceneName, ref);
|
||||
}
|
||||
#end
|
||||
// Attach tilesheet
|
||||
if (o.tilesheet_ref != null) {
|
||||
cast(object, MeshObject).setupTilesheet(sceneName, o.tilesheet_ref, o.tilesheet_action_ref);
|
||||
}
|
||||
returnObject(object, o, done);
|
||||
});
|
||||
}
|
||||
|
||||
function returnObject(object: Object, o: TObj, done: Object->Void) {
|
||||
// Load object actions
|
||||
if (object != null && o.object_actions != null) {
|
||||
var oactions: Array<TSceneFormat> = [];
|
||||
while (oactions.length < o.object_actions.length) oactions.push(null);
|
||||
var actionsLoaded = 0;
|
||||
for (i in 0...o.object_actions.length) {
|
||||
var ref = o.object_actions[i];
|
||||
if (ref == "null") { // No startup action set
|
||||
actionsLoaded++;
|
||||
continue;
|
||||
}
|
||||
Data.getSceneRaw(ref, function(action: TSceneFormat) {
|
||||
oactions[i] = action;
|
||||
actionsLoaded++;
|
||||
if (actionsLoaded == o.object_actions.length) {
|
||||
returnObjectLoaded(object, o, oactions, done);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else returnObjectLoaded(object, o, null, done);
|
||||
}
|
||||
|
||||
function returnObjectLoaded(object: Object, o: TObj, oactions: Array<TSceneFormat>, done: Object->Void) {
|
||||
if (object != null) {
|
||||
object.raw = o;
|
||||
object.name = o.name;
|
||||
if (o.visible != null) object.visible = o.visible;
|
||||
if (o.visible_mesh != null) object.visibleMesh = o.visible_mesh;
|
||||
if (o.visible_shadow != null) object.visibleShadow = o.visible_shadow;
|
||||
createConstraints(o.constraints, object);
|
||||
generateTransform(o, object.transform);
|
||||
object.setupAnimation(oactions);
|
||||
#if lnx_morph_target
|
||||
object.setupMorphTargets();
|
||||
#end
|
||||
if (o.properties != null) {
|
||||
object.properties = new Map();
|
||||
for (p in o.properties) object.properties.set(p.name, p.value);
|
||||
}
|
||||
|
||||
if (o.vertex_groups != null) {
|
||||
object.vertex_groups = new Map();
|
||||
for (p in o.vertex_groups){
|
||||
var verts = [];
|
||||
for(i in 0...Std.int(p.value.length/3)){
|
||||
var x = Std.parseFloat(p.value[i*3]);
|
||||
var y = Std.parseFloat(p.value[i*3+1]);
|
||||
var z = Std.parseFloat(p.value[i*3+2]);
|
||||
verts.push(new iron.math.Vec4(x, y, z, 1));
|
||||
}
|
||||
object.vertex_groups.set(p.name, verts);
|
||||
}
|
||||
}
|
||||
|
||||
// If the scene is still initializing, traits will be created later
|
||||
// to ensure that object references for trait properties are valid
|
||||
if (!active.initializing) createTraits(o.traits, object);
|
||||
}
|
||||
done(object);
|
||||
}
|
||||
|
||||
static function generateTransform(object: TObj, transform: Transform) {
|
||||
transform.world = object.transform != null ? iron.math.Mat4.fromFloat32Array(object.transform.values) : iron.math.Mat4.identity();
|
||||
transform.world.decompose(transform.loc, transform.rot, transform.scale);
|
||||
// Whether to apply parent matrix
|
||||
if (object.local_only != null) transform.localOnly = object.local_only;
|
||||
if (transform.object.parent != null) transform.update();
|
||||
}
|
||||
|
||||
static function createTraits(traits: Array<TTrait>, object: Object) {
|
||||
if (traits == null) return;
|
||||
for (t in traits) {
|
||||
if (t.type == "Script") {
|
||||
// Assign arguments if any
|
||||
var args: Array<Dynamic> = [];
|
||||
if (t.parameters != null) {
|
||||
for (param in t.parameters) {
|
||||
args.push(parseArg(param));
|
||||
}
|
||||
}
|
||||
var traitInst = createTraitClassInstance(t.class_name, args);
|
||||
if (traitInst == null) {
|
||||
trace("Error: Trait '" + t.class_name + "' referenced in object '" + object.name + "' not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set trait properties
|
||||
if (t.props != null) {
|
||||
for (i in 0...Std.int(t.props.length / 3)) {
|
||||
var pname: String = t.props[i * 3];
|
||||
var ptype: String = t.props[i * 3 + 1];
|
||||
var pval: Dynamic = t.props[i * 3 + 2];
|
||||
|
||||
if (StringTools.endsWith(ptype, "Object") && pval != "") {
|
||||
Reflect.setProperty(traitInst, pname, Scene.active.getChild(pval));
|
||||
}
|
||||
else {
|
||||
switch (ptype) {
|
||||
case "Vec2":
|
||||
final pVec: kha.arrays.Float32Array = cast pval;
|
||||
Reflect.setProperty(traitInst, pname, new iron.math.Vec2(pVec[0], pVec[1]));
|
||||
case "Vec3":
|
||||
final pVec: kha.arrays.Float32Array = cast pval;
|
||||
Reflect.setProperty(traitInst, pname, new iron.math.Vec3(pVec[0], pVec[1], pVec[2]));
|
||||
case "Vec4":
|
||||
final pVec: kha.arrays.Float32Array = cast pval;
|
||||
Reflect.setProperty(traitInst, pname, new iron.math.Vec4(pVec[0], pVec[1], pVec[2], pVec[3]));
|
||||
default:
|
||||
Reflect.setProperty(traitInst, pname, pval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
object.addTrait(traitInst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function parseArg(str: String): Dynamic {
|
||||
if (str == "true") return true;
|
||||
else if (str == "false") return false;
|
||||
else if (str == "null") return null;
|
||||
else if (str.charAt(0) == "'") return str.replace("'", "");
|
||||
else if (str.charAt(0) == '"') return str.replace('"', "");
|
||||
else if (str.charAt(0) == "[") { // Array
|
||||
// Remove [] and recursively parse into array, then append into parent
|
||||
str = str.replace("[", "");
|
||||
str = str.replace("]", "");
|
||||
str = str.replace(" ", "");
|
||||
var ar: Dynamic = [];
|
||||
var vals = str.split(",");
|
||||
for (v in vals) ar.push(parseArg(v));
|
||||
return ar;
|
||||
}
|
||||
else if (str.charAt(0) == '{') { // Typedef or anonymous structure
|
||||
return haxe.Json.parse(str);
|
||||
}
|
||||
else {
|
||||
var f = Std.parseFloat(str);
|
||||
var i = Std.parseInt(str);
|
||||
return f == i ? i : f;
|
||||
}
|
||||
}
|
||||
|
||||
static function createConstraints(constraints: Array<TConstraint>, object: Object) {
|
||||
if (constraints == null) return;
|
||||
object.constraints = [];
|
||||
for (c in constraints) {
|
||||
var constr = new Constraint(c);
|
||||
object.constraints.push(constr);
|
||||
}
|
||||
}
|
||||
|
||||
static function createTraitClassInstance(traitName: String, args: Array<Dynamic>): Dynamic {
|
||||
var cname = Type.resolveClass(traitName);
|
||||
if (cname == null) return null;
|
||||
return Type.createInstance(cname, args);
|
||||
}
|
||||
|
||||
function loadEmbeddedData(datas: Array<String>, done: Void->Void) {
|
||||
if (datas == null) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
var loaded = 0;
|
||||
for (file in datas) {
|
||||
embedData(file, function() {
|
||||
loaded++;
|
||||
if (loaded == datas.length) done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function embedData(file: String, done: Void->Void) {
|
||||
if (file.endsWith(".raw")) {
|
||||
Data.getBlob(file, function(blob: kha.Blob) {
|
||||
// Raw 3D texture bytes
|
||||
var b = blob.toBytes();
|
||||
var w = Std.int(Math.pow(b.length, 1 / 3)) + 1;
|
||||
var image = kha.Image.fromBytes3D(b, w, w, w, TextureFormat.L8);
|
||||
embedded.set(file, image);
|
||||
done();
|
||||
});
|
||||
}
|
||||
else {
|
||||
Data.getImage(file, function(image: kha.Image) {
|
||||
embedded.set(file, image);
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Hooks
|
||||
public function notifyOnInit(f: Void->Void) {
|
||||
if (ready) f(); // Scene already running
|
||||
else traitInits.push(f);
|
||||
}
|
||||
|
||||
public function removeInit(f: Void->Void) {
|
||||
traitInits.remove(f);
|
||||
}
|
||||
|
||||
public function notifyOnRemove(f: Void->Void) {
|
||||
traitRemoves.push(f);
|
||||
}
|
||||
}
|
||||
123
leenkx/Sources/iron/Trait.hx
Normal file
123
leenkx/Sources/iron/Trait.hx
Normal file
@ -0,0 +1,123 @@
|
||||
package iron;
|
||||
|
||||
import iron.object.Object;
|
||||
|
||||
class Trait {
|
||||
|
||||
public var name: String = "";
|
||||
|
||||
/**
|
||||
Object this trait belongs to.
|
||||
**/
|
||||
public var object: Object;
|
||||
|
||||
var _add: Array<Void->Void> = null;
|
||||
var _init: Array<Void->Void> = null;
|
||||
var _remove: Array<Void->Void> = null;
|
||||
var _update: Array<Void->Void> = null;
|
||||
var _lateUpdate: Array<Void->Void> = null;
|
||||
var _render: Array<kha.graphics4.Graphics->Void> = null;
|
||||
var _render2D: Array<kha.graphics2.Graphics->Void> = null;
|
||||
|
||||
public function new() {}
|
||||
|
||||
/**
|
||||
Remove the trait from the object.
|
||||
**/
|
||||
public function remove() {
|
||||
object.removeTrait(this);
|
||||
}
|
||||
|
||||
/**
|
||||
Trait is added to an object.
|
||||
**/
|
||||
public function notifyOnAdd(f: Void->Void) {
|
||||
if (_add == null) _add = [];
|
||||
_add.push(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Object which this trait belongs to is added to scene.
|
||||
**/
|
||||
public function notifyOnInit(f: Void->Void) {
|
||||
if (_init == null) _init = [];
|
||||
_init.push(f);
|
||||
App.notifyOnInit(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Object which this trait belongs to is removed from scene.
|
||||
**/
|
||||
public function notifyOnRemove(f: Void->Void) {
|
||||
if (_remove == null) _remove = [];
|
||||
_remove.push(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Add game logic handler.
|
||||
**/
|
||||
public function notifyOnUpdate(f: Void->Void) {
|
||||
if (_update == null) _update = [];
|
||||
_update.push(f);
|
||||
App.notifyOnUpdate(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove game logic handler.
|
||||
**/
|
||||
public function removeUpdate(f: Void->Void) {
|
||||
_update.remove(f);
|
||||
App.removeUpdate(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Add late game logic handler.
|
||||
**/
|
||||
public function notifyOnLateUpdate(f: Void->Void) {
|
||||
if (_lateUpdate == null) _lateUpdate = [];
|
||||
_lateUpdate.push(f);
|
||||
App.notifyOnLateUpdate(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove late game logic handler.
|
||||
**/
|
||||
public function removeLateUpdate(f: Void->Void) {
|
||||
_lateUpdate.remove(f);
|
||||
App.removeLateUpdate(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Add render handler.
|
||||
**/
|
||||
public function notifyOnRender(f: kha.graphics4.Graphics->Void) {
|
||||
if (_render == null) _render = [];
|
||||
_render.push(f);
|
||||
App.notifyOnRender(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove render handler.
|
||||
**/
|
||||
public function removeRender(f: kha.graphics4.Graphics->Void) {
|
||||
_render.remove(f);
|
||||
App.removeRender(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Add 2D render handler.
|
||||
**/
|
||||
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
if (_render2D == null) _render2D = [];
|
||||
_render2D.push(f);
|
||||
App.notifyOnRender2D(f);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove 2D render handler.
|
||||
**/
|
||||
public function removeRender2D(f: kha.graphics2.Graphics->Void) {
|
||||
_render2D.remove(f);
|
||||
App.removeRender2D(f);
|
||||
}
|
||||
}
|
||||
73
leenkx/Sources/iron/data/Armature.hx
Normal file
73
leenkx/Sources/iron/data/Armature.hx
Normal file
@ -0,0 +1,73 @@
|
||||
package iron.data;
|
||||
|
||||
#if lnx_skin
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Mat4;
|
||||
|
||||
class Armature {
|
||||
public var uid: Int;
|
||||
public var name: String;
|
||||
public var actions: Array<TAction> = [];
|
||||
var matsReady = false;
|
||||
|
||||
public function new(uid: Int, name: String, actions: Array<TSceneFormat>) {
|
||||
this.uid = uid;
|
||||
this.name = name;
|
||||
|
||||
for (a in actions) {
|
||||
for (o in a.objects) setParents(o);
|
||||
var bones: Array<TObj> = [];
|
||||
traverseBones(a.objects, function(object: TObj) { bones.push(object); });
|
||||
this.actions.push({ name: a.name, bones: bones, mats: null });
|
||||
}
|
||||
}
|
||||
|
||||
public function initMats() {
|
||||
if (matsReady) return;
|
||||
matsReady = true;
|
||||
|
||||
for (a in actions) {
|
||||
if (a.mats != null) continue;
|
||||
a.mats = [];
|
||||
for (b in a.bones) {
|
||||
a.mats.push(Mat4.fromFloat32Array(b.transform.values));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAction(name: String): TAction {
|
||||
for (a in actions) if (a.name == name) return a;
|
||||
return null;
|
||||
}
|
||||
|
||||
static function setParents(object: TObj) {
|
||||
if (object.children == null) return;
|
||||
for (o in object.children) {
|
||||
o.parent = object;
|
||||
setParents(o);
|
||||
}
|
||||
}
|
||||
|
||||
static function traverseBones(objects: Array<TObj>, callback: TObj->Void) {
|
||||
for (i in 0...objects.length) {
|
||||
traverseBonesStep(objects[i], callback);
|
||||
}
|
||||
}
|
||||
|
||||
static function traverseBonesStep(object: TObj, callback: TObj->Void) {
|
||||
if (object.type == "bone_object") callback(object);
|
||||
if (object.children == null) return;
|
||||
for (i in 0...object.children.length) {
|
||||
traverseBonesStep(object.children[i], callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef TAction = {
|
||||
var name: String;
|
||||
var bones: Array<TObj>;
|
||||
var mats: Array<Mat4>;
|
||||
}
|
||||
|
||||
#end
|
||||
26
leenkx/Sources/iron/data/CameraData.hx
Normal file
26
leenkx/Sources/iron/data/CameraData.hx
Normal file
@ -0,0 +1,26 @@
|
||||
package iron.data;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class CameraData {
|
||||
|
||||
public var name: String;
|
||||
public var raw: TCameraData;
|
||||
|
||||
public function new(raw: TCameraData, done: CameraData->Void) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
done(this);
|
||||
}
|
||||
|
||||
public static function parse(name: String, id: String, done: CameraData->Void) {
|
||||
Data.getSceneRaw(name, function(format: TSceneFormat) {
|
||||
var raw: TCameraData = Data.getCameraRawByName(format.camera_datas, id);
|
||||
if (raw == null) {
|
||||
trace('Camera data "$id" not found!');
|
||||
done(null);
|
||||
}
|
||||
new CameraData(raw, done);
|
||||
});
|
||||
}
|
||||
}
|
||||
131
leenkx/Sources/iron/data/ConstData.hx
Normal file
131
leenkx/Sources/iron/data/ConstData.hx
Normal file
File diff suppressed because one or more lines are too long
624
leenkx/Sources/iron/data/Data.hx
Normal file
624
leenkx/Sources/iron/data/Data.hx
Normal file
@ -0,0 +1,624 @@
|
||||
package iron.data;
|
||||
|
||||
import haxe.Json;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.LnxPack;
|
||||
import iron.system.Lz4;
|
||||
using StringTools;
|
||||
|
||||
// Global data list and asynchronous data loading
|
||||
class Data {
|
||||
|
||||
public static var cachedSceneRaws: Map<String, TSceneFormat> = new Map();
|
||||
public static var cachedMeshes: Map<String, MeshData> = new Map();
|
||||
public static var cachedLights: Map<String, LightData> = new Map();
|
||||
public static var cachedCameras: Map<String, CameraData> = new Map();
|
||||
public static var cachedMaterials: Map<String, MaterialData> = new Map();
|
||||
public static var cachedParticles: Map<String, ParticleData> = new Map();
|
||||
public static var cachedWorlds: Map<String, WorldData> = new Map();
|
||||
public static var cachedShaders: Map<String, ShaderData> = new Map();
|
||||
#if rp_probes
|
||||
public static var cachedProbes: Map<String, ProbeData> = new Map();
|
||||
#end
|
||||
|
||||
public static var cachedBlobs: Map<String, kha.Blob> = new Map();
|
||||
public static var cachedImages: Map<String, kha.Image> = new Map();
|
||||
#if lnx_audio
|
||||
public static var cachedSounds: Map<String, kha.Sound> = new Map();
|
||||
#end
|
||||
public static var cachedVideos: Map<String, kha.Video> = new Map();
|
||||
public static var cachedFonts: Map<String, kha.Font> = new Map();
|
||||
|
||||
public static var assetsLoaded = 0;
|
||||
static var loadingMeshes: Map<String, Array<MeshData->Void>> = new Map();
|
||||
static var loadingLights: Map<String, Array<LightData->Void>> = new Map();
|
||||
static var loadingCameras: Map<String, Array<CameraData->Void>> = new Map();
|
||||
static var loadingMaterials: Map<String, Array<MaterialData->Void>> = new Map();
|
||||
static var loadingParticles: Map<String, Array<ParticleData->Void>> = new Map();
|
||||
static var loadingWorlds: Map<String, Array<WorldData->Void>> = new Map();
|
||||
static var loadingShaders: Map<String, Array<ShaderData->Void>> = new Map();
|
||||
static var loadingSceneRaws: Map<String, Array<TSceneFormat->Void>> = new Map();
|
||||
#if rp_probes
|
||||
static var loadingProbes: Map<String, Array<ProbeData->Void>> = new Map();
|
||||
#end
|
||||
static var loadingBlobs: Map<String, Array<kha.Blob->Void>> = new Map();
|
||||
static var loadingImages: Map<String, Array<kha.Image->Void>> = new Map();
|
||||
#if lnx_audio
|
||||
static var loadingSounds: Map<String, Array<kha.Sound->Void>> = new Map();
|
||||
#end
|
||||
static var loadingVideos: Map<String, Array<kha.Video->Void>> = new Map();
|
||||
static var loadingFonts: Map<String, Array<kha.Font->Void>> = new Map();
|
||||
|
||||
#if krom_windows
|
||||
public static inline var sep = "\\";
|
||||
#else
|
||||
public static inline var sep = "/";
|
||||
#end
|
||||
|
||||
#if lnx_data_dir
|
||||
#if krom_android
|
||||
public static var dataPath = "data" + sep;
|
||||
#else
|
||||
public static var dataPath = "." + sep + "data" + sep;
|
||||
#end
|
||||
#else
|
||||
public static var dataPath = "";
|
||||
#end
|
||||
|
||||
public function new() {}
|
||||
|
||||
public static function deleteAll() {
|
||||
for (c in cachedMeshes) c.delete();
|
||||
cachedMeshes = new Map();
|
||||
for (c in cachedShaders) c.delete();
|
||||
cachedShaders = new Map();
|
||||
cachedSceneRaws = new Map();
|
||||
cachedLights = new Map();
|
||||
cachedCameras = new Map();
|
||||
cachedMaterials = new Map();
|
||||
cachedParticles = new Map();
|
||||
cachedWorlds = new Map();
|
||||
if (RenderPath.active != null) RenderPath.active.unload();
|
||||
|
||||
for (c in cachedBlobs) c.unload();
|
||||
cachedBlobs = new Map();
|
||||
for (c in cachedImages) c.unload();
|
||||
cachedImages = new Map();
|
||||
#if lnx_audio
|
||||
for (c in cachedSounds) c.unload();
|
||||
cachedSounds = new Map();
|
||||
#end
|
||||
for (c in cachedVideos) c.unload();
|
||||
cachedVideos = new Map();
|
||||
for (c in cachedFonts) c.unload();
|
||||
cachedFonts = new Map();
|
||||
}
|
||||
|
||||
public static function getMesh(file: String, name: String, done: MeshData->Void) {
|
||||
var handle = file + name;
|
||||
var cached = cachedMeshes.get(handle);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingMeshes.get(handle);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingMeshes.set(handle, [done]);
|
||||
|
||||
MeshData.parse(file, name, function(b: MeshData) {
|
||||
cachedMeshes.set(handle, b);
|
||||
b.handle = handle;
|
||||
for (f in loadingMeshes.get(handle)) f(b);
|
||||
loadingMeshes.remove(handle);
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteMesh(handle: String) {
|
||||
// Remove cached mesh
|
||||
var mesh = cachedMeshes.get(handle);
|
||||
if (mesh == null) return;
|
||||
mesh.delete();
|
||||
cachedMeshes.remove(handle);
|
||||
}
|
||||
|
||||
public static function getLight(file: String, name: String, done: LightData->Void) {
|
||||
var handle = file + name;
|
||||
var cached = cachedLights.get(handle);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingLights.get(handle);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingLights.set(handle, [done]);
|
||||
|
||||
LightData.parse(file, name, function(b: LightData) {
|
||||
cachedLights.set(handle, b);
|
||||
for (f in loadingLights.get(handle)) f(b);
|
||||
loadingLights.remove(handle);
|
||||
});
|
||||
}
|
||||
|
||||
#if rp_probes
|
||||
public static function getProbe(file: String, name: String, done: ProbeData->Void) {
|
||||
var handle = file + name;
|
||||
var cached = cachedProbes.get(handle);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingProbes.get(handle);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingProbes.set(handle, [done]);
|
||||
|
||||
ProbeData.parse(file, name, function(b: ProbeData) {
|
||||
cachedProbes.set(handle, b);
|
||||
for (f in loadingProbes.get(handle)) f(b);
|
||||
loadingProbes.remove(handle);
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
public static function getCamera(file: String, name: String, done: CameraData->Void) {
|
||||
var handle = file + name;
|
||||
var cached = cachedCameras.get(handle);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingCameras.get(handle);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingCameras.set(handle, [done]);
|
||||
|
||||
CameraData.parse(file, name, function(b: CameraData) {
|
||||
cachedCameras.set(handle, b);
|
||||
for (f in loadingCameras.get(handle)) f(b);
|
||||
loadingCameras.remove(handle);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getMaterial(file: String, name: String, done: MaterialData->Void) {
|
||||
var handle = file + name;
|
||||
var cached = cachedMaterials.get(handle);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingMaterials.get(handle);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingMaterials.set(handle, [done]);
|
||||
|
||||
MaterialData.parse(file, name, function(b: MaterialData) {
|
||||
cachedMaterials.set(handle, b);
|
||||
for (f in loadingMaterials.get(handle)) f(b);
|
||||
loadingMaterials.remove(handle);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getParticle(file: String, name: String, done: ParticleData->Void) {
|
||||
var handle = file + name;
|
||||
var cached = cachedParticles.get(handle);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingParticles.get(handle);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingParticles.set(handle, [done]);
|
||||
|
||||
ParticleData.parse(file, name, function(b: ParticleData) {
|
||||
cachedParticles.set(handle, b);
|
||||
for (f in loadingParticles.get(handle)) f(b);
|
||||
loadingParticles.remove(handle);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getWorld(file: String, name: String, done: WorldData->Void) {
|
||||
if (name == null) { // No world defined in scene
|
||||
done(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var handle = file + name;
|
||||
var cached = cachedWorlds.get(handle);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingWorlds.get(handle);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingWorlds.set(handle, [done]);
|
||||
|
||||
WorldData.parse(file, name, function(b: WorldData) {
|
||||
cachedWorlds.set(handle, b);
|
||||
for (f in loadingWorlds.get(handle)) f(b);
|
||||
loadingWorlds.remove(handle);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getShader(file: String, name: String, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
||||
// Only one context override per shader data for now
|
||||
var cacheName = name;
|
||||
if (overrideContext != null) cacheName += "2";
|
||||
var cached = cachedShaders.get(cacheName); // Shader must have unique name
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingShaders.get(cacheName);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingShaders.set(cacheName, [done]);
|
||||
|
||||
ShaderData.parse(file, name, function(b: ShaderData) {
|
||||
cachedShaders.set(cacheName, b);
|
||||
for (f in loadingShaders.get(cacheName)) f(b);
|
||||
loadingShaders.remove(cacheName);
|
||||
}, overrideContext);
|
||||
}
|
||||
|
||||
public static function getSceneRaw(file: String, done: TSceneFormat->Void) {
|
||||
var cached = cachedSceneRaws.get(file);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingSceneRaws.get(file);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingSceneRaws.set(file, [done]);
|
||||
|
||||
// If no extension specified, set to .arm
|
||||
var compressed = file.endsWith(".lz4");
|
||||
var isJson = file.endsWith(".json");
|
||||
var ext = (compressed || isJson || file.endsWith(".lnx")) ? "" : ".lnx";
|
||||
|
||||
getBlob(file + ext, function(b: kha.Blob) {
|
||||
var parsed: TSceneFormat = null;
|
||||
|
||||
#if lnx_compress
|
||||
if (compressed) {
|
||||
var bytes = b.toBytes();
|
||||
|
||||
// First 8 bytes contain data size for decoding
|
||||
var packedSize = haxe.Int64.toInt(bytes.getInt64(0));
|
||||
|
||||
parsed = LnxPack.decode(Lz4.decode(bytes.sub(8, bytes.length - 8), packedSize));
|
||||
}
|
||||
|
||||
else #end if (isJson) {
|
||||
var s = b.toString();
|
||||
parsed = s.charAt(0) == "{" ? Json.parse(s) : LnxPack.decode(b.toBytes());
|
||||
}
|
||||
else {
|
||||
parsed = LnxPack.decode(b.toBytes());
|
||||
}
|
||||
|
||||
returnSceneRaw(file, parsed);
|
||||
});
|
||||
}
|
||||
|
||||
static function returnSceneRaw(file: String, parsed: TSceneFormat) {
|
||||
cachedSceneRaws.set(file, parsed);
|
||||
for (f in loadingSceneRaws.get(file)) f(parsed);
|
||||
loadingSceneRaws.remove(file);
|
||||
}
|
||||
|
||||
public static function getMeshRawByName(datas: Array<TMeshData>, name: String): TMeshData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getLightRawByName(datas: Array<TLightData>, name: String): TLightData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
#if rp_probes
|
||||
public static function getProbeRawByName(datas: Array<TProbeData>, name: String): TProbeData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
public static function getCameraRawByName(datas: Array<TCameraData>, name: String): TCameraData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getMaterialRawByName(datas: Array<TMaterialData>, name: String): TMaterialData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getParticleRawByName(datas: Array<TParticleData>, name: String): TParticleData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getWorldRawByName(datas: Array<TWorldData>, name: String): TWorldData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getShaderRawByName(datas: Array<TShaderData>, name: String): TShaderData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
|
||||
#if lnx_audio
|
||||
public static function getSpeakerRawByName(datas: Array<TSpeakerData>, name: String): TSpeakerData {
|
||||
if (name == "") return datas[0];
|
||||
for (dat in datas) if (dat.name == name) return dat;
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
// Raw assets
|
||||
public static function getBlob(file: String, done: kha.Blob->Void) {
|
||||
var cached = cachedBlobs.get(file); // Is already cached
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingBlobs.get(file); // Is already being loaded
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingBlobs.set(file, [done]); // Start loading
|
||||
|
||||
kha.Assets.loadBlobFromPath(resolvePath(file), function(b: kha.Blob) {
|
||||
cachedBlobs.set(file, b);
|
||||
for (f in loadingBlobs.get(file)) f(b);
|
||||
loadingBlobs.remove(file);
|
||||
assetsLoaded++;
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteBlob(handle: String) {
|
||||
var blob = cachedBlobs.get(handle);
|
||||
if (blob == null) return;
|
||||
blob.unload();
|
||||
cachedBlobs.remove(handle);
|
||||
}
|
||||
|
||||
public static function getImage(file: String, done: kha.Image->Void, readable = false, format = "RGBA32") {
|
||||
#if (cpp || hl || lnx_use_k_images)
|
||||
if (!file.endsWith(".k")) {
|
||||
file = file.substring(0, file.length - 4) + ".k";
|
||||
}
|
||||
#end
|
||||
|
||||
var cached = cachedImages.get(file);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingImages.get(file);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingImages.set(file, [done]);
|
||||
|
||||
#if lnx_image_embed
|
||||
var imageBlob = cachedBlobs.get(file);
|
||||
if (imageBlob != null) {
|
||||
kha.Image.fromEncodedBytes(imageBlob.bytes, ".k", function(b: kha.Image) {
|
||||
cachedImages.set(file, b);
|
||||
for (f in loadingImages.get(file)) f(b);
|
||||
loadingImages.remove(file);
|
||||
assetsLoaded++;
|
||||
}, null, readable);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
kha.Assets.loadImageFromPath(resolvePath(file), readable, function(b: kha.Image) {
|
||||
cachedImages.set(file, b);
|
||||
for (f in loadingImages.get(file)) f(b);
|
||||
loadingImages.remove(file);
|
||||
assetsLoaded++;
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteImage(handle: String) {
|
||||
var image = cachedImages.get(handle);
|
||||
if (image == null) return;
|
||||
image.unload();
|
||||
cachedImages.remove(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
Load sound file from disk into ram.
|
||||
@param file A String matching the file name of the sound file on disk.
|
||||
@param done Completion handler function to do something after the sound is loaded.
|
||||
*/
|
||||
#if lnx_audio
|
||||
public static function getSound(file: String, done: kha.Sound->Void) {
|
||||
#if lnx_soundcompress
|
||||
if (file.endsWith(".wav")) file = file.substring(0, file.length - 4) + ".ogg";
|
||||
#end
|
||||
|
||||
var cached = cachedSounds.get(file);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingSounds.get(file);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingSounds.set(file, [done]);
|
||||
|
||||
kha.Assets.loadSoundFromPath(resolvePath(file), function(b: kha.Sound) {
|
||||
#if lnx_soundcompress
|
||||
b.uncompress(function () {
|
||||
#end
|
||||
cachedSounds.set(file, b);
|
||||
for (f in loadingSounds.get(file)) f(b);
|
||||
loadingSounds.remove(file);
|
||||
assetsLoaded++;
|
||||
#if lnx_soundcompress
|
||||
});
|
||||
#end
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteSound(handle: String) {
|
||||
var sound = cachedSounds.get(handle);
|
||||
if (sound == null) return;
|
||||
sound.unload();
|
||||
cachedSounds.remove(handle);
|
||||
}
|
||||
#end // lnx_audio
|
||||
|
||||
public static function getVideo(file: String, done: kha.Video->Void) {
|
||||
#if (cpp || hl)
|
||||
file = file.substring(0, file.length - 4) + ".avi";
|
||||
#elseif krom
|
||||
file = file.substring(0, file.length - 4) + ".webm";
|
||||
#end
|
||||
var cached = cachedVideos.get(file);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingVideos.get(file);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingVideos.set(file, [done]);
|
||||
|
||||
kha.Assets.loadVideoFromPath(resolvePath(file), function(b: kha.Video) {
|
||||
cachedVideos.set(file, b);
|
||||
for (f in loadingVideos.get(file)) f(b);
|
||||
loadingVideos.remove(file);
|
||||
assetsLoaded++;
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteVideo(handle: String) {
|
||||
var video = cachedVideos.get(handle);
|
||||
if (video == null) return;
|
||||
video.unload();
|
||||
cachedVideos.remove(handle);
|
||||
}
|
||||
|
||||
public static function getFont(file: String, done: kha.Font->Void) {
|
||||
var cached = cachedFonts.get(file);
|
||||
if (cached != null) {
|
||||
done(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
var loading = loadingFonts.get(file);
|
||||
if (loading != null) {
|
||||
loading.push(done);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingFonts.set(file, [done]);
|
||||
|
||||
kha.Assets.loadFontFromPath(resolvePath(file), function(b: kha.Font) {
|
||||
cachedFonts.set(file, b);
|
||||
for (f in loadingFonts.get(file)) f(b);
|
||||
loadingFonts.remove(file);
|
||||
assetsLoaded++;
|
||||
});
|
||||
}
|
||||
|
||||
public static function deleteFont(handle: String) {
|
||||
var font = cachedFonts.get(handle);
|
||||
if (font == null) return;
|
||||
font.unload();
|
||||
cachedFonts.remove(handle);
|
||||
}
|
||||
|
||||
public static function isAbsolute(file: String): Bool {
|
||||
return file.charAt(0) == "/" || file.charAt(1) == ":" || file.charAt(4) == ":" || (file.charAt(0) == "\\" && file.charAt(1) == "\\");
|
||||
}
|
||||
|
||||
static inline function isUp(file: String): Bool {
|
||||
return file.charAt(0) == "." && file.charAt(1) == ".";
|
||||
}
|
||||
|
||||
/**
|
||||
Extract filename from path.
|
||||
*/
|
||||
static inline function baseName(path: String): String {
|
||||
var slash = path.lastIndexOf(sep);
|
||||
return slash >= 0 ? path.substr(slash + 1) : path;
|
||||
}
|
||||
|
||||
static inline function resolvePath(file: String): String {
|
||||
if (isAbsolute(file) || isUp(file)) return file;
|
||||
#if lnx_data_dir
|
||||
return dataPath + file;
|
||||
#else
|
||||
return baseName(file);
|
||||
#end
|
||||
}
|
||||
}
|
||||
390
leenkx/Sources/iron/data/Geometry.hx
Normal file
390
leenkx/Sources/iron/data/Geometry.hx
Normal file
@ -0,0 +1,390 @@
|
||||
package iron.data;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import kha.graphics4.VertexBuffer;
|
||||
import kha.graphics4.IndexBuffer;
|
||||
import kha.graphics4.Usage;
|
||||
import kha.graphics4.VertexStructure;
|
||||
import kha.graphics4.VertexData;
|
||||
import kha.arrays.ByteArray;
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.arrays.Uint32Array;
|
||||
import kha.arrays.Int16Array;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.MeshData;
|
||||
|
||||
class Geometry {
|
||||
#if lnx_deinterleaved
|
||||
public var vertexBuffers: Vector<InterleavedVertexBuffer>;
|
||||
#else
|
||||
public var vertexBuffer: VertexBuffer;
|
||||
public var vertexBufferMap: Map<String, VertexBuffer> = new Map();
|
||||
#end
|
||||
public var indexBuffers: Array<IndexBuffer>;
|
||||
public var start = 0; // For drawIndexedVertices
|
||||
public var count = -1;
|
||||
public var name = "";
|
||||
|
||||
public var ready = false;
|
||||
public var vertices: ByteArray;
|
||||
public var indices: Array<Uint32Array>;
|
||||
public var numTris = 0;
|
||||
public var materialIndices: Array<Int>;
|
||||
public var struct: VertexStructure;
|
||||
public var structLength: Int;
|
||||
public var structStr: String;
|
||||
public var usage: Usage;
|
||||
|
||||
public var instancedVB: VertexBuffer = null;
|
||||
public var instanced = false;
|
||||
public var instanceCount = 0;
|
||||
|
||||
public var positions: TVertexArray;
|
||||
public var normals: TVertexArray;
|
||||
public var uvs: TVertexArray;
|
||||
public var cols: TVertexArray;
|
||||
public var vertexArrays: Array<TVertexArray>;
|
||||
var data: MeshData;
|
||||
|
||||
public var aabb: Vec4 = null;
|
||||
public var aabbMin: Vec4 = null;
|
||||
public var aabbMax: Vec4 = null;
|
||||
|
||||
// Skinned
|
||||
#if lnx_skin
|
||||
public var skeletonTransformsI: Array<Mat4> = null;
|
||||
#end
|
||||
|
||||
public function new(data: MeshData, indices: Array<Uint32Array>, materialIndices: Array<Int>, usage: Usage = null) {
|
||||
if (usage == null) usage = Usage.StaticUsage;
|
||||
|
||||
this.indices = indices;
|
||||
this.materialIndices = materialIndices;
|
||||
this.usage = usage;
|
||||
|
||||
this.vertexArrays = data.raw.vertex_arrays;
|
||||
this.positions = getVArray('pos');
|
||||
this.normals = getVArray('nor');
|
||||
this.uvs = getVArray('tex');
|
||||
this.cols = getVArray('col');
|
||||
this.data = data;
|
||||
|
||||
struct = getVertexStructure(vertexArrays);
|
||||
structLength = Std.int(struct.byteSize() / 2);
|
||||
structStr = "";
|
||||
for (e in struct.elements) structStr += e.name;
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
#if lnx_deinterleaved
|
||||
for (buf in vertexBuffers) if (buf.buffer != null) buf.buffer.delete();
|
||||
#else
|
||||
for (buf in vertexBufferMap) if (buf != null) buf.delete();
|
||||
#end
|
||||
for (buf in indexBuffers) buf.delete();
|
||||
}
|
||||
|
||||
static function getVertexStructure(vertexArrays: Array<TVertexArray>): VertexStructure {
|
||||
var structure = new VertexStructure();
|
||||
for (i in 0...vertexArrays.length) {
|
||||
structure.add(vertexArrays[i].attrib, getVertexData(vertexArrays[i].data));
|
||||
}
|
||||
return structure;
|
||||
}
|
||||
|
||||
static function getVertexData(data: String): VertexData {
|
||||
switch (data) {
|
||||
case "short4norm": return VertexData.Short4Norm;
|
||||
case "short2norm": return VertexData.Short2Norm;
|
||||
default: return VertexData.Short4Norm;
|
||||
}
|
||||
}
|
||||
|
||||
public function applyScale(sx: Float, sy: Float, sz: Float) {
|
||||
data.scalePos *= sx;
|
||||
}
|
||||
|
||||
public function getVArray(name: String): TVertexArray {
|
||||
for (i in 0...vertexArrays.length) {
|
||||
if (vertexArrays[i].attrib == name) {
|
||||
return vertexArrays[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setupInstanced(data: Float32Array, instancedType: Int, usage: Usage) {
|
||||
var structure = new VertexStructure();
|
||||
structure.instanced = true;
|
||||
instanced = true;
|
||||
// pos, pos+rot, pos+scale, pos+rot+scale
|
||||
structure.add("ipos", kha.graphics4.VertexData.Float3);
|
||||
if (instancedType == 2 || instancedType == 4) {
|
||||
structure.add("irot", kha.graphics4.VertexData.Float3);
|
||||
}
|
||||
if (instancedType == 3 || instancedType == 4) {
|
||||
structure.add("iscl", kha.graphics4.VertexData.Float3);
|
||||
}
|
||||
|
||||
instanceCount = Std.int(data.length / Std.int(structure.byteSize() / 4));
|
||||
instancedVB = new VertexBuffer(instanceCount, structure, usage, 1);
|
||||
var vertices = instancedVB.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 4)) vertices.setFloat32(i * 4, data[i]);
|
||||
instancedVB.unlock();
|
||||
}
|
||||
|
||||
public function copyVertices(vertices: ByteArray, offset = 0, fakeUVs = false) {
|
||||
buildVertices(vertices, vertexArrays, offset, fakeUVs);
|
||||
}
|
||||
|
||||
static function buildVertices(vertices: ByteArray, vertexArrays: Array<TVertexArray>, offset = 0, fakeUVs = false, uvsIndex = -1) {
|
||||
var numVertices = verticesCount(vertexArrays[0]);
|
||||
var di = -1 + offset;
|
||||
for (i in 0...numVertices) {
|
||||
for (va in 0...vertexArrays.length) {
|
||||
var l = vertexArrays[va].size;
|
||||
if (fakeUVs && va == uvsIndex) { // Add fake uvs if uvs where "asked" for but not found
|
||||
for (j in 0...l) vertices.setInt16(++di * 2, 0);
|
||||
continue;
|
||||
}
|
||||
for (o in 0...l) {
|
||||
vertices.setInt16(++di * 2, vertexArrays[va].values[i * l + o]);
|
||||
}
|
||||
if (vertexArrays[va].padding != null) {
|
||||
if (vertexArrays[va].padding == 1) {
|
||||
vertices.setInt16(++di * 2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getVerticesLength(): Int {
|
||||
var res = 0;
|
||||
for (i in 0...vertexArrays.length) {
|
||||
res += vertexArrays[i].values.length;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#if lnx_deinterleaved
|
||||
public function get(vs: Array<TVertexElement>): Array<VertexBuffer> {
|
||||
var vbs = [];
|
||||
for (e in vs) {
|
||||
for (v in 0...vertexBuffers.length)
|
||||
if (vertexBuffers[v].name == e.name) {
|
||||
vbs.push(vertexBuffers[v].buffer);
|
||||
continue;
|
||||
}
|
||||
if (e.name == "ipos" && instancedVB != null) {
|
||||
vbs.push(instancedVB);
|
||||
}
|
||||
}
|
||||
return vbs;
|
||||
}
|
||||
#else
|
||||
|
||||
public function get(vs: Array<TVertexElement>): VertexBuffer {
|
||||
var key = "";
|
||||
for (e in vs) key += e.name;
|
||||
var vb = vertexBufferMap.get(key);
|
||||
if (vb == null) {
|
||||
var nVertexArrays = [];
|
||||
var atex = false;
|
||||
var texOffset = -1;
|
||||
var acol = false;
|
||||
for (e in 0...vs.length) {
|
||||
if (vs[e].name == "tex") {
|
||||
atex = true;
|
||||
texOffset = e;
|
||||
}
|
||||
if (vs[e].name == "col") {
|
||||
acol = true;
|
||||
}
|
||||
for (va in 0...vertexArrays.length) {
|
||||
if (vs[e].name == vertexArrays[va].attrib) {
|
||||
nVertexArrays.push(vertexArrays[va]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Multi-mat mesh with different vertex structures
|
||||
var struct = getVertexStructure(nVertexArrays);
|
||||
vb = new VertexBuffer(Std.int(positions.values.length / positions.size), struct, usage);
|
||||
vertices = vb.lock();
|
||||
buildVertices(vertices, nVertexArrays, 0, atex && uvs == null, texOffset);
|
||||
vb.unlock();
|
||||
vertexBufferMap.set(key, vb);
|
||||
if (atex && uvs == null) trace("Leenkx Warning: Geometry " + name + " is missing UV map");
|
||||
if (acol && cols == null) trace("Leenkx Warning: Geometry " + name + " is missing vertex colors");
|
||||
}
|
||||
return vb;
|
||||
}
|
||||
#end
|
||||
|
||||
public function build() {
|
||||
if (ready) return;
|
||||
|
||||
#if lnx_deinterleaved
|
||||
var vaLength = vertexArrays.length;
|
||||
vertexBuffers = new Vector(vaLength);
|
||||
for (i in 0...vaLength)
|
||||
vertexBuffers[i] = {
|
||||
name: vertexArrays[i].attrib,
|
||||
buffer: makeDeinterleavedVB(vertexArrays[i].values, vertexArrays[i].attrib, vertexArrays[i].size)
|
||||
};
|
||||
#else
|
||||
|
||||
vertexBuffer = new VertexBuffer(Std.int(positions.values.length / positions.size), struct, usage);
|
||||
vertices = vertexBuffer.lock();
|
||||
buildVertices(vertices, vertexArrays);
|
||||
vertexBuffer.unlock();
|
||||
vertexBufferMap.set(structStr, vertexBuffer);
|
||||
#end
|
||||
|
||||
indexBuffers = [];
|
||||
for (id in indices) {
|
||||
if (id.length == 0) continue;
|
||||
var indexBuffer = new IndexBuffer(id.length, usage);
|
||||
numTris += Std.int(id.length / 3);
|
||||
|
||||
var indicesA = indexBuffer.lock();
|
||||
for (i in 0...indicesA.length) indicesA[i] = id[i];
|
||||
|
||||
indexBuffer.unlock();
|
||||
indexBuffers.push(indexBuffer);
|
||||
}
|
||||
|
||||
// Instanced
|
||||
if (data.raw.instanced_data != null) setupInstanced(data.raw.instanced_data, data.raw.instanced_type, usage);
|
||||
|
||||
ready = true;
|
||||
}
|
||||
|
||||
#if lnx_deinterleaved
|
||||
function makeDeinterleavedVB(data: ByteArray, name: String, structLength: Int): VertexBuffer {
|
||||
var struct = new VertexStructure();
|
||||
struct.add(name, structLength == 2 ? VertexData.Short2Norm : VertexData.Short4Norm);
|
||||
|
||||
var vertexBuffer = new VertexBuffer(Std.int(data.byteLength / 2 / structLength), struct, usage);
|
||||
|
||||
var vertices = vertexBuffer.lock();
|
||||
for (i in 0...Std.int(vertices.byteLength / 2)) vertices.setInt16(i * 2, data.getInt16(i * 2));
|
||||
vertexBuffer.unlock();
|
||||
|
||||
return vertexBuffer;
|
||||
}
|
||||
#end
|
||||
|
||||
public function getVerticesCount(): Int {
|
||||
return Std.int(positions.values.length / positions.size);
|
||||
}
|
||||
|
||||
inline static function verticesCount(arr: TVertexArray): Int {
|
||||
return Std.int(arr.values.length / arr.size);
|
||||
}
|
||||
|
||||
// Skinned
|
||||
#if lnx_skin
|
||||
public function initSkeletonTransforms(transformsI: Array<Float32Array>) {
|
||||
skeletonTransformsI = [];
|
||||
for (t in transformsI) {
|
||||
var mi = Mat4.fromFloat32Array(t);
|
||||
skeletonTransformsI.push(mi);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
public function calculateAABB() {
|
||||
aabbMin = new Vec4(-0.01, -0.01, -0.01);
|
||||
aabbMax = new Vec4(0.01, 0.01, 0.01);
|
||||
aabb = new Vec4();
|
||||
var i = 0;
|
||||
while (i < positions.values.length) {
|
||||
if (positions.values[i ] > aabbMax.x) aabbMax.x = positions.values[i];
|
||||
if (positions.values[i + 1] > aabbMax.y) aabbMax.y = positions.values[i + 1];
|
||||
if (positions.values[i + 2] > aabbMax.z) aabbMax.z = positions.values[i + 2];
|
||||
if (positions.values[i ] < aabbMin.x) aabbMin.x = positions.values[i];
|
||||
if (positions.values[i + 1] < aabbMin.y) aabbMin.y = positions.values[i + 1];
|
||||
if (positions.values[i + 2] < aabbMin.z) aabbMin.z = positions.values[i + 2];
|
||||
i += 4;
|
||||
}
|
||||
aabb.x = (Math.abs(aabbMin.x) + Math.abs(aabbMax.x)) / 32767 * data.scalePos;
|
||||
aabb.y = (Math.abs(aabbMin.y) + Math.abs(aabbMax.y)) / 32767 * data.scalePos;
|
||||
aabb.z = (Math.abs(aabbMin.z) + Math.abs(aabbMax.z)) / 32767 * data.scalePos;
|
||||
}
|
||||
|
||||
public function calculateTangents() {
|
||||
// var num_verts = Std.int(positions.length / 4);
|
||||
// var tangents = new Float32Array(num_verts * 3);
|
||||
// var bitangents = new Float32Array(num_verts * 3);
|
||||
// for (ia in indices) {
|
||||
// var num_tris = Std.int(ia.length / 3);
|
||||
// for (i in 0...num_tris) {
|
||||
// var i0 = ia[i * 3 ];
|
||||
// var i1 = ia[i * 3 + 1];
|
||||
// var i2 = ia[i * 3 + 2];
|
||||
// var v0 = Vector((positions[i0 * 4], positions[i0 * 4 + 1], positions[i0 * 4 + 2]));
|
||||
// var v1 = Vector((positions[i1 * 4], positions[i1 * 4 + 1], positions[i1 * 4 + 2]));
|
||||
// var v2 = Vector((positions[i2 * 4], positions[i2 * 4 + 1], positions[i2 * 4 + 2]));
|
||||
// var uv0 = Vector((uvs[i0 * 2], uvs[i0 * 2 + 1]));
|
||||
// var uv1 = Vector((uvs[i1 * 2], uvs[i1 * 2 + 1]));
|
||||
// var uv2 = Vector((uvs[i2 * 2], uvs[i2 * 2 + 1]));
|
||||
|
||||
// var deltaPos1 = v1 - v0;
|
||||
// var deltaPos2 = v2 - v0;
|
||||
// var deltaUV1 = uv1 - uv0;
|
||||
// var deltaUV2 = uv2 - uv0;
|
||||
// var d = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
|
||||
// var r = d != 0 ? 1.0 / d : 1.0;
|
||||
// var tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
|
||||
// var bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
|
||||
|
||||
// var tangents[i0 * 3 ] += tangent.x;
|
||||
// var tangents[i0 * 3 + 1] += tangent.y;
|
||||
// var tangents[i0 * 3 + 2] += tangent.z;
|
||||
// var tangents[i1 * 3 ] += tangent.x;
|
||||
// var tangents[i1 * 3 + 1] += tangent.y;
|
||||
// var tangents[i1 * 3 + 2] += tangent.z;
|
||||
// var tangents[i2 * 3 ] += tangent.x;
|
||||
// var tangents[i2 * 3 + 1] += tangent.y;
|
||||
// var tangents[i2 * 3 + 2] += tangent.z;
|
||||
// var bitangents[i0 * 3 ] += bitangent.x;
|
||||
// var bitangents[i0 * 3 + 1] += bitangent.y;
|
||||
// var bitangents[i0 * 3 + 2] += bitangent.z;
|
||||
// var bitangents[i1 * 3 ] += bitangent.x;
|
||||
// var bitangents[i1 * 3 + 1] += bitangent.y;
|
||||
// var bitangents[i1 * 3 + 2] += bitangent.z;
|
||||
// var bitangents[i2 * 3 ] += bitangent.x;
|
||||
// var bitangents[i2 * 3 + 1] += bitangent.y;
|
||||
// var bitangents[i2 * 3 + 2] += bitangent.z;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Orthogonalize
|
||||
// for (i in 0...num_verts) {
|
||||
// var t = Vector((tangents[i * 3], tangents[i * 3 + 1], tangents[i * 3 + 2]));
|
||||
// var b = Vector((bitangents[i * 3], bitangents[i * 3 + 1], bitangents[i * 3 + 2]));
|
||||
// var n = Vector((normals[i * 2], normals[i * 2 + 1], positions[i * 4 + 3] / scale_pos));
|
||||
// var v = t - n * n.dot(t);
|
||||
// v.normalize();
|
||||
// // Calculate handedness
|
||||
// var cnv = n.cross(v);
|
||||
// if (cnv.dot(b) < 0.0)
|
||||
// v = v * -1.0;
|
||||
// tangents[i * 3 ] = v.x;
|
||||
// tangents[i * 3 + 1] = v.y;
|
||||
// tangents[i * 3 + 2] = v.z;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef InterleavedVertexBuffer = {
|
||||
#else
|
||||
@:structInit class InterleavedVertexBuffer {
|
||||
#end
|
||||
public var name: String;
|
||||
public var buffer: VertexBuffer;
|
||||
}
|
||||
36
leenkx/Sources/iron/data/LightData.hx
Normal file
36
leenkx/Sources/iron/data/LightData.hx
Normal file
@ -0,0 +1,36 @@
|
||||
package iron.data;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class LightData {
|
||||
|
||||
public var name: String;
|
||||
public var raw: TLightData;
|
||||
|
||||
public function new(raw: TLightData, done: LightData->Void) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
done(this);
|
||||
}
|
||||
|
||||
public static inline function typeToInt(s: String): Int {
|
||||
switch (s) {
|
||||
case "sun": return 0;
|
||||
case "point": return 1;
|
||||
case "spot": return 2;
|
||||
case "area": return 3;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static function parse(name: String, id: String, done: LightData->Void) {
|
||||
Data.getSceneRaw(name, function(format: TSceneFormat) {
|
||||
var raw: TLightData = Data.getLightRawByName(format.light_datas, id);
|
||||
if (raw == null) {
|
||||
trace('Light data "$id" not found!');
|
||||
done(null);
|
||||
}
|
||||
new LightData(raw, done);
|
||||
});
|
||||
}
|
||||
}
|
||||
144
leenkx/Sources/iron/data/MaterialData.hx
Normal file
144
leenkx/Sources/iron/data/MaterialData.hx
Normal file
@ -0,0 +1,144 @@
|
||||
package iron.data;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.ShaderData;
|
||||
import iron.object.MeshObject;
|
||||
|
||||
class MaterialData {
|
||||
|
||||
static var uidCounter = 0;
|
||||
public var uid: Float;
|
||||
public var name: String;
|
||||
public var raw: TMaterialData;
|
||||
public var shader: ShaderData;
|
||||
|
||||
public var contexts: Array<MaterialContext> = null;
|
||||
|
||||
public function new(raw: TMaterialData, done: MaterialData->Void, file = "") {
|
||||
uid = ++uidCounter; // Start from 1
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
|
||||
var ref = raw.shader.split("/");
|
||||
var object_file = "";
|
||||
var data_ref = "";
|
||||
if (ref.length == 2) { // File reference
|
||||
object_file = ref[0];
|
||||
data_ref = ref[1];
|
||||
}
|
||||
else { // Local data
|
||||
object_file = file;
|
||||
data_ref = raw.shader;
|
||||
}
|
||||
|
||||
Data.getShader(object_file, data_ref, function(b: ShaderData) {
|
||||
shader = b;
|
||||
|
||||
// Contexts have to be in the same order as in raw data for now
|
||||
contexts = [];
|
||||
while (contexts.length < raw.contexts.length) contexts.push(null);
|
||||
var contextsLoaded = 0;
|
||||
|
||||
for (i in 0...raw.contexts.length) {
|
||||
var c = raw.contexts[i];
|
||||
new MaterialContext(c, function(self: MaterialContext) {
|
||||
contexts[i] = self;
|
||||
contextsLoaded++;
|
||||
if (contextsLoaded == raw.contexts.length) done(this);
|
||||
});
|
||||
}
|
||||
}, raw.override_context);
|
||||
}
|
||||
|
||||
public static function parse(file: String, name: String, done: MaterialData->Void) {
|
||||
Data.getSceneRaw(file, function(format: TSceneFormat) {
|
||||
var raw: TMaterialData = Data.getMaterialRawByName(format.material_datas, name);
|
||||
if (raw == null) {
|
||||
trace('Material data "$name" not found!');
|
||||
done(null);
|
||||
}
|
||||
new MaterialData(raw, done, file);
|
||||
});
|
||||
}
|
||||
|
||||
public function getContext(name: String): MaterialContext {
|
||||
for (c in contexts) {
|
||||
// 'mesh' will fetch both 'mesh' and 'meshheight' contexts
|
||||
if (c.raw.name.substr(0, name.length) == name) return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MaterialContext {
|
||||
public var raw: TMaterialContext;
|
||||
public var textures: Vector<kha.Image> = null;
|
||||
public var id = 0;
|
||||
static var num = 0;
|
||||
|
||||
public function new(raw: TMaterialContext, done: MaterialContext->Void) {
|
||||
this.raw = raw;
|
||||
id = num++;
|
||||
|
||||
if (raw.bind_textures != null && raw.bind_textures.length > 0) {
|
||||
|
||||
textures = new Vector(raw.bind_textures.length);
|
||||
var texturesLoaded = 0;
|
||||
|
||||
for (i in 0...raw.bind_textures.length) {
|
||||
var tex = raw.bind_textures[i];
|
||||
|
||||
if (tex.file == "" || tex.source == "movie") { // Empty texture
|
||||
texturesLoaded++;
|
||||
if (texturesLoaded == raw.bind_textures.length) done(this);
|
||||
continue;
|
||||
}
|
||||
|
||||
Data.getImage(tex.file, function(image: kha.Image) {
|
||||
textures[i] = image;
|
||||
texturesLoaded++;
|
||||
|
||||
// Set mipmaps
|
||||
if (tex.mipmaps != null) {
|
||||
var mipmaps: Array<kha.Image> = [];
|
||||
while (mipmaps.length < tex.mipmaps.length) mipmaps.push(null);
|
||||
var mipmapsLoaded = 0;
|
||||
|
||||
for (j in 0...tex.mipmaps.length) {
|
||||
var name = tex.mipmaps[j];
|
||||
|
||||
Data.getImage(name, function(mipimg: kha.Image) {
|
||||
mipmaps[j] = mipimg;
|
||||
mipmapsLoaded++;
|
||||
|
||||
if (mipmapsLoaded == tex.mipmaps.length) {
|
||||
image.setMipmaps(mipmaps);
|
||||
tex.mipmaps = null;
|
||||
tex.generate_mipmaps = false;
|
||||
|
||||
if (texturesLoaded == raw.bind_textures.length) done(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (tex.generate_mipmaps == true && image != null) {
|
||||
image.generateMipmaps(1000);
|
||||
tex.mipmaps = null;
|
||||
tex.generate_mipmaps = false;
|
||||
|
||||
if (texturesLoaded == raw.bind_textures.length) done(this);
|
||||
}
|
||||
else if (texturesLoaded == raw.bind_textures.length) done(this);
|
||||
|
||||
}, false, tex.format != null ? tex.format : "RGBA32");
|
||||
}
|
||||
}
|
||||
else done(this);
|
||||
}
|
||||
|
||||
public function setTextureParameters(g: kha.graphics4.Graphics, textureIndex: Int, context: ShaderContext, unitIndex: Int) {
|
||||
// This function is called by MeshObject for samplers set using material context
|
||||
context.setTextureParameters(g, unitIndex, raw.bind_textures[textureIndex]);
|
||||
}
|
||||
}
|
||||
274
leenkx/Sources/iron/data/MeshBatch.hx
Normal file
274
leenkx/Sources/iron/data/MeshBatch.hx
Normal file
@ -0,0 +1,274 @@
|
||||
package iron.data;
|
||||
|
||||
#if lnx_batch
|
||||
|
||||
import kha.arrays.ByteArray;
|
||||
import kha.graphics4.VertexBuffer;
|
||||
import kha.graphics4.IndexBuffer;
|
||||
import kha.graphics4.Usage;
|
||||
import kha.graphics4.VertexStructure;
|
||||
import kha.graphics4.Graphics;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Uniforms;
|
||||
import iron.data.Geometry;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.ShaderData;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
@:access(iron.object.MeshObject)
|
||||
class MeshBatch {
|
||||
|
||||
var buckets: Map<ShaderData, Bucket> = new Map();
|
||||
public var nonBatched: Array<MeshObject> = [];
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function remove() {
|
||||
for (b in buckets) b.remove();
|
||||
}
|
||||
|
||||
public static function isBatchable(m: MeshObject): Bool {
|
||||
m.computeDepthRead();
|
||||
var batch =
|
||||
m.materials != null &&
|
||||
m.materials.length == 1 &&
|
||||
!m.data.geom.instanced &&
|
||||
!m.data.isSkinned &&
|
||||
m.data.raw.morph_target == null &&
|
||||
!m.depthRead;
|
||||
return batch;
|
||||
}
|
||||
|
||||
public function addMesh(m: MeshObject, isLod: Bool): Bool {
|
||||
// No instancing, multimat, skinning, morph targets or lod batching
|
||||
if (!isBatchable(m) || isLod) {
|
||||
nonBatched.push(m);
|
||||
return false;
|
||||
}
|
||||
|
||||
var shader = m.materials[0].shader;
|
||||
var b = buckets.get(shader);
|
||||
if (b == null) {
|
||||
b = new Bucket(shader);
|
||||
buckets.set(shader, b);
|
||||
}
|
||||
b.addMesh(m);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeMesh(m: MeshObject) {
|
||||
var shader = m.materials[0].shader;
|
||||
var b = buckets.get(shader);
|
||||
if (b != null) b.removeMesh(m);
|
||||
else nonBatched.remove(m);
|
||||
}
|
||||
|
||||
@:access(iron.RenderPath)
|
||||
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||
|
||||
for (b in buckets) {
|
||||
if (!b.batched) b.batch();
|
||||
if (b.meshes.length > 0 && b.meshes[0].cullMaterial(context)) continue;
|
||||
|
||||
var scontext = b.shader.getContext(context);
|
||||
g.setPipeline(scontext.pipeState);
|
||||
// #if lnx_deinterleaved // TODO
|
||||
// g.setVertexBuffers(b.vertexBuffers);
|
||||
// #else
|
||||
g.setVertexBuffer(b.getVertexBuffer(scontext.raw.vertex_elements));
|
||||
// #end
|
||||
g.setIndexBuffer(b.indexBuffer);
|
||||
|
||||
Uniforms.setContextConstants(g, scontext, bindParams);
|
||||
|
||||
RenderPath.sortMeshesDistance(b.meshes);
|
||||
|
||||
for (m in b.meshes) {
|
||||
if (!m.visible) continue; // Skip render if object is hidden
|
||||
if (m.cullMesh(context, Scene.active.camera, RenderPath.active.light)) continue;
|
||||
|
||||
// Get context
|
||||
var materialContexts: Array<MaterialContext> = [];
|
||||
var shaderContexts: Array<ShaderContext> = [];
|
||||
m.getContexts(context, m.materials, materialContexts, shaderContexts);
|
||||
|
||||
Uniforms.posUnpack = m.data.scalePos;
|
||||
Uniforms.texUnpack = m.data.scaleTex;
|
||||
m.transform.update();
|
||||
|
||||
// Render mesh
|
||||
Uniforms.setObjectConstants(g, scontext, m);
|
||||
Uniforms.setMaterialConstants(g, scontext, materialContexts[0]);
|
||||
|
||||
g.drawIndexedVertices(m.data.start, m.data.count);
|
||||
|
||||
#if lnx_veloc
|
||||
m.prevMatrix.setFrom(m.transform.worldUnpack);
|
||||
#end
|
||||
|
||||
#if lnx_debug
|
||||
RenderPath.drawCalls++;
|
||||
RenderPath.batchCalls++;
|
||||
#end
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
RenderPath.batchBuckets++;
|
||||
#end
|
||||
}
|
||||
|
||||
// Render non-batched meshes
|
||||
inline RenderPath.meshRenderLoop(g, context, bindParams, nonBatched);
|
||||
}
|
||||
}
|
||||
|
||||
class Bucket {
|
||||
|
||||
public var batched = false;
|
||||
public var shader: ShaderData;
|
||||
var vertexBuffer: VertexBuffer;
|
||||
var vertexBufferMap: Map<String, VertexBuffer> = new Map();
|
||||
public var indexBuffer: IndexBuffer;
|
||||
public var meshes: Array<MeshObject> = [];
|
||||
|
||||
public function new(shader: ShaderData) {
|
||||
this.shader = shader;
|
||||
}
|
||||
|
||||
public function remove() {
|
||||
indexBuffer.delete();
|
||||
// this.vertexBuffer is in the map, so it's also deleted here
|
||||
for (buf in vertexBufferMap) buf.delete();
|
||||
meshes = [];
|
||||
}
|
||||
|
||||
public function addMesh(m: MeshObject) {
|
||||
meshes.push(m);
|
||||
}
|
||||
|
||||
public function removeMesh(m: MeshObject) {
|
||||
meshes.remove(m);
|
||||
}
|
||||
|
||||
function copyAttribute(attribSize: Int, count: Int, to: ByteArray, toStride: Int, toOffset: Int, from: ByteArray, fromStride: Int, fromOffset: Int) {
|
||||
for (i in 0...count) {
|
||||
for (j in 0...attribSize) {
|
||||
to.setInt16((i * toStride + toOffset + j) * 2, from.getInt16((i * fromStride + fromOffset + j) * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractVertexBuffer(elems: Array<TVertexElement>): VertexBuffer {
|
||||
// Build vertex buffer for specific context
|
||||
var vs = new VertexStructure();
|
||||
for (e in elems) vs.add(e.name, ShaderContext.parseData(e.data));
|
||||
|
||||
var vb = new VertexBuffer(vertexBuffer.count(), vs, Usage.StaticUsage);
|
||||
var to = vb.lock();
|
||||
var from = vertexBuffer.lock();
|
||||
|
||||
var toOffset = 0;
|
||||
var toStride = Std.int(vb.stride() / 2);
|
||||
var fromOffset = 0;
|
||||
var fromStride = Std.int(vertexBuffer.stride() / 2);
|
||||
|
||||
for (e in elems) {
|
||||
var size = 0;
|
||||
if (e.name == "pos") { size = 4; fromOffset = 0; }
|
||||
else if (e.name == "nor") { size = 2; fromOffset = 4; }
|
||||
else if (e.name == "tex") { size = 2; fromOffset = 6; }
|
||||
copyAttribute(size, vertexBuffer.count(), to, toStride, toOffset, from, fromStride, fromOffset);
|
||||
toOffset += size;
|
||||
}
|
||||
|
||||
vb.unlock();
|
||||
return vb;
|
||||
}
|
||||
|
||||
public function getVertexBuffer(elems: Array<TVertexElement>): VertexBuffer {
|
||||
var s = "";
|
||||
for (e in elems) s += e.name;
|
||||
var vb = vertexBufferMap.get(s);
|
||||
if (vb == null) {
|
||||
vb = extractVertexBuffer(elems);
|
||||
vertexBufferMap.set(s, vb);
|
||||
}
|
||||
return vb;
|
||||
}
|
||||
|
||||
function vertexCount(g: Geometry, hasUVs: Bool): Int {
|
||||
var vcount = g.getVerticesLength();
|
||||
if (hasUVs && g.uvs == null) {
|
||||
vcount += Std.int(g.positions.values.length / 4) * 2;
|
||||
}
|
||||
return vcount;
|
||||
}
|
||||
|
||||
public function batch() {
|
||||
batched = true;
|
||||
|
||||
// Ensure same vertex structure for batched meshes
|
||||
var hasUVs = false;
|
||||
for (m in meshes) {
|
||||
if (m.data.geom.uvs != null) {
|
||||
hasUVs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Unique mesh datas
|
||||
var vcount = 0;
|
||||
var icount = 0;
|
||||
var mdatas: Array<MeshData> = [];
|
||||
for (m in meshes) {
|
||||
var mdFound = false;
|
||||
for (md in mdatas) {
|
||||
if (m.data == md) {
|
||||
mdFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mdFound) {
|
||||
mdatas.push(m.data);
|
||||
m.data.start = icount;
|
||||
m.data.count = m.data.geom.indices[0].length;
|
||||
icount += m.data.count;
|
||||
vcount += vertexCount(m.data.geom, hasUVs);
|
||||
}
|
||||
}
|
||||
|
||||
if (mdatas.length == 0) return;
|
||||
|
||||
// Pick UVs if present
|
||||
var vs = mdatas[0].geom.struct;
|
||||
for (md in mdatas) if (md.geom.struct.size() > vs.size()) vs = md.geom.struct;
|
||||
|
||||
// Build shared buffers
|
||||
vertexBuffer = new VertexBuffer(vcount, vs, Usage.StaticUsage);
|
||||
var vertices = vertexBuffer.lock();
|
||||
var offset = 0;
|
||||
for (md in mdatas) {
|
||||
md.geom.copyVertices(vertices, offset, hasUVs);
|
||||
offset += vertexCount(md.geom, hasUVs);
|
||||
}
|
||||
vertexBuffer.unlock();
|
||||
|
||||
var s = "";
|
||||
for (e in vs.elements) s += e.name;
|
||||
vertexBufferMap.set(s, vertexBuffer);
|
||||
|
||||
indexBuffer = new IndexBuffer(icount, Usage.StaticUsage);
|
||||
var indices = indexBuffer.lock();
|
||||
var di = -1;
|
||||
var offset = 0;
|
||||
for (md in mdatas) {
|
||||
for (i in 0...md.geom.indices[0].length) {
|
||||
indices[++di] = md.geom.indices[0][i] + offset;
|
||||
}
|
||||
offset += Std.int(md.geom.getVerticesLength() / md.geom.structLength);
|
||||
}
|
||||
indexBuffer.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
129
leenkx/Sources/iron/data/MeshData.hx
Normal file
129
leenkx/Sources/iron/data/MeshData.hx
Normal file
@ -0,0 +1,129 @@
|
||||
package iron.data;
|
||||
|
||||
import kha.graphics4.Usage;
|
||||
import kha.graphics4.VertexData;
|
||||
import kha.arrays.Int16Array;
|
||||
import kha.arrays.Uint32Array;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class MeshData {
|
||||
|
||||
public var name: String;
|
||||
public var raw: TMeshData;
|
||||
public var format: TSceneFormat;
|
||||
public var geom: Geometry;
|
||||
public var start = 0; // Batched
|
||||
public var count = -1;
|
||||
public var refcount = 0; // Number of users
|
||||
public var handle: String; // Handle used to retrieve this object in Data
|
||||
public var scalePos: kha.FastFloat = 1.0;
|
||||
public var scaleTex: kha.FastFloat = 1.0;
|
||||
public var isSkinned: Bool;
|
||||
|
||||
public function new(raw: TMeshData, done: MeshData->Void) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
|
||||
if (raw.scale_pos != null) scalePos = raw.scale_pos;
|
||||
if (raw.scale_tex != null) scaleTex = raw.scale_tex;
|
||||
|
||||
// Mesh data
|
||||
var indices: Array<Uint32Array> = [];
|
||||
var materialIndices: Array<Int> = [];
|
||||
for (ind in raw.index_arrays) {
|
||||
indices.push(ind.values);
|
||||
materialIndices.push(ind.material);
|
||||
}
|
||||
|
||||
// Skinning
|
||||
isSkinned = raw.skin != null;
|
||||
|
||||
// Prepare vertex array for skinning and fill size data
|
||||
var vertexArrays = raw.vertex_arrays;
|
||||
if (isSkinned) {
|
||||
vertexArrays.push({ attrib: "bone", values: null, data: "short4norm" });
|
||||
vertexArrays.push({ attrib: "weight", values: null, data: "short4norm" });
|
||||
}
|
||||
for (i in 0...vertexArrays.length) {
|
||||
vertexArrays[i].size = getVertexSize(vertexArrays[i].data, getPadding(vertexArrays[i].padding));
|
||||
}
|
||||
|
||||
// Usage, also used for instanced data
|
||||
var parsedUsage = Usage.StaticUsage;
|
||||
if (raw.dynamic_usage != null && raw.dynamic_usage == true) parsedUsage = Usage.DynamicUsage;
|
||||
var usage = parsedUsage;
|
||||
|
||||
if (isSkinned) {
|
||||
var bonea = null;
|
||||
var weighta = null;
|
||||
var vertex_length = Std.int(vertexArrays[0].values.length / vertexArrays[0].size);
|
||||
var l = vertex_length * 4;
|
||||
bonea = new Int16Array(l);
|
||||
weighta = new Int16Array(l);
|
||||
|
||||
var index = 0;
|
||||
var ai = 0;
|
||||
for (i in 0...vertex_length) {
|
||||
var boneCount = raw.skin.bone_count_array[i];
|
||||
for (j in index...(index + boneCount)) {
|
||||
bonea[ai] = raw.skin.bone_index_array[j];
|
||||
weighta[ai] = raw.skin.bone_weight_array[j];
|
||||
ai++;
|
||||
}
|
||||
// Fill unused weights
|
||||
for (j in boneCount...4) {
|
||||
bonea[ai] = 0;
|
||||
weighta[ai] = 0;
|
||||
ai++;
|
||||
}
|
||||
index += boneCount;
|
||||
}
|
||||
vertexArrays[vertexArrays.length - 2].values = bonea;
|
||||
vertexArrays[vertexArrays.length - 1].values = weighta;
|
||||
}
|
||||
|
||||
// Make vertex buffers
|
||||
geom = new Geometry(this, indices, materialIndices, usage);
|
||||
geom.name = name;
|
||||
|
||||
done(this);
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
geom.delete();
|
||||
}
|
||||
|
||||
public static function parse(name: String, id: String, done: MeshData->Void) {
|
||||
Data.getSceneRaw(name, function(format: TSceneFormat) {
|
||||
var raw: TMeshData = Data.getMeshRawByName(format.mesh_datas, id);
|
||||
if (raw == null) {
|
||||
trace('Mesh data "$id" not found!');
|
||||
done(null);
|
||||
}
|
||||
|
||||
new MeshData(raw, function(dat: MeshData) {
|
||||
dat.format = format;
|
||||
// Skinned
|
||||
#if lnx_skin
|
||||
if (raw.skin != null) {
|
||||
dat.geom.initSkeletonTransforms(raw.skin.transformsI);
|
||||
}
|
||||
#end
|
||||
done(dat);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getVertexSize(vertex_data: String, padding: Int = 0): Int {
|
||||
switch (vertex_data) {
|
||||
case "short4norm": return 4 - padding;
|
||||
case "short2norm": return 2 - padding;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline function getPadding(padding: Null<Int>): Int {
|
||||
return padding != null ? padding : 0;
|
||||
}
|
||||
}
|
||||
|
||||
27
leenkx/Sources/iron/data/ParticleData.hx
Normal file
27
leenkx/Sources/iron/data/ParticleData.hx
Normal file
@ -0,0 +1,27 @@
|
||||
package iron.data;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class ParticleData {
|
||||
|
||||
public var name: String;
|
||||
public var raw: TParticleData;
|
||||
|
||||
public function new(raw: TParticleData, done: ParticleData->Void) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
|
||||
done(this);
|
||||
}
|
||||
|
||||
public static function parse(name: String, id: String, done: ParticleData->Void) {
|
||||
Data.getSceneRaw(name, function(format: TSceneFormat) {
|
||||
var raw: TParticleData = Data.getParticleRawByName(format.particle_datas, id);
|
||||
if (raw == null) {
|
||||
trace('Particle data "$id" not found!');
|
||||
done(null);
|
||||
}
|
||||
new ParticleData(raw, done);
|
||||
});
|
||||
}
|
||||
}
|
||||
27
leenkx/Sources/iron/data/ProbeData.hx
Normal file
27
leenkx/Sources/iron/data/ProbeData.hx
Normal file
@ -0,0 +1,27 @@
|
||||
package iron.data;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class ProbeData {
|
||||
|
||||
#if rp_probes
|
||||
|
||||
public var raw: TProbeData;
|
||||
|
||||
public function new(raw: TProbeData, done: ProbeData->Void) {
|
||||
this.raw = raw;
|
||||
done(this);
|
||||
}
|
||||
|
||||
public static function parse(name: String, id: String, done: ProbeData->Void) {
|
||||
Data.getSceneRaw(name, function(format: TSceneFormat) {
|
||||
var raw: TProbeData = Data.getProbeRawByName(format.probe_datas, id);
|
||||
if (raw == null) {
|
||||
trace('Probe data "$id" not found!');
|
||||
done(null);
|
||||
}
|
||||
new ProbeData(raw, done);
|
||||
});
|
||||
}
|
||||
#end
|
||||
}
|
||||
578
leenkx/Sources/iron/data/SceneFormat.hx
Normal file
578
leenkx/Sources/iron/data/SceneFormat.hx
Normal file
@ -0,0 +1,578 @@
|
||||
package iron.data;
|
||||
|
||||
#if !macro
|
||||
import kha.FastFloat;
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.arrays.Uint32Array;
|
||||
import kha.arrays.Int16Array;
|
||||
#end
|
||||
|
||||
#if macro
|
||||
typedef Float32Array = haxe.io.Float32Array;
|
||||
typedef Uint32Array = haxe.io.UInt32Array;
|
||||
typedef Int16Array = haxe.io.UInt16Array;
|
||||
typedef FastFloat = Float;
|
||||
#end
|
||||
|
||||
#if js
|
||||
typedef TSceneFormat = {
|
||||
#else
|
||||
@:structInit class TSceneFormat {
|
||||
#end
|
||||
@:optional public var name: String;
|
||||
@:optional public var mesh_datas: Array<TMeshData>;
|
||||
@:optional public var light_datas: Array<TLightData>;
|
||||
@:optional public var probe_datas: Array<TProbeData>;
|
||||
@:optional public var camera_datas: Array<TCameraData>;
|
||||
@:optional public var camera_ref: String; // Active camera
|
||||
@:optional public var material_datas: Array<TMaterialData>;
|
||||
@:optional public var particle_datas: Array<TParticleData>;
|
||||
@:optional public var shader_datas: Array<TShaderData>;
|
||||
@:optional public var speaker_datas: Array<TSpeakerData>;
|
||||
@:optional public var world_datas: Array<TWorldData>;
|
||||
@:optional public var world_ref: String;
|
||||
@:optional public var tilesheet_datas: Array<TTilesheetData>;
|
||||
@:optional public var objects: Array<TObj>;
|
||||
@:optional public var groups: Array<TGroup>;
|
||||
@:optional public var gravity: Float32Array;
|
||||
@:optional public var traits: Array<TTrait>; // Scene root traits
|
||||
@:optional public var embedded_datas: Array<String>; // Preload for this scene, images only for now
|
||||
@:optional public var frame_time: Null<FastFloat>;
|
||||
@:optional public var irradiance: Float32Array; // Blob with spherical harmonics, bands 0,1,2
|
||||
@:optional public var terrain_datas: Array<TTerrainData>;
|
||||
@:optional public var terrain_ref: String;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TMeshData = {
|
||||
#else
|
||||
@:structInit class TMeshData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var vertex_arrays: Array<TVertexArray>;
|
||||
public var index_arrays: Array<TIndexArray>;
|
||||
@:optional public var dynamic_usage: Null<Bool>;
|
||||
@:optional public var skin: TSkin;
|
||||
@:optional public var instanced_data: Float32Array;
|
||||
@:optional public var instanced_type: Null<Int>; // off, loc, loc+rot, loc+scale, loc+rot+scale
|
||||
@:optional public var scale_pos: Null<FastFloat>; // Unpack pos from (-1,1) coords
|
||||
@:optional public var scale_tex: Null<FastFloat>; // Unpack tex from (-1,1) coords
|
||||
@:optional public var morph_target: TMorphTarget;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TMorphTarget = {
|
||||
#else
|
||||
@:structInit class TMorphTarget {
|
||||
#end
|
||||
public var morph_target_data_file: String;
|
||||
public var morph_scale: FastFloat;
|
||||
public var morph_offset: FastFloat;
|
||||
public var num_morph_targets: Int;
|
||||
public var morph_img_size: Int;
|
||||
public var morph_block_size: Int;
|
||||
public var morph_target_ref: Array<String>;
|
||||
public var morph_target_defaults: Float32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TSkin = {
|
||||
#else
|
||||
@:structInit class TSkin {
|
||||
#end
|
||||
public var transform: TTransform;
|
||||
public var bone_ref_array: Array<String>;
|
||||
public var bone_len_array: Float32Array;
|
||||
public var transformsI: Array<Float32Array>; // per-bone, size = 16, with skin.transform, pre-inverted
|
||||
public var bone_count_array: Int16Array;
|
||||
public var bone_index_array: Int16Array;
|
||||
public var bone_weight_array: Int16Array;
|
||||
public var constraints: Array<TConstraint>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TVertexArray = {
|
||||
#else
|
||||
@:structInit class TVertexArray {
|
||||
#end
|
||||
public var attrib: String;
|
||||
public var values: Int16Array;
|
||||
public var data: String; // short4norm, short2norm
|
||||
@:optional public var padding: Null<Int>;
|
||||
@:optional public var size: Null<Int>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TIndexArray = {
|
||||
#else
|
||||
@:structInit class TIndexArray {
|
||||
#end
|
||||
public var values: Uint32Array; // size = 3
|
||||
public var material: Int;
|
||||
@:optional public var vertex_map: Uint32Array; // size = 3
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TLightData = {
|
||||
#else
|
||||
@:structInit class TLightData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var type: String; // sun, point, spot
|
||||
public var color: Float32Array;
|
||||
public var strength: FastFloat;
|
||||
@:optional public var cast_shadow: Null<Bool>;
|
||||
@:optional public var near_plane: Null<FastFloat>;
|
||||
@:optional public var far_plane: Null<FastFloat>;
|
||||
@:optional public var fov: Null<FastFloat>;
|
||||
@:optional public var shadows_bias: Null<FastFloat>;
|
||||
@:optional public var shadowmap_size: Null<Int>;
|
||||
@:optional public var shadowmap_cube: Null<Bool>; // Omni shadows for point
|
||||
@:optional public var spot_size: Null<FastFloat>;
|
||||
@:optional public var spot_blend: Null<FastFloat>;
|
||||
@:optional public var light_size: Null<FastFloat>; // Shadow soft size
|
||||
@:optional public var size: Null<FastFloat>; // Area light
|
||||
@:optional public var size_y: Null<FastFloat>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TCameraData = {
|
||||
#else
|
||||
@:structInit class TCameraData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var near_plane: FastFloat;
|
||||
public var far_plane: FastFloat;
|
||||
public var fov: FastFloat;
|
||||
@:optional public var clear_color: Float32Array;
|
||||
@:optional public var aspect: Null<FastFloat>;
|
||||
@:optional public var frustum_culling: Null<Bool>;
|
||||
@:optional public var ortho: Float32Array; // Indicates ortho camera, left, right, bottom, top
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TMaterialData = {
|
||||
#else
|
||||
@:structInit class TMaterialData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var shader: String;
|
||||
public var contexts: Array<TMaterialContext>;
|
||||
@:optional public var skip_context: String;
|
||||
@:optional public var override_context: TShaderOverride;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TShaderOverride = {
|
||||
#else
|
||||
@:structInit class TShaderOverride {
|
||||
#end
|
||||
@:optional public var cull_mode: String;
|
||||
@:optional public var addressing: String;
|
||||
@:optional public var filter: String;
|
||||
@:optional public var shared_sampler: String;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TMaterialContext = {
|
||||
#else
|
||||
@:structInit class TMaterialContext {
|
||||
#end
|
||||
public var name: String;
|
||||
@:optional public var depth_read: Null<Bool>;
|
||||
@:optional public var bind_constants: Array<TBindConstant>;
|
||||
@:optional public var bind_textures: Array<TBindTexture>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TBindConstant = {
|
||||
#else
|
||||
@:structInit class TBindConstant {
|
||||
#end
|
||||
public var name: String;
|
||||
@:optional public var vec4Value: Float32Array;
|
||||
@:optional public var vec3Value: Float32Array;
|
||||
@:optional public var vec2Value: Float32Array;
|
||||
@:optional public var floatValue: Null<FastFloat>;
|
||||
@:optional public var boolValue: Null<Bool>;
|
||||
@:optional public var intValue: Null<Int>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TBindTexture = {
|
||||
#else
|
||||
@:structInit class TBindTexture {
|
||||
#end
|
||||
public var name: String;
|
||||
public var file: String;
|
||||
@:optional public var format: String; // RGBA32, RGBA64, R8
|
||||
@:optional public var generate_mipmaps: Null<Bool>;
|
||||
@:optional public var mipmaps: Array<String>; // Reference image names
|
||||
@:optional public var u_addressing: String;
|
||||
@:optional public var v_addressing: String;
|
||||
@:optional public var min_filter: String;
|
||||
@:optional public var mag_filter: String;
|
||||
@:optional public var mipmap_filter: String;
|
||||
@:optional public var source: String; // file, movie
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TShaderData = {
|
||||
#else
|
||||
@:structInit class TShaderData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var contexts: Array<TShaderContext>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TShaderContext = {
|
||||
#else
|
||||
@:structInit class TShaderContext {
|
||||
#end
|
||||
public var name: String;
|
||||
public var depth_write: Bool;
|
||||
public var compare_mode: String;
|
||||
public var cull_mode: String;
|
||||
public var vertex_elements: Array<TVertexElement>;
|
||||
public var vertex_shader: String;
|
||||
public var fragment_shader: String;
|
||||
@:optional public var geometry_shader: String;
|
||||
@:optional public var tesscontrol_shader: String;
|
||||
@:optional public var tesseval_shader: String;
|
||||
@:optional public var constants: Array<TShaderConstant>;
|
||||
@:optional public var texture_units: Array<TTextureUnit>;
|
||||
@:optional public var blend_source: String;
|
||||
@:optional public var blend_destination: String;
|
||||
@:optional public var blend_operation: String;
|
||||
@:optional public var alpha_blend_source: String;
|
||||
@:optional public var alpha_blend_destination: String;
|
||||
@:optional public var alpha_blend_operation: String;
|
||||
@:optional public var color_writes_red: Array<Bool>; // Per target masks
|
||||
@:optional public var color_writes_green: Array<Bool>;
|
||||
@:optional public var color_writes_blue: Array<Bool>;
|
||||
@:optional public var color_writes_alpha: Array<Bool>;
|
||||
@:optional public var color_attachments: Array<String>; // RGBA32, RGBA64, R8
|
||||
@:optional public var depth_attachment: String; // DEPTH32
|
||||
@:optional public var conservative_raster: Null<Bool>;
|
||||
@:optional public var shader_from_source: Null<Bool>; // Build shader at runtime using fromSource()
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TVertexElement = {
|
||||
#else
|
||||
@:structInit class TVertexElement {
|
||||
#end
|
||||
public var name: String;
|
||||
public var data: String; // "float4", "short2norm"
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TShaderConstant = {
|
||||
#else
|
||||
@:structInit class TShaderConstant {
|
||||
#end
|
||||
public var name: String;
|
||||
public var type: String;
|
||||
@:optional public var link: String;
|
||||
@:optional public var vec4Value: Float32Array;
|
||||
@:optional public var vec3Value: Float32Array;
|
||||
@:optional public var vec2Value: Float32Array;
|
||||
@:optional public var floatValue: Null<FastFloat>;
|
||||
@:optional public var boolValue: Null<Bool>;
|
||||
@:optional public var intValue: Null<Int>;
|
||||
@:optional public var is_lnx_parameter: Null<Bool>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TTextureUnit = {
|
||||
#else
|
||||
@:structInit class TTextureUnit {
|
||||
#end
|
||||
public var name: String;
|
||||
@:optional public var is_image: Null<Bool>; // image2D
|
||||
@:optional public var link: String;
|
||||
@:optional public var addressing_u: String;
|
||||
@:optional public var addressing_v: String;
|
||||
@:optional public var filter_min: String;
|
||||
@:optional public var filter_mag: String;
|
||||
@:optional public var mipmap_filter: String;
|
||||
@:optional public var default_image_file: String;
|
||||
@:optional public var is_lnx_parameter: Null<Bool>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TSpeakerData = {
|
||||
#else
|
||||
@:structInit class TSpeakerData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var sound: String;
|
||||
public var muted: Bool;
|
||||
public var loop: Bool;
|
||||
public var stream: Bool;
|
||||
public var volume: FastFloat;
|
||||
public var pitch: FastFloat;
|
||||
public var volume_min: FastFloat;
|
||||
public var volume_max: FastFloat;
|
||||
public var attenuation: FastFloat;
|
||||
public var distance_max: FastFloat;
|
||||
public var distance_reference: FastFloat;
|
||||
public var play_on_start: Bool;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TTerrainData = {
|
||||
#else
|
||||
@:structInit class TTerrainData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var sectors_x: Int;
|
||||
public var sectors_y: Int;
|
||||
public var sector_size: FastFloat;
|
||||
public var height_scale: FastFloat;
|
||||
public var material_ref: String;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TWorldData = {
|
||||
#else
|
||||
@:structInit class TWorldData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var background_color: Int;
|
||||
public var probe: TProbeData;
|
||||
@:optional public var sun_direction: Float32Array; // Sky data
|
||||
@:optional public var turbidity: Null<FastFloat>;
|
||||
@:optional public var ground_albedo: Null<FastFloat>;
|
||||
@:optional public var envmap: String;
|
||||
@:optional public var nishita_density: Float32Array; // Rayleigh, Mie, ozone
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TProbeData = {
|
||||
#else
|
||||
@:structInit class TProbeData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var type: String; // grid, planar, cubemap
|
||||
public var strength: FastFloat;
|
||||
@:optional public var irradiance: String; // Reference to TIrradiance blob
|
||||
@:optional public var radiance: String;
|
||||
@:optional public var radiance_mipmaps: Null<Int>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TTilesheetData = {
|
||||
#else
|
||||
@:structInit class TTilesheetData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var tilesx: Int;
|
||||
public var tilesy: Int;
|
||||
public var framerate: Int;
|
||||
public var actions: Array<TTilesheetAction>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TTilesheetAction = {
|
||||
#else
|
||||
@:structInit class TTilesheetAction {
|
||||
#end
|
||||
public var name: String;
|
||||
public var start: Int;
|
||||
public var end: Int;
|
||||
public var loop: Bool;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TParticleData = {
|
||||
#else
|
||||
@:structInit class TParticleData {
|
||||
#end
|
||||
public var name: String;
|
||||
public var type: Int; // 0 - Emitter, Hair
|
||||
public var loop: Bool;
|
||||
public var count: Int;
|
||||
public var frame_start: FastFloat;
|
||||
public var frame_end: FastFloat;
|
||||
public var lifetime: FastFloat;
|
||||
public var lifetime_random: FastFloat;
|
||||
public var emit_from: Int; // 0 - Vert, 1 - Face, 2 - Volume
|
||||
public var object_align_factor: Float32Array;
|
||||
public var factor_random: FastFloat;
|
||||
public var physics_type: Int; // 0 - No, 1 - Newton
|
||||
public var particle_size: FastFloat; // Object scale
|
||||
public var size_random: FastFloat; // Random scale
|
||||
public var mass: FastFloat;
|
||||
public var instance_object: String; // Object reference
|
||||
public var weight_gravity: FastFloat;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TParticleReference = {
|
||||
#else
|
||||
@:structInit class TParticleReference {
|
||||
#end
|
||||
public var name: String;
|
||||
public var particle: String;
|
||||
public var seed: Int;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TObj = {
|
||||
#else
|
||||
@:structInit class TObj {
|
||||
#end
|
||||
public var type: String; // object, mesh_object, light_object, camera_object, speaker_object, decal_object
|
||||
public var name: String;
|
||||
public var data_ref: String;
|
||||
public var transform: TTransform;
|
||||
@:optional public var material_refs: Array<String>;
|
||||
@:optional public var particle_refs: Array<TParticleReference>;
|
||||
@:optional public var render_emitter: Bool;
|
||||
@:optional public var is_particle: Null<Bool>; // This object is used as a particle object
|
||||
@:optional public var children: Array<TObj>;
|
||||
@:optional public var group_ref: String; // instance_type
|
||||
@:optional public var lods: Array<TLod>;
|
||||
@:optional public var lod_material: Null<Bool>;
|
||||
@:optional public var traits: Array<TTrait>;
|
||||
@:optional public var properties: Array<TProperty>;
|
||||
@:optional public var vertex_groups: Array<TVertex_groups>;
|
||||
@:optional public var constraints: Array<TConstraint>;
|
||||
@:optional public var dimensions: Float32Array; // Geometry objects
|
||||
@:optional public var object_actions: Array<String>;
|
||||
@:optional public var bone_actions: Array<String>;
|
||||
@:optional public var anim: TAnimation; // Bone/object animation
|
||||
@:optional public var parent: TObj;
|
||||
@:optional public var parent_bone: String;
|
||||
@:optional public var parent_bone_tail: Float32Array; // Translate from head to tail
|
||||
@:optional public var parent_bone_tail_pose: Float32Array;
|
||||
@:optional public var parent_bone_connected: Null<Bool>;
|
||||
@:optional public var bone_length: FastFloat;
|
||||
@:optional public var visible: Null<Bool>;
|
||||
@:optional public var visible_mesh: Null<Bool>;
|
||||
@:optional public var visible_shadow: Null<Bool>;
|
||||
@:optional public var mobile: Null<Bool>;
|
||||
@:optional public var spawn: Null<Bool>; // Auto add object when creating scene
|
||||
@:optional public var local_only: Null<Bool>; // Apply parent matrix
|
||||
@:optional public var tilesheet_ref: String;
|
||||
@:optional public var tilesheet_action_ref: String;
|
||||
@:optional public var sampled: Null<Bool>; // Object action
|
||||
@:optional public var is_ik_fk_only: Null<Bool>; // Bone IK or FK only
|
||||
@:optional public var bone_layers: Array<Bool>; // Bone Layer
|
||||
@:optional public var relative_bone_constraints: Null<Bool>; // Use parent relative bone constraints
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TProperty = {
|
||||
#else
|
||||
@:structInit class TProperty {
|
||||
#end
|
||||
public var name: String;
|
||||
public var value: Dynamic;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TVertex_groups = {
|
||||
#else
|
||||
@:structInit class TVertex_groups {
|
||||
#end
|
||||
public var name: String;
|
||||
public var value: Dynamic;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TGroup = {
|
||||
#else
|
||||
@:structInit class TGroup {
|
||||
#end
|
||||
public var name: String;
|
||||
public var instance_offset: Float32Array;
|
||||
public var object_refs: Array<String>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TLod = {
|
||||
#else
|
||||
@:structInit class TLod {
|
||||
#end
|
||||
public var object_ref: String; // Empty when limiting draw distance
|
||||
public var screen_size: FastFloat; // (0-1) size compared to lod0
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TConstraint = {
|
||||
#else
|
||||
@:structInit class TConstraint {
|
||||
#end
|
||||
public var name: String;
|
||||
public var type: String;
|
||||
@:optional public var bone: String; // Bone constraint
|
||||
@:optional public var target: String;
|
||||
@:optional public var use_x: Null<Bool>;
|
||||
@:optional public var use_y: Null<Bool>;
|
||||
@:optional public var use_z: Null<Bool>;
|
||||
@:optional public var invert_x: Null<Bool>;
|
||||
@:optional public var invert_y: Null<Bool>;
|
||||
@:optional public var invert_z: Null<Bool>;
|
||||
@:optional public var use_offset: Null<Bool>;
|
||||
@:optional public var influence: Null<FastFloat>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TTrait = {
|
||||
#else
|
||||
@:structInit class TTrait {
|
||||
#end
|
||||
public var type: String;
|
||||
public var class_name: String;
|
||||
@:optional public var parameters: Array<String>; // constructor params
|
||||
@:optional public var props: Array<Dynamic>; // name - type - value list
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TTransform = {
|
||||
#else
|
||||
@:structInit class TTransform {
|
||||
#end
|
||||
@:optional public var target: String;
|
||||
public var values: Float32Array;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TAnimation = {
|
||||
#else
|
||||
@:structInit class TAnimation {
|
||||
#end
|
||||
public var tracks: Array<TTrack>;
|
||||
@:optional public var begin: Null<Int>; // Frames, for non-sampled
|
||||
@:optional public var end: Null<Int>;
|
||||
@:optional public var has_delta: Null<Bool>; // Delta transform
|
||||
@:optional public var marker_frames: Uint32Array;
|
||||
@:optional public var marker_names: Array<String>;
|
||||
@:optional public var root_motion_pos: Null<Bool>;
|
||||
@:optional public var root_motion_rot: Null<Bool>;
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TAnimationTransform = {
|
||||
#else
|
||||
@:structInit class TAnimationTransform {
|
||||
#end
|
||||
public var type: String; // translation, translation_x, ...
|
||||
@:optional public var name: String;
|
||||
@:optional public var values: Float32Array; // translation
|
||||
@:optional public var value: Null<FastFloat>; // translation_x
|
||||
}
|
||||
|
||||
#if js
|
||||
typedef TTrack = {
|
||||
#else
|
||||
@:structInit class TTrack {
|
||||
#end
|
||||
public var target: String;
|
||||
public var frames: Uint32Array;
|
||||
public var values: Float32Array; // sampled - full matrix transforms, non-sampled - values
|
||||
@:optional public var ref_values: Array<Array<String>>; // ref values
|
||||
}
|
||||
124
leenkx/Sources/iron/data/SceneStream.hx
Normal file
124
leenkx/Sources/iron/data/SceneStream.hx
Normal file
@ -0,0 +1,124 @@
|
||||
package iron.data;
|
||||
|
||||
#if lnx_stream
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.data.MaterialData;
|
||||
import iron.object.Object;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.CameraObject;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class SceneStream {
|
||||
|
||||
var checkMax = 64; // Objects checked per frame
|
||||
var checkPos = 0;
|
||||
var loadMax = 8; // Max objects loaded at once
|
||||
var loading = 0; // Objects being loaded
|
||||
var loadDistance = -1;
|
||||
var unloadDistance = -1;
|
||||
var sectors: Array<StreamSector>; // 100x100 groups
|
||||
|
||||
public function sceneTotal(): Int {
|
||||
return sectors[0].handles.length;
|
||||
}
|
||||
|
||||
public function new() {
|
||||
sectors = [new StreamSector()];
|
||||
}
|
||||
|
||||
public function remove() {}
|
||||
|
||||
public function add(object_file: String, data_ref: String, sceneName: String, armature: Armature, materials: Vector<MaterialData>, parent: Object, parentObject:TObj, obj: TObj) {
|
||||
sectors[0].handles.push({ object_file: object_file, data_ref: data_ref, sceneName: sceneName, armature: armature, materials: materials, parent: parent, parentObject: parentObject, obj: obj, object: null, loading: false });
|
||||
}
|
||||
|
||||
function setup(camera: CameraObject) {
|
||||
loadDistance = Std.int(camera.data.raw.far_plane * 1.1);
|
||||
unloadDistance = Std.int(camera.data.raw.far_plane * 1.5);
|
||||
}
|
||||
|
||||
public function update(camera: CameraObject) {
|
||||
if (loadDistance == -1) setup(camera);
|
||||
|
||||
if (loading >= loadMax) return; // Busy loading..
|
||||
|
||||
var sec = sectors[0];
|
||||
var to = Std.int(Math.min(checkMax, sec.handles.length));
|
||||
for (i in 0...to) {
|
||||
|
||||
var h = sec.handles[checkPos];
|
||||
checkPos++;
|
||||
if (checkPos >= sec.handles.length) checkPos = 0;
|
||||
|
||||
// Check radius in sector
|
||||
var camX = camera.transform.worldx();
|
||||
var camY = camera.transform.worldy();
|
||||
var camZ = camera.transform.worldz();
|
||||
var hx = h.obj.transform.values[3];
|
||||
var hy = h.obj.transform.values[7];
|
||||
var hz = h.obj.transform.values[11];
|
||||
var cameraDistance = Vec4.distancef(camX, camY, camZ, hx, hy, hz);
|
||||
var dim = h.obj.dimensions;
|
||||
if (dim != null) {
|
||||
var r = dim[0];
|
||||
if (dim[1] > r) r = dim[1];
|
||||
if (dim[2] > r) r = dim[2];
|
||||
cameraDistance -= r;
|
||||
// TODO: handle scale & rot
|
||||
}
|
||||
|
||||
if (cameraDistance < loadDistance && h.object == null && !h.loading) { // Load mesh
|
||||
// Wait for the parent object to be added to scene
|
||||
if (h.parent == null) {
|
||||
if (Scene.active.getChild(h.parentObject.name) == null) return;
|
||||
h.parent = Scene.active.getChild(h.parentObject.name);
|
||||
}
|
||||
|
||||
// Start loading
|
||||
h.loading = true;
|
||||
loading++;
|
||||
iron.Scene.active.returnMeshObject(h.object_file, h.data_ref, h.sceneName, h.armature, h.materials, h.parent, h.parentObject, h.obj, function(object: Object) {
|
||||
h.object = cast(object, MeshObject);
|
||||
h.loading = false;
|
||||
loading--;
|
||||
});
|
||||
if (loading >= loadMax) return;
|
||||
}
|
||||
else if (cameraDistance > unloadDistance && h.object != null) { // Unload mesh
|
||||
// Remove objects
|
||||
h.object.remove();
|
||||
if (h.object.data.refcount <= 0) {
|
||||
iron.data.Data.deleteMesh(h.object_file + h.data_ref);
|
||||
}
|
||||
h.object = null;
|
||||
|
||||
// Clear parents
|
||||
if (h.parent.name != Scene.active.raw.name) {
|
||||
h.parent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef TMeshHandle = {
|
||||
var object_file: String;
|
||||
var data_ref: String;
|
||||
var sceneName: String;
|
||||
var armature: Armature;
|
||||
var materials: Vector<MaterialData>;
|
||||
var parent: Object;
|
||||
var parentObject: TObj;
|
||||
var obj: TObj;
|
||||
var object: MeshObject;
|
||||
var loading: Bool;
|
||||
}
|
||||
|
||||
class StreamSector {
|
||||
public function new() {}
|
||||
public var handles: Array<TMeshHandle> = []; // Mesh objects
|
||||
}
|
||||
|
||||
#end
|
||||
401
leenkx/Sources/iron/data/ShaderData.hx
Normal file
401
leenkx/Sources/iron/data/ShaderData.hx
Normal file
@ -0,0 +1,401 @@
|
||||
package iron.data;
|
||||
|
||||
import kha.graphics4.PipelineState;
|
||||
import kha.graphics4.ConstantLocation;
|
||||
import kha.graphics4.TextureUnit;
|
||||
import kha.graphics4.VertexStructure;
|
||||
import kha.graphics4.VertexData;
|
||||
import kha.graphics4.CompareMode;
|
||||
import kha.graphics4.CullMode;
|
||||
import kha.graphics4.BlendingOperation;
|
||||
import kha.graphics4.BlendingFactor;
|
||||
import kha.graphics4.TextureAddressing;
|
||||
import kha.graphics4.TextureFilter;
|
||||
import kha.graphics4.MipMapFilter;
|
||||
import kha.graphics4.VertexShader;
|
||||
import kha.graphics4.FragmentShader;
|
||||
import kha.graphics4.TextureFormat;
|
||||
import kha.graphics4.DepthStencilFormat;
|
||||
import iron.data.SceneFormat;
|
||||
using StringTools;
|
||||
|
||||
class ShaderData {
|
||||
|
||||
public var name: String;
|
||||
public var raw: TShaderData;
|
||||
public var contexts: Array<ShaderContext> = [];
|
||||
|
||||
#if (lnx_noembed && kha_krom)
|
||||
public static var shaderPath = "../krom-resources/";
|
||||
public static inline var shaderExt = #if kha_vulkan ".spirv" #elseif (krom_android || krom_wasm) ".essl" #elseif kha_opengl ".glsl" #elseif kha_metal ".metal" #else ".d3d11" #end ;
|
||||
#end
|
||||
|
||||
public function new(raw: TShaderData, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
|
||||
for (c in raw.contexts) contexts.push(null);
|
||||
var contextsLoaded = 0;
|
||||
|
||||
for (i in 0...raw.contexts.length) {
|
||||
var c = raw.contexts[i];
|
||||
new ShaderContext(c, function(con: ShaderContext) {
|
||||
contexts[i] = con;
|
||||
contextsLoaded++;
|
||||
if (contextsLoaded == raw.contexts.length) done(this);
|
||||
}, overrideContext);
|
||||
}
|
||||
}
|
||||
|
||||
public static function parse(file: String, name: String, done: ShaderData->Void, overrideContext: TShaderOverride = null) {
|
||||
Data.getSceneRaw(file, function(format: TSceneFormat) {
|
||||
var raw: TShaderData = Data.getShaderRawByName(format.shader_datas, name);
|
||||
if (raw == null) {
|
||||
trace('Shader data "$name" not found!');
|
||||
done(null);
|
||||
}
|
||||
new ShaderData(raw, done, overrideContext);
|
||||
});
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
for (c in contexts) c.delete();
|
||||
}
|
||||
|
||||
public function getContext(name: String): ShaderContext {
|
||||
for (c in contexts) if (c.raw.name == name) return c;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class ShaderContext {
|
||||
public var raw: TShaderContext;
|
||||
public var pipeState: PipelineState;
|
||||
public var constants: Array<ConstantLocation>;
|
||||
public var textureUnits: Array<TextureUnit>;
|
||||
public var overrideContext: TShaderOverride;
|
||||
|
||||
var structure: VertexStructure;
|
||||
var instancingType = 0;
|
||||
|
||||
public function new(raw: TShaderContext, done: ShaderContext->Void, overrideContext: TShaderOverride = null) {
|
||||
this.raw = raw;
|
||||
#if (!rp_voxels)
|
||||
if (raw.name == "voxel") {
|
||||
done(this);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
this.overrideContext = overrideContext;
|
||||
parseVertexStructure();
|
||||
compile(done);
|
||||
}
|
||||
|
||||
public function compile(done: ShaderContext->Void) {
|
||||
if (pipeState != null) pipeState.delete();
|
||||
pipeState = new PipelineState();
|
||||
constants = [];
|
||||
textureUnits = [];
|
||||
|
||||
if (instancingType > 0) { // Instancing
|
||||
var instStruct = new VertexStructure();
|
||||
instStruct.add("ipos", VertexData.Float3);
|
||||
if (instancingType == 2 || instancingType == 4) {
|
||||
instStruct.add("irot", VertexData.Float3);
|
||||
}
|
||||
if (instancingType == 3 || instancingType == 4) {
|
||||
instStruct.add("iscl", VertexData.Float3);
|
||||
}
|
||||
instStruct.instanced = true;
|
||||
pipeState.inputLayout = [structure, instStruct];
|
||||
}
|
||||
else { // Regular
|
||||
pipeState.inputLayout = [structure];
|
||||
}
|
||||
|
||||
// Depth
|
||||
pipeState.depthWrite = raw.depth_write;
|
||||
pipeState.depthMode = getCompareMode(raw.compare_mode);
|
||||
|
||||
// Cull
|
||||
pipeState.cullMode = getCullMode(raw.cull_mode);
|
||||
|
||||
// Blending
|
||||
if (raw.blend_source != null) pipeState.blendSource = getBlendingFactor(raw.blend_source);
|
||||
if (raw.blend_destination != null) pipeState.blendDestination = getBlendingFactor(raw.blend_destination);
|
||||
if (raw.blend_operation != null) pipeState.blendOperation = getBlendingOperation(raw.blend_operation);
|
||||
if (raw.alpha_blend_source != null) pipeState.alphaBlendSource = getBlendingFactor(raw.alpha_blend_source);
|
||||
if (raw.alpha_blend_destination != null) pipeState.alphaBlendDestination = getBlendingFactor(raw.alpha_blend_destination);
|
||||
if (raw.alpha_blend_operation != null) pipeState.alphaBlendOperation = getBlendingOperation(raw.alpha_blend_operation);
|
||||
|
||||
// Per-target color write mask
|
||||
if (raw.color_writes_red != null) for (i in 0...raw.color_writes_red.length) pipeState.colorWriteMasksRed[i] = raw.color_writes_red[i];
|
||||
if (raw.color_writes_green != null) for (i in 0...raw.color_writes_green.length) pipeState.colorWriteMasksGreen[i] = raw.color_writes_green[i];
|
||||
if (raw.color_writes_blue != null) for (i in 0...raw.color_writes_blue.length) pipeState.colorWriteMasksBlue[i] = raw.color_writes_blue[i];
|
||||
if (raw.color_writes_alpha != null) for (i in 0...raw.color_writes_alpha.length) pipeState.colorWriteMasksAlpha[i] = raw.color_writes_alpha[i];
|
||||
|
||||
// Color attachment format
|
||||
if (raw.color_attachments != null) {
|
||||
pipeState.colorAttachmentCount = raw.color_attachments.length;
|
||||
for (i in 0...raw.color_attachments.length) pipeState.colorAttachments[i] = getTextureFormat(raw.color_attachments[i]);
|
||||
}
|
||||
|
||||
// Depth attachment format
|
||||
if (raw.depth_attachment != null) {
|
||||
#if (krom_windows || krom_linux || krom_darwin || krom_android || krom_ios)
|
||||
pipeState.depthStencilAttachment = getDepthStencilFormat(raw.depth_attachment);
|
||||
#end
|
||||
}
|
||||
|
||||
// Conservative raster for voxelization
|
||||
if (raw.conservative_raster != null) pipeState.conservativeRasterization = raw.conservative_raster;
|
||||
|
||||
// Shaders
|
||||
if (raw.shader_from_source) {
|
||||
pipeState.vertexShader = VertexShader.fromSource(raw.vertex_shader);
|
||||
pipeState.fragmentShader = FragmentShader.fromSource(raw.fragment_shader);
|
||||
|
||||
#if kha_krom
|
||||
// Shader compile error
|
||||
if (pipeState.vertexShader.shader == null || pipeState.fragmentShader.shader == null) {
|
||||
done(null);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
finishCompile(done);
|
||||
}
|
||||
else {
|
||||
|
||||
#if (lnx_noembed && kha_krom) // Load shaders manually
|
||||
|
||||
var shadersLoaded = 0;
|
||||
var numShaders = 2;
|
||||
if (raw.geometry_shader != null) numShaders++;
|
||||
if (raw.tesscontrol_shader != null) numShaders++;
|
||||
if (raw.tesseval_shader != null) numShaders++;
|
||||
|
||||
function loadShader(file: String, type: Int) {
|
||||
var path = ShaderData.shaderPath + file + ShaderData.shaderExt;
|
||||
Data.getBlob(path, function(b: kha.Blob) {
|
||||
if (type == 0) pipeState.vertexShader = new VertexShader([b], [file]);
|
||||
else if (type == 1) pipeState.fragmentShader = new FragmentShader([b], [file]);
|
||||
else if (type == 2) pipeState.geometryShader = new kha.graphics4.GeometryShader([b], [file]);
|
||||
else if (type == 3) pipeState.tessellationControlShader = new kha.graphics4.TessellationControlShader([b], [file]);
|
||||
else if (type == 4) pipeState.tessellationEvaluationShader = new kha.graphics4.TessellationEvaluationShader([b], [file]);
|
||||
shadersLoaded++;
|
||||
if (shadersLoaded >= numShaders) finishCompile(done);
|
||||
});
|
||||
}
|
||||
loadShader(raw.vertex_shader, 0);
|
||||
loadShader(raw.fragment_shader, 1);
|
||||
if (raw.geometry_shader != null) loadShader(raw.geometry_shader, 2);
|
||||
if (raw.tesscontrol_shader != null) loadShader(raw.tesscontrol_shader, 3);
|
||||
if (raw.tesseval_shader != null) loadShader(raw.tesseval_shader, 4);
|
||||
|
||||
#elseif lnx_shader_embed
|
||||
|
||||
pipeState.fragmentShader = kha.Shaders.getFragment(raw.fragment_shader);
|
||||
pipeState.vertexShader = kha.Shaders.getVertex(raw.vertex_shader);
|
||||
if (raw.geometry_shader != null) {
|
||||
pipeState.geometryShader = kha.Shaders.getGeometry(raw.geometry_shader);
|
||||
}
|
||||
finishCompile(done);
|
||||
|
||||
#else
|
||||
|
||||
pipeState.fragmentShader = Reflect.field(kha.Shaders, raw.fragment_shader.replace(".", "_"));
|
||||
pipeState.vertexShader = Reflect.field(kha.Shaders, raw.vertex_shader.replace(".", "_"));
|
||||
|
||||
if (raw.geometry_shader != null) {
|
||||
pipeState.geometryShader = Reflect.field(kha.Shaders, raw.geometry_shader.replace(".", "_"));
|
||||
}
|
||||
if (raw.tesscontrol_shader != null) {
|
||||
pipeState.tessellationControlShader = Reflect.field(kha.Shaders, raw.tesscontrol_shader.replace(".", "_"));
|
||||
}
|
||||
if (raw.tesseval_shader != null) {
|
||||
pipeState.tessellationEvaluationShader = Reflect.field(kha.Shaders, raw.tesseval_shader.replace(".", "_"));
|
||||
}
|
||||
finishCompile(done);
|
||||
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
function finishCompile(done: ShaderContext->Void) {
|
||||
// Override specified values
|
||||
if (overrideContext != null) {
|
||||
if (overrideContext.cull_mode != null) {
|
||||
pipeState.cullMode = getCullMode(overrideContext.cull_mode);
|
||||
}
|
||||
}
|
||||
|
||||
pipeState.compile();
|
||||
|
||||
if (raw.constants != null) {
|
||||
for (c in raw.constants) addConstant(c);
|
||||
}
|
||||
|
||||
if (raw.texture_units != null) {
|
||||
for (tu in raw.texture_units) addTexture(tu);
|
||||
}
|
||||
|
||||
done(this);
|
||||
}
|
||||
|
||||
public static function parseData(data: String): VertexData {
|
||||
if (data == "float1") return VertexData.Float1;
|
||||
else if (data == "float2") return VertexData.Float2;
|
||||
else if (data == "float3") return VertexData.Float3;
|
||||
else if (data == "float4") return VertexData.Float4;
|
||||
else if (data == "short2norm") return VertexData.Short2Norm;
|
||||
else if (data == "short4norm") return VertexData.Short4Norm;
|
||||
return VertexData.Float1;
|
||||
}
|
||||
|
||||
function parseVertexStructure() {
|
||||
structure = new VertexStructure();
|
||||
var ipos = false;
|
||||
var irot = false;
|
||||
var iscl = false;
|
||||
for (elem in raw.vertex_elements) {
|
||||
#if cpp
|
||||
if (Reflect.field(elem, "name") == "ipos") { ipos = true; continue; }
|
||||
if (Reflect.field(elem, "name") == "irot") { irot = true; continue; }
|
||||
if (Reflect.field(elem, "name") == "iscl") { iscl = true; continue; }
|
||||
#else
|
||||
if (elem.name == "ipos") { ipos = true; continue; }
|
||||
if (elem.name == "irot") { irot = true; continue; }
|
||||
if (elem.name == "iscl") { iscl = true; continue; }
|
||||
#end
|
||||
structure.add(elem.name, parseData(elem.data));
|
||||
}
|
||||
if (ipos && !irot && !iscl) instancingType = 1;
|
||||
else if (ipos && irot && !iscl) instancingType = 2;
|
||||
else if (ipos && !irot && iscl) instancingType = 3;
|
||||
else if (ipos && irot && iscl) instancingType = 4;
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
if (pipeState.fragmentShader != null) pipeState.fragmentShader.delete();
|
||||
if (pipeState.vertexShader != null) pipeState.vertexShader.delete();
|
||||
if (pipeState.geometryShader != null) pipeState.geometryShader.delete();
|
||||
if (pipeState.tessellationControlShader != null) pipeState.tessellationControlShader.delete();
|
||||
if (pipeState.tessellationEvaluationShader != null) pipeState.tessellationEvaluationShader.delete();
|
||||
pipeState.delete();
|
||||
}
|
||||
|
||||
function getCompareMode(s: String): CompareMode {
|
||||
switch (s) {
|
||||
case "always": return CompareMode.Always;
|
||||
case "never": return CompareMode.Never;
|
||||
case "less": return CompareMode.Less;
|
||||
case "less_equal": return CompareMode.LessEqual;
|
||||
case "greater": return CompareMode.Greater;
|
||||
case "greater_equal": return CompareMode.GreaterEqual;
|
||||
case "equal": return CompareMode.Equal;
|
||||
case "not_equal": return CompareMode.NotEqual;
|
||||
default: return CompareMode.Less;
|
||||
}
|
||||
}
|
||||
|
||||
function getCullMode(s: String): CullMode {
|
||||
switch (s) {
|
||||
case "none": return CullMode.None;
|
||||
case "clockwise": return CullMode.Clockwise;
|
||||
default: return CullMode.CounterClockwise;
|
||||
}
|
||||
}
|
||||
|
||||
function getBlendingOperation(s: String): BlendingOperation {
|
||||
switch (s) {
|
||||
case "add": return BlendingOperation.Add;
|
||||
case "subtract": return BlendingOperation.Subtract;
|
||||
case "reverse_subtract": return BlendingOperation.ReverseSubtract;
|
||||
case "min": return BlendingOperation.Min;
|
||||
case "max": return BlendingOperation.Max;
|
||||
default: return BlendingOperation.Add;
|
||||
}
|
||||
}
|
||||
|
||||
function getBlendingFactor(s: String): BlendingFactor {
|
||||
switch (s) {
|
||||
case "blend_one": return BlendingFactor.BlendOne;
|
||||
case "blend_zero": return BlendingFactor.BlendZero;
|
||||
case "source_alpha": return BlendingFactor.SourceAlpha;
|
||||
case "destination_alpha": return BlendingFactor.DestinationAlpha;
|
||||
case "inverse_source_alpha": return BlendingFactor.InverseSourceAlpha;
|
||||
case "inverse_destination_alpha": return BlendingFactor.InverseDestinationAlpha;
|
||||
case "source_color": return BlendingFactor.SourceColor;
|
||||
case "destination_color": return BlendingFactor.DestinationColor;
|
||||
case "inverse_source_color": return BlendingFactor.InverseSourceColor;
|
||||
case "inverse_destination_color": return BlendingFactor.InverseDestinationColor;
|
||||
default: return BlendingFactor.Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getTextureAddresing(s: String): TextureAddressing {
|
||||
switch (s) {
|
||||
case "repeat": return TextureAddressing.Repeat;
|
||||
case "mirror": return TextureAddressing.Mirror;
|
||||
default: return TextureAddressing.Clamp;
|
||||
}
|
||||
}
|
||||
|
||||
function getTextureFilter(s: String): TextureFilter {
|
||||
switch (s) {
|
||||
case "point": return TextureFilter.PointFilter;
|
||||
case "linear": return TextureFilter.LinearFilter;
|
||||
default: return TextureFilter.AnisotropicFilter;
|
||||
}
|
||||
}
|
||||
|
||||
function getMipmapFilter(s: String): MipMapFilter {
|
||||
switch (s) {
|
||||
case "no": return MipMapFilter.NoMipFilter;
|
||||
case "point": return MipMapFilter.PointMipFilter;
|
||||
default: return MipMapFilter.LinearMipFilter;
|
||||
}
|
||||
}
|
||||
|
||||
function getTextureFormat(s: String): TextureFormat {
|
||||
switch (s) {
|
||||
case "RGBA32": return TextureFormat.RGBA32;
|
||||
case "RGBA64": return TextureFormat.RGBA64;
|
||||
case "RGBA128": return TextureFormat.RGBA128;
|
||||
case "DEPTH16": return TextureFormat.DEPTH16;
|
||||
case "R32": return TextureFormat.A32;
|
||||
case "R16": return TextureFormat.A16;
|
||||
case "R8": return TextureFormat.L8;
|
||||
default: return TextureFormat.RGBA32;
|
||||
}
|
||||
}
|
||||
|
||||
function getDepthStencilFormat(s: String): DepthStencilFormat {
|
||||
switch (s) {
|
||||
case "DEPTH32": return DepthStencilFormat.DepthOnly;
|
||||
case "NONE": return DepthStencilFormat.NoDepthAndStencil;
|
||||
default: return DepthStencilFormat.DepthOnly;
|
||||
}
|
||||
}
|
||||
|
||||
function addConstant(c: TShaderConstant) {
|
||||
constants.push(pipeState.getConstantLocation(c.name));
|
||||
}
|
||||
|
||||
function addTexture(tu: TTextureUnit) {
|
||||
var unit = pipeState.getTextureUnit(tu.name);
|
||||
textureUnits.push(unit);
|
||||
}
|
||||
|
||||
public function setTextureParameters(g: kha.graphics4.Graphics, unitIndex: Int, tex: TBindTexture) {
|
||||
// This function is called for samplers set using material context
|
||||
var unit = textureUnits[unitIndex];
|
||||
g.setTextureParameters(unit,
|
||||
tex.u_addressing == null ? TextureAddressing.Repeat : getTextureAddresing(tex.u_addressing),
|
||||
tex.v_addressing == null ? TextureAddressing.Repeat : getTextureAddresing(tex.v_addressing),
|
||||
tex.min_filter == null ? TextureFilter.LinearFilter : getTextureFilter(tex.min_filter),
|
||||
tex.mag_filter == null ? TextureFilter.LinearFilter : getTextureFilter(tex.mag_filter),
|
||||
tex.mipmap_filter == null ? MipMapFilter.NoMipFilter : getMipmapFilter(tex.mipmap_filter));
|
||||
}
|
||||
}
|
||||
157
leenkx/Sources/iron/data/TerrainStream.hx
Normal file
157
leenkx/Sources/iron/data/TerrainStream.hx
Normal file
@ -0,0 +1,157 @@
|
||||
package iron.data;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import kha.arrays.Int16Array;
|
||||
import kha.arrays.Uint32Array;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.object.Object;
|
||||
import iron.object.CameraObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Uniforms;
|
||||
import iron.Scene;
|
||||
|
||||
#if lnx_terrain
|
||||
|
||||
class TerrainStream {
|
||||
|
||||
public var sectors: Array<MeshObject> = [];
|
||||
public var heightTextures: Array<kha.Image> = [];
|
||||
public var ready = false;
|
||||
public var onReady: Void->Void = null;
|
||||
|
||||
var raw: TTerrainData;
|
||||
var planes: Array<MeshData> = [];
|
||||
var materials: Vector<MaterialData>;
|
||||
|
||||
public function new(raw: TTerrainData) {
|
||||
this.raw = raw;
|
||||
|
||||
Data.getMaterial(Scene.active.raw.name, raw.material_ref, function(mat: MaterialData) {
|
||||
materials = Vector.fromData([mat]);
|
||||
|
||||
var imagesLoaded = 0;
|
||||
var numSectors = raw.sectors_x * raw.sectors_y;
|
||||
for (i in 0...numSectors) {
|
||||
var j = i + 1;
|
||||
var ext = j < 10 ? "0" + j : "" + j;
|
||||
Data.getImage("heightmap_" + ext + ".png", function(image: kha.Image) {
|
||||
heightTextures[i] = image;
|
||||
imagesLoaded++;
|
||||
if (imagesLoaded == numSectors) {
|
||||
loaded();
|
||||
}
|
||||
}, true); // Readable
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function notifyOnReady(f: Void->Void) {
|
||||
onReady = f;
|
||||
if (ready) onReady();
|
||||
}
|
||||
|
||||
function loaded() {
|
||||
for (i in 0...4) {
|
||||
makePlane(i, raw.sector_size, raw.sector_size, heightTextures[0].width, heightTextures[0].height);
|
||||
}
|
||||
|
||||
for (i in 0...raw.sectors_x * raw.sectors_y) {
|
||||
makeSector(i);
|
||||
}
|
||||
|
||||
iron.App.notifyOnInit(function() {
|
||||
Uniforms.externalTextureLinks.push(textureLink);
|
||||
});
|
||||
|
||||
ready = true;
|
||||
if (onReady != null) onReady();
|
||||
}
|
||||
|
||||
function makePlane(index: Int, sizeX: Float, sizeY: Float, vertsX: Int, vertsY: Int) {
|
||||
// Pack positions to (-1, 1) range
|
||||
var halfX = sizeX / 2;
|
||||
var halfY = sizeY / 2;
|
||||
var halfZ = raw.height_scale / 2;
|
||||
var scalePos = Math.max(halfX, Math.max(halfY, halfZ));
|
||||
var inv = 1 / scalePos;
|
||||
|
||||
var posa = new Int16Array(vertsX * vertsY * 4);
|
||||
var nora = new Int16Array(vertsX * vertsY * 2);
|
||||
var texa = new Int16Array(vertsX * vertsY * 2);
|
||||
var inda = new Uint32Array((vertsX - 1) * (vertsY - 1) * 6);
|
||||
var stepX = sizeX / (vertsX - 1);
|
||||
var stepY = sizeY / (vertsY - 1);
|
||||
for (i in 0...vertsX * vertsY) {
|
||||
var x = (i % vertsX) * stepX - halfX;
|
||||
var y = Std.int(i / vertsX) * stepY - halfY;
|
||||
posa[i * 4 ] = Std.int(x * 32767 * inv);
|
||||
posa[i * 4 + 1] = Std.int(y * 32767 * inv);
|
||||
posa[i * 4 + 2] = Std.int(-halfZ * 32767 * inv);
|
||||
nora[i * 2 ] = 0;
|
||||
nora[i * 2 + 1] = 0;
|
||||
posa[i * 4 + 3] = 32767;
|
||||
x = (i % vertsX) / vertsX;
|
||||
y = (Std.int(i / vertsX)) / vertsY;
|
||||
texa[i * 2 ] = Std.int(x * 32767);
|
||||
texa[i * 2 + 1] = Std.int(y * 32767);
|
||||
}
|
||||
for (i in 0...(vertsX - 1) * (vertsY - 1)) {
|
||||
var x = i % (vertsX - 1);
|
||||
var y = Std.int(i / (vertsY - 1));
|
||||
inda[i * 6 ] = y * vertsX + x;
|
||||
inda[i * 6 + 1] = y * vertsX + x + 1;
|
||||
inda[i * 6 + 2] = (y + 1) * vertsX + x;
|
||||
inda[i * 6 + 3] = y * vertsX + x + 1;
|
||||
inda[i * 6 + 4] = (y + 1) * vertsX + x + 1;
|
||||
inda[i * 6 + 5] = (y + 1) * vertsX + x;
|
||||
}
|
||||
|
||||
// Positions, normals and indices
|
||||
var pos: TVertexArray = { attrib: "pos", values: posa, data: "short4norm" };
|
||||
var nor: TVertexArray = { attrib: "nor", values: nora, data: "short2norm" };
|
||||
var tex: TVertexArray = { attrib: "tex", values: texa, data: "short2norm" };
|
||||
var ind: TIndexArray = { material: 0, values: inda };
|
||||
|
||||
var rawmeshData: TMeshData = {
|
||||
name: "Terrain",
|
||||
vertex_arrays: [pos, nor, tex],
|
||||
index_arrays: [ind],
|
||||
scale_pos: scalePos,
|
||||
scale_tex: 1.0
|
||||
};
|
||||
|
||||
new MeshData(rawmeshData, function(data: MeshData) {
|
||||
planes[index] = data;
|
||||
data.geom.calculateAABB();
|
||||
});
|
||||
}
|
||||
|
||||
function makeSector(index: Int) {
|
||||
var object = Scene.active.addMeshObject(planes[0], materials);
|
||||
sectors[index] = object;
|
||||
object.uid = index;
|
||||
object.name = "Terrain." + index;
|
||||
object.transform.loc.x = (index % raw.sectors_x) * 2;
|
||||
object.transform.loc.y = Std.int(index / raw.sectors_x) * 2;
|
||||
object.transform.loc.z = 0;
|
||||
object.transform.buildMatrix();
|
||||
object.transform.dim.x = raw.sector_size;
|
||||
object.transform.dim.y = raw.sector_size;
|
||||
object.transform.dim.z = raw.height_scale;
|
||||
}
|
||||
|
||||
public function remove() {}
|
||||
|
||||
public function update(camera: CameraObject) {
|
||||
if (!ready) return;
|
||||
}
|
||||
|
||||
function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
|
||||
if (link == "_TerrainHeight") {
|
||||
return heightTextures[object.uid];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
52
leenkx/Sources/iron/data/Wasm.hx
Normal file
52
leenkx/Sources/iron/data/Wasm.hx
Normal file
@ -0,0 +1,52 @@
|
||||
package iron.data;
|
||||
|
||||
#if js
|
||||
|
||||
class Wasm {
|
||||
|
||||
public var exports: Dynamic;
|
||||
|
||||
public static inline function instance(blob: kha.Blob, importObject: Dynamic = null): Wasm {
|
||||
var data = blob.toBytes().getData();
|
||||
var module = new js.lib.webassembly.Module(data);
|
||||
var exports: Dynamic = importObject == null ?
|
||||
new js.lib.webassembly.Instance(module).exports :
|
||||
new js.lib.webassembly.Instance(module, importObject).exports;
|
||||
return new Wasm(exports);
|
||||
}
|
||||
|
||||
#if kha_html5_js
|
||||
public static function instantiateStreaming(blob: kha.Blob, importObject: Dynamic = null, done: Wasm->Void) {
|
||||
js.lib.WebAssembly.instantiateStreaming(new js.html.Response(blob.toBytes().getData(), {
|
||||
headers: new js.html.Headers({"Content-Type": "application/wasm"})
|
||||
}), importObject).then(m -> done(new Wasm(m.instance.exports)));
|
||||
}
|
||||
#end
|
||||
|
||||
function new(exports: Dynamic) {
|
||||
this.exports = exports;
|
||||
}
|
||||
|
||||
public function getString(i: Int): String { // Retrieve string from memory pointer
|
||||
var mem = getMemory(i, 32);
|
||||
var s = "";
|
||||
for (i in 0...32) {
|
||||
mem[i] == 0 ? break : s += String.fromCharCode(mem[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public inline function getMemory(offset: Int, length: Int): js.lib.Uint8Array {
|
||||
return new js.lib.Uint8Array(exports.memory.buffer, offset, length);
|
||||
}
|
||||
|
||||
public inline function getMemoryF32(offset: Int, length: Int): kha.arrays.Float32Array {
|
||||
return new kha.arrays.Float32Array(exports.memory.buffer).subarray( offset, length );
|
||||
}
|
||||
|
||||
public inline function getMemoryU32(offset: Int, length: Int): kha.arrays.Uint32Array {
|
||||
return new kha.arrays.Uint32Array(exports.memory.buffer).subarray(offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
128
leenkx/Sources/iron/data/WorldData.hx
Normal file
128
leenkx/Sources/iron/data/WorldData.hx
Normal file
@ -0,0 +1,128 @@
|
||||
package iron.data;
|
||||
|
||||
import haxe.Json;
|
||||
import kha.arrays.Float32Array;
|
||||
import iron.math.Vec4;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.LnxPack;
|
||||
using StringTools;
|
||||
|
||||
class WorldData {
|
||||
|
||||
public var name: String;
|
||||
public var raw: TWorldData;
|
||||
public var envmap: kha.Image;
|
||||
public var probe: Probe;
|
||||
|
||||
static var emptyIrr: Float32Array = null;
|
||||
|
||||
public function new(raw: TWorldData, done: WorldData->Void) {
|
||||
this.raw = raw;
|
||||
this.name = raw.name;
|
||||
|
||||
// Parse probes
|
||||
if (raw.probe != null) {
|
||||
new Probe(raw.probe, function(self: Probe) {
|
||||
probe = self;
|
||||
#if lnx_skip_envmap
|
||||
done(this);
|
||||
#else
|
||||
loadEnvmap(done);
|
||||
#end
|
||||
});
|
||||
}
|
||||
else {
|
||||
#if lnx_skip_envmap
|
||||
done(this);
|
||||
#else
|
||||
loadEnvmap(done);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
public function loadEnvmap(done: WorldData->Void) {
|
||||
if (raw.envmap != null) {
|
||||
Data.getImage(raw.envmap, function(image: kha.Image) {
|
||||
envmap = image;
|
||||
done(this);
|
||||
});
|
||||
}
|
||||
else done(this);
|
||||
}
|
||||
|
||||
public static function parse(name: String, id: String, done: WorldData->Void) {
|
||||
Data.getSceneRaw(name, function(format: TSceneFormat) {
|
||||
var raw: TWorldData = Data.getWorldRawByName(format.world_datas, id);
|
||||
if (raw == null) {
|
||||
trace('World data "$id" not found!');
|
||||
done(null);
|
||||
}
|
||||
new WorldData(raw, done);
|
||||
});
|
||||
}
|
||||
|
||||
public static function getEmptyIrradiance(): Float32Array {
|
||||
if (emptyIrr == null) {
|
||||
emptyIrr = new Float32Array(28);
|
||||
for (i in 0...emptyIrr.length) emptyIrr.set(i, 0.0);
|
||||
}
|
||||
return emptyIrr;
|
||||
}
|
||||
}
|
||||
|
||||
class Probe {
|
||||
|
||||
public var raw: TProbeData;
|
||||
public var radiance: kha.Image;
|
||||
public var radianceMipmaps: Array<kha.Image> = [];
|
||||
public var irradiance: Float32Array;
|
||||
|
||||
public function new(raw: TProbeData, done: Probe->Void) {
|
||||
this.raw = raw;
|
||||
|
||||
setIrradiance(function(irr: Float32Array) {
|
||||
irradiance = irr;
|
||||
if (raw.radiance != null) {
|
||||
Data.getImage(raw.radiance, function(rad: kha.Image) {
|
||||
radiance = rad;
|
||||
while (radianceMipmaps.length < raw.radiance_mipmaps) radianceMipmaps.push(null);
|
||||
var dot = raw.radiance.lastIndexOf(".");
|
||||
var ext = raw.radiance.substring(dot);
|
||||
var base = raw.radiance.substring(0, dot);
|
||||
|
||||
var mipsLoaded = 0;
|
||||
for (i in 0...raw.radiance_mipmaps) {
|
||||
Data.getImage(base + "_" + i + ext, function(mipimg: kha.Image) {
|
||||
radianceMipmaps[i] = mipimg;
|
||||
mipsLoaded++;
|
||||
|
||||
if (mipsLoaded == raw.radiance_mipmaps) {
|
||||
radiance.setMipmaps(radianceMipmaps);
|
||||
done(this);
|
||||
}
|
||||
}, true); // Readable
|
||||
}
|
||||
});
|
||||
}
|
||||
else done(this);
|
||||
});
|
||||
}
|
||||
|
||||
function setIrradiance(done: Float32Array->Void) {
|
||||
// Parse probe data
|
||||
if (raw.irradiance == null) {
|
||||
done(WorldData.getEmptyIrradiance());
|
||||
}
|
||||
else {
|
||||
var ext = raw.irradiance.endsWith(".json") ? "" : ".lnx";
|
||||
Data.getBlob(raw.irradiance + ext, function(b: kha.Blob) {
|
||||
var irradianceParsed: TSceneFormat = ext == "" ?
|
||||
Json.parse(b.toString()) :
|
||||
LnxPack.decode(b.toBytes());
|
||||
var irr = new Float32Array(28); // Align to mult of 4 - 27->28
|
||||
for (i in 0...27) irr[i] = irradianceParsed.irradiance[i];
|
||||
done(irr);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
44
leenkx/Sources/iron/math/Mat3.hx
Normal file
44
leenkx/Sources/iron/math/Mat3.hx
Normal file
@ -0,0 +1,44 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
class Mat3 {
|
||||
|
||||
public var self: kha.math.FastMatrix3;
|
||||
|
||||
public inline function new(_00: FastFloat, _10: FastFloat, _20: FastFloat,
|
||||
_01: FastFloat, _11: FastFloat, _21: FastFloat,
|
||||
_02: FastFloat, _12: FastFloat, _22: FastFloat) {
|
||||
self = new kha.math.FastMatrix3(_00, _10, _20, _01, _11, _21, _02, _12, _22);
|
||||
}
|
||||
|
||||
public static inline function identity(): Mat3 {
|
||||
return new Mat3(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
public inline function setFrom4(m: Mat4) {
|
||||
_00 = m._00;
|
||||
_01 = m._01;
|
||||
_02 = m._02;
|
||||
_10 = m._10;
|
||||
_11 = m._11;
|
||||
_12 = m._12;
|
||||
_20 = m._20;
|
||||
_21 = m._21;
|
||||
_22 = m._22;
|
||||
}
|
||||
|
||||
public var _00(get, set): FastFloat; inline function get__00(): FastFloat { return self._00; } inline function set__00(f: FastFloat): FastFloat { return self._00 = f; }
|
||||
public var _01(get, set): FastFloat; inline function get__01(): FastFloat { return self._01; } inline function set__01(f: FastFloat): FastFloat { return self._01 = f; }
|
||||
public var _02(get, set): FastFloat; inline function get__02(): FastFloat { return self._02; } inline function set__02(f: FastFloat): FastFloat { return self._02 = f; }
|
||||
public var _10(get, set): FastFloat; inline function get__10(): FastFloat { return self._10; } inline function set__10(f: FastFloat): FastFloat { return self._10 = f; }
|
||||
public var _11(get, set): FastFloat; inline function get__11(): FastFloat { return self._11; } inline function set__11(f: FastFloat): FastFloat { return self._11 = f; }
|
||||
public var _12(get, set): FastFloat; inline function get__12(): FastFloat { return self._12; } inline function set__12(f: FastFloat): FastFloat { return self._12 = f; }
|
||||
public var _20(get, set): FastFloat; inline function get__20(): FastFloat { return self._20; } inline function set__20(f: FastFloat): FastFloat { return self._20 = f; }
|
||||
public var _21(get, set): FastFloat; inline function get__21(): FastFloat { return self._21; } inline function set__21(f: FastFloat): FastFloat { return self._21 = f; }
|
||||
public var _22(get, set): FastFloat; inline function get__22(): FastFloat { return self._22; } inline function set__22(f: FastFloat): FastFloat { return self._22 = f; }
|
||||
}
|
||||
619
leenkx/Sources/iron/math/Mat4.hx
Normal file
619
leenkx/Sources/iron/math/Mat4.hx
Normal file
@ -0,0 +1,619 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
class Mat4 {
|
||||
|
||||
public var self: kha.math.FastMatrix4;
|
||||
static var helpVec = new Vec4();
|
||||
static var helpMat = Mat4.identity();
|
||||
|
||||
public inline function new(_00: FastFloat, _10: FastFloat, _20: FastFloat, _30: FastFloat,
|
||||
_01: FastFloat, _11: FastFloat, _21: FastFloat, _31: FastFloat,
|
||||
_02: FastFloat, _12: FastFloat, _22: FastFloat, _32: FastFloat,
|
||||
_03: FastFloat, _13: FastFloat, _23: FastFloat, _33: FastFloat) {
|
||||
self = new kha.math.FastMatrix4(_00, _10, _20, _30, _01, _11, _21, _31, _02, _12, _22, _32, _03, _13, _23, _33);
|
||||
}
|
||||
|
||||
/**
|
||||
Set the transform from a location, rotation and scale.
|
||||
@param loc The location to use.
|
||||
@param quat The rotation to use.
|
||||
@param sc The scale to use.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function compose(loc: Vec4, quat: Quat, sc: Vec4): Mat4 {
|
||||
fromQuat(quat);
|
||||
scale(sc);
|
||||
setLoc(loc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Decompose this matrix into its location, rotation and scale components.
|
||||
Additional transforms (skew, projection) will be ignored.
|
||||
@param loc A vector to write the location to.
|
||||
@param quat A quaternion to write the rotation to.
|
||||
@param scale A vector to write the scale to.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function decompose(loc: Vec4, quat: Quat, scale: Vec4): Mat4 {
|
||||
loc.x = _30; loc.y = _31; loc.z = _32;
|
||||
scale.x = helpVec.set(_00, _01, _02).length();
|
||||
scale.y = helpVec.set(_10, _11, _12).length();
|
||||
scale.z = helpVec.set(_20, _21, _22).length();
|
||||
if (self.determinant() < 0.0) scale.x = -scale.x;
|
||||
var invs = 1.0 / scale.x; // Scale the rotation part
|
||||
helpMat._00 = _00 * invs;
|
||||
helpMat._01 = _01 * invs;
|
||||
helpMat._02 = _02 * invs;
|
||||
invs = 1.0 / scale.y;
|
||||
helpMat._10 = _10 * invs;
|
||||
helpMat._11 = _11 * invs;
|
||||
helpMat._12 = _12 * invs;
|
||||
invs = 1.0 / scale.z;
|
||||
helpMat._20 = _20 * invs;
|
||||
helpMat._21 = _21 * invs;
|
||||
helpMat._22 = _22 * invs;
|
||||
quat.fromRotationMat(helpMat);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Set the location component of this matrix.
|
||||
@param v The location to use.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function setLoc(v: Vec4): Mat4 {
|
||||
_30 = v.x;
|
||||
_31 = v.y;
|
||||
_32 = v.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Set the transform to a rotation from a quaternion. Other existing
|
||||
transforms will be removed.
|
||||
@param q The rotation to use.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function fromQuat(q: Quat): Mat4 {
|
||||
var x = q.x; var y = q.y; var z = q.z; var w = q.w;
|
||||
var x2 = x + x; var y2 = y + y; var z2 = z + z;
|
||||
var xx = x * x2; var xy = x * y2; var xz = x * z2;
|
||||
var yy = y * y2; var yz = y * z2; var zz = z * z2;
|
||||
var wx = w * x2; var wy = w * y2; var wz = w * z2;
|
||||
|
||||
_00 = 1.0 - (yy + zz);
|
||||
_10 = xy - wz;
|
||||
_20 = xz + wy;
|
||||
|
||||
_01 = xy + wz;
|
||||
_11 = 1.0 - (xx + zz);
|
||||
_21 = yz - wx;
|
||||
|
||||
_02 = xz - wy;
|
||||
_12 = yz + wx;
|
||||
_22 = 1.0 - (xx + yy);
|
||||
|
||||
_03 = 0.0;
|
||||
_13 = 0.0;
|
||||
_23 = 0.0;
|
||||
_30 = 0.0;
|
||||
_31 = 0.0;
|
||||
_32 = 0.0;
|
||||
_33 = 1.0;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Set all components of this matrix from an array.
|
||||
@param a The 16-component array to use. Components should be in the
|
||||
same order as for `Mat4.new()`.
|
||||
@param offset An offset index to the start of the data in the array.
|
||||
Defaults to 0.
|
||||
@return A new matrix.
|
||||
**/
|
||||
public static inline function fromFloat32Array(a: kha.arrays.Float32Array, offset = 0): Mat4 {
|
||||
return new Mat4(
|
||||
a[0 + offset], a[1 + offset], a[2 + offset], a[3 + offset],
|
||||
a[4 + offset], a[5 + offset], a[6 + offset], a[7 + offset],
|
||||
a[8 + offset], a[9 + offset], a[10 + offset], a[11 + offset],
|
||||
a[12 + offset], a[13 + offset], a[14 + offset], a[15 + offset]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Create a matrix that represents no transform - located at the origin,
|
||||
zero rotation, and a uniform scale of 1.
|
||||
@return A new matrix.
|
||||
**/
|
||||
public static inline function identity(): Mat4 {
|
||||
return new Mat4(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Set this matrix to the identity (see `identity()`).
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function setIdentity(): Mat4 {
|
||||
_00 = 1.0; _01 = 0.0; _02 = 0.0; _03 = 0.0;
|
||||
_10 = 0.0; _11 = 1.0; _12 = 0.0; _13 = 0.0;
|
||||
_20 = 0.0; _21 = 0.0; _22 = 1.0; _23 = 0.0;
|
||||
_30 = 0.0; _31 = 0.0; _32 = 0.0; _33 = 1.0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Reset this matrix to the identity and set its location.
|
||||
@param x The x location.
|
||||
@param y The y location.
|
||||
@param z The z location.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function initTranslate(x: FastFloat = 0.0, y: FastFloat = 0.0, z: FastFloat = 0.0): Mat4 {
|
||||
_00 = 1.0; _01 = 0.0; _02 = 0.0; _03 = 0.0;
|
||||
_10 = 0.0; _11 = 1.0; _12 = 0.0; _13 = 0.0;
|
||||
_20 = 0.0; _21 = 0.0; _22 = 1.0; _23 = 0.0;
|
||||
_30 = x; _31 = y; _32 = z; _33 = 1.0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Apply an additional translation to this matrix.
|
||||
@param x The distance to move in the x direction.
|
||||
@param y The distance to move in the x direction.
|
||||
@param z The distance to move in the x direction.
|
||||
@return This matrix
|
||||
**/
|
||||
public inline function translate(x: FastFloat, y: FastFloat, z: FastFloat): Mat4 {
|
||||
_00 += x * _03; _01 += y * _03; _02 += z * _03;
|
||||
_10 += x * _13; _11 += y * _13; _12 += z * _13;
|
||||
_20 += x * _23; _21 += y * _23; _22 += z * _23;
|
||||
_30 += x * _33; _31 += y * _33; _32 += z * _33;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Apply an additional scale to this matrix.
|
||||
@param v The vector to scale by.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function scale(v: Vec4): Mat4 {
|
||||
var x = v.x; var y = v.y; var z = v.z;
|
||||
_00 *= x;
|
||||
_01 *= x;
|
||||
_02 *= x;
|
||||
_03 *= x;
|
||||
_10 *= y;
|
||||
_11 *= y;
|
||||
_12 *= y;
|
||||
_13 *= y;
|
||||
_20 *= z;
|
||||
_21 *= z;
|
||||
_22 *= z;
|
||||
_23 *= z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function multmats3x4(a: Mat4, b: Mat4): Mat4 {
|
||||
var a00 = a._00; var a01 = a._01; var a02 = a._02; var a03 = a._03;
|
||||
var a10 = a._10; var a11 = a._11; var a12 = a._12; var a13 = a._13;
|
||||
var a20 = a._20; var a21 = a._21; var a22 = a._22; var a23 = a._23;
|
||||
var a30 = a._30; var a31 = a._31; var a32 = a._32; var a33 = a._33;
|
||||
|
||||
var b0 = b._00; var b1 = b._10; var b2 = b._20; var b3 = b._30;
|
||||
_00 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_10 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_20 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_30 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = b._01; b1 = b._11; b2 = b._21; b3 = b._31;
|
||||
_01 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_11 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_21 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_31 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = b._02; b1 = b._12; b2 = b._22; b3 = b._32;
|
||||
_02 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_12 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_22 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_32 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
_03 = 0;
|
||||
_13 = 0;
|
||||
_23 = 0;
|
||||
_33 = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function multmats(b: Mat4, a: Mat4): Mat4 {
|
||||
var a00 = a._00; var a01 = a._01; var a02 = a._02; var a03 = a._03;
|
||||
var a10 = a._10; var a11 = a._11; var a12 = a._12; var a13 = a._13;
|
||||
var a20 = a._20; var a21 = a._21; var a22 = a._22; var a23 = a._23;
|
||||
var a30 = a._30; var a31 = a._31; var a32 = a._32; var a33 = a._33;
|
||||
|
||||
var b0 = b._00; var b1 = b._10; var b2 = b._20; var b3 = b._30;
|
||||
_00 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_10 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_20 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_30 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = b._01; b1 = b._11; b2 = b._21; b3 = b._31;
|
||||
_01 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_11 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_21 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_31 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = b._02; b1 = b._12; b2 = b._22; b3 = b._32;
|
||||
_02 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_12 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_22 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_32 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = b._03; b1 = b._13; b2 = b._23; b3 = b._33;
|
||||
_03 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_13 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_23 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_33 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function multmat(m: Mat4): Mat4 {
|
||||
var a00 = _00; var a01 = _01; var a02 = _02; var a03 = _03;
|
||||
var a10 = _10; var a11 = _11; var a12 = _12; var a13 = _13;
|
||||
var a20 = _20; var a21 = _21; var a22 = _22; var a23 = _23;
|
||||
var a30 = _30; var a31 = _31; var a32 = _32; var a33 = _33;
|
||||
|
||||
var b0 = m._00; var b1 = m._10; var b2 = m._20; var b3 = m._30;
|
||||
_00 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_10 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_20 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_30 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = m._01; b1 = m._11; b2 = m._21; b3 = m._31;
|
||||
_01 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_11 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_21 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_31 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = m._02; b1 = m._12; b2 = m._22; b3 = m._32;
|
||||
_02 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_12 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_22 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_32 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
b0 = m._03; b1 = m._13; b2 = m._23; b3 = m._33;
|
||||
_03 = a00 * b0 + a01 * b1 + a02 * b2 + a03 * b3;
|
||||
_13 = a10 * b0 + a11 * b1 + a12 * b2 + a13 * b3;
|
||||
_23 = a20 * b0 + a21 * b1 + a22 * b2 + a23 * b3;
|
||||
_33 = a30 * b0 + a31 * b1 + a32 * b2 + a33 * b3;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Invert a matrix and store the result in this one.
|
||||
@param m The matrix to invert.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function getInverse(m: Mat4): Mat4 {
|
||||
var a00 = m._00; var a01 = m._01; var a02 = m._02; var a03 = m._03;
|
||||
var a10 = m._10; var a11 = m._11; var a12 = m._12; var a13 = m._13;
|
||||
var a20 = m._20; var a21 = m._21; var a22 = m._22; var a23 = m._23;
|
||||
var a30 = m._30; var a31 = m._31; var a32 = m._32; var a33 = m._33;
|
||||
var b00 = a00 * a11 - a01 * a10;
|
||||
var b01 = a00 * a12 - a02 * a10;
|
||||
var b02 = a00 * a13 - a03 * a10;
|
||||
var b03 = a01 * a12 - a02 * a11;
|
||||
var b04 = a01 * a13 - a03 * a11;
|
||||
var b05 = a02 * a13 - a03 * a12;
|
||||
var b06 = a20 * a31 - a21 * a30;
|
||||
var b07 = a20 * a32 - a22 * a30;
|
||||
var b08 = a20 * a33 - a23 * a30;
|
||||
var b09 = a21 * a32 - a22 * a31;
|
||||
var b10 = a21 * a33 - a23 * a31;
|
||||
var b11 = a22 * a33 - a23 * a32;
|
||||
|
||||
var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
||||
if (det == 0.0) return setIdentity();
|
||||
det = 1.0 / det;
|
||||
|
||||
_00 = (a11 * b11 - a12 * b10 + a13 * b09) * det;
|
||||
_01 = (a02 * b10 - a01 * b11 - a03 * b09) * det;
|
||||
_02 = (a31 * b05 - a32 * b04 + a33 * b03) * det;
|
||||
_03 = (a22 * b04 - a21 * b05 - a23 * b03) * det;
|
||||
_10 = (a12 * b08 - a10 * b11 - a13 * b07) * det;
|
||||
_11 = (a00 * b11 - a02 * b08 + a03 * b07) * det;
|
||||
_12 = (a32 * b02 - a30 * b05 - a33 * b01) * det;
|
||||
_13 = (a20 * b05 - a22 * b02 + a23 * b01) * det;
|
||||
_20 = (a10 * b10 - a11 * b08 + a13 * b06) * det;
|
||||
_21 = (a01 * b08 - a00 * b10 - a03 * b06) * det;
|
||||
_22 = (a30 * b04 - a31 * b02 + a33 * b00) * det;
|
||||
_23 = (a21 * b02 - a20 * b04 - a23 * b00) * det;
|
||||
_30 = (a11 * b07 - a10 * b09 - a12 * b06) * det;
|
||||
_31 = (a00 * b09 - a01 * b07 + a02 * b06) * det;
|
||||
_32 = (a31 * b01 - a30 * b03 - a32 * b00) * det;
|
||||
_33 = (a20 * b03 - a21 * b01 + a22 * b00) * det;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Transpose this matrix.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function transpose(): Mat4 {
|
||||
var f = _01; _01 = _10; _10 = f;
|
||||
f = _02; _02 = _20; _20 = f;
|
||||
f = _03; _03 = _30; _30 = f;
|
||||
f = _12; _12 = _21; _21 = f;
|
||||
f = _13; _13 = _31; _31 = f;
|
||||
f = _23; _23 = _32; _32 = f;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function transpose3x3(): Mat4 {
|
||||
var f = _01; _01 = _10; _10 = f;
|
||||
f = _02; _02 = _20; _20 = f;
|
||||
f = _12; _12 = _21; _21 = f;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Create a copy of this matrix.
|
||||
@return A new matrix.
|
||||
**/
|
||||
public inline function clone(): Mat4 {
|
||||
return new Mat4(
|
||||
_00, _10, _20, _30,
|
||||
_01, _11, _21, _31,
|
||||
_02, _12, _22, _32,
|
||||
_03, _13, _23, _33
|
||||
);
|
||||
}
|
||||
|
||||
public inline function setF32(a: kha.arrays.Float32Array, offset = 0): Mat4 {
|
||||
_00 = a[0 + offset]; _10 = a[1 + offset]; _20 = a[2 + offset]; _30 = a[3 + offset];
|
||||
_01 = a[4 + offset]; _11 = a[5 + offset]; _21 = a[6 + offset]; _31 = a[7 + offset];
|
||||
_02 = a[8 + offset]; _12 = a[9 + offset]; _22 = a[10 + offset]; _32 = a[11 + offset];
|
||||
_03 = a[12 + offset]; _13 = a[13 + offset]; _23 = a[14 + offset]; _33 = a[15 + offset];
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function setFrom(m: Mat4): Mat4 {
|
||||
_00 = m._00; _01 = m._01; _02 = m._02; _03 = m._03;
|
||||
_10 = m._10; _11 = m._11; _12 = m._12; _13 = m._13;
|
||||
_20 = m._20; _21 = m._21; _22 = m._22; _23 = m._23;
|
||||
_30 = m._30; _31 = m._31; _32 = m._32; _33 = m._33;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the location component.
|
||||
@return A new vector.
|
||||
**/
|
||||
public inline function getLoc(): Vec4 {
|
||||
return new Vec4(_30, _31, _32, _33);
|
||||
}
|
||||
|
||||
/**
|
||||
Get the scale component.
|
||||
@return A new vector.
|
||||
**/
|
||||
public inline function getScale(): Vec4 {
|
||||
return new Vec4(
|
||||
Math.sqrt(_00 * _00 + _10 * _10 + _20 * _20),
|
||||
Math.sqrt(_01 * _01 + _11 * _11 + _21 * _21),
|
||||
Math.sqrt(_02 * _02 + _12 * _12 + _22 * _22)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Multiply this vector by a scalar.
|
||||
@param s The value to multiply by.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function mult(s: FastFloat): Mat4 {
|
||||
_00 *= s; _10 *= s; _20 *= s; _30 *= s;
|
||||
_01 *= s; _11 *= s; _21 *= s; _31 *= s;
|
||||
_02 *= s; _12 *= s; _22 *= s; _32 *= s;
|
||||
_03 *= s; _13 *= s; _23 *= s; _33 *= s;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Convert this matrix to a rotation matrix, and discard location and
|
||||
scale information.
|
||||
@return This matrix.
|
||||
**/
|
||||
public inline function toRotation(): Mat4 {
|
||||
var scale = 1.0 / helpVec.set(_00, _01, _02).length();
|
||||
_00 = _00 * scale;
|
||||
_01 = _01 * scale;
|
||||
_02 = _02 * scale;
|
||||
scale = 1.0 / helpVec.set(_10, _11, _12).length();
|
||||
_10 = _10 * scale;
|
||||
_11 = _11 * scale;
|
||||
_12 = _12 * scale;
|
||||
scale = 1.0 / helpVec.set(_20, _21, _22).length();
|
||||
_20 = _20 * scale;
|
||||
_21 = _21 * scale;
|
||||
_22 = _22 * scale;
|
||||
_03 = 0.0;
|
||||
_13 = 0.0;
|
||||
_23 = 0.0;
|
||||
_30 = 0.0;
|
||||
_31 = 0.0;
|
||||
_32 = 0.0;
|
||||
_33 = 1.0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new perspective projection matrix.
|
||||
@param fovY The vertical field of view.
|
||||
@param aspect The aspect ratio.
|
||||
@param zn The depth of the near floor of the frustum.
|
||||
@param zf The depth of the far floor of the frustum.
|
||||
@return A new matrix.
|
||||
**/
|
||||
public static inline function persp(fovY: FastFloat, aspect: FastFloat, zn: FastFloat, zf: FastFloat): Mat4 {
|
||||
var uh = 1.0 / Math.tan(fovY / 2);
|
||||
var uw = uh / aspect;
|
||||
return new Mat4(
|
||||
uw, 0, 0, 0,
|
||||
0, uh, 0, 0,
|
||||
0, 0, (zf + zn) / (zn - zf), 2 * zf * zn / (zn - zf),
|
||||
0, 0, -1, 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
Create a new orthographic projection matrix.
|
||||
@param left The left of the box.
|
||||
@param right The right of the box.
|
||||
@param bottom The bottom of the box.
|
||||
@param top The top of the box.
|
||||
@param near The depth of the near floor of the box.
|
||||
@param far The depth of the far floor of the box.
|
||||
@return A new matrix.
|
||||
**/
|
||||
public static inline function ortho(left: FastFloat, right: FastFloat, bottom: FastFloat, top: FastFloat, near: FastFloat, far: FastFloat): Mat4 {
|
||||
var rl = right - left;
|
||||
var tb = top - bottom;
|
||||
var fn = far - near;
|
||||
var tx = -(right + left) / (rl);
|
||||
var ty = -(top + bottom) / (tb);
|
||||
var tz = -(far + near) / (fn);
|
||||
return new Mat4(
|
||||
2 / rl, 0, 0, tx,
|
||||
0, 2 / tb, 0, ty,
|
||||
0, 0, -2 / fn, tz,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
public inline function setLookAt(eye: Vec4, center: Vec4, up: Vec4): Mat4 {
|
||||
var f0 = center.x - eye.x;
|
||||
var f1 = center.y - eye.y;
|
||||
var f2 = center.z - eye.z;
|
||||
var n = 1.0 / Math.sqrt(f0 * f0 + f1 * f1 + f2 * f2);
|
||||
f0 *= n;
|
||||
f1 *= n;
|
||||
f2 *= n;
|
||||
|
||||
var s0 = f1 * up.z - f2 * up.y;
|
||||
var s1 = f2 * up.x - f0 * up.z;
|
||||
var s2 = f0 * up.y - f1 * up.x;
|
||||
n = 1.0 / Math.sqrt(s0 * s0 + s1 * s1 + s2 * s2);
|
||||
s0 *= n;
|
||||
s1 *= n;
|
||||
s2 *= n;
|
||||
|
||||
var u0 = s1 * f2 - s2 * f1;
|
||||
var u1 = s2 * f0 - s0 * f2;
|
||||
var u2 = s0 * f1 - s1 * f0;
|
||||
var d0 = -eye.x * s0 - eye.y * s1 - eye.z * s2;
|
||||
var d1 = -eye.x * u0 - eye.y * u1 - eye.z * u2;
|
||||
var d2 = eye.x * f0 + eye.y * f1 + eye.z * f2;
|
||||
|
||||
_00 = s0;
|
||||
_10 = s1;
|
||||
_20 = s2;
|
||||
_30 = d0;
|
||||
_01 = u0;
|
||||
_11 = u1;
|
||||
_21 = u2;
|
||||
_31 = d1;
|
||||
_02 = -f0;
|
||||
_12 = -f1;
|
||||
_22 = -f2;
|
||||
_32 = d2;
|
||||
_03 = 0.0;
|
||||
_13 = 0.0;
|
||||
_23 = 0.0;
|
||||
_33 = 1.0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Apply an additional rotation to this matrix.
|
||||
@param q The quaternion to rotate by.
|
||||
**/
|
||||
public inline function applyQuat(q: Quat) {
|
||||
helpMat.fromQuat(q);
|
||||
multmat(helpMat);
|
||||
}
|
||||
|
||||
/**
|
||||
@return The right vector; the positive x axis of the space defined by
|
||||
this matrix.
|
||||
**/
|
||||
public inline function right(): Vec4 {
|
||||
return new Vec4(_00, _01, _02);
|
||||
}
|
||||
/**
|
||||
@return The look vector; the positive y axis of the space defined by
|
||||
this matrix.
|
||||
**/
|
||||
public inline function look(): Vec4 {
|
||||
return new Vec4(_10, _11, _12);
|
||||
}
|
||||
/**
|
||||
@return The up vector; the positive z axis of the space defined by
|
||||
this matrix.
|
||||
**/
|
||||
public inline function up(): Vec4 {
|
||||
return new Vec4(_20, _21, _22);
|
||||
}
|
||||
|
||||
public var _00(get, set): FastFloat; inline function get__00(): FastFloat { return self._00; } inline function set__00(f: FastFloat): FastFloat { return self._00 = f; }
|
||||
public var _01(get, set): FastFloat; inline function get__01(): FastFloat { return self._01; } inline function set__01(f: FastFloat): FastFloat { return self._01 = f; }
|
||||
public var _02(get, set): FastFloat; inline function get__02(): FastFloat { return self._02; } inline function set__02(f: FastFloat): FastFloat { return self._02 = f; }
|
||||
public var _03(get, set): FastFloat; inline function get__03(): FastFloat { return self._03; } inline function set__03(f: FastFloat): FastFloat { return self._03 = f; }
|
||||
public var _10(get, set): FastFloat; inline function get__10(): FastFloat { return self._10; } inline function set__10(f: FastFloat): FastFloat { return self._10 = f; }
|
||||
public var _11(get, set): FastFloat; inline function get__11(): FastFloat { return self._11; } inline function set__11(f: FastFloat): FastFloat { return self._11 = f; }
|
||||
public var _12(get, set): FastFloat; inline function get__12(): FastFloat { return self._12; } inline function set__12(f: FastFloat): FastFloat { return self._12 = f; }
|
||||
public var _13(get, set): FastFloat; inline function get__13(): FastFloat { return self._13; } inline function set__13(f: FastFloat): FastFloat { return self._13 = f; }
|
||||
public var _20(get, set): FastFloat; inline function get__20(): FastFloat { return self._20; } inline function set__20(f: FastFloat): FastFloat { return self._20 = f; }
|
||||
public var _21(get, set): FastFloat; inline function get__21(): FastFloat { return self._21; } inline function set__21(f: FastFloat): FastFloat { return self._21 = f; }
|
||||
public var _22(get, set): FastFloat; inline function get__22(): FastFloat { return self._22; } inline function set__22(f: FastFloat): FastFloat { return self._22 = f; }
|
||||
public var _23(get, set): FastFloat; inline function get__23(): FastFloat { return self._23; } inline function set__23(f: FastFloat): FastFloat { return self._23 = f; }
|
||||
public var _30(get, set): FastFloat; inline function get__30(): FastFloat { return self._30; } inline function set__30(f: FastFloat): FastFloat { return self._30 = f; }
|
||||
public var _31(get, set): FastFloat; inline function get__31(): FastFloat { return self._31; } inline function set__31(f: FastFloat): FastFloat { return self._31 = f; }
|
||||
public var _32(get, set): FastFloat; inline function get__32(): FastFloat { return self._32; } inline function set__32(f: FastFloat): FastFloat { return self._32 = f; }
|
||||
public var _33(get, set): FastFloat; inline function get__33(): FastFloat { return self._33; } inline function set__33(f: FastFloat): FastFloat { return self._33 = f; }
|
||||
|
||||
public function toString(): String {
|
||||
return '[[$_00, $_10, $_20, $_30], [$_01, $_11, $_21, $_31], [$_02, $_12, $_22, $_32], [$_03, $_13, $_23, $_33]]';
|
||||
}
|
||||
|
||||
public function toFloat32Array(): kha.arrays.Float32Array {
|
||||
var array = new kha.arrays.Float32Array(16);
|
||||
array[0] = _00;
|
||||
array[1] = _10;
|
||||
array[2] = _20;
|
||||
array[3] = _30;
|
||||
array[4] = _01;
|
||||
array[5] = _11;
|
||||
array[6] = _21;
|
||||
array[7] = _31;
|
||||
array[8] = _02;
|
||||
array[9] = _12;
|
||||
array[10] = _22;
|
||||
array[11] = _32;
|
||||
array[12] = _03;
|
||||
array[13] = _13;
|
||||
array[14] = _23;
|
||||
array[15] = _33;
|
||||
return array;
|
||||
}
|
||||
}
|
||||
499
leenkx/Sources/iron/math/Quat.hx
Normal file
499
leenkx/Sources/iron/math/Quat.hx
Normal file
@ -0,0 +1,499 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
class Quat {
|
||||
|
||||
public var x: FastFloat;
|
||||
public var y: FastFloat;
|
||||
public var z: FastFloat;
|
||||
public var w: FastFloat;
|
||||
|
||||
static var helpVec0 = new Vec4();
|
||||
static var helpVec1 = new Vec4();
|
||||
static var helpVec2 = new Vec4();
|
||||
static var helpMat = Mat4.identity();
|
||||
static var xAxis = Vec4.xAxis();
|
||||
static var yAxis = Vec4.yAxis();
|
||||
|
||||
static inline var SQRT2: FastFloat = 1.4142135623730951;
|
||||
|
||||
public inline function new(x: FastFloat = 0.0, y: FastFloat = 0.0, z: FastFloat = 0.0, w: FastFloat = 1.0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public inline function set(x: FastFloat, y: FastFloat, z: FastFloat, w: FastFloat): Quat {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function add(q: Quat): Quat {
|
||||
this.x += q.x;
|
||||
this.y += q.y;
|
||||
this.z += q.z;
|
||||
this.w += q.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function addquat(a: Quat, b: Quat): Quat {
|
||||
this.x = a.x + b.x;
|
||||
this.y = a.y + b.y;
|
||||
this.z = a.z + b.z;
|
||||
this.w = a.w + b.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function sub(q: Quat): Quat {
|
||||
this.x -= q.x;
|
||||
this.y -= q.y;
|
||||
this.z -= q.z;
|
||||
this.w -= q.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function subquat(a: Quat, b: Quat): Quat {
|
||||
this.x = a.x - b.x;
|
||||
this.y = a.y - b.y;
|
||||
this.z = a.z - b.z;
|
||||
this.w = a.w - b.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function fromAxisAngle(axis: Vec4, angle: FastFloat): Quat {
|
||||
var s: FastFloat = Math.sin(angle * 0.5);
|
||||
x = axis.x * s;
|
||||
y = axis.y * s;
|
||||
z = axis.z * s;
|
||||
w = Math.cos(angle * 0.5);
|
||||
return normalize();
|
||||
}
|
||||
|
||||
public inline function toAxisAngle(axis: Vec4): FastFloat {
|
||||
normalize();
|
||||
var angle = 2 * Math.acos(w);
|
||||
var s = Math.sqrt(1 - w * w);
|
||||
if (s < 0.001) {
|
||||
axis.x = this.x;
|
||||
axis.y = this.y;
|
||||
axis.z = this.z;
|
||||
}
|
||||
else {
|
||||
axis.x = this.x / s;
|
||||
axis.y = this.y / s;
|
||||
axis.z = this.z / s;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
public inline function fromMat(m: Mat4): Quat {
|
||||
helpMat.setFrom(m);
|
||||
helpMat.toRotation();
|
||||
return fromRotationMat(helpMat);
|
||||
}
|
||||
|
||||
public inline function fromRotationMat(m: Mat4): Quat {
|
||||
// Assumes the upper 3x3 is a pure rotation matrix
|
||||
var m11 = m._00; var m12 = m._10; var m13 = m._20;
|
||||
var m21 = m._01; var m22 = m._11; var m23 = m._21;
|
||||
var m31 = m._02; var m32 = m._12; var m33 = m._22;
|
||||
var tr = m11 + m22 + m33;
|
||||
var s = 0.0;
|
||||
|
||||
if (tr > 0) {
|
||||
s = 0.5 / Math.sqrt(tr + 1.0);
|
||||
this.w = 0.25 / s;
|
||||
this.x = (m32 - m23) * s;
|
||||
this.y = (m13 - m31) * s;
|
||||
this.z = (m21 - m12) * s;
|
||||
}
|
||||
else if (m11 > m22 && m11 > m33) {
|
||||
s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
|
||||
this.w = (m32 - m23) / s;
|
||||
this.x = 0.25 * s;
|
||||
this.y = (m12 + m21) / s;
|
||||
this.z = (m13 + m31) / s;
|
||||
}
|
||||
else if (m22 > m33) {
|
||||
s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
|
||||
this.w = (m13 - m31) / s;
|
||||
this.x = (m12 + m21) / s;
|
||||
this.y = 0.25 * s;
|
||||
this.z = (m23 + m32) / s;
|
||||
}
|
||||
else {
|
||||
s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
|
||||
this.w = (m21 - m12) / s;
|
||||
this.x = (m13 + m31) / s;
|
||||
this.y = (m23 + m32) / s;
|
||||
this.z = 0.25 * s;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Multiply this quaternion by float
|
||||
public inline function scale(scale: FastFloat): Quat {
|
||||
this.x *= scale;
|
||||
this.y *= scale;
|
||||
this.z *= scale;
|
||||
this.w *= scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function scalequat(q: Quat, scale: FastFloat): Quat {
|
||||
q.x *= scale;
|
||||
q.y *= scale;
|
||||
q.z *= scale;
|
||||
q.w *= scale;
|
||||
return q;
|
||||
}
|
||||
|
||||
/**
|
||||
Multiply this quaternion by another.
|
||||
@param q The quaternion to multiply this one with.
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function mult(q: Quat): Quat {
|
||||
return multquats(this, q);
|
||||
}
|
||||
|
||||
/**
|
||||
Multiply two other quaternions and store the result in this one.
|
||||
@param q1 The first operand.
|
||||
@param q2 The second operand.
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function multquats(q1: Quat, q2: Quat): Quat {
|
||||
var q1x = q1.x; var q1y = q1.y; var q1z = q1.z; var q1w = q1.w;
|
||||
var q2x = q2.x; var q2y = q2.y; var q2z = q2.z; var q2w = q2.w;
|
||||
x = q1x * q2w + q1w * q2x + q1y * q2z - q1z * q2y;
|
||||
y = q1w * q2y - q1x * q2z + q1y * q2w + q1z * q2x;
|
||||
z = q1w * q2z + q1x * q2y - q1y * q2x + q1z * q2w;
|
||||
w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function module(): FastFloat {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
|
||||
}
|
||||
|
||||
/**
|
||||
Scale this quaternion to have a magnitude of 1.
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function normalize(): Quat {
|
||||
var l = Math.sqrt(x * x + y * y + z * z + w * w);
|
||||
if (l == 0.0) {
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
w = 0;
|
||||
}
|
||||
else {
|
||||
l = 1.0 / l;
|
||||
x *= l;
|
||||
y *= l;
|
||||
z *= l;
|
||||
w *= l;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Invert the given quaternion and store the result in this one.
|
||||
@param q Quaternion to invert.
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function inverse(q: Quat): Quat {
|
||||
var sqsum = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
|
||||
sqsum = -1 / sqsum;
|
||||
x = q.x * sqsum;
|
||||
y = q.y * sqsum;
|
||||
z = q.z * sqsum;
|
||||
w = -q.w * sqsum;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Copy the rotation of another quaternion to this one.
|
||||
@param q A quaternion to copy.
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function setFrom(q: Quat): Quat {
|
||||
x = q.x;
|
||||
y = q.y;
|
||||
z = q.z;
|
||||
w = q.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Convert this quaternion to a YZX Euler (note: XZY in blender order terms).
|
||||
@return A new YZX Euler that represents the same rotation as this
|
||||
quaternion.
|
||||
**/
|
||||
public inline function getEuler(): Vec4 {
|
||||
var a = -2 * (x * z - w * y);
|
||||
var b = w * w + x * x - y * y - z * z;
|
||||
var c = 2 * (x * y + w * z);
|
||||
var d = -2 * (y * z - w * x);
|
||||
var e = w * w - x * x + y * y - z * z;
|
||||
return new Vec4(Math.atan2(d, e), Math.atan2(a, b), Math.asin(c));
|
||||
}
|
||||
|
||||
/**
|
||||
Set this quaternion to the rotation represented by a YZX Euler (XZY in blender terms).
|
||||
@param x The Euler's x component.
|
||||
@param y The Euler's y component.
|
||||
@param z The Euler's z component.
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function fromEuler(x: FastFloat, y: FastFloat, z: FastFloat): Quat {
|
||||
var f = x / 2;
|
||||
var c1 = Math.cos(f);
|
||||
var s1 = Math.sin(f);
|
||||
f = y / 2;
|
||||
var c2 = Math.cos(f);
|
||||
var s2 = Math.sin(f);
|
||||
f = z / 2;
|
||||
var c3 = Math.cos(f);
|
||||
var s3 = Math.sin(f);
|
||||
// YZX
|
||||
this.x = s1 * c2 * c3 + c1 * s2 * s3;
|
||||
this.y = c1 * s2 * c3 + s1 * c2 * s3;
|
||||
this.z = c1 * c2 * s3 - s1 * s2 * c3;
|
||||
this.w = c1 * c2 * c3 - s1 * s2 * s3;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Convert this quaternion to an Euler of arbitrary order.
|
||||
@param the order of the euler to obtain
|
||||
(in blender order, opposite from mathematical order)
|
||||
can be "XYZ", "XZY", "YXZ", "YZX", "ZXY", or "ZYX".
|
||||
@return A new YZX Euler that represents the same rotation as this
|
||||
quaternion.
|
||||
**/
|
||||
// this method use matrices as a middle ground
|
||||
// (and is copied from blender's internal code in mathutils)
|
||||
// note: there are two possible eulers for the same rotation, blender defines the 'best' as the one with the smallest sum of absolute components
|
||||
// should we actually make that choice, or is just getting one of them randomly good?
|
||||
// note2: it seems that this engine transforms a vector by using vector×matrix instead of matrix×vector, meaning that the outer transformations are on the RIGHT.
|
||||
// (…Except for quaternions, where the outer quaternions are on the LEFT.)
|
||||
// anywho, the way the elements of the matrix are ordered makes sense (first digit-> row ID, second digit->column ID) in this system.
|
||||
public inline function toEulerOrdered(p: String): Vec4{
|
||||
// normalize quat ?
|
||||
|
||||
var q0: FastFloat = SQRT2 * this.w;
|
||||
var q1: FastFloat = SQRT2 * this.x;
|
||||
var q2: FastFloat = SQRT2 * this.y;
|
||||
var q3: FastFloat = SQRT2 * this.z;
|
||||
|
||||
var qda: FastFloat = q0 * q1;
|
||||
var qdb: FastFloat = q0 * q2;
|
||||
var qdc: FastFloat = q0 * q3;
|
||||
var qaa: FastFloat = q1 * q1;
|
||||
var qab: FastFloat = q1 * q2;
|
||||
var qac: FastFloat = q1 * q3;
|
||||
var qbb: FastFloat = q2 * q2;
|
||||
var qbc: FastFloat = q2 * q3;
|
||||
var qcc: FastFloat = q3 * q3;
|
||||
|
||||
var m = new Mat3(
|
||||
// OK, *this* matrix is transposed with respect to what leenkx expects.
|
||||
// it is transposed again in the next step though
|
||||
|
||||
(1.0 - qbb - qcc),
|
||||
(qdc + qab),
|
||||
(-qdb + qac),
|
||||
|
||||
(-qdc + qab),
|
||||
(1.0 - qaa - qcc),
|
||||
(qda + qbc),
|
||||
|
||||
(qdb + qac),
|
||||
(-qda + qbc),
|
||||
(1.0 - qaa - qbb)
|
||||
);
|
||||
|
||||
// now define what is necessary to perform look-ups in that matrix
|
||||
var ml: Array<Array<FastFloat>> = [[m._00, m._10, m._20],
|
||||
[m._01, m._11, m._21],
|
||||
[m._02, m._12, m._22]];
|
||||
var eull: Array<FastFloat> = [0, 0, 0];
|
||||
|
||||
var i: Int = p.charCodeAt(0) - "X".charCodeAt(0);
|
||||
var j: Int = p.charCodeAt(1) - "X".charCodeAt(0);
|
||||
var k: Int = p.charCodeAt(2) - "X".charCodeAt(0);
|
||||
|
||||
// now the dumber version (isolating code)
|
||||
if (p.charAt(0) == "X") i = 0;
|
||||
else if (p.charAt(0) == "Y") i = 1;
|
||||
else i = 2;
|
||||
if (p.charAt(1) == "X") j = 0;
|
||||
else if (p.charAt(1) == "Y") j = 1;
|
||||
else j = 2;
|
||||
if (p.charAt(2) == "X") k = 0;
|
||||
else if (p.charAt(2) == "Y") k = 1;
|
||||
else k = 2;
|
||||
|
||||
var cy: FastFloat = Math.sqrt(ml[i][i] * ml[i][i] + ml[i][j] * ml[i][j]);
|
||||
|
||||
var eul1 = new Vec4();
|
||||
|
||||
if (cy > 16.0 * 1e-3) {
|
||||
eull[i] = Math.atan2(ml[j][k], ml[k][k]);
|
||||
eull[j] = Math.atan2(-ml[i][k], cy);
|
||||
eull[k] = Math.atan2(ml[i][j], ml[i][i]);
|
||||
}
|
||||
else {
|
||||
eull[i] = Math.atan2(-ml[k][j], ml[j][j]);
|
||||
eull[j] = Math.atan2(-ml[i][k], cy);
|
||||
eull[k] = 0; // 2 * Math.PI;
|
||||
}
|
||||
eul1.x = eull[0];
|
||||
eul1.y = eull[1];
|
||||
eul1.z = eull[2];
|
||||
|
||||
if (p == "XZY" || p == "YXZ" || p == "ZYX") {
|
||||
eul1.x *= -1;
|
||||
eul1.y *= -1;
|
||||
eul1.z *= -1;
|
||||
}
|
||||
return eul1;
|
||||
}
|
||||
|
||||
/**
|
||||
Set this quaternion to the rotation represented by an Euler.
|
||||
@param x The Euler's x component.
|
||||
@param y The Euler's y component.
|
||||
@param z The Euler's z component.
|
||||
@param order: the (blender) order of the euler
|
||||
(which is the OPPOSITE of the mathematical order)
|
||||
can be "XYZ", "XZY", "YXZ", "YZX", "ZXY", or "ZYX".
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function fromEulerOrdered(e: Vec4, order: String): Quat {
|
||||
var c1 = Math.cos(e.x / 2);
|
||||
var c2 = Math.cos(e.y / 2);
|
||||
var c3 = Math.cos(e.z / 2);
|
||||
var s1 = Math.sin(e.x / 2);
|
||||
var s2 = Math.sin(e.y / 2);
|
||||
var s3 = Math.sin(e.z / 2);
|
||||
|
||||
var qx = new Quat(s1, 0, 0, c1);
|
||||
var qy = new Quat(0, s2, 0, c2);
|
||||
var qz = new Quat(0, 0, s3, c3);
|
||||
|
||||
if (order.charAt(2) == 'X')
|
||||
this.setFrom(qx);
|
||||
else if (order.charAt(2) == 'Y')
|
||||
this.setFrom(qy);
|
||||
else
|
||||
this.setFrom(qz);
|
||||
if (order.charAt(1) == 'X')
|
||||
this.mult(qx);
|
||||
else if (order.charAt(1) == 'Y')
|
||||
this.mult(qy);
|
||||
else
|
||||
this.mult(qz);
|
||||
if (order.charAt(0) == 'X')
|
||||
this.mult(qx);
|
||||
else if (order.charAt(0) == 'Y')
|
||||
this.mult(qy);
|
||||
else
|
||||
this.mult(qz);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
Linearly interpolate between two other quaterions, and store the
|
||||
result in this one. This is not a so-called slerp operation.
|
||||
@param from The quaterion to interpolate from.
|
||||
@param to The quaterion to interpolate to.
|
||||
@param s The amount to interpolate, with 0 being `from` and 1 being
|
||||
`to`, and 0.5 being half way between the two.
|
||||
@return This quaternion.
|
||||
**/
|
||||
public inline function lerp(from: Quat, to: Quat, s: FastFloat): Quat {
|
||||
var fromx = from.x;
|
||||
var fromy = from.y;
|
||||
var fromz = from.z;
|
||||
var fromw = from.w;
|
||||
var dot: FastFloat = from.dot(to);
|
||||
if (dot < 0.0) {
|
||||
fromx = -fromx;
|
||||
fromy = -fromy;
|
||||
fromz = -fromz;
|
||||
fromw = -fromw;
|
||||
}
|
||||
x = fromx + (to.x - fromx) * s;
|
||||
y = fromy + (to.y - fromy) * s;
|
||||
z = fromz + (to.z - fromz) * s;
|
||||
w = fromw + (to.w - fromw) * s;
|
||||
return normalize();
|
||||
}
|
||||
|
||||
// Slerp is shorthand for spherical linear interpolation
|
||||
public inline function slerp(from: Quat, to: Quat, t: FastFloat): Quat {
|
||||
var epsilon: Float = 0.0005;
|
||||
|
||||
var dot = from.dot(to);
|
||||
if (dot > 1 - epsilon) {
|
||||
var result: Quat = to.add((from.sub(to)).scale(t));
|
||||
result.normalize();
|
||||
return result;
|
||||
}
|
||||
if (dot < 0) dot = 0;
|
||||
if (dot > 1) dot = 1;
|
||||
|
||||
var theta0: Float = Math.acos(dot);
|
||||
var theta: Float = theta0 * t;
|
||||
var q2: Quat = to.sub(scale(dot));
|
||||
q2.normalize();
|
||||
var result: Quat = scale(Math.cos(theta)).add(q2.scale(Math.sin(theta)));
|
||||
result.normalize();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Find the dot product of this quaternion with another.
|
||||
@param q The other quaternion.
|
||||
@return The dot product.
|
||||
**/
|
||||
public inline function dot(q: Quat): FastFloat {
|
||||
return (x * q.x) + (y * q.y) + (z * q.z) + (w * q.w);
|
||||
}
|
||||
|
||||
public inline function fromTo(v1: Vec4, v2: Vec4): Quat {
|
||||
// Rotation formed by direction vectors
|
||||
// v1 and v2 should be normalized first
|
||||
var a = helpVec0;
|
||||
var dot = v1.dot(v2);
|
||||
if (dot < -0.999999) {
|
||||
a.crossvecs(xAxis, v1);
|
||||
if (a.length() < 0.000001) a.crossvecs(yAxis, v1);
|
||||
a.normalize();
|
||||
fromAxisAngle(a, Math.PI);
|
||||
}
|
||||
else if (dot > 0.999999) {
|
||||
set(0, 0, 0, 1);
|
||||
}
|
||||
else {
|
||||
a.crossvecs(v1, v2);
|
||||
set(a.x, a.y, a.z, 1 + dot);
|
||||
normalize();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public function toString(): String {
|
||||
return this.x + ", " + this.y + ", " + this.z + ", " + this.w;
|
||||
}
|
||||
}
|
||||
222
leenkx/Sources/iron/math/Ray.hx
Normal file
222
leenkx/Sources/iron/math/Ray.hx
Normal file
@ -0,0 +1,222 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
class Ray {
|
||||
|
||||
public var origin: Vec4;
|
||||
public var direction: Vec4;
|
||||
|
||||
public function new(origin: Vec4 = null, direction: Vec4 = null) {
|
||||
this.origin = origin == null ? new Vec4() : origin;
|
||||
this.direction = direction == null ? new Vec4() : direction;
|
||||
}
|
||||
|
||||
public function at(t: FastFloat): Vec4 {
|
||||
var result = new Vec4();
|
||||
return result.setFrom(direction).mult(t).add(origin);
|
||||
}
|
||||
|
||||
public function distanceToPoint(point: Vec4): FastFloat {
|
||||
var v1 = new Vec4();
|
||||
var directionDistance = v1.subvecs(point, this.origin).dot(this.direction);
|
||||
|
||||
// Point behind the ray
|
||||
if (directionDistance < 0) {
|
||||
return this.origin.distanceTo(point);
|
||||
}
|
||||
|
||||
v1.setFrom(this.direction).mult(directionDistance).add(this.origin);
|
||||
|
||||
return v1.distanceTo(point);
|
||||
}
|
||||
|
||||
public function intersectsSphere(sphereCenter: Vec4, sphereRadius: FastFloat): Bool {
|
||||
return distanceToPoint(sphereCenter) <= sphereRadius;
|
||||
}
|
||||
|
||||
public function intersectsPlane(plane: Plane): Bool {
|
||||
// Check if the ray lies on the plane first
|
||||
var distToPoint = plane.distanceToPoint(this.origin);
|
||||
if (distToPoint == 0) return true;
|
||||
|
||||
var denominator = plane.normal.dot(this.direction);
|
||||
if (denominator * distToPoint < 0) return true;
|
||||
|
||||
// Ray origin is behind the plane (and is pointing behind it)
|
||||
return false;
|
||||
}
|
||||
|
||||
public function distanceToPlane(plane: Plane): FastFloat {
|
||||
var denominator = plane.normal.dot(this.direction);
|
||||
if (denominator == 0) {
|
||||
// Line is coplanar, return origin
|
||||
if (plane.distanceToPoint(this.origin) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Null is preferable to undefined since undefined means.... it is undefined
|
||||
return -1;
|
||||
}
|
||||
|
||||
var t = -(this.origin.dot(plane.normal) + plane.constant) / denominator;
|
||||
|
||||
// Return if the ray never intersects the plane
|
||||
return t >= 0 ? t : -1;
|
||||
}
|
||||
|
||||
public function intersectPlane(plane: Plane): Vec4 {
|
||||
var t = this.distanceToPlane(plane);
|
||||
if (t == -1) return null;
|
||||
return this.at(t);
|
||||
}
|
||||
|
||||
public function intersectsBox(center: Vec4, dim: Vec4): Bool {
|
||||
return this.intersectBox(center, dim) != null;
|
||||
}
|
||||
|
||||
public function intersectBox(center: Vec4, dim: Vec4): Vec4 {
|
||||
// http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
|
||||
var tmin, tmax, tymin, tymax, tzmin, tzmax;
|
||||
|
||||
var halfX = dim.x / 2;
|
||||
var halfY = dim.y / 2;
|
||||
var halfZ = dim.z / 2;
|
||||
var boxMinX = center.x - halfX;
|
||||
var boxMinY = center.y - halfY;
|
||||
var boxMinZ = center.z - halfZ;
|
||||
var boxMaxX = center.x + halfX;
|
||||
var boxMaxY = center.y + halfY;
|
||||
var boxMaxZ = center.z + halfZ;
|
||||
|
||||
var invdirx = 1 / this.direction.x;
|
||||
var invdiry = 1 / this.direction.y;
|
||||
var invdirz = 1 / this.direction.z;
|
||||
|
||||
var origin = this.origin;
|
||||
|
||||
if (invdirx >= 0) {
|
||||
tmin = (boxMinX - origin.x) * invdirx;
|
||||
tmax = (boxMaxX - origin.x) * invdirx;
|
||||
}
|
||||
else {
|
||||
tmin = (boxMaxX - origin.x) * invdirx;
|
||||
tmax = (boxMinX - origin.x) * invdirx;
|
||||
}
|
||||
|
||||
if (invdiry >= 0) {
|
||||
tymin = (boxMinY - origin.y) * invdiry;
|
||||
tymax = (boxMaxY - origin.y) * invdiry;
|
||||
}
|
||||
else {
|
||||
tymin = (boxMaxY - origin.y) * invdiry;
|
||||
tymax = (boxMinY - origin.y) * invdiry;
|
||||
}
|
||||
|
||||
if ((tmin > tymax) || (tymin > tmax)) return null;
|
||||
|
||||
// These lines also handle the case where tmin or tmax is NaN
|
||||
// (result of 0 * Infinity). x !== x returns true if x is NaN
|
||||
if (tymin > tmin || tmin != tmin) tmin = tymin;
|
||||
if (tymax < tmax || tmax != tmax) tmax = tymax;
|
||||
|
||||
if (invdirz >= 0) {
|
||||
tzmin = (boxMinZ - origin.z) * invdirz;
|
||||
tzmax = (boxMaxZ - origin.z) * invdirz;
|
||||
}
|
||||
else {
|
||||
tzmin = (boxMaxZ - origin.z) * invdirz;
|
||||
tzmax = (boxMinZ - origin.z) * invdirz;
|
||||
}
|
||||
|
||||
if ((tmin > tzmax) || (tzmin > tmax)) return null;
|
||||
if (tzmin > tmin || tmin != tmin ) tmin = tzmin;
|
||||
if (tzmax < tmax || tmax != tmax ) tmax = tzmax;
|
||||
|
||||
// Return point closest to the ray (positive side)
|
||||
if (tmax < 0) return null;
|
||||
|
||||
return this.at(tmin >= 0 ? tmin : tmax);
|
||||
}
|
||||
|
||||
public function intersectTriangle(a: Vec4, b: Vec4, c: Vec4, backfaceCulling: Bool): Vec4 {
|
||||
// Compute the offset origin, edges, and normal
|
||||
var diff = new Vec4();
|
||||
var edge1 = new Vec4();
|
||||
var edge2 = new Vec4();
|
||||
var normal = new Vec4();
|
||||
|
||||
// from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp
|
||||
edge1.subvecs(b, a);
|
||||
edge2.subvecs(c, a);
|
||||
normal.crossvecs(edge1, edge2);
|
||||
|
||||
// Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
|
||||
// E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
|
||||
// |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
|
||||
// |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
|
||||
// |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
|
||||
var DdN = this.direction.dot(normal);
|
||||
var sign;
|
||||
|
||||
if (DdN > 0) {
|
||||
if (backfaceCulling) return null;
|
||||
sign = 1;
|
||||
}
|
||||
else if (DdN < 0) {
|
||||
sign = -1;
|
||||
DdN = -DdN;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
diff.subvecs(this.origin, a);
|
||||
var DdQxE2 = sign * this.direction.dot(edge2.crossvecs(diff, edge2));
|
||||
|
||||
// b1 < 0, no intersection
|
||||
if (DdQxE2 < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var DdE1xQ = sign * this.direction.dot(edge1.cross(diff));
|
||||
|
||||
// b2 < 0, no intersection
|
||||
if (DdE1xQ < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// b1+b2 > 1, no intersection
|
||||
if (DdQxE2 + DdE1xQ > DdN) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Line intersects triangle, check if ray does.
|
||||
var QdN = -sign * diff.dot(normal);
|
||||
|
||||
// t < 0, no intersection
|
||||
if (QdN < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ray intersects triangle.
|
||||
return this.at(QdN / DdN);
|
||||
}
|
||||
}
|
||||
|
||||
class Plane {
|
||||
public var normal = new Vec4(1.0, 0.0, 0.0);
|
||||
public var constant = 0.0;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function distanceToPoint(point: Vec4): FastFloat {
|
||||
return normal.dot(point) + constant;
|
||||
}
|
||||
|
||||
public function set(normal: Vec4, point: Vec4): Plane {
|
||||
this.normal.setFrom(normal);
|
||||
constant = -point.dot(this.normal);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
178
leenkx/Sources/iron/math/RayCaster.hx
Normal file
178
leenkx/Sources/iron/math/RayCaster.hx
Normal file
@ -0,0 +1,178 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
import iron.object.CameraObject;
|
||||
import iron.object.MeshObject;
|
||||
import iron.object.Transform;
|
||||
import iron.object.Object;
|
||||
import iron.math.Ray;
|
||||
|
||||
class RayCaster {
|
||||
|
||||
static var VPInv = Mat4.identity();
|
||||
static var PInv = Mat4.identity();
|
||||
static var VInv = Mat4.identity();
|
||||
|
||||
static var loc = new Vec4();
|
||||
static var nor = new Vec4();
|
||||
static var m = Mat4.identity();
|
||||
|
||||
public static function getRay(inputX: FastFloat, inputY: FastFloat, camera: CameraObject): Ray {
|
||||
var start = new Vec4();
|
||||
var end = new Vec4();
|
||||
getDirection(start, end, inputX, inputY, camera);
|
||||
|
||||
// Find direction from start to end
|
||||
end.sub(start);
|
||||
end.normalize();
|
||||
end.x *= camera.data.raw.far_plane;
|
||||
end.y *= camera.data.raw.far_plane;
|
||||
end.z *= camera.data.raw.far_plane;
|
||||
|
||||
return new Ray(start, end);
|
||||
}
|
||||
|
||||
public static function getDirection(start: Vec4, end: Vec4, inputX: FastFloat, inputY: FastFloat, camera: CameraObject) {
|
||||
// Get 3D point form screen coords
|
||||
// Set two vectors with opposing z values
|
||||
start.x = (inputX / iron.App.w()) * 2.0 - 1.0;
|
||||
start.y = -((inputY / iron.App.h()) * 2.0 - 1.0);
|
||||
start.z = -1.0;
|
||||
end.x = start.x;
|
||||
end.y = start.y;
|
||||
end.z = 1.0;
|
||||
|
||||
PInv.getInverse(camera.P);
|
||||
VInv.getInverse(camera.V);
|
||||
VPInv.multmats(VInv, PInv);
|
||||
start.applyproj(VPInv);
|
||||
end.applyproj(VPInv);
|
||||
}
|
||||
|
||||
public static function boxIntersect(transform: Transform, inputX: FastFloat, inputY: FastFloat, camera: CameraObject): Vec4 {
|
||||
var ray = getRay(inputX, inputY, camera);
|
||||
|
||||
var t = transform;
|
||||
var c = new Vec4(t.worldx(), t.worldy(), t.worldz());
|
||||
var s = new Vec4(t.dim.x, t.dim.y, t.dim.z);
|
||||
return ray.intersectBox(c, s);
|
||||
}
|
||||
|
||||
public static function boxIntersectObject(o: Object, inputX: FastFloat, inputY: FastFloat, camera: CameraObject): Vec4 {
|
||||
var ray = getRay(inputX, inputY, camera);
|
||||
|
||||
var t = o.transform;
|
||||
var c = new Vec4(t.worldx(), t.worldy(), t.worldz());
|
||||
var s = new Vec4(t.dim.x, t.dim.y, t.dim.z);
|
||||
return ray.intersectBox(c, s);
|
||||
}
|
||||
|
||||
public static function closestBoxIntersect(transforms: Array<Transform>, inputX: FastFloat, inputY: FastFloat, camera: CameraObject): Transform {
|
||||
var intersects: Array<Transform> = [];
|
||||
|
||||
// Get intersects
|
||||
for (t in transforms) {
|
||||
var intersect = boxIntersect(t, inputX, inputY, camera);
|
||||
if (intersect != null) intersects.push(t);
|
||||
}
|
||||
|
||||
// No intersects
|
||||
if (intersects.length == 0) return null;
|
||||
|
||||
// Get closest intersect
|
||||
var closest: Transform = null;
|
||||
var minDist = Math.POSITIVE_INFINITY;
|
||||
for (t in intersects) {
|
||||
var dist = Vec4.distance(t.loc, camera.transform.loc);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closest = t;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
public static function closestBoxIntersectObject(objects: Array<Object>, inputX: FastFloat, inputY: FastFloat, camera: CameraObject): Object {
|
||||
var intersects: Array<Object> = [];
|
||||
|
||||
// Get intersects
|
||||
for (o in objects) {
|
||||
var intersect = boxIntersectObject(o, inputX, inputY, camera);
|
||||
if (intersect != null) intersects.push(o);
|
||||
}
|
||||
|
||||
// No intersects
|
||||
if (intersects.length == 0) return null;
|
||||
|
||||
// Get closest intersect
|
||||
var closest: Object = null;
|
||||
var minDist = Math.POSITIVE_INFINITY;
|
||||
for (t in intersects) {
|
||||
var dist = Vec4.distance(t.transform.loc, camera.transform.loc);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closest = t;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
public static function planeIntersect(normal: Vec4, a: Vec4, inputX: FastFloat, inputY: FastFloat, camera: CameraObject): Vec4 {
|
||||
var ray = getRay(inputX, inputY, camera);
|
||||
|
||||
var plane = new Plane();
|
||||
plane.set(normal, a);
|
||||
|
||||
return ray.intersectPlane(plane);
|
||||
}
|
||||
|
||||
// Project screen-space point onto 3D plane
|
||||
public static function getPlaneUV(obj: MeshObject, screenX: FastFloat, screenY: FastFloat, camera: CameraObject): Vec2 {
|
||||
nor = obj.transform.up(); // Transformed normal
|
||||
|
||||
// Plane intersection
|
||||
loc.set(obj.transform.worldx(), obj.transform.worldy(), obj.transform.worldz());
|
||||
var hit = RayCaster.planeIntersect(nor, loc, screenX, screenY, camera);
|
||||
|
||||
// Convert to uv
|
||||
if (hit != null) {
|
||||
var normals = obj.data.geom.normals.values;
|
||||
nor.set(normals[0], normals[1], normals[2]); // Raw normal
|
||||
|
||||
var a = nor.x;
|
||||
var b = nor.y;
|
||||
var c = nor.z;
|
||||
var e = 0.0001;
|
||||
var u = a >= e && b >= e ? new Vec4(b, -a, 0) : new Vec4(c, -a, 0);
|
||||
u.normalize();
|
||||
|
||||
var v = nor.clone();
|
||||
v.cross(u);
|
||||
|
||||
m.setFrom(obj.transform.world);
|
||||
m.getInverse(m);
|
||||
m.transpose3x3();
|
||||
m._30 = m._31 = m._32 = 0;
|
||||
u.applymat(m);
|
||||
u.normalize();
|
||||
v.applymat(m);
|
||||
v.normalize();
|
||||
|
||||
hit.sub(loc); // Center
|
||||
var ucoord = u.dot(hit);
|
||||
var vcoord = v.dot(hit);
|
||||
|
||||
var dim = obj.transform.dim;
|
||||
var size = dim.x > dim.y ? dim.x / 2 : dim.y / 2;
|
||||
|
||||
// Screen space
|
||||
var ix = ucoord / size * -0.5 + 0.5;
|
||||
var iy = vcoord / size * -0.5 + 0.5;
|
||||
|
||||
return new Vec2(ix, iy);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
138
leenkx/Sources/iron/math/Vec2.hx
Normal file
138
leenkx/Sources/iron/math/Vec2.hx
Normal file
@ -0,0 +1,138 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
class Vec2 {
|
||||
public var x: FastFloat;
|
||||
public var y: FastFloat;
|
||||
|
||||
public inline function new(x: FastFloat = 0.0, y: FastFloat = 0.0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public inline function cross(v: Vec2): FastFloat {
|
||||
return x * v.y - y * v.x;
|
||||
}
|
||||
|
||||
public inline function set(x: FastFloat, y: FastFloat): Vec2{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function add(v: Vec2): Vec2 {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function addf(x: FastFloat, y: FastFloat): Vec2 {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function addvecs(a: Vec2, b: Vec2): Vec2 {
|
||||
x = a.x + b.x;
|
||||
y = a.y + b.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function subvecs(a: Vec2, b: Vec2): Vec2 {
|
||||
x = a.x - b.x;
|
||||
y = a.y - b.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function normalize(): Vec2 {
|
||||
var a = this.x;
|
||||
var b = this.y;
|
||||
var l = a * a + b * b;
|
||||
if (l > 0.0) {
|
||||
l = 1.0 / Math.sqrt(l);
|
||||
this.x = a * l;
|
||||
this.y = b * l;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function mult(f: FastFloat): Vec2 {
|
||||
x *= f;
|
||||
y *= f;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function dot(v: Vec2): FastFloat {
|
||||
return x * v.x + y * v.y;
|
||||
}
|
||||
|
||||
public inline function setFrom(v: Vec2): Vec2 {
|
||||
x = v.x;
|
||||
y = v.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function clone(): Vec2 {
|
||||
return new Vec2(x, y);
|
||||
}
|
||||
|
||||
public inline function lerp(from: Vec2, to: Vec2, s: FastFloat): Vec2 {
|
||||
x = from.x + (to.x - from.x) * s;
|
||||
y = from.y + (to.y - from.y) * s;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function equals(v: Vec2): Bool {
|
||||
return x == v.x && y == v.y;
|
||||
}
|
||||
|
||||
public inline function length(): FastFloat {
|
||||
return Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
public inline function sub(v: Vec2): Vec2 {
|
||||
x -= v.x;
|
||||
y -= v.y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function exp(v: Vec2): Vec2 {
|
||||
x = Math.exp(v.x);
|
||||
y = Math.exp(v.y);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static inline function distance(v1: Vec2, v2: Vec2): FastFloat {
|
||||
return distancef(v1.x, v1.y, v2.x, v2.y);
|
||||
}
|
||||
|
||||
public static inline function distancef(v1x: FastFloat, v1y: FastFloat, v2x: FastFloat, v2y: FastFloat): FastFloat {
|
||||
var vx = v1x - v2x;
|
||||
var vy = v1y - v2y;
|
||||
return Math.sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
|
||||
public inline function distanceTo(p: Vec2): FastFloat {
|
||||
return Math.sqrt((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y));
|
||||
}
|
||||
|
||||
public inline function clamp(min: FastFloat, max: FastFloat): Vec2 {
|
||||
var l = length();
|
||||
if (l < min) normalize().mult(min);
|
||||
else if (l > max) normalize().mult(max);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static inline function xAxis(): Vec2 {
|
||||
return new Vec2(1.0, 0.0);
|
||||
}
|
||||
|
||||
public static inline function yAxis(): Vec2 {
|
||||
return new Vec2(0.0, 1.0);
|
||||
}
|
||||
|
||||
public function toString(): String {
|
||||
return "(" + this.x + ", " + this.y + ")";
|
||||
}
|
||||
}
|
||||
181
leenkx/Sources/iron/math/Vec3.hx
Normal file
181
leenkx/Sources/iron/math/Vec3.hx
Normal file
@ -0,0 +1,181 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
class Vec3 {
|
||||
public var x: FastFloat;
|
||||
public var y: FastFloat;
|
||||
public var z: FastFloat;
|
||||
|
||||
public inline function new(x: FastFloat = 0.0, y: FastFloat = 0.0, z: FastFloat = 0.0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public inline function cross(v: Vec3): Vec3 {
|
||||
var ax = x; var ay = y; var az = z;
|
||||
var vx = v.x; var vy = v.y; var vz = v.z;
|
||||
x = ay * vz - az * vy;
|
||||
y = az * vx - ax * vz;
|
||||
z = ax * vy - ay * vx;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function crossvecs(a: Vec3, b: Vec3): Vec3 {
|
||||
var ax = a.x; var ay = a.y; var az = a.z;
|
||||
var bx = b.x; var by = b.y; var bz = b.z;
|
||||
x = ay * bz - az * by;
|
||||
y = az * bx - ax * bz;
|
||||
z = ax * by - ay * bx;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function set(x: FastFloat, y: FastFloat, z: FastFloat): Vec3{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function add(v: Vec3): Vec3 {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
z += v.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function addf(x: FastFloat, y: FastFloat, z: FastFloat): Vec3 {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function addvecs(a: Vec3, b: Vec3): Vec3 {
|
||||
x = a.x + b.x;
|
||||
y = a.y + b.y;
|
||||
z = a.z + b.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function subvecs(a: Vec3, b: Vec3): Vec3 {
|
||||
x = a.x - b.x;
|
||||
y = a.y - b.y;
|
||||
z = a.z - b.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function normalize(): Vec3 {
|
||||
var n = length();
|
||||
if (n > 0.0) {
|
||||
var invN = 1.0 / n;
|
||||
this.x *= invN; this.y *= invN; this.z *= invN;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function mult(f: FastFloat): Vec3 {
|
||||
x *= f;
|
||||
y *= f;
|
||||
z *= f;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function dot(v: Vec3): FastFloat {
|
||||
return x * v.x + y * v.y + z * v.z;
|
||||
}
|
||||
|
||||
public inline function setFrom(v: Vec3): Vec3 {
|
||||
x = v.x;
|
||||
y = v.y;
|
||||
z = v.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function clone(): Vec3 {
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
|
||||
public inline function lerp(from: Vec3, to: Vec3, s: FastFloat): Vec3 {
|
||||
x = from.x + (to.x - from.x) * s;
|
||||
y = from.y + (to.y - from.y) * s;
|
||||
z = from.z + (to.z - from.z) * s;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function applyproj(m: Mat4): Vec3 {
|
||||
var x = this.x; var y = this.y; var z = this.z;
|
||||
var d = 1.0 / (m._03 * x + m._13 * y + m._23 * z + m._33); // Perspective divide
|
||||
this.x = (m._00 * x + m._10 * y + m._20 * z + m._30) * d;
|
||||
this.y = (m._01 * x + m._11 * y + m._21 * z + m._31) * d;
|
||||
this.z = (m._02 * x + m._12 * y + m._22 * z + m._32) * d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function applymat(m: Mat4): Vec3 {
|
||||
var x = this.x; var y = this.y; var z = this.z;
|
||||
this.x = m._00 * x + m._10 * y + m._20 * z + m._30;
|
||||
this.y = m._01 * x + m._11 * y + m._21 * z + m._31;
|
||||
this.z = m._02 * x + m._12 * y + m._22 * z + m._32;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function equals(v: Vec3): Bool {
|
||||
return x == v.x && y == v.y && z == v.z;
|
||||
}
|
||||
|
||||
public inline function length(): FastFloat {
|
||||
return Math.sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
public inline function sub(v: Vec3): Vec3 {
|
||||
x -= v.x; y -= v.y; z -= v.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function exp(v: Vec3): Vec3 {
|
||||
x = Math.exp(v.x);
|
||||
y = Math.exp(v.y);
|
||||
z = Math.exp(v.z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static inline function distance(v1: Vec3, v2: Vec3): FastFloat {
|
||||
return distancef(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z);
|
||||
}
|
||||
|
||||
public static inline function distancef(v1x: FastFloat, v1y: FastFloat, v1z: FastFloat, v2x: FastFloat, v2y: FastFloat, v2z: FastFloat): FastFloat {
|
||||
var vx = v1x - v2x;
|
||||
var vy = v1y - v2y;
|
||||
var vz = v1z - v2z;
|
||||
return Math.sqrt(vx * vx + vy * vy + vz * vz);
|
||||
}
|
||||
|
||||
public inline function distanceTo(p: Vec3): FastFloat {
|
||||
return Math.sqrt((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) + (p.z - z) * (p.z - z));
|
||||
}
|
||||
|
||||
public inline function clamp(min: FastFloat, max: FastFloat): Vec3 {
|
||||
var l = length();
|
||||
if (l < min) normalize().mult(min);
|
||||
else if (l > max) normalize().mult(max);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static inline function xAxis(): Vec3 {
|
||||
return new Vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
public static inline function yAxis(): Vec3 {
|
||||
return new Vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
public static inline function zAxis(): Vec3 {
|
||||
return new Vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
public function toString(): String {
|
||||
return "(" + this.x + ", " + this.y + ", " + this.z + ")";
|
||||
}
|
||||
}
|
||||
226
leenkx/Sources/iron/math/Vec4.hx
Normal file
226
leenkx/Sources/iron/math/Vec4.hx
Normal file
@ -0,0 +1,226 @@
|
||||
package iron.math;
|
||||
|
||||
import kha.FastFloat;
|
||||
|
||||
class Vec4 {
|
||||
|
||||
public var x: FastFloat;
|
||||
public var y: FastFloat;
|
||||
public var z: FastFloat;
|
||||
public var w: FastFloat;
|
||||
|
||||
public inline function new(x: FastFloat = 0.0, y: FastFloat = 0.0, z: FastFloat = 0.0, w: FastFloat = 1.0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public inline function cross(v: Vec4): Vec4 {
|
||||
var ax = x; var ay = y; var az = z;
|
||||
var vx = v.x; var vy = v.y; var vz = v.z;
|
||||
x = ay * vz - az * vy;
|
||||
y = az * vx - ax * vz;
|
||||
z = ax * vy - ay * vx;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function crossvecs(a: Vec4, b: Vec4): Vec4 {
|
||||
var ax = a.x; var ay = a.y; var az = a.z;
|
||||
var bx = b.x; var by = b.y; var bz = b.z;
|
||||
x = ay * bz - az * by;
|
||||
y = az * bx - ax * bz;
|
||||
z = ax * by - ay * bx;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function set(x: FastFloat, y: FastFloat, z: FastFloat, w: FastFloat = 1.0): Vec4{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function add(v: Vec4): Vec4 {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
z += v.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function addf(x: FastFloat, y: FastFloat, z: FastFloat): Vec4 {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function addvecs(a: Vec4, b: Vec4): Vec4 {
|
||||
x = a.x + b.x;
|
||||
y = a.y + b.y;
|
||||
z = a.z + b.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function subvecs(a: Vec4, b: Vec4): Vec4 {
|
||||
x = a.x - b.x;
|
||||
y = a.y - b.y;
|
||||
z = a.z - b.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function normalize(): Vec4 {
|
||||
var n = length();
|
||||
if (n > 0.0) {
|
||||
var invN = 1.0 / n;
|
||||
this.x *= invN;
|
||||
this.y *= invN;
|
||||
this.z *= invN;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function mult(f: FastFloat): Vec4 {
|
||||
x *= f;
|
||||
y *= f;
|
||||
z *= f;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function dot(v: Vec4): FastFloat {
|
||||
return x * v.x + y * v.y + z * v.z;
|
||||
}
|
||||
|
||||
public inline function setFrom(v: Vec4): Vec4 {
|
||||
x = v.x;
|
||||
y = v.y;
|
||||
z = v.z;
|
||||
w = v.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function clone(): Vec4 {
|
||||
return new Vec4(x, y, z, w);
|
||||
}
|
||||
|
||||
public inline function lerp(from: Vec4, to: Vec4, s: FastFloat): Vec4 {
|
||||
x = from.x + (to.x - from.x) * s;
|
||||
y = from.y + (to.y - from.y) * s;
|
||||
z = from.z + (to.z - from.z) * s;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function applyproj(m: Mat4): Vec4 {
|
||||
var x = this.x; var y = this.y; var z = this.z;
|
||||
var d = 1.0 / (m._03 * x + m._13 * y + m._23 * z + m._33); // Perspective divide
|
||||
this.x = (m._00 * x + m._10 * y + m._20 * z + m._30) * d;
|
||||
this.y = (m._01 * x + m._11 * y + m._21 * z + m._31) * d;
|
||||
this.z = (m._02 * x + m._12 * y + m._22 * z + m._32) * d;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function applymat(m: Mat4): Vec4 {
|
||||
var x = this.x; var y = this.y; var z = this.z;
|
||||
this.x = m._00 * x + m._10 * y + m._20 * z + m._30;
|
||||
this.y = m._01 * x + m._11 * y + m._21 * z + m._31;
|
||||
this.z = m._02 * x + m._12 * y + m._22 * z + m._32;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function applymat4(m: Mat4): Vec4 {
|
||||
var x = this.x; var y = this.y; var z = this.z; var w = this.w;
|
||||
this.x = m._00 * x + m._10 * y + m._20 * z + m._30 * w;
|
||||
this.y = m._01 * x + m._11 * y + m._21 * z + m._31 * w;
|
||||
this.z = m._02 * x + m._12 * y + m._22 * z + m._32 * w;
|
||||
this.w = m._03 * x + m._13 * y + m._23 * z + m._33 * w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function applyAxisAngle(axis: Vec4, angle: FastFloat): Vec4 {
|
||||
var quat = new Quat();
|
||||
quat.fromAxisAngle(axis, angle);
|
||||
return applyQuat(quat);
|
||||
}
|
||||
|
||||
public inline function applyQuat(q: Quat): Vec4 {
|
||||
var ix = q.w * x + q.y * z - q.z * y;
|
||||
var iy = q.w * y + q.z * x - q.x * z;
|
||||
var iz = q.w * z + q.x * y - q.y * x;
|
||||
var iw = -q.x * x - q.y * y - q.z * z;
|
||||
x = ix * q.w + iw * -q.x + iy * -q.z - iz * -q.y;
|
||||
y = iy * q.w + iw * -q.y + iz * -q.x - ix * -q.z;
|
||||
z = iz * q.w + iw * -q.z + ix * -q.y - iy * -q.x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function equals(v: Vec4): Bool {
|
||||
return x == v.x && y == v.y && z == v.z;
|
||||
}
|
||||
|
||||
public inline function almostEquals(v: Vec4, prec: FastFloat): Bool {
|
||||
return Math.abs(x - v.x) < prec && Math.abs(y - v.y) < prec && Math.abs(z - v.z) < prec;
|
||||
}
|
||||
|
||||
public inline function length(): FastFloat {
|
||||
return Math.sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
public inline function sub(v: Vec4): Vec4 {
|
||||
x -= v.x; y -= v.y; z -= v.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function exp(v: Vec4): Vec4 {
|
||||
x = Math.exp(v.x);
|
||||
y = Math.exp(v.y);
|
||||
z = Math.exp(v.z);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static inline function distance(v1: Vec4, v2: Vec4): FastFloat {
|
||||
return distancef(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z);
|
||||
}
|
||||
|
||||
public static inline function distancef(v1x: FastFloat, v1y: FastFloat, v1z: FastFloat, v2x: FastFloat, v2y: FastFloat, v2z: FastFloat): FastFloat {
|
||||
var vx = v1x - v2x;
|
||||
var vy = v1y - v2y;
|
||||
var vz = v1z - v2z;
|
||||
return Math.sqrt(vx * vx + vy * vy + vz * vz);
|
||||
}
|
||||
|
||||
public inline function distanceTo(p: Vec4): FastFloat {
|
||||
return Math.sqrt((p.x - x) * (p.x - x) + (p.y - y) * (p.y - y) + (p.z - z) * (p.z - z));
|
||||
}
|
||||
|
||||
public inline function reflect(n: Vec4): Vec4 {
|
||||
var d = 2 * this.dot(n);
|
||||
x = x - d * n.x;
|
||||
y = y - d * n.y;
|
||||
z = z - d * n.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public inline function clamp(min: FastFloat, max: FastFloat): Vec4 {
|
||||
var l = length();
|
||||
if (l < min) normalize().mult(min);
|
||||
else if (l > max) normalize().mult(max);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static inline function xAxis(): Vec4 {
|
||||
return new Vec4(1.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
public static inline function yAxis(): Vec4 {
|
||||
return new Vec4(0.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
public static inline function zAxis(): Vec4 {
|
||||
return new Vec4(0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
public function toString(): String {
|
||||
return "(" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + ")";
|
||||
}
|
||||
}
|
||||
447
leenkx/Sources/iron/object/Animation.hx
Normal file
447
leenkx/Sources/iron/object/Animation.hx
Normal file
@ -0,0 +1,447 @@
|
||||
package iron.object;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import iron.math.Vec3;
|
||||
import iron.math.Vec2;
|
||||
import kha.FastFloat;
|
||||
import kha.arrays.Uint32Array;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class Animation {
|
||||
|
||||
public var isSkinned: Bool;
|
||||
public var isSampled: Bool;
|
||||
public var action = "";
|
||||
#if lnx_skin
|
||||
public var armature: iron.data.Armature; // Bone
|
||||
#end
|
||||
|
||||
// Helper variables.
|
||||
static var m1 = Mat4.identity();
|
||||
static var m2 = Mat4.identity();
|
||||
static var vpos = new Vec4();
|
||||
static var vpos2 = new Vec4();
|
||||
static var vscl = new Vec4();
|
||||
static var vscl2 = new Vec4();
|
||||
static var q1 = new Quat();
|
||||
static var q2 = new Quat();
|
||||
static var q3 = new Quat();
|
||||
static var vp = new Vec4();
|
||||
static var vs = new Vec4();
|
||||
|
||||
public var time: FastFloat = 0.0;
|
||||
public var speed: FastFloat = 1.0;
|
||||
public var loop = true;
|
||||
public var frameIndex = 0;
|
||||
public var onComplete: Void->Void = null;
|
||||
public var paused = false;
|
||||
var frameTime: FastFloat = 1 / 60;
|
||||
|
||||
var blendTime: FastFloat = 0.0;
|
||||
var blendCurrent: FastFloat = 0.0;
|
||||
var blendFactor: FastFloat = 0.0;
|
||||
|
||||
var lastFrameIndex = -1;
|
||||
var markerEvents: Map<ActionSampler, Map<String, Array<Void->Void>>> = null;
|
||||
|
||||
public var activeActions: Map<String, ActionSampler> = null;
|
||||
|
||||
function new() {
|
||||
Scene.active.animations.push(this);
|
||||
if (Scene.active.raw.frame_time != null) {
|
||||
frameTime = Scene.active.raw.frame_time;
|
||||
}
|
||||
play();
|
||||
}
|
||||
|
||||
public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
||||
if (blendTime > 0) {
|
||||
this.blendTime = blendTime;
|
||||
this.blendCurrent = 0.0;
|
||||
frameIndex = 0;
|
||||
time = 0.0;
|
||||
}
|
||||
else frameIndex = -1;
|
||||
this.action = action;
|
||||
this.onComplete = onComplete;
|
||||
this.speed = speed;
|
||||
this.loop = loop;
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
paused = true;
|
||||
}
|
||||
|
||||
public function resume() {
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public function remove() {
|
||||
Scene.active.animations.remove(this);
|
||||
}
|
||||
|
||||
public function updateActionTrack(sampler: ActionSampler){
|
||||
return;
|
||||
}
|
||||
|
||||
public function update(delta: FastFloat) {
|
||||
if(activeActions == null) return;
|
||||
|
||||
for(sampler in activeActions){
|
||||
if (sampler.paused || sampler.speed == 0.0) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
sampler.timeOld = sampler.time;
|
||||
sampler.offsetOld = sampler.offset;
|
||||
sampler.setTimeOnly(sampler.time + delta * sampler.speed);
|
||||
updateActionTrack(sampler);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function registerAction(actionID: String, sampler: ActionSampler){
|
||||
if (activeActions == null) activeActions = new Map();
|
||||
activeActions.set(actionID, sampler);
|
||||
}
|
||||
|
||||
public function deRegisterAction(actionID: String) {
|
||||
if (activeActions == null) return;
|
||||
if(activeActions.exists(actionID)) activeActions.remove(actionID);
|
||||
|
||||
}
|
||||
|
||||
inline function isTrackEnd(track: TTrack, frameIndex: Int, speed: FastFloat): Bool {
|
||||
return speed > 0 ?
|
||||
frameIndex >= track.frames.length - 1 :
|
||||
frameIndex <= 0;
|
||||
}
|
||||
|
||||
inline function checkFrameIndex(frameValues: Uint32Array, time: FastFloat, frameIndex: Int, speed: FastFloat): Bool {
|
||||
return speed > 0 ?
|
||||
((frameIndex + 1) < frameValues.length && time > frameValues[frameIndex + 1] * frameTime) :
|
||||
((frameIndex - 1) > -1 && time < frameValues[frameIndex - 1] * frameTime);
|
||||
}
|
||||
|
||||
function rewind(track: TTrack) {
|
||||
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
||||
time = track.frames[frameIndex] * frameTime;
|
||||
}
|
||||
|
||||
function updateTrack(anim: TAnimation, sampler: ActionSampler) {
|
||||
|
||||
var time = sampler.time;
|
||||
var frameIndex = sampler.offset;
|
||||
var speed = sampler.speed;
|
||||
sampler.cacheSet = false;
|
||||
sampler.trackEnd = false;
|
||||
|
||||
var track = anim.tracks[0];
|
||||
|
||||
if (frameIndex == -1) {
|
||||
sampler.timeOld = sampler.time;
|
||||
sampler.offsetOld = sampler.offset;
|
||||
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
||||
time = track.frames[frameIndex] * frameTime;
|
||||
}
|
||||
|
||||
// Move keyframe
|
||||
var sign = speed > 0 ? 1 : -1;
|
||||
while (checkFrameIndex(track.frames, time, frameIndex, speed)) frameIndex += sign;
|
||||
|
||||
// Marker events
|
||||
if (markerEvents != null && anim.marker_names != null && frameIndex != lastFrameIndex) {
|
||||
if(markerEvents.get(sampler) != null){
|
||||
for (i in 0...anim.marker_frames.length) {
|
||||
if (frameIndex == anim.marker_frames[i]) {
|
||||
var marketAct = markerEvents.get(sampler);
|
||||
var ar = marketAct.get(anim.marker_names[i]);
|
||||
if (ar != null) for (f in ar) f();
|
||||
}
|
||||
}
|
||||
lastFrameIndex = frameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// End of track
|
||||
if (isTrackEnd(track, frameIndex, speed)) {
|
||||
if (sampler.loop) {
|
||||
sampler.offsetOld = frameIndex;
|
||||
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
||||
time = track.frames[frameIndex] * frameTime;
|
||||
}
|
||||
else {
|
||||
frameIndex -= sign;
|
||||
sampler.paused = true;
|
||||
}
|
||||
if (sampler.onComplete != null) for(func in sampler.onComplete){ func();};
|
||||
sampler.trackEnd = true;
|
||||
}
|
||||
|
||||
sampler.setFrameOffsetOnly(frameIndex);
|
||||
sampler.speed = speed;
|
||||
sampler.setTimeOnly(time);
|
||||
|
||||
}
|
||||
|
||||
public function notifyOnMarker(sampler: ActionSampler, name: String, onMarker: Void->Void) {
|
||||
if (markerEvents == null) markerEvents = new Map();
|
||||
|
||||
var markerAct = markerEvents.get(sampler);
|
||||
if(markerAct == null){
|
||||
markerAct = new Map();
|
||||
markerEvents.set(sampler, markerAct);
|
||||
}
|
||||
|
||||
var ar = markerAct.get(name);
|
||||
if (ar == null) {
|
||||
ar = [];
|
||||
markerAct.set(name, ar);
|
||||
}
|
||||
ar.push(onMarker);
|
||||
}
|
||||
|
||||
public function removeMarker(sampler: ActionSampler, name: String, onMarker: Void->Void) {
|
||||
var markerAct = markerEvents.get(sampler);
|
||||
if(markerAct == null) return;
|
||||
|
||||
markerAct.get(name).remove(onMarker);
|
||||
}
|
||||
|
||||
public function currentFrame(): Int {
|
||||
return Std.int(time / frameTime);
|
||||
}
|
||||
|
||||
public function getTotalFrames(sampler: ActionSampler): Int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
public static var animationTime = 0.0;
|
||||
static var startTime = 0.0;
|
||||
|
||||
static function beginProfile() {
|
||||
startTime = kha.Scheduler.realTime();
|
||||
}
|
||||
static function endProfile() {
|
||||
animationTime += kha.Scheduler.realTime() - startTime;
|
||||
}
|
||||
public static function endFrame() {
|
||||
animationTime = 0;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Sampler State.
|
||||
*/
|
||||
class ActionSampler {
|
||||
|
||||
/**
|
||||
* Name of the action.
|
||||
*/
|
||||
public var action(default, null): String;
|
||||
/**
|
||||
* Current time of the sampler.
|
||||
*/
|
||||
public var time(default, null): FastFloat = 0.0;
|
||||
/**
|
||||
* Current frame of the sampler.
|
||||
*/
|
||||
public var offset(default, null): Int = 0;
|
||||
/**
|
||||
* Total frames in the action.
|
||||
*/
|
||||
public var totalFrames: Null<Int> = null;
|
||||
/**
|
||||
* Speed of action sampling.
|
||||
*/
|
||||
public var speed: FastFloat;
|
||||
/**
|
||||
* Loop action.
|
||||
*/
|
||||
public var loop: Bool;
|
||||
/**
|
||||
* Sampler paused.
|
||||
*/
|
||||
public var paused: Bool = false;
|
||||
/**
|
||||
* Callback functions to call after action ends.
|
||||
*/
|
||||
public var onComplete: Array<Void -> Void>;
|
||||
/**
|
||||
* Action track ended.
|
||||
*/
|
||||
public var trackEnd: Bool = false;
|
||||
public var timeOld: FastFloat = 0.0;
|
||||
public var offsetOld: Int = 0;
|
||||
/**
|
||||
* Cache action data objects. May be Bones or Objects.
|
||||
*/
|
||||
var actionData: Array<TObj> = null;
|
||||
/**
|
||||
* Action data has been cached.
|
||||
*/
|
||||
public var actionDataInit(default, null): Bool = false;
|
||||
/**
|
||||
* Positional Root Motion for this action.
|
||||
*/
|
||||
public var rootMotionPos: Bool = false;
|
||||
/**
|
||||
* Rotational Root Motion for this action.
|
||||
*/
|
||||
public var rootMotionRot: Bool = false;
|
||||
/**
|
||||
* Action matrix from previous sample. Mainly used for root motion.
|
||||
*/
|
||||
var actionCache: Mat4 = Mat4.identity();
|
||||
/**
|
||||
* `actionCache` set this frame.
|
||||
*/
|
||||
public var cacheSet: Bool = false;
|
||||
/**
|
||||
* `actionCache` initialized. Set to false to force reset cache.
|
||||
*/
|
||||
public var cacheInit(default, null): Bool = false;
|
||||
|
||||
/**
|
||||
* Create a new action sampler.
|
||||
* @param action Name of the action.
|
||||
* @param speed Speed of sampler.
|
||||
* @param loop Loop after action ends.
|
||||
* @param startPaused Do not start sample on init.
|
||||
* @param onComplete Callback functions after action completes.
|
||||
*/
|
||||
public inline function new(action: String, speed: FastFloat = 1.0, loop: Bool = true, startPaused: Bool = false, onComplete: Array<Void -> Void> = null) {
|
||||
this.action = action;
|
||||
this.speed = speed;
|
||||
this.loop = loop;
|
||||
this.onComplete = onComplete;
|
||||
this.paused = startPaused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current frame of the sampler. Time is calculated.
|
||||
* @param frameOffset Frame.
|
||||
*/
|
||||
public inline function setFrameOffset(frameOffset: Int){
|
||||
this.offset = frameOffset;
|
||||
this.time = Scene.active.raw.frame_time * offset;
|
||||
cacheInit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current time of the sampler. Frame is calculated.
|
||||
* @param timeOffset Time.
|
||||
*/
|
||||
public inline function setTimeOffset(timeOffset: FastFloat){
|
||||
this.time = timeOffset;
|
||||
var ftime: FastFloat = Scene.active.raw.frame_time;
|
||||
this.offset = Std.int(time / ftime);
|
||||
cacheInit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart action.
|
||||
*/
|
||||
public inline function restartAction() {
|
||||
this.setFrameOffset(0);
|
||||
paused = false;
|
||||
cacheInit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback function when action completes.
|
||||
* @param onComplete Callback
|
||||
*/
|
||||
public function notifyOnComplete(onComplete: Void -> Void) {
|
||||
if(this.onComplete == null) this.onComplete = [];
|
||||
this.onComplete.push(onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove callback function
|
||||
* @param onComplete Callback
|
||||
*/
|
||||
public function removeOnComplete(onComplete: Void -> Void) {
|
||||
this.onComplete.remove(onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time offset only. Frame will not be set.
|
||||
* @param time Time.
|
||||
*/
|
||||
public inline function setTimeOnly(time: FastFloat) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set frame offset only. Time will not be set.
|
||||
* @param frame Frame
|
||||
*/
|
||||
public inline function setFrameOffsetOnly(frame: Int) {
|
||||
this.offset = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw bones data for bone animation.
|
||||
* @return Null<Array<TObj>> Raw bone action data.
|
||||
*/
|
||||
public inline function getBoneAction(): Null<Array<TObj>> {
|
||||
return actionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw object data for object animation.
|
||||
* @return Null<TObj> Raw object action data.
|
||||
*/
|
||||
public inline function getObjectAction(): Null<TObj> {
|
||||
if(actionData != null) return actionData[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache raw bones data for bone animation.
|
||||
* @param actionData Raw bone data.
|
||||
*/
|
||||
public inline function setBoneAction(actionData: Array<TObj>) {
|
||||
this.actionData = actionData;
|
||||
this.totalFrames = actionData[0].anim.tracks[0].frames.length;
|
||||
if(actionData[0].anim.root_motion_pos) this.rootMotionPos = true;
|
||||
if(actionData[0].anim.root_motion_rot) this.rootMotionRot = true;
|
||||
actionDataInit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache raw object data for object animation.
|
||||
* @param actionData Raw object data.
|
||||
*/
|
||||
public inline function setObjectAction(actionData: TObj) {
|
||||
this.actionData = [actionData];
|
||||
this.totalFrames = actionData.anim.tracks[0].frames.length;
|
||||
actionDataInit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary cache of action matrix from previous frame.
|
||||
* @param m Matrix to cache.
|
||||
*/
|
||||
public inline function setActionCache(m: Mat4) {
|
||||
if(! cacheSet) actionCache.setFrom(m);
|
||||
cacheSet = true;
|
||||
cacheInit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy cahced action matrix and to the matrix.
|
||||
* @param m Matrix to copy the cache to.
|
||||
*/
|
||||
public inline function getActionCache(m: Mat4) {
|
||||
m.setFrom(actionCache);
|
||||
}
|
||||
}
|
||||
1168
leenkx/Sources/iron/object/BoneAnimation.hx
Normal file
1168
leenkx/Sources/iron/object/BoneAnimation.hx
Normal file
File diff suppressed because it is too large
Load Diff
233
leenkx/Sources/iron/object/CameraObject.hx
Normal file
233
leenkx/Sources/iron/object/CameraObject.hx
Normal file
@ -0,0 +1,233 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.graphics4.Graphics;
|
||||
import kha.graphics4.CubeMap;
|
||||
import iron.Scene;
|
||||
import iron.RenderPath;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.CameraData;
|
||||
|
||||
class CameraObject extends Object {
|
||||
|
||||
public var data: CameraData;
|
||||
public var P: Mat4;
|
||||
#if lnx_taa
|
||||
public var noJitterP = Mat4.identity();
|
||||
var frame = 0;
|
||||
#end
|
||||
public var V: Mat4;
|
||||
public var prevV: Mat4 = null;
|
||||
public var VP: Mat4;
|
||||
public var frustumPlanes: Array<FrustumPlane> = null;
|
||||
public var renderTarget: kha.Image = null; // Render camera view to texture
|
||||
public var renderTargetCube: CubeMap = null;
|
||||
public var currentFace = 0;
|
||||
|
||||
static var temp = new Vec4();
|
||||
static var q = new Quat();
|
||||
static var sphereCenter = new Vec4();
|
||||
static var vcenter = new Vec4();
|
||||
static var vup = new Vec4();
|
||||
|
||||
public function new(data: CameraData) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
|
||||
buildProjection();
|
||||
|
||||
V = Mat4.identity();
|
||||
VP = Mat4.identity();
|
||||
|
||||
if (data.raw.frustum_culling) {
|
||||
frustumPlanes = [];
|
||||
for (i in 0...6) frustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
|
||||
Scene.active.cameras.push(this);
|
||||
}
|
||||
|
||||
public function buildProjection(screenAspect: Null<Float> = null) {
|
||||
if (data.raw.ortho != null) {
|
||||
P = Mat4.ortho(data.raw.ortho[0], data.raw.ortho[1], data.raw.ortho[2], data.raw.ortho[3], data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
else {
|
||||
if (screenAspect == null) screenAspect = iron.App.w() / iron.App.h();
|
||||
var aspect = data.raw.aspect != null ? data.raw.aspect : screenAspect;
|
||||
P = Mat4.persp(data.raw.fov, aspect, data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
#if lnx_taa
|
||||
noJitterP.setFrom(P);
|
||||
#end
|
||||
}
|
||||
|
||||
override public function remove() {
|
||||
Scene.active.cameras.remove(this);
|
||||
// if (renderTarget != null) renderTarget.unload();
|
||||
// if (renderTargetCube != null) renderTargetCube.unload();
|
||||
super.remove();
|
||||
}
|
||||
|
||||
public function renderFrame(g: Graphics) {
|
||||
#if lnx_taa
|
||||
projectionJitter();
|
||||
#end
|
||||
|
||||
buildMatrix();
|
||||
|
||||
RenderPath.active.renderFrame(g);
|
||||
|
||||
prevV.setFrom(V);
|
||||
}
|
||||
|
||||
#if lnx_taa
|
||||
function projectionJitter() {
|
||||
var w = RenderPath.active.currentW;
|
||||
var h = RenderPath.active.currentH;
|
||||
P.setFrom(noJitterP);
|
||||
var x = 0.0;
|
||||
var y = 0.0;
|
||||
if (frame % 2 == 0) {
|
||||
x = 0.25;
|
||||
y = 0.25;
|
||||
}
|
||||
else {
|
||||
x = -0.25;
|
||||
y = -0.25;
|
||||
}
|
||||
P._20 += x / w;
|
||||
P._21 += y / h;
|
||||
frame++;
|
||||
}
|
||||
#end
|
||||
|
||||
public function buildMatrix() {
|
||||
transform.buildMatrix();
|
||||
|
||||
// Prevent camera matrix scaling
|
||||
// TODO: discards position affected by scaled camera parent
|
||||
var sc = transform.world.getScale();
|
||||
if (sc.x != 1.0 || sc.y != 1.0 || sc.z != 1.0) {
|
||||
temp.set(1.0 / sc.x, 1.0 / sc.y, 1.0 / sc.z);
|
||||
transform.world.scale(temp);
|
||||
}
|
||||
|
||||
V.getInverse(transform.world);
|
||||
VP.multmats(P, V);
|
||||
|
||||
if (data.raw.frustum_culling) {
|
||||
buildViewFrustum(VP, frustumPlanes);
|
||||
}
|
||||
|
||||
// First time setting up previous V, prevents first frame flicker
|
||||
if (prevV == null) {
|
||||
prevV = Mat4.identity();
|
||||
prevV.setFrom(V);
|
||||
}
|
||||
}
|
||||
|
||||
public static function buildViewFrustum(VP: Mat4, frustumPlanes: Array<FrustumPlane>) {
|
||||
// Left plane
|
||||
frustumPlanes[0].setComponents(VP._03 + VP._00, VP._13 + VP._10, VP._23 + VP._20, VP._33 + VP._30);
|
||||
// Right plane
|
||||
frustumPlanes[1].setComponents(VP._03 - VP._00, VP._13 - VP._10, VP._23 - VP._20, VP._33 - VP._30);
|
||||
// Top plane
|
||||
frustumPlanes[2].setComponents(VP._03 - VP._01, VP._13 - VP._11, VP._23 - VP._21, VP._33 - VP._31);
|
||||
// Bottom plane
|
||||
frustumPlanes[3].setComponents(VP._03 + VP._01, VP._13 + VP._11, VP._23 + VP._21, VP._33 + VP._31);
|
||||
// Near plane
|
||||
frustumPlanes[4].setComponents(VP._02, VP._12, VP._22, VP._32);
|
||||
// Far plane
|
||||
frustumPlanes[5].setComponents(VP._03 - VP._02, VP._13 - VP._12, VP._23 - VP._22, VP._33 - VP._32);
|
||||
// Normalize planes
|
||||
for (plane in frustumPlanes) plane.normalize();
|
||||
}
|
||||
|
||||
public static function sphereInFrustum(frustumPlanes: Array<FrustumPlane>, t: Transform, radiusScale = 1.0, offsetX = 0.0, offsetY = 0.0, offsetZ = 0.0): Bool {
|
||||
// Use scale when radius is changing
|
||||
var radius = t.radius * radiusScale;
|
||||
for (plane in frustumPlanes) {
|
||||
sphereCenter.set(t.worldx() + offsetX, t.worldy() + offsetY, t.worldz() + offsetZ);
|
||||
// Outside the frustum
|
||||
if (plane.distanceToSphere(sphereCenter, radius) + radius * 2 < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function setCubeFace(m: Mat4, eye: Vec4, face: Int, flip = false) {
|
||||
// Set matrix to match cubemap face
|
||||
vcenter.setFrom(eye);
|
||||
var f = flip ? -1.0 : 1.0;
|
||||
switch (face) {
|
||||
case 0: // x+
|
||||
vcenter.addf(1.0 * f, 0.0, 0.0);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
case 1: // x-
|
||||
vcenter.addf(-1.0 * f, 0.0, 0.0);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
case 2: // y+
|
||||
vcenter.addf(0.0, 1.0 * f, 0.0);
|
||||
vup.set(0.0, 0.0, 1.0 * f);
|
||||
case 3: // y-
|
||||
vcenter.addf(0.0, -1.0 * f, 0.0);
|
||||
vup.set(0.0, 0.0, -1.0 * f);
|
||||
case 4: // z+
|
||||
vcenter.addf(0.0, 0.0, 1.0 * f);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
case 5: // z-
|
||||
vcenter.addf(0.0, 0.0, -1.0 * f);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
}
|
||||
m.setLookAt(eye, vcenter, vup);
|
||||
}
|
||||
|
||||
public inline function right(): Vec4 {
|
||||
return new Vec4(transform.local._00, transform.local._01, transform.local._02);
|
||||
}
|
||||
|
||||
public inline function up(): Vec4 {
|
||||
return new Vec4(transform.local._10, transform.local._11, transform.local._12);
|
||||
}
|
||||
|
||||
public inline function look(): Vec4 {
|
||||
return new Vec4(-transform.local._20, -transform.local._21, -transform.local._22);
|
||||
}
|
||||
|
||||
public inline function rightWorld(): Vec4 {
|
||||
return new Vec4(transform.world._00, transform.world._01, transform.world._02);
|
||||
}
|
||||
|
||||
public inline function upWorld(): Vec4 {
|
||||
return new Vec4(transform.world._10, transform.world._11, transform.world._12);
|
||||
}
|
||||
|
||||
public inline function lookWorld(): Vec4 {
|
||||
return new Vec4(-transform.world._20, -transform.world._21, -transform.world._22);
|
||||
}
|
||||
}
|
||||
|
||||
class FrustumPlane {
|
||||
public var normal = new Vec4(1.0, 0.0, 0.0);
|
||||
public var constant = 0.0;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function normalize() {
|
||||
var inverseNormalLength = 1.0 / normal.length();
|
||||
normal.mult(inverseNormalLength);
|
||||
constant *= inverseNormalLength;
|
||||
}
|
||||
|
||||
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
|
||||
return (normal.dot(sphereCenter) + constant) - sphereRadius;
|
||||
}
|
||||
|
||||
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
|
||||
normal.set(x, y, z);
|
||||
constant = w;
|
||||
}
|
||||
}
|
||||
10
leenkx/Sources/iron/object/Clipmap.hx
Normal file
10
leenkx/Sources/iron/object/Clipmap.hx
Normal file
@ -0,0 +1,10 @@
|
||||
package iron.object;
|
||||
|
||||
class Clipmap {
|
||||
public var voxelSize = 0.125;
|
||||
public var extents:iron.math.Vec3;
|
||||
public var center:iron.math.Vec3;
|
||||
public var offset_prev:iron.math.Vec3;
|
||||
|
||||
public function new() {};
|
||||
}
|
||||
31
leenkx/Sources/iron/object/Constraint.hx
Normal file
31
leenkx/Sources/iron/object/Constraint.hx
Normal file
@ -0,0 +1,31 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class Constraint {
|
||||
var raw: TConstraint;
|
||||
var target: Transform = null;
|
||||
|
||||
public function new(constr: TConstraint) {
|
||||
raw = constr;
|
||||
}
|
||||
|
||||
public function apply(transform: Transform) {
|
||||
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform;
|
||||
|
||||
if (raw.type == "COPY_LOCATION") {
|
||||
if (raw.use_x) {
|
||||
transform.world._30 = target.loc.x;
|
||||
if (raw.use_offset) transform.world._30 += transform.loc.x;
|
||||
}
|
||||
if (raw.use_y) {
|
||||
transform.world._31 = target.loc.y;
|
||||
if (raw.use_offset) transform.world._31 += transform.loc.y;
|
||||
}
|
||||
if (raw.use_z) {
|
||||
transform.world._32 = target.loc.z;
|
||||
if (raw.use_offset) transform.world._32 += transform.loc.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
leenkx/Sources/iron/object/DecalObject.hx
Normal file
57
leenkx/Sources/iron/object/DecalObject.hx
Normal file
@ -0,0 +1,57 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.graphics4.Graphics;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.ConstData;
|
||||
import iron.object.Uniforms;
|
||||
|
||||
class DecalObject extends Object {
|
||||
|
||||
#if rp_decals
|
||||
|
||||
public var material: MaterialData;
|
||||
|
||||
public function new(material: MaterialData) {
|
||||
super();
|
||||
this.material = material;
|
||||
Scene.active.decals.push(this);
|
||||
}
|
||||
|
||||
public override function remove() {
|
||||
if (Scene.active != null) Scene.active.decals.remove(this);
|
||||
super.remove();
|
||||
}
|
||||
|
||||
// Called before rendering decal in render path
|
||||
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||
|
||||
// Check context skip
|
||||
if (material.raw.skip_context != null &&
|
||||
material.raw.skip_context == context) {
|
||||
return;
|
||||
}
|
||||
|
||||
transform.update();
|
||||
|
||||
var materialContext: MaterialContext = null;
|
||||
for (i in 0...material.raw.contexts.length) {
|
||||
if (material.raw.contexts[i].name == context) {
|
||||
materialContext = material.contexts[i]; // Single material decals
|
||||
break;
|
||||
}
|
||||
}
|
||||
var shaderContext = material.shader.getContext(context);
|
||||
|
||||
g.setPipeline(shaderContext.pipeState);
|
||||
|
||||
Uniforms.setContextConstants(g, shaderContext, bindParams);
|
||||
Uniforms.setObjectConstants(g, shaderContext, this);
|
||||
Uniforms.setMaterialConstants(g, shaderContext, materialContext);
|
||||
|
||||
g.setVertexBuffer(ConstData.boxVB);
|
||||
g.setIndexBuffer(ConstData.boxIB);
|
||||
g.drawIndexedVertices();
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
||||
677
leenkx/Sources/iron/object/LightObject.hx
Normal file
677
leenkx/Sources/iron/object/LightObject.hx
Normal file
@ -0,0 +1,677 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.graphics4.TextureFormat;
|
||||
import kha.graphics4.Usage;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Vec4;
|
||||
import iron.data.LightData;
|
||||
import iron.object.CameraObject;
|
||||
|
||||
class LightObject extends Object {
|
||||
|
||||
public var data: LightData;
|
||||
|
||||
#if rp_shadowmap
|
||||
#if lnx_shadowmap_atlas
|
||||
public var tileNotifyOnRemove: Void -> Void;
|
||||
public var lightInAtlas = false;
|
||||
public var lightInAtlasTransparent = false;
|
||||
public var culledLight = false;
|
||||
public static var pointLightsData: kha.arrays.Float32Array = null;
|
||||
public var shadowMapScale = 1.0; // When in forward if this defaults to 0.0, the atlas are not drawn before being bound.
|
||||
// Data used in uniforms
|
||||
public var tileOffsetX: Array<Float> = [0.0];
|
||||
public var tileOffsetY: Array<Float> = [0.0];
|
||||
public var tileScale: Array<Float> = [1.0];
|
||||
#end
|
||||
// Cascades
|
||||
public static var cascadeCount = 1;
|
||||
public static var cascadeSplitFactor = 0.8;
|
||||
public static var cascadeBounds = 1.0;
|
||||
#if lnx_csm
|
||||
var cascadeData: Float32Array = null;
|
||||
var cascadeVP: Array<Mat4>;
|
||||
var camSlicedP: Array<Mat4> = null;
|
||||
var cascadeSplit: Array<kha.FastFloat>;
|
||||
var bias = Mat4.identity();
|
||||
#else
|
||||
var camSlicedP: Mat4 = null;
|
||||
#end
|
||||
#end // rp_shadowmap
|
||||
|
||||
#if (lnx_csm || lnx_clusters)
|
||||
static var helpMat = Mat4.identity();
|
||||
#end
|
||||
|
||||
// Clusters
|
||||
#if lnx_clusters
|
||||
static var slicesX = 16;
|
||||
static var slicesY = 16;
|
||||
static var slicesZ = 16;
|
||||
static inline var maxLights = getMaxLights();
|
||||
public static inline var maxLightsCluster = getMaxLightsCluster(); // Mirror shader constant
|
||||
static inline var clusterNear = 3.0;
|
||||
public static var lightsArray: Float32Array = null;
|
||||
#if lnx_spot
|
||||
public static var lightsArraySpot: Float32Array = null;
|
||||
#end
|
||||
public static var clustersData: kha.Image = null;
|
||||
static var lpos = new Vec4();
|
||||
public static var LWVPMatrixArray: Float32Array = null;
|
||||
#end // lnx_clusters
|
||||
|
||||
public var V: Mat4 = Mat4.identity();
|
||||
public var P: Mat4 = null;
|
||||
public var VP: Mat4 = Mat4.identity();
|
||||
|
||||
public var frustumPlanes: Array<FrustumPlane> = null;
|
||||
static var m = Mat4.identity();
|
||||
static var eye = new Vec4();
|
||||
#if rp_shadowmap
|
||||
static var corners: Array<Vec4> = null;
|
||||
#end
|
||||
|
||||
public function new(data: LightData) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
|
||||
var type = data.raw.type;
|
||||
var fov = data.raw.fov;
|
||||
|
||||
if (type == "sun") {
|
||||
#if rp_shadowmap
|
||||
if (corners == null) {
|
||||
corners = [];
|
||||
for (i in 0...8) corners.push(new Vec4());
|
||||
}
|
||||
P = Mat4.identity();
|
||||
#else
|
||||
P = Mat4.ortho(-1, 1, -1, 1, data.raw.near_plane, data.raw.far_plane);
|
||||
#end
|
||||
#if lnx_shadowmap_atlas
|
||||
this.shadowMapScale = 1.0;
|
||||
#end
|
||||
}
|
||||
else if (type == "point" || type == "area") {
|
||||
P = Mat4.persp(fov, 1, data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
else if (type == "spot") {
|
||||
P = Mat4.persp(fov, 1, data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
|
||||
Scene.active.lights.push(this);
|
||||
}
|
||||
|
||||
override public function remove() {
|
||||
if (Scene.active != null) Scene.active.lights.remove(this);
|
||||
final rp = RenderPath.active;
|
||||
if (rp.light == this) { rp.light = null; }
|
||||
if (rp.point == this) { rp.point = null; }
|
||||
else if (rp.sun == this) { rp.sun = null; }
|
||||
#if rp_shadowmap
|
||||
#if lnx_shadowmap_atlas
|
||||
if (tileNotifyOnRemove != null) {
|
||||
tileNotifyOnRemove();
|
||||
tileNotifyOnRemove = null;
|
||||
}
|
||||
#end
|
||||
#end
|
||||
super.remove();
|
||||
}
|
||||
|
||||
public function buildMatrix(camera: CameraObject) {
|
||||
transform.buildMatrix();
|
||||
if (data.raw.type == "sun") { // Cover camera frustum
|
||||
#if (rp_shadowmap && !lnx_csm) // Otherwise set cascades on mesh draw
|
||||
setCascade(camera, 0);
|
||||
#else
|
||||
V.getInverse(transform.world);
|
||||
updateViewFrustum(camera);
|
||||
#end
|
||||
}
|
||||
else { // Point, spot, area
|
||||
V.getInverse(transform.world);
|
||||
updateViewFrustum(camera);
|
||||
}
|
||||
}
|
||||
|
||||
#if rp_shadowmap
|
||||
|
||||
static inline function setCorners() {
|
||||
corners[0].set(-1.0, -1.0, 1.0);
|
||||
corners[1].set(-1.0, -1.0, -1.0);
|
||||
corners[2].set(-1.0, 1.0, 1.0);
|
||||
corners[3].set(-1.0, 1.0, -1.0);
|
||||
corners[4].set(1.0, -1.0, 1.0);
|
||||
corners[5].set(1.0, -1.0, -1.0);
|
||||
corners[6].set(1.0, 1.0, 1.0);
|
||||
corners[7].set(1.0, 1.0, -1.0);
|
||||
}
|
||||
|
||||
static inline function mix(a: Float, b: Float, f: Float): Float {
|
||||
return a * (1 - f) + b * f;
|
||||
}
|
||||
|
||||
public function setCascade(camera: CameraObject, cascade: Int) {
|
||||
m.setFrom(camera.V);
|
||||
|
||||
#if lnx_csm
|
||||
if (camSlicedP == null) {
|
||||
camSlicedP = [];
|
||||
cascadeSplit = [];
|
||||
var ortho = camera.data.raw.ortho;
|
||||
if (ortho == null) {
|
||||
var aspect = camera.data.raw.aspect != null ? camera.data.raw.aspect : iron.App.w() / iron.App.h();
|
||||
var fov = camera.data.raw.fov;
|
||||
var near = camera.data.raw.near_plane;
|
||||
var far = camera.data.raw.far_plane;
|
||||
var factor = cascadeCount > 2 ? cascadeSplitFactor : cascadeSplitFactor * 0.25;
|
||||
for (i in 0...cascadeCount) {
|
||||
var f = i + 1.0;
|
||||
var cfar = mix(
|
||||
near + (f / cascadeCount) * (far - near),
|
||||
near * Math.pow(far / near, f / cascadeCount),
|
||||
factor);
|
||||
cascadeSplit.push(cfar);
|
||||
camSlicedP.push(Mat4.persp(fov, aspect, near, cfar));
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i in 0...cascadeCount) {
|
||||
cascadeSplit.push(data.raw.far_plane);
|
||||
camSlicedP.push(Mat4.ortho(ortho[0], ortho[1], ortho[2], ortho[3], data.raw.near_plane, data.raw.far_plane));
|
||||
}
|
||||
}
|
||||
}
|
||||
m.multmat(camSlicedP[cascade]);
|
||||
#else
|
||||
if (camSlicedP == null) { // Fit to light far plane
|
||||
var ortho = camera.data.raw.ortho;
|
||||
if (ortho == null) {
|
||||
var fov = camera.data.raw.fov;
|
||||
var near = data.raw.near_plane;
|
||||
var far = data.raw.far_plane;
|
||||
var aspect = camera.data.raw.aspect != null ? camera.data.raw.aspect : iron.App.w() / iron.App.h();
|
||||
camSlicedP = Mat4.persp(fov, aspect, near, far);
|
||||
}
|
||||
else {
|
||||
// camSlicedP = camera.P;
|
||||
camSlicedP = Mat4.ortho(ortho[0], ortho[1], ortho[2], ortho[3], data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
}
|
||||
m.multmat(camSlicedP);
|
||||
#end
|
||||
|
||||
m.getInverse(m);
|
||||
V.getInverse(transform.world);
|
||||
V.toRotation();
|
||||
m.multmat(V);
|
||||
setCorners();
|
||||
for (v in corners) {
|
||||
v.applymat4(m);
|
||||
v.set(v.x / v.w, v.y / v.w, v.z / v.w);
|
||||
}
|
||||
|
||||
var minx = corners[0].x;
|
||||
var miny = corners[0].y;
|
||||
var minz = corners[0].z;
|
||||
var maxx = corners[0].x;
|
||||
var maxy = corners[0].y;
|
||||
var maxz = corners[0].z;
|
||||
for (v in corners) {
|
||||
if (v.x < minx) minx = v.x;
|
||||
if (v.x > maxx) maxx = v.x;
|
||||
if (v.y < miny) miny = v.y;
|
||||
if (v.y > maxy) maxy = v.y;
|
||||
if (v.z < minz) minz = v.z;
|
||||
if (v.z > maxz) maxz = v.z;
|
||||
}
|
||||
|
||||
// Adjust frustum size by longest diagonal - fix rotation swim
|
||||
var diag0 = Vec4.distance(corners[0], corners[7]);
|
||||
var offx = (diag0 - (maxx - minx)) * 0.5;
|
||||
var offy = (diag0 - (maxy - miny)) * 0.5;
|
||||
minx -= offx;
|
||||
maxx += offx;
|
||||
miny -= offy;
|
||||
maxy += offy;
|
||||
|
||||
// Snap to texel coords - fix translation swim
|
||||
var smsize = data.raw.shadowmap_size;
|
||||
#if lnx_csm // Cascades
|
||||
smsize = Std.int(smsize / 4);
|
||||
#end
|
||||
var worldPerTexelX = (maxx - minx) / smsize;
|
||||
var worldPerTexelY = (maxy - miny) / smsize;
|
||||
var worldPerTexelZ = (maxz - minz) / smsize;
|
||||
minx = Math.floor(minx / worldPerTexelX) * worldPerTexelX;
|
||||
miny = Math.floor(miny / worldPerTexelY) * worldPerTexelY;
|
||||
minz = Math.floor(minz / worldPerTexelZ) * worldPerTexelZ;
|
||||
maxx = Math.floor(maxx / worldPerTexelX) * worldPerTexelX;
|
||||
maxy = Math.floor(maxy / worldPerTexelY) * worldPerTexelY;
|
||||
maxz = Math.floor(maxz / worldPerTexelZ) * worldPerTexelZ;
|
||||
|
||||
var hx = (maxx - minx) / 2;
|
||||
var hy = (maxy - miny) / 2;
|
||||
var hz = (maxz - minz) / 2;
|
||||
V._30 = -(minx + hx);
|
||||
V._31 = -(miny + hy);
|
||||
V._32 = -(minz + hz);
|
||||
|
||||
// (-hz * 4 * cascadeBounds) - include shadow casters out of view frustum
|
||||
m = Mat4.ortho(-hx, hx, -hy, hy, -hz * 4 * cascadeBounds, hz);
|
||||
P.setFrom(m);
|
||||
|
||||
updateViewFrustum(camera);
|
||||
|
||||
#if lnx_csm
|
||||
if (cascadeVP == null) {
|
||||
cascadeVP = [];
|
||||
for (i in 0...cascadeCount) {
|
||||
cascadeVP.push(Mat4.identity());
|
||||
}
|
||||
}
|
||||
cascadeVP[cascade].setFrom(VP);
|
||||
#end
|
||||
}
|
||||
#end // rp_shadowmap
|
||||
|
||||
function updateViewFrustum(camera: CameraObject) {
|
||||
VP.multmats(P, V);
|
||||
|
||||
// Frustum culling enabled
|
||||
if (camera.data.raw.frustum_culling) {
|
||||
if (frustumPlanes == null) {
|
||||
frustumPlanes = [];
|
||||
for (i in 0...6) frustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
CameraObject.buildViewFrustum(VP, frustumPlanes);
|
||||
}
|
||||
}
|
||||
|
||||
public function setCubeFace(face: Int, camera: CameraObject) {
|
||||
// Set matrix to match cubemap face
|
||||
eye.set(transform.worldx(), transform.worldy(), transform.worldz());
|
||||
#if (!kha_opengl && !kha_webgl && !lnx_shadowmap_atlas)
|
||||
var flip = (face == 2 || face == 3) ? true : false; // Flip +Y, -Y
|
||||
#else
|
||||
var flip = false;
|
||||
#end
|
||||
CameraObject.setCubeFace(V, eye, face, flip);
|
||||
updateViewFrustum(camera);
|
||||
}
|
||||
|
||||
#if lnx_csm
|
||||
public function getCascadeData(): Float32Array {
|
||||
// Cascade mats + split distances
|
||||
if (cascadeData == null) {
|
||||
cascadeData = new Float32Array(cascadeCount * 16 + 4);
|
||||
}
|
||||
if (cascadeVP == null) return cascadeData;
|
||||
|
||||
// 4 cascade mats + split distances
|
||||
for (i in 0...cascadeCount) {
|
||||
m.setFrom(cascadeVP[i]);
|
||||
bias.setFrom(Uniforms.biasMat);
|
||||
#if (!lnx_shadowmap_atlas)
|
||||
bias._00 /= cascadeCount; // Atlas offset
|
||||
bias._30 /= cascadeCount;
|
||||
bias._30 += i * (1 / cascadeCount);
|
||||
#else
|
||||
// tile matrix
|
||||
helpMat.setIdentity();
|
||||
// scale [0-1] coords to [0-tilescale]
|
||||
helpMat._00 = this.tileScale[i];
|
||||
helpMat._11 = this.tileScale[i];
|
||||
// offset coordinate start from [0, 0] to [tile-start-x, tile-start-y]
|
||||
helpMat._30 = this.tileOffsetX[i];
|
||||
helpMat._31 = this.tileOffsetY[i];
|
||||
bias.multmat(helpMat);
|
||||
#if (!kha_opengl)
|
||||
helpMat.setIdentity();
|
||||
helpMat._11 = -1.0;
|
||||
helpMat._31 = 1.0;
|
||||
bias.multmat(helpMat);
|
||||
#end
|
||||
#end
|
||||
m.multmat(bias);
|
||||
cascadeData[i * 16] = m._00;
|
||||
cascadeData[i * 16 + 1] = m._01;
|
||||
cascadeData[i * 16 + 2] = m._02;
|
||||
cascadeData[i * 16 + 3] = m._03;
|
||||
cascadeData[i * 16 + 4] = m._10;
|
||||
cascadeData[i * 16 + 5] = m._11;
|
||||
cascadeData[i * 16 + 6] = m._12;
|
||||
cascadeData[i * 16 + 7] = m._13;
|
||||
cascadeData[i * 16 + 8] = m._20;
|
||||
cascadeData[i * 16 + 9] = m._21;
|
||||
cascadeData[i * 16 + 10] = m._22;
|
||||
cascadeData[i * 16 + 11] = m._23;
|
||||
cascadeData[i * 16 + 12] = m._30;
|
||||
cascadeData[i * 16 + 13] = m._31;
|
||||
cascadeData[i * 16 + 14] = m._32;
|
||||
cascadeData[i * 16 + 15] = m._33;
|
||||
}
|
||||
cascadeData[cascadeCount * 16 ] = cascadeSplit[0];
|
||||
cascadeData[cascadeCount * 16 + 1] = cascadeSplit[1];
|
||||
cascadeData[cascadeCount * 16 + 2] = cascadeSplit[2];
|
||||
cascadeData[cascadeCount * 16 + 3] = cascadeSplit[3];
|
||||
return cascadeData;
|
||||
}
|
||||
#end // lnx_csm
|
||||
|
||||
#if lnx_clusters
|
||||
|
||||
// Centralize discarding conditions when iterating over lights
|
||||
// Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray)
|
||||
public inline static function discardLight(light: LightObject) {
|
||||
return !light.visible || light.data.raw.strength == 0.0 || light.data.raw.type == "sun";
|
||||
}
|
||||
// Discarding conditions but with culling included
|
||||
public inline static function discardLightCulled(light: LightObject) {
|
||||
return #if lnx_shadowmap_atlas light.culledLight || #end discardLight(light);
|
||||
}
|
||||
|
||||
#if (lnx_shadowmap_atlas && lnx_shadowmap_atlas_lod)
|
||||
// Arbitrary function to map from [0-16] to [1.0-0.0]
|
||||
public inline static function zToShadowMapScale(z: Int, max: Int): Float {
|
||||
return 0.25 * Math.sqrt(-z + max);
|
||||
}
|
||||
#end
|
||||
|
||||
static function getRadius(strength: kha.FastFloat): kha.FastFloat {
|
||||
// (1.0 / (dist * dist)) * strength = 0.01
|
||||
return Math.sqrt(strength / 0.004);
|
||||
}
|
||||
|
||||
inline static function distSliceX(f: Float, lpos: Vec4): Float {
|
||||
return (lpos.x - f * lpos.z) / Math.sqrt(1.0 + f * f);
|
||||
}
|
||||
|
||||
inline static function distSliceY(f: Float, lpos: Vec4): Float {
|
||||
return (lpos.y - f * lpos.z) / Math.sqrt(1.0 + f * f);
|
||||
}
|
||||
|
||||
static function sliceToDist(camera: CameraObject, z: Int): Float {
|
||||
var cnear = clusterNear + camera.data.raw.near_plane;
|
||||
switch (z) {
|
||||
case 0:
|
||||
return camera.data.raw.near_plane;
|
||||
case 1:
|
||||
return cnear;
|
||||
default: {
|
||||
var depthl = (z - 1) / (slicesZ - 1);
|
||||
return Math.exp(depthl * Math.log(camera.data.raw.far_plane - cnear + 1.0)) + cnear - 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function updateClusters(camera: CameraObject) {
|
||||
// Reference: https://newq.net/publications/more/s2015-many-lights-course
|
||||
var lights = Scene.active.lights;
|
||||
|
||||
#if lnx_spot // Point lamps first
|
||||
lights.sort(function(a, b): Int {
|
||||
return a.data.raw.type >= b.data.raw.type ? 1 : -1;
|
||||
});
|
||||
#end
|
||||
|
||||
if (clustersData == null) {
|
||||
var lines = #if (lnx_spot) 2 #else 1 #end;
|
||||
clustersData = kha.Image.create(slicesX * slicesY * slicesZ, lines + maxLightsCluster, TextureFormat.L8, Usage.DynamicUsage);
|
||||
}
|
||||
|
||||
var bytes = clustersData.lock();
|
||||
|
||||
var stride = slicesX * slicesY * slicesZ;
|
||||
for (i in 0...stride) {
|
||||
bytes.set(i, 0);
|
||||
#if lnx_spot
|
||||
bytes.set(i + stride * (maxLightsCluster + 1), 0);
|
||||
#end
|
||||
}
|
||||
|
||||
var fovtan = Math.tan(camera.data.raw.fov * 0.5);
|
||||
var stepY = (2.0 * fovtan) / slicesY;
|
||||
var aspect = RenderPath.active.currentW / RenderPath.active.currentH;
|
||||
var stepX = (2.0 * fovtan * aspect) / slicesX;
|
||||
|
||||
var n = lights.length > maxLights ? maxLights : lights.length;
|
||||
var i = 0;
|
||||
for (l in lights) {
|
||||
if (discardLight(l)) continue;
|
||||
if (i >= n) break;
|
||||
// Light bounds
|
||||
lpos.set(l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
|
||||
lpos.applymat4(camera.V);
|
||||
lpos.z *= -1.0;
|
||||
var radius = getRadius(l.data.raw.strength);
|
||||
var minX = 0;
|
||||
var minY = 0;
|
||||
var minZ = 0;
|
||||
var maxX = slicesX;
|
||||
var maxY = slicesY;
|
||||
var maxZ = slicesZ;
|
||||
while (minX <= slicesX) {
|
||||
if (distSliceX(stepX * (minX + 1 - slicesX * 0.5), lpos) <= radius) break;
|
||||
minX++;
|
||||
}
|
||||
while (maxX >= minX) {
|
||||
if (-distSliceX(stepX * (maxX - 1 - slicesX * 0.5), lpos) <= radius) { maxX--; break; }
|
||||
maxX--;
|
||||
}
|
||||
while (minY <= slicesY) {
|
||||
if (distSliceY(stepY * (minY + 1 - slicesY * 0.5), lpos) <= radius) break;
|
||||
minY++;
|
||||
}
|
||||
while (maxY >= minY) {
|
||||
if (-distSliceY(stepY * (maxY - 1 - slicesY * 0.5), lpos) <= radius) { maxY--; break; }
|
||||
maxY--;
|
||||
}
|
||||
while (minZ <= slicesZ) {
|
||||
if (sliceToDist(camera, minZ + 1) >= lpos.z - radius) break;
|
||||
minZ++;
|
||||
}
|
||||
while (maxZ >= minZ) {
|
||||
if (sliceToDist(camera, maxZ - 1) <= lpos.z + radius) break;
|
||||
maxZ--;
|
||||
}
|
||||
#if lnx_shadowmap_atlas
|
||||
l.culledLight = maxZ < 0 || minX > maxX || minY > maxY;
|
||||
l.shadowMapScale = l.culledLight ? 0.0 : #if lnx_shadowmap_atlas_lod zToShadowMapScale(minZ, slicesZ) #else 1.0 #end;
|
||||
// Discard lights that are outside of the view
|
||||
if (l.culledLight) {
|
||||
continue;
|
||||
}
|
||||
#end
|
||||
// Mark affected clusters
|
||||
for (z in minZ...maxZ + 1) {
|
||||
for (y in minY...maxY + 1) {
|
||||
for (x in minX...maxX + 1) {
|
||||
var cluster = x + y * slicesX + z * slicesX * slicesY;
|
||||
var numLights = bytes.get(cluster);
|
||||
if (numLights < maxLightsCluster) {
|
||||
numLights++;
|
||||
bytes.set(cluster, numLights);
|
||||
bytes.set(cluster + stride * numLights, i);
|
||||
#if lnx_spot
|
||||
if (l.data.raw.type == "spot") {
|
||||
// Last line
|
||||
var numSpots = bytes.get(cluster + stride * (maxLightsCluster + 1)) + 1;
|
||||
bytes.set(cluster + stride * (maxLightsCluster + 1), numSpots);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
clustersData.unlock();
|
||||
|
||||
updateLightsArray(); // TODO: only update on light change
|
||||
}
|
||||
|
||||
static function updateLightsArray() {
|
||||
if (lightsArray == null) { // vec4x3 - 1: pos, a, color, b, 2: dir, c
|
||||
lightsArray = new Float32Array(maxLights * 4 * 3);
|
||||
#if lnx_spot
|
||||
lightsArraySpot = new Float32Array(maxLights * 4 * 2);
|
||||
#end
|
||||
}
|
||||
var lights = Scene.active.lights;
|
||||
var n = lights.length > maxLights ? maxLights : lights.length;
|
||||
var i = 0;
|
||||
for (l in lights) {
|
||||
if (discardLightCulled(l)) continue;
|
||||
if (i >= n) break;
|
||||
|
||||
// light position
|
||||
lightsArray[i * 12 ] = l.transform.worldx();
|
||||
lightsArray[i * 12 + 1] = l.transform.worldy();
|
||||
lightsArray[i * 12 + 2] = l.transform.worldz();
|
||||
lightsArray[i * 12 + 3] = 0.0; // padding or spot scale x
|
||||
|
||||
// light color
|
||||
var f = l.data.raw.strength;
|
||||
lightsArray[i * 12 + 4] = l.data.raw.color[0] * f;
|
||||
lightsArray[i * 12 + 5] = l.data.raw.color[1] * f;
|
||||
lightsArray[i * 12 + 6] = l.data.raw.color[2] * f;
|
||||
lightsArray[i * 12 + 7] = 0.0; // padding or spot scale y
|
||||
|
||||
// other data
|
||||
lightsArray[i * 12 + 8] = l.data.raw.shadows_bias; // bias
|
||||
lightsArray[i * 12 + 9] = 0.0; // cutoff for detecting spot
|
||||
lightsArray[i * 12 + 10] = l.data.raw.cast_shadow ? 1.0 : 0.0; // hasShadows
|
||||
lightsArray[i * 12 + 11] = 0.0; // padding
|
||||
|
||||
#if lnx_spot
|
||||
if (l.data.raw.type == "spot") {
|
||||
lightsArray[i * 12 + 9] = l.data.raw.spot_size;
|
||||
|
||||
var dir = l.look().normalize();
|
||||
lightsArraySpot[i * 8 ] = dir.x;
|
||||
lightsArraySpot[i * 8 + 1] = dir.y;
|
||||
lightsArraySpot[i * 8 + 2] = dir.z;
|
||||
lightsArraySpot[i * 8 + 3] = l.data.raw.spot_blend;
|
||||
|
||||
// Premultiply scale with z component
|
||||
var scale = l.transform.scale;
|
||||
lightsArray[i * 12 + 3] = scale.z == 0.0 ? 0.0 : scale.x / scale.z;
|
||||
lightsArray[i * 12 + 7] = scale.z == 0.0 ? 0.0 : scale.y / scale.z;
|
||||
|
||||
final right = l.right().normalize();
|
||||
lightsArraySpot[i * 8 + 4] = right.x;
|
||||
lightsArraySpot[i * 8 + 5] = right.y;
|
||||
lightsArraySpot[i * 8 + 6] = right.z;
|
||||
lightsArraySpot[i * 8 + 7] = 0.0; // padding
|
||||
}
|
||||
#end
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public static function updateLWVPMatrixArray(object: Object, type: String) {
|
||||
if (LWVPMatrixArray == null) {
|
||||
LWVPMatrixArray = new Float32Array(maxLightsCluster * 16);
|
||||
}
|
||||
|
||||
var lights = Scene.active.lights;
|
||||
var n = lights.length > maxLightsCluster ? maxLightsCluster : lights.length;
|
||||
var i = 0;
|
||||
|
||||
for (light in lights) {
|
||||
if (i >= n) {
|
||||
break;
|
||||
}
|
||||
if (discardLightCulled(light)) continue;
|
||||
if (light.data.raw.type == type) {
|
||||
m.setFrom(light.VP);
|
||||
m.multmat(Uniforms.biasMat);
|
||||
#if lnx_shadowmap_atlas
|
||||
// tile matrix
|
||||
helpMat.setIdentity();
|
||||
// scale [0-1] coords to [0-tilescale]
|
||||
helpMat._00 = light.tileScale[0];
|
||||
helpMat._11 = light.tileScale[0];
|
||||
// offset coordinate start from [0, 0] to [tile-start-x, tile-start-y]
|
||||
helpMat._30 = light.tileOffsetX[0];
|
||||
helpMat._31 = light.tileOffsetY[0];
|
||||
m.multmat(helpMat);
|
||||
#if (!kha_opengl)
|
||||
helpMat.setIdentity();
|
||||
helpMat._11 = -1.0;
|
||||
helpMat._31 = 1.0;
|
||||
m.multmat(helpMat);
|
||||
#end
|
||||
#end
|
||||
|
||||
LWVPMatrixArray[i * 16 ] = m._00;
|
||||
LWVPMatrixArray[i * 16 + 1] = m._01;
|
||||
LWVPMatrixArray[i * 16 + 2] = m._02;
|
||||
LWVPMatrixArray[i * 16 + 3] = m._03;
|
||||
LWVPMatrixArray[i * 16 + 4] = m._10;
|
||||
LWVPMatrixArray[i * 16 + 5] = m._11;
|
||||
LWVPMatrixArray[i * 16 + 6] = m._12;
|
||||
LWVPMatrixArray[i * 16 + 7] = m._13;
|
||||
LWVPMatrixArray[i * 16 + 8] = m._20;
|
||||
LWVPMatrixArray[i * 16 + 9] = m._21;
|
||||
LWVPMatrixArray[i * 16 + 10] = m._22;
|
||||
LWVPMatrixArray[i * 16 + 11] = m._23;
|
||||
LWVPMatrixArray[i * 16 + 12] = m._30;
|
||||
LWVPMatrixArray[i * 16 + 13] = m._31;
|
||||
LWVPMatrixArray[i * 16 + 14] = m._32;
|
||||
LWVPMatrixArray[i * 16 + 15] = m._33;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return LWVPMatrixArray;
|
||||
}
|
||||
|
||||
public static inline function getMaxLights(): Int {
|
||||
#if (rp_max_lights == 8)
|
||||
return 8;
|
||||
#elseif (rp_max_lights == 16)
|
||||
return 16;
|
||||
#elseif (rp_max_lights == 24)
|
||||
return 24;
|
||||
#elseif (rp_max_lights == 32)
|
||||
return 32;
|
||||
#elseif (rp_max_lights == 64)
|
||||
return 64;
|
||||
#else
|
||||
return 4;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function getMaxLightsCluster(): Int {
|
||||
#if (rp_max_lights_cluster == 8)
|
||||
return 8;
|
||||
#elseif (rp_max_lights_cluster == 16)
|
||||
return 16;
|
||||
#elseif (rp_max_lights_cluster == 24)
|
||||
return 24;
|
||||
#elseif (rp_max_lights_cluster == 32)
|
||||
return 32;
|
||||
#elseif (rp_max_lights_cluster == 64)
|
||||
return 64;
|
||||
#else
|
||||
return 4;
|
||||
#end
|
||||
}
|
||||
#end // lnx_clusters
|
||||
|
||||
public inline function right(): Vec4 {
|
||||
return new Vec4(V._00, V._10, V._20);
|
||||
}
|
||||
|
||||
public inline function up(): Vec4 {
|
||||
return new Vec4(V._01, V._11, V._21);
|
||||
}
|
||||
|
||||
public inline function look(): Vec4 {
|
||||
return new Vec4(V._02, V._12, V._22);
|
||||
}
|
||||
}
|
||||
403
leenkx/Sources/iron/object/MeshObject.hx
Normal file
403
leenkx/Sources/iron/object/MeshObject.hx
Normal file
@ -0,0 +1,403 @@
|
||||
package iron.object;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import kha.graphics4.Graphics;
|
||||
import kha.graphics4.PipelineState;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.ShaderData;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class MeshObject extends Object {
|
||||
|
||||
public var data: MeshData = null;
|
||||
public var materials: Vector<MaterialData>;
|
||||
public var materialIndex = 0;
|
||||
public var depthRead(default, null) = false;
|
||||
#if lnx_particles
|
||||
public var particleSystems: Array<ParticleSystem> = null; // Particle owner
|
||||
public var particleChildren: Array<MeshObject> = null;
|
||||
public var particleOwner: MeshObject = null; // Particle object
|
||||
public var particleIndex = -1;
|
||||
#end
|
||||
public var cameraDistance: Float;
|
||||
public var screenSize = 0.0;
|
||||
public var frustumCulling = true;
|
||||
public var activeTilesheet: Tilesheet = null;
|
||||
public var tilesheets: Array<Tilesheet> = null;
|
||||
public var skip_context: String = null; // Do not draw this context
|
||||
public var force_context: String = null; // Draw only this context
|
||||
static var lastPipeline: PipelineState = null;
|
||||
#if lnx_morph_target
|
||||
public var morphTarget: MorphTarget = null;
|
||||
#end
|
||||
|
||||
#if lnx_veloc
|
||||
public var prevMatrix = Mat4.identity();
|
||||
#end
|
||||
|
||||
public function new(data: MeshData, materials: Vector<MaterialData>) {
|
||||
super();
|
||||
|
||||
this.materials = materials;
|
||||
setData(data);
|
||||
Scene.active.meshes.push(this);
|
||||
}
|
||||
|
||||
public function setData(data: MeshData) {
|
||||
this.data = data;
|
||||
data.refcount++;
|
||||
|
||||
#if (!lnx_batch)
|
||||
data.geom.build();
|
||||
#end
|
||||
|
||||
// Scale-up packed (-1,1) mesh coords
|
||||
transform.scaleWorld = data.scalePos;
|
||||
}
|
||||
|
||||
#if lnx_batch
|
||||
@:allow(iron.Scene)
|
||||
function batch(isLod: Bool) {
|
||||
var batched = Scene.active.meshBatch.addMesh(this, isLod);
|
||||
if (!batched) data.geom.build();
|
||||
}
|
||||
#end
|
||||
|
||||
override public function remove() {
|
||||
#if lnx_batch
|
||||
Scene.active.meshBatch.removeMesh(this);
|
||||
#end
|
||||
#if lnx_particles
|
||||
if (particleChildren != null) {
|
||||
for (c in particleChildren) c.remove();
|
||||
particleChildren = null;
|
||||
}
|
||||
if (particleSystems != null) {
|
||||
for (psys in particleSystems) psys.remove();
|
||||
particleSystems = null;
|
||||
}
|
||||
#end
|
||||
if (activeTilesheet != null) activeTilesheet.remove();
|
||||
if (tilesheets != null) for (ts in tilesheets) { ts.remove(); }
|
||||
if (Scene.active != null) Scene.active.meshes.remove(this);
|
||||
data.refcount--;
|
||||
super.remove();
|
||||
}
|
||||
|
||||
override public function setupAnimation(oactions: Array<TSceneFormat> = null) {
|
||||
#if lnx_skin
|
||||
var hasAction = parent != null && parent.raw != null && parent.raw.bone_actions != null;
|
||||
if (hasAction) {
|
||||
var armatureUid = parent.uid;
|
||||
animation = getBoneAnimation(armatureUid);
|
||||
if (animation == null) animation = new BoneAnimation(armatureUid, parent);
|
||||
if (data.isSkinned) cast(animation, BoneAnimation).setSkin(this);
|
||||
}
|
||||
#end
|
||||
super.setupAnimation(oactions);
|
||||
}
|
||||
|
||||
#if lnx_morph_target
|
||||
override public function setupMorphTargets() {
|
||||
if (data.raw.morph_target != null) {
|
||||
morphTarget = new MorphTarget(data.raw.morph_target);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_particles
|
||||
public function setupParticleSystem(sceneName: String, pref: TParticleReference) {
|
||||
if (particleSystems == null) particleSystems = [];
|
||||
var psys = new ParticleSystem(sceneName, pref);
|
||||
particleSystems.push(psys);
|
||||
}
|
||||
#end
|
||||
|
||||
public function setupTilesheet(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) {
|
||||
activeTilesheet = new Tilesheet(sceneName, tilesheet_ref, tilesheet_action_ref);
|
||||
if(tilesheets == null) tilesheets = new Array<Tilesheet>();
|
||||
tilesheets.push(activeTilesheet);
|
||||
}
|
||||
|
||||
public function setActiveTilesheet(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) {
|
||||
var set = false;
|
||||
// Check if tilesheet already created
|
||||
if (tilesheets != null) {
|
||||
for (ts in tilesheets) {
|
||||
if (ts.raw.name == tilesheet_ref) {
|
||||
activeTilesheet = ts;
|
||||
activeTilesheet.play(tilesheet_action_ref);
|
||||
set = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If not already created
|
||||
if (!set) {
|
||||
setupTilesheet(sceneName, tilesheet_ref, tilesheet_action_ref);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline function isLodMaterial(): Bool {
|
||||
return (raw != null && raw.lod_material != null && raw.lod_material == true);
|
||||
}
|
||||
|
||||
function setCulled(isShadow: Bool, b: Bool): Bool {
|
||||
isShadow ? culledShadow = b : culledMesh = b;
|
||||
culled = culledMesh && culledShadow;
|
||||
#if lnx_debug
|
||||
if (b) RenderPath.culled++;
|
||||
#end
|
||||
return b;
|
||||
}
|
||||
|
||||
public function cullMaterial(context: String): Bool {
|
||||
// Skip render if material does not contain current context
|
||||
var mats = materials;
|
||||
if (!isLodMaterial() && !validContext(mats, context)) return true;
|
||||
|
||||
var isShadow = context == "shadowmap";
|
||||
if (!visibleMesh && !isShadow) return setCulled(isShadow, true);
|
||||
if (!visibleShadow && isShadow) return setCulled(isShadow, true);
|
||||
|
||||
if (skip_context == context) return setCulled(isShadow, true);
|
||||
if (force_context != null && force_context != context) return setCulled(isShadow, true);
|
||||
|
||||
return setCulled(isShadow, false);
|
||||
}
|
||||
|
||||
function cullMesh(context: String, camera: CameraObject, light: LightObject): Bool {
|
||||
if (camera == null) return false;
|
||||
|
||||
if (camera.data.raw.frustum_culling && frustumCulling) {
|
||||
// Scale radius for skinned mesh and particle system
|
||||
// TODO: define skin & particle bounds
|
||||
var radiusScale = data.isSkinned ? 2.0 : 1.0;
|
||||
#if lnx_particles
|
||||
// particleSystems for update, particleOwner for render
|
||||
if (particleSystems != null || particleOwner != null) radiusScale *= 1000;
|
||||
#end
|
||||
if (context == "voxel") radiusScale *= 100;
|
||||
if (data.geom.instanced) radiusScale *= 100;
|
||||
var isShadow = context == "shadowmap";
|
||||
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
|
||||
|
||||
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
|
||||
light.transform.radius = light.data.raw.far_plane;
|
||||
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, light.transform)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CameraObject.sphereInFrustum(frustumPlanes, transform, radiusScale)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
}
|
||||
|
||||
culled = false;
|
||||
return culled;
|
||||
}
|
||||
|
||||
function skipContext(context: String, mat: MaterialData): Bool {
|
||||
if (mat.raw.skip_context != null &&
|
||||
mat.raw.skip_context == context) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getContexts(context: String, materials: Vector<MaterialData>, materialContexts: Array<MaterialContext>, shaderContexts: Array<ShaderContext>) {
|
||||
for (mat in materials) {
|
||||
var found = false;
|
||||
for (i in 0...mat.raw.contexts.length) {
|
||||
if (mat.raw.contexts[i].name.substr(0, context.length) == context) {
|
||||
materialContexts.push(mat.contexts[i]);
|
||||
shaderContexts.push(mat.shader.getContext(context));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
materialContexts.push(null);
|
||||
shaderContexts.push(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||
if (data == null || !data.geom.ready) return; // Data not yet streamed
|
||||
if (!visible) return; // Skip render if object is hidden
|
||||
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
|
||||
var meshContext = raw != null ? context == "mesh" : false;
|
||||
|
||||
#if lnx_particles
|
||||
if (raw != null && raw.is_particle && particleOwner == null) return; // Instancing not yet set-up by particle system owner
|
||||
if (particleSystems != null && meshContext) {
|
||||
if (particleChildren == null) {
|
||||
particleChildren = [];
|
||||
for (psys in particleSystems) {
|
||||
// var c: MeshObject = cast Scene.active.getChild(psys.data.raw.instance_object);
|
||||
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
|
||||
if (o != null) {
|
||||
var c: MeshObject = cast o;
|
||||
particleChildren.push(c);
|
||||
c.particleOwner = this;
|
||||
c.particleIndex = particleChildren.length - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
for (i in 0...particleSystems.length) {
|
||||
particleSystems[i].update(particleChildren[i], this);
|
||||
}
|
||||
}
|
||||
if (particleSystems != null && particleSystems.length > 0 && !raw.render_emitter) return;
|
||||
#end
|
||||
|
||||
if (cullMaterial(context)) return;
|
||||
|
||||
// Get lod
|
||||
var mats = materials;
|
||||
var lod = this;
|
||||
if (raw != null && raw.lods != null && raw.lods.length > 0) {
|
||||
computeScreenSize(Scene.active.camera);
|
||||
initLods();
|
||||
if (context == "voxel") {
|
||||
// Voxelize using the lowest lod
|
||||
lod = cast lods[lods.length - 1];
|
||||
}
|
||||
else {
|
||||
// Select lod
|
||||
for (i in 0...raw.lods.length) {
|
||||
// Lod found
|
||||
if (screenSize > raw.lods[i].screen_size) break;
|
||||
lod = cast lods[i];
|
||||
if (isLodMaterial()) mats = lod.materials;
|
||||
}
|
||||
}
|
||||
if (lod == null) return; // Empty object
|
||||
}
|
||||
#if lnx_debug
|
||||
else computeScreenSize(Scene.active.camera);
|
||||
#end
|
||||
if (isLodMaterial() && !validContext(mats, context)) return;
|
||||
|
||||
// Get context
|
||||
var materialContexts: Array<MaterialContext> = [];
|
||||
var shaderContexts: Array<ShaderContext> = [];
|
||||
getContexts(context, mats, materialContexts, shaderContexts);
|
||||
|
||||
Uniforms.posUnpack = data.scalePos;
|
||||
Uniforms.texUnpack = data.scaleTex;
|
||||
transform.update();
|
||||
|
||||
// Render mesh
|
||||
var ldata = lod.data;
|
||||
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||
|
||||
var mi = ldata.geom.materialIndices[i];
|
||||
if (shaderContexts.length <= mi || shaderContexts[mi] == null) continue;
|
||||
materialIndex = mi;
|
||||
|
||||
// Check context skip
|
||||
if (materials.length > mi && skipContext(context, materials[mi])) continue;
|
||||
|
||||
var scontext = shaderContexts[mi];
|
||||
if (scontext == null) continue;
|
||||
var elems = scontext.raw.vertex_elements;
|
||||
|
||||
// Uniforms
|
||||
if (scontext.pipeState != lastPipeline) {
|
||||
g.setPipeline(scontext.pipeState);
|
||||
lastPipeline = scontext.pipeState;
|
||||
// Uniforms.setContextConstants(g, scontext, bindParams);
|
||||
}
|
||||
Uniforms.setContextConstants(g, scontext, bindParams); //
|
||||
Uniforms.setObjectConstants(g, scontext, this);
|
||||
if (materialContexts.length > mi) {
|
||||
Uniforms.setMaterialConstants(g, scontext, materialContexts[mi]);
|
||||
}
|
||||
|
||||
// VB / IB
|
||||
#if lnx_deinterleaved
|
||||
g.setVertexBuffers(ldata.geom.get(elems));
|
||||
#else
|
||||
if (ldata.geom.instancedVB != null) {
|
||||
g.setVertexBuffers([ldata.geom.get(elems), ldata.geom.instancedVB]);
|
||||
}
|
||||
else {
|
||||
g.setVertexBuffer(ldata.geom.get(elems));
|
||||
}
|
||||
#end
|
||||
|
||||
g.setIndexBuffer(ldata.geom.indexBuffers[i]);
|
||||
|
||||
// Draw
|
||||
if (ldata.geom.instanced) {
|
||||
g.drawIndexedVerticesInstanced(ldata.geom.instanceCount, ldata.geom.start, ldata.geom.count);
|
||||
}
|
||||
else {
|
||||
g.drawIndexedVertices(ldata.geom.start, ldata.geom.count);
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
var isShadow = context == "shadowmap";
|
||||
if (meshContext) RenderPath.numTrisMesh += ldata.geom.numTris;
|
||||
else if (isShadow) RenderPath.numTrisShadow += ldata.geom.numTris;
|
||||
RenderPath.drawCalls++;
|
||||
#end
|
||||
|
||||
#if lnx_veloc
|
||||
prevMatrix.setFrom(transform.worldUnpack);
|
||||
#end
|
||||
}
|
||||
|
||||
function validContext(mats: Vector<MaterialData>, context: String): Bool {
|
||||
for (mat in mats) if (mat.getContext(context) != null) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public inline function computeCameraDistance(camX: Float, camY: Float, camZ: Float) {
|
||||
// Render path mesh sorting
|
||||
cameraDistance = Vec4.distancef(camX, camY, camZ, transform.worldx(), transform.worldy(), transform.worldz());
|
||||
}
|
||||
|
||||
public inline function computeDepthRead() {
|
||||
#if rp_depth_texture
|
||||
depthRead = false;
|
||||
for (material in materials) {
|
||||
for (context in material.contexts) {
|
||||
if (context.raw.depth_read == true) {
|
||||
depthRead = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public inline function computeScreenSize(camera: CameraObject) {
|
||||
// Approx..
|
||||
// var rp = camera.renderPath;
|
||||
// var screenVolume = rp.currentW * rp.currentH;
|
||||
var tr = transform;
|
||||
var volume = tr.dim.x * tr.dim.y * tr.dim.z;
|
||||
screenSize = volume * (1.0 / cameraDistance);
|
||||
screenSize = screenSize > 1.0 ? 1.0 : screenSize;
|
||||
}
|
||||
|
||||
inline function initLods() {
|
||||
if (lods == null) {
|
||||
lods = [];
|
||||
for (l in raw.lods) {
|
||||
if (l.object_ref == "") lods.push(null); // Empty
|
||||
else lods.push(Scene.active.getChild(l.object_ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
leenkx/Sources/iron/object/MorphTarget.hx
Normal file
62
leenkx/Sources/iron/object/MorphTarget.hx
Normal file
@ -0,0 +1,62 @@
|
||||
package iron.object;
|
||||
|
||||
#if lnx_morph_target
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.Image;
|
||||
import kha.FastFloat;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class MorphTarget {
|
||||
|
||||
public var data: TMorphTarget;
|
||||
public var numMorphTargets: Int = 0;
|
||||
public var morphImageSize: Int = 0;
|
||||
public var morphBlockSize: Int = 0;
|
||||
public var scaling: FastFloat;
|
||||
public var offset: FastFloat;
|
||||
public var morphWeights: Float32Array;
|
||||
public var morphDataPos: Image;
|
||||
public var morphDataNor: Image;
|
||||
public var morphMap: Map<String, Int> = null;
|
||||
|
||||
public function new(data: TMorphTarget) {
|
||||
initWeights(data.morph_target_defaults);
|
||||
scaling = data.morph_scale;
|
||||
offset = data.morph_offset;
|
||||
numMorphTargets = data.num_morph_targets;
|
||||
morphImageSize = data.morph_img_size;
|
||||
morphBlockSize = data.morph_block_size;
|
||||
|
||||
Data.getImage(data.morph_target_data_file + "_morph_pos.png", function(img: Image) {
|
||||
if (img != null) morphDataPos = img;
|
||||
});
|
||||
Data.getImage(data.morph_target_data_file + "_morph_nor.png", function(img: Image) {
|
||||
if (img != null) morphDataNor = img;
|
||||
});
|
||||
morphMap = new Map();
|
||||
|
||||
var i = 0;
|
||||
for (name in data.morph_target_ref) {
|
||||
morphMap.set(name, i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
inline function initWeights(defaults: Float32Array) {
|
||||
morphWeights = new Float32Array(defaults.length);
|
||||
for (i in 0...morphWeights.length) {
|
||||
morphWeights.set(i, defaults.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public function setMorphValue(name: String, value: Float) {
|
||||
var i = morphMap.get(name);
|
||||
if (i != null) {
|
||||
morphWeights.set(i, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
248
leenkx/Sources/iron/object/Object.hx
Normal file
248
leenkx/Sources/iron/object/Object.hx
Normal file
@ -0,0 +1,248 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.Trait;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class Object {
|
||||
static var uidCounter = 0;
|
||||
public var uid: Int;
|
||||
public var urandom: Float;
|
||||
public var raw: TObj = null;
|
||||
|
||||
public var name: String = "";
|
||||
public var transform: Transform;
|
||||
public var constraints: Array<Constraint> = null;
|
||||
public var traits: Array<Trait> = [];
|
||||
|
||||
public var parent: Object = null;
|
||||
public var children: Array<Object> = [];
|
||||
public var lods: Array<Object> = null;
|
||||
|
||||
public var animation: Animation = null;
|
||||
public var visible = true; // Skip render, keep updating
|
||||
public var visibleMesh = true;
|
||||
public var visibleShadow = true;
|
||||
public var culled = false; // Object was culled last frame
|
||||
public var culledMesh = false;
|
||||
public var culledShadow = false;
|
||||
public var vertex_groups: Map<String, Array<Vec4>> = null;
|
||||
public var properties: Map<String, Dynamic> = null;
|
||||
var isEmpty = false;
|
||||
|
||||
public function new() {
|
||||
uid = uidCounter++;
|
||||
urandom = seededRandom(); // Math.random();
|
||||
transform = new Transform(this);
|
||||
isEmpty = Type.getClass(this) == Object;
|
||||
if (isEmpty && Scene.active != null) Scene.active.empties.push(this);
|
||||
}
|
||||
|
||||
/**
|
||||
Set the given `parentObject` as the parent of this object.
|
||||
|
||||
If `parentObject` is `null`, the object is parented to the scene's
|
||||
`sceneParent`, which is the topmost object of the scene tree.
|
||||
If you want to remove it from the scene, use `Object.remove()` instead.
|
||||
|
||||
If `parentObject` is the object on which this function is called,
|
||||
nothing happens.
|
||||
|
||||
@param parentObject The new parent object.
|
||||
@param parentInverse (Optional) Change the scale of the child object to be relative to the new parents 3D space or use the original scale.
|
||||
@param keepTransform (Optional) When unparenting from the old parent, keep the transform given by the old parent or revert to the object's default.
|
||||
**/
|
||||
public function setParent(parentObject: Object, parentInverse = false, keepTransform = false) {
|
||||
if (parentObject == this || parentObject == parent) return;
|
||||
|
||||
if (parent != null) {
|
||||
parent.children.remove(this);
|
||||
if (keepTransform) this.transform.applyParent();
|
||||
this.parent = null; // rebuild matrix without a parent
|
||||
this.transform.buildMatrix();
|
||||
}
|
||||
|
||||
if (parentObject == null) {
|
||||
parentObject = Scene.active.sceneParent;
|
||||
}
|
||||
parent = parentObject;
|
||||
parent.children.push(this);
|
||||
if (parentInverse) this.transform.applyParentInverse();
|
||||
}
|
||||
|
||||
/**
|
||||
Add a game Object as a child of this game Object.
|
||||
@param o The game Object instance to be added as a child.
|
||||
@param parentInverse Optional (default false) change the scale of the child object to be relative to the parents 3D space or use the original scale.
|
||||
**/
|
||||
@:deprecated("addChild() is deprecated, please use setParent() instead")
|
||||
public inline function addChild(o: Object, parentInverse = false) {
|
||||
o.setParent(this, parentInverse, false);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove a child game Object from it's parentage. Does not remove the object from the scene.
|
||||
@param o The game Object instance to be removed.
|
||||
@param keepTransform Optional (defaut false) keep the transform given by the parent or revert to the objects default.
|
||||
**/
|
||||
@:deprecated("removeChild() is deprecated, please use setParent(null) instead")
|
||||
public inline function removeChild(o: Object, keepTransform = false) {
|
||||
o.setParent(null, false, keepTransform);
|
||||
}
|
||||
|
||||
/**
|
||||
Removes the game object from the scene.
|
||||
**/
|
||||
public function remove() {
|
||||
if (isEmpty && Scene.active != null) Scene.active.empties.remove(this);
|
||||
if (animation != null) animation.remove();
|
||||
while (children.length > 0) children[0].remove();
|
||||
while (traits.length > 0) traits[0].remove();
|
||||
if (parent != null) {
|
||||
parent.children.remove(this);
|
||||
parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get a child game Object of this game Object. Using the childs name property as a lookup.
|
||||
@param name A string matching the name property of the game Object to fetch.
|
||||
@return Object or null
|
||||
**/
|
||||
public function getChild(name: String): Object {
|
||||
if (this.name == name) return this;
|
||||
else {
|
||||
for (c in children) {
|
||||
var r = c.getChild(name);
|
||||
if (r != null) return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the children of the object.
|
||||
|
||||
If 'recursive' is set to `false`, only direct children will be included
|
||||
in the returned array. If `recursive` is `true`, children of children and
|
||||
so on will be included too.
|
||||
|
||||
@param recursive = false Include children of children
|
||||
@return `Array<Object>`
|
||||
**/
|
||||
public function getChildren(?recursive = false): Array<Object> {
|
||||
if (!recursive) return children;
|
||||
|
||||
var retChildren = children.copy();
|
||||
for (child in children) {
|
||||
retChildren = retChildren.concat(child.getChildren(recursive));
|
||||
}
|
||||
return retChildren;
|
||||
}
|
||||
|
||||
public function getChildOfType<T: Object>(type: Class<T>): T {
|
||||
if (Std.isOfType(this, type)) return cast this;
|
||||
else {
|
||||
for (c in children) {
|
||||
var r = c.getChildOfType(type);
|
||||
if (r != null) return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@:access(iron.Trait)
|
||||
public function addTrait(t: Trait) {
|
||||
traits.push(t);
|
||||
t.object = this;
|
||||
|
||||
if (t._add != null) {
|
||||
for (f in t._add) f();
|
||||
t._add = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the Trait from the Object.
|
||||
@param t The Trait to be removed from the game Object.
|
||||
**/
|
||||
@:access(iron.Trait)
|
||||
public function removeTrait(t: Trait) {
|
||||
if (t._init != null) {
|
||||
for (f in t._init) App.removeInit(f);
|
||||
t._init = null;
|
||||
}
|
||||
if (t._update != null) {
|
||||
for (f in t._update) App.removeUpdate(f);
|
||||
t._update = null;
|
||||
}
|
||||
if (t._lateUpdate != null) {
|
||||
for (f in t._lateUpdate) App.removeLateUpdate(f);
|
||||
t._lateUpdate = null;
|
||||
}
|
||||
if (t._render != null) {
|
||||
for (f in t._render) App.removeRender(f);
|
||||
t._render = null;
|
||||
}
|
||||
if (t._render2D != null) {
|
||||
for (f in t._render2D) App.removeRender2D(f);
|
||||
t._render2D = null;
|
||||
}
|
||||
if (t._remove != null) {
|
||||
for (f in t._remove) f();
|
||||
t._remove = null;
|
||||
}
|
||||
traits.remove(t);
|
||||
}
|
||||
|
||||
/**
|
||||
Get the Trait instance that is attached to this game Object.
|
||||
@param c The class of type Trait to attempt to retrieve.
|
||||
@return Trait or null
|
||||
**/
|
||||
public function getTrait<T: Trait>(c: Class<T>): T {
|
||||
for (t in traits) if (Type.getClass(t) == cast c) return cast t;
|
||||
return null;
|
||||
}
|
||||
|
||||
#if lnx_skin
|
||||
public function getBoneAnimation(armatureUid): BoneAnimation {
|
||||
for (a in Scene.active.animations) if (a.armature != null && a.armature.uid == armatureUid) return cast a;
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
public function getBoneAnimation(armatureUid): Animation {
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
public function getObjectAnimation(): ObjectAnimation {
|
||||
if(animation != null) return cast animation;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setupAnimation(oactions: Array<TSceneFormat> = null) {
|
||||
// Parented to bone
|
||||
#if lnx_skin
|
||||
if (raw.parent_bone != null) {
|
||||
Scene.active.notifyOnInit(function() {
|
||||
var banim = getBoneAnimation(parent.uid);
|
||||
if (banim != null) banim.addBoneChild(raw.parent_bone, this);
|
||||
});
|
||||
}
|
||||
#end
|
||||
// Object actions
|
||||
if (oactions == null) return;
|
||||
animation = new ObjectAnimation(this, oactions);
|
||||
}
|
||||
|
||||
#if lnx_morph_target
|
||||
public function setupMorphTargets() {}
|
||||
#end
|
||||
|
||||
static var seed = 1; // cpp / js not consistent
|
||||
static function seededRandom(): Float {
|
||||
seed = (seed * 9301 + 49297) % 233280;
|
||||
return seed / 233280.0;
|
||||
}
|
||||
}
|
||||
217
leenkx/Sources/iron/object/ObjectAnimation.hx
Normal file
217
leenkx/Sources/iron/object/ObjectAnimation.hx
Normal file
@ -0,0 +1,217 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.object.Animation.ActionSampler;
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.FastFloat;
|
||||
import kha.arrays.Uint32Array;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class ObjectAnimation extends Animation {
|
||||
|
||||
public var object: Object;
|
||||
public var oactions: Array<TSceneFormat>;
|
||||
var oaction: TObj;
|
||||
var s0: FastFloat = 0.0;
|
||||
var bezierFrameIndex = -1;
|
||||
|
||||
var updateAnimation: Map<String, FastFloat> -> Void;
|
||||
|
||||
public var transformArr: Float32Array;
|
||||
|
||||
public var transformMap: Map<String, FastFloat>;
|
||||
|
||||
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
|
||||
"xrot", "yrot", "zrot",
|
||||
"qwrot", "qxrot", "qyrot", "qzrot",
|
||||
"xscl", "yscl", "zscl",
|
||||
"dxloc", "dyloc", "dzloc",
|
||||
"dxrot", "dyrot", "dzrot",
|
||||
"dqwrot", "dqxrot", "dqyrot", "dqzrot",
|
||||
"dxscl", "dyscl", "dzscl"];
|
||||
|
||||
public function new(object: Object, oactions: Array<TSceneFormat>) {
|
||||
this.object = object;
|
||||
this.oactions = oactions;
|
||||
isSkinned = false;
|
||||
super();
|
||||
}
|
||||
|
||||
function getAction(action: String): TObj {
|
||||
for (a in oactions) if (a != null && a.objects[0].name == action) return a.objects[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
||||
super.play(action, onComplete, blendTime, speed, loop);
|
||||
if (this.action == "" && oactions[0] != null) this.action = oactions[0].objects[0].name;
|
||||
oaction = getAction(this.action);
|
||||
if (oaction != null) {
|
||||
isSampled = oaction.sampled != null && oaction.sampled;
|
||||
}
|
||||
}
|
||||
|
||||
override public function update(delta: FastFloat) {
|
||||
if (!object.visible || object.culled) return;
|
||||
|
||||
#if lnx_debug
|
||||
Animation.beginProfile();
|
||||
#end
|
||||
|
||||
if(transformMap == null) transformMap = new Map();
|
||||
transformMap = initTransformMap();
|
||||
|
||||
super.update(delta);
|
||||
if (paused) return;
|
||||
if(updateAnimation == null) return;
|
||||
if (!isSkinned) updateObjectAnimation();
|
||||
|
||||
#if lnx_debug
|
||||
Animation.endProfile();
|
||||
#end
|
||||
}
|
||||
|
||||
public override function getTotalFrames(sampler: ActionSampler): Int {
|
||||
var track = getAction(sampler.action).anim.tracks[0];
|
||||
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
|
||||
}
|
||||
|
||||
public function initTransformMap(){
|
||||
|
||||
var map = new Map<String, Null<FastFloat>>();
|
||||
for (name in trackNames){
|
||||
map.set(name, null);
|
||||
}
|
||||
|
||||
return map;
|
||||
|
||||
}
|
||||
|
||||
public function animationLoop(f: Map<String, FastFloat>->Void){
|
||||
|
||||
updateAnimation = f;
|
||||
}
|
||||
|
||||
function updateObjectAnimation() {
|
||||
updateAnimation(transformMap);
|
||||
updateTransform(transformMap, object.transform);
|
||||
object.transform.buildMatrix();
|
||||
}
|
||||
|
||||
override public function updateActionTrack(sampler: ActionSampler) {
|
||||
if(sampler.paused) return;
|
||||
|
||||
if(! sampler.actionDataInit) {
|
||||
var objanim = getAction(sampler.action);
|
||||
sampler.setObjectAction(objanim);
|
||||
}
|
||||
|
||||
oaction = sampler.getObjectAction();
|
||||
updateTrack(oaction.anim, sampler);
|
||||
|
||||
}
|
||||
|
||||
function updateAnimSampled(anim: TAnimation, transformMap: Map<String, FastFloat>, sampler: ActionSampler) {
|
||||
|
||||
for (track in anim.tracks) {
|
||||
var sign = sampler.speed > 0 ? 1 : -1;
|
||||
|
||||
var t = sampler.time;
|
||||
//t = t < 0 ? 0.1 : t;
|
||||
|
||||
var ti = sampler.offset;
|
||||
//ti = ti < 0 ? 1 : ti;
|
||||
|
||||
var t1 = track.frames[ti] * frameTime;
|
||||
var t2 = track.frames[ti + sign] * frameTime;
|
||||
var v1 = track.values[ti];
|
||||
var v2 = track.values[ti + sign];
|
||||
|
||||
var value = interpolateLinear(t, t1, t2, v1, v2);
|
||||
|
||||
if(value == null) continue;
|
||||
|
||||
transformMap.set(track.target, value);
|
||||
}
|
||||
}
|
||||
|
||||
public function sampleAction(sampler: ActionSampler, transformMap: Map<String, FastFloat>){
|
||||
|
||||
if(! sampler.actionDataInit) {
|
||||
var objanim = getAction(sampler.action);
|
||||
sampler.setObjectAction(objanim);
|
||||
}
|
||||
|
||||
var objanim = sampler.getObjectAction();
|
||||
updateAnimSampled(objanim.anim, transformMap, sampler);
|
||||
}
|
||||
|
||||
public function blendActionObject(transformMap1: Map<String, FastFloat>, transformMap2: Map<String, FastFloat>, transformMapRes: Map<String, FastFloat>, factor: FastFloat ) {
|
||||
|
||||
for(track in transformMapRes.keys()){
|
||||
|
||||
var v1 = transformMap1.get(track);
|
||||
var v2 = transformMap2.get(track);
|
||||
|
||||
if(v1 == null || v2 == null) continue;
|
||||
|
||||
var maxVal: FastFloat = 1.0;
|
||||
var tempValue = (maxVal - factor) * v1 + factor * v2;
|
||||
transformMapRes.set(track, tempValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline function interpolateLinear(t: FastFloat, t1: FastFloat, t2: FastFloat, v1: FastFloat, v2: FastFloat): Null<FastFloat> {
|
||||
var s = (t - t1) / (t2 - t1);
|
||||
return (1.0 - s) * v1 + s * v2;
|
||||
}
|
||||
|
||||
@:access(iron.object.Transform)
|
||||
function updateTransform(transformMap: Map<String, FastFloat>, transform: Transform) {
|
||||
|
||||
var t = transform;
|
||||
t.resetDelta();
|
||||
|
||||
for (track in transformMap.keys()){
|
||||
|
||||
var value = transformMap.get(track);
|
||||
|
||||
if(value == null) continue;
|
||||
|
||||
switch (track) {
|
||||
|
||||
case "xloc": transform.loc.x = value;
|
||||
case "yloc": transform.loc.y = value;
|
||||
case "zloc": transform.loc.z = value;
|
||||
case "xrot": transform.setRotation(value, transform._eulerY, transform._eulerZ);
|
||||
case "yrot": transform.setRotation(transform._eulerX, value, transform._eulerZ);
|
||||
case "zrot": transform.setRotation(transform._eulerX, transform._eulerY, value);
|
||||
case "qwrot": transform.rot.w = value;
|
||||
case "qxrot": transform.rot.x = value;
|
||||
case "qyrot": transform.rot.y = value;
|
||||
case "qzrot": transform.rot.z = value;
|
||||
case "xscl": transform.scale.x = value;
|
||||
case "yscl": transform.scale.y = value;
|
||||
case "zscl": transform.scale.z = value;
|
||||
// Delta
|
||||
case "dxloc": transform.dloc.x = value;
|
||||
case "dyloc": transform.dloc.y = value;
|
||||
case "dzloc": transform.dloc.z = value;
|
||||
case "dxrot": transform._deulerX = value;
|
||||
case "dyrot": transform._deulerY = value;
|
||||
case "dzrot": transform._deulerZ = value;
|
||||
case "dqwrot": transform.drot.w = value;
|
||||
case "dqxrot": transform.drot.x = value;
|
||||
case "dqyrot": transform.drot.y = value;
|
||||
case "dqzrot": transform.drot.z = value;
|
||||
case "dxscl": transform.dscale.x = value;
|
||||
case "dyscl": transform.dscale.y = value;
|
||||
case "dzscl": transform.dscale.z = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
249
leenkx/Sources/iron/object/ParticleSystem.hx
Normal file
249
leenkx/Sources/iron/object/ParticleSystem.hx
Normal file
@ -0,0 +1,249 @@
|
||||
package iron.object;
|
||||
|
||||
#if lnx_particles
|
||||
|
||||
import kha.graphics4.Usage;
|
||||
import kha.arrays.Float32Array;
|
||||
import iron.data.Data;
|
||||
import iron.data.ParticleData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.Time;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec3;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class ParticleSystem {
|
||||
public var data: ParticleData;
|
||||
public var speed = 1.0;
|
||||
var particles: Array<Particle>;
|
||||
var ready: Bool;
|
||||
var frameRate = 24;
|
||||
var lifetime = 0.0;
|
||||
var animtime = 0.0;
|
||||
var time = 0.0;
|
||||
var spawnRate = 0.0;
|
||||
var seed = 0;
|
||||
|
||||
var r: TParticleData;
|
||||
var gx: Float;
|
||||
var gy: Float;
|
||||
var gz: Float;
|
||||
var alignx: Float;
|
||||
var aligny: Float;
|
||||
var alignz: Float;
|
||||
var dimx: Float;
|
||||
var dimy: Float;
|
||||
var tilesx: Int;
|
||||
var tilesy: Int;
|
||||
var tilesFramerate: Int;
|
||||
|
||||
var count = 0;
|
||||
var lap = 0;
|
||||
var lapTime = 0.0;
|
||||
var m = Mat4.identity();
|
||||
|
||||
var ownerLoc = new Vec4();
|
||||
var ownerRot = new Quat();
|
||||
var ownerScl = new Vec4();
|
||||
|
||||
public function new(sceneName: String, pref: TParticleReference) {
|
||||
seed = pref.seed;
|
||||
particles = [];
|
||||
ready = false;
|
||||
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
|
||||
data = b;
|
||||
r = data.raw;
|
||||
if (Scene.active.raw.gravity != null) {
|
||||
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
|
||||
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
|
||||
gz = Scene.active.raw.gravity[2] * r.weight_gravity;
|
||||
}
|
||||
else {
|
||||
gx = 0;
|
||||
gy = 0;
|
||||
gz = -9.81 * r.weight_gravity;
|
||||
}
|
||||
alignx = r.object_align_factor[0] / 2;
|
||||
aligny = r.object_align_factor[1] / 2;
|
||||
alignz = r.object_align_factor[2] / 2;
|
||||
lifetime = r.lifetime / frameRate;
|
||||
animtime = (r.frame_end - r.frame_start) / frameRate;
|
||||
spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate;
|
||||
for (i in 0...r.count) particles.push(new Particle(i));
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
lifetime = 0;
|
||||
}
|
||||
|
||||
public function resume() {
|
||||
lifetime = r.lifetime / frameRate;
|
||||
}
|
||||
|
||||
public function update(object: MeshObject, owner: MeshObject) {
|
||||
if (!ready || object == null || speed == 0.0) return;
|
||||
|
||||
// Copy owner world transform but discard scale
|
||||
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
|
||||
object.transform.loc = ownerLoc;
|
||||
object.transform.rot = ownerRot;
|
||||
|
||||
// Set particle size per particle system
|
||||
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
|
||||
|
||||
object.transform.buildMatrix();
|
||||
owner.transform.buildMatrix();
|
||||
object.transform.dim.setFrom(owner.transform.dim);
|
||||
|
||||
dimx = object.transform.dim.x;
|
||||
dimy = object.transform.dim.y;
|
||||
|
||||
if (object.activeTilesheet != null) {
|
||||
tilesx = object.activeTilesheet.raw.tilesx;
|
||||
tilesy = object.activeTilesheet.raw.tilesy;
|
||||
tilesFramerate = object.activeTilesheet.raw.framerate;
|
||||
}
|
||||
|
||||
// Animate
|
||||
time += Time.realDelta * speed;
|
||||
lap = Std.int(time / animtime);
|
||||
lapTime = time - lap * animtime;
|
||||
count = Std.int(lapTime / spawnRate);
|
||||
|
||||
updateGpu(object, owner);
|
||||
}
|
||||
|
||||
public function getData(): Mat4 {
|
||||
var hair = r.type == 1;
|
||||
m._00 = r.loop ? animtime : -animtime;
|
||||
m._01 = hair ? 1 / particles.length : spawnRate;
|
||||
m._02 = hair ? 1 : lifetime;
|
||||
m._03 = particles.length;
|
||||
m._10 = hair ? 0 : alignx;
|
||||
m._11 = hair ? 0 : aligny;
|
||||
m._12 = hair ? 0 : alignz;
|
||||
m._13 = hair ? 0 : r.factor_random;
|
||||
m._20 = hair ? 0 : gx * r.mass;
|
||||
m._21 = hair ? 0 : gy * r.mass;
|
||||
m._22 = hair ? 0 : gz * r.mass;
|
||||
m._23 = hair ? 0 : r.lifetime_random;
|
||||
m._30 = tilesx;
|
||||
m._31 = tilesy;
|
||||
m._32 = 1 / tilesFramerate;
|
||||
m._33 = hair ? 1 : lapTime;
|
||||
return m;
|
||||
}
|
||||
|
||||
function updateGpu(object: MeshObject, owner: MeshObject) {
|
||||
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
||||
// GPU particles transform is attached to owner object
|
||||
}
|
||||
|
||||
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
|
||||
var instancedData = new Float32Array(particles.length * 3);
|
||||
var i = 0;
|
||||
|
||||
var normFactor = 1 / 32767; // pa.values are not normalized
|
||||
var scalePosOwner = owner.data.scalePos;
|
||||
var scalePosParticle = object.data.scalePos;
|
||||
var particleSize = r.particle_size;
|
||||
var scaleFactor = new Vec4().setFrom(owner.transform.scale);
|
||||
scaleFactor.mult(scalePosOwner / (particleSize * scalePosParticle));
|
||||
|
||||
switch (r.emit_from) {
|
||||
case 0: // Vert
|
||||
var pa = owner.data.geom.positions;
|
||||
|
||||
for (p in particles) {
|
||||
var j = Std.int(fhash(i) * (pa.values.length / pa.size));
|
||||
instancedData.set(i, pa.values[j * pa.size ] * normFactor * scaleFactor.x); i++;
|
||||
instancedData.set(i, pa.values[j * pa.size + 1] * normFactor * scaleFactor.y); i++;
|
||||
instancedData.set(i, pa.values[j * pa.size + 2] * normFactor * scaleFactor.z); i++;
|
||||
}
|
||||
|
||||
case 1: // Face
|
||||
var positions = owner.data.geom.positions.values;
|
||||
|
||||
for (p in particles) {
|
||||
// Choose random index array (there is one per material) and random face
|
||||
var ia = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
|
||||
var faceIndex = Std.random(Std.int(ia.length / 3));
|
||||
|
||||
var i0 = ia[faceIndex * 3 + 0];
|
||||
var i1 = ia[faceIndex * 3 + 1];
|
||||
var i2 = ia[faceIndex * 3 + 2];
|
||||
|
||||
var v0 = new Vec3(positions[i0 * 4], positions[i0 * 4 + 1], positions[i0 * 4 + 2]);
|
||||
var v1 = new Vec3(positions[i1 * 4], positions[i1 * 4 + 1], positions[i1 * 4 + 2]);
|
||||
var v2 = new Vec3(positions[i2 * 4], positions[i2 * 4 + 1], positions[i2 * 4 + 2]);
|
||||
|
||||
var pos = randomPointInTriangle(v0, v1, v2);
|
||||
|
||||
instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++;
|
||||
instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++;
|
||||
instancedData.set(i, pos.z * normFactor * scaleFactor.z); i++;
|
||||
}
|
||||
|
||||
case 2: // Volume
|
||||
var scaleFactorVolume = new Vec4().setFrom(object.transform.dim);
|
||||
scaleFactorVolume.mult(0.5 / (particleSize * scalePosParticle));
|
||||
|
||||
for (p in particles) {
|
||||
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.x); i++;
|
||||
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y); i++;
|
||||
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z); i++;
|
||||
}
|
||||
}
|
||||
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
|
||||
}
|
||||
|
||||
function fhash(n: Int): Float {
|
||||
var s = n + 1.0;
|
||||
s *= 9301.0 % s;
|
||||
s = (s * 9301.0 + 49297.0) % 233280.0;
|
||||
return s / 233280.0;
|
||||
}
|
||||
|
||||
public function remove() {}
|
||||
|
||||
/**
|
||||
Generates a random point in the triangle with vertex positions abc.
|
||||
|
||||
Please note that the given position vectors are changed in-place by this
|
||||
function and can be considered garbage afterwards, so make sure to clone
|
||||
them first if needed.
|
||||
**/
|
||||
public static inline function randomPointInTriangle(a: Vec3, b: Vec3, c: Vec3): Vec3 {
|
||||
// Generate a random point in a square where (0, 0) <= (x, y) < (1, 1)
|
||||
var x = Math.random();
|
||||
var y = Math.random();
|
||||
|
||||
if (x + y > 1) {
|
||||
// We're in the upper right triangle in the square, mirror to lower left
|
||||
x = 1 - x;
|
||||
y = 1 - y;
|
||||
}
|
||||
|
||||
// Transform the point to the triangle abc
|
||||
var u = b.sub(a);
|
||||
var v = c.sub(a);
|
||||
return a.add(u.mult(x).add(v.mult(y)));
|
||||
}
|
||||
}
|
||||
|
||||
class Particle {
|
||||
public var i: Int;
|
||||
public var x = 0.0;
|
||||
public var y = 0.0;
|
||||
public var z = 0.0;
|
||||
public var cameraDistance: Float;
|
||||
|
||||
public function new(i: Int) {
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
205
leenkx/Sources/iron/object/ProbeObject.hx
Normal file
205
leenkx/Sources/iron/object/ProbeObject.hx
Normal file
@ -0,0 +1,205 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.graphics4.Graphics;
|
||||
import kha.graphics4.TextureFormat;
|
||||
import kha.graphics4.DepthStencilFormat;
|
||||
import kha.graphics4.CubeMap;
|
||||
import iron.data.ProbeData;
|
||||
import iron.data.CameraData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
|
||||
class ProbeObject extends Object {
|
||||
|
||||
#if rp_probes
|
||||
|
||||
public var data: ProbeData;
|
||||
public var renderTarget: kha.Image = null;
|
||||
public var camera: CameraObject = null;
|
||||
public var ready = false;
|
||||
|
||||
// Cubemap update
|
||||
public var perFrame = false; // Update probe every frame
|
||||
public var redraw = true; // Update probe next frame
|
||||
|
||||
var m1: Mat4;
|
||||
var m2: Mat4;
|
||||
var proben: Vec4;
|
||||
var probep: Vec4;
|
||||
// static var v = new Vec4();
|
||||
static var p = new Vec4();
|
||||
static var q = new Vec4();
|
||||
|
||||
public function new(data: ProbeData) {
|
||||
super();
|
||||
this.data = data;
|
||||
Scene.active.probes.push(this);
|
||||
iron.App.notifyOnInit(init);
|
||||
}
|
||||
|
||||
public override function remove() {
|
||||
if (Scene.active != null) Scene.active.probes.remove(this);
|
||||
// if (camera != null) camera.remove();
|
||||
super.remove();
|
||||
}
|
||||
|
||||
function init() {
|
||||
probep = transform.world.getLoc();
|
||||
proben = transform.up().normalize();
|
||||
proben.w = -probep.dot(proben);
|
||||
|
||||
if (data.raw.type == "planar") {
|
||||
m1 = Mat4.identity();
|
||||
m2 = Mat4.identity();
|
||||
reflect(m1, proben, probep);
|
||||
reflect(m2, new Vec4(0, 1, 0), probep);
|
||||
|
||||
transform.scale.z = 1.0; // Only take dim.z into account
|
||||
transform.buildMatrix();
|
||||
|
||||
// var aspect = transform.scale.x / transform.scale.y;
|
||||
var aspect = iron.App.w() / iron.App.h(); // TODO
|
||||
var craw: TCameraData = {
|
||||
name: raw.name + "_Camera",
|
||||
near_plane: Scene.active.camera.data.raw.near_plane,
|
||||
far_plane: Scene.active.camera.data.raw.far_plane,
|
||||
fov: Scene.active.camera.data.raw.fov,
|
||||
aspect: aspect
|
||||
};
|
||||
new CameraData(craw, function(cdata: CameraData) {
|
||||
camera = new CameraObject(cdata);
|
||||
camera.renderTarget = kha.Image.createRenderTarget(
|
||||
iron.App.w(), // TODO
|
||||
iron.App.h(),
|
||||
TextureFormat.RGBA32,
|
||||
DepthStencilFormat.NoDepthAndStencil
|
||||
);
|
||||
camera.name = craw.name;
|
||||
camera.setParent(iron.Scene.active.root);
|
||||
// Make target bindable from render path
|
||||
var rt = new RenderPath.RenderTarget(new RenderPath.RenderTargetRaw());
|
||||
rt.raw.name = raw.name;
|
||||
rt.image = camera.renderTarget;
|
||||
RenderPath.active.renderTargets.set(rt.raw.name, rt);
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
else if (data.raw.type == "cubemap") {
|
||||
transform.scale.x *= transform.dim.x;
|
||||
transform.scale.y *= transform.dim.y;
|
||||
transform.scale.z *= transform.dim.z;
|
||||
transform.buildMatrix();
|
||||
|
||||
var craw: TCameraData = {
|
||||
name: data.raw.name + "_Camera",
|
||||
near_plane: Scene.active.camera.data.raw.near_plane,
|
||||
far_plane: Scene.active.camera.data.raw.far_plane,
|
||||
fov: 1.5708, // pi/2
|
||||
aspect: 1.0
|
||||
};
|
||||
new CameraData(craw, function(cdata: CameraData) {
|
||||
camera = new CameraObject(cdata);
|
||||
camera.renderTargetCube = CubeMap.createRenderTarget(
|
||||
1024, // TODO
|
||||
TextureFormat.RGBA32,
|
||||
DepthStencilFormat.NoDepthAndStencil
|
||||
);
|
||||
camera.name = craw.name;
|
||||
camera.setParent(iron.Scene.active.root);
|
||||
// Make target bindable from render path
|
||||
var rt = new RenderPath.RenderTarget(new RenderPath.RenderTargetRaw());
|
||||
rt.raw.name = raw.name;
|
||||
rt.raw.is_cubemap = true;
|
||||
rt.isCubeMap = true;
|
||||
rt.cubeMap = camera.renderTargetCube;
|
||||
RenderPath.active.renderTargets.set(rt.raw.name, rt);
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static function reflect(m: Mat4, n: Vec4, p: Vec4) {
|
||||
var c = -p.dot(n);
|
||||
m._00 = 1 - 2 * n.x * n.x;
|
||||
m._10 = - 2 * n.x * n.y;
|
||||
m._20 = - 2 * n.x * n.z;
|
||||
m._30 = - 2 * n.x * c;
|
||||
m._01 = - 2 * n.x * n.y;
|
||||
m._11 = 1 - 2 * n.y * n.y;
|
||||
m._21 = - 2 * n.y * n.z;
|
||||
m._31 = - 2 * n.y * c;
|
||||
m._02 = - 2 * n.x * n.z;
|
||||
m._12 = - 2 * n.y * n.z;
|
||||
m._22 = 1 - 2 * n.z * n.z;
|
||||
m._32 = - 2 * n.z * c;
|
||||
m._03 = 0;
|
||||
m._13 = 0;
|
||||
m._23 = 0;
|
||||
m._33 = 1;
|
||||
}
|
||||
|
||||
static inline function sign(f: Float): Float {
|
||||
return f > 0.0 ? 1.0 : f < 0.0 ? -1.0 : 0.0;
|
||||
}
|
||||
|
||||
static function obliqueProjection(m: Mat4, plane: Vec4) {
|
||||
// http://www.terathon.com/code/oblique.html
|
||||
p.x = (sign(plane.x) + m._20) / m._00;
|
||||
p.y = (sign(plane.y) + m._21) / m._11;
|
||||
p.z = -1.0;
|
||||
p.w = (1.0 + m._22) / m._32;
|
||||
q.setFrom(plane).mult(2.0 / plane.dot(p));
|
||||
m._02 = q.x;
|
||||
m._12 = q.y;
|
||||
m._22 = q.z + 1.0;
|
||||
m._32 = q.w;
|
||||
}
|
||||
|
||||
function cullProbe(camera: CameraObject): Bool {
|
||||
if (camera.data.raw.frustum_culling) {
|
||||
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, transform, 1.0)) {
|
||||
culled = true;
|
||||
return culled;
|
||||
}
|
||||
}
|
||||
culled = false;
|
||||
return culled;
|
||||
}
|
||||
|
||||
public function render(g: Graphics, activeCamera: CameraObject) {
|
||||
if (camera == null || !ready || !RenderPath.active.ready || !visible || cullProbe(activeCamera)) return;
|
||||
|
||||
if (data.raw.type == "planar") {
|
||||
camera.V.setFrom(m1);
|
||||
camera.V.multmat(activeCamera.V);
|
||||
camera.V.multmat(m2);
|
||||
camera.transform.local.getInverse(camera.V);
|
||||
camera.transform.decompose();
|
||||
// Skip objects below the reflection plane
|
||||
// v.setFrom(proben).applyproj(camera.V);
|
||||
// obliqueProjection(#if (lnx_taa) camera.noJitterP #else camera.P #end, v);
|
||||
camera.renderFrame(g);
|
||||
}
|
||||
else if (data.raw.type == "cubemap") {
|
||||
if (perFrame || redraw) {
|
||||
for (i in 0...6) {
|
||||
camera.currentFace = i;
|
||||
#if (!kha_opengl && !kha_webgl)
|
||||
var flip = (i == 2 || i == 3) ? true : false; // Flip +Y, -Y
|
||||
#else
|
||||
var flip = false;
|
||||
#end
|
||||
CameraObject.setCubeFace(camera.V, probep, i, flip);
|
||||
camera.transform.local.getInverse(camera.V);
|
||||
camera.transform.decompose();
|
||||
camera.renderFrame(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redraw = false;
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
||||
108
leenkx/Sources/iron/object/SpeakerObject.hx
Normal file
108
leenkx/Sources/iron/object/SpeakerObject.hx
Normal file
@ -0,0 +1,108 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.audio1.AudioChannel;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
import iron.system.Audio;
|
||||
|
||||
class SpeakerObject extends Object {
|
||||
|
||||
#if lnx_audio
|
||||
|
||||
public var data: TSpeakerData;
|
||||
public var paused(default, null) = false;
|
||||
public var sound(default, null): kha.Sound = null;
|
||||
public var channels(default, null): Array<AudioChannel> = [];
|
||||
public var volume(default, null) : FastFloat;
|
||||
|
||||
public function new(data: TSpeakerData) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
|
||||
Scene.active.speakers.push(this);
|
||||
|
||||
if (data.sound == "") return;
|
||||
|
||||
Data.getSound(data.sound, function(sound: kha.Sound) {
|
||||
this.sound = sound;
|
||||
App.notifyOnInit(init);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (visible && data.play_on_start) play();
|
||||
}
|
||||
|
||||
public function play() {
|
||||
if (sound == null || data.muted) return;
|
||||
if (paused) {
|
||||
for (c in channels) c.play();
|
||||
paused = false;
|
||||
return;
|
||||
}
|
||||
var channel = Audio.play(sound, data.loop, data.stream);
|
||||
if (channel != null) {
|
||||
channels.push(channel);
|
||||
if (data.attenuation > 0 && channels.length == 1) App.notifyOnUpdate(update);
|
||||
}
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
for (c in channels) c.pause();
|
||||
paused = true;
|
||||
}
|
||||
|
||||
public function stop() {
|
||||
for (c in channels) c.stop();
|
||||
channels.splice(0, channels.length);
|
||||
}
|
||||
|
||||
public function setSound(sound: String) {
|
||||
if (sound == null) return;
|
||||
|
||||
data.sound = sound;
|
||||
|
||||
Data.getSound(sound, function(sound: kha.Sound) {
|
||||
this.sound = sound;
|
||||
});
|
||||
}
|
||||
|
||||
public function setVolume(volume: FastFloat) {
|
||||
data.volume = volume;
|
||||
}
|
||||
function update() {
|
||||
if (paused) return;
|
||||
for (c in channels) if (c.finished) channels.remove(c);
|
||||
if (channels.length == 0) {
|
||||
App.removeUpdate(update);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.attenuation > 0) {
|
||||
var distance = Vec4.distance(Scene.active.camera.transform.world.getLoc(), transform.world.getLoc());
|
||||
distance = Math.max(Math.min(data.distance_max, distance), data.distance_reference);
|
||||
volume = data.distance_reference / (data.distance_reference + data.attenuation * (distance - data.distance_reference));
|
||||
volume *= data.volume;
|
||||
}
|
||||
else {
|
||||
volume = data.volume;
|
||||
}
|
||||
|
||||
if (volume > data.volume_max) volume = data.volume_max;
|
||||
else if (volume < data.volume_min) volume = data.volume_min;
|
||||
|
||||
for (c in channels) c.volume = volume;
|
||||
}
|
||||
|
||||
public override function remove() {
|
||||
stop();
|
||||
if (Scene.active != null) Scene.active.speakers.remove(this);
|
||||
super.remove();
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
}
|
||||
117
leenkx/Sources/iron/object/Tilesheet.hx
Normal file
117
leenkx/Sources/iron/object/Tilesheet.hx
Normal file
@ -0,0 +1,117 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.Scene;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.Time;
|
||||
|
||||
@:allow(iron.Scene)
|
||||
class Tilesheet {
|
||||
|
||||
public var tileX = 0.0; // Tile offset on tilesheet texture 0-1
|
||||
public var tileY = 0.0;
|
||||
|
||||
public var raw: TTilesheetData;
|
||||
public var action: TTilesheetAction = null;
|
||||
var ready: Bool;
|
||||
|
||||
public var paused = false;
|
||||
public var frame = 0;
|
||||
var time = 0.0;
|
||||
var onActionComplete: Void->Void = null;
|
||||
|
||||
public function new(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) {
|
||||
ready = false;
|
||||
Data.getSceneRaw(sceneName, function(format: TSceneFormat) {
|
||||
for (ts in format.tilesheet_datas) {
|
||||
if (ts.name == tilesheet_ref) {
|
||||
raw = ts;
|
||||
Scene.active.tilesheets.push(this);
|
||||
play(tilesheet_action_ref);
|
||||
ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function play(action_ref: String, onActionComplete: Void->Void = null) {
|
||||
this.onActionComplete = onActionComplete;
|
||||
for (a in raw.actions) {
|
||||
if (a.name == action_ref) {
|
||||
action = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setFrame(action.start);
|
||||
paused = false;
|
||||
time = 0.0;
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
paused = true;
|
||||
}
|
||||
|
||||
public function resume() {
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public function remove() {
|
||||
Scene.active.tilesheets.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frame of the current active tilesheet action. Automatically un-pauses action.
|
||||
* @param frame Frame offset with 0 as the first frame of the active action.
|
||||
**/
|
||||
public function setFrameOffset(frame: Int) {
|
||||
setFrame(action.start + frame);
|
||||
paused = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current frame.
|
||||
* @return Frame offset with 0 as the first frame of the active action.
|
||||
*/
|
||||
public function getFrameOffset(): Int {
|
||||
return frame - action.start;
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (!ready || paused || action.start >= action.end) return;
|
||||
|
||||
time += Time.realDelta;
|
||||
|
||||
var frameTime = 1 / raw.framerate;
|
||||
var framesToAdvance = 0;
|
||||
|
||||
// Check how many animation frames passed during the last render frame
|
||||
// and catch up if required. The remaining `time` that couldn't fit in
|
||||
// another animation frame will be used in the next `update()`.
|
||||
while (time >= frameTime) {
|
||||
time -= frameTime;
|
||||
framesToAdvance++;
|
||||
}
|
||||
|
||||
if (framesToAdvance != 0) {
|
||||
setFrame(frame + framesToAdvance);
|
||||
}
|
||||
}
|
||||
|
||||
function setFrame(f: Int) {
|
||||
frame = f;
|
||||
|
||||
// Action end
|
||||
if (frame > action.end && action.start < action.end) {
|
||||
if (onActionComplete != null) onActionComplete();
|
||||
if (action.loop) setFrame(action.start);
|
||||
else paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = frame % raw.tilesx;
|
||||
var ty = Std.int(frame / raw.tilesx);
|
||||
tileX = tx * (1 / raw.tilesx);
|
||||
tileY = ty * (1 / raw.tilesy);
|
||||
}
|
||||
}
|
||||
364
leenkx/Sources/iron/object/Transform.hx
Normal file
364
leenkx/Sources/iron/object/Transform.hx
Normal file
@ -0,0 +1,364 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
|
||||
class Transform {
|
||||
/**
|
||||
The world matrix (read-only).
|
||||
**/
|
||||
public var world: Mat4;
|
||||
/**
|
||||
Prevent applying parent matrix.
|
||||
**/
|
||||
public var localOnly = false;
|
||||
/**
|
||||
The local matrix. If you modify this, call `decompose()` to update the
|
||||
`loc`, `rot` and `scale` fields, or `buildMatrix()` to update
|
||||
everything.
|
||||
**/
|
||||
public var local: Mat4;
|
||||
/**
|
||||
The local translation. Changes to this field should be applied by
|
||||
calling `buildMatrix()`.
|
||||
**/
|
||||
public var loc: Vec4;
|
||||
/**
|
||||
The local rotation. Changes to this field should be applied by
|
||||
calling `buildMatrix()`.
|
||||
**/
|
||||
public var rot: Quat;
|
||||
/**
|
||||
The local scale. Changes to this field should be applied by
|
||||
calling `buildMatrix()`.
|
||||
**/
|
||||
public var scale: Vec4;
|
||||
/**
|
||||
Uniform scale factor for `world` matrix.
|
||||
**/
|
||||
public var scaleWorld: kha.FastFloat = 1.0;
|
||||
/**
|
||||
The world matrix with `scaleWorld` applied (read-only).
|
||||
**/
|
||||
public var worldUnpack: Mat4;
|
||||
/**
|
||||
Flag to rebuild the `world` matrix on next update.
|
||||
**/
|
||||
public var dirty: Bool;
|
||||
/**
|
||||
The object that is effected by this transform.
|
||||
**/
|
||||
public var object: Object;
|
||||
/**
|
||||
The dimensions of the object in local space (without parent, prepended
|
||||
or appended matrices applied).
|
||||
**/
|
||||
public var dim: Vec4;
|
||||
/**
|
||||
The radius of the smallest sphere that encompasses the object in local
|
||||
space.
|
||||
**/
|
||||
public var radius: kha.FastFloat;
|
||||
|
||||
static var temp = Mat4.identity();
|
||||
static var q = new Quat();
|
||||
|
||||
var boneParent: Mat4 = null;
|
||||
var lastWorld: Mat4 = null;
|
||||
|
||||
// Wrong order returned from getEuler(), store last state for animation
|
||||
var _eulerX: kha.FastFloat;
|
||||
var _eulerY: kha.FastFloat;
|
||||
var _eulerZ: kha.FastFloat;
|
||||
|
||||
// Animated delta transform
|
||||
var dloc: Vec4 = null;
|
||||
var drot: Quat = null;
|
||||
var dscale: Vec4 = null;
|
||||
var _deulerX: kha.FastFloat;
|
||||
var _deulerY: kha.FastFloat;
|
||||
var _deulerZ: kha.FastFloat;
|
||||
|
||||
public function new(object: Object) {
|
||||
this.object = object;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
Reset to a null transform: zero location and rotation, and a uniform
|
||||
scale of one. Other fields such as prepended matrices and bone parents
|
||||
will not be changed.
|
||||
**/
|
||||
public function reset() {
|
||||
world = Mat4.identity();
|
||||
worldUnpack = Mat4.identity();
|
||||
local = Mat4.identity();
|
||||
loc = new Vec4();
|
||||
rot = new Quat();
|
||||
scale = new Vec4(1.0, 1.0, 1.0);
|
||||
dim = new Vec4(2.0, 2.0, 2.0);
|
||||
radius = 1.0;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Rebuild the matrices, if needed.
|
||||
**/
|
||||
public function update() {
|
||||
if (dirty) buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Clear delta transforms. `dloc`, `drot` and `dscale` are set to `null`
|
||||
**/
|
||||
public function clearDelta() {
|
||||
|
||||
dloc = null;
|
||||
drot = null;
|
||||
dscale = null;
|
||||
}
|
||||
|
||||
/**
|
||||
Reset delta transforms. `dloc`, `drot` and `dscale`
|
||||
are set to `Vec4(0, 0, 0)`, `Quat(0, 0, 0, 0)` and `Vec4(1, 1, 1)` respectively
|
||||
**/
|
||||
public function resetDelta() {
|
||||
dloc = new Vec4();
|
||||
drot = new Quat();
|
||||
_deulerX = _deulerY = _deulerZ = 0.0;
|
||||
dscale = new Vec4().set(1, 1, 1);
|
||||
}
|
||||
|
||||
function composeDelta() {
|
||||
// Delta transform
|
||||
var dl = new Vec4().addvecs(loc, dloc);
|
||||
var ds = new Vec4().setFrom(scale);
|
||||
ds.x *= dscale.x;
|
||||
ds.y *= dscale.y;
|
||||
ds.z *= dscale.z;
|
||||
var dr = new Quat().fromEuler(_deulerX, _deulerY, _deulerZ);
|
||||
dr.multquats(dr, rot);
|
||||
dr.multquats(drot, dr);
|
||||
local.compose(dl, dr, ds);
|
||||
}
|
||||
|
||||
/**
|
||||
Update the transform matrix based on `loc`, `rot`, and `scale`. If any
|
||||
change is made to `loc`, `rot`, or `scale` `buildMatrix()` must be
|
||||
called to update the objects transform.
|
||||
**/
|
||||
public function buildMatrix() {
|
||||
dloc == null ? local.compose(loc, rot, scale) : composeDelta();
|
||||
|
||||
if (boneParent != null) local.multmats(boneParent, local);
|
||||
|
||||
if (object.parent != null && !localOnly) {
|
||||
world.multmats3x4(local, object.parent.transform.world);
|
||||
}
|
||||
else {
|
||||
world.setFrom(local);
|
||||
}
|
||||
|
||||
worldUnpack.setFrom(world);
|
||||
if (scaleWorld != 1.0) {
|
||||
worldUnpack._00 *= scaleWorld;
|
||||
worldUnpack._01 *= scaleWorld;
|
||||
worldUnpack._02 *= scaleWorld;
|
||||
worldUnpack._03 *= scaleWorld;
|
||||
worldUnpack._10 *= scaleWorld;
|
||||
worldUnpack._11 *= scaleWorld;
|
||||
worldUnpack._12 *= scaleWorld;
|
||||
worldUnpack._13 *= scaleWorld;
|
||||
worldUnpack._20 *= scaleWorld;
|
||||
worldUnpack._21 *= scaleWorld;
|
||||
worldUnpack._22 *= scaleWorld;
|
||||
worldUnpack._23 *= scaleWorld;
|
||||
}
|
||||
|
||||
// Constraints
|
||||
if (object.constraints != null) for (c in object.constraints) c.apply(this);
|
||||
|
||||
computeDim();
|
||||
|
||||
// Update children
|
||||
for (n in object.children) {
|
||||
n.transform.buildMatrix();
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
Move the game Object by the defined amount relative to its current location.
|
||||
@param x Amount to move on the local x axis.
|
||||
@param y Amount to move on the local y axis.
|
||||
@param z Amount to move on the local z axis.
|
||||
**/
|
||||
public function translate(x: kha.FastFloat, y: kha.FastFloat, z: kha.FastFloat) {
|
||||
loc.x += x;
|
||||
loc.y += y;
|
||||
loc.z += z;
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Set the local matrix and update `loc`, `rot`, `scale` and `world`.
|
||||
@param mat The new local matrix.
|
||||
**/
|
||||
public function setMatrix(mat: Mat4) {
|
||||
local.setFrom(mat);
|
||||
decompose();
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Apply another transform to this one, i.e. multiply this transform's
|
||||
local matrix by another.
|
||||
@param mat The other transform to apply.
|
||||
**/
|
||||
public function multMatrix(mat: Mat4) {
|
||||
local.multmat(mat);
|
||||
decompose();
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Update the `loc`, `rot` and `scale` fields according to the local
|
||||
matrix. You may need to call this after directly mutating the local
|
||||
matrix.
|
||||
**/
|
||||
public function decompose() {
|
||||
local.decompose(loc, rot, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
Rotate around an axis.
|
||||
@param axis The axis to rotate around.
|
||||
@param f The magnitude of the rotation in radians.
|
||||
**/
|
||||
public function rotate(axis: Vec4, f: kha.FastFloat) {
|
||||
q.fromAxisAngle(axis, f);
|
||||
rot.multquats(q, rot);
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Apply a scaled translation in local space.
|
||||
@param axis The direction to move.
|
||||
@param f A multiplier for the movement. If `axis` is a unit
|
||||
vector, then this is the distance to move.
|
||||
**/
|
||||
public function move(axis: Vec4, f = 1.0) {
|
||||
loc.addf(axis.x * f, axis.y * f, axis.z * f);
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Set the rotation of the object in radians.
|
||||
@param x Set the x axis rotation in radians.
|
||||
@param y Set the y axis rotation in radians.
|
||||
@param z Set the z axis rotation in radians.
|
||||
**/
|
||||
public function setRotation(x: kha.FastFloat, y: kha.FastFloat, z: kha.FastFloat) {
|
||||
rot.fromEuler(x, y, z);
|
||||
_eulerX = x;
|
||||
_eulerY = y;
|
||||
_eulerZ = z;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
function computeRadius() {
|
||||
radius = Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
|
||||
}
|
||||
|
||||
function computeDim() {
|
||||
if (object.raw == null) {
|
||||
computeRadius();
|
||||
return;
|
||||
}
|
||||
var d = object.raw.dimensions;
|
||||
if (d == null) dim.set(2 * scale.x, 2 * scale.y, 2 * scale.z);
|
||||
else dim.set(d[0] * scale.x, d[1] * scale.y, d[2] * scale.z);
|
||||
computeRadius();
|
||||
}
|
||||
|
||||
public function applyParentInverse() {
|
||||
var pt = object.parent.transform;
|
||||
pt.buildMatrix();
|
||||
temp.getInverse(pt.world);
|
||||
this.local.multmat(temp);
|
||||
this.decompose();
|
||||
this.buildMatrix();
|
||||
}
|
||||
|
||||
public function applyParent() {
|
||||
var pt = object.parent.transform;
|
||||
pt.buildMatrix();
|
||||
this.local.multmat(pt.world);
|
||||
this.decompose();
|
||||
this.buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Check whether the transform has changed at all since the last time
|
||||
this function was called.
|
||||
@return `true` if the transform has changed.
|
||||
**/
|
||||
public function diff(): Bool {
|
||||
if (lastWorld == null) {
|
||||
lastWorld = Mat4.identity().setFrom(world);
|
||||
return false;
|
||||
}
|
||||
var a = world;
|
||||
var b = lastWorld;
|
||||
var r = a._00 != b._00 || a._01 != b._01 || a._02 != b._02 || a._03 != b._03 ||
|
||||
a._10 != b._10 || a._11 != b._11 || a._12 != b._12 || a._13 != b._13 ||
|
||||
a._20 != b._20 || a._21 != b._21 || a._22 != b._22 || a._23 != b._23 ||
|
||||
a._30 != b._30 || a._31 != b._31 || a._32 != b._32 || a._33 != b._33;
|
||||
if (r) lastWorld.setFrom(world);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
@return The look vector (positive local y axis) in world space.
|
||||
**/
|
||||
public inline function look(): Vec4 {
|
||||
return world.look();
|
||||
}
|
||||
|
||||
/**
|
||||
@return The right vector (positive local x axis) in world space.
|
||||
**/
|
||||
public inline function right(): Vec4 {
|
||||
return world.right();
|
||||
}
|
||||
|
||||
/**
|
||||
@return The up vector (positive local z axis) in world space.
|
||||
**/
|
||||
public inline function up(): Vec4 {
|
||||
return world.up();
|
||||
}
|
||||
|
||||
/**
|
||||
@return The world x location.
|
||||
**/
|
||||
public inline function worldx(): kha.FastFloat {
|
||||
return world._30;
|
||||
}
|
||||
|
||||
/**
|
||||
@return The world y location.
|
||||
**/
|
||||
public inline function worldy(): kha.FastFloat {
|
||||
return world._31;
|
||||
}
|
||||
|
||||
/**
|
||||
@return The world z location.
|
||||
**/
|
||||
public inline function worldz(): kha.FastFloat {
|
||||
return world._32;
|
||||
}
|
||||
}
|
||||
1297
leenkx/Sources/iron/object/Uniforms.hx
Normal file
1297
leenkx/Sources/iron/object/Uniforms.hx
Normal file
File diff suppressed because it is too large
Load Diff
38
leenkx/Sources/iron/system/Audio.hx
Normal file
38
leenkx/Sources/iron/system/Audio.hx
Normal file
@ -0,0 +1,38 @@
|
||||
package iron.system;
|
||||
|
||||
import kha.audio1.AudioChannel;
|
||||
|
||||
/**
|
||||
Audio playback. This class wraps around `kha.audio1.Audio`.
|
||||
**/
|
||||
class Audio {
|
||||
|
||||
#if lnx_audio
|
||||
|
||||
public function new() {}
|
||||
|
||||
/**
|
||||
Plays the given sound.
|
||||
|
||||
If `stream` is `true` and the sound has compressed data, it is streamed
|
||||
from disk instead of being fully loaded into memory. This is useful for
|
||||
longer sounds such as background music.
|
||||
|
||||
This function returns `null` if:
|
||||
|
||||
- there is no unoccupied audio channel available for playback.
|
||||
- the sound has compressed data only but `stream` is `false`. In this
|
||||
case, call `kha.Sound.uncompress()` first.
|
||||
**/
|
||||
public static function play(sound: kha.Sound, loop = false, stream = false): Null<AudioChannel> {
|
||||
if (stream && sound.compressedData != null) {
|
||||
return kha.audio1.Audio.stream(sound, loop);
|
||||
}
|
||||
else if (sound.uncompressedData != null) {
|
||||
return kha.audio1.Audio.play(sound, loop);
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
||||
782
leenkx/Sources/iron/system/Input.hx
Normal file
782
leenkx/Sources/iron/system/Input.hx
Normal file
@ -0,0 +1,782 @@
|
||||
package iron.system;
|
||||
|
||||
import kha.input.KeyCode;
|
||||
|
||||
class Input {
|
||||
|
||||
public static var occupied = false;
|
||||
static var mouse: Mouse = null;
|
||||
static var pen: Pen = null;
|
||||
static var keyboard: Keyboard = null;
|
||||
static var gamepads: Array<Gamepad> = [];
|
||||
static var sensor: Sensor = null;
|
||||
static var registered = false;
|
||||
public static var virtualButtons: Map<String, VirtualButton> = null; // Button name
|
||||
|
||||
public static function reset() {
|
||||
occupied = false;
|
||||
if (mouse != null) mouse.reset();
|
||||
if (pen != null) pen.reset();
|
||||
if (keyboard != null) keyboard.reset();
|
||||
for (gamepad in gamepads) gamepad.reset();
|
||||
}
|
||||
|
||||
public static function endFrame() {
|
||||
if (mouse != null) mouse.endFrame();
|
||||
if (pen != null) pen.endFrame();
|
||||
if (keyboard != null) keyboard.endFrame();
|
||||
for (gamepad in gamepads) gamepad.endFrame();
|
||||
|
||||
if (virtualButtons != null) {
|
||||
for (vb in virtualButtons) vb.started = vb.released = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMouse(): Mouse {
|
||||
if (!registered) register();
|
||||
if (mouse == null) mouse = new Mouse();
|
||||
return mouse;
|
||||
}
|
||||
|
||||
public static function getPen(): Pen {
|
||||
if (!registered) register();
|
||||
if (pen == null) pen = new Pen();
|
||||
return pen;
|
||||
}
|
||||
|
||||
public static function getSurface(): Surface {
|
||||
if (!registered) register();
|
||||
// Map to mouse for now..
|
||||
return getMouse();
|
||||
}
|
||||
|
||||
/**
|
||||
Get the Keyboard object. If it is not registered yet then register a new Keyboard.
|
||||
**/
|
||||
public static function getKeyboard(): Keyboard {
|
||||
if (!registered) register();
|
||||
if (keyboard == null) keyboard = new Keyboard();
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
public static function getGamepad(i = 0): Gamepad {
|
||||
if (i >= 4) return null;
|
||||
if (!registered) register();
|
||||
while (gamepads.length <= i) gamepads.push(new Gamepad(gamepads.length));
|
||||
return gamepads[i].connected ? gamepads[i] : null;
|
||||
}
|
||||
|
||||
public static function getSensor(): Sensor {
|
||||
if (!registered) register();
|
||||
if (sensor == null) sensor = new Sensor();
|
||||
return sensor;
|
||||
}
|
||||
|
||||
public static function getVirtualButton(virtual: String): VirtualButton {
|
||||
if (!registered) register();
|
||||
if (virtualButtons == null) return null;
|
||||
return virtualButtons.get(virtual);
|
||||
}
|
||||
|
||||
static inline function register() {
|
||||
registered = true;
|
||||
App.notifyOnEndFrame(endFrame);
|
||||
App.notifyOnReset(reset);
|
||||
// Reset mouse delta on foreground
|
||||
kha.System.notifyOnApplicationState(function() { getMouse().reset(); }, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualButton {
|
||||
public var started = false;
|
||||
public var released = false;
|
||||
public var down = false;
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
class VirtualInput {
|
||||
var virtualButtons: Map<String, VirtualButton> = null; // Button id
|
||||
|
||||
public function setVirtual(virtual: String, button: String) {
|
||||
if (Input.virtualButtons == null) Input.virtualButtons = new Map<String, VirtualButton>();
|
||||
|
||||
var vb = Input.virtualButtons.get(virtual);
|
||||
if (vb == null) {
|
||||
vb = new VirtualButton();
|
||||
Input.virtualButtons.set(virtual, vb);
|
||||
}
|
||||
|
||||
if (virtualButtons == null) virtualButtons = new Map<String, VirtualButton>();
|
||||
virtualButtons.set(button, vb);
|
||||
}
|
||||
|
||||
function downVirtual(button: String) {
|
||||
if (virtualButtons != null) {
|
||||
var vb = virtualButtons.get(button);
|
||||
if (vb != null) {
|
||||
vb.down = true;
|
||||
vb.started = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function upVirtual(button: String) {
|
||||
if (virtualButtons != null) {
|
||||
var vb = virtualButtons.get(button);
|
||||
if (vb != null) {
|
||||
vb.down = false;
|
||||
vb.released = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Surface = Mouse;
|
||||
|
||||
class Mouse extends VirtualInput {
|
||||
|
||||
public static var buttons = ["left", "right", "middle", "side1", "side2"];
|
||||
var buttonsDown = [false, false, false, false, false];
|
||||
var buttonsStarted = [false, false, false, false, false];
|
||||
var buttonsReleased = [false, false, false, false, false];
|
||||
|
||||
public var x(default, null) = 0.0;
|
||||
public var y(default, null) = 0.0;
|
||||
public var viewX(get, null) = 0.0;
|
||||
public var viewY(get, null) = 0.0;
|
||||
public var moved(default, null) = false;
|
||||
public var movementX(default, null) = 0.0;
|
||||
public var movementY(default, null) = 0.0;
|
||||
public var wheelDelta(default, null) = 0;
|
||||
public var locked(default, null) = false;
|
||||
public var hidden(default, null) = false;
|
||||
public var lastX = -1.0;
|
||||
public var lastY = -1.0;
|
||||
|
||||
public function new() {
|
||||
kha.input.Mouse.get().notify(downListener, upListener, moveListener, wheelListener);
|
||||
#if (kha_android || kha_ios)
|
||||
if (kha.input.Surface.get() != null) kha.input.Surface.get().notify(onTouchDown, onTouchUp, onTouchMove);
|
||||
#end
|
||||
}
|
||||
|
||||
public function endFrame() {
|
||||
buttonsStarted[0] = buttonsStarted[1] = buttonsStarted[2] = buttonsStarted[3] = buttonsStarted[4] = false;
|
||||
buttonsReleased[0] = buttonsReleased[1] = buttonsReleased[2] = buttonsReleased[3] = buttonsReleased[4] = false;
|
||||
moved = false;
|
||||
movementX = 0;
|
||||
movementY = 0;
|
||||
wheelDelta = 0;
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
buttonsDown[0] = buttonsDown[1] = buttonsDown[2] = buttonsDown[3] = buttonsDown[4] = false;
|
||||
endFrame();
|
||||
}
|
||||
|
||||
function buttonIndex(button: String): Int {
|
||||
for (i in 0...buttons.length) if (buttons[i] == button) return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function down(button = "left"): Bool {
|
||||
return buttonsDown[buttonIndex(button)];
|
||||
}
|
||||
|
||||
public function started(button = "left"): Bool {
|
||||
return buttonsStarted[buttonIndex(button)];
|
||||
}
|
||||
|
||||
public function released(button = "left"): Bool {
|
||||
return buttonsReleased[buttonIndex(button)];
|
||||
}
|
||||
|
||||
public function lock() {
|
||||
if (kha.input.Mouse.get().canLock()) {
|
||||
kha.input.Mouse.get().lock();
|
||||
locked = true;
|
||||
hidden = true;
|
||||
}
|
||||
}
|
||||
public function unlock() {
|
||||
if (kha.input.Mouse.get().canLock()) {
|
||||
kha.input.Mouse.get().unlock();
|
||||
locked = false;
|
||||
hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function hide() {
|
||||
kha.input.Mouse.get().hideSystemCursor();
|
||||
hidden = true;
|
||||
}
|
||||
|
||||
public function show() {
|
||||
kha.input.Mouse.get().showSystemCursor();
|
||||
hidden = false;
|
||||
}
|
||||
|
||||
function downListener(index: Int, x: Int, y: Int) {
|
||||
if (Input.getPen().inUse) return;
|
||||
|
||||
buttonsDown[index] = true;
|
||||
buttonsStarted[index] = true;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
#if (kha_android || kha_ios || kha_webgl) // For movement delta using touch
|
||||
if (index == 0) {
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
#end
|
||||
|
||||
downVirtual(buttons[index]);
|
||||
}
|
||||
|
||||
function upListener(index: Int, x: Int, y: Int) {
|
||||
if (Input.getPen().inUse) return;
|
||||
|
||||
buttonsDown[index] = false;
|
||||
buttonsReleased[index] = true;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
upVirtual(buttons[index]);
|
||||
}
|
||||
|
||||
function moveListener(x: Int, y: Int, movementX: Int, movementY: Int) {
|
||||
if (lastX == -1.0 && lastY == -1.0) { // First frame init
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
if (locked) {
|
||||
// Can be called multiple times per frame
|
||||
this.movementX += movementX;
|
||||
this.movementY += movementY;
|
||||
}
|
||||
else {
|
||||
this.movementX += x - lastX;
|
||||
this.movementY += y - lastY;
|
||||
}
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
moved = true;
|
||||
}
|
||||
|
||||
function wheelListener(delta: Int) {
|
||||
wheelDelta = delta;
|
||||
}
|
||||
|
||||
#if (kha_android || kha_ios)
|
||||
public function onTouchDown(index: Int, x: Int, y: Int) {
|
||||
if (index == 1) { // Two fingers down - right mouse button
|
||||
buttonsDown[0] = false;
|
||||
downListener(1, Std.int(this.x), Std.int(this.y));
|
||||
pinchStarted = true;
|
||||
pinchTotal = 0.0;
|
||||
pinchDistance = 0.0;
|
||||
}
|
||||
else if (index == 2) { // Three fingers down - middle mouse button
|
||||
buttonsDown[1] = false;
|
||||
downListener(2, Std.int(this.x), Std.int(this.y));
|
||||
}
|
||||
}
|
||||
|
||||
public function onTouchUp(index: Int, x: Int, y: Int) {
|
||||
if (index == 1) upListener(1, Std.int(this.x), Std.int(this.y));
|
||||
else if (index == 2) upListener(2, Std.int(this.x), Std.int(this.y));
|
||||
}
|
||||
|
||||
var pinchDistance = 0.0;
|
||||
var pinchTotal = 0.0;
|
||||
var pinchStarted = false;
|
||||
|
||||
public function onTouchMove(index: Int, x: Int, y: Int) {
|
||||
// Pinch to zoom - mouse wheel
|
||||
if (index == 1) {
|
||||
var lastDistance = pinchDistance;
|
||||
var dx = this.x - x;
|
||||
var dy = this.y - y;
|
||||
pinchDistance = Math.sqrt(dx * dx + dy * dy);
|
||||
pinchTotal += lastDistance != 0 ? lastDistance - pinchDistance : 0;
|
||||
if (!pinchStarted) {
|
||||
wheelDelta = Std.int(pinchTotal / 10);
|
||||
if (wheelDelta != 0) {
|
||||
pinchTotal = 0.0;
|
||||
}
|
||||
}
|
||||
pinchStarted = false;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
inline function get_viewX(): Float {
|
||||
return x - iron.App.x();
|
||||
}
|
||||
|
||||
inline function get_viewY(): Float {
|
||||
return y - iron.App.y();
|
||||
}
|
||||
}
|
||||
|
||||
class Pen extends VirtualInput {
|
||||
|
||||
static var buttons = ["tip"];
|
||||
var buttonsDown = [false];
|
||||
var buttonsStarted = [false];
|
||||
var buttonsReleased = [false];
|
||||
|
||||
public var x(default, null) = 0.0;
|
||||
public var y(default, null) = 0.0;
|
||||
public var viewX(get, null) = 0.0;
|
||||
public var viewY(get, null) = 0.0;
|
||||
public var moved(default, null) = false;
|
||||
public var movementX(default, null) = 0.0;
|
||||
public var movementY(default, null) = 0.0;
|
||||
public var pressure(default, null) = 0.0;
|
||||
public var connected = false;
|
||||
public var inUse = false;
|
||||
var lastX = -1.0;
|
||||
var lastY = -1.0;
|
||||
|
||||
public function new() {
|
||||
var pen = kha.input.Pen.get();
|
||||
if (pen != null) pen.notify(downListener, upListener, moveListener);
|
||||
}
|
||||
|
||||
public function endFrame() {
|
||||
buttonsStarted[0] = false;
|
||||
buttonsReleased[0] = false;
|
||||
moved = false;
|
||||
movementX = 0;
|
||||
movementY = 0;
|
||||
inUse = false;
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
buttonsDown[0] = false;
|
||||
endFrame();
|
||||
}
|
||||
|
||||
function buttonIndex(button: String): Int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function down(button = "tip"): Bool {
|
||||
return buttonsDown[buttonIndex(button)];
|
||||
}
|
||||
|
||||
public function started(button = "tip"): Bool {
|
||||
return buttonsStarted[buttonIndex(button)];
|
||||
}
|
||||
|
||||
public function released(button = "tip"): Bool {
|
||||
return buttonsReleased[buttonIndex(button)];
|
||||
}
|
||||
|
||||
function downListener(x: Int, y: Int, pressure: Float) {
|
||||
buttonsDown[0] = true;
|
||||
buttonsStarted[0] = true;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pressure = pressure;
|
||||
|
||||
#if (!kha_android && !kha_ios)
|
||||
@:privateAccess Input.getMouse().downListener(0, x, y);
|
||||
#end
|
||||
}
|
||||
|
||||
function upListener(x: Int, y: Int, pressure: Float) {
|
||||
#if (!kha_android && !kha_ios)
|
||||
if (buttonsStarted[0]) { buttonsStarted[0] = false; inUse = true; return; }
|
||||
#end
|
||||
|
||||
buttonsDown[0] = false;
|
||||
buttonsReleased[0] = true;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.pressure = pressure;
|
||||
|
||||
#if (!kha_android && !kha_ios)
|
||||
@:privateAccess Input.getMouse().upListener(0, x, y);
|
||||
inUse = true; // On pen release, additional mouse down & up events are fired at once - filter those out
|
||||
#end
|
||||
}
|
||||
|
||||
function moveListener(x: Int, y: Int, pressure: Float) {
|
||||
if (lastX == -1.0 && lastY == -1.0) { // First frame init
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
this.movementX = x - lastX;
|
||||
this.movementY = y - lastY;
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
moved = true;
|
||||
this.pressure = pressure;
|
||||
connected = true;
|
||||
}
|
||||
|
||||
inline function get_viewX(): Float {
|
||||
return x - iron.App.x();
|
||||
}
|
||||
|
||||
inline function get_viewY(): Float {
|
||||
return y - iron.App.y();
|
||||
}
|
||||
}
|
||||
|
||||
class Keyboard extends VirtualInput {
|
||||
|
||||
public static var keys = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "space", "backspace", "tab", "enter", "shift", "control", "alt", "capslock", "win", "escape", "delete", "up", "down", "left", "right", "back", ",", ".", ":", ";", "<", "=", ">", "?", "!", '"', "#", "$", "%", "&", "_", "(", ")", "*", "|", "{", "}", "[", "]", "~", "`", "/", "\\", "@", "+", "-", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12"];
|
||||
var keysDown = new Map<String, Bool>();
|
||||
var keysStarted = new Map<String, Bool>();
|
||||
var keysReleased = new Map<String, Bool>();
|
||||
var keysFrame: Array<String> = [];
|
||||
var repeatKey = false;
|
||||
var repeatTime = 0.0;
|
||||
|
||||
public function new() {
|
||||
reset();
|
||||
kha.input.Keyboard.get().notify(downListener, upListener, pressListener);
|
||||
}
|
||||
|
||||
public function endFrame() {
|
||||
if (keysFrame.length > 0) {
|
||||
for (s in keysFrame) {
|
||||
keysStarted.set(s, false);
|
||||
keysReleased.set(s, false);
|
||||
}
|
||||
keysFrame.splice(0, keysFrame.length);
|
||||
}
|
||||
|
||||
if (kha.Scheduler.time() - repeatTime > 0.05) {
|
||||
repeatTime = kha.Scheduler.time();
|
||||
repeatKey = true;
|
||||
}
|
||||
else repeatKey = false;
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
// Use Map for now..
|
||||
for (s in keys) {
|
||||
keysDown.set(s, false);
|
||||
keysStarted.set(s, false);
|
||||
keysReleased.set(s, false);
|
||||
}
|
||||
endFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
Check if a key is currently pressed.
|
||||
@param key A String representing the physical keyboard key to check.
|
||||
@return Bool. Returns true or false depending on the keyboard state.
|
||||
**/
|
||||
public function down(key: String): Bool {
|
||||
return keysDown.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
Check if a key has started being pressed down. Will only be run once until the key is released and pressed again.
|
||||
@param key A String representing the physical keyboard key to check.
|
||||
@return Bool. Returns true or false depending on the keyboard state.
|
||||
**/
|
||||
public function started(key: String): Bool {
|
||||
return keysStarted.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
Check if a key has been released from being pressed down. Will only be run once until the key is pressed again and release again.
|
||||
@param key A String representing the physical keyboard key to check.
|
||||
@return Bool. Returns true or false depending on the keyboard state.
|
||||
**/
|
||||
public function released(key: String): Bool {
|
||||
return keysReleased.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
Check every repeat period if a key is currently pressed.
|
||||
@param key A String representing the physical keyboard key to check.
|
||||
@return Bool. Returns true or false depending on the keyboard state.
|
||||
**/
|
||||
public function repeat(key: String): Bool {
|
||||
return keysStarted.get(key) || (repeatKey && keysDown.get(key));
|
||||
}
|
||||
|
||||
public static function keyCode(key: KeyCode): String {
|
||||
return switch(key) {
|
||||
case KeyCode.Space: "space";
|
||||
case KeyCode.Backspace: "backspace";
|
||||
case KeyCode.Tab: "tab";
|
||||
case KeyCode.Return: "enter";
|
||||
case KeyCode.Shift: "shift";
|
||||
case KeyCode.Control: "control";
|
||||
#if kha_darwin
|
||||
case KeyCode.Meta: "control";
|
||||
#end
|
||||
case KeyCode.Alt: "alt";
|
||||
case KeyCode.CapsLock: "capslock";
|
||||
case KeyCode.Win: "win";
|
||||
case KeyCode.Escape: "escape";
|
||||
case KeyCode.Delete: "delete";
|
||||
case KeyCode.Up: "up";
|
||||
case KeyCode.Down: "down";
|
||||
case KeyCode.Left: "left";
|
||||
case KeyCode.Right: "right";
|
||||
case KeyCode.Back: "back";
|
||||
case KeyCode.Comma: ",";
|
||||
case KeyCode.Period: ".";
|
||||
case KeyCode.Colon: ":";
|
||||
case KeyCode.Semicolon: ";";
|
||||
case KeyCode.LessThan: "<";
|
||||
case KeyCode.Equals: "=";
|
||||
case KeyCode.GreaterThan: ">";
|
||||
case KeyCode.QuestionMark: "?";
|
||||
case KeyCode.Exclamation: "!";
|
||||
case KeyCode.DoubleQuote: '"';
|
||||
case KeyCode.Hash: "#";
|
||||
case KeyCode.Dollar: "$";
|
||||
case KeyCode.Percent: "%";
|
||||
case KeyCode.Ampersand: "&";
|
||||
case KeyCode.Underscore: "_";
|
||||
case KeyCode.OpenParen: "(";
|
||||
case KeyCode.CloseParen: ")";
|
||||
case KeyCode.Asterisk: "*";
|
||||
case KeyCode.Pipe: "|";
|
||||
case KeyCode.OpenCurlyBracket: "{";
|
||||
case KeyCode.CloseCurlyBracket: "}";
|
||||
case KeyCode.OpenBracket: "[";
|
||||
case KeyCode.CloseBracket: "]";
|
||||
case KeyCode.Tilde: "~";
|
||||
case KeyCode.BackQuote: "`";
|
||||
case KeyCode.Slash: "/";
|
||||
case KeyCode.BackSlash: "\\";
|
||||
case KeyCode.At: "@";
|
||||
case KeyCode.Add: "+";
|
||||
case KeyCode.Plus: "+";
|
||||
case KeyCode.Subtract: "-";
|
||||
case KeyCode.HyphenMinus: "-";
|
||||
case KeyCode.Multiply: "*";
|
||||
case KeyCode.Divide: "/";
|
||||
case KeyCode.Decimal: ".";
|
||||
case KeyCode.Zero: "0";
|
||||
case KeyCode.Numpad0: "0";
|
||||
case KeyCode.One: "1";
|
||||
case KeyCode.Numpad1: "1";
|
||||
case KeyCode.Two: "2";
|
||||
case KeyCode.Numpad2: "2";
|
||||
case KeyCode.Three: "3";
|
||||
case KeyCode.Numpad3: "3";
|
||||
case KeyCode.Four: "4";
|
||||
case KeyCode.Numpad4: "4";
|
||||
case KeyCode.Five: "5";
|
||||
case KeyCode.Numpad5: "5";
|
||||
case KeyCode.Six: "6";
|
||||
case KeyCode.Numpad6: "6";
|
||||
case KeyCode.Seven: "7";
|
||||
case KeyCode.Numpad7: "7";
|
||||
case KeyCode.Eight: "8";
|
||||
case KeyCode.Numpad8: "8";
|
||||
case KeyCode.Nine: "9";
|
||||
case KeyCode.Numpad9: "9";
|
||||
case KeyCode.F1: "f1";
|
||||
case KeyCode.F2: "f2";
|
||||
case KeyCode.F3: "f3";
|
||||
case KeyCode.F4: "f4";
|
||||
case KeyCode.F5: "f5";
|
||||
case KeyCode.F6: "f6";
|
||||
case KeyCode.F7: "f7";
|
||||
case KeyCode.F8: "f8";
|
||||
case KeyCode.F9: "f9";
|
||||
case KeyCode.F10: "f10";
|
||||
case KeyCode.F11: "f11";
|
||||
case KeyCode.F12: "f12";
|
||||
default: String.fromCharCode(cast key).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function downListener(code: KeyCode) {
|
||||
var s = keyCode(code);
|
||||
keysFrame.push(s);
|
||||
keysStarted.set(s, true);
|
||||
keysDown.set(s, true);
|
||||
repeatTime = kha.Scheduler.time() + 0.4;
|
||||
|
||||
#if kha_android_rmb // Detect right mouse button on Android..
|
||||
if (code == KeyCode.Back) {
|
||||
var m = Input.getMouse();
|
||||
@:privateAccess if (!m.buttonsDown[1]) m.downListener(1, Std.int(m.x), Std.int(m.y));
|
||||
}
|
||||
#end
|
||||
|
||||
downVirtual(s);
|
||||
}
|
||||
|
||||
function upListener(code: KeyCode) {
|
||||
var s = keyCode(code);
|
||||
keysFrame.push(s);
|
||||
keysReleased.set(s, true);
|
||||
keysDown.set(s, false);
|
||||
|
||||
#if kha_android_rmb
|
||||
if (code == KeyCode.Back) {
|
||||
var m = Input.getMouse();
|
||||
@:privateAccess m.upListener(1, Std.int(m.x), Std.int(m.y));
|
||||
}
|
||||
#end
|
||||
|
||||
upVirtual(s);
|
||||
}
|
||||
|
||||
function pressListener(char: String) {}
|
||||
}
|
||||
|
||||
class GamepadStick {
|
||||
public var x = 0.0;
|
||||
public var y = 0.0;
|
||||
public var lastX = 0.0;
|
||||
public var lastY = 0.0;
|
||||
public var moved = false;
|
||||
public var movementX = 0.0;
|
||||
public var movementY = 0.0;
|
||||
public function new() {}
|
||||
}
|
||||
|
||||
class Gamepad extends VirtualInput {
|
||||
|
||||
public static var buttonsPS = ["cross", "circle", "square", "triangle", "l1", "r1", "l2", "r2", "share", "options", "l3", "r3", "up", "down", "left", "right", "home", "touchpad"];
|
||||
public static var buttonsXBOX = ["a", "b", "x", "y", "l1", "r1", "l2", "r2", "share", "options", "l3", "r3", "up", "down", "left", "right", "home", "touchpad"];
|
||||
public static var buttons = buttonsPS;
|
||||
|
||||
public var id(get, never): String;
|
||||
inline function get_id() return kha.input.Gamepad.get(num).id;
|
||||
|
||||
var buttonsDown: Array<Float> = []; // Intensity 0 - 1
|
||||
var buttonsStarted: Array<Bool> = [];
|
||||
var buttonsReleased: Array<Bool> = [];
|
||||
|
||||
var buttonsFrame: Array<Int> = [];
|
||||
|
||||
public var leftStick = new GamepadStick();
|
||||
public var rightStick = new GamepadStick();
|
||||
|
||||
public var connected = false;
|
||||
var num = 0;
|
||||
|
||||
public function new(i: Int, virtual = false) {
|
||||
for (s in buttons) {
|
||||
buttonsDown.push(0.0);
|
||||
buttonsStarted.push(false);
|
||||
buttonsReleased.push(false);
|
||||
}
|
||||
num = i;
|
||||
reset();
|
||||
virtual ? connected = true : connect();
|
||||
}
|
||||
|
||||
var connects = 0;
|
||||
function connect() {
|
||||
var gamepad = kha.input.Gamepad.get(num);
|
||||
if (gamepad == null) {
|
||||
// if (connects < 10) leenkx.system.Tween.timer(1, connect);
|
||||
// connects++;
|
||||
return;
|
||||
}
|
||||
connected = true;
|
||||
gamepad.notify(axisListener, buttonListener);
|
||||
}
|
||||
|
||||
public function endFrame() {
|
||||
if (buttonsFrame.length > 0) {
|
||||
for (i in buttonsFrame) {
|
||||
buttonsStarted[i] = false;
|
||||
buttonsReleased[i] = false;
|
||||
}
|
||||
buttonsFrame.splice(0, buttonsFrame.length);
|
||||
}
|
||||
leftStick.moved = false;
|
||||
leftStick.movementX = 0;
|
||||
leftStick.movementY = 0;
|
||||
rightStick.moved = false;
|
||||
rightStick.movementX = 0;
|
||||
rightStick.movementY = 0;
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
for (i in 0...buttonsDown.length) {
|
||||
buttonsDown[i] = 0.0;
|
||||
buttonsStarted[i] = false;
|
||||
buttonsReleased[i] = false;
|
||||
}
|
||||
endFrame();
|
||||
}
|
||||
|
||||
public static function keyCode(button: Int): String {
|
||||
return buttons[button];
|
||||
}
|
||||
|
||||
function buttonIndex(button: String): Int {
|
||||
for (i in 0...buttons.length) if (buttons[i] == button) return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function down(button: String): Float {
|
||||
return buttonsDown[buttonIndex(button)];
|
||||
}
|
||||
|
||||
public function started(button: String): Bool {
|
||||
return buttonsStarted[buttonIndex(button)];
|
||||
}
|
||||
|
||||
public function released(button: String): Bool {
|
||||
return buttonsReleased[buttonIndex(button)];
|
||||
}
|
||||
|
||||
function axisListener(axis: Int, value: Float) {
|
||||
var stick = axis <= 1 ? leftStick : rightStick;
|
||||
|
||||
if (axis == 0 || axis == 2) { // X
|
||||
stick.lastX = stick.x;
|
||||
stick.x = value;
|
||||
stick.movementX = stick.x - stick.lastX;
|
||||
}
|
||||
else if (axis == 1 || axis == 3) { // Y
|
||||
stick.lastY = stick.y;
|
||||
stick.y = value;
|
||||
stick.movementY = stick.y - stick.lastY;
|
||||
}
|
||||
stick.moved = true;
|
||||
}
|
||||
|
||||
function buttonListener(button: Int, value: Float) {
|
||||
buttonsFrame.push(button);
|
||||
|
||||
buttonsDown[button] = value;
|
||||
if (value > 0) buttonsStarted[button] = true; // Will trigger L2/R2 multiple times..
|
||||
else buttonsReleased[button] = true;
|
||||
|
||||
if (value == 0.0) upVirtual(buttons[button]);
|
||||
else if (value == 1.0) downVirtual(buttons[button]);
|
||||
}
|
||||
}
|
||||
|
||||
class Sensor {
|
||||
|
||||
public var x = 0.0;
|
||||
public var y = 0.0;
|
||||
public var z = 0.0;
|
||||
|
||||
public function new() {
|
||||
kha.input.Sensor.get(kha.input.SensorType.Accelerometer).notify(listener);
|
||||
}
|
||||
|
||||
function listener(x: Float, y: Float, z: Float) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
239
leenkx/Sources/iron/system/LnxPack.hx
Normal file
239
leenkx/Sources/iron/system/LnxPack.hx
Normal file
@ -0,0 +1,239 @@
|
||||
// Msgpack parser with typed arrays
|
||||
// Based on https://github.com/aaulia/msgpack-haxe
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
package iron.system;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.BytesInput;
|
||||
import haxe.io.BytesOutput;
|
||||
import haxe.io.Eof;
|
||||
import iron.data.SceneFormat;
|
||||
#if (!macro)
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.arrays.Uint32Array;
|
||||
import kha.arrays.Int16Array;
|
||||
#end
|
||||
|
||||
class LnxPack {
|
||||
|
||||
public static inline function decode<T>(b: Bytes): T {
|
||||
var i = new BytesInput(b);
|
||||
i.bigEndian = false;
|
||||
return read(i);
|
||||
}
|
||||
|
||||
static function read(i: BytesInput, key = "", parentKey = ""): Any {
|
||||
try {
|
||||
var b = i.readByte();
|
||||
switch (b) {
|
||||
case 0xc0: return null;
|
||||
case 0xc2: return false;
|
||||
case 0xc3: return true;
|
||||
case 0xc4: return i.read(i.readByte());
|
||||
case 0xc5: return i.read(i.readUInt16());
|
||||
case 0xc6: return i.read(i.readInt32());
|
||||
case 0xca: return i.readFloat();
|
||||
case 0xcc: return i.readByte();
|
||||
case 0xcd: return i.readUInt16();
|
||||
case 0xce: return i.readInt32();
|
||||
case 0xd0: return i.readInt8();
|
||||
case 0xd1: return i.readInt16();
|
||||
case 0xd2: return i.readInt32();
|
||||
// case 0xd3: return Int64.make(i.readInt32(), i.readInt32());
|
||||
case 0xd9: return i.readString(i.readByte());
|
||||
case 0xda: return i.readString(i.readUInt16());
|
||||
case 0xdb: return i.readString(i.readInt32());
|
||||
case 0xdc: return readArray(i, i.readUInt16(), key, parentKey);
|
||||
case 0xdd: return readArray(i, i.readInt32(), key, parentKey);
|
||||
case 0xde: return readMap(i, i.readUInt16(), key, parentKey);
|
||||
case 0xdf: return readMap(i, i.readInt32(), key, parentKey);
|
||||
|
||||
default: {
|
||||
if (b < 0x80) return b; // positive fix num
|
||||
else if (b < 0x90) return readMap(i, (0xf & b), key, parentKey); // fix map
|
||||
else if (b < 0xa0) return readArray(i, (0xf & b), key, parentKey); // fix array
|
||||
else if (b < 0xc0) return i.readString(0x1f & b); // fix string
|
||||
else if (b > 0xdf) return 0xffffff00 | b; // negative fix num
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Eof) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
static function readArray(i: BytesInput, length: Int, key = "", parentKey = ""): Any {
|
||||
var b = i.readByte();
|
||||
i.position--;
|
||||
|
||||
if (b == 0xca) { // Typed float32
|
||||
i.position++;
|
||||
var a = new Float32Array(length);
|
||||
for (x in 0...length) a[x] = i.readFloat();
|
||||
return a;
|
||||
}
|
||||
else if (b == 0xd2) { // Typed int32
|
||||
i.position++;
|
||||
var a = new Uint32Array(length);
|
||||
for (x in 0...length) a[x] = i.readInt32();
|
||||
return a;
|
||||
}
|
||||
else if (b == 0xd1) { // Typed int16
|
||||
i.position++;
|
||||
var a = new Int16Array(length);
|
||||
for (x in 0...length) a[x] = i.readInt16();
|
||||
return a;
|
||||
}
|
||||
else { // Dynamic type-value
|
||||
var a: Array<Dynamic> = [];
|
||||
for (x in 0...length) a.push(read(i, key, parentKey));
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
static function readMap(i: BytesInput, length: Int, key = "", parentKey = ""): Any {
|
||||
#if js
|
||||
var out = {};
|
||||
#else
|
||||
var out = Type.createEmptyInstance(getClass(key, parentKey));
|
||||
#end
|
||||
for (n in 0...length) {
|
||||
var k = Std.string(read(i));
|
||||
var v = read(i, k, key);
|
||||
Reflect.setField(out, k, v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#if (!js)
|
||||
static function getClass(key: String, parentKey: String): Class<Dynamic> {
|
||||
return switch (key) {
|
||||
case "": TSceneFormat;
|
||||
case "mesh_datas": TMeshData;
|
||||
case "light_datas": TLightData;
|
||||
case "probe_datas": TProbeData;
|
||||
case "probe": TProbeData;
|
||||
case "camera_datas": TCameraData;
|
||||
case "material_datas": TMaterialData;
|
||||
case "particle_datas": TParticleData;
|
||||
case "shader_datas": TShaderData;
|
||||
case "speaker_datas": TSpeakerData;
|
||||
case "world_datas": TWorldData;
|
||||
case "terrain_datas": TTerrainData;
|
||||
case "tilesheet_datas": TTilesheetData;
|
||||
case "objects": TObj;
|
||||
case "children": TObj;
|
||||
case "groups": TGroup;
|
||||
case "traits": TTrait;
|
||||
case "properties": TProperty;
|
||||
case "vertex_arrays": TVertexArray;
|
||||
case "index_arrays": TIndexArray;
|
||||
case "skin": TSkin;
|
||||
case "transform": TTransform;
|
||||
case "constraints": TConstraint;
|
||||
case "contexts": parentKey == "material_datas" ? TMaterialContext : TShaderContext;
|
||||
case "override_context": TShaderOverride;
|
||||
case "bind_constants": TBindConstant;
|
||||
case "bind_textures": TBindTexture;
|
||||
case "vertex_elements": TVertexElement;
|
||||
case "constants": TShaderConstant;
|
||||
case "texture_units": TTextureUnit;
|
||||
case "actions": TTilesheetAction;
|
||||
case "particle_refs": TParticleReference;
|
||||
case "lods": TLod;
|
||||
case "anim": TAnimation;
|
||||
case "tracks": TTrack;
|
||||
case "morph_target": TMorphTarget;
|
||||
case _: TSceneFormat;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
#if (!macro && armorcore)
|
||||
|
||||
public static inline function encode(d: Dynamic): Bytes {
|
||||
var o = new BytesOutput();
|
||||
o.bigEndian = false;
|
||||
write(o, d);
|
||||
return o.getBytes();
|
||||
}
|
||||
|
||||
static function write(o: BytesOutput, d: Dynamic) {
|
||||
switch (Type.typeof(d)) {
|
||||
case TNull: o.writeByte(0xc0);
|
||||
case TBool: o.writeByte(d ? 0xc3 : 0xc2);
|
||||
case TInt: { o.writeByte(0xd2); o.writeInt32(d); }
|
||||
case TFloat: { o.writeByte(0xca); o.writeFloat(d); }
|
||||
case TClass(c): {
|
||||
switch (Type.getClassName(c)) {
|
||||
case "String": {
|
||||
o.writeByte(0xdb);
|
||||
var b = Bytes.ofString(d);
|
||||
o.writeInt32(b.length);
|
||||
o.writeFullBytes(b, 0, b.length);
|
||||
}
|
||||
case "Array", null: { // kha.arrays give null
|
||||
o.writeByte(0xdd);
|
||||
o.writeInt32(d.length);
|
||||
var isInt16 = Std.isOfType(d, #if js js.lib.Int16Array #else Int16ArrayPrivate #end);
|
||||
var isInt = Std.isOfType(d[0], Int) && !Std.isOfType(d, #if js js.lib.Float32Array #else Float32ArrayPrivate #end);
|
||||
var isFloat = Std.isOfType(d[0], Float);
|
||||
|
||||
if (isInt16) { // Int16Array
|
||||
o.writeByte(0xd1);
|
||||
for (i in 0...d.length) o.writeInt16(d[i]);
|
||||
}
|
||||
else if (isFloat && !isInt) { // Float32Array
|
||||
o.writeByte(0xca);
|
||||
for (i in 0...d.length) o.writeFloat(d[i]);
|
||||
}
|
||||
else if (isInt) { // Uint32Array
|
||||
o.writeByte(0xd2);
|
||||
for (i in 0...d.length) o.writeInt32(d[i]);
|
||||
}
|
||||
else for (i in 0...d.length) write(o, d[i]); // Array
|
||||
}
|
||||
case "haxe.io.Bytes": {
|
||||
o.writeByte(0xc6);
|
||||
o.writeInt32(d.length);
|
||||
o.writeFullBytes(d, 0, d.length);
|
||||
}
|
||||
default: writeObject(o, d);
|
||||
}
|
||||
}
|
||||
case TObject: writeObject(o, d);
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
|
||||
static function writeObject(o: BytesOutput, d: Dynamic) {
|
||||
var f = Reflect.fields(d);
|
||||
o.writeByte(0xdf);
|
||||
o.writeInt32(f.length);
|
||||
for (k in f) {
|
||||
o.writeByte(0xdb);
|
||||
var b = Bytes.ofString(k);
|
||||
o.writeInt32(b.length);
|
||||
o.writeFullBytes(b, 0, b.length);
|
||||
write(o, Reflect.field(d, k));
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
||||
261
leenkx/Sources/iron/system/Lz4.hx
Normal file
261
leenkx/Sources/iron/system/Lz4.hx
Normal file
@ -0,0 +1,261 @@
|
||||
// Based on https://github.com/gorhill/lz4-wasm
|
||||
// BSD 2-Clause License
|
||||
// Copyright (c) 2018, Raymond Hill
|
||||
// All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
package iron.system;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
#if macro
|
||||
import haxe.io.Int32Array;
|
||||
typedef Uint8Array = haxe.io.UInt8Array;
|
||||
#else
|
||||
import kha.arrays.Int32Array;
|
||||
import kha.arrays.Uint8Array;
|
||||
#end
|
||||
|
||||
class Lz4 {
|
||||
|
||||
static var hashTable: Int32Array = null;
|
||||
|
||||
static inline function encodeBound(size: Int): Int {
|
||||
return untyped size > 0x7e000000 ? 0 : size + (size / 255 | 0) + 16;
|
||||
}
|
||||
|
||||
public static function encode(b: Bytes): Bytes {
|
||||
#if armorcore
|
||||
var iBuf = new Uint8Array(cast b.getData());
|
||||
|
||||
#else
|
||||
var iBuf: Uint8Array = new Uint8Array(b.length);
|
||||
for (i in 0...b.length) iBuf[i] = b.get(i);
|
||||
#end
|
||||
|
||||
var iLen = iBuf.length;
|
||||
if (iLen >= 0x7e000000) {
|
||||
trace("LZ4 range error");
|
||||
return null;
|
||||
}
|
||||
|
||||
// "The last match must start at least 12 bytes before end of block"
|
||||
var lastMatchPos = iLen - 12;
|
||||
|
||||
// "The last 5 bytes are always literals"
|
||||
var lastLiteralPos = iLen - 5;
|
||||
|
||||
if (hashTable == null) hashTable = new Int32Array(65536);
|
||||
for (i in 0...hashTable.length) {
|
||||
hashTable[i] = -65536;
|
||||
}
|
||||
|
||||
var oLen = encodeBound(iLen);
|
||||
var oBuf = new Uint8Array(oLen);
|
||||
var iPos = 0;
|
||||
var oPos = 0;
|
||||
var anchorPos = 0;
|
||||
|
||||
// Sequence-finding loop
|
||||
while (true) {
|
||||
var refPos = 0;
|
||||
var mOffset = 0;
|
||||
var sequence = iBuf[iPos] << 8 | iBuf[iPos + 1] << 16 | iBuf[iPos + 2] << 24;
|
||||
|
||||
// Match-finding loop
|
||||
while (iPos <= lastMatchPos) {
|
||||
sequence = sequence >>> 8 | iBuf[iPos + 3] << 24;
|
||||
var hash = (sequence * 0x9e37 & 0xffff) + (sequence * 0x79b1 >>> 16) & 0xffff;
|
||||
refPos = hashTable[hash];
|
||||
hashTable[hash] = iPos;
|
||||
mOffset = iPos - refPos;
|
||||
if (mOffset < 65536 &&
|
||||
iBuf[refPos + 0] == ((sequence ) & 0xff) &&
|
||||
iBuf[refPos + 1] == ((sequence >>> 8) & 0xff) &&
|
||||
iBuf[refPos + 2] == ((sequence >>> 16) & 0xff) &&
|
||||
iBuf[refPos + 3] == ((sequence >>> 24) & 0xff)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
iPos += 1;
|
||||
}
|
||||
|
||||
// No match found
|
||||
if (iPos > lastMatchPos) break;
|
||||
|
||||
// Match found
|
||||
var lLen = iPos - anchorPos;
|
||||
var mLen = iPos;
|
||||
iPos += 4; refPos += 4;
|
||||
while (iPos < lastLiteralPos && iBuf[iPos] == iBuf[refPos]) {
|
||||
iPos += 1; refPos += 1;
|
||||
}
|
||||
mLen = iPos - mLen;
|
||||
var token = mLen < 19 ? mLen - 4 : 15;
|
||||
|
||||
// Write token, length of literals if needed
|
||||
if (lLen >= 15) {
|
||||
oBuf[oPos++] = 0xf0 | token;
|
||||
var l = lLen - 15;
|
||||
while (l >= 255) {
|
||||
oBuf[oPos++] = 255;
|
||||
l -= 255;
|
||||
}
|
||||
oBuf[oPos++] = l;
|
||||
}
|
||||
else {
|
||||
oBuf[oPos++] = (lLen << 4) | token;
|
||||
}
|
||||
|
||||
// Write literals
|
||||
while (lLen-- > 0) {
|
||||
oBuf[oPos++] = iBuf[anchorPos++];
|
||||
}
|
||||
|
||||
if (mLen == 0) break;
|
||||
|
||||
// Write offset of match
|
||||
oBuf[oPos + 0] = mOffset;
|
||||
oBuf[oPos + 1] = mOffset >>> 8;
|
||||
oPos += 2;
|
||||
|
||||
// Write length of match if needed
|
||||
if (mLen >= 19) {
|
||||
var l = mLen - 19;
|
||||
while (l >= 255) {
|
||||
oBuf[oPos++] = 255;
|
||||
l -= 255;
|
||||
}
|
||||
oBuf[oPos++] = l;
|
||||
}
|
||||
|
||||
anchorPos = iPos;
|
||||
}
|
||||
|
||||
// Last sequence is literals only
|
||||
var lLen = iLen - anchorPos;
|
||||
if (lLen >= 15) {
|
||||
oBuf[oPos++] = 0xf0;
|
||||
var l = lLen - 15;
|
||||
while (l >= 255) {
|
||||
oBuf[oPos++] = 255;
|
||||
l -= 255;
|
||||
}
|
||||
oBuf[oPos++] = l;
|
||||
}
|
||||
else {
|
||||
oBuf[oPos++] = lLen << 4;
|
||||
}
|
||||
while (lLen-- > 0) {
|
||||
oBuf[oPos++] = iBuf[anchorPos++];
|
||||
}
|
||||
|
||||
#if js
|
||||
return Bytes.ofData(untyped oBuf.buffer.slice(0, oPos));
|
||||
|
||||
#elseif hl
|
||||
return oBuf.getData().toBytes(oPos);
|
||||
|
||||
#else
|
||||
var bOut = Bytes.alloc(oPos);
|
||||
for (i in 0...oPos) {
|
||||
bOut.set(i, oBuf[i]);
|
||||
}
|
||||
return bOut;
|
||||
#end
|
||||
}
|
||||
|
||||
public static function decode(b: Bytes, oLen: Int): Bytes {
|
||||
#if armorcore
|
||||
var iBuf = new Uint8Array(cast b.getData());
|
||||
|
||||
#else
|
||||
var iBuf: Uint8Array = new Uint8Array(b.length);
|
||||
for (i in 0...b.length) iBuf[i] = b.get(i);
|
||||
#end
|
||||
|
||||
var iLen = iBuf.length;
|
||||
var oBuf = new Uint8Array(oLen);
|
||||
var iPos = 0;
|
||||
var oPos = 0;
|
||||
|
||||
while (iPos < iLen) {
|
||||
var token = iBuf[iPos++];
|
||||
|
||||
// Literals
|
||||
var clen = token >>> 4;
|
||||
|
||||
// Length of literals
|
||||
if (clen != 0) {
|
||||
if (clen == 15) {
|
||||
var l = 0;
|
||||
while (true) {
|
||||
l = iBuf[iPos++];
|
||||
if (l != 255) break;
|
||||
clen += 255;
|
||||
}
|
||||
clen += l;
|
||||
}
|
||||
|
||||
// Copy literals
|
||||
var end = iPos + clen;
|
||||
while (iPos < end) {
|
||||
oBuf[oPos++] = iBuf[iPos++];
|
||||
}
|
||||
if (iPos == iLen) break;
|
||||
}
|
||||
|
||||
// Match
|
||||
var mOffset = iBuf[iPos + 0] | (iBuf[iPos + 1] << 8);
|
||||
if (mOffset == 0 || mOffset > oPos) return null;
|
||||
iPos += 2;
|
||||
|
||||
// Length of match
|
||||
clen = (token & 0x0f) + 4;
|
||||
if (clen == 19) {
|
||||
var l = 0;
|
||||
while (true) {
|
||||
l = iBuf[iPos++];
|
||||
if (l != 255) break;
|
||||
clen += 255;
|
||||
}
|
||||
clen += l;
|
||||
}
|
||||
|
||||
// Copy match
|
||||
var mPos = oPos - mOffset;
|
||||
var end = oPos + clen;
|
||||
while (oPos < end) {
|
||||
oBuf[oPos++] = oBuf[mPos++];
|
||||
}
|
||||
}
|
||||
|
||||
#if js
|
||||
return Bytes.ofData(untyped oBuf.buffer);
|
||||
|
||||
#elseif hl
|
||||
return oBuf.getData().toBytes(oBuf.length);
|
||||
|
||||
#else
|
||||
var bOut = Bytes.alloc(oLen);
|
||||
for (i in 0...oLen) {
|
||||
bOut.set(i, oBuf[i]);
|
||||
}
|
||||
return bOut;
|
||||
#end
|
||||
}
|
||||
}
|
||||
38
leenkx/Sources/iron/system/Storage.hx
Normal file
38
leenkx/Sources/iron/system/Storage.hx
Normal file
@ -0,0 +1,38 @@
|
||||
package iron.system;
|
||||
|
||||
import kha.StorageFile;
|
||||
|
||||
class Storage {
|
||||
|
||||
static var file: StorageFile = null;
|
||||
public static var data(get, set): Dynamic;
|
||||
static var _data: Dynamic = null;
|
||||
|
||||
static function init() {
|
||||
file = kha.Storage.defaultFile();
|
||||
if (file != null) {
|
||||
_data = file.readObject();
|
||||
if (_data == null) _data = {};
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
public static function save() {
|
||||
if (file != null) {
|
||||
file.writeObject(_data);
|
||||
}
|
||||
}
|
||||
|
||||
public static function clear() {
|
||||
_data = {};
|
||||
}
|
||||
|
||||
public static function set_data(d: Dynamic): Dynamic {
|
||||
return _data = d;
|
||||
}
|
||||
|
||||
public static function get_data(): Dynamic {
|
||||
if (file == null) init();
|
||||
return _data;
|
||||
}
|
||||
}
|
||||
37
leenkx/Sources/iron/system/Time.hx
Normal file
37
leenkx/Sources/iron/system/Time.hx
Normal file
@ -0,0 +1,37 @@
|
||||
package iron.system;
|
||||
|
||||
class Time {
|
||||
|
||||
public static var step(get, never): Float;
|
||||
static function get_step(): Float {
|
||||
if (frequency == null) initFrequency();
|
||||
return 1 / frequency;
|
||||
}
|
||||
|
||||
public static var scale = 1.0;
|
||||
public static var delta(get, never): Float;
|
||||
static function get_delta(): Float {
|
||||
if (frequency == null) initFrequency();
|
||||
return (1 / frequency) * scale;
|
||||
}
|
||||
|
||||
static var last = 0.0;
|
||||
public static var realDelta = 0.0;
|
||||
public static inline function time(): Float {
|
||||
return kha.Scheduler.time();
|
||||
}
|
||||
public static inline function realTime(): Float {
|
||||
return kha.Scheduler.realTime();
|
||||
}
|
||||
|
||||
static var frequency: Null<Int> = null;
|
||||
|
||||
static function initFrequency() {
|
||||
frequency = kha.Display.primary != null ? kha.Display.primary.frequency : 60;
|
||||
}
|
||||
|
||||
public static function update() {
|
||||
realDelta = realTime() - last;
|
||||
last = realTime();
|
||||
}
|
||||
}
|
||||
290
leenkx/Sources/iron/system/Tween.hx
Normal file
290
leenkx/Sources/iron/system/Tween.hx
Normal file
@ -0,0 +1,290 @@
|
||||
package iron.system;
|
||||
|
||||
class Tween {
|
||||
|
||||
static inline var DEFAULT_OVERSHOOT: Float = 1.70158;
|
||||
|
||||
static var eases: Array<Float->Float> = [
|
||||
easeLinear,
|
||||
easeSineIn, easeSineOut, easeSineInOut,
|
||||
easeQuadIn, easeQuadOut, easeQuadInOut,
|
||||
easeCubicIn, easeCubicOut, easeCubicInOut,
|
||||
easeQuartIn, easeQuartOut, easeQuartInOut,
|
||||
easeQuintIn, easeQuintOut, easeQuintInOut,
|
||||
easeExpoIn, easeExpoOut, easeExpoInOut,
|
||||
easeCircIn, easeCircOut, easeCircInOut,
|
||||
easeBackIn, easeBackOut, easeBackInOut,
|
||||
easeBounceIn, easeBounceOut, easeBounceInOut,
|
||||
easeElasticIn, easeElasticOut, easeElasticInOut
|
||||
];
|
||||
|
||||
static var anims: Array<TAnim> = [];
|
||||
|
||||
static var registered = false;
|
||||
static inline function register() {
|
||||
registered = true;
|
||||
App.notifyOnUpdate(update);
|
||||
App.notifyOnReset(function() { App.notifyOnUpdate(update); reset(); });
|
||||
}
|
||||
|
||||
public static function to(anim: TAnim): TAnim {
|
||||
if (!registered) register();
|
||||
anim._time = 0;
|
||||
anim.isPlaying = (anim.delay != null && anim.delay > 0.0) ? false : true;
|
||||
|
||||
if (anim.ease == null) anim.ease = Ease.Linear;
|
||||
|
||||
if (anim.target != null && anim.props != null) {
|
||||
|
||||
anim._comps = []; anim._x = []; anim._y = []; anim._z = []; anim._w = []; anim._normalize = [];
|
||||
for (p in Reflect.fields(anim.props)) {
|
||||
var val: Dynamic = Reflect.getProperty(anim.target, p);
|
||||
if (Std.isOfType(val, iron.math.Vec4) || Std.isOfType(val, iron.math.Quat)) {
|
||||
anim._comps.push(4);
|
||||
anim._x.push(val.x);
|
||||
anim._y.push(val.y);
|
||||
anim._z.push(val.z);
|
||||
anim._w.push(val.w);
|
||||
anim._normalize.push(Std.isOfType(val, iron.math.Quat));
|
||||
}
|
||||
else {
|
||||
anim._comps.push(1);
|
||||
anim._x.push(val);
|
||||
anim._y.push(0);
|
||||
anim._z.push(0);
|
||||
anim._w.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anims.push(anim);
|
||||
return anim;
|
||||
}
|
||||
|
||||
public static function timer(delay: Float, done: Void->Void): TAnim {
|
||||
return to({ target: null, props: null, duration: 0, delay: delay, done: done });
|
||||
}
|
||||
|
||||
public static function stop(anim: TAnim) {
|
||||
anim.isPlaying = false;
|
||||
anims.remove(anim);
|
||||
}
|
||||
|
||||
public static function reset() {
|
||||
anims = [];
|
||||
}
|
||||
|
||||
public static function update() {
|
||||
var d = Time.delta;
|
||||
var i = anims.length;
|
||||
while (i-- > 0 && anims.length > 0) {
|
||||
var a = anims[i];
|
||||
|
||||
if (a.delay > 0) { // Delay
|
||||
a.delay -= d;
|
||||
if (a.delay > 0) continue;
|
||||
}
|
||||
|
||||
a._time += d;
|
||||
a.isPlaying = a._time < a.duration;
|
||||
|
||||
if (a.target != null) {
|
||||
|
||||
if (Std.isOfType(a.target, iron.object.Transform)) a.target.dirty = true;
|
||||
|
||||
// Way too much Reflect trickery..
|
||||
var ps = Reflect.fields(a.props);
|
||||
for (i in 0...ps.length) {
|
||||
var p = ps[i];
|
||||
var k = a._time / a.duration;
|
||||
if (k > 1) k = 1;
|
||||
|
||||
if (a._comps[i] == 1) {
|
||||
var fromVal: Float = a._x[i];
|
||||
var toVal: Float = Reflect.getProperty(a.props, p);
|
||||
var val: Float = fromVal + (toVal - fromVal) * eases[a.ease](k);
|
||||
Reflect.setProperty(a.target, p, val);
|
||||
}
|
||||
else { // _comps[i] == 4
|
||||
var obj = Reflect.getProperty(a.props, p);
|
||||
var toX: Float = Reflect.getProperty(obj, "x");
|
||||
var toY: Float = Reflect.getProperty(obj, "y");
|
||||
var toZ: Float = Reflect.getProperty(obj, "z");
|
||||
var toW: Float = Reflect.getProperty(obj, "w");
|
||||
if (a._normalize[i]) {
|
||||
var qdot = (a._x[i] * toX) + (a._y[i] * toY) + (a._z[i] * toZ) + (a._w[i] * toW);
|
||||
if (qdot < 0.0) {
|
||||
toX = -toX; toY = -toY; toZ = -toZ; toW = -toW;
|
||||
}
|
||||
}
|
||||
var x: Float = a._x[i] + (toX - a._x[i]) * eases[a.ease](k);
|
||||
var y: Float = a._y[i] + (toY - a._y[i]) * eases[a.ease](k);
|
||||
var z: Float = a._z[i] + (toZ - a._z[i]) * eases[a.ease](k);
|
||||
var w: Float = a._w[i] + (toW - a._w[i]) * eases[a.ease](k);
|
||||
if (a._normalize[i]) {
|
||||
var l = Math.sqrt(x * x + y * y + z * z + w * w);
|
||||
if (l > 0.0) {
|
||||
l = 1.0 / l;
|
||||
x *= l; y *= l; z *= l; w *= l;
|
||||
}
|
||||
}
|
||||
var t = Reflect.getProperty(a.target, p);
|
||||
Reflect.setProperty(t, "x", x);
|
||||
Reflect.setProperty(t, "y", y);
|
||||
Reflect.setProperty(t, "z", z);
|
||||
Reflect.setProperty(t, "w", w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (a.isPlaying) {
|
||||
if (a.tick != null) a.tick();
|
||||
}
|
||||
else {
|
||||
anims.splice(i, 1);
|
||||
i--;
|
||||
a.isPlaying = false;
|
||||
if (a.done != null) a.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function easeLinear(k: Float): Float { return k; }
|
||||
public static function easeSineIn(k: Float): Float { if (k == 0) { return 0; } else if (k == 1) { return 1; } else { return 1 - Math.cos(k * Math.PI / 2); } }
|
||||
public static function easeSineOut(k: Float): Float { if (k == 0) { return 0; } else if (k == 1) { return 1; } else { return Math.sin(k * (Math.PI * 0.5)); } }
|
||||
public static function easeSineInOut(k: Float): Float { if (k == 0) { return 0; } else if (k == 1) { return 1; } else { return -0.5 * (Math.cos(Math.PI * k) - 1); } }
|
||||
public static function easeQuadIn(k: Float): Float { return k * k; }
|
||||
public static function easeQuadOut(k: Float): Float { return -k * (k - 2); }
|
||||
public static function easeQuadInOut(k: Float): Float { return (k < 0.5) ? 2 * k * k : -2 * ((k -= 1) * k) + 1; }
|
||||
public static function easeCubicIn(k: Float): Float { return k * k * k; }
|
||||
public static function easeCubicOut(k: Float): Float { return (k = k - 1) * k * k + 1; }
|
||||
public static function easeCubicInOut(k: Float): Float { return ((k *= 2) < 1) ? 0.5 * k * k * k : 0.5 * ((k -= 2) * k * k + 2); }
|
||||
public static function easeQuartIn(k: Float): Float { return (k *= k) * k; }
|
||||
public static function easeQuartOut(k: Float): Float { return 1 - (k = (k = k - 1) * k) * k; }
|
||||
public static function easeQuartInOut(k: Float): Float { return ((k *= 2) < 1) ? 0.5 * (k *= k) * k : -0.5 * ((k = (k -= 2) * k) * k - 2); }
|
||||
public static function easeQuintIn(k: Float): Float { return k * (k *= k) * k; }
|
||||
public static function easeQuintOut(k: Float): Float { return (k = k - 1) * (k *= k) * k + 1; }
|
||||
public static function easeQuintInOut(k: Float): Float { return ((k *= 2) < 1) ? 0.5 * k * (k *= k) * k : 0.5 * (k -= 2) * (k *= k) * k + 1; }
|
||||
public static function easeExpoIn(k: Float): Float { return k == 0 ? 0 : Math.pow(2, 10 * (k - 1)); }
|
||||
public static function easeExpoOut(k: Float): Float { return k == 1 ? 1 : (1 - Math.pow(2, -10 * k)); }
|
||||
public static function easeExpoInOut(k: Float): Float { if (k == 0) { return 0; } if (k == 1) { return 1; } if ((k /= 1 / 2.0) < 1.0) { return 0.5 * Math.pow(2, 10 * (k - 1)); } return 0.5 * (2 - Math.pow(2, -10 * --k)); }
|
||||
public static function easeCircIn(k: Float): Float { return -(Math.sqrt(1 - k * k) - 1); }
|
||||
public static function easeCircOut(k: Float): Float { return Math.sqrt(1 - (k - 1) * (k - 1)); }
|
||||
public static function easeCircInOut(k: Float): Float { return k <= .5 ? (Math.sqrt(1 - k * k * 4) - 1) / -2 : (Math.sqrt(1 - (k * 2 - 2) * (k * 2 - 2)) + 1) / 2; }
|
||||
public static function easeBackIn(k: Float): Float { if (k == 0) { return 0; } else if (k == 1) { return 1; } else { return k * k * ((DEFAULT_OVERSHOOT + 1) * k - DEFAULT_OVERSHOOT); } }
|
||||
public static function easeBackOut(k: Float): Float { if (k == 0) { return 0; } else if (k == 1) { return 1; } else { return ((k = k - 1) * k * ((DEFAULT_OVERSHOOT + 1) * k + DEFAULT_OVERSHOOT) + 1); } }
|
||||
public static function easeBackInOut(k: Float): Float { if (k == 0) { return 0; } else if (k == 1) { return 1; } else if ((k *= 2) < 1) { return (0.5 * (k * k * (((DEFAULT_OVERSHOOT * 1.525) + 1) * k - DEFAULT_OVERSHOOT * 1.525))); } else { return (0.5 * ((k -= 2) * k * (((DEFAULT_OVERSHOOT * 1.525) + 1) * k + DEFAULT_OVERSHOOT * 1.525) + 2)); } }
|
||||
public static function easeBounceIn(k: Float): Float { return 1 - easeBounceOut(1 - k); }
|
||||
public static function easeBounceOut(k: Float): Float { return if (k < (1 / 2.75)) { 7.5625 * k * k; } else if (k < (2 / 2.75)) { 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; } else if (k < (2.5 / 2.75)) { 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; } else { 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; } }
|
||||
public static function easeBounceInOut(k: Float): Float { return (k < 0.5) ? easeBounceIn(k * 2) * 0.5 : easeBounceOut(k * 2 - 1) * 0.5 + 0.5; }
|
||||
|
||||
public static function easeElasticIn(k: Float): Float {
|
||||
var s: Null<Float> = null;
|
||||
var a = 0.1, p = 0.4;
|
||||
if (k == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (k == 1) {
|
||||
return 1;
|
||||
}
|
||||
if (a < 1) {
|
||||
a = 1;
|
||||
s = p / 4;
|
||||
}
|
||||
else {
|
||||
s = p * Math.asin(1 / a) / (2 * Math.PI);
|
||||
}
|
||||
return -(a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
|
||||
}
|
||||
|
||||
public static function easeElasticOut(k: Float): Float {
|
||||
var s: Null<Float> = null;
|
||||
var a = 0.1, p = 0.4;
|
||||
if (k == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (k == 1) {
|
||||
return 1;
|
||||
}
|
||||
if (a < 1) {
|
||||
a = 1;
|
||||
s = p / 4;
|
||||
}
|
||||
else {
|
||||
s = p * Math.asin(1 / a) / (2 * Math.PI);
|
||||
}
|
||||
return (a * Math.pow(2, -10 * k) * Math.sin((k - s) * (2 * Math.PI) / p) + 1);
|
||||
}
|
||||
|
||||
public static function easeElasticInOut(k: Float): Float {
|
||||
var s, a = 0.1, p = 0.4;
|
||||
if (k == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (k == 1) {
|
||||
return 1;
|
||||
}
|
||||
if (a != 0 || a < 1) {
|
||||
a = 1;
|
||||
s = p / 4;
|
||||
}
|
||||
else {
|
||||
s = p * Math.asin(1 / a) / (2 * Math.PI);
|
||||
}
|
||||
if ((k *= 2) < 1) return - 0.5 * (a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
|
||||
return a * Math.pow(2, -10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
typedef TAnim = {
|
||||
var target: Dynamic;
|
||||
var props: Dynamic;
|
||||
var duration: Float;
|
||||
@:optional var isPlaying: Null<Bool>;
|
||||
@:optional var done: Void->Void;
|
||||
@:optional var tick: Void->Void;
|
||||
@:optional var delay: Null<Float>;
|
||||
@:optional var ease: Null<Ease>;
|
||||
// Internal
|
||||
@:optional var _time: Null<Float>;
|
||||
@:optional var _comps: Array<Int>;
|
||||
@:optional var _x: Array<Float>;
|
||||
@:optional var _y: Array<Float>;
|
||||
@:optional var _z: Array<Float>;
|
||||
@:optional var _w: Array<Float>;
|
||||
@:optional var _normalize: Array<Bool>;
|
||||
}
|
||||
|
||||
@:enum abstract Ease(Int) from Int to Int {
|
||||
var Linear = 0;
|
||||
var SineIn = 1;
|
||||
var SineOut = 2;
|
||||
var SineInOut = 3;
|
||||
var QuadIn = 4;
|
||||
var QuadOut = 5;
|
||||
var QuadInOut = 6;
|
||||
var CubicIn = 7;
|
||||
var CubicOut = 8;
|
||||
var CubicInOut = 9;
|
||||
var QuartIn = 10;
|
||||
var QuartOut = 11;
|
||||
var QuartInOut = 12;
|
||||
var QuintIn = 13;
|
||||
var QuintOut = 14;
|
||||
var QuintInOut = 15;
|
||||
var ExpoIn = 16;
|
||||
var ExpoOut = 17;
|
||||
var ExpoInOut = 18;
|
||||
var CircIn = 19;
|
||||
var CircOut = 20;
|
||||
var CircInOut = 21;
|
||||
var BackIn = 22;
|
||||
var BackOut = 23;
|
||||
var BackInOut = 24;
|
||||
var BounceIn = 25;
|
||||
var BounceOut = 26;
|
||||
var BounceInOut = 27;
|
||||
var ElasticIn = 28;
|
||||
var ElasticOut = 29;
|
||||
var ElasticInOut = 30;
|
||||
}
|
||||
Reference in New Issue
Block a user