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 = 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 = 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) { // 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, 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; } }