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; public var materialIndex = 0; public var depthRead(default, null) = false; #if lnx_particles public var particleSystems: Array = null; // Particle owner public var particleChildren: Array = 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 = 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) { 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 = 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(); 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, materialContexts: Array, shaderContexts: Array) { 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) { 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 = []; var shaderContexts: Array = []; 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, 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)); } } } }