From 91a66137076e04d3c0f83b2f142c33bb0eb94225 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Sun, 31 May 2020 23:04:59 -0700 Subject: [PATCH] Fix transform handling and projection culling Now to figure out why my collider is disappearing... --- .../ConformalDecals/Parts/decal-blank.cfg | 3 +- .../Plugins/ConformalDecals.dll | Bin 25088 -> 26624 bytes .../ConformalDecals/ModuleConformalDecal.cs | 219 +++++++++++------- Source/ConformalDecals/ProjectionTarget.cs | 6 +- 4 files changed, 136 insertions(+), 92 deletions(-) diff --git a/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg b/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg index 0daf3e0..289a285 100644 --- a/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg +++ b/Distribution/GameData/ConformalDecals/Parts/decal-blank.cfg @@ -45,7 +45,8 @@ PART { name = ModuleConformalDecal - decalPreviewTransform = Decal-Front + decalFront = Decal-Front + decalBack = Decal-Back MATERIAL { diff --git a/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/Distribution/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 2142929fc863daf47ed8639de2e2ae8e6f98705f..e10635015efd78e164ef4c7e39e14ffc523d4c78 100644 GIT binary patch literal 26624 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 literal 25088 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 _targets; + private bool _isAttached; + private Matrix4x4 _orthoMatrix; private Bounds _decalBounds; - - private bool IsAttached => part.parent != null; + private Vector2 _backTextureBaseScale; + private Material _backMaterial; public override void OnLoad(ConfigNode node) { this.Log("Loading module"); @@ -62,35 +69,42 @@ namespace ConformalDecals { aspectRatio = 1; } - // find preview object references - modelTransformRef = part.transform.Find("model"); + // find front transform + decalFrontTransform = part.FindModelTransform(decalFront); + if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'."); - decalPreviewTransformRef = part.FindModelTransform(decalPreviewTransform); - if (decalPreviewTransformRef == null) throw new FormatException("Missing decal preview reference"); - - if (String.IsNullOrEmpty(decalModelTransform)) { - decalModelTransformRef = decalPreviewTransformRef; + // find back transform + this.Log($"decalBack name is {decalBack}"); + this.Log($"updateBaseScale is {updateBackScale}"); + if (string.IsNullOrEmpty(decalBack)) { + if (updateBackScale) { + this.LogWarning("updateBackScale is true but has no specified decalBack transform!"); + this.LogWarning("Setting updateBackScale to false."); + updateBackScale = false; + } } else { - decalModelTransformRef = part.FindModelTransform(decalModelTransform); - if (decalModelTransformRef == null) throw new FormatException("Missing decal mesh reference"); + decalBackTransform = part.FindModelTransform(decalBack); + if (decalBackTransform == null) throw new FormatException($"Could not find decalBack transform: '{decalBack}'."); } - if (String.IsNullOrEmpty(decalProjectorTransform)) { - decalProjectorTransformRef = modelTransformRef; + // find model transform + if (string.IsNullOrEmpty(decalModel)) { + decalModelTransform = decalFrontTransform; } else { - decalProjectorTransformRef = part.FindModelTransform(decalProjectorTransform); - if (decalProjectorTransform == null) throw new FormatException("Missing decal projector reference"); + decalModelTransform = part.FindModelTransform(decalModel); + if (decalModelTransform == null) throw new FormatException($"Could not find decalModel transform: '{decalModel}'."); } - colliderTransformRef = new GameObject("Decal Collider").transform; - colliderTransformRef.parent = modelTransformRef; - colliderTransformRef.position = decalProjectorTransformRef.position; - colliderTransformRef.rotation = decalProjectorTransformRef.rotation; - colliderTransformRef.gameObject.SetActive(false); - - colliderRef = colliderTransformRef.gameObject.AddComponent(); + // find projector transform + if (string.IsNullOrEmpty(decalProjector)) { + decalProjectorTransform = part.transform.Find("model"); + } + else { + decalProjectorTransform = part.FindModelTransform(decalProjector); + if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'."); + } } catch (Exception e) { this.LogException("Exception parsing partmodule", e); @@ -99,71 +113,95 @@ namespace ConformalDecals { public override void OnStart(StartState state) { this.Log("Starting module"); + // generate orthogonal projection matrix and offset it by 0.5 on x and y axes _orthoMatrix = Matrix4x4.identity; _orthoMatrix[0, 3] = 0.5f; _orthoMatrix[1, 3] = 0.5f; + // setup OnTweakEvent for scale and depth fields in editor if ((state & StartState.Editor) != 0) { - // setup OnTweakEvent for scale and depth fields in editor GameEvents.onEditorPartEvent.Add(OnEditorEvent); GameEvents.onVariantApplied.Add(OnVariantApplied); - Fields[nameof(scale)].uiControlEditor.onFieldChanged = OnTweakEvent; - Fields[nameof(depth)].uiControlEditor.onFieldChanged = OnTweakEvent; + Fields[nameof(scale)].uiControlEditor.onFieldChanged = OnScaleTweakEvent; + Fields[nameof(depth)].uiControlEditor.onFieldChanged = OnScaleTweakEvent; + } + + // get back material if necessary + if (updateBackScale) { + this.Log("Getting material and base scale for back material"); + var backRenderer = decalBackTransform.GetComponent(); + if (backRenderer == null) { + this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false."); + updateBackScale = false; + } + else if ((_backMaterial = backRenderer.material) == null) { + this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false."); + updateBackScale = false; + } + else { + _backTextureBaseScale = _backMaterial.GetTextureScale(PropertyIDs._MainTex); + } + } + + // set initial attachment state + if (part.parent == null) { + OnDetach(); } else { - // if we start in the flight scene attached, call Attach - if (IsAttached) Attach(); + OnAttach(); } } - private void OnDestroy() { + public void OnDestroy() { GameEvents.onEditorPartEvent.Remove(OnEditorEvent); GameEvents.onVariantApplied.Remove(OnVariantApplied); - + // remove from preCull delegate Camera.onPreCull -= Render; } - - public void OnTweakEvent(BaseField field, object obj) { - // scale or depth values have been changed, so update the projection matrix for each target - Project(); + private void OnScaleTweakEvent(BaseField field, object obj) { + // scale or depth values have been changed, so update scale + // and update projection matrices if attached + UpdateScale(); + if (_isAttached) UpdateProjection(); } - public void OnVariantApplied(Part eventPart, PartVariant variant) { - if (IsAttached && eventPart == part.parent) { - Detach(); - Attach(); + private void OnVariantApplied(Part eventPart, PartVariant variant) { + if (_isAttached && eventPart == part.parent) { + OnDetach(); + OnAttach(); } } - public void OnEditorEvent(ConstructionEventType eventType, Part eventPart) { + private void OnEditorEvent(ConstructionEventType eventType, Part eventPart) { if (eventPart != this.part) return; switch (eventType) { case ConstructionEventType.PartAttached: - Attach(); + OnAttach(); break; case ConstructionEventType.PartDetached: - Detach(); + OnDetach(); break; case ConstructionEventType.PartOffsetting: case ConstructionEventType.PartRotating: case ConstructionEventType.PartDragging: - Project(); + UpdateProjection(); break; } } - public void Attach() { - if (!IsAttached) { + private void OnAttach() { + if (part.parent == null) { this.LogError("Attach function called but part has no parent!"); + _isAttached = false; return; } + _isAttached = true; + this.Log($"Decal attached to {part.parent.partName}"); - this.Log($"{materialProperties == null}"); - this.Log($"{decalModelTransformRef == null}"); if (_targets == null) { _targets = new List(); @@ -177,7 +215,7 @@ namespace ConformalDecals { foreach (var renderer in renderers) { // skip disabled renderers if (renderer.gameObject.activeInHierarchy == false) continue; - + var meshFilter = renderer.GetComponent(); if (meshFilter == null) continue; // object has a meshRenderer with no filter, invalid var mesh = meshFilter.mesh; @@ -194,58 +232,61 @@ namespace ConformalDecals { } // hide preview model - decalModelTransformRef.gameObject.SetActive(false); - - // enable decal collider - colliderTransformRef.gameObject.SetActive(true); + decalModelTransform.gameObject.SetActive(false); // add to preCull delegate Camera.onPreCull += Render; - - Project(); + + UpdateScale(); + UpdateProjection(); } - public void Detach() { - // unhide preview model - decalModelTransformRef.gameObject.SetActive(true); + private void OnDetach() { + _isAttached = false; - // enable decal collider - colliderTransformRef.gameObject.SetActive(false); + // unhide preview model + decalModelTransform.gameObject.SetActive(true); // remove from preCull delegate Camera.onPreCull -= Render; + + UpdateScale(); } - [KSPEvent(guiActive = false, guiName = "Project", guiActiveEditor = true, active = true)] - public void Project() { - if (!IsAttached) return; + private void UpdateScale() { + var size = new Vector2(scale, scale * aspectRatio); - float width = scale; - float height = scale * aspectRatio; - // generate orthogonal matrix scale values - _orthoMatrix[0, 0] = 1 / width; - _orthoMatrix[1, 1] = 1 / height; + // update orthogonal matrix scale + _orthoMatrix[0, 0] = 1 / size.x; + _orthoMatrix[1, 1] = 1 / size.y; _orthoMatrix[2, 2] = 1 / depth; // generate bounding box for decal for culling purposes _decalBounds.center = Vector3.forward * (depth / 2); - _decalBounds.extents = new Vector3(width / 2, height / 2, depth / 2); + _decalBounds.extents = new Vector3(size.x / 2, size.y / 2, depth / 2); // rescale preview model - decalModelTransformRef.localScale = new Vector3(width, height, (width + height) / 2); + decalModelTransform.localScale = new Vector3(size.x, size.y, (size.x + size.y) / 2); - // assign dimensions to collider - colliderRef.center = _decalBounds.center; - colliderRef.size = _decalBounds.size; + // update back material scale + if (updateBackScale) { + _backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * _backTextureBaseScale.x, size.y * _backTextureBaseScale.y)); + } + // update material scale + materialProperties.UpdateMaterial(size); + } + + private void UpdateProjection() { + if (!_isAttached) return; // project to each target object foreach (var target in _targets) { - target.Project(_orthoMatrix, colliderRef.bounds, decalProjectorTransformRef); + target.Project(_orthoMatrix, new OrientedBounds(decalProjectorTransform.localToWorldMatrix, _decalBounds), decalProjectorTransform); } } - public void Render(Camera camera) { - if (!IsAttached) return; + private void Render(Camera camera) { + if (!_isAttached) return; // render on each target object foreach (var target in _targets) { diff --git a/Source/ConformalDecals/ProjectionTarget.cs b/Source/ConformalDecals/ProjectionTarget.cs index 61213cd..1accc20 100644 --- a/Source/ConformalDecals/ProjectionTarget.cs +++ b/Source/ConformalDecals/ProjectionTarget.cs @@ -45,9 +45,9 @@ namespace ConformalDecals { } } - public void Project(Matrix4x4 orthoMatrix, Bounds projectorBounds, Transform projector) { + public void Project(Matrix4x4 orthoMatrix, OrientedBounds projectorBounds, Transform projector) { var targetBounds = _targetRenderer.bounds; - if (targetBounds.Intersects(projectorBounds)) { + if (projectorBounds.Intersects(targetBounds)) { _projectionEnabled = true; var projectorToTargetMatrix = target.worldToLocalMatrix * projector.localToWorldMatrix; @@ -58,9 +58,11 @@ namespace ConformalDecals { decalMPB.SetMatrix(_projectionMatrixID, projectionMatrix); decalMPB.SetVector(_decalNormalID, decalNormal); decalMPB.SetVector(_decalTangentID, decalTangent); + Debug.Log($"Projection enabled for {target.gameObject}"); } else { _projectionEnabled = false; + Debug.Log($"Projection disabled for {target.gameObject}"); } }