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,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