diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index f55108e..2336c5f 100644 Binary files a/GameData/ConformalDecals/Plugins/ConformalDecals.dll and b/GameData/ConformalDecals/Plugins/ConformalDecals.dll differ diff --git a/Source/ConformalDecals/IProjectionTarget.cs b/Source/ConformalDecals/IProjectionTarget.cs new file mode 100644 index 0000000..31f6f58 --- /dev/null +++ b/Source/ConformalDecals/IProjectionTarget.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +namespace ConformalDecals { + public interface IProjectionTarget { + void Project(Matrix4x4 orthoMatrix, Transform projector, Bounds projectionBounds); + void Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera); + ConfigNode Save(); + } +} \ No newline at end of file diff --git a/Source/ConformalDecals/ModuleConformalDecal.cs b/Source/ConformalDecals/ModuleConformalDecal.cs index 3580f2e..4ffb31e 100644 --- a/Source/ConformalDecals/ModuleConformalDecal.cs +++ b/Source/ConformalDecals/ModuleConformalDecal.cs @@ -99,7 +99,7 @@ namespace ConformalDecals { private const int DecalQueueMax = 2400; private static int _decalQueueCounter = -1; - private List _targets; + private Dictionary _targets; private bool _isAttached; private Matrix4x4 _orthoMatrix; @@ -155,7 +155,7 @@ namespace ConformalDecals { /// public override void OnIconCreate() { UpdateTextures(); - UpdateScale(); + UpdateProjection(); } /// @@ -226,12 +226,12 @@ namespace ConformalDecals { protected void OnProjectionTweakEvent(BaseField field, object obj) { // scale or depth values have been changed, so update scale // and update projection matrices if attached - UpdateScale(); + UpdateProjection(); UpdateTargets(); foreach (var counterpart in part.symmetryCounterparts) { var decal = counterpart.GetComponent(); - decal.UpdateScale(); + decal.UpdateProjection(); decal.UpdateTargets(); } } @@ -256,38 +256,80 @@ namespace ConformalDecals { /// Called when a new variant is applied in the editor protected void OnVariantApplied(Part eventPart, PartVariant variant) { - if (_isAttached && eventPart != null) { - if (projectMultiple && eventPart != part.parent) return; - else if (!_targets.Select(o => o.targetPart).Contains(eventPart)) return; - + if (_isAttached && eventPart != null && (!projectMultiple || eventPart == part.parent)) { + _targets.Remove(eventPart); UpdateTargets(); } } /// Called when an editor event occurs protected void OnEditorEvent(ConstructionEventType eventType, Part eventPart) { - if (this.part != eventPart && !part.symmetryCounterparts.Contains(eventPart)) return; switch (eventType) { case ConstructionEventType.PartAttached: - OnAttach(); + OnPartAttached(eventPart); break; case ConstructionEventType.PartDetached: - OnDetach(); + OnPartDetached(eventPart); break; case ConstructionEventType.PartOffsetting: case ConstructionEventType.PartRotating: - UpdateScale(); - UpdateTargets(); + OnPartTransformed(eventPart); break; } } + /// Called when a part is transformed in the editor + protected void OnPartTransformed(Part eventPart) { + if (this.part == eventPart) { + UpdateProjection(); + UpdateTargets(); + } + else { + UpdatePartTarget(eventPart, _boundsRenderer.bounds); + // recursively call for child parts + foreach (var child in eventPart.children) { + OnPartTransformed(child); + } + } + } + + /// Called when a part is attached in the editor + protected void OnPartAttached(Part eventPart) { + if (this.part == eventPart) { + OnAttach(); + } + else { + UpdatePartTarget(eventPart, _boundsRenderer.bounds); + // recursively call for child parts + foreach (var child in eventPart.children) { + OnPartAttached(child); + } + } + } + + /// Called when a part is detached in the editor + protected void OnPartDetached(Part eventPart) { + if (this.part == eventPart) { + OnDetach(); + } + else { + _targets.Remove(eventPart); + // recursively call for child parts + foreach (var child in eventPart.children) { + OnPartDetached(child); + } + } + } + /// Called when part `willDie` will be destroyed protected void OnPartWillDie(Part willDie) { if (willDie == part.parent) { this.Log("Parent part about to be destroyed! Killing decal part."); part.Die(); } + else if (projectMultiple) { + _targets.Remove(willDie); + } } /// Called when decal is attached to a new part @@ -310,7 +352,7 @@ namespace ConformalDecals { Camera.onPreCull += Render; UpdateMaterials(); - UpdateScale(); + UpdateProjection(); UpdateTargets(); } @@ -328,11 +370,11 @@ namespace ConformalDecals { Camera.onPreCull -= Render; UpdateMaterials(); - UpdateScale(); + UpdateProjection(); } // FUNCTIONS - + /// Load any settings from the decal config protected virtual void LoadDecal(ConfigNode node) { // PARSE TRANSFORMS @@ -498,10 +540,10 @@ namespace ConformalDecals { protected virtual void UpdateAll() { UpdateTextures(); UpdateMaterials(); - UpdateScale(); + UpdateProjection(); UpdateTargets(); } - + /// Update decal textures protected virtual void UpdateTextures() { } @@ -521,7 +563,7 @@ namespace ConformalDecals { } /// Update decal scale and projection - protected void UpdateScale() { + protected void UpdateProjection() { // Update scale and depth scale = Mathf.Max(0.01f, scale); @@ -559,7 +601,7 @@ namespace ConformalDecals { if (_isAttached) { // Update projection targets if (_targets == null) { - _targets = new List(); + _targets = new Dictionary(); } else { _targets.Clear(); @@ -587,6 +629,9 @@ namespace ConformalDecals { protected void UpdateTargets() { if (!_isAttached) return; + var projectionBounds = _boundsRenderer.bounds; + + // collect list of potential targets IEnumerable targetParts; if (projectMultiple) { targetParts = HighLogic.LoadedSceneIsFlight ? part.vessel.parts : EditorLogic.fetch.ship.parts; @@ -596,25 +641,30 @@ namespace ConformalDecals { } foreach (var targetPart in targetParts) { - if (targetPart.GetComponent() != null) continue; // skip other decals + UpdatePartTarget(targetPart, projectionBounds); + } + } - foreach (var renderer in targetPart.FindModelComponents()) { - var target = renderer.transform; - var filter = target.GetComponent(); + protected void UpdatePartTarget(Part targetPart, Bounds projectionBounds) { + if (targetPart.GetComponent() != null) return; // skip other decals - // check if the target has any missing data - if (!ProjectionTarget.ValidateTarget(target, renderer, filter)) continue; + this.Log($"Updating projection onto part {targetPart.name}"); - // check bounds for intersection - if (_boundsRenderer.bounds.Intersects(renderer.bounds)) { - // create new ProjectionTarget to represent the renderer - var projectionTarget = new ProjectionTarget(targetPart, target, renderer, filter, _orthoMatrix, decalProjectorTransform, useBaseNormal); + if (!_targets.TryGetValue(targetPart, out var target)) { + var rendererList = targetPart.FindModelComponents(); - // add the target to the list - _targets.Add(projectionTarget); - } + if (rendererList.Any(o => projectionBounds.Intersects(o.bounds))) { + target = new ProjectionPartTarget(targetPart, useBaseNormal); + _targets.Add(targetPart, target); + } + else { + return; } } + + this.Log($"valid target: {targetPart.name}"); + + target.Project(_orthoMatrix, decalProjectorTransform, projectionBounds); } /// Render the decal @@ -622,7 +672,7 @@ namespace ConformalDecals { if (!_isAttached) return; // render on each target object - foreach (var target in _targets) { + foreach (var target in _targets.Values) { target.Render(_decalMaterial, part.mpb, camera); } } diff --git a/Source/ConformalDecals/ModuleConformalText.cs b/Source/ConformalDecals/ModuleConformalText.cs index 7aee17d..9e60841 100644 --- a/Source/ConformalDecals/ModuleConformalText.cs +++ b/Source/ConformalDecals/ModuleConformalText.cs @@ -264,7 +264,7 @@ namespace ConformalDecals { UpdateTextures(); UpdateMaterials(); - UpdateScale(); + UpdateProjection(); // QUEUE PART FOR ICON FIXING IN VAB DecalIconFixer.QueuePart(part.name); diff --git a/Source/ConformalDecals/ProjectionTarget.cs b/Source/ConformalDecals/ProjectionMeshTarget.cs similarity index 60% rename from Source/ConformalDecals/ProjectionTarget.cs rename to Source/ConformalDecals/ProjectionMeshTarget.cs index fc0eb0c..49b3dea 100644 --- a/Source/ConformalDecals/ProjectionTarget.cs +++ b/Source/ConformalDecals/ProjectionMeshTarget.cs @@ -6,69 +6,65 @@ using UnityEngine; using UnityEngine.Rendering; namespace ConformalDecals { - public class ProjectionTarget { + public class ProjectionMeshTarget : IProjectionTarget { + // enabled flag + public bool enabled = true; + // Target object data - public readonly Transform target; - public readonly Part targetPart; - private readonly Mesh _targetMesh; - private readonly Matrix4x4 _decalMatrix; - private readonly Vector3 _decalNormal; - private readonly Vector3 _decalTangent; - private readonly bool _useBaseNormal; + public readonly Transform target; + public readonly Transform root; + public readonly Mesh mesh; + public readonly MeshRenderer renderer; + + // Projection data + private Matrix4x4 _decalMatrix; + private Vector3 _decalNormal; + private Vector3 _decalTangent; // property block private readonly MaterialPropertyBlock _decalMPB; - public ProjectionTarget(Part targetPart, Transform target, MeshRenderer renderer, MeshFilter filter, - Matrix4x4 orthoMatrix, Transform projector, bool useBaseNormal) { - - this.targetPart = targetPart; + public ProjectionMeshTarget(Transform target, Transform root, MeshRenderer renderer, Mesh mesh, bool useBaseNormal) { + this.root = root; this.target = target; - _targetMesh = filter.sharedMesh; - _useBaseNormal = useBaseNormal; + this.renderer = renderer; + this.mesh = mesh; _decalMPB = new MaterialPropertyBlock(); - var projectorToTargetMatrix = target.worldToLocalMatrix * projector.localToWorldMatrix; - - _decalMatrix = orthoMatrix * projectorToTargetMatrix.inverse; - _decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized; - _decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized; - - SetupMPB(renderer.sharedMaterial); + SetNormalMap(renderer.sharedMaterial, useBaseNormal); } - public ProjectionTarget(ConfigNode node, Vessel vessel, bool useBaseNormal) { - var flightID = (uint) ParseUtil.ParseInt(node, "part"); + public ProjectionMeshTarget(ConfigNode node, Transform root, bool useBaseNormal) { + if (node == null) throw new ArgumentNullException(nameof(node)); + if (root == null) throw new ArgumentNullException(nameof(root)); + var targetPath = ParseUtil.ParseString(node, "targetPath"); var targetName = ParseUtil.ParseString(node, "targetName"); _decalMatrix = ParseUtil.ParseMatrix4x4(node, "decalMatrix"); _decalNormal = ParseUtil.ParseVector3(node, "decalNormal"); _decalTangent = ParseUtil.ParseVector3(node, "decalTangent"); - _useBaseNormal = useBaseNormal; _decalMPB = new MaterialPropertyBlock(); - targetPart = vessel[flightID]; - if (targetPart == null) throw new IndexOutOfRangeException("Vessel returned null part"); - target = LoadTransformPath(targetPath, targetPart.transform); + target = LoadTransformPath(targetPath, root); if (target.name != targetName) throw new FormatException("Target name does not match"); - var renderer = target.GetComponent(); + renderer = target.GetComponent(); var filter = target.GetComponent(); if (!ValidateTarget(target, renderer, filter)) throw new FormatException("Invalid target"); - _targetMesh = filter.sharedMesh; + mesh = filter.sharedMesh; - SetupMPB(renderer.sharedMaterial); - } - - private void SetupMPB(Material targetMaterial) { + SetNormalMap(renderer.sharedMaterial, useBaseNormal); + _decalMPB.SetMatrix(DecalPropertyIDs._ProjectionMatrix, _decalMatrix); _decalMPB.SetVector(DecalPropertyIDs._DecalNormal, _decalNormal); - _decalMPB.SetVector(DecalPropertyIDs._DecalTangent, _decalTangent); + _decalMPB.SetVector(DecalPropertyIDs._DecalTangent, _decalTangent); + } - if (_useBaseNormal && targetMaterial.HasProperty(DecalPropertyIDs._BumpMap)) { + private void SetNormalMap(Material targetMaterial, bool useBaseNormal) { + if (useBaseNormal && targetMaterial.HasProperty(DecalPropertyIDs._BumpMap)) { _decalMPB.SetTexture(DecalPropertyIDs._BumpMap, targetMaterial.GetTexture(DecalPropertyIDs._BumpMap)); var normalScale = targetMaterial.GetTextureScale(DecalPropertyIDs._BumpMap); @@ -81,20 +77,40 @@ namespace ConformalDecals { } } + public void Project(Matrix4x4 orthoMatrix, Transform projector, Bounds projectionBounds) { + if (projectionBounds.Intersects(renderer.bounds)) { + enabled = true; + + var projectorToTargetMatrix = target.worldToLocalMatrix * projector.localToWorldMatrix; + + _decalMatrix = orthoMatrix * projectorToTargetMatrix.inverse; + _decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized; + _decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized; + + _decalMPB.SetMatrix(DecalPropertyIDs._ProjectionMatrix, _decalMatrix); + _decalMPB.SetVector(DecalPropertyIDs._DecalNormal, _decalNormal); + _decalMPB.SetVector(DecalPropertyIDs._DecalTangent, _decalTangent); + } + else { + enabled = false; + } + } + public void Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) { + if (!enabled) return; + _decalMPB.SetFloat(PropertyIDs._RimFalloff, partMPB.GetFloat(PropertyIDs._RimFalloff)); _decalMPB.SetColor(PropertyIDs._RimColor, partMPB.GetColor(PropertyIDs._RimColor)); - Graphics.DrawMesh(_targetMesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, _decalMPB, ShadowCastingMode.Off, true); + Graphics.DrawMesh(mesh, target.localToWorldMatrix, decalMaterial, 0, camera, 0, _decalMPB, ShadowCastingMode.Off, true); } public ConfigNode Save() { - var node = new ConfigNode("TARGET"); - node.AddValue("part", targetPart.flightID); + var node = new ConfigNode("MESH_TARGET"); node.AddValue("decalMatrix", _decalMatrix); node.AddValue("decalNormal", _decalNormal); node.AddValue("decalTangent", _decalTangent); - node.AddValue("targetPath", SaveTransformPath(target, targetPart.transform)); // used to find the target transform + node.AddValue("targetPath", SaveTransformPath(target, root)); // used to find the target transform node.AddValue("targetName", target.name); // used to validate the mesh has not changed since last load return node; @@ -116,7 +132,7 @@ namespace ConformalDecals { } private static string SaveTransformPath(Transform leaf, Transform root) { - var builder = new StringBuilder(leaf.name); + var builder = new StringBuilder($"{leaf.GetSiblingIndex()}"); var current = leaf.parent; while (current != root) { diff --git a/Source/ConformalDecals/ProjectionPartTarget.cs b/Source/ConformalDecals/ProjectionPartTarget.cs new file mode 100644 index 0000000..3b060e1 --- /dev/null +++ b/Source/ConformalDecals/ProjectionPartTarget.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace ConformalDecals { + public class ProjectionPartTarget : IProjectionTarget { + public readonly Part part; + public readonly List meshTargets; + + public ProjectionPartTarget(Part part, bool useBaseNormal) { + this.part = part; + meshTargets = new List(); + + foreach (var renderer in part.FindModelComponents()) { + var target = renderer.transform; + var filter = target.GetComponent(); + + // check if the target has any missing data + if (!ProjectionMeshTarget.ValidateTarget(target, renderer, filter)) continue; + + // create new ProjectionTarget to represent the renderer + var projectionTarget = new ProjectionMeshTarget(target, part.transform, renderer, filter.sharedMesh, useBaseNormal); + + // add the target to the list + meshTargets.Add(projectionTarget); + } + } + + public void Project(Matrix4x4 orthoMatrix, Transform projector, Bounds projectionBounds) { + foreach (var meshTarget in meshTargets) { + meshTarget.Project(orthoMatrix, projector, projectionBounds); + } + } + + public void Render(Material decalMaterial, MaterialPropertyBlock partMPB, Camera camera) { + foreach (var target in meshTargets) { + target.Render(decalMaterial, partMPB, camera); + } + } + + public ConfigNode Save() { + var node = new ConfigNode("PART_TARGET"); + node.AddValue("part", part.flightID); + foreach (var meshTarget in meshTargets) { + node.AddNode(meshTarget.Save()); + } + + return node; + } + } +} \ No newline at end of file