forked from LeenkxTeam/LNXSDK
Update Files
This commit is contained in:
447
leenkx/Sources/iron/object/Animation.hx
Normal file
447
leenkx/Sources/iron/object/Animation.hx
Normal file
@ -0,0 +1,447 @@
|
||||
package iron.object;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import iron.math.Vec3;
|
||||
import iron.math.Vec2;
|
||||
import kha.FastFloat;
|
||||
import kha.arrays.Uint32Array;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class Animation {
|
||||
|
||||
public var isSkinned: Bool;
|
||||
public var isSampled: Bool;
|
||||
public var action = "";
|
||||
#if lnx_skin
|
||||
public var armature: iron.data.Armature; // Bone
|
||||
#end
|
||||
|
||||
// Helper variables.
|
||||
static var m1 = Mat4.identity();
|
||||
static var m2 = Mat4.identity();
|
||||
static var vpos = new Vec4();
|
||||
static var vpos2 = new Vec4();
|
||||
static var vscl = new Vec4();
|
||||
static var vscl2 = new Vec4();
|
||||
static var q1 = new Quat();
|
||||
static var q2 = new Quat();
|
||||
static var q3 = new Quat();
|
||||
static var vp = new Vec4();
|
||||
static var vs = new Vec4();
|
||||
|
||||
public var time: FastFloat = 0.0;
|
||||
public var speed: FastFloat = 1.0;
|
||||
public var loop = true;
|
||||
public var frameIndex = 0;
|
||||
public var onComplete: Void->Void = null;
|
||||
public var paused = false;
|
||||
var frameTime: FastFloat = 1 / 60;
|
||||
|
||||
var blendTime: FastFloat = 0.0;
|
||||
var blendCurrent: FastFloat = 0.0;
|
||||
var blendFactor: FastFloat = 0.0;
|
||||
|
||||
var lastFrameIndex = -1;
|
||||
var markerEvents: Map<ActionSampler, Map<String, Array<Void->Void>>> = null;
|
||||
|
||||
public var activeActions: Map<String, ActionSampler> = null;
|
||||
|
||||
function new() {
|
||||
Scene.active.animations.push(this);
|
||||
if (Scene.active.raw.frame_time != null) {
|
||||
frameTime = Scene.active.raw.frame_time;
|
||||
}
|
||||
play();
|
||||
}
|
||||
|
||||
public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
||||
if (blendTime > 0) {
|
||||
this.blendTime = blendTime;
|
||||
this.blendCurrent = 0.0;
|
||||
frameIndex = 0;
|
||||
time = 0.0;
|
||||
}
|
||||
else frameIndex = -1;
|
||||
this.action = action;
|
||||
this.onComplete = onComplete;
|
||||
this.speed = speed;
|
||||
this.loop = loop;
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
paused = true;
|
||||
}
|
||||
|
||||
public function resume() {
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public function remove() {
|
||||
Scene.active.animations.remove(this);
|
||||
}
|
||||
|
||||
public function updateActionTrack(sampler: ActionSampler){
|
||||
return;
|
||||
}
|
||||
|
||||
public function update(delta: FastFloat) {
|
||||
if(activeActions == null) return;
|
||||
|
||||
for(sampler in activeActions){
|
||||
if (sampler.paused || sampler.speed == 0.0) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
sampler.timeOld = sampler.time;
|
||||
sampler.offsetOld = sampler.offset;
|
||||
sampler.setTimeOnly(sampler.time + delta * sampler.speed);
|
||||
updateActionTrack(sampler);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function registerAction(actionID: String, sampler: ActionSampler){
|
||||
if (activeActions == null) activeActions = new Map();
|
||||
activeActions.set(actionID, sampler);
|
||||
}
|
||||
|
||||
public function deRegisterAction(actionID: String) {
|
||||
if (activeActions == null) return;
|
||||
if(activeActions.exists(actionID)) activeActions.remove(actionID);
|
||||
|
||||
}
|
||||
|
||||
inline function isTrackEnd(track: TTrack, frameIndex: Int, speed: FastFloat): Bool {
|
||||
return speed > 0 ?
|
||||
frameIndex >= track.frames.length - 1 :
|
||||
frameIndex <= 0;
|
||||
}
|
||||
|
||||
inline function checkFrameIndex(frameValues: Uint32Array, time: FastFloat, frameIndex: Int, speed: FastFloat): Bool {
|
||||
return speed > 0 ?
|
||||
((frameIndex + 1) < frameValues.length && time > frameValues[frameIndex + 1] * frameTime) :
|
||||
((frameIndex - 1) > -1 && time < frameValues[frameIndex - 1] * frameTime);
|
||||
}
|
||||
|
||||
function rewind(track: TTrack) {
|
||||
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
||||
time = track.frames[frameIndex] * frameTime;
|
||||
}
|
||||
|
||||
function updateTrack(anim: TAnimation, sampler: ActionSampler) {
|
||||
|
||||
var time = sampler.time;
|
||||
var frameIndex = sampler.offset;
|
||||
var speed = sampler.speed;
|
||||
sampler.cacheSet = false;
|
||||
sampler.trackEnd = false;
|
||||
|
||||
var track = anim.tracks[0];
|
||||
|
||||
if (frameIndex == -1) {
|
||||
sampler.timeOld = sampler.time;
|
||||
sampler.offsetOld = sampler.offset;
|
||||
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
||||
time = track.frames[frameIndex] * frameTime;
|
||||
}
|
||||
|
||||
// Move keyframe
|
||||
var sign = speed > 0 ? 1 : -1;
|
||||
while (checkFrameIndex(track.frames, time, frameIndex, speed)) frameIndex += sign;
|
||||
|
||||
// Marker events
|
||||
if (markerEvents != null && anim.marker_names != null && frameIndex != lastFrameIndex) {
|
||||
if(markerEvents.get(sampler) != null){
|
||||
for (i in 0...anim.marker_frames.length) {
|
||||
if (frameIndex == anim.marker_frames[i]) {
|
||||
var marketAct = markerEvents.get(sampler);
|
||||
var ar = marketAct.get(anim.marker_names[i]);
|
||||
if (ar != null) for (f in ar) f();
|
||||
}
|
||||
}
|
||||
lastFrameIndex = frameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// End of track
|
||||
if (isTrackEnd(track, frameIndex, speed)) {
|
||||
if (sampler.loop) {
|
||||
sampler.offsetOld = frameIndex;
|
||||
frameIndex = speed > 0 ? 0 : track.frames.length - 1;
|
||||
time = track.frames[frameIndex] * frameTime;
|
||||
}
|
||||
else {
|
||||
frameIndex -= sign;
|
||||
sampler.paused = true;
|
||||
}
|
||||
if (sampler.onComplete != null) for(func in sampler.onComplete){ func();};
|
||||
sampler.trackEnd = true;
|
||||
}
|
||||
|
||||
sampler.setFrameOffsetOnly(frameIndex);
|
||||
sampler.speed = speed;
|
||||
sampler.setTimeOnly(time);
|
||||
|
||||
}
|
||||
|
||||
public function notifyOnMarker(sampler: ActionSampler, name: String, onMarker: Void->Void) {
|
||||
if (markerEvents == null) markerEvents = new Map();
|
||||
|
||||
var markerAct = markerEvents.get(sampler);
|
||||
if(markerAct == null){
|
||||
markerAct = new Map();
|
||||
markerEvents.set(sampler, markerAct);
|
||||
}
|
||||
|
||||
var ar = markerAct.get(name);
|
||||
if (ar == null) {
|
||||
ar = [];
|
||||
markerAct.set(name, ar);
|
||||
}
|
||||
ar.push(onMarker);
|
||||
}
|
||||
|
||||
public function removeMarker(sampler: ActionSampler, name: String, onMarker: Void->Void) {
|
||||
var markerAct = markerEvents.get(sampler);
|
||||
if(markerAct == null) return;
|
||||
|
||||
markerAct.get(name).remove(onMarker);
|
||||
}
|
||||
|
||||
public function currentFrame(): Int {
|
||||
return Std.int(time / frameTime);
|
||||
}
|
||||
|
||||
public function getTotalFrames(sampler: ActionSampler): Int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
public static var animationTime = 0.0;
|
||||
static var startTime = 0.0;
|
||||
|
||||
static function beginProfile() {
|
||||
startTime = kha.Scheduler.realTime();
|
||||
}
|
||||
static function endProfile() {
|
||||
animationTime += kha.Scheduler.realTime() - startTime;
|
||||
}
|
||||
public static function endFrame() {
|
||||
animationTime = 0;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Sampler State.
|
||||
*/
|
||||
class ActionSampler {
|
||||
|
||||
/**
|
||||
* Name of the action.
|
||||
*/
|
||||
public var action(default, null): String;
|
||||
/**
|
||||
* Current time of the sampler.
|
||||
*/
|
||||
public var time(default, null): FastFloat = 0.0;
|
||||
/**
|
||||
* Current frame of the sampler.
|
||||
*/
|
||||
public var offset(default, null): Int = 0;
|
||||
/**
|
||||
* Total frames in the action.
|
||||
*/
|
||||
public var totalFrames: Null<Int> = null;
|
||||
/**
|
||||
* Speed of action sampling.
|
||||
*/
|
||||
public var speed: FastFloat;
|
||||
/**
|
||||
* Loop action.
|
||||
*/
|
||||
public var loop: Bool;
|
||||
/**
|
||||
* Sampler paused.
|
||||
*/
|
||||
public var paused: Bool = false;
|
||||
/**
|
||||
* Callback functions to call after action ends.
|
||||
*/
|
||||
public var onComplete: Array<Void -> Void>;
|
||||
/**
|
||||
* Action track ended.
|
||||
*/
|
||||
public var trackEnd: Bool = false;
|
||||
public var timeOld: FastFloat = 0.0;
|
||||
public var offsetOld: Int = 0;
|
||||
/**
|
||||
* Cache action data objects. May be Bones or Objects.
|
||||
*/
|
||||
var actionData: Array<TObj> = null;
|
||||
/**
|
||||
* Action data has been cached.
|
||||
*/
|
||||
public var actionDataInit(default, null): Bool = false;
|
||||
/**
|
||||
* Positional Root Motion for this action.
|
||||
*/
|
||||
public var rootMotionPos: Bool = false;
|
||||
/**
|
||||
* Rotational Root Motion for this action.
|
||||
*/
|
||||
public var rootMotionRot: Bool = false;
|
||||
/**
|
||||
* Action matrix from previous sample. Mainly used for root motion.
|
||||
*/
|
||||
var actionCache: Mat4 = Mat4.identity();
|
||||
/**
|
||||
* `actionCache` set this frame.
|
||||
*/
|
||||
public var cacheSet: Bool = false;
|
||||
/**
|
||||
* `actionCache` initialized. Set to false to force reset cache.
|
||||
*/
|
||||
public var cacheInit(default, null): Bool = false;
|
||||
|
||||
/**
|
||||
* Create a new action sampler.
|
||||
* @param action Name of the action.
|
||||
* @param speed Speed of sampler.
|
||||
* @param loop Loop after action ends.
|
||||
* @param startPaused Do not start sample on init.
|
||||
* @param onComplete Callback functions after action completes.
|
||||
*/
|
||||
public inline function new(action: String, speed: FastFloat = 1.0, loop: Bool = true, startPaused: Bool = false, onComplete: Array<Void -> Void> = null) {
|
||||
this.action = action;
|
||||
this.speed = speed;
|
||||
this.loop = loop;
|
||||
this.onComplete = onComplete;
|
||||
this.paused = startPaused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current frame of the sampler. Time is calculated.
|
||||
* @param frameOffset Frame.
|
||||
*/
|
||||
public inline function setFrameOffset(frameOffset: Int){
|
||||
this.offset = frameOffset;
|
||||
this.time = Scene.active.raw.frame_time * offset;
|
||||
cacheInit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current time of the sampler. Frame is calculated.
|
||||
* @param timeOffset Time.
|
||||
*/
|
||||
public inline function setTimeOffset(timeOffset: FastFloat){
|
||||
this.time = timeOffset;
|
||||
var ftime: FastFloat = Scene.active.raw.frame_time;
|
||||
this.offset = Std.int(time / ftime);
|
||||
cacheInit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart action.
|
||||
*/
|
||||
public inline function restartAction() {
|
||||
this.setFrameOffset(0);
|
||||
paused = false;
|
||||
cacheInit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback function when action completes.
|
||||
* @param onComplete Callback
|
||||
*/
|
||||
public function notifyOnComplete(onComplete: Void -> Void) {
|
||||
if(this.onComplete == null) this.onComplete = [];
|
||||
this.onComplete.push(onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove callback function
|
||||
* @param onComplete Callback
|
||||
*/
|
||||
public function removeOnComplete(onComplete: Void -> Void) {
|
||||
this.onComplete.remove(onComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time offset only. Frame will not be set.
|
||||
* @param time Time.
|
||||
*/
|
||||
public inline function setTimeOnly(time: FastFloat) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set frame offset only. Time will not be set.
|
||||
* @param frame Frame
|
||||
*/
|
||||
public inline function setFrameOffsetOnly(frame: Int) {
|
||||
this.offset = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw bones data for bone animation.
|
||||
* @return Null<Array<TObj>> Raw bone action data.
|
||||
*/
|
||||
public inline function getBoneAction(): Null<Array<TObj>> {
|
||||
return actionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw object data for object animation.
|
||||
* @return Null<TObj> Raw object action data.
|
||||
*/
|
||||
public inline function getObjectAction(): Null<TObj> {
|
||||
if(actionData != null) return actionData[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache raw bones data for bone animation.
|
||||
* @param actionData Raw bone data.
|
||||
*/
|
||||
public inline function setBoneAction(actionData: Array<TObj>) {
|
||||
this.actionData = actionData;
|
||||
this.totalFrames = actionData[0].anim.tracks[0].frames.length;
|
||||
if(actionData[0].anim.root_motion_pos) this.rootMotionPos = true;
|
||||
if(actionData[0].anim.root_motion_rot) this.rootMotionRot = true;
|
||||
actionDataInit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache raw object data for object animation.
|
||||
* @param actionData Raw object data.
|
||||
*/
|
||||
public inline function setObjectAction(actionData: TObj) {
|
||||
this.actionData = [actionData];
|
||||
this.totalFrames = actionData.anim.tracks[0].frames.length;
|
||||
actionDataInit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary cache of action matrix from previous frame.
|
||||
* @param m Matrix to cache.
|
||||
*/
|
||||
public inline function setActionCache(m: Mat4) {
|
||||
if(! cacheSet) actionCache.setFrom(m);
|
||||
cacheSet = true;
|
||||
cacheInit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy cahced action matrix and to the matrix.
|
||||
* @param m Matrix to copy the cache to.
|
||||
*/
|
||||
public inline function getActionCache(m: Mat4) {
|
||||
m.setFrom(actionCache);
|
||||
}
|
||||
}
|
1168
leenkx/Sources/iron/object/BoneAnimation.hx
Normal file
1168
leenkx/Sources/iron/object/BoneAnimation.hx
Normal file
File diff suppressed because it is too large
Load Diff
233
leenkx/Sources/iron/object/CameraObject.hx
Normal file
233
leenkx/Sources/iron/object/CameraObject.hx
Normal file
@ -0,0 +1,233 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.graphics4.Graphics;
|
||||
import kha.graphics4.CubeMap;
|
||||
import iron.Scene;
|
||||
import iron.RenderPath;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.CameraData;
|
||||
|
||||
class CameraObject extends Object {
|
||||
|
||||
public var data: CameraData;
|
||||
public var P: Mat4;
|
||||
#if lnx_taa
|
||||
public var noJitterP = Mat4.identity();
|
||||
var frame = 0;
|
||||
#end
|
||||
public var V: Mat4;
|
||||
public var prevV: Mat4 = null;
|
||||
public var VP: Mat4;
|
||||
public var frustumPlanes: Array<FrustumPlane> = null;
|
||||
public var renderTarget: kha.Image = null; // Render camera view to texture
|
||||
public var renderTargetCube: CubeMap = null;
|
||||
public var currentFace = 0;
|
||||
|
||||
static var temp = new Vec4();
|
||||
static var q = new Quat();
|
||||
static var sphereCenter = new Vec4();
|
||||
static var vcenter = new Vec4();
|
||||
static var vup = new Vec4();
|
||||
|
||||
public function new(data: CameraData) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
|
||||
buildProjection();
|
||||
|
||||
V = Mat4.identity();
|
||||
VP = Mat4.identity();
|
||||
|
||||
if (data.raw.frustum_culling) {
|
||||
frustumPlanes = [];
|
||||
for (i in 0...6) frustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
|
||||
Scene.active.cameras.push(this);
|
||||
}
|
||||
|
||||
public function buildProjection(screenAspect: Null<Float> = null) {
|
||||
if (data.raw.ortho != null) {
|
||||
P = Mat4.ortho(data.raw.ortho[0], data.raw.ortho[1], data.raw.ortho[2], data.raw.ortho[3], data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
else {
|
||||
if (screenAspect == null) screenAspect = iron.App.w() / iron.App.h();
|
||||
var aspect = data.raw.aspect != null ? data.raw.aspect : screenAspect;
|
||||
P = Mat4.persp(data.raw.fov, aspect, data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
#if lnx_taa
|
||||
noJitterP.setFrom(P);
|
||||
#end
|
||||
}
|
||||
|
||||
override public function remove() {
|
||||
Scene.active.cameras.remove(this);
|
||||
// if (renderTarget != null) renderTarget.unload();
|
||||
// if (renderTargetCube != null) renderTargetCube.unload();
|
||||
super.remove();
|
||||
}
|
||||
|
||||
public function renderFrame(g: Graphics) {
|
||||
#if lnx_taa
|
||||
projectionJitter();
|
||||
#end
|
||||
|
||||
buildMatrix();
|
||||
|
||||
RenderPath.active.renderFrame(g);
|
||||
|
||||
prevV.setFrom(V);
|
||||
}
|
||||
|
||||
#if lnx_taa
|
||||
function projectionJitter() {
|
||||
var w = RenderPath.active.currentW;
|
||||
var h = RenderPath.active.currentH;
|
||||
P.setFrom(noJitterP);
|
||||
var x = 0.0;
|
||||
var y = 0.0;
|
||||
if (frame % 2 == 0) {
|
||||
x = 0.25;
|
||||
y = 0.25;
|
||||
}
|
||||
else {
|
||||
x = -0.25;
|
||||
y = -0.25;
|
||||
}
|
||||
P._20 += x / w;
|
||||
P._21 += y / h;
|
||||
frame++;
|
||||
}
|
||||
#end
|
||||
|
||||
public function buildMatrix() {
|
||||
transform.buildMatrix();
|
||||
|
||||
// Prevent camera matrix scaling
|
||||
// TODO: discards position affected by scaled camera parent
|
||||
var sc = transform.world.getScale();
|
||||
if (sc.x != 1.0 || sc.y != 1.0 || sc.z != 1.0) {
|
||||
temp.set(1.0 / sc.x, 1.0 / sc.y, 1.0 / sc.z);
|
||||
transform.world.scale(temp);
|
||||
}
|
||||
|
||||
V.getInverse(transform.world);
|
||||
VP.multmats(P, V);
|
||||
|
||||
if (data.raw.frustum_culling) {
|
||||
buildViewFrustum(VP, frustumPlanes);
|
||||
}
|
||||
|
||||
// First time setting up previous V, prevents first frame flicker
|
||||
if (prevV == null) {
|
||||
prevV = Mat4.identity();
|
||||
prevV.setFrom(V);
|
||||
}
|
||||
}
|
||||
|
||||
public static function buildViewFrustum(VP: Mat4, frustumPlanes: Array<FrustumPlane>) {
|
||||
// Left plane
|
||||
frustumPlanes[0].setComponents(VP._03 + VP._00, VP._13 + VP._10, VP._23 + VP._20, VP._33 + VP._30);
|
||||
// Right plane
|
||||
frustumPlanes[1].setComponents(VP._03 - VP._00, VP._13 - VP._10, VP._23 - VP._20, VP._33 - VP._30);
|
||||
// Top plane
|
||||
frustumPlanes[2].setComponents(VP._03 - VP._01, VP._13 - VP._11, VP._23 - VP._21, VP._33 - VP._31);
|
||||
// Bottom plane
|
||||
frustumPlanes[3].setComponents(VP._03 + VP._01, VP._13 + VP._11, VP._23 + VP._21, VP._33 + VP._31);
|
||||
// Near plane
|
||||
frustumPlanes[4].setComponents(VP._02, VP._12, VP._22, VP._32);
|
||||
// Far plane
|
||||
frustumPlanes[5].setComponents(VP._03 - VP._02, VP._13 - VP._12, VP._23 - VP._22, VP._33 - VP._32);
|
||||
// Normalize planes
|
||||
for (plane in frustumPlanes) plane.normalize();
|
||||
}
|
||||
|
||||
public static function sphereInFrustum(frustumPlanes: Array<FrustumPlane>, t: Transform, radiusScale = 1.0, offsetX = 0.0, offsetY = 0.0, offsetZ = 0.0): Bool {
|
||||
// Use scale when radius is changing
|
||||
var radius = t.radius * radiusScale;
|
||||
for (plane in frustumPlanes) {
|
||||
sphereCenter.set(t.worldx() + offsetX, t.worldy() + offsetY, t.worldz() + offsetZ);
|
||||
// Outside the frustum
|
||||
if (plane.distanceToSphere(sphereCenter, radius) + radius * 2 < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function setCubeFace(m: Mat4, eye: Vec4, face: Int, flip = false) {
|
||||
// Set matrix to match cubemap face
|
||||
vcenter.setFrom(eye);
|
||||
var f = flip ? -1.0 : 1.0;
|
||||
switch (face) {
|
||||
case 0: // x+
|
||||
vcenter.addf(1.0 * f, 0.0, 0.0);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
case 1: // x-
|
||||
vcenter.addf(-1.0 * f, 0.0, 0.0);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
case 2: // y+
|
||||
vcenter.addf(0.0, 1.0 * f, 0.0);
|
||||
vup.set(0.0, 0.0, 1.0 * f);
|
||||
case 3: // y-
|
||||
vcenter.addf(0.0, -1.0 * f, 0.0);
|
||||
vup.set(0.0, 0.0, -1.0 * f);
|
||||
case 4: // z+
|
||||
vcenter.addf(0.0, 0.0, 1.0 * f);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
case 5: // z-
|
||||
vcenter.addf(0.0, 0.0, -1.0 * f);
|
||||
vup.set(0.0, -1.0 * f, 0.0);
|
||||
}
|
||||
m.setLookAt(eye, vcenter, vup);
|
||||
}
|
||||
|
||||
public inline function right(): Vec4 {
|
||||
return new Vec4(transform.local._00, transform.local._01, transform.local._02);
|
||||
}
|
||||
|
||||
public inline function up(): Vec4 {
|
||||
return new Vec4(transform.local._10, transform.local._11, transform.local._12);
|
||||
}
|
||||
|
||||
public inline function look(): Vec4 {
|
||||
return new Vec4(-transform.local._20, -transform.local._21, -transform.local._22);
|
||||
}
|
||||
|
||||
public inline function rightWorld(): Vec4 {
|
||||
return new Vec4(transform.world._00, transform.world._01, transform.world._02);
|
||||
}
|
||||
|
||||
public inline function upWorld(): Vec4 {
|
||||
return new Vec4(transform.world._10, transform.world._11, transform.world._12);
|
||||
}
|
||||
|
||||
public inline function lookWorld(): Vec4 {
|
||||
return new Vec4(-transform.world._20, -transform.world._21, -transform.world._22);
|
||||
}
|
||||
}
|
||||
|
||||
class FrustumPlane {
|
||||
public var normal = new Vec4(1.0, 0.0, 0.0);
|
||||
public var constant = 0.0;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function normalize() {
|
||||
var inverseNormalLength = 1.0 / normal.length();
|
||||
normal.mult(inverseNormalLength);
|
||||
constant *= inverseNormalLength;
|
||||
}
|
||||
|
||||
public function distanceToSphere(sphereCenter: Vec4, sphereRadius: Float): Float {
|
||||
return (normal.dot(sphereCenter) + constant) - sphereRadius;
|
||||
}
|
||||
|
||||
public inline function setComponents(x: Float, y: Float, z: Float, w: Float) {
|
||||
normal.set(x, y, z);
|
||||
constant = w;
|
||||
}
|
||||
}
|
10
leenkx/Sources/iron/object/Clipmap.hx
Normal file
10
leenkx/Sources/iron/object/Clipmap.hx
Normal file
@ -0,0 +1,10 @@
|
||||
package iron.object;
|
||||
|
||||
class Clipmap {
|
||||
public var voxelSize = 0.125;
|
||||
public var extents:iron.math.Vec3;
|
||||
public var center:iron.math.Vec3;
|
||||
public var offset_prev:iron.math.Vec3;
|
||||
|
||||
public function new() {};
|
||||
}
|
31
leenkx/Sources/iron/object/Constraint.hx
Normal file
31
leenkx/Sources/iron/object/Constraint.hx
Normal file
@ -0,0 +1,31 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class Constraint {
|
||||
var raw: TConstraint;
|
||||
var target: Transform = null;
|
||||
|
||||
public function new(constr: TConstraint) {
|
||||
raw = constr;
|
||||
}
|
||||
|
||||
public function apply(transform: Transform) {
|
||||
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform;
|
||||
|
||||
if (raw.type == "COPY_LOCATION") {
|
||||
if (raw.use_x) {
|
||||
transform.world._30 = target.loc.x;
|
||||
if (raw.use_offset) transform.world._30 += transform.loc.x;
|
||||
}
|
||||
if (raw.use_y) {
|
||||
transform.world._31 = target.loc.y;
|
||||
if (raw.use_offset) transform.world._31 += transform.loc.y;
|
||||
}
|
||||
if (raw.use_z) {
|
||||
transform.world._32 = target.loc.z;
|
||||
if (raw.use_offset) transform.world._32 += transform.loc.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
leenkx/Sources/iron/object/DecalObject.hx
Normal file
57
leenkx/Sources/iron/object/DecalObject.hx
Normal file
@ -0,0 +1,57 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.graphics4.Graphics;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.ConstData;
|
||||
import iron.object.Uniforms;
|
||||
|
||||
class DecalObject extends Object {
|
||||
|
||||
#if rp_decals
|
||||
|
||||
public var material: MaterialData;
|
||||
|
||||
public function new(material: MaterialData) {
|
||||
super();
|
||||
this.material = material;
|
||||
Scene.active.decals.push(this);
|
||||
}
|
||||
|
||||
public override function remove() {
|
||||
if (Scene.active != null) Scene.active.decals.remove(this);
|
||||
super.remove();
|
||||
}
|
||||
|
||||
// Called before rendering decal in render path
|
||||
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||
|
||||
// Check context skip
|
||||
if (material.raw.skip_context != null &&
|
||||
material.raw.skip_context == context) {
|
||||
return;
|
||||
}
|
||||
|
||||
transform.update();
|
||||
|
||||
var materialContext: MaterialContext = null;
|
||||
for (i in 0...material.raw.contexts.length) {
|
||||
if (material.raw.contexts[i].name == context) {
|
||||
materialContext = material.contexts[i]; // Single material decals
|
||||
break;
|
||||
}
|
||||
}
|
||||
var shaderContext = material.shader.getContext(context);
|
||||
|
||||
g.setPipeline(shaderContext.pipeState);
|
||||
|
||||
Uniforms.setContextConstants(g, shaderContext, bindParams);
|
||||
Uniforms.setObjectConstants(g, shaderContext, this);
|
||||
Uniforms.setMaterialConstants(g, shaderContext, materialContext);
|
||||
|
||||
g.setVertexBuffer(ConstData.boxVB);
|
||||
g.setIndexBuffer(ConstData.boxIB);
|
||||
g.drawIndexedVertices();
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
677
leenkx/Sources/iron/object/LightObject.hx
Normal file
677
leenkx/Sources/iron/object/LightObject.hx
Normal file
@ -0,0 +1,677 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.graphics4.TextureFormat;
|
||||
import kha.graphics4.Usage;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Vec4;
|
||||
import iron.data.LightData;
|
||||
import iron.object.CameraObject;
|
||||
|
||||
class LightObject extends Object {
|
||||
|
||||
public var data: LightData;
|
||||
|
||||
#if rp_shadowmap
|
||||
#if lnx_shadowmap_atlas
|
||||
public var tileNotifyOnRemove: Void -> Void;
|
||||
public var lightInAtlas = false;
|
||||
public var lightInAtlasTransparent = false;
|
||||
public var culledLight = false;
|
||||
public static var pointLightsData: kha.arrays.Float32Array = null;
|
||||
public var shadowMapScale = 1.0; // When in forward if this defaults to 0.0, the atlas are not drawn before being bound.
|
||||
// Data used in uniforms
|
||||
public var tileOffsetX: Array<Float> = [0.0];
|
||||
public var tileOffsetY: Array<Float> = [0.0];
|
||||
public var tileScale: Array<Float> = [1.0];
|
||||
#end
|
||||
// Cascades
|
||||
public static var cascadeCount = 1;
|
||||
public static var cascadeSplitFactor = 0.8;
|
||||
public static var cascadeBounds = 1.0;
|
||||
#if lnx_csm
|
||||
var cascadeData: Float32Array = null;
|
||||
var cascadeVP: Array<Mat4>;
|
||||
var camSlicedP: Array<Mat4> = null;
|
||||
var cascadeSplit: Array<kha.FastFloat>;
|
||||
var bias = Mat4.identity();
|
||||
#else
|
||||
var camSlicedP: Mat4 = null;
|
||||
#end
|
||||
#end // rp_shadowmap
|
||||
|
||||
#if (lnx_csm || lnx_clusters)
|
||||
static var helpMat = Mat4.identity();
|
||||
#end
|
||||
|
||||
// Clusters
|
||||
#if lnx_clusters
|
||||
static var slicesX = 16;
|
||||
static var slicesY = 16;
|
||||
static var slicesZ = 16;
|
||||
static inline var maxLights = getMaxLights();
|
||||
public static inline var maxLightsCluster = getMaxLightsCluster(); // Mirror shader constant
|
||||
static inline var clusterNear = 3.0;
|
||||
public static var lightsArray: Float32Array = null;
|
||||
#if lnx_spot
|
||||
public static var lightsArraySpot: Float32Array = null;
|
||||
#end
|
||||
public static var clustersData: kha.Image = null;
|
||||
static var lpos = new Vec4();
|
||||
public static var LWVPMatrixArray: Float32Array = null;
|
||||
#end // lnx_clusters
|
||||
|
||||
public var V: Mat4 = Mat4.identity();
|
||||
public var P: Mat4 = null;
|
||||
public var VP: Mat4 = Mat4.identity();
|
||||
|
||||
public var frustumPlanes: Array<FrustumPlane> = null;
|
||||
static var m = Mat4.identity();
|
||||
static var eye = new Vec4();
|
||||
#if rp_shadowmap
|
||||
static var corners: Array<Vec4> = null;
|
||||
#end
|
||||
|
||||
public function new(data: LightData) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
|
||||
var type = data.raw.type;
|
||||
var fov = data.raw.fov;
|
||||
|
||||
if (type == "sun") {
|
||||
#if rp_shadowmap
|
||||
if (corners == null) {
|
||||
corners = [];
|
||||
for (i in 0...8) corners.push(new Vec4());
|
||||
}
|
||||
P = Mat4.identity();
|
||||
#else
|
||||
P = Mat4.ortho(-1, 1, -1, 1, data.raw.near_plane, data.raw.far_plane);
|
||||
#end
|
||||
#if lnx_shadowmap_atlas
|
||||
this.shadowMapScale = 1.0;
|
||||
#end
|
||||
}
|
||||
else if (type == "point" || type == "area") {
|
||||
P = Mat4.persp(fov, 1, data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
else if (type == "spot") {
|
||||
P = Mat4.persp(fov, 1, data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
|
||||
Scene.active.lights.push(this);
|
||||
}
|
||||
|
||||
override public function remove() {
|
||||
if (Scene.active != null) Scene.active.lights.remove(this);
|
||||
final rp = RenderPath.active;
|
||||
if (rp.light == this) { rp.light = null; }
|
||||
if (rp.point == this) { rp.point = null; }
|
||||
else if (rp.sun == this) { rp.sun = null; }
|
||||
#if rp_shadowmap
|
||||
#if lnx_shadowmap_atlas
|
||||
if (tileNotifyOnRemove != null) {
|
||||
tileNotifyOnRemove();
|
||||
tileNotifyOnRemove = null;
|
||||
}
|
||||
#end
|
||||
#end
|
||||
super.remove();
|
||||
}
|
||||
|
||||
public function buildMatrix(camera: CameraObject) {
|
||||
transform.buildMatrix();
|
||||
if (data.raw.type == "sun") { // Cover camera frustum
|
||||
#if (rp_shadowmap && !lnx_csm) // Otherwise set cascades on mesh draw
|
||||
setCascade(camera, 0);
|
||||
#else
|
||||
V.getInverse(transform.world);
|
||||
updateViewFrustum(camera);
|
||||
#end
|
||||
}
|
||||
else { // Point, spot, area
|
||||
V.getInverse(transform.world);
|
||||
updateViewFrustum(camera);
|
||||
}
|
||||
}
|
||||
|
||||
#if rp_shadowmap
|
||||
|
||||
static inline function setCorners() {
|
||||
corners[0].set(-1.0, -1.0, 1.0);
|
||||
corners[1].set(-1.0, -1.0, -1.0);
|
||||
corners[2].set(-1.0, 1.0, 1.0);
|
||||
corners[3].set(-1.0, 1.0, -1.0);
|
||||
corners[4].set(1.0, -1.0, 1.0);
|
||||
corners[5].set(1.0, -1.0, -1.0);
|
||||
corners[6].set(1.0, 1.0, 1.0);
|
||||
corners[7].set(1.0, 1.0, -1.0);
|
||||
}
|
||||
|
||||
static inline function mix(a: Float, b: Float, f: Float): Float {
|
||||
return a * (1 - f) + b * f;
|
||||
}
|
||||
|
||||
public function setCascade(camera: CameraObject, cascade: Int) {
|
||||
m.setFrom(camera.V);
|
||||
|
||||
#if lnx_csm
|
||||
if (camSlicedP == null) {
|
||||
camSlicedP = [];
|
||||
cascadeSplit = [];
|
||||
var ortho = camera.data.raw.ortho;
|
||||
if (ortho == null) {
|
||||
var aspect = camera.data.raw.aspect != null ? camera.data.raw.aspect : iron.App.w() / iron.App.h();
|
||||
var fov = camera.data.raw.fov;
|
||||
var near = camera.data.raw.near_plane;
|
||||
var far = camera.data.raw.far_plane;
|
||||
var factor = cascadeCount > 2 ? cascadeSplitFactor : cascadeSplitFactor * 0.25;
|
||||
for (i in 0...cascadeCount) {
|
||||
var f = i + 1.0;
|
||||
var cfar = mix(
|
||||
near + (f / cascadeCount) * (far - near),
|
||||
near * Math.pow(far / near, f / cascadeCount),
|
||||
factor);
|
||||
cascadeSplit.push(cfar);
|
||||
camSlicedP.push(Mat4.persp(fov, aspect, near, cfar));
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i in 0...cascadeCount) {
|
||||
cascadeSplit.push(data.raw.far_plane);
|
||||
camSlicedP.push(Mat4.ortho(ortho[0], ortho[1], ortho[2], ortho[3], data.raw.near_plane, data.raw.far_plane));
|
||||
}
|
||||
}
|
||||
}
|
||||
m.multmat(camSlicedP[cascade]);
|
||||
#else
|
||||
if (camSlicedP == null) { // Fit to light far plane
|
||||
var ortho = camera.data.raw.ortho;
|
||||
if (ortho == null) {
|
||||
var fov = camera.data.raw.fov;
|
||||
var near = data.raw.near_plane;
|
||||
var far = data.raw.far_plane;
|
||||
var aspect = camera.data.raw.aspect != null ? camera.data.raw.aspect : iron.App.w() / iron.App.h();
|
||||
camSlicedP = Mat4.persp(fov, aspect, near, far);
|
||||
}
|
||||
else {
|
||||
// camSlicedP = camera.P;
|
||||
camSlicedP = Mat4.ortho(ortho[0], ortho[1], ortho[2], ortho[3], data.raw.near_plane, data.raw.far_plane);
|
||||
}
|
||||
}
|
||||
m.multmat(camSlicedP);
|
||||
#end
|
||||
|
||||
m.getInverse(m);
|
||||
V.getInverse(transform.world);
|
||||
V.toRotation();
|
||||
m.multmat(V);
|
||||
setCorners();
|
||||
for (v in corners) {
|
||||
v.applymat4(m);
|
||||
v.set(v.x / v.w, v.y / v.w, v.z / v.w);
|
||||
}
|
||||
|
||||
var minx = corners[0].x;
|
||||
var miny = corners[0].y;
|
||||
var minz = corners[0].z;
|
||||
var maxx = corners[0].x;
|
||||
var maxy = corners[0].y;
|
||||
var maxz = corners[0].z;
|
||||
for (v in corners) {
|
||||
if (v.x < minx) minx = v.x;
|
||||
if (v.x > maxx) maxx = v.x;
|
||||
if (v.y < miny) miny = v.y;
|
||||
if (v.y > maxy) maxy = v.y;
|
||||
if (v.z < minz) minz = v.z;
|
||||
if (v.z > maxz) maxz = v.z;
|
||||
}
|
||||
|
||||
// Adjust frustum size by longest diagonal - fix rotation swim
|
||||
var diag0 = Vec4.distance(corners[0], corners[7]);
|
||||
var offx = (diag0 - (maxx - minx)) * 0.5;
|
||||
var offy = (diag0 - (maxy - miny)) * 0.5;
|
||||
minx -= offx;
|
||||
maxx += offx;
|
||||
miny -= offy;
|
||||
maxy += offy;
|
||||
|
||||
// Snap to texel coords - fix translation swim
|
||||
var smsize = data.raw.shadowmap_size;
|
||||
#if lnx_csm // Cascades
|
||||
smsize = Std.int(smsize / 4);
|
||||
#end
|
||||
var worldPerTexelX = (maxx - minx) / smsize;
|
||||
var worldPerTexelY = (maxy - miny) / smsize;
|
||||
var worldPerTexelZ = (maxz - minz) / smsize;
|
||||
minx = Math.floor(minx / worldPerTexelX) * worldPerTexelX;
|
||||
miny = Math.floor(miny / worldPerTexelY) * worldPerTexelY;
|
||||
minz = Math.floor(minz / worldPerTexelZ) * worldPerTexelZ;
|
||||
maxx = Math.floor(maxx / worldPerTexelX) * worldPerTexelX;
|
||||
maxy = Math.floor(maxy / worldPerTexelY) * worldPerTexelY;
|
||||
maxz = Math.floor(maxz / worldPerTexelZ) * worldPerTexelZ;
|
||||
|
||||
var hx = (maxx - minx) / 2;
|
||||
var hy = (maxy - miny) / 2;
|
||||
var hz = (maxz - minz) / 2;
|
||||
V._30 = -(minx + hx);
|
||||
V._31 = -(miny + hy);
|
||||
V._32 = -(minz + hz);
|
||||
|
||||
// (-hz * 4 * cascadeBounds) - include shadow casters out of view frustum
|
||||
m = Mat4.ortho(-hx, hx, -hy, hy, -hz * 4 * cascadeBounds, hz);
|
||||
P.setFrom(m);
|
||||
|
||||
updateViewFrustum(camera);
|
||||
|
||||
#if lnx_csm
|
||||
if (cascadeVP == null) {
|
||||
cascadeVP = [];
|
||||
for (i in 0...cascadeCount) {
|
||||
cascadeVP.push(Mat4.identity());
|
||||
}
|
||||
}
|
||||
cascadeVP[cascade].setFrom(VP);
|
||||
#end
|
||||
}
|
||||
#end // rp_shadowmap
|
||||
|
||||
function updateViewFrustum(camera: CameraObject) {
|
||||
VP.multmats(P, V);
|
||||
|
||||
// Frustum culling enabled
|
||||
if (camera.data.raw.frustum_culling) {
|
||||
if (frustumPlanes == null) {
|
||||
frustumPlanes = [];
|
||||
for (i in 0...6) frustumPlanes.push(new FrustumPlane());
|
||||
}
|
||||
CameraObject.buildViewFrustum(VP, frustumPlanes);
|
||||
}
|
||||
}
|
||||
|
||||
public function setCubeFace(face: Int, camera: CameraObject) {
|
||||
// Set matrix to match cubemap face
|
||||
eye.set(transform.worldx(), transform.worldy(), transform.worldz());
|
||||
#if (!kha_opengl && !kha_webgl && !lnx_shadowmap_atlas)
|
||||
var flip = (face == 2 || face == 3) ? true : false; // Flip +Y, -Y
|
||||
#else
|
||||
var flip = false;
|
||||
#end
|
||||
CameraObject.setCubeFace(V, eye, face, flip);
|
||||
updateViewFrustum(camera);
|
||||
}
|
||||
|
||||
#if lnx_csm
|
||||
public function getCascadeData(): Float32Array {
|
||||
// Cascade mats + split distances
|
||||
if (cascadeData == null) {
|
||||
cascadeData = new Float32Array(cascadeCount * 16 + 4);
|
||||
}
|
||||
if (cascadeVP == null) return cascadeData;
|
||||
|
||||
// 4 cascade mats + split distances
|
||||
for (i in 0...cascadeCount) {
|
||||
m.setFrom(cascadeVP[i]);
|
||||
bias.setFrom(Uniforms.biasMat);
|
||||
#if (!lnx_shadowmap_atlas)
|
||||
bias._00 /= cascadeCount; // Atlas offset
|
||||
bias._30 /= cascadeCount;
|
||||
bias._30 += i * (1 / cascadeCount);
|
||||
#else
|
||||
// tile matrix
|
||||
helpMat.setIdentity();
|
||||
// scale [0-1] coords to [0-tilescale]
|
||||
helpMat._00 = this.tileScale[i];
|
||||
helpMat._11 = this.tileScale[i];
|
||||
// offset coordinate start from [0, 0] to [tile-start-x, tile-start-y]
|
||||
helpMat._30 = this.tileOffsetX[i];
|
||||
helpMat._31 = this.tileOffsetY[i];
|
||||
bias.multmat(helpMat);
|
||||
#if (!kha_opengl)
|
||||
helpMat.setIdentity();
|
||||
helpMat._11 = -1.0;
|
||||
helpMat._31 = 1.0;
|
||||
bias.multmat(helpMat);
|
||||
#end
|
||||
#end
|
||||
m.multmat(bias);
|
||||
cascadeData[i * 16] = m._00;
|
||||
cascadeData[i * 16 + 1] = m._01;
|
||||
cascadeData[i * 16 + 2] = m._02;
|
||||
cascadeData[i * 16 + 3] = m._03;
|
||||
cascadeData[i * 16 + 4] = m._10;
|
||||
cascadeData[i * 16 + 5] = m._11;
|
||||
cascadeData[i * 16 + 6] = m._12;
|
||||
cascadeData[i * 16 + 7] = m._13;
|
||||
cascadeData[i * 16 + 8] = m._20;
|
||||
cascadeData[i * 16 + 9] = m._21;
|
||||
cascadeData[i * 16 + 10] = m._22;
|
||||
cascadeData[i * 16 + 11] = m._23;
|
||||
cascadeData[i * 16 + 12] = m._30;
|
||||
cascadeData[i * 16 + 13] = m._31;
|
||||
cascadeData[i * 16 + 14] = m._32;
|
||||
cascadeData[i * 16 + 15] = m._33;
|
||||
}
|
||||
cascadeData[cascadeCount * 16 ] = cascadeSplit[0];
|
||||
cascadeData[cascadeCount * 16 + 1] = cascadeSplit[1];
|
||||
cascadeData[cascadeCount * 16 + 2] = cascadeSplit[2];
|
||||
cascadeData[cascadeCount * 16 + 3] = cascadeSplit[3];
|
||||
return cascadeData;
|
||||
}
|
||||
#end // lnx_csm
|
||||
|
||||
#if lnx_clusters
|
||||
|
||||
// Centralize discarding conditions when iterating over lights
|
||||
// Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray)
|
||||
public inline static function discardLight(light: LightObject) {
|
||||
return !light.visible || light.data.raw.strength == 0.0 || light.data.raw.type == "sun";
|
||||
}
|
||||
// Discarding conditions but with culling included
|
||||
public inline static function discardLightCulled(light: LightObject) {
|
||||
return #if lnx_shadowmap_atlas light.culledLight || #end discardLight(light);
|
||||
}
|
||||
|
||||
#if (lnx_shadowmap_atlas && lnx_shadowmap_atlas_lod)
|
||||
// Arbitrary function to map from [0-16] to [1.0-0.0]
|
||||
public inline static function zToShadowMapScale(z: Int, max: Int): Float {
|
||||
return 0.25 * Math.sqrt(-z + max);
|
||||
}
|
||||
#end
|
||||
|
||||
static function getRadius(strength: kha.FastFloat): kha.FastFloat {
|
||||
// (1.0 / (dist * dist)) * strength = 0.01
|
||||
return Math.sqrt(strength / 0.004);
|
||||
}
|
||||
|
||||
inline static function distSliceX(f: Float, lpos: Vec4): Float {
|
||||
return (lpos.x - f * lpos.z) / Math.sqrt(1.0 + f * f);
|
||||
}
|
||||
|
||||
inline static function distSliceY(f: Float, lpos: Vec4): Float {
|
||||
return (lpos.y - f * lpos.z) / Math.sqrt(1.0 + f * f);
|
||||
}
|
||||
|
||||
static function sliceToDist(camera: CameraObject, z: Int): Float {
|
||||
var cnear = clusterNear + camera.data.raw.near_plane;
|
||||
switch (z) {
|
||||
case 0:
|
||||
return camera.data.raw.near_plane;
|
||||
case 1:
|
||||
return cnear;
|
||||
default: {
|
||||
var depthl = (z - 1) / (slicesZ - 1);
|
||||
return Math.exp(depthl * Math.log(camera.data.raw.far_plane - cnear + 1.0)) + cnear - 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function updateClusters(camera: CameraObject) {
|
||||
// Reference: https://newq.net/publications/more/s2015-many-lights-course
|
||||
var lights = Scene.active.lights;
|
||||
|
||||
#if lnx_spot // Point lamps first
|
||||
lights.sort(function(a, b): Int {
|
||||
return a.data.raw.type >= b.data.raw.type ? 1 : -1;
|
||||
});
|
||||
#end
|
||||
|
||||
if (clustersData == null) {
|
||||
var lines = #if (lnx_spot) 2 #else 1 #end;
|
||||
clustersData = kha.Image.create(slicesX * slicesY * slicesZ, lines + maxLightsCluster, TextureFormat.L8, Usage.DynamicUsage);
|
||||
}
|
||||
|
||||
var bytes = clustersData.lock();
|
||||
|
||||
var stride = slicesX * slicesY * slicesZ;
|
||||
for (i in 0...stride) {
|
||||
bytes.set(i, 0);
|
||||
#if lnx_spot
|
||||
bytes.set(i + stride * (maxLightsCluster + 1), 0);
|
||||
#end
|
||||
}
|
||||
|
||||
var fovtan = Math.tan(camera.data.raw.fov * 0.5);
|
||||
var stepY = (2.0 * fovtan) / slicesY;
|
||||
var aspect = RenderPath.active.currentW / RenderPath.active.currentH;
|
||||
var stepX = (2.0 * fovtan * aspect) / slicesX;
|
||||
|
||||
var n = lights.length > maxLights ? maxLights : lights.length;
|
||||
var i = 0;
|
||||
for (l in lights) {
|
||||
if (discardLight(l)) continue;
|
||||
if (i >= n) break;
|
||||
// Light bounds
|
||||
lpos.set(l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
|
||||
lpos.applymat4(camera.V);
|
||||
lpos.z *= -1.0;
|
||||
var radius = getRadius(l.data.raw.strength);
|
||||
var minX = 0;
|
||||
var minY = 0;
|
||||
var minZ = 0;
|
||||
var maxX = slicesX;
|
||||
var maxY = slicesY;
|
||||
var maxZ = slicesZ;
|
||||
while (minX <= slicesX) {
|
||||
if (distSliceX(stepX * (minX + 1 - slicesX * 0.5), lpos) <= radius) break;
|
||||
minX++;
|
||||
}
|
||||
while (maxX >= minX) {
|
||||
if (-distSliceX(stepX * (maxX - 1 - slicesX * 0.5), lpos) <= radius) { maxX--; break; }
|
||||
maxX--;
|
||||
}
|
||||
while (minY <= slicesY) {
|
||||
if (distSliceY(stepY * (minY + 1 - slicesY * 0.5), lpos) <= radius) break;
|
||||
minY++;
|
||||
}
|
||||
while (maxY >= minY) {
|
||||
if (-distSliceY(stepY * (maxY - 1 - slicesY * 0.5), lpos) <= radius) { maxY--; break; }
|
||||
maxY--;
|
||||
}
|
||||
while (minZ <= slicesZ) {
|
||||
if (sliceToDist(camera, minZ + 1) >= lpos.z - radius) break;
|
||||
minZ++;
|
||||
}
|
||||
while (maxZ >= minZ) {
|
||||
if (sliceToDist(camera, maxZ - 1) <= lpos.z + radius) break;
|
||||
maxZ--;
|
||||
}
|
||||
#if lnx_shadowmap_atlas
|
||||
l.culledLight = maxZ < 0 || minX > maxX || minY > maxY;
|
||||
l.shadowMapScale = l.culledLight ? 0.0 : #if lnx_shadowmap_atlas_lod zToShadowMapScale(minZ, slicesZ) #else 1.0 #end;
|
||||
// Discard lights that are outside of the view
|
||||
if (l.culledLight) {
|
||||
continue;
|
||||
}
|
||||
#end
|
||||
// Mark affected clusters
|
||||
for (z in minZ...maxZ + 1) {
|
||||
for (y in minY...maxY + 1) {
|
||||
for (x in minX...maxX + 1) {
|
||||
var cluster = x + y * slicesX + z * slicesX * slicesY;
|
||||
var numLights = bytes.get(cluster);
|
||||
if (numLights < maxLightsCluster) {
|
||||
numLights++;
|
||||
bytes.set(cluster, numLights);
|
||||
bytes.set(cluster + stride * numLights, i);
|
||||
#if lnx_spot
|
||||
if (l.data.raw.type == "spot") {
|
||||
// Last line
|
||||
var numSpots = bytes.get(cluster + stride * (maxLightsCluster + 1)) + 1;
|
||||
bytes.set(cluster + stride * (maxLightsCluster + 1), numSpots);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
clustersData.unlock();
|
||||
|
||||
updateLightsArray(); // TODO: only update on light change
|
||||
}
|
||||
|
||||
static function updateLightsArray() {
|
||||
if (lightsArray == null) { // vec4x3 - 1: pos, a, color, b, 2: dir, c
|
||||
lightsArray = new Float32Array(maxLights * 4 * 3);
|
||||
#if lnx_spot
|
||||
lightsArraySpot = new Float32Array(maxLights * 4 * 2);
|
||||
#end
|
||||
}
|
||||
var lights = Scene.active.lights;
|
||||
var n = lights.length > maxLights ? maxLights : lights.length;
|
||||
var i = 0;
|
||||
for (l in lights) {
|
||||
if (discardLightCulled(l)) continue;
|
||||
if (i >= n) break;
|
||||
|
||||
// light position
|
||||
lightsArray[i * 12 ] = l.transform.worldx();
|
||||
lightsArray[i * 12 + 1] = l.transform.worldy();
|
||||
lightsArray[i * 12 + 2] = l.transform.worldz();
|
||||
lightsArray[i * 12 + 3] = 0.0; // padding or spot scale x
|
||||
|
||||
// light color
|
||||
var f = l.data.raw.strength;
|
||||
lightsArray[i * 12 + 4] = l.data.raw.color[0] * f;
|
||||
lightsArray[i * 12 + 5] = l.data.raw.color[1] * f;
|
||||
lightsArray[i * 12 + 6] = l.data.raw.color[2] * f;
|
||||
lightsArray[i * 12 + 7] = 0.0; // padding or spot scale y
|
||||
|
||||
// other data
|
||||
lightsArray[i * 12 + 8] = l.data.raw.shadows_bias; // bias
|
||||
lightsArray[i * 12 + 9] = 0.0; // cutoff for detecting spot
|
||||
lightsArray[i * 12 + 10] = l.data.raw.cast_shadow ? 1.0 : 0.0; // hasShadows
|
||||
lightsArray[i * 12 + 11] = 0.0; // padding
|
||||
|
||||
#if lnx_spot
|
||||
if (l.data.raw.type == "spot") {
|
||||
lightsArray[i * 12 + 9] = l.data.raw.spot_size;
|
||||
|
||||
var dir = l.look().normalize();
|
||||
lightsArraySpot[i * 8 ] = dir.x;
|
||||
lightsArraySpot[i * 8 + 1] = dir.y;
|
||||
lightsArraySpot[i * 8 + 2] = dir.z;
|
||||
lightsArraySpot[i * 8 + 3] = l.data.raw.spot_blend;
|
||||
|
||||
// Premultiply scale with z component
|
||||
var scale = l.transform.scale;
|
||||
lightsArray[i * 12 + 3] = scale.z == 0.0 ? 0.0 : scale.x / scale.z;
|
||||
lightsArray[i * 12 + 7] = scale.z == 0.0 ? 0.0 : scale.y / scale.z;
|
||||
|
||||
final right = l.right().normalize();
|
||||
lightsArraySpot[i * 8 + 4] = right.x;
|
||||
lightsArraySpot[i * 8 + 5] = right.y;
|
||||
lightsArraySpot[i * 8 + 6] = right.z;
|
||||
lightsArraySpot[i * 8 + 7] = 0.0; // padding
|
||||
}
|
||||
#end
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public static function updateLWVPMatrixArray(object: Object, type: String) {
|
||||
if (LWVPMatrixArray == null) {
|
||||
LWVPMatrixArray = new Float32Array(maxLightsCluster * 16);
|
||||
}
|
||||
|
||||
var lights = Scene.active.lights;
|
||||
var n = lights.length > maxLightsCluster ? maxLightsCluster : lights.length;
|
||||
var i = 0;
|
||||
|
||||
for (light in lights) {
|
||||
if (i >= n) {
|
||||
break;
|
||||
}
|
||||
if (discardLightCulled(light)) continue;
|
||||
if (light.data.raw.type == type) {
|
||||
m.setFrom(light.VP);
|
||||
m.multmat(Uniforms.biasMat);
|
||||
#if lnx_shadowmap_atlas
|
||||
// tile matrix
|
||||
helpMat.setIdentity();
|
||||
// scale [0-1] coords to [0-tilescale]
|
||||
helpMat._00 = light.tileScale[0];
|
||||
helpMat._11 = light.tileScale[0];
|
||||
// offset coordinate start from [0, 0] to [tile-start-x, tile-start-y]
|
||||
helpMat._30 = light.tileOffsetX[0];
|
||||
helpMat._31 = light.tileOffsetY[0];
|
||||
m.multmat(helpMat);
|
||||
#if (!kha_opengl)
|
||||
helpMat.setIdentity();
|
||||
helpMat._11 = -1.0;
|
||||
helpMat._31 = 1.0;
|
||||
m.multmat(helpMat);
|
||||
#end
|
||||
#end
|
||||
|
||||
LWVPMatrixArray[i * 16 ] = m._00;
|
||||
LWVPMatrixArray[i * 16 + 1] = m._01;
|
||||
LWVPMatrixArray[i * 16 + 2] = m._02;
|
||||
LWVPMatrixArray[i * 16 + 3] = m._03;
|
||||
LWVPMatrixArray[i * 16 + 4] = m._10;
|
||||
LWVPMatrixArray[i * 16 + 5] = m._11;
|
||||
LWVPMatrixArray[i * 16 + 6] = m._12;
|
||||
LWVPMatrixArray[i * 16 + 7] = m._13;
|
||||
LWVPMatrixArray[i * 16 + 8] = m._20;
|
||||
LWVPMatrixArray[i * 16 + 9] = m._21;
|
||||
LWVPMatrixArray[i * 16 + 10] = m._22;
|
||||
LWVPMatrixArray[i * 16 + 11] = m._23;
|
||||
LWVPMatrixArray[i * 16 + 12] = m._30;
|
||||
LWVPMatrixArray[i * 16 + 13] = m._31;
|
||||
LWVPMatrixArray[i * 16 + 14] = m._32;
|
||||
LWVPMatrixArray[i * 16 + 15] = m._33;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return LWVPMatrixArray;
|
||||
}
|
||||
|
||||
public static inline function getMaxLights(): Int {
|
||||
#if (rp_max_lights == 8)
|
||||
return 8;
|
||||
#elseif (rp_max_lights == 16)
|
||||
return 16;
|
||||
#elseif (rp_max_lights == 24)
|
||||
return 24;
|
||||
#elseif (rp_max_lights == 32)
|
||||
return 32;
|
||||
#elseif (rp_max_lights == 64)
|
||||
return 64;
|
||||
#else
|
||||
return 4;
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function getMaxLightsCluster(): Int {
|
||||
#if (rp_max_lights_cluster == 8)
|
||||
return 8;
|
||||
#elseif (rp_max_lights_cluster == 16)
|
||||
return 16;
|
||||
#elseif (rp_max_lights_cluster == 24)
|
||||
return 24;
|
||||
#elseif (rp_max_lights_cluster == 32)
|
||||
return 32;
|
||||
#elseif (rp_max_lights_cluster == 64)
|
||||
return 64;
|
||||
#else
|
||||
return 4;
|
||||
#end
|
||||
}
|
||||
#end // lnx_clusters
|
||||
|
||||
public inline function right(): Vec4 {
|
||||
return new Vec4(V._00, V._10, V._20);
|
||||
}
|
||||
|
||||
public inline function up(): Vec4 {
|
||||
return new Vec4(V._01, V._11, V._21);
|
||||
}
|
||||
|
||||
public inline function look(): Vec4 {
|
||||
return new Vec4(V._02, V._12, V._22);
|
||||
}
|
||||
}
|
403
leenkx/Sources/iron/object/MeshObject.hx
Normal file
403
leenkx/Sources/iron/object/MeshObject.hx
Normal file
@ -0,0 +1,403 @@
|
||||
package iron.object;
|
||||
|
||||
import haxe.ds.Vector;
|
||||
import kha.graphics4.Graphics;
|
||||
import kha.graphics4.PipelineState;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.data.MeshData;
|
||||
import iron.data.MaterialData;
|
||||
import iron.data.ShaderData;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class MeshObject extends Object {
|
||||
|
||||
public var data: MeshData = null;
|
||||
public var materials: Vector<MaterialData>;
|
||||
public var materialIndex = 0;
|
||||
public var depthRead(default, null) = false;
|
||||
#if lnx_particles
|
||||
public var particleSystems: Array<ParticleSystem> = null; // Particle owner
|
||||
public var particleChildren: Array<MeshObject> = null;
|
||||
public var particleOwner: MeshObject = null; // Particle object
|
||||
public var particleIndex = -1;
|
||||
#end
|
||||
public var cameraDistance: Float;
|
||||
public var screenSize = 0.0;
|
||||
public var frustumCulling = true;
|
||||
public var activeTilesheet: Tilesheet = null;
|
||||
public var tilesheets: Array<Tilesheet> = null;
|
||||
public var skip_context: String = null; // Do not draw this context
|
||||
public var force_context: String = null; // Draw only this context
|
||||
static var lastPipeline: PipelineState = null;
|
||||
#if lnx_morph_target
|
||||
public var morphTarget: MorphTarget = null;
|
||||
#end
|
||||
|
||||
#if lnx_veloc
|
||||
public var prevMatrix = Mat4.identity();
|
||||
#end
|
||||
|
||||
public function new(data: MeshData, materials: Vector<MaterialData>) {
|
||||
super();
|
||||
|
||||
this.materials = materials;
|
||||
setData(data);
|
||||
Scene.active.meshes.push(this);
|
||||
}
|
||||
|
||||
public function setData(data: MeshData) {
|
||||
this.data = data;
|
||||
data.refcount++;
|
||||
|
||||
#if (!lnx_batch)
|
||||
data.geom.build();
|
||||
#end
|
||||
|
||||
// Scale-up packed (-1,1) mesh coords
|
||||
transform.scaleWorld = data.scalePos;
|
||||
}
|
||||
|
||||
#if lnx_batch
|
||||
@:allow(iron.Scene)
|
||||
function batch(isLod: Bool) {
|
||||
var batched = Scene.active.meshBatch.addMesh(this, isLod);
|
||||
if (!batched) data.geom.build();
|
||||
}
|
||||
#end
|
||||
|
||||
override public function remove() {
|
||||
#if lnx_batch
|
||||
Scene.active.meshBatch.removeMesh(this);
|
||||
#end
|
||||
#if lnx_particles
|
||||
if (particleChildren != null) {
|
||||
for (c in particleChildren) c.remove();
|
||||
particleChildren = null;
|
||||
}
|
||||
if (particleSystems != null) {
|
||||
for (psys in particleSystems) psys.remove();
|
||||
particleSystems = null;
|
||||
}
|
||||
#end
|
||||
if (activeTilesheet != null) activeTilesheet.remove();
|
||||
if (tilesheets != null) for (ts in tilesheets) { ts.remove(); }
|
||||
if (Scene.active != null) Scene.active.meshes.remove(this);
|
||||
data.refcount--;
|
||||
super.remove();
|
||||
}
|
||||
|
||||
override public function setupAnimation(oactions: Array<TSceneFormat> = null) {
|
||||
#if lnx_skin
|
||||
var hasAction = parent != null && parent.raw != null && parent.raw.bone_actions != null;
|
||||
if (hasAction) {
|
||||
var armatureUid = parent.uid;
|
||||
animation = getBoneAnimation(armatureUid);
|
||||
if (animation == null) animation = new BoneAnimation(armatureUid, parent);
|
||||
if (data.isSkinned) cast(animation, BoneAnimation).setSkin(this);
|
||||
}
|
||||
#end
|
||||
super.setupAnimation(oactions);
|
||||
}
|
||||
|
||||
#if lnx_morph_target
|
||||
override public function setupMorphTargets() {
|
||||
if (data.raw.morph_target != null) {
|
||||
morphTarget = new MorphTarget(data.raw.morph_target);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
#if lnx_particles
|
||||
public function setupParticleSystem(sceneName: String, pref: TParticleReference) {
|
||||
if (particleSystems == null) particleSystems = [];
|
||||
var psys = new ParticleSystem(sceneName, pref);
|
||||
particleSystems.push(psys);
|
||||
}
|
||||
#end
|
||||
|
||||
public function setupTilesheet(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) {
|
||||
activeTilesheet = new Tilesheet(sceneName, tilesheet_ref, tilesheet_action_ref);
|
||||
if(tilesheets == null) tilesheets = new Array<Tilesheet>();
|
||||
tilesheets.push(activeTilesheet);
|
||||
}
|
||||
|
||||
public function setActiveTilesheet(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) {
|
||||
var set = false;
|
||||
// Check if tilesheet already created
|
||||
if (tilesheets != null) {
|
||||
for (ts in tilesheets) {
|
||||
if (ts.raw.name == tilesheet_ref) {
|
||||
activeTilesheet = ts;
|
||||
activeTilesheet.play(tilesheet_action_ref);
|
||||
set = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If not already created
|
||||
if (!set) {
|
||||
setupTilesheet(sceneName, tilesheet_ref, tilesheet_action_ref);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline function isLodMaterial(): Bool {
|
||||
return (raw != null && raw.lod_material != null && raw.lod_material == true);
|
||||
}
|
||||
|
||||
function setCulled(isShadow: Bool, b: Bool): Bool {
|
||||
isShadow ? culledShadow = b : culledMesh = b;
|
||||
culled = culledMesh && culledShadow;
|
||||
#if lnx_debug
|
||||
if (b) RenderPath.culled++;
|
||||
#end
|
||||
return b;
|
||||
}
|
||||
|
||||
public function cullMaterial(context: String): Bool {
|
||||
// Skip render if material does not contain current context
|
||||
var mats = materials;
|
||||
if (!isLodMaterial() && !validContext(mats, context)) return true;
|
||||
|
||||
var isShadow = context == "shadowmap";
|
||||
if (!visibleMesh && !isShadow) return setCulled(isShadow, true);
|
||||
if (!visibleShadow && isShadow) return setCulled(isShadow, true);
|
||||
|
||||
if (skip_context == context) return setCulled(isShadow, true);
|
||||
if (force_context != null && force_context != context) return setCulled(isShadow, true);
|
||||
|
||||
return setCulled(isShadow, false);
|
||||
}
|
||||
|
||||
function cullMesh(context: String, camera: CameraObject, light: LightObject): Bool {
|
||||
if (camera == null) return false;
|
||||
|
||||
if (camera.data.raw.frustum_culling && frustumCulling) {
|
||||
// Scale radius for skinned mesh and particle system
|
||||
// TODO: define skin & particle bounds
|
||||
var radiusScale = data.isSkinned ? 2.0 : 1.0;
|
||||
#if lnx_particles
|
||||
// particleSystems for update, particleOwner for render
|
||||
if (particleSystems != null || particleOwner != null) radiusScale *= 1000;
|
||||
#end
|
||||
if (context == "voxel") radiusScale *= 100;
|
||||
if (data.geom.instanced) radiusScale *= 100;
|
||||
var isShadow = context == "shadowmap";
|
||||
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
|
||||
|
||||
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
|
||||
light.transform.radius = light.data.raw.far_plane;
|
||||
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, light.transform)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CameraObject.sphereInFrustum(frustumPlanes, transform, radiusScale)) {
|
||||
return setCulled(isShadow, true);
|
||||
}
|
||||
}
|
||||
|
||||
culled = false;
|
||||
return culled;
|
||||
}
|
||||
|
||||
function skipContext(context: String, mat: MaterialData): Bool {
|
||||
if (mat.raw.skip_context != null &&
|
||||
mat.raw.skip_context == context) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getContexts(context: String, materials: Vector<MaterialData>, materialContexts: Array<MaterialContext>, shaderContexts: Array<ShaderContext>) {
|
||||
for (mat in materials) {
|
||||
var found = false;
|
||||
for (i in 0...mat.raw.contexts.length) {
|
||||
if (mat.raw.contexts[i].name.substr(0, context.length) == context) {
|
||||
materialContexts.push(mat.contexts[i]);
|
||||
shaderContexts.push(mat.shader.getContext(context));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
materialContexts.push(null);
|
||||
shaderContexts.push(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render(g: Graphics, context: String, bindParams: Array<String>) {
|
||||
if (data == null || !data.geom.ready) return; // Data not yet streamed
|
||||
if (!visible) return; // Skip render if object is hidden
|
||||
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
|
||||
var meshContext = raw != null ? context == "mesh" : false;
|
||||
|
||||
#if lnx_particles
|
||||
if (raw != null && raw.is_particle && particleOwner == null) return; // Instancing not yet set-up by particle system owner
|
||||
if (particleSystems != null && meshContext) {
|
||||
if (particleChildren == null) {
|
||||
particleChildren = [];
|
||||
for (psys in particleSystems) {
|
||||
// var c: MeshObject = cast Scene.active.getChild(psys.data.raw.instance_object);
|
||||
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
|
||||
if (o != null) {
|
||||
var c: MeshObject = cast o;
|
||||
particleChildren.push(c);
|
||||
c.particleOwner = this;
|
||||
c.particleIndex = particleChildren.length - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
for (i in 0...particleSystems.length) {
|
||||
particleSystems[i].update(particleChildren[i], this);
|
||||
}
|
||||
}
|
||||
if (particleSystems != null && particleSystems.length > 0 && !raw.render_emitter) return;
|
||||
#end
|
||||
|
||||
if (cullMaterial(context)) return;
|
||||
|
||||
// Get lod
|
||||
var mats = materials;
|
||||
var lod = this;
|
||||
if (raw != null && raw.lods != null && raw.lods.length > 0) {
|
||||
computeScreenSize(Scene.active.camera);
|
||||
initLods();
|
||||
if (context == "voxel") {
|
||||
// Voxelize using the lowest lod
|
||||
lod = cast lods[lods.length - 1];
|
||||
}
|
||||
else {
|
||||
// Select lod
|
||||
for (i in 0...raw.lods.length) {
|
||||
// Lod found
|
||||
if (screenSize > raw.lods[i].screen_size) break;
|
||||
lod = cast lods[i];
|
||||
if (isLodMaterial()) mats = lod.materials;
|
||||
}
|
||||
}
|
||||
if (lod == null) return; // Empty object
|
||||
}
|
||||
#if lnx_debug
|
||||
else computeScreenSize(Scene.active.camera);
|
||||
#end
|
||||
if (isLodMaterial() && !validContext(mats, context)) return;
|
||||
|
||||
// Get context
|
||||
var materialContexts: Array<MaterialContext> = [];
|
||||
var shaderContexts: Array<ShaderContext> = [];
|
||||
getContexts(context, mats, materialContexts, shaderContexts);
|
||||
|
||||
Uniforms.posUnpack = data.scalePos;
|
||||
Uniforms.texUnpack = data.scaleTex;
|
||||
transform.update();
|
||||
|
||||
// Render mesh
|
||||
var ldata = lod.data;
|
||||
for (i in 0...ldata.geom.indexBuffers.length) {
|
||||
|
||||
var mi = ldata.geom.materialIndices[i];
|
||||
if (shaderContexts.length <= mi || shaderContexts[mi] == null) continue;
|
||||
materialIndex = mi;
|
||||
|
||||
// Check context skip
|
||||
if (materials.length > mi && skipContext(context, materials[mi])) continue;
|
||||
|
||||
var scontext = shaderContexts[mi];
|
||||
if (scontext == null) continue;
|
||||
var elems = scontext.raw.vertex_elements;
|
||||
|
||||
// Uniforms
|
||||
if (scontext.pipeState != lastPipeline) {
|
||||
g.setPipeline(scontext.pipeState);
|
||||
lastPipeline = scontext.pipeState;
|
||||
// Uniforms.setContextConstants(g, scontext, bindParams);
|
||||
}
|
||||
Uniforms.setContextConstants(g, scontext, bindParams); //
|
||||
Uniforms.setObjectConstants(g, scontext, this);
|
||||
if (materialContexts.length > mi) {
|
||||
Uniforms.setMaterialConstants(g, scontext, materialContexts[mi]);
|
||||
}
|
||||
|
||||
// VB / IB
|
||||
#if lnx_deinterleaved
|
||||
g.setVertexBuffers(ldata.geom.get(elems));
|
||||
#else
|
||||
if (ldata.geom.instancedVB != null) {
|
||||
g.setVertexBuffers([ldata.geom.get(elems), ldata.geom.instancedVB]);
|
||||
}
|
||||
else {
|
||||
g.setVertexBuffer(ldata.geom.get(elems));
|
||||
}
|
||||
#end
|
||||
|
||||
g.setIndexBuffer(ldata.geom.indexBuffers[i]);
|
||||
|
||||
// Draw
|
||||
if (ldata.geom.instanced) {
|
||||
g.drawIndexedVerticesInstanced(ldata.geom.instanceCount, ldata.geom.start, ldata.geom.count);
|
||||
}
|
||||
else {
|
||||
g.drawIndexedVertices(ldata.geom.start, ldata.geom.count);
|
||||
}
|
||||
}
|
||||
|
||||
#if lnx_debug
|
||||
var isShadow = context == "shadowmap";
|
||||
if (meshContext) RenderPath.numTrisMesh += ldata.geom.numTris;
|
||||
else if (isShadow) RenderPath.numTrisShadow += ldata.geom.numTris;
|
||||
RenderPath.drawCalls++;
|
||||
#end
|
||||
|
||||
#if lnx_veloc
|
||||
prevMatrix.setFrom(transform.worldUnpack);
|
||||
#end
|
||||
}
|
||||
|
||||
function validContext(mats: Vector<MaterialData>, context: String): Bool {
|
||||
for (mat in mats) if (mat.getContext(context) != null) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public inline function computeCameraDistance(camX: Float, camY: Float, camZ: Float) {
|
||||
// Render path mesh sorting
|
||||
cameraDistance = Vec4.distancef(camX, camY, camZ, transform.worldx(), transform.worldy(), transform.worldz());
|
||||
}
|
||||
|
||||
public inline function computeDepthRead() {
|
||||
#if rp_depth_texture
|
||||
depthRead = false;
|
||||
for (material in materials) {
|
||||
for (context in material.contexts) {
|
||||
if (context.raw.depth_read == true) {
|
||||
depthRead = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public inline function computeScreenSize(camera: CameraObject) {
|
||||
// Approx..
|
||||
// var rp = camera.renderPath;
|
||||
// var screenVolume = rp.currentW * rp.currentH;
|
||||
var tr = transform;
|
||||
var volume = tr.dim.x * tr.dim.y * tr.dim.z;
|
||||
screenSize = volume * (1.0 / cameraDistance);
|
||||
screenSize = screenSize > 1.0 ? 1.0 : screenSize;
|
||||
}
|
||||
|
||||
inline function initLods() {
|
||||
if (lods == null) {
|
||||
lods = [];
|
||||
for (l in raw.lods) {
|
||||
if (l.object_ref == "") lods.push(null); // Empty
|
||||
else lods.push(Scene.active.getChild(l.object_ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
leenkx/Sources/iron/object/MorphTarget.hx
Normal file
62
leenkx/Sources/iron/object/MorphTarget.hx
Normal file
@ -0,0 +1,62 @@
|
||||
package iron.object;
|
||||
|
||||
#if lnx_morph_target
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.Image;
|
||||
import kha.FastFloat;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class MorphTarget {
|
||||
|
||||
public var data: TMorphTarget;
|
||||
public var numMorphTargets: Int = 0;
|
||||
public var morphImageSize: Int = 0;
|
||||
public var morphBlockSize: Int = 0;
|
||||
public var scaling: FastFloat;
|
||||
public var offset: FastFloat;
|
||||
public var morphWeights: Float32Array;
|
||||
public var morphDataPos: Image;
|
||||
public var morphDataNor: Image;
|
||||
public var morphMap: Map<String, Int> = null;
|
||||
|
||||
public function new(data: TMorphTarget) {
|
||||
initWeights(data.morph_target_defaults);
|
||||
scaling = data.morph_scale;
|
||||
offset = data.morph_offset;
|
||||
numMorphTargets = data.num_morph_targets;
|
||||
morphImageSize = data.morph_img_size;
|
||||
morphBlockSize = data.morph_block_size;
|
||||
|
||||
Data.getImage(data.morph_target_data_file + "_morph_pos.png", function(img: Image) {
|
||||
if (img != null) morphDataPos = img;
|
||||
});
|
||||
Data.getImage(data.morph_target_data_file + "_morph_nor.png", function(img: Image) {
|
||||
if (img != null) morphDataNor = img;
|
||||
});
|
||||
morphMap = new Map();
|
||||
|
||||
var i = 0;
|
||||
for (name in data.morph_target_ref) {
|
||||
morphMap.set(name, i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
inline function initWeights(defaults: Float32Array) {
|
||||
morphWeights = new Float32Array(defaults.length);
|
||||
for (i in 0...morphWeights.length) {
|
||||
morphWeights.set(i, defaults.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public function setMorphValue(name: String, value: Float) {
|
||||
var i = morphMap.get(name);
|
||||
if (i != null) {
|
||||
morphWeights.set(i, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
248
leenkx/Sources/iron/object/Object.hx
Normal file
248
leenkx/Sources/iron/object/Object.hx
Normal file
@ -0,0 +1,248 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.Trait;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class Object {
|
||||
static var uidCounter = 0;
|
||||
public var uid: Int;
|
||||
public var urandom: Float;
|
||||
public var raw: TObj = null;
|
||||
|
||||
public var name: String = "";
|
||||
public var transform: Transform;
|
||||
public var constraints: Array<Constraint> = null;
|
||||
public var traits: Array<Trait> = [];
|
||||
|
||||
public var parent: Object = null;
|
||||
public var children: Array<Object> = [];
|
||||
public var lods: Array<Object> = null;
|
||||
|
||||
public var animation: Animation = null;
|
||||
public var visible = true; // Skip render, keep updating
|
||||
public var visibleMesh = true;
|
||||
public var visibleShadow = true;
|
||||
public var culled = false; // Object was culled last frame
|
||||
public var culledMesh = false;
|
||||
public var culledShadow = false;
|
||||
public var vertex_groups: Map<String, Array<Vec4>> = null;
|
||||
public var properties: Map<String, Dynamic> = null;
|
||||
var isEmpty = false;
|
||||
|
||||
public function new() {
|
||||
uid = uidCounter++;
|
||||
urandom = seededRandom(); // Math.random();
|
||||
transform = new Transform(this);
|
||||
isEmpty = Type.getClass(this) == Object;
|
||||
if (isEmpty && Scene.active != null) Scene.active.empties.push(this);
|
||||
}
|
||||
|
||||
/**
|
||||
Set the given `parentObject` as the parent of this object.
|
||||
|
||||
If `parentObject` is `null`, the object is parented to the scene's
|
||||
`sceneParent`, which is the topmost object of the scene tree.
|
||||
If you want to remove it from the scene, use `Object.remove()` instead.
|
||||
|
||||
If `parentObject` is the object on which this function is called,
|
||||
nothing happens.
|
||||
|
||||
@param parentObject The new parent object.
|
||||
@param parentInverse (Optional) Change the scale of the child object to be relative to the new parents 3D space or use the original scale.
|
||||
@param keepTransform (Optional) When unparenting from the old parent, keep the transform given by the old parent or revert to the object's default.
|
||||
**/
|
||||
public function setParent(parentObject: Object, parentInverse = false, keepTransform = false) {
|
||||
if (parentObject == this || parentObject == parent) return;
|
||||
|
||||
if (parent != null) {
|
||||
parent.children.remove(this);
|
||||
if (keepTransform) this.transform.applyParent();
|
||||
this.parent = null; // rebuild matrix without a parent
|
||||
this.transform.buildMatrix();
|
||||
}
|
||||
|
||||
if (parentObject == null) {
|
||||
parentObject = Scene.active.sceneParent;
|
||||
}
|
||||
parent = parentObject;
|
||||
parent.children.push(this);
|
||||
if (parentInverse) this.transform.applyParentInverse();
|
||||
}
|
||||
|
||||
/**
|
||||
Add a game Object as a child of this game Object.
|
||||
@param o The game Object instance to be added as a child.
|
||||
@param parentInverse Optional (default false) change the scale of the child object to be relative to the parents 3D space or use the original scale.
|
||||
**/
|
||||
@:deprecated("addChild() is deprecated, please use setParent() instead")
|
||||
public inline function addChild(o: Object, parentInverse = false) {
|
||||
o.setParent(this, parentInverse, false);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove a child game Object from it's parentage. Does not remove the object from the scene.
|
||||
@param o The game Object instance to be removed.
|
||||
@param keepTransform Optional (defaut false) keep the transform given by the parent or revert to the objects default.
|
||||
**/
|
||||
@:deprecated("removeChild() is deprecated, please use setParent(null) instead")
|
||||
public inline function removeChild(o: Object, keepTransform = false) {
|
||||
o.setParent(null, false, keepTransform);
|
||||
}
|
||||
|
||||
/**
|
||||
Removes the game object from the scene.
|
||||
**/
|
||||
public function remove() {
|
||||
if (isEmpty && Scene.active != null) Scene.active.empties.remove(this);
|
||||
if (animation != null) animation.remove();
|
||||
while (children.length > 0) children[0].remove();
|
||||
while (traits.length > 0) traits[0].remove();
|
||||
if (parent != null) {
|
||||
parent.children.remove(this);
|
||||
parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get a child game Object of this game Object. Using the childs name property as a lookup.
|
||||
@param name A string matching the name property of the game Object to fetch.
|
||||
@return Object or null
|
||||
**/
|
||||
public function getChild(name: String): Object {
|
||||
if (this.name == name) return this;
|
||||
else {
|
||||
for (c in children) {
|
||||
var r = c.getChild(name);
|
||||
if (r != null) return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the children of the object.
|
||||
|
||||
If 'recursive' is set to `false`, only direct children will be included
|
||||
in the returned array. If `recursive` is `true`, children of children and
|
||||
so on will be included too.
|
||||
|
||||
@param recursive = false Include children of children
|
||||
@return `Array<Object>`
|
||||
**/
|
||||
public function getChildren(?recursive = false): Array<Object> {
|
||||
if (!recursive) return children;
|
||||
|
||||
var retChildren = children.copy();
|
||||
for (child in children) {
|
||||
retChildren = retChildren.concat(child.getChildren(recursive));
|
||||
}
|
||||
return retChildren;
|
||||
}
|
||||
|
||||
public function getChildOfType<T: Object>(type: Class<T>): T {
|
||||
if (Std.isOfType(this, type)) return cast this;
|
||||
else {
|
||||
for (c in children) {
|
||||
var r = c.getChildOfType(type);
|
||||
if (r != null) return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@:access(iron.Trait)
|
||||
public function addTrait(t: Trait) {
|
||||
traits.push(t);
|
||||
t.object = this;
|
||||
|
||||
if (t._add != null) {
|
||||
for (f in t._add) f();
|
||||
t._add = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the Trait from the Object.
|
||||
@param t The Trait to be removed from the game Object.
|
||||
**/
|
||||
@:access(iron.Trait)
|
||||
public function removeTrait(t: Trait) {
|
||||
if (t._init != null) {
|
||||
for (f in t._init) App.removeInit(f);
|
||||
t._init = null;
|
||||
}
|
||||
if (t._update != null) {
|
||||
for (f in t._update) App.removeUpdate(f);
|
||||
t._update = null;
|
||||
}
|
||||
if (t._lateUpdate != null) {
|
||||
for (f in t._lateUpdate) App.removeLateUpdate(f);
|
||||
t._lateUpdate = null;
|
||||
}
|
||||
if (t._render != null) {
|
||||
for (f in t._render) App.removeRender(f);
|
||||
t._render = null;
|
||||
}
|
||||
if (t._render2D != null) {
|
||||
for (f in t._render2D) App.removeRender2D(f);
|
||||
t._render2D = null;
|
||||
}
|
||||
if (t._remove != null) {
|
||||
for (f in t._remove) f();
|
||||
t._remove = null;
|
||||
}
|
||||
traits.remove(t);
|
||||
}
|
||||
|
||||
/**
|
||||
Get the Trait instance that is attached to this game Object.
|
||||
@param c The class of type Trait to attempt to retrieve.
|
||||
@return Trait or null
|
||||
**/
|
||||
public function getTrait<T: Trait>(c: Class<T>): T {
|
||||
for (t in traits) if (Type.getClass(t) == cast c) return cast t;
|
||||
return null;
|
||||
}
|
||||
|
||||
#if lnx_skin
|
||||
public function getBoneAnimation(armatureUid): BoneAnimation {
|
||||
for (a in Scene.active.animations) if (a.armature != null && a.armature.uid == armatureUid) return cast a;
|
||||
return null;
|
||||
}
|
||||
#else
|
||||
public function getBoneAnimation(armatureUid): Animation {
|
||||
return null;
|
||||
}
|
||||
#end
|
||||
|
||||
public function getObjectAnimation(): ObjectAnimation {
|
||||
if(animation != null) return cast animation;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setupAnimation(oactions: Array<TSceneFormat> = null) {
|
||||
// Parented to bone
|
||||
#if lnx_skin
|
||||
if (raw.parent_bone != null) {
|
||||
Scene.active.notifyOnInit(function() {
|
||||
var banim = getBoneAnimation(parent.uid);
|
||||
if (banim != null) banim.addBoneChild(raw.parent_bone, this);
|
||||
});
|
||||
}
|
||||
#end
|
||||
// Object actions
|
||||
if (oactions == null) return;
|
||||
animation = new ObjectAnimation(this, oactions);
|
||||
}
|
||||
|
||||
#if lnx_morph_target
|
||||
public function setupMorphTargets() {}
|
||||
#end
|
||||
|
||||
static var seed = 1; // cpp / js not consistent
|
||||
static function seededRandom(): Float {
|
||||
seed = (seed * 9301 + 49297) % 233280;
|
||||
return seed / 233280.0;
|
||||
}
|
||||
}
|
217
leenkx/Sources/iron/object/ObjectAnimation.hx
Normal file
217
leenkx/Sources/iron/object/ObjectAnimation.hx
Normal file
@ -0,0 +1,217 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.object.Animation.ActionSampler;
|
||||
|
||||
import kha.arrays.Float32Array;
|
||||
import kha.FastFloat;
|
||||
import kha.arrays.Uint32Array;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.data.SceneFormat;
|
||||
|
||||
class ObjectAnimation extends Animation {
|
||||
|
||||
public var object: Object;
|
||||
public var oactions: Array<TSceneFormat>;
|
||||
var oaction: TObj;
|
||||
var s0: FastFloat = 0.0;
|
||||
var bezierFrameIndex = -1;
|
||||
|
||||
var updateAnimation: Map<String, FastFloat> -> Void;
|
||||
|
||||
public var transformArr: Float32Array;
|
||||
|
||||
public var transformMap: Map<String, FastFloat>;
|
||||
|
||||
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
|
||||
"xrot", "yrot", "zrot",
|
||||
"qwrot", "qxrot", "qyrot", "qzrot",
|
||||
"xscl", "yscl", "zscl",
|
||||
"dxloc", "dyloc", "dzloc",
|
||||
"dxrot", "dyrot", "dzrot",
|
||||
"dqwrot", "dqxrot", "dqyrot", "dqzrot",
|
||||
"dxscl", "dyscl", "dzscl"];
|
||||
|
||||
public function new(object: Object, oactions: Array<TSceneFormat>) {
|
||||
this.object = object;
|
||||
this.oactions = oactions;
|
||||
isSkinned = false;
|
||||
super();
|
||||
}
|
||||
|
||||
function getAction(action: String): TObj {
|
||||
for (a in oactions) if (a != null && a.objects[0].name == action) return a.objects[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
|
||||
super.play(action, onComplete, blendTime, speed, loop);
|
||||
if (this.action == "" && oactions[0] != null) this.action = oactions[0].objects[0].name;
|
||||
oaction = getAction(this.action);
|
||||
if (oaction != null) {
|
||||
isSampled = oaction.sampled != null && oaction.sampled;
|
||||
}
|
||||
}
|
||||
|
||||
override public function update(delta: FastFloat) {
|
||||
if (!object.visible || object.culled) return;
|
||||
|
||||
#if lnx_debug
|
||||
Animation.beginProfile();
|
||||
#end
|
||||
|
||||
if(transformMap == null) transformMap = new Map();
|
||||
transformMap = initTransformMap();
|
||||
|
||||
super.update(delta);
|
||||
if (paused) return;
|
||||
if(updateAnimation == null) return;
|
||||
if (!isSkinned) updateObjectAnimation();
|
||||
|
||||
#if lnx_debug
|
||||
Animation.endProfile();
|
||||
#end
|
||||
}
|
||||
|
||||
public override function getTotalFrames(sampler: ActionSampler): Int {
|
||||
var track = getAction(sampler.action).anim.tracks[0];
|
||||
return Std.int(track.frames[track.frames.length - 1] - track.frames[0]);
|
||||
}
|
||||
|
||||
public function initTransformMap(){
|
||||
|
||||
var map = new Map<String, Null<FastFloat>>();
|
||||
for (name in trackNames){
|
||||
map.set(name, null);
|
||||
}
|
||||
|
||||
return map;
|
||||
|
||||
}
|
||||
|
||||
public function animationLoop(f: Map<String, FastFloat>->Void){
|
||||
|
||||
updateAnimation = f;
|
||||
}
|
||||
|
||||
function updateObjectAnimation() {
|
||||
updateAnimation(transformMap);
|
||||
updateTransform(transformMap, object.transform);
|
||||
object.transform.buildMatrix();
|
||||
}
|
||||
|
||||
override public function updateActionTrack(sampler: ActionSampler) {
|
||||
if(sampler.paused) return;
|
||||
|
||||
if(! sampler.actionDataInit) {
|
||||
var objanim = getAction(sampler.action);
|
||||
sampler.setObjectAction(objanim);
|
||||
}
|
||||
|
||||
oaction = sampler.getObjectAction();
|
||||
updateTrack(oaction.anim, sampler);
|
||||
|
||||
}
|
||||
|
||||
function updateAnimSampled(anim: TAnimation, transformMap: Map<String, FastFloat>, sampler: ActionSampler) {
|
||||
|
||||
for (track in anim.tracks) {
|
||||
var sign = sampler.speed > 0 ? 1 : -1;
|
||||
|
||||
var t = sampler.time;
|
||||
//t = t < 0 ? 0.1 : t;
|
||||
|
||||
var ti = sampler.offset;
|
||||
//ti = ti < 0 ? 1 : ti;
|
||||
|
||||
var t1 = track.frames[ti] * frameTime;
|
||||
var t2 = track.frames[ti + sign] * frameTime;
|
||||
var v1 = track.values[ti];
|
||||
var v2 = track.values[ti + sign];
|
||||
|
||||
var value = interpolateLinear(t, t1, t2, v1, v2);
|
||||
|
||||
if(value == null) continue;
|
||||
|
||||
transformMap.set(track.target, value);
|
||||
}
|
||||
}
|
||||
|
||||
public function sampleAction(sampler: ActionSampler, transformMap: Map<String, FastFloat>){
|
||||
|
||||
if(! sampler.actionDataInit) {
|
||||
var objanim = getAction(sampler.action);
|
||||
sampler.setObjectAction(objanim);
|
||||
}
|
||||
|
||||
var objanim = sampler.getObjectAction();
|
||||
updateAnimSampled(objanim.anim, transformMap, sampler);
|
||||
}
|
||||
|
||||
public function blendActionObject(transformMap1: Map<String, FastFloat>, transformMap2: Map<String, FastFloat>, transformMapRes: Map<String, FastFloat>, factor: FastFloat ) {
|
||||
|
||||
for(track in transformMapRes.keys()){
|
||||
|
||||
var v1 = transformMap1.get(track);
|
||||
var v2 = transformMap2.get(track);
|
||||
|
||||
if(v1 == null || v2 == null) continue;
|
||||
|
||||
var maxVal: FastFloat = 1.0;
|
||||
var tempValue = (maxVal - factor) * v1 + factor * v2;
|
||||
transformMapRes.set(track, tempValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline function interpolateLinear(t: FastFloat, t1: FastFloat, t2: FastFloat, v1: FastFloat, v2: FastFloat): Null<FastFloat> {
|
||||
var s = (t - t1) / (t2 - t1);
|
||||
return (1.0 - s) * v1 + s * v2;
|
||||
}
|
||||
|
||||
@:access(iron.object.Transform)
|
||||
function updateTransform(transformMap: Map<String, FastFloat>, transform: Transform) {
|
||||
|
||||
var t = transform;
|
||||
t.resetDelta();
|
||||
|
||||
for (track in transformMap.keys()){
|
||||
|
||||
var value = transformMap.get(track);
|
||||
|
||||
if(value == null) continue;
|
||||
|
||||
switch (track) {
|
||||
|
||||
case "xloc": transform.loc.x = value;
|
||||
case "yloc": transform.loc.y = value;
|
||||
case "zloc": transform.loc.z = value;
|
||||
case "xrot": transform.setRotation(value, transform._eulerY, transform._eulerZ);
|
||||
case "yrot": transform.setRotation(transform._eulerX, value, transform._eulerZ);
|
||||
case "zrot": transform.setRotation(transform._eulerX, transform._eulerY, value);
|
||||
case "qwrot": transform.rot.w = value;
|
||||
case "qxrot": transform.rot.x = value;
|
||||
case "qyrot": transform.rot.y = value;
|
||||
case "qzrot": transform.rot.z = value;
|
||||
case "xscl": transform.scale.x = value;
|
||||
case "yscl": transform.scale.y = value;
|
||||
case "zscl": transform.scale.z = value;
|
||||
// Delta
|
||||
case "dxloc": transform.dloc.x = value;
|
||||
case "dyloc": transform.dloc.y = value;
|
||||
case "dzloc": transform.dloc.z = value;
|
||||
case "dxrot": transform._deulerX = value;
|
||||
case "dyrot": transform._deulerY = value;
|
||||
case "dzrot": transform._deulerZ = value;
|
||||
case "dqwrot": transform.drot.w = value;
|
||||
case "dqxrot": transform.drot.x = value;
|
||||
case "dqyrot": transform.drot.y = value;
|
||||
case "dqzrot": transform.drot.z = value;
|
||||
case "dxscl": transform.dscale.x = value;
|
||||
case "dyscl": transform.dscale.y = value;
|
||||
case "dzscl": transform.dscale.z = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
249
leenkx/Sources/iron/object/ParticleSystem.hx
Normal file
249
leenkx/Sources/iron/object/ParticleSystem.hx
Normal file
@ -0,0 +1,249 @@
|
||||
package iron.object;
|
||||
|
||||
#if lnx_particles
|
||||
|
||||
import kha.graphics4.Usage;
|
||||
import kha.arrays.Float32Array;
|
||||
import iron.data.Data;
|
||||
import iron.data.ParticleData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.Time;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec3;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class ParticleSystem {
|
||||
public var data: ParticleData;
|
||||
public var speed = 1.0;
|
||||
var particles: Array<Particle>;
|
||||
var ready: Bool;
|
||||
var frameRate = 24;
|
||||
var lifetime = 0.0;
|
||||
var animtime = 0.0;
|
||||
var time = 0.0;
|
||||
var spawnRate = 0.0;
|
||||
var seed = 0;
|
||||
|
||||
var r: TParticleData;
|
||||
var gx: Float;
|
||||
var gy: Float;
|
||||
var gz: Float;
|
||||
var alignx: Float;
|
||||
var aligny: Float;
|
||||
var alignz: Float;
|
||||
var dimx: Float;
|
||||
var dimy: Float;
|
||||
var tilesx: Int;
|
||||
var tilesy: Int;
|
||||
var tilesFramerate: Int;
|
||||
|
||||
var count = 0;
|
||||
var lap = 0;
|
||||
var lapTime = 0.0;
|
||||
var m = Mat4.identity();
|
||||
|
||||
var ownerLoc = new Vec4();
|
||||
var ownerRot = new Quat();
|
||||
var ownerScl = new Vec4();
|
||||
|
||||
public function new(sceneName: String, pref: TParticleReference) {
|
||||
seed = pref.seed;
|
||||
particles = [];
|
||||
ready = false;
|
||||
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
|
||||
data = b;
|
||||
r = data.raw;
|
||||
if (Scene.active.raw.gravity != null) {
|
||||
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
|
||||
gy = Scene.active.raw.gravity[1] * r.weight_gravity;
|
||||
gz = Scene.active.raw.gravity[2] * r.weight_gravity;
|
||||
}
|
||||
else {
|
||||
gx = 0;
|
||||
gy = 0;
|
||||
gz = -9.81 * r.weight_gravity;
|
||||
}
|
||||
alignx = r.object_align_factor[0] / 2;
|
||||
aligny = r.object_align_factor[1] / 2;
|
||||
alignz = r.object_align_factor[2] / 2;
|
||||
lifetime = r.lifetime / frameRate;
|
||||
animtime = (r.frame_end - r.frame_start) / frameRate;
|
||||
spawnRate = ((r.frame_end - r.frame_start) / r.count) / frameRate;
|
||||
for (i in 0...r.count) particles.push(new Particle(i));
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
lifetime = 0;
|
||||
}
|
||||
|
||||
public function resume() {
|
||||
lifetime = r.lifetime / frameRate;
|
||||
}
|
||||
|
||||
public function update(object: MeshObject, owner: MeshObject) {
|
||||
if (!ready || object == null || speed == 0.0) return;
|
||||
|
||||
// Copy owner world transform but discard scale
|
||||
owner.transform.world.decompose(ownerLoc, ownerRot, ownerScl);
|
||||
object.transform.loc = ownerLoc;
|
||||
object.transform.rot = ownerRot;
|
||||
|
||||
// Set particle size per particle system
|
||||
object.transform.scale = new Vec4(r.particle_size, r.particle_size, r.particle_size, 1);
|
||||
|
||||
object.transform.buildMatrix();
|
||||
owner.transform.buildMatrix();
|
||||
object.transform.dim.setFrom(owner.transform.dim);
|
||||
|
||||
dimx = object.transform.dim.x;
|
||||
dimy = object.transform.dim.y;
|
||||
|
||||
if (object.activeTilesheet != null) {
|
||||
tilesx = object.activeTilesheet.raw.tilesx;
|
||||
tilesy = object.activeTilesheet.raw.tilesy;
|
||||
tilesFramerate = object.activeTilesheet.raw.framerate;
|
||||
}
|
||||
|
||||
// Animate
|
||||
time += Time.realDelta * speed;
|
||||
lap = Std.int(time / animtime);
|
||||
lapTime = time - lap * animtime;
|
||||
count = Std.int(lapTime / spawnRate);
|
||||
|
||||
updateGpu(object, owner);
|
||||
}
|
||||
|
||||
public function getData(): Mat4 {
|
||||
var hair = r.type == 1;
|
||||
m._00 = r.loop ? animtime : -animtime;
|
||||
m._01 = hair ? 1 / particles.length : spawnRate;
|
||||
m._02 = hair ? 1 : lifetime;
|
||||
m._03 = particles.length;
|
||||
m._10 = hair ? 0 : alignx;
|
||||
m._11 = hair ? 0 : aligny;
|
||||
m._12 = hair ? 0 : alignz;
|
||||
m._13 = hair ? 0 : r.factor_random;
|
||||
m._20 = hair ? 0 : gx * r.mass;
|
||||
m._21 = hair ? 0 : gy * r.mass;
|
||||
m._22 = hair ? 0 : gz * r.mass;
|
||||
m._23 = hair ? 0 : r.lifetime_random;
|
||||
m._30 = tilesx;
|
||||
m._31 = tilesy;
|
||||
m._32 = 1 / tilesFramerate;
|
||||
m._33 = hair ? 1 : lapTime;
|
||||
return m;
|
||||
}
|
||||
|
||||
function updateGpu(object: MeshObject, owner: MeshObject) {
|
||||
if (!object.data.geom.instanced) setupGeomGpu(object, owner);
|
||||
// GPU particles transform is attached to owner object
|
||||
}
|
||||
|
||||
function setupGeomGpu(object: MeshObject, owner: MeshObject) {
|
||||
var instancedData = new Float32Array(particles.length * 3);
|
||||
var i = 0;
|
||||
|
||||
var normFactor = 1 / 32767; // pa.values are not normalized
|
||||
var scalePosOwner = owner.data.scalePos;
|
||||
var scalePosParticle = object.data.scalePos;
|
||||
var particleSize = r.particle_size;
|
||||
var scaleFactor = new Vec4().setFrom(owner.transform.scale);
|
||||
scaleFactor.mult(scalePosOwner / (particleSize * scalePosParticle));
|
||||
|
||||
switch (r.emit_from) {
|
||||
case 0: // Vert
|
||||
var pa = owner.data.geom.positions;
|
||||
|
||||
for (p in particles) {
|
||||
var j = Std.int(fhash(i) * (pa.values.length / pa.size));
|
||||
instancedData.set(i, pa.values[j * pa.size ] * normFactor * scaleFactor.x); i++;
|
||||
instancedData.set(i, pa.values[j * pa.size + 1] * normFactor * scaleFactor.y); i++;
|
||||
instancedData.set(i, pa.values[j * pa.size + 2] * normFactor * scaleFactor.z); i++;
|
||||
}
|
||||
|
||||
case 1: // Face
|
||||
var positions = owner.data.geom.positions.values;
|
||||
|
||||
for (p in particles) {
|
||||
// Choose random index array (there is one per material) and random face
|
||||
var ia = owner.data.geom.indices[Std.random(owner.data.geom.indices.length)];
|
||||
var faceIndex = Std.random(Std.int(ia.length / 3));
|
||||
|
||||
var i0 = ia[faceIndex * 3 + 0];
|
||||
var i1 = ia[faceIndex * 3 + 1];
|
||||
var i2 = ia[faceIndex * 3 + 2];
|
||||
|
||||
var v0 = new Vec3(positions[i0 * 4], positions[i0 * 4 + 1], positions[i0 * 4 + 2]);
|
||||
var v1 = new Vec3(positions[i1 * 4], positions[i1 * 4 + 1], positions[i1 * 4 + 2]);
|
||||
var v2 = new Vec3(positions[i2 * 4], positions[i2 * 4 + 1], positions[i2 * 4 + 2]);
|
||||
|
||||
var pos = randomPointInTriangle(v0, v1, v2);
|
||||
|
||||
instancedData.set(i, pos.x * normFactor * scaleFactor.x); i++;
|
||||
instancedData.set(i, pos.y * normFactor * scaleFactor.y); i++;
|
||||
instancedData.set(i, pos.z * normFactor * scaleFactor.z); i++;
|
||||
}
|
||||
|
||||
case 2: // Volume
|
||||
var scaleFactorVolume = new Vec4().setFrom(object.transform.dim);
|
||||
scaleFactorVolume.mult(0.5 / (particleSize * scalePosParticle));
|
||||
|
||||
for (p in particles) {
|
||||
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.x); i++;
|
||||
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y); i++;
|
||||
instancedData.set(i, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z); i++;
|
||||
}
|
||||
}
|
||||
object.data.geom.setupInstanced(instancedData, 1, Usage.StaticUsage);
|
||||
}
|
||||
|
||||
function fhash(n: Int): Float {
|
||||
var s = n + 1.0;
|
||||
s *= 9301.0 % s;
|
||||
s = (s * 9301.0 + 49297.0) % 233280.0;
|
||||
return s / 233280.0;
|
||||
}
|
||||
|
||||
public function remove() {}
|
||||
|
||||
/**
|
||||
Generates a random point in the triangle with vertex positions abc.
|
||||
|
||||
Please note that the given position vectors are changed in-place by this
|
||||
function and can be considered garbage afterwards, so make sure to clone
|
||||
them first if needed.
|
||||
**/
|
||||
public static inline function randomPointInTriangle(a: Vec3, b: Vec3, c: Vec3): Vec3 {
|
||||
// Generate a random point in a square where (0, 0) <= (x, y) < (1, 1)
|
||||
var x = Math.random();
|
||||
var y = Math.random();
|
||||
|
||||
if (x + y > 1) {
|
||||
// We're in the upper right triangle in the square, mirror to lower left
|
||||
x = 1 - x;
|
||||
y = 1 - y;
|
||||
}
|
||||
|
||||
// Transform the point to the triangle abc
|
||||
var u = b.sub(a);
|
||||
var v = c.sub(a);
|
||||
return a.add(u.mult(x).add(v.mult(y)));
|
||||
}
|
||||
}
|
||||
|
||||
class Particle {
|
||||
public var i: Int;
|
||||
public var x = 0.0;
|
||||
public var y = 0.0;
|
||||
public var z = 0.0;
|
||||
public var cameraDistance: Float;
|
||||
|
||||
public function new(i: Int) {
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
205
leenkx/Sources/iron/object/ProbeObject.hx
Normal file
205
leenkx/Sources/iron/object/ProbeObject.hx
Normal file
@ -0,0 +1,205 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.graphics4.Graphics;
|
||||
import kha.graphics4.TextureFormat;
|
||||
import kha.graphics4.DepthStencilFormat;
|
||||
import kha.graphics4.CubeMap;
|
||||
import iron.data.ProbeData;
|
||||
import iron.data.CameraData;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
|
||||
class ProbeObject extends Object {
|
||||
|
||||
#if rp_probes
|
||||
|
||||
public var data: ProbeData;
|
||||
public var renderTarget: kha.Image = null;
|
||||
public var camera: CameraObject = null;
|
||||
public var ready = false;
|
||||
|
||||
// Cubemap update
|
||||
public var perFrame = false; // Update probe every frame
|
||||
public var redraw = true; // Update probe next frame
|
||||
|
||||
var m1: Mat4;
|
||||
var m2: Mat4;
|
||||
var proben: Vec4;
|
||||
var probep: Vec4;
|
||||
// static var v = new Vec4();
|
||||
static var p = new Vec4();
|
||||
static var q = new Vec4();
|
||||
|
||||
public function new(data: ProbeData) {
|
||||
super();
|
||||
this.data = data;
|
||||
Scene.active.probes.push(this);
|
||||
iron.App.notifyOnInit(init);
|
||||
}
|
||||
|
||||
public override function remove() {
|
||||
if (Scene.active != null) Scene.active.probes.remove(this);
|
||||
// if (camera != null) camera.remove();
|
||||
super.remove();
|
||||
}
|
||||
|
||||
function init() {
|
||||
probep = transform.world.getLoc();
|
||||
proben = transform.up().normalize();
|
||||
proben.w = -probep.dot(proben);
|
||||
|
||||
if (data.raw.type == "planar") {
|
||||
m1 = Mat4.identity();
|
||||
m2 = Mat4.identity();
|
||||
reflect(m1, proben, probep);
|
||||
reflect(m2, new Vec4(0, 1, 0), probep);
|
||||
|
||||
transform.scale.z = 1.0; // Only take dim.z into account
|
||||
transform.buildMatrix();
|
||||
|
||||
// var aspect = transform.scale.x / transform.scale.y;
|
||||
var aspect = iron.App.w() / iron.App.h(); // TODO
|
||||
var craw: TCameraData = {
|
||||
name: raw.name + "_Camera",
|
||||
near_plane: Scene.active.camera.data.raw.near_plane,
|
||||
far_plane: Scene.active.camera.data.raw.far_plane,
|
||||
fov: Scene.active.camera.data.raw.fov,
|
||||
aspect: aspect
|
||||
};
|
||||
new CameraData(craw, function(cdata: CameraData) {
|
||||
camera = new CameraObject(cdata);
|
||||
camera.renderTarget = kha.Image.createRenderTarget(
|
||||
iron.App.w(), // TODO
|
||||
iron.App.h(),
|
||||
TextureFormat.RGBA32,
|
||||
DepthStencilFormat.NoDepthAndStencil
|
||||
);
|
||||
camera.name = craw.name;
|
||||
camera.setParent(iron.Scene.active.root);
|
||||
// Make target bindable from render path
|
||||
var rt = new RenderPath.RenderTarget(new RenderPath.RenderTargetRaw());
|
||||
rt.raw.name = raw.name;
|
||||
rt.image = camera.renderTarget;
|
||||
RenderPath.active.renderTargets.set(rt.raw.name, rt);
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
else if (data.raw.type == "cubemap") {
|
||||
transform.scale.x *= transform.dim.x;
|
||||
transform.scale.y *= transform.dim.y;
|
||||
transform.scale.z *= transform.dim.z;
|
||||
transform.buildMatrix();
|
||||
|
||||
var craw: TCameraData = {
|
||||
name: data.raw.name + "_Camera",
|
||||
near_plane: Scene.active.camera.data.raw.near_plane,
|
||||
far_plane: Scene.active.camera.data.raw.far_plane,
|
||||
fov: 1.5708, // pi/2
|
||||
aspect: 1.0
|
||||
};
|
||||
new CameraData(craw, function(cdata: CameraData) {
|
||||
camera = new CameraObject(cdata);
|
||||
camera.renderTargetCube = CubeMap.createRenderTarget(
|
||||
1024, // TODO
|
||||
TextureFormat.RGBA32,
|
||||
DepthStencilFormat.NoDepthAndStencil
|
||||
);
|
||||
camera.name = craw.name;
|
||||
camera.setParent(iron.Scene.active.root);
|
||||
// Make target bindable from render path
|
||||
var rt = new RenderPath.RenderTarget(new RenderPath.RenderTargetRaw());
|
||||
rt.raw.name = raw.name;
|
||||
rt.raw.is_cubemap = true;
|
||||
rt.isCubeMap = true;
|
||||
rt.cubeMap = camera.renderTargetCube;
|
||||
RenderPath.active.renderTargets.set(rt.raw.name, rt);
|
||||
ready = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static function reflect(m: Mat4, n: Vec4, p: Vec4) {
|
||||
var c = -p.dot(n);
|
||||
m._00 = 1 - 2 * n.x * n.x;
|
||||
m._10 = - 2 * n.x * n.y;
|
||||
m._20 = - 2 * n.x * n.z;
|
||||
m._30 = - 2 * n.x * c;
|
||||
m._01 = - 2 * n.x * n.y;
|
||||
m._11 = 1 - 2 * n.y * n.y;
|
||||
m._21 = - 2 * n.y * n.z;
|
||||
m._31 = - 2 * n.y * c;
|
||||
m._02 = - 2 * n.x * n.z;
|
||||
m._12 = - 2 * n.y * n.z;
|
||||
m._22 = 1 - 2 * n.z * n.z;
|
||||
m._32 = - 2 * n.z * c;
|
||||
m._03 = 0;
|
||||
m._13 = 0;
|
||||
m._23 = 0;
|
||||
m._33 = 1;
|
||||
}
|
||||
|
||||
static inline function sign(f: Float): Float {
|
||||
return f > 0.0 ? 1.0 : f < 0.0 ? -1.0 : 0.0;
|
||||
}
|
||||
|
||||
static function obliqueProjection(m: Mat4, plane: Vec4) {
|
||||
// http://www.terathon.com/code/oblique.html
|
||||
p.x = (sign(plane.x) + m._20) / m._00;
|
||||
p.y = (sign(plane.y) + m._21) / m._11;
|
||||
p.z = -1.0;
|
||||
p.w = (1.0 + m._22) / m._32;
|
||||
q.setFrom(plane).mult(2.0 / plane.dot(p));
|
||||
m._02 = q.x;
|
||||
m._12 = q.y;
|
||||
m._22 = q.z + 1.0;
|
||||
m._32 = q.w;
|
||||
}
|
||||
|
||||
function cullProbe(camera: CameraObject): Bool {
|
||||
if (camera.data.raw.frustum_culling) {
|
||||
if (!CameraObject.sphereInFrustum(camera.frustumPlanes, transform, 1.0)) {
|
||||
culled = true;
|
||||
return culled;
|
||||
}
|
||||
}
|
||||
culled = false;
|
||||
return culled;
|
||||
}
|
||||
|
||||
public function render(g: Graphics, activeCamera: CameraObject) {
|
||||
if (camera == null || !ready || !RenderPath.active.ready || !visible || cullProbe(activeCamera)) return;
|
||||
|
||||
if (data.raw.type == "planar") {
|
||||
camera.V.setFrom(m1);
|
||||
camera.V.multmat(activeCamera.V);
|
||||
camera.V.multmat(m2);
|
||||
camera.transform.local.getInverse(camera.V);
|
||||
camera.transform.decompose();
|
||||
// Skip objects below the reflection plane
|
||||
// v.setFrom(proben).applyproj(camera.V);
|
||||
// obliqueProjection(#if (lnx_taa) camera.noJitterP #else camera.P #end, v);
|
||||
camera.renderFrame(g);
|
||||
}
|
||||
else if (data.raw.type == "cubemap") {
|
||||
if (perFrame || redraw) {
|
||||
for (i in 0...6) {
|
||||
camera.currentFace = i;
|
||||
#if (!kha_opengl && !kha_webgl)
|
||||
var flip = (i == 2 || i == 3) ? true : false; // Flip +Y, -Y
|
||||
#else
|
||||
var flip = false;
|
||||
#end
|
||||
CameraObject.setCubeFace(camera.V, probep, i, flip);
|
||||
camera.transform.local.getInverse(camera.V);
|
||||
camera.transform.decompose();
|
||||
camera.renderFrame(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redraw = false;
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
108
leenkx/Sources/iron/object/SpeakerObject.hx
Normal file
108
leenkx/Sources/iron/object/SpeakerObject.hx
Normal file
@ -0,0 +1,108 @@
|
||||
package iron.object;
|
||||
|
||||
import kha.FastFloat;
|
||||
import kha.audio1.AudioChannel;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.math.Vec4;
|
||||
import iron.system.Audio;
|
||||
|
||||
class SpeakerObject extends Object {
|
||||
|
||||
#if lnx_audio
|
||||
|
||||
public var data: TSpeakerData;
|
||||
public var paused(default, null) = false;
|
||||
public var sound(default, null): kha.Sound = null;
|
||||
public var channels(default, null): Array<AudioChannel> = [];
|
||||
public var volume(default, null) : FastFloat;
|
||||
|
||||
public function new(data: TSpeakerData) {
|
||||
super();
|
||||
|
||||
this.data = data;
|
||||
|
||||
Scene.active.speakers.push(this);
|
||||
|
||||
if (data.sound == "") return;
|
||||
|
||||
Data.getSound(data.sound, function(sound: kha.Sound) {
|
||||
this.sound = sound;
|
||||
App.notifyOnInit(init);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (visible && data.play_on_start) play();
|
||||
}
|
||||
|
||||
public function play() {
|
||||
if (sound == null || data.muted) return;
|
||||
if (paused) {
|
||||
for (c in channels) c.play();
|
||||
paused = false;
|
||||
return;
|
||||
}
|
||||
var channel = Audio.play(sound, data.loop, data.stream);
|
||||
if (channel != null) {
|
||||
channels.push(channel);
|
||||
if (data.attenuation > 0 && channels.length == 1) App.notifyOnUpdate(update);
|
||||
}
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
for (c in channels) c.pause();
|
||||
paused = true;
|
||||
}
|
||||
|
||||
public function stop() {
|
||||
for (c in channels) c.stop();
|
||||
channels.splice(0, channels.length);
|
||||
}
|
||||
|
||||
public function setSound(sound: String) {
|
||||
if (sound == null) return;
|
||||
|
||||
data.sound = sound;
|
||||
|
||||
Data.getSound(sound, function(sound: kha.Sound) {
|
||||
this.sound = sound;
|
||||
});
|
||||
}
|
||||
|
||||
public function setVolume(volume: FastFloat) {
|
||||
data.volume = volume;
|
||||
}
|
||||
function update() {
|
||||
if (paused) return;
|
||||
for (c in channels) if (c.finished) channels.remove(c);
|
||||
if (channels.length == 0) {
|
||||
App.removeUpdate(update);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.attenuation > 0) {
|
||||
var distance = Vec4.distance(Scene.active.camera.transform.world.getLoc(), transform.world.getLoc());
|
||||
distance = Math.max(Math.min(data.distance_max, distance), data.distance_reference);
|
||||
volume = data.distance_reference / (data.distance_reference + data.attenuation * (distance - data.distance_reference));
|
||||
volume *= data.volume;
|
||||
}
|
||||
else {
|
||||
volume = data.volume;
|
||||
}
|
||||
|
||||
if (volume > data.volume_max) volume = data.volume_max;
|
||||
else if (volume < data.volume_min) volume = data.volume_min;
|
||||
|
||||
for (c in channels) c.volume = volume;
|
||||
}
|
||||
|
||||
public override function remove() {
|
||||
stop();
|
||||
if (Scene.active != null) Scene.active.speakers.remove(this);
|
||||
super.remove();
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
}
|
117
leenkx/Sources/iron/object/Tilesheet.hx
Normal file
117
leenkx/Sources/iron/object/Tilesheet.hx
Normal file
@ -0,0 +1,117 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.Scene;
|
||||
import iron.data.Data;
|
||||
import iron.data.SceneFormat;
|
||||
import iron.system.Time;
|
||||
|
||||
@:allow(iron.Scene)
|
||||
class Tilesheet {
|
||||
|
||||
public var tileX = 0.0; // Tile offset on tilesheet texture 0-1
|
||||
public var tileY = 0.0;
|
||||
|
||||
public var raw: TTilesheetData;
|
||||
public var action: TTilesheetAction = null;
|
||||
var ready: Bool;
|
||||
|
||||
public var paused = false;
|
||||
public var frame = 0;
|
||||
var time = 0.0;
|
||||
var onActionComplete: Void->Void = null;
|
||||
|
||||
public function new(sceneName: String, tilesheet_ref: String, tilesheet_action_ref: String) {
|
||||
ready = false;
|
||||
Data.getSceneRaw(sceneName, function(format: TSceneFormat) {
|
||||
for (ts in format.tilesheet_datas) {
|
||||
if (ts.name == tilesheet_ref) {
|
||||
raw = ts;
|
||||
Scene.active.tilesheets.push(this);
|
||||
play(tilesheet_action_ref);
|
||||
ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function play(action_ref: String, onActionComplete: Void->Void = null) {
|
||||
this.onActionComplete = onActionComplete;
|
||||
for (a in raw.actions) {
|
||||
if (a.name == action_ref) {
|
||||
action = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setFrame(action.start);
|
||||
paused = false;
|
||||
time = 0.0;
|
||||
}
|
||||
|
||||
public function pause() {
|
||||
paused = true;
|
||||
}
|
||||
|
||||
public function resume() {
|
||||
paused = false;
|
||||
}
|
||||
|
||||
public function remove() {
|
||||
Scene.active.tilesheets.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frame of the current active tilesheet action. Automatically un-pauses action.
|
||||
* @param frame Frame offset with 0 as the first frame of the active action.
|
||||
**/
|
||||
public function setFrameOffset(frame: Int) {
|
||||
setFrame(action.start + frame);
|
||||
paused = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current frame.
|
||||
* @return Frame offset with 0 as the first frame of the active action.
|
||||
*/
|
||||
public function getFrameOffset(): Int {
|
||||
return frame - action.start;
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (!ready || paused || action.start >= action.end) return;
|
||||
|
||||
time += Time.realDelta;
|
||||
|
||||
var frameTime = 1 / raw.framerate;
|
||||
var framesToAdvance = 0;
|
||||
|
||||
// Check how many animation frames passed during the last render frame
|
||||
// and catch up if required. The remaining `time` that couldn't fit in
|
||||
// another animation frame will be used in the next `update()`.
|
||||
while (time >= frameTime) {
|
||||
time -= frameTime;
|
||||
framesToAdvance++;
|
||||
}
|
||||
|
||||
if (framesToAdvance != 0) {
|
||||
setFrame(frame + framesToAdvance);
|
||||
}
|
||||
}
|
||||
|
||||
function setFrame(f: Int) {
|
||||
frame = f;
|
||||
|
||||
// Action end
|
||||
if (frame > action.end && action.start < action.end) {
|
||||
if (onActionComplete != null) onActionComplete();
|
||||
if (action.loop) setFrame(action.start);
|
||||
else paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var tx = frame % raw.tilesx;
|
||||
var ty = Std.int(frame / raw.tilesx);
|
||||
tileX = tx * (1 / raw.tilesx);
|
||||
tileY = ty * (1 / raw.tilesy);
|
||||
}
|
||||
}
|
364
leenkx/Sources/iron/object/Transform.hx
Normal file
364
leenkx/Sources/iron/object/Transform.hx
Normal file
@ -0,0 +1,364 @@
|
||||
package iron.object;
|
||||
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
|
||||
class Transform {
|
||||
/**
|
||||
The world matrix (read-only).
|
||||
**/
|
||||
public var world: Mat4;
|
||||
/**
|
||||
Prevent applying parent matrix.
|
||||
**/
|
||||
public var localOnly = false;
|
||||
/**
|
||||
The local matrix. If you modify this, call `decompose()` to update the
|
||||
`loc`, `rot` and `scale` fields, or `buildMatrix()` to update
|
||||
everything.
|
||||
**/
|
||||
public var local: Mat4;
|
||||
/**
|
||||
The local translation. Changes to this field should be applied by
|
||||
calling `buildMatrix()`.
|
||||
**/
|
||||
public var loc: Vec4;
|
||||
/**
|
||||
The local rotation. Changes to this field should be applied by
|
||||
calling `buildMatrix()`.
|
||||
**/
|
||||
public var rot: Quat;
|
||||
/**
|
||||
The local scale. Changes to this field should be applied by
|
||||
calling `buildMatrix()`.
|
||||
**/
|
||||
public var scale: Vec4;
|
||||
/**
|
||||
Uniform scale factor for `world` matrix.
|
||||
**/
|
||||
public var scaleWorld: kha.FastFloat = 1.0;
|
||||
/**
|
||||
The world matrix with `scaleWorld` applied (read-only).
|
||||
**/
|
||||
public var worldUnpack: Mat4;
|
||||
/**
|
||||
Flag to rebuild the `world` matrix on next update.
|
||||
**/
|
||||
public var dirty: Bool;
|
||||
/**
|
||||
The object that is effected by this transform.
|
||||
**/
|
||||
public var object: Object;
|
||||
/**
|
||||
The dimensions of the object in local space (without parent, prepended
|
||||
or appended matrices applied).
|
||||
**/
|
||||
public var dim: Vec4;
|
||||
/**
|
||||
The radius of the smallest sphere that encompasses the object in local
|
||||
space.
|
||||
**/
|
||||
public var radius: kha.FastFloat;
|
||||
|
||||
static var temp = Mat4.identity();
|
||||
static var q = new Quat();
|
||||
|
||||
var boneParent: Mat4 = null;
|
||||
var lastWorld: Mat4 = null;
|
||||
|
||||
// Wrong order returned from getEuler(), store last state for animation
|
||||
var _eulerX: kha.FastFloat;
|
||||
var _eulerY: kha.FastFloat;
|
||||
var _eulerZ: kha.FastFloat;
|
||||
|
||||
// Animated delta transform
|
||||
var dloc: Vec4 = null;
|
||||
var drot: Quat = null;
|
||||
var dscale: Vec4 = null;
|
||||
var _deulerX: kha.FastFloat;
|
||||
var _deulerY: kha.FastFloat;
|
||||
var _deulerZ: kha.FastFloat;
|
||||
|
||||
public function new(object: Object) {
|
||||
this.object = object;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
Reset to a null transform: zero location and rotation, and a uniform
|
||||
scale of one. Other fields such as prepended matrices and bone parents
|
||||
will not be changed.
|
||||
**/
|
||||
public function reset() {
|
||||
world = Mat4.identity();
|
||||
worldUnpack = Mat4.identity();
|
||||
local = Mat4.identity();
|
||||
loc = new Vec4();
|
||||
rot = new Quat();
|
||||
scale = new Vec4(1.0, 1.0, 1.0);
|
||||
dim = new Vec4(2.0, 2.0, 2.0);
|
||||
radius = 1.0;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Rebuild the matrices, if needed.
|
||||
**/
|
||||
public function update() {
|
||||
if (dirty) buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Clear delta transforms. `dloc`, `drot` and `dscale` are set to `null`
|
||||
**/
|
||||
public function clearDelta() {
|
||||
|
||||
dloc = null;
|
||||
drot = null;
|
||||
dscale = null;
|
||||
}
|
||||
|
||||
/**
|
||||
Reset delta transforms. `dloc`, `drot` and `dscale`
|
||||
are set to `Vec4(0, 0, 0)`, `Quat(0, 0, 0, 0)` and `Vec4(1, 1, 1)` respectively
|
||||
**/
|
||||
public function resetDelta() {
|
||||
dloc = new Vec4();
|
||||
drot = new Quat();
|
||||
_deulerX = _deulerY = _deulerZ = 0.0;
|
||||
dscale = new Vec4().set(1, 1, 1);
|
||||
}
|
||||
|
||||
function composeDelta() {
|
||||
// Delta transform
|
||||
var dl = new Vec4().addvecs(loc, dloc);
|
||||
var ds = new Vec4().setFrom(scale);
|
||||
ds.x *= dscale.x;
|
||||
ds.y *= dscale.y;
|
||||
ds.z *= dscale.z;
|
||||
var dr = new Quat().fromEuler(_deulerX, _deulerY, _deulerZ);
|
||||
dr.multquats(dr, rot);
|
||||
dr.multquats(drot, dr);
|
||||
local.compose(dl, dr, ds);
|
||||
}
|
||||
|
||||
/**
|
||||
Update the transform matrix based on `loc`, `rot`, and `scale`. If any
|
||||
change is made to `loc`, `rot`, or `scale` `buildMatrix()` must be
|
||||
called to update the objects transform.
|
||||
**/
|
||||
public function buildMatrix() {
|
||||
dloc == null ? local.compose(loc, rot, scale) : composeDelta();
|
||||
|
||||
if (boneParent != null) local.multmats(boneParent, local);
|
||||
|
||||
if (object.parent != null && !localOnly) {
|
||||
world.multmats3x4(local, object.parent.transform.world);
|
||||
}
|
||||
else {
|
||||
world.setFrom(local);
|
||||
}
|
||||
|
||||
worldUnpack.setFrom(world);
|
||||
if (scaleWorld != 1.0) {
|
||||
worldUnpack._00 *= scaleWorld;
|
||||
worldUnpack._01 *= scaleWorld;
|
||||
worldUnpack._02 *= scaleWorld;
|
||||
worldUnpack._03 *= scaleWorld;
|
||||
worldUnpack._10 *= scaleWorld;
|
||||
worldUnpack._11 *= scaleWorld;
|
||||
worldUnpack._12 *= scaleWorld;
|
||||
worldUnpack._13 *= scaleWorld;
|
||||
worldUnpack._20 *= scaleWorld;
|
||||
worldUnpack._21 *= scaleWorld;
|
||||
worldUnpack._22 *= scaleWorld;
|
||||
worldUnpack._23 *= scaleWorld;
|
||||
}
|
||||
|
||||
// Constraints
|
||||
if (object.constraints != null) for (c in object.constraints) c.apply(this);
|
||||
|
||||
computeDim();
|
||||
|
||||
// Update children
|
||||
for (n in object.children) {
|
||||
n.transform.buildMatrix();
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
Move the game Object by the defined amount relative to its current location.
|
||||
@param x Amount to move on the local x axis.
|
||||
@param y Amount to move on the local y axis.
|
||||
@param z Amount to move on the local z axis.
|
||||
**/
|
||||
public function translate(x: kha.FastFloat, y: kha.FastFloat, z: kha.FastFloat) {
|
||||
loc.x += x;
|
||||
loc.y += y;
|
||||
loc.z += z;
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Set the local matrix and update `loc`, `rot`, `scale` and `world`.
|
||||
@param mat The new local matrix.
|
||||
**/
|
||||
public function setMatrix(mat: Mat4) {
|
||||
local.setFrom(mat);
|
||||
decompose();
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Apply another transform to this one, i.e. multiply this transform's
|
||||
local matrix by another.
|
||||
@param mat The other transform to apply.
|
||||
**/
|
||||
public function multMatrix(mat: Mat4) {
|
||||
local.multmat(mat);
|
||||
decompose();
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Update the `loc`, `rot` and `scale` fields according to the local
|
||||
matrix. You may need to call this after directly mutating the local
|
||||
matrix.
|
||||
**/
|
||||
public function decompose() {
|
||||
local.decompose(loc, rot, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
Rotate around an axis.
|
||||
@param axis The axis to rotate around.
|
||||
@param f The magnitude of the rotation in radians.
|
||||
**/
|
||||
public function rotate(axis: Vec4, f: kha.FastFloat) {
|
||||
q.fromAxisAngle(axis, f);
|
||||
rot.multquats(q, rot);
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Apply a scaled translation in local space.
|
||||
@param axis The direction to move.
|
||||
@param f A multiplier for the movement. If `axis` is a unit
|
||||
vector, then this is the distance to move.
|
||||
**/
|
||||
public function move(axis: Vec4, f = 1.0) {
|
||||
loc.addf(axis.x * f, axis.y * f, axis.z * f);
|
||||
buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Set the rotation of the object in radians.
|
||||
@param x Set the x axis rotation in radians.
|
||||
@param y Set the y axis rotation in radians.
|
||||
@param z Set the z axis rotation in radians.
|
||||
**/
|
||||
public function setRotation(x: kha.FastFloat, y: kha.FastFloat, z: kha.FastFloat) {
|
||||
rot.fromEuler(x, y, z);
|
||||
_eulerX = x;
|
||||
_eulerY = y;
|
||||
_eulerZ = z;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
function computeRadius() {
|
||||
radius = Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
|
||||
}
|
||||
|
||||
function computeDim() {
|
||||
if (object.raw == null) {
|
||||
computeRadius();
|
||||
return;
|
||||
}
|
||||
var d = object.raw.dimensions;
|
||||
if (d == null) dim.set(2 * scale.x, 2 * scale.y, 2 * scale.z);
|
||||
else dim.set(d[0] * scale.x, d[1] * scale.y, d[2] * scale.z);
|
||||
computeRadius();
|
||||
}
|
||||
|
||||
public function applyParentInverse() {
|
||||
var pt = object.parent.transform;
|
||||
pt.buildMatrix();
|
||||
temp.getInverse(pt.world);
|
||||
this.local.multmat(temp);
|
||||
this.decompose();
|
||||
this.buildMatrix();
|
||||
}
|
||||
|
||||
public function applyParent() {
|
||||
var pt = object.parent.transform;
|
||||
pt.buildMatrix();
|
||||
this.local.multmat(pt.world);
|
||||
this.decompose();
|
||||
this.buildMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
Check whether the transform has changed at all since the last time
|
||||
this function was called.
|
||||
@return `true` if the transform has changed.
|
||||
**/
|
||||
public function diff(): Bool {
|
||||
if (lastWorld == null) {
|
||||
lastWorld = Mat4.identity().setFrom(world);
|
||||
return false;
|
||||
}
|
||||
var a = world;
|
||||
var b = lastWorld;
|
||||
var r = a._00 != b._00 || a._01 != b._01 || a._02 != b._02 || a._03 != b._03 ||
|
||||
a._10 != b._10 || a._11 != b._11 || a._12 != b._12 || a._13 != b._13 ||
|
||||
a._20 != b._20 || a._21 != b._21 || a._22 != b._22 || a._23 != b._23 ||
|
||||
a._30 != b._30 || a._31 != b._31 || a._32 != b._32 || a._33 != b._33;
|
||||
if (r) lastWorld.setFrom(world);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
@return The look vector (positive local y axis) in world space.
|
||||
**/
|
||||
public inline function look(): Vec4 {
|
||||
return world.look();
|
||||
}
|
||||
|
||||
/**
|
||||
@return The right vector (positive local x axis) in world space.
|
||||
**/
|
||||
public inline function right(): Vec4 {
|
||||
return world.right();
|
||||
}
|
||||
|
||||
/**
|
||||
@return The up vector (positive local z axis) in world space.
|
||||
**/
|
||||
public inline function up(): Vec4 {
|
||||
return world.up();
|
||||
}
|
||||
|
||||
/**
|
||||
@return The world x location.
|
||||
**/
|
||||
public inline function worldx(): kha.FastFloat {
|
||||
return world._30;
|
||||
}
|
||||
|
||||
/**
|
||||
@return The world y location.
|
||||
**/
|
||||
public inline function worldy(): kha.FastFloat {
|
||||
return world._31;
|
||||
}
|
||||
|
||||
/**
|
||||
@return The world z location.
|
||||
**/
|
||||
public inline function worldz(): kha.FastFloat {
|
||||
return world._32;
|
||||
}
|
||||
}
|
1297
leenkx/Sources/iron/object/Uniforms.hx
Normal file
1297
leenkx/Sources/iron/object/Uniforms.hx
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user