Update Files

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

View File

@ -0,0 +1,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);
});
}
}
}