Update Files

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

208
leenkx/Sources/iron/App.hx Normal file
View 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);
}
}

View 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
}

View 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);
}
}

View 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);
}
}

View 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

View 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);
});
}
}

File diff suppressed because one or more lines are too long

View 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
}
}

View 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;
}

View 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);
});
}
}

View 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]);
}
}

View 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

View 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;
}
}

View 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);
});
}
}

View 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
}

View 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
}

View 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

View 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));
}
}

View 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

View 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

View 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);
});
}
}
}

View 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; }
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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 + ")";
}
}

View 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 + ")";
}
}

View 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 + ")";
}
}

View 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);
}
}

File diff suppressed because it is too large Load Diff

View 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;
}
}

View 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() {};
}

View 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;
}
}
}
}

View 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
}

View 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);
}
}

View 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));
}
}
}
}

View 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

View 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;
}
}

View 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;
}
}
}
}

View 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

View 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
}

View 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
}

View 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);
}
}

View 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;
}
}

File diff suppressed because it is too large Load Diff

View 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
}

View 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;
}
}

View 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
}

View 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
}
}

View 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;
}
}

View 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();
}
}

View 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;
}