package armory.renderpath; import iron.RenderPath; import iron.object.LightObject; class Inc { static var path: RenderPath; public static var superSample = 1.0; static var pointIndex = 0; static var spotIndex = 0; static var lastFrame = -1; #if (rp_voxelao && arm_config) static var voxelsCreated = false; #end public static function init(_path: RenderPath) { path = _path; #if arm_config var config = armory.data.Config.raw; for (l in iron.Scene.active.lights) { l.data.raw.shadowmap_size = l.data.raw.type == "sun" ? config.rp_shadowmap_cascade : config.rp_shadowmap_cube; } superSample = config.rp_supersample; #else #if (rp_supersampling == 1.5) superSample = 1.5; #elseif (rp_supersampling == 2) superSample = 2.0; #elseif (rp_supersampling == 4) superSample = 4.0; #end #end } #if arm_shadowmap_atlas public static function updatePointLightAtlasData(): Void { var atlas = ShadowMapAtlas.shadowMapAtlases.get(ShadowMapAtlas.shadowMapAtlasName("point")); if (atlas != null) { if(LightObject.pointLightsData == null) { LightObject.pointLightsData = new kha.arrays.Float32Array( LightObject.maxLightsCluster * ShadowMapTile.tilesLightType("point") * 4 ); // max possible visible lights * 6 or 2 (faces) * 4 (xyzw) } var n = iron.Scene.active.lights.length > LightObject.maxLightsCluster ? LightObject.maxLightsCluster : iron.Scene.active.lights.length; var i = 0; var j = 0; for (light in iron.Scene.active.lights) { if (i >= n) break; if (LightObject.discardLightCulled(light)) continue; if (light.data.raw.type == "point") { if (!light.data.raw.cast_shadow) { j += 4 * 6; continue; } for(k in 0...6) { LightObject.pointLightsData[j ] = light.tileOffsetX[k]; // posx LightObject.pointLightsData[j + 1] = light.tileOffsetY[k]; // posy LightObject.pointLightsData[j + 2] = light.tileScale[k]; // tile scale factor relative to atlas LightObject.pointLightsData[j + 3] = 0; // padding j += 4; } } i++; } } } public static function bindShadowMapAtlas() { for (atlas in ShadowMapAtlas.shadowMapAtlases) { path.bindTarget(atlas.target, atlas.target); } } static function getShadowMapAtlas(atlas:ShadowMapAtlas):String { inline function createDepthTarget(name: String, size: Int) { var t = new RenderTargetRaw(); t.name = name; t.width = t.height = size; t.format = "DEPTH16"; return path.createRenderTarget(t); } var rt = path.renderTargets.get(atlas.target); // Create shadowmap atlas texture on the fly and replace existing on size change if (rt == null) { rt = createDepthTarget(atlas.target, atlas.sizew); } else if (atlas.updateRenderTarget) { atlas.updateRenderTarget = false; // Resize shadow map rt.unload(); rt = createDepthTarget(atlas.target, atlas.sizew); } return atlas.target; } public static function drawShadowMapAtlas() { #if rp_shadowmap #if rp_probes // Share shadow map with probe if (lastFrame == RenderPath.active.frame) return; lastFrame = RenderPath.active.frame; #end // add new lights to the atlases #if arm_debug beginShadowsLogicProfile(); // reset data on rejected lights for (atlas in ShadowMapAtlas.shadowMapAtlases) { atlas.rejectedLights = []; } #end for (light in iron.Scene.active.lights) { if (!light.lightInAtlas && !light.culledLight && light.visible && light.shadowMapScale > 0.0 && light.data.raw.strength > 0.0 && light.data.raw.cast_shadow) { ShadowMapAtlas.addLight(light); } } // update point light data before rendering updatePointLightAtlasData(); for (atlas in ShadowMapAtlas.shadowMapAtlases) { var tilesToRemove = []; #if arm_shadowmap_atlas_lod var tilesToChangeSize = []; #end var shadowmap = getShadowMapAtlas(atlas); path.setTargetStream(shadowmap); path.clearTarget(null, 1.0); for (tile in atlas.activeTiles) { if (tile.light == null || !tile.light.visible || tile.light.culledLight || !tile.light.data.raw.cast_shadow || tile.light.data.raw.strength == 0) { tile.unlockLight = true; tilesToRemove.push(tile); continue; } #if arm_shadowmap_atlas_lod var newTileSize = atlas.getTileSize(tile.light.shadowMapScale); if (newTileSize != tile.size) { if (newTileSize == 0) { tile.unlockLight = true; tilesToRemove.push(tile); continue; } // queue for size change tile.newTileSize = newTileSize; tilesToChangeSize.push(tile); } #end // set the tile offset for this tile and every linked tile to this one var j = 0; tile.forEachTileLinked(function (lTile) { tile.light.tileOffsetX[j] = lTile.coordsX / atlas.sizew; tile.light.tileOffsetY[j] = lTile.coordsY / atlas.sizew; tile.light.tileScale[j] = lTile.size / atlas.sizew; j++; }); // set shadowmap size for uniform tile.light.data.raw.shadowmap_size = atlas.sizew; path.light = tile.light; var face = 0; var faces = ShadowMapTile.tilesLightType(tile.light.data.raw.type); #if arm_debug beginShadowsRenderProfile(); #end tile.forEachTileLinked(function (lTile) { if (faces > 1) { #if arm_csm switch (tile.light.data.raw.type) { case "sun": tile.light.setCascade(iron.Scene.active.camera, face); case "point": path.currentFace = face; } #else path.currentFace = face; #end face++; } path.setCurrentViewportWithOffset(lTile.size, lTile.size, lTile.coordsX, lTile.coordsY); path.drawMeshesStream("shadowmap"); }); #if arm_debug endShadowsRenderProfile(); #end path.currentFace = -1; } path.endStream(); #if arm_shadowmap_atlas_lod for (tile in tilesToChangeSize) { tilesToRemove.push(tile); var newTile = ShadowMapTile.assignTiles(tile.light, atlas, tile); if (newTile != null) atlas.activeTiles.push(newTile); } // update point light data after changing size of tiles to avoid render issues updatePointLightAtlasData(); #end for (tile in tilesToRemove) { atlas.activeTiles.remove(tile); tile.freeTile(); } } #if arm_debug endShadowsLogicProfile(); #end #end // rp_shadowmap } #else public static function bindShadowMap() { for (l in iron.Scene.active.lights) { if (!l.visible || l.data.raw.type != "sun") continue; var n = "shadowMap"; path.bindTarget(n, n); break; } for (i in 0...pointIndex) { var n = "shadowMapPoint[" + i + "]"; path.bindTarget(n, n); } for (i in 0...spotIndex) { var n = "shadowMapSpot[" + i + "]"; path.bindTarget(n, n); } } static function shadowMapName(light: LightObject): String { switch (light.data.raw.type) { case "sun": return "shadowMap"; case "point": return "shadowMapPoint[" + pointIndex + "]"; default: return "shadowMapSpot[" + spotIndex + "]"; } } static function getShadowMap(l: iron.object.LightObject): String { var target = shadowMapName(l); var rt = path.renderTargets.get(target); // Create shadowmap on the fly if (rt == null) { if (path.light.data.raw.shadowmap_cube) { // Cubemap size var size = path.light.data.raw.shadowmap_size; var t = new RenderTargetRaw(); t.name = target; t.width = size; t.height = size; t.format = "DEPTH16"; t.is_cubemap = true; rt = path.createRenderTarget(t); } else { // Non-cube sm var sizew = path.light.data.raw.shadowmap_size; var sizeh = sizew; #if arm_csm // Cascades - atlas on x axis if (l.data.raw.type == "sun") { sizew = sizew * iron.object.LightObject.cascadeCount; } #end var t = new RenderTargetRaw(); t.name = target; t.width = sizew; t.height = sizeh; t.format = "DEPTH16"; rt = path.createRenderTarget(t); } } return target; } public static function drawShadowMap() { #if (rp_shadowmap) #if rp_probes // Share shadow map with probe if (lastFrame == RenderPath.active.frame) return; lastFrame = RenderPath.active.frame; #end pointIndex = 0; spotIndex = 0; for (l in iron.Scene.active.lights) { if (!l.visible) continue; path.light = l; var shadowmap = Inc.getShadowMap(l); var faces = l.data.raw.shadowmap_cube ? 6 : 1; for (i in 0...faces) { if (faces > 1) path.currentFace = i; path.setTarget(shadowmap); path.clearTarget(null, 1.0); if (l.data.raw.cast_shadow) { path.drawMeshes("shadowmap"); } } path.currentFace = -1; if (l.data.raw.type == "point") pointIndex++; else if (l.data.raw.type == "spot" || l.data.raw.type == "area") spotIndex++; } #end // rp_shadowmap } #end public static function applyConfig() { #if arm_config var config = armory.data.Config.raw; // Resize shadow map var l = path.light; if (l.data.raw.type == "sun" && l.data.raw.shadowmap_size != config.rp_shadowmap_cascade) { l.data.raw.shadowmap_size = config.rp_shadowmap_cascade; var rt = path.renderTargets.get("shadowMap"); if (rt != null) { rt.unload(); path.renderTargets.remove("shadowMap"); } } else if (l.data.raw.shadowmap_size != config.rp_shadowmap_cube) { l.data.raw.shadowmap_size = config.rp_shadowmap_cube; var rt = path.renderTargets.get("shadowMapCube"); if (rt != null) { rt.unload(); path.renderTargets.remove("shadowMapCube"); } } if (superSample != config.rp_supersample) { superSample = config.rp_supersample; for (rt in path.renderTargets) { if (rt.raw.width == 0 && rt.raw.scale != null) { rt.raw.scale = getSuperSampling(); } } path.resize(); } // Init voxels #if rp_voxelao if (!voxelsCreated) initGI(); #end #end // arm_config } #if (rp_translucency) public static function initTranslucency() { path.createDepthBuffer("main", "DEPTH24"); var t = new RenderTargetRaw(); t.name = "accum"; t.width = 0; t.height = 0; t.displayp = getDisplayp(); t.format = "RGBA64"; t.scale = getSuperSampling(); t.depth_buffer = "main"; path.createRenderTarget(t); var t = new RenderTargetRaw(); t.name = "revealage"; t.width = 0; t.height = 0; t.displayp = getDisplayp(); t.format = "R16"; t.scale = getSuperSampling(); t.depth_buffer = "main"; path.createRenderTarget(t); path.loadShader("shader_datas/translucent_resolve/translucent_resolve"); } public static function drawTranslucency(target: String) { path.setTarget("accum"); path.clearTarget(0xff000000); path.setTarget("revealage"); path.clearTarget(0xffffffff); path.setTarget("accum", ["revealage"]); #if rp_shadowmap { #if arm_shadowmap_atlas bindShadowMapAtlas(); #else bindShadowMap(); #end } #end path.drawMeshes("translucent"); #if rp_render_to_texture { path.setTarget(target); } #else { path.setTarget(""); } #end path.bindTarget("accum", "gbuffer0"); path.bindTarget("revealage", "gbuffer1"); path.drawShader("shader_datas/translucent_resolve/translucent_resolve"); } #end #if rp_voxelao public static function initGI(tname = "voxels") { #if arm_config var config = armory.data.Config.raw; if (config.rp_gi != true || voxelsCreated) return; voxelsCreated = true; #end var t = new RenderTargetRaw(); t.name = tname; t.format = "R8"; var res = getVoxelRes(); var resZ = getVoxelResZ(); t.width = res; t.height = res; t.depth = Std.int(res * resZ); t.is_image = true; t.mipmaps = true; path.createRenderTarget(t); #if arm_voxelgi_temporal { var tB = new RenderTargetRaw(); tB.name = t.name + "B"; tB.format = t.format; tB.width = t.width; tB.height = t.height; tB.depth = t.depth; tB.is_image = t.is_image; tB.mipmaps = t.mipmaps; path.createRenderTarget(tB); } #end } #end public static inline function getCubeSize(): Int { #if (rp_shadowmap_cube == 256) return 256; #elseif (rp_shadowmap_cube == 512) return 512; #elseif (rp_shadowmap_cube == 1024) return 1024; #elseif (rp_shadowmap_cube == 2048) return 2048; #elseif (rp_shadowmap_cube == 4096) return 4096; #else return 0; #end } public static inline function getCascadeSize(): Int { #if (rp_shadowmap_cascade == 256) return 256; #elseif (rp_shadowmap_cascade == 512) return 512; #elseif (rp_shadowmap_cascade == 1024) return 1024; #elseif (rp_shadowmap_cascade == 2048) return 2048; #elseif (rp_shadowmap_cascade == 4096) return 4096; #elseif (rp_shadowmap_cascade == 8192) return 8192; #elseif (rp_shadowmap_cascade == 16384) return 16384; #else return 0; #end } public static inline function getVoxelRes(): Int { #if (rp_voxelgi_resolution == 512) return 512; #elseif (rp_voxelgi_resolution == 256) return 256; #elseif (rp_voxelgi_resolution == 128) return 128; #elseif (rp_voxelgi_resolution == 64) return 64; #elseif (rp_voxelgi_resolution == 32) return 32; #else return 0; #end } public static inline function getVoxelResZ(): Float { #if (rp_voxelgi_resolution_z == 1.0) return 1.0; #elseif (rp_voxelgi_resolution_z == 0.5) return 0.5; #elseif (rp_voxelgi_resolution_z == 0.25) return 0.25; #else return 0.0; #end } public static inline function getSuperSampling(): Float { return superSample; } public static inline function getHdrFormat(): String { #if rp_hdr return "RGBA64"; #else return "RGBA32"; #end } public static inline function getDisplayp(): Null { #if rp_resolution_filter // Custom resolution set return Main.resolutionSize; #else return null; #end } #if arm_debug public static var shadowsLogicTime = 0.0; public static var shadowsRenderTime = 0.0; static var startShadowsLogicTime = 0.0; static var startShadowsRenderTime = 0.0; static var callBackSetup = false; static function setupEndFrameCallback() { if (!callBackSetup) { callBackSetup = true; iron.App.endFrameCallbacks.push(endFrame); } } static function beginShadowsLogicProfile() { setupEndFrameCallback(); startShadowsLogicTime = kha.Scheduler.realTime(); } static function beginShadowsRenderProfile() { startShadowsRenderTime = kha.Scheduler.realTime(); } static function endShadowsLogicProfile() { shadowsLogicTime += kha.Scheduler.realTime() - startShadowsLogicTime - shadowsRenderTime; } static function endShadowsRenderProfile() { shadowsRenderTime += kha.Scheduler.realTime() - startShadowsRenderTime; } public static function endFrame() { shadowsLogicTime = 0; shadowsRenderTime = 0; } #end } #if arm_shadowmap_atlas class ShadowMapAtlas { public var target: String; public var baseTileSizeConst: Int; public var maxAtlasSizeConst: Int; public var sizew: Int; public var sizeh: Int; public var currTileOffset = 0; public var tiles: Array = []; public var activeTiles: Array = []; public var depth = 1; #if arm_shadowmap_atlas_lod var tileSizes: Array = []; var tileSizeFactor: Array = []; #end public var updateRenderTarget = false; public static var shadowMapAtlases:Map = new Map(); // map a shadowmap atlas to their light type #if arm_debug public var lightType: String; public var rejectedLights: Array = []; #end function new(light: LightObject) { var maxTileSize = shadowMapAtlasSize(light); this.target = shadowMapAtlasName(light.data.raw.type); this.sizew = this.sizeh = this.baseTileSizeConst = maxTileSize; this.depth = getSubdivisions(); this.maxAtlasSizeConst = getMaxAtlasSize(light.data.raw.type); #if arm_shadowmap_atlas_lod computeTileSizes(maxTileSize, depth); #end #if arm_debug #if arm_shadowmap_atlas_single_map this.lightType = "any"; #else this.lightType = light.data.raw.type; #end #end } /** * Adds a light to an atlas. The atlas is decided based on the type of the light * @param light of type LightObject to be added to an yatlas * @return if the light was added succesfully */ public static function addLight(light: LightObject) { var atlasName = shadowMapAtlasName(light.data.raw.type); var atlas = shadowMapAtlases.get(atlasName); if (atlas == null) { // create a new atlas atlas = new ShadowMapAtlas(light); shadowMapAtlases.set(atlasName, atlas); } // find a free tile for this light var mainTile = ShadowMapTile.assignTiles(light, atlas, null); if (mainTile == null) { #if arm_debug if (!atlas.rejectedLights.contains(light)) atlas.rejectedLights.push(light); #end return; } atlas.activeTiles.push(mainTile); // notify the tile on light remove light.tileNotifyOnRemove = mainTile.notifyOnLightRemove; // notify atlas when this tile is freed mainTile.notifyOnFree = atlas.freeActiveTile; // "lock" light to make sure it's not eligible to be added again light.lightInAtlas = true; } static inline function shadowMapAtlasSize(light:LightObject):Int { // TODO: this can break because we are changing shadowmap_size elsewhere. return light.data.raw.shadowmap_size; } public function getTileSize(shadowMapScale: Float): Int { #if arm_shadowmap_atlas_lod // find the first scale factor that is smaller to the shadowmap scale, and then return the previous one. var i = 0; for (sizeFactor in tileSizeFactor) { if (sizeFactor < shadowMapScale) break; i++; } return tileSizes[i - 1]; #else return this.baseTileSizeConst; #end } #if arm_shadowmap_atlas_lod function computeTileSizes(maxTileSize: Int, depth: Int): Void { // find the highest value based on the calculation done in the cluster code var base = LightObject.zToShadowMapScale(0, 16); var subdiv = base / depth; for(i in 0...depth){ this.tileSizes.push(Std.int(maxTileSize / Math.pow(2, i))); this.tileSizeFactor.push(base); base -= subdiv; } this.tileSizes.push(0); this.tileSizeFactor.push(0.0); } #end public inline function atlasLimitReached() { // asume square atlas return (currTileOffset + 1) * baseTileSizeConst > maxAtlasSizeConst; } public static inline function shadowMapAtlasName(type: String): String { #if arm_shadowmap_atlas_single_map return "shadowMapAtlas"; #else switch (type) { case "point": return "shadowMapAtlasPoint"; case "sun": return "shadowMapAtlasSun"; default: return "shadowMapAtlasSpot"; } #end } public static inline function getSubdivisions(): Int { #if (rp_shadowmap_atlas_lod_subdivisions == 2) return 2; #elseif (rp_shadowmap_atlas_lod_subdivisions == 3) return 3; #elseif (rp_shadowmap_atlas_lod_subdivisions == 4) return 4; #elseif (rp_shadowmap_atlas_lod_subdivisions == 5) return 5; #elseif (rp_shadowmap_atlas_lod_subdivisions == 6) return 6; #elseif (rp_shadowmap_atlas_lod_subdivisions == 7) return 7; #elseif (rp_shadowmap_atlas_lod_subdivisions == 8) return 8; #elseif (!arm_shadowmap_atlas_lod) return 1; #end } public static inline function getMaxAtlasSize(type: String): Int { #if arm_shadowmap_atlas_single_map #if (rp_shadowmap_atlas_max_size == 512) return 512; #elseif (rp_shadowmap_atlas_max_size == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size == 2048) return 2048; #elseif (rp_shadowmap_atlas_max_size == 4096) return 4096; #elseif (rp_shadowmap_atlas_max_size == 8192) return 8192; #elseif (rp_shadowmap_atlas_max_size == 16384) return 16384; #end #else switch (type) { case "point": { #if (rp_shadowmap_atlas_max_size_point == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size_point == 2048) return 2048; #elseif (rp_shadowmap_atlas_max_size_point == 4096) return 4096; #elseif (rp_shadowmap_atlas_max_size_point == 8192) return 8192; #elseif (rp_shadowmap_atlas_max_size_point == 16384) return 16384; #end } case "spot": { #if (rp_shadowmap_atlas_max_size_spot == 512) return 512; #elseif (rp_shadowmap_atlas_max_size_spot == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size_spot == 2048) return 2048; #elseif (rp_shadowmap_atlas_max_size_spot == 4096) return 4096; #elseif (rp_shadowmap_atlas_max_size_spot == 8192) return 8192; #elseif (rp_shadowmap_atlas_max_size_spot == 16384) return 16384; #end } case "sun": { #if (rp_shadowmap_atlas_max_size_sun == 512) return 512; #elseif (rp_shadowmap_atlas_max_size_sun == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size_sun == 2048) return 2048; #elseif (rp_shadowmap_atlas_max_size_sun == 4096) return 4096; #elseif (rp_shadowmap_atlas_max_size_sun == 8192) return 8192; #elseif (rp_shadowmap_atlas_max_size_sun == 16384) return 16384; #end } default: { #if (rp_shadowmap_atlas_max_size == 512) return 512; #elseif (rp_shadowmap_atlas_max_size == 1024) return 1024; #elseif (rp_shadowmap_atlas_max_size == 2048) return 2048; #elseif (rp_shadowmap_atlas_max_size == 4096) return 4096; #elseif (rp_shadowmap_atlas_max_size == 8192) return 8192; #elseif (rp_shadowmap_atlas_max_size == 16384) return 16384; #end } } #end } function freeActiveTile(tile: ShadowMapTile) { activeTiles.remove(tile); } } class ShadowMapTile { public var light:Null = null; public var coordsX:Int; public var coordsY:Int; public var size:Int; public var tiles:Array = []; public var linkedTile:ShadowMapTile = null; #if arm_shadowmap_atlas_lod public var parentTile: ShadowMapTile = null; public var activeSubTiles: Int = 0; public var newTileSize: Int = -1; static var tilePattern = [[0, 0], [1, 0], [0, 1], [1, 1]]; #end function new(coordsX: Int, coordsY: Int, size: Int) { this.coordsX = coordsX; this.coordsY = coordsY; this.size = size; } public static function assignTiles(light: LightObject, atlas: ShadowMapAtlas, oldTile: ShadowMapTile): ShadowMapTile { var tileSize = 0; #if arm_shadowmap_atlas_lod if (oldTile != null && oldTile.newTileSize != -1) { // reuse tilesize instead of computing it again tileSize = oldTile.newTileSize; oldTile.newTileSize = -1; } else #end tileSize = atlas.getTileSize(light.shadowMapScale); if (tileSize == 0) return null; var tiles = []; tiles = findCreateTiles(light, oldTile, atlas, tilesLightType(light.data.raw.type), tileSize); // lock new tiles with light for (tile in tiles) tile.lockTile(light); return linkTiles(tiles); } static inline function linkTiles(tiles: Array): ShadowMapTile { if (tiles.length > 1) { var linkedTile = tiles[0]; for (i in 1...tiles.length) { linkedTile.linkedTile = tiles[i]; linkedTile = tiles[i]; } } return tiles[0]; } static inline function findCreateTiles(light: LightObject, oldTile: ShadowMapTile, atlas: ShadowMapAtlas, tilesPerLightType: Int, tileSize: Int): Array { var tilesFound: Array = []; while (tilesFound.length < tilesPerLightType) { findTiles(light, oldTile, atlas.tiles, tileSize, tilesPerLightType, tilesFound); if (tilesFound.length < tilesPerLightType) { tilesFound = []; // empty tilesFound // skip creating more tiles if limit has been reached if (atlas.atlasLimitReached()) break; createTiles(atlas.tiles, atlas.baseTileSizeConst, atlas.depth, atlas.currTileOffset, atlas.currTileOffset); atlas.currTileOffset++; // update texture to accomodate new size atlas.updateRenderTarget = true; atlas.sizew = atlas.sizeh = atlas.currTileOffset * atlas.baseTileSizeConst; } } return tilesFound; } inline static function findTiles(light:LightObject, oldTile: ShadowMapTile, tiles: Array, size: Int, tilesCount: Int, tilesFound: Array): Void { #if arm_shadowmap_atlas_lod if (oldTile != null) { // reuse children tiles if (size < oldTile.size) { oldTile.forEachTileLinked(function(lTile) { var childTile = findFreeChildTile(lTile, size); tilesFound.push(childTile); }); } // reuse parent tiles else { oldTile.forEachTileLinked(function(lTile) { // find out if parents tiles are not occupied var parentTile = findFreeParentTile(lTile, size); // if parent is free, add it to found tiles if (parentTile != null) tilesFound.push(parentTile); }); if (tilesFound.length < tilesCount) { // find naively the rest of the tiles that couldn't be reused findTilesNaive(light, tiles, size, tilesCount, tilesFound); } } } else #end findTilesNaive(light, tiles, size, tilesCount, tilesFound); } #if arm_shadowmap_atlas_lod static inline function findFreeChildTile(tile: ShadowMapTile, size: Int): ShadowMapTile { var childrenTile = tile; while (size < childrenTile.size) { childrenTile = childrenTile.tiles[0]; } return childrenTile; } static inline function findFreeParentTile(tile: ShadowMapTile, size: Int): ShadowMapTile { var parentTile = tile; while (size > parentTile.size) { parentTile = parentTile.parentTile; // stop if parent tile is occupied if (parentTile.activeSubTiles > 1) { parentTile = null; break; } } return parentTile; } #end static function findTilesNaive(light:LightObject, tiles: Array, size: Int, tilesCount: Int, tilesFound: Array): Void { for (tile in tiles) { if (tile.size == size) { if (tile.light == null #if arm_shadowmap_atlas_lod && tile.activeSubTiles == 0 #end) { tilesFound.push(tile); // stop after finding enough tiles if (tilesFound.length == tilesCount) return; } } else { // skip over if end of the tree or tile is occupied if (tile.tiles.length == 0 || tile.light != null) continue; findTilesNaive(light, tile.tiles, size, tilesCount, tilesFound); // skip iterating over the rest of the tiles if found enough if (tilesFound.length == tilesCount) return; } } } // create a basic tile and subdivide it if needed public static function createTiles(tiles:Array, size:Int, depth: Int, baseX:Int, baseY:Int) { var i = baseX; var j = 0; var lastTile = tiles.length; // assume occupied tiles start from 1 line before the base x var occupiedTiles = baseX - 1; while (i >= 0) { if (i <= occupiedTiles) { // avoid overriding tiles j = baseY; } while (j <= baseY) { // create base tile of max-size tiles.push(new ShadowMapTile(size * i, size * j, size)); #if arm_shadowmap_atlas_lod tiles[lastTile].tiles = subDivTile(tiles[lastTile], size, size * i, size * j, depth - 1); #end lastTile++; j++; } i--; } } #if arm_shadowmap_atlas_lod static function subDivTile(parent: ShadowMapTile, size: Int, baseCoordsX: Int, baseCoordsY: Int, depth: Int): Array { var tileSize = Std.int(size / 2); var tiles = []; for (i in 0...4) { var coordsX = baseCoordsX + tilePattern[i][0] * tileSize; var coordsY = baseCoordsY + tilePattern[i][1] * tileSize; var tile = new ShadowMapTile(coordsX, coordsY, tileSize); tile.parentTile = parent; if (depth > 1) tile.tiles = subDivTile(tile, tileSize, coordsX, coordsY, depth - 1); tiles.push(tile); } return tiles; } #end public static inline function tilesLightType(type: String): Int { switch (type) { case "sun": return LightObject.cascadeCount; case "point": return 6; default: return 1; } } public function notifyOnLightRemove() { unlockLight = true; freeTile(); } inline function lockTile(light: LightObject): Void { if (this.light != null) return; this.light = light; #if arm_shadowmap_atlas_lod // update the count of used tiles for parents this.forEachParentTile(function (pTile) { pTile.activeSubTiles++; }); #end } public var unlockLight: Bool = false; public var notifyOnFree: ShadowMapTile -> Void; public function freeTile(): Void { // prevent duplicates if (light != null && unlockLight) { light.lightInAtlas = false; unlockLight = false; } var linkedTile = this; var tempTile = this; while (linkedTile != null) { linkedTile.light = null; #if arm_shadowmap_atlas_lod // update the count of used tiles for parents linkedTile.forEachParentTile(function (pTile) { if (pTile.activeSubTiles > 0) pTile.activeSubTiles--; }); #end linkedTile = linkedTile.linkedTile; // unlink linked tiles tempTile.linkedTile = null; tempTile = linkedTile; } // notify atlas that this tile has been freed if (notifyOnFree != null) { notifyOnFree(this); notifyOnFree = null; } } public inline function forEachTileLinked(action: ShadowMapTile->Void): Void { var linkedTile = this; while (linkedTile != null) { action(linkedTile); linkedTile = linkedTile.linkedTile; } } #if arm_shadowmap_atlas_lod public inline function forEachParentTile(action: ShadowMapTile->Void): Void { var parentTile = this.parentTile; while (parentTile != null) { action(parentTile); parentTile = parentTile.parentTile; } } #end } #end