/******************************************************************************/ /* Project - MudBun Publisher - Long Bunny Labs http://LongBunnyLabs.com Author - Ming-Lun "Allen" Chou http://AllenChou.net */ /******************************************************************************/ using System; using System.Linq; using UnityEditor; using UnityEngine; namespace MudBun { [CustomEditor(typeof(MudRendererBase))] [CanEditMultipleObjects] public class MudRendererBaseEditor : MudEditorBase { protected SerializedProperty MaxVoxelsK; protected SerializedProperty MaxChunks; protected SerializedProperty ShowGpuMemoryUsage; protected SerializedProperty AutoAdjustBudgetsToHighWaterMarks; protected SerializedProperty AutoAdjustBudgetsToHighWaterMarksMarginPercent; protected SerializedProperty VoxelDensity; protected SerializedProperty ComputeMode; protected SerializedProperty TimeSliceMode; protected SerializedProperty TimeSliceFrames; protected SerializedProperty TimeSliceFramesOffset; protected SerializedProperty TimeSlicePeriod; protected SerializedProperty TimeSliceTimeOffset; protected SerializedProperty HardwareMode; protected SerializedProperty RenderMode; protected SerializedProperty RayTracedVoxelMode; protected SerializedProperty RayTracedVoxelSizeMultiplier; protected SerializedProperty RayTracedVoxelSmoothCubeNormal; protected SerializedProperty RayTracedVoxelSphereFullness; protected SerializedProperty RayTracedVoxelPaddingMode; protected SerializedProperty RayTracedVoxelInternalPaddingDistance; protected SerializedProperty RayTracedVoxelSizeFadeDistance; protected SerializedProperty ShowAdvancedNormalOptions; protected SerializedProperty NormalQuantization; protected SerializedProperty Normal2dFade; protected SerializedProperty Normal2dStrength; protected SerializedProperty SmoothNormalBlurRelative; protected SerializedProperty SmoothNormalBlurAbsolute; protected SerializedProperty EnableAutoSmoothing; protected SerializedProperty AutoSmoothingMaxAngle; protected SerializedProperty EnableSmoothCorner; protected SerializedProperty SmoothCornerSubdivision; protected SerializedProperty SmoothCornerNormalBlur; protected SerializedProperty SmoothCornerFade; protected SerializedProperty InvertNormals; protected SerializedProperty ForceEvaluateAllBrushes; protected SerializedProperty Enable2dMode; protected SerializedProperty SurfaceShift; protected SerializedProperty MeshingMode; protected SerializedProperty ShowAdvancedGeometryOptions; protected SerializedProperty SurfaceNetsDualQuadsBlend; protected SerializedProperty SurfaceNetsBinarySearchIterations; protected SerializedProperty SurfaceNetsGradientDescentIterations; protected SerializedProperty SurfaceNetsGradientDescentFactor; protected SerializedProperty SurfaceNetsHighAccuracyMode; protected SerializedProperty DualContouringDualQuadsBlend; protected SerializedProperty DualContouringRelaxation; protected SerializedProperty DualContouringSolverIterations; protected SerializedProperty DualContouringBinarySearchIterations; protected SerializedProperty DualContouringGradientDescentIterations; protected SerializedProperty DualContouringGradientDescentFactor; protected SerializedProperty DualContouringHighAccuracyMode; protected SerializedProperty ShowAdvancedSplatOptions; protected SerializedProperty SplatSize; protected SerializedProperty SplatSizeJitter; protected SerializedProperty SplatNormalShift; protected SerializedProperty SplatNormalShiftJitter; protected SerializedProperty SplatColorJitter; protected SerializedProperty SplatPositionJitter; protected SerializedProperty SplatRotationJitter; protected SerializedProperty SplatOrientationJitter; protected SerializedProperty SplatOriginalNormalBlend; protected SerializedProperty SplatJitterNoisiness; protected SerializedProperty SplatCameraFacing; protected SerializedProperty SplatNormalsMatchCameraFacing; protected SerializedProperty SplatShadowsMatchCameraFacing; protected SerializedProperty SplatScreenSpaceFlattening; //protected SerializedProperty SplatSmoothNormalBlend; protected SerializedProperty CastShadows; protected SerializedProperty ReceiveShadows; protected SerializedProperty SharedMaterial; protected SerializedProperty MasterColor; protected SerializedProperty MasterEmission; protected SerializedProperty MasterMetallic; protected SerializedProperty MasterSmoothness; protected SerializedProperty RenderMaterialMesh; protected SerializedProperty RenderMaterialSplats; protected SerializedProperty RenderMaterialDecal; //protected SerializedProperty RenderMaterialRayMarchedSurface; //protected SerializedProperty RenderMaterialRayTracedVoxels; protected SerializedProperty RenderMaterialMode; protected SerializedProperty MaxRayMarchSteps; protected SerializedProperty RayMarchAccuracy; protected SerializedProperty RayMarchMaxRayDistance; /* protected SerializedProperty RayMarchStepSize; protected SerializedProperty NumLightMarchSteps; protected SerializedProperty RayMarchDistance; protected SerializedProperty RayMarchVolumeDensity; protected SerializedProperty RayMarchLight; protected SerializedProperty RayMarchVolumeAbsorption; protected SerializedProperty RayMarchLightAbsorption; protected SerializedProperty RayMarchDarknesThreshold; protected SerializedProperty RayMarchTransmittanceCurve; protected SerializedProperty UseRayMarchNoise; protected SerializedProperty RayMarchNoiseThreshold; protected SerializedProperty RayMarchNoiseEdgeFade; protected SerializedProperty RayMarchNoiseScrollSpeed; protected SerializedProperty RayMarchNoiseBaseOctaveSize; protected SerializedProperty RayMarchNoiseNumOctaves; protected SerializedProperty RayMarchNoiseOctaveOffsetFactor; */ protected SerializedProperty MeshGenerationCreateNewObject; protected SerializedProperty MeshGenerationRenderableMeshMode; protected SerializedProperty MeshGenerationCreateCollider; protected SerializedProperty MeshGenerationForceConvexCollider; protected SerializedProperty MeshGenerationColliderSurfaceShift; protected SerializedProperty MeshGenerationCreateRigidBody; protected SerializedProperty GenerateColliderMeshAssetByEditor; protected SerializedProperty GenerateColliderMeshAssetByEditorName; protected SerializedProperty MeshGenerationColliderVoxelDensity; protected SerializedProperty MeshGenerationAutoRigging; protected SerializedProperty MeshGenerationAutoRiggingAlgorithm; protected SerializedProperty MeshGenerationAutoRiggingMaxBonesPerVertex; protected SerializedProperty MeshGenerationGenerateTextureUV; protected SerializedProperty MeshGenerationGenerateLightMapUV; protected SerializedProperty MeshGenerationWeldVertices; protected SerializedProperty MeshGenerationLockOnStart; protected SerializedProperty GenerateMeshAssetByEditor; protected SerializedProperty GenerateMeshAssetByEditorName; protected SerializedProperty RecursiveLockMeshByEditor; protected SerializedProperty RememberLockedMeshMaterialByEditor; protected SerializedProperty DrawGenerateSdfGizmos; protected SerializedProperty GenerateSdfByEditorName; protected SerializedProperty GenerateSdfCenter; protected SerializedProperty GenerateSdfDimension; protected SerializedProperty GenerateSdfTextureSize; protected SerializedProperty UseCutoffVolume; protected SerializedProperty CutoffVolumeCenter; protected SerializedProperty CutoffVolumeSize; protected SerializedProperty ClickSelection; protected SerializedProperty AlwaysDrawGizmos; protected SerializedProperty DrawRawBrushBounds; protected SerializedProperty DrawComputeBrushBounds; protected SerializedProperty DrawRenderBounds; protected SerializedProperty DrawVoxelNodes; protected SerializedProperty DrawVoxelNodesDepth; protected SerializedProperty DrawVoxelNodesScale; protected void InitSerializedProperties() { MaxVoxelsK = serializedObject.FindProperty("MaxVoxelsK"); MaxChunks = serializedObject.FindProperty("MaxChunks"); ShowGpuMemoryUsage = serializedObject.FindProperty("ShowGpuMemoryUsage"); AutoAdjustBudgetsToHighWaterMarks = serializedObject.FindProperty("AutoAdjustBudgetsToHighWaterMarks"); AutoAdjustBudgetsToHighWaterMarksMarginPercent = serializedObject.FindProperty("AutoAdjustBudgetsToHighWaterMarksMarginPercent"); VoxelDensity = serializedObject.FindProperty("VoxelDensity"); ComputeMode = serializedObject.FindProperty("ComputeMode"); TimeSliceMode = serializedObject.FindProperty("TimeSliceMode"); TimeSliceFrames = serializedObject.FindProperty("TimeSliceFrames"); TimeSliceFramesOffset = serializedObject.FindProperty("TimeSliceFramesOffset"); TimeSlicePeriod = serializedObject.FindProperty("TimeSlicePeriod"); TimeSliceTimeOffset = serializedObject.FindProperty("TimeSliceTimeOffset"); HardwareMode = serializedObject.FindProperty("HardwareMode"); RenderMode = serializedObject.FindProperty("RenderMode"); RayTracedVoxelMode = serializedObject.FindProperty("RayTracedVoxelMode"); RayTracedVoxelSizeMultiplier = serializedObject.FindProperty("RayTracedVoxelSizeMultiplier"); RayTracedVoxelSmoothCubeNormal = serializedObject.FindProperty("RayTracedVoxelSmoothCubeNormal"); RayTracedVoxelSphereFullness = serializedObject.FindProperty("RayTracedVoxelSphereFullness"); RayTracedVoxelPaddingMode = serializedObject.FindProperty("RayTracedVoxelPaddingMode"); RayTracedVoxelInternalPaddingDistance = serializedObject.FindProperty("RayTracedVoxelInternalPaddingDistance"); RayTracedVoxelSizeFadeDistance = serializedObject.FindProperty("RayTracedVoxelSizeFadeDistance"); ShowAdvancedNormalOptions = serializedObject.FindProperty("ShowAdvancedNormalOptions"); NormalQuantization = serializedObject.FindProperty("NormalQuantization"); Normal2dFade = serializedObject.FindProperty("Normal2dFade"); Normal2dStrength = serializedObject.FindProperty("Normal2dStrength"); SmoothNormalBlurRelative = serializedObject.FindProperty("SmoothNormalBlurRelative"); SmoothNormalBlurAbsolute = serializedObject.FindProperty("SmoothNormalBlurAbsolute"); EnableAutoSmoothing = serializedObject.FindProperty("EnableAutoSmoothing"); AutoSmoothingMaxAngle = serializedObject.FindProperty("AutoSmoothingMaxAngle"); EnableSmoothCorner = serializedObject.FindProperty("EnableSmoothCorner"); SmoothCornerSubdivision = serializedObject.FindProperty("SmoothCornerSubdivision"); SmoothCornerNormalBlur = serializedObject.FindProperty("SmoothCornerNormalBlur"); SmoothCornerFade = serializedObject.FindProperty("SmoothCornerFade"); InvertNormals = serializedObject.FindProperty("InvertNormals"); ForceEvaluateAllBrushes = serializedObject.FindProperty("ForceEvaluateAllBrushes"); Enable2dMode = serializedObject.FindProperty("Enable2dMode"); SurfaceShift = serializedObject.FindProperty("SurfaceShift"); MeshingMode = serializedObject.FindProperty("MeshingMode"); ShowAdvancedGeometryOptions = serializedObject.FindProperty("ShowAdvancedGeometryOptions"); SurfaceNetsDualQuadsBlend = serializedObject.FindProperty("SurfaceNetsDualQuadsBlend"); SurfaceNetsBinarySearchIterations = serializedObject.FindProperty("SurfaceNetsBinarySearchIterations"); SurfaceNetsGradientDescentIterations = serializedObject.FindProperty("SurfaceNetsGradientDescentIterations"); SurfaceNetsHighAccuracyMode = serializedObject.FindProperty("SurfaceNetsHighAccuracyMode"); SurfaceNetsGradientDescentFactor = serializedObject.FindProperty("SurfaceNetsGradientDescentFactor"); DualContouringDualQuadsBlend = serializedObject.FindProperty("DualContouringDualQuadsBlend"); DualContouringRelaxation = serializedObject.FindProperty("DualContouringRelaxation"); DualContouringSolverIterations = serializedObject.FindProperty("DualContouringSolverIterations"); DualContouringBinarySearchIterations = serializedObject.FindProperty("DualContouringBinarySearchIterations"); DualContouringGradientDescentIterations = serializedObject.FindProperty("DualContouringGradientDescentIterations"); DualContouringHighAccuracyMode = serializedObject.FindProperty("DualContouringHighAccuracyMode"); DualContouringGradientDescentFactor = serializedObject.FindProperty("DualContouringGradientDescentFactor"); ShowAdvancedSplatOptions = serializedObject.FindProperty("ShowAdvancedSplatOptions"); SplatSize = serializedObject.FindProperty("SplatSize"); SplatSizeJitter = serializedObject.FindProperty("SplatSizeJitter"); SplatNormalShift = serializedObject.FindProperty("SplatNormalShift"); SplatNormalShiftJitter = serializedObject.FindProperty("SplatNormalShiftJitter"); SplatColorJitter = serializedObject.FindProperty("SplatColorJitter"); SplatPositionJitter = serializedObject.FindProperty("SplatPositionJitter"); SplatRotationJitter = serializedObject.FindProperty("SplatRotationJitter"); SplatOrientationJitter = serializedObject.FindProperty("SplatOrientationJitter"); SplatOriginalNormalBlend = serializedObject.FindProperty("SplatOriginalNormalBlend"); SplatJitterNoisiness = serializedObject.FindProperty("SplatJitterNoisiness"); SplatCameraFacing = serializedObject.FindProperty("SplatCameraFacing"); SplatNormalsMatchCameraFacing = serializedObject.FindProperty("SplatNormalsMatchCameraFacing"); SplatShadowsMatchCameraFacing = serializedObject.FindProperty("SplatShadowsMatchCameraFacing"); SplatScreenSpaceFlattening = serializedObject.FindProperty("SplatScreenSpaceFlattening"); //SplatSmoothNormalBlend = serializedObject.FindProperty("SplatSmoothNormalBlend"); SplatCameraFacing = serializedObject.FindProperty("SplatCameraFacing"); CastShadows = serializedObject.FindProperty("CastShadows"); ReceiveShadows = serializedObject.FindProperty("ReceiveShadows"); SharedMaterial = serializedObject.FindProperty("SharedMaterial"); MasterColor = serializedObject.FindProperty("m_masterColor"); MasterEmission = serializedObject.FindProperty("m_masterEmission"); MasterMetallic = serializedObject.FindProperty("m_masterMetallic"); MasterSmoothness = serializedObject.FindProperty("m_masterSmoothness"); RenderMaterialMesh = serializedObject.FindProperty("RenderMaterialMesh"); RenderMaterialSplats = serializedObject.FindProperty("RenderMaterialSplats"); RenderMaterialDecal = serializedObject.FindProperty("RenderMaterialDecal"); //RenderMaterialRayMarchedSurface = serializedObject.FindProperty("RenderMaterialRayMarchedSurface"); //RenderMaterialRayTracedVoxels = serializedObject.FindProperty("RenderMaterialRayTracedVoxels"); RenderMaterialMode = serializedObject.FindProperty("RenderMaterialMode"); MaxRayMarchSteps = serializedObject.FindProperty("MaxRayMarchSteps"); RayMarchAccuracy = serializedObject.FindProperty("RayMarchAccuracy"); RayMarchMaxRayDistance = serializedObject.FindProperty("RayMarchMaxRayDistance"); /* RayMarchStepSize = serializedObject.FindProperty("RayMarchStepSize"); NumLightMarchSteps = serializedObject.FindProperty("NumLightMarchSteps"); RayMarchDistance = serializedObject.FindProperty("RayMarchDistance"); RayMarchVolumeDensity = serializedObject.FindProperty("RayMarchVolumeDensity"); RayMarchLight = serializedObject.FindProperty("RayMarchLight"); RayMarchVolumeAbsorption = serializedObject.FindProperty("RayMarchVolumeAbsorption"); RayMarchLightAbsorption = serializedObject.FindProperty("RayMarchLightAbsorption"); RayMarchDarknesThreshold = serializedObject.FindProperty("RayMarchDarknesThreshold"); RayMarchTransmittanceCurve = serializedObject.FindProperty("RayMarchTransmittanceCurve"); RayMarchNoiseEdgeFade = serializedObject.FindProperty("RayMarchNoiseEdgeFade"); UseRayMarchNoise = serializedObject.FindProperty("UseRayMarchNoise"); RayMarchNoiseThreshold = serializedObject.FindProperty("RayMarchNoiseThreshold"); RayMarchNoiseScrollSpeed = serializedObject.FindProperty("RayMarchNoiseScrollSpeed"); RayMarchNoiseBaseOctaveSize = serializedObject.FindProperty("RayMarchNoiseBaseOctaveSize"); RayMarchNoiseNumOctaves = serializedObject.FindProperty("RayMarchNoiseNumOctaves"); RayMarchNoiseOctaveOffsetFactor = serializedObject.FindProperty("RayMarchNoiseOctaveOffsetFactor"); */ MeshGenerationCreateNewObject = serializedObject.FindProperty("MeshGenerationCreateNewObject"); MeshGenerationRenderableMeshMode = serializedObject.FindProperty("MeshGenerationRenderableMeshMode"); MeshGenerationCreateCollider = serializedObject.FindProperty("MeshGenerationCreateCollider"); MeshGenerationForceConvexCollider = serializedObject.FindProperty("MeshGenerationForceConvexCollider"); MeshGenerationColliderSurfaceShift = serializedObject.FindProperty("MeshGenerationColliderSurfaceShift"); MeshGenerationCreateRigidBody = serializedObject.FindProperty("MeshGenerationCreateRigidBody"); GenerateColliderMeshAssetByEditor = serializedObject.FindProperty("GenerateColliderMeshAssetByEditor"); GenerateColliderMeshAssetByEditorName = serializedObject.FindProperty("GenerateColliderMeshAssetByEditorName"); MeshGenerationColliderVoxelDensity = serializedObject.FindProperty("MeshGenerationColliderVoxelDensity"); MeshGenerationAutoRigging = serializedObject.FindProperty("MeshGenerationAutoRigging"); MeshGenerationAutoRiggingAlgorithm = serializedObject.FindProperty("MeshGenerationAutoRiggingAlgorithm"); MeshGenerationAutoRiggingMaxBonesPerVertex = serializedObject.FindProperty("MeshGenerationAutoRiggingMaxBonesPerVertex"); MeshGenerationGenerateTextureUV = serializedObject.FindProperty("MeshGenerationGenerateTextureUV"); MeshGenerationGenerateLightMapUV = serializedObject.FindProperty("MeshGenerationGenerateLightMapUV"); MeshGenerationWeldVertices = serializedObject.FindProperty("MeshGenerationWeldVertices"); MeshGenerationLockOnStart = serializedObject.FindProperty("MeshGenerationLockOnStart"); GenerateMeshAssetByEditor = serializedObject.FindProperty("GenerateMeshAssetByEditor"); GenerateMeshAssetByEditorName = serializedObject.FindProperty("GenerateMeshAssetByEditorName"); RecursiveLockMeshByEditor = serializedObject.FindProperty("RecursiveLockMeshByEditor"); RememberLockedMeshMaterialByEditor = serializedObject.FindProperty("RememberLockedMeshMaterialByEditor"); DrawGenerateSdfGizmos = serializedObject.FindProperty("DrawGenerateSdfGizmos"); GenerateSdfByEditorName = serializedObject.FindProperty("GenerateSdfByEditorName"); GenerateSdfCenter = serializedObject.FindProperty("GenerateSdfCenter"); GenerateSdfDimension = serializedObject.FindProperty("GenerateSdfDimension"); GenerateSdfTextureSize = serializedObject.FindProperty("GenerateSdfTextureSize"); UseCutoffVolume = serializedObject.FindProperty("UseCutoffVolume"); CutoffVolumeCenter = serializedObject.FindProperty("CutoffVolumeCenter"); CutoffVolumeSize = serializedObject.FindProperty("CutoffVolumeSize"); ClickSelection = serializedObject.FindProperty("ClickSelection"); AlwaysDrawGizmos = serializedObject.FindProperty("AlwaysDrawGizmos"); DrawRawBrushBounds = serializedObject.FindProperty("DrawRawBrushBounds"); DrawComputeBrushBounds = serializedObject.FindProperty("DrawComputeBrushBounds"); DrawRenderBounds = serializedObject.FindProperty("DrawRenderBounds"); DrawVoxelNodes = serializedObject.FindProperty("DrawVoxelNodes"); DrawVoxelNodesDepth = serializedObject.FindProperty("DrawVoxelNodesDepth"); DrawVoxelNodesScale = serializedObject.FindProperty("DrawVoxelNodesScale"); } private void ShowMaterialOptions() { var renderer = (MudRendererBase) target; Header("Material"); Property(SharedMaterial, "Shared Material", "External material used as the renderer's master material." ); if (SharedMaterial.objectReferenceValue == null) { Property(MasterColor, "Master Color", "Master color multiplier." ); Property(MasterEmission, "Master Emission", "Master emission multiplier. Alpha is not used." ); Property(MasterMetallic, "Master Metallic", "Master metallic multiplier." ); Property(MasterSmoothness, "Master Smoothness", "Master smoothness multiplier." ); switch (renderer.RenderModeCategory) { case MudRendererBase.RenderModeCategoryEnum.Mesh: Property(RenderMaterialMesh, "Render Material"); break; case MudRendererBase.RenderModeCategoryEnum.Splats: Property(RenderMaterialSplats, "Render Material"); break; case MudRendererBase.RenderModeCategoryEnum.Decal: Property(RenderMaterialDecal, "Render Material"); break; /* case MudRendererBase.RenderModeCategoryEnum.RayMarchedSurface: Property(RenderMaterialRayMarchedSurface, "Render Material"); break; case MudRendererBase.RenderModeCategoryEnum.RayTracedVoxels: Property(RenderMaterialRayTracedVoxels, "Render Material"); break; */ } Property(RenderMaterialMode, " Render Material Mode", "A renderer enables & disables its render material's shader keywords to select the correct shader variant, " + "which requires making a local copy of the render material. " + "This means the renderer needs to refresh its copy if the referenced render material has changed. " + "This is done automatically in the editor for convenience, but in builds the renderer uses the render material mode to decide how to refresh the copy.\n\n" + "Static - The render material is not expected to change. It is only copied once. Manual refresh of the copy can be done by calling the MarkMaterialDirty method.\n\n" + "Dynamic - The render material is expected to be constantly changing, and it is copied once every frame. This may impact performance."); /* Indent(); //Property(MaxRayMarchSteps, "Ray Steps"); Property(RayMarchStepSize, "Ray Step Size"); Property(NumLightMarchSteps, "Light Steps"); //Property(RayMarchDistance, "Total Ray Distance"); Property(RayMarchVolumeDensity, "Volume Density"); Property(RayMarchLight, "Light", "Type supported: directional, point."); Property(RayMarchVolumeAbsorption, "Volume Absorption"); Property(RayMarchLightAbsorption, "Light Absorption"); Property(RayMarchDarknesThreshold, "Darkness Threshold"); Property(RayMarchTransmittanceCurve, "Transmittance Curve"); Property(UseRayMarchNoise, "Use Noise"); if (UseRayMarchNoise.boolValue) { Indent(); Property(RayMarchNoiseThreshold, "Threshold"); Property(RayMarchNoiseEdgeFade, "Edge Fade"); Property(RayMarchNoiseScrollSpeed, "Scroll Speed"); Property(RayMarchNoiseBaseOctaveSize, "Base Octave Size"); Property(RayMarchNoiseNumOctaves, "Octaves"); Property(RayMarchNoiseOctaveOffsetFactor, "Octave Offset Factor"); Unindent(); } Unindent(); */ } Space(); } public virtual void OnEnable() { InitSerializedProperties(); EditorApplication.update += Update; } private void OnDisable() { EditorApplication.update -= Update; } private static string IntCountString(long n, bool space = false, string suffix = "") { if (n < 1024) return n.ToString() + (space ? " " : "") + suffix; if (n < 1048576) return (n / 1024.0f).ToString("N1") + (space ? " " : "") + "K" + suffix; return (n / 1048576.0f).ToString("N1") + (space ? " " : "") + "M" + suffix; } public override void OnInspectorGUI() { serializedObject.Update(); var renderer = (MudRendererBase) target; bool gpuMode = false; { Header("MudBun Version " + MudBun.Version); if (MudBun.IsFreeVersion) { Indent(); Text("Trial Version Limitations:"); Text(" - Watermark."); Text(" - Limited voxel density & triangle count for mesh utilities."); Text(" - No full source code."); Text(" - No commercial use."); Unindent(); } Space(); } if (!renderer.MeshLocked) { // editor { Header("Editor"); Property(ClickSelection, "Click Selection"); Space(); } // end: editor // budgets { Header("Memory Budgets"); Property(MaxVoxelsK, "Max Voxels (K)", "Maximum number of voxels times 1024.\n\n" + "A voxel is a minimum unit where SDFs are evaluated.\n\n" + "Try increasing this value if voxels appear missing.\n\n" + "The higher this value, the more GPU memory is used." ); Property(MaxChunks, "Max Voxel Chunks", "Maximum number voxel chunks.\n\n" + "A voxel chunk is a block of space that can contain multiple voxels." + "The larger the space spanned by solids, the more voxel chunks are needed.\n\n" + "Try increasing this value if voxels appear missing.\n\n" + "The higher this value, the more GPU memory is used." ); Property(ShowGpuMemoryUsage, "(*) Show/Adjust Usage", "(*): Impacts performance."); if (ShowGpuMemoryUsage.boolValue) { Text(" Used / Allocated: ", FontStyle.Bold); long memoryAllocated = renderer.LocalResourceGpuMemoryAllocated; long memoryUsed = Math.Min(memoryAllocated, renderer.LocalResourceGpuMemoryUsed); float memoryUtilizationPercent = 100.0f * (memoryUsed / Mathf.Max(MathUtil.Epsilon, memoryAllocated)); Text ( " GPU Memory - " + IntCountString(memoryUsed, true, "B") + " / " + IntCountString(memoryAllocated, true, "B") + " (" + memoryUtilizationPercent.ToString("N1") + "%)" ); int voxelsAllocated = renderer.NumVoxelsAllocated; int voxelsUsed = Mathf.Min(voxelsAllocated, renderer.NumVoxelsUsed); float voxelUtilizationPercent = 100.0f * (voxelsUsed / Mathf.Max(MathUtil.Epsilon, voxelsAllocated)); Text ( " Voxels - " + IntCountString(voxelsUsed) + " / " + IntCountString(voxelsAllocated) + " (" + voxelUtilizationPercent.ToString("N1") + "%)" ); int chunksAllocated = renderer.NumChunksAllocated; int chunksUsed = Mathf.Min(chunksAllocated, renderer.NumChunksUsed); float chunkUtilizationPercent = 100.0f * (chunksUsed / Mathf.Max(MathUtil.Epsilon, chunksAllocated)); Text ( " Voxel Chunks - " + IntCountString(chunksUsed) + " / " + IntCountString(chunksAllocated) + " (" + chunkUtilizationPercent.ToString("N1") + "%)" ); int vertsAllocated = renderer.NumVerticesAllocated; int vertsGenerated = Mathf.Min(vertsAllocated, renderer.NumVerticesGenerated); float vertUtilizationPercent = 100.0f * (vertsGenerated / Mathf.Max(MathUtil.Epsilon, vertsAllocated)); Text ( " Vertices - " + IntCountString(vertsGenerated) + " / " + IntCountString(vertsAllocated) + " (" + vertUtilizationPercent.ToString("N1") + "%)" ); Property(AutoAdjustBudgetsToHighWaterMarks, " Auto-Adjust Budgets"); Property(AutoAdjustBudgetsToHighWaterMarksMarginPercent, " Margin Percent"); EditorGUILayout.HelpBox("Showing GPU memory usage impacts performance.", MessageType.Warning); } Space(); } // end budgets // render { Header("Render"); Property(VoxelDensity, "(*^) Voxel Density", "Number of voxels per unit distance.\n\n" + "Higher density means more pixels and more computation.\n\n" + "(*): Impacts performance.\n" + "(^): Impacts GPU memory usage." ); Property(ComputeMode, "Compute Mode", "Controls the timing and frequency of volumetric re-computation.\n\n" + "Auto - Automatic re-computation upon detection of transform changes to brushes. The downside is that transform changes to the renderer will also trigger re-computation, even if the brushes haven't changed.\n\n" + "Manual - Only perform re-computation if this renderer's MarkNeedsRecompute() method is called in script. If used properly, this mode is the most computationally efficient.\n\n" + "Every Frame - Force re-computation every frame.\n\n" + "Time Sliced - Automatic re-computation on a lower frequency than once every frame. Useful for improving performance with staggered computation, or achieving stopmotion-like visuals." ); if (ComputeMode.intValue == (int) MudRendererBase.ComputeModeEnum.TimeSliced) { Indent(); Property(TimeSliceMode, "Time Slice Mode", "By Frames Auto Offset - Automatic re-compute after this many frames, with frame offset determined by internal renderer index.\n\n" + "By Frames Manual Offset - Automatic re-compute after this many frames, with explicitly specified frame offset.\n\n" + "By Period Auto Offset - Automatic re-compute after this many seconds, with time offset determined by internal renderer index.\n\n" + "By Period Manual Offset - Automatic re-compute after this many seconds, with explicitly specified time offset.\n\n" ); switch ((MudRendererBase.TimeSliceModeEnum) TimeSliceMode.intValue) { case MudRendererBase.TimeSliceModeEnum.ByFramesAutoOffset: Indent(); Property(TimeSliceFrames, "Frames"); Unindent(); break; case MudRendererBase.TimeSliceModeEnum.ByFramesManualOffset: Indent(); Property(TimeSliceFrames, "Frames"); Property(TimeSliceFramesOffset, "Offset"); Unindent(); break; case MudRendererBase.TimeSliceModeEnum.ByPeriodAutoOffset: Indent(); Property(TimeSlicePeriod, "Period"); Unindent(); break; case MudRendererBase.TimeSliceModeEnum.ByPeriodManualOffset: Indent(); Property(TimeSlicePeriod, "Period"); Property(TimeSliceTimeOffset, "Offset"); Unindent(); break; } Unindent(); } Property(CastShadows, "Cast Shadows"); bool receiveShadowsEffective = false; var camera = Camera.main; if (camera) { switch (MudRendererBase.RenderPipeline) { case ResourcesUtil.RenderPipelineEnum.BuiltIn: if (camera.actualRenderingPath == RenderingPath.Forward) receiveShadowsEffective = true; break; } if (receiveShadowsEffective) Property(ReceiveShadows, "Receive Shadows"); } Property(ForceEvaluateAllBrushes, "Force Evaluate All Brushes", "Check to force evaluation of all brushes. By default, brushes will be skipped for a voxel tree node if their AABBs are not intersecting." + "Checking this option will force all brushes to be evaluated for every voxel tree node.\n\n" + "This option is automatically checked in 2D mode if the 2D/3D Normal Blend value is larger than zero with the presence of any subtractive solid brush.\n\n" + "This option might need to be manually checked if there are subtraction brushes in 2D mode and the SDF value is used in the shader of the render material." ); /* Property(HardwareMode, "Hardware Mode", "GPU - The default and original MudBun implementation. Supports all features. Only actively tested for Windows platforms.\n\n" + "CPU - Less performant implementation than GPU mode. Supports only a subset of features, but has a much wider platform compatibility." ); */ gpuMode = ((MudRendererBase.HardwareModeEnum) HardwareMode.intValue) == MudRendererBase.HardwareModeEnum.Gpu; Property(RenderMode, "Render Mode", "Smooth Mesh - Mesh with smooth normals. More performance intensive than flat mesh and splats.\n\n" + "Flat Mesh - Mesh with flat normals.\n\n" + "Circle Splats - Flat circle splats scattered on solid surface. Good for circular textures.\n\n" + "Quad Splats - Flat quad splats scattered on solid surface. Doubles the triangle count compared to circle splats. Good for square textures.\n\n" + "Decal - Decals composed of brushes.\n\n" + "Ray-Marched Surface - [Experimental / Early URP only] Ray-based render mode that uses ray-marching to render each pixel. Performance is heavily impacted by the number of pixels rendered.\n\n" + "Ray-Traced Voxels - [Experimental / Early URP only] Ray-based render mode that visualizes voxel cells." ); if (gpuMode) { switch ((MudRendererBase.RenderModeEnum) RenderMode.intValue) { case MudRendererBase.RenderModeEnum.FlatMesh: case MudRendererBase.RenderModeEnum.SmoothMesh: case MudRendererBase.RenderModeEnum.CircleSplats: case MudRendererBase.RenderModeEnum.QuadSplats: Property(MeshingMode, "Meshing Mode", "Marching Cubes - Default meshing algorithm. Good balance between speed and quality.\n\n" + "Dual Quads - Faster than marching cubes. Gives a stylized blocky look.\n\n" + "Surface Nets - Slightly slower than marching cubes. Comes with the added benefit of blending with dual quads.\n\n" + "Dual Contouring - Much slower than surface nets, but better at preserving sharp features. Good for edit-time hard-surface modeling. Comes with the added benefit of blending with dual quads." ); Property(Enable2dMode, "2D Mode", "Make everything operate on the 2D XY plane." ); if (!Enable2dMode.boolValue) { Property(InvertNormals, "Invert Normals"); } break; /* case MudRendererBase.RenderModeEnum.RayMarchedSurface: Property(MaxRayMarchSteps, "Max Ray March Steps"); Property(RayMarchAccuracy, "Ray March Accuracy"); Property(RayMarchMaxRayDistance, "Max Ray March Distance"); EditorGUILayout.HelpBox("The Ray-Marched Surface render mode is experimental and works in early URP only.", MessageType.Warning); break; case MudRendererBase.RenderModeEnum.RayTracedVoxels: Property(RayTracedVoxelMode, "Voxel Mode"); Property(RayTracedVoxelSizeMultiplier, "Voxel Size"); switch ((MudRendererBase.RayTracedVoxelModeEnum) RayTracedVoxelMode.intValue) { case MudRendererBase.RayTracedVoxelModeEnum.FacetedCubes: Property(RayTracedVoxelSmoothCubeNormal, " Smooth Cube Normal"); break; case MudRendererBase.RayTracedVoxelModeEnum.SmoothSpheres: case MudRendererBase.RayTracedVoxelModeEnum.FlatSpheres: Property(RayTracedVoxelSphereFullness, " Sphere Fullness"); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(" Presets"); if (GUILayout.Button("Unit")) { RayTracedVoxelSphereFullness.floatValue = 0.0f; } if (GUILayout.Button("Cell")) { RayTracedVoxelSphereFullness.floatValue = 0.565f; } if (GUILayout.Button("Dice")) { RayTracedVoxelSphereFullness.floatValue = 0.765f; } EditorGUILayout.EndHorizontal(); break; } Property(RayTracedVoxelInternalPaddingDistance, "Internal Padding", "Distance within SDF isosurface to create additional internal voxels."); Property(RayTracedVoxelSizeFadeDistance, "Size Fade Distance", "Distance over which to fade voxel size from zero to full size starting from the isosurface."); EditorGUILayout.HelpBox("The Ray-Traced Voxels render mode is experimental and works in early URP only.", MessageType.Warning); break; */ } } bool meshGenerationRenderMode = false; switch ((MudRendererBase.RenderModeEnum) RenderMode.intValue) { case MudRendererBase.RenderModeEnum.FlatMesh: case MudRendererBase.RenderModeEnum.SmoothMesh: case MudRendererBase.RenderModeEnum.CircleSplats: case MudRendererBase.RenderModeEnum.QuadSplats: meshGenerationRenderMode = true; break; } serializedObject.ApplyModifiedProperties(); // advanced meshing options Property(ShowAdvancedGeometryOptions, "Advanced Geometry Options"); if (ShowAdvancedGeometryOptions.boolValue) { Indent(); Property(SurfaceShift, "Surface Shift", "Apply a surface shift along surface normals." ); if (gpuMode && meshGenerationRenderMode) { switch (renderer.MeshingMode) { case MudRendererBase.MeshingModeEnum.SurfaceNets: case MudRendererBase.MeshingModeEnum.DualContouring: switch (renderer.MeshingMode) { case MudRendererBase.MeshingModeEnum.SurfaceNets: Property(SurfaceNetsDualQuadsBlend, "Dual Quads Blend"); //Property(SurfaceNetsBinarySearchIterations, " Binary Search Iterations"); // hidden as this doesn't seem to buy us much //Property(SurfaceNetsGradientDescentIterations, "Gradient Descent Iterations"); //Property(SurfaceNetsGradientDescentFactor, "Gradient Descent Factor"); //Property(SurfaceNetsHighAccuracyMode, "(*) High Accuracy"); break; case MudRendererBase.MeshingModeEnum.DualContouring: Property(DualContouringDualQuadsBlend, "Dual Quads Blend"); Property(DualContouringRelaxation, "Relaxation"); //Property(DualContouringSolverIterations, "Solver Iterations"); //Property(DualContouringBinarySearchIterations, "Binary Search Iterations"); //Property(DualContouringGradientDescentIterations, "Gradient Descent Iterations"); //Property(DualContouringGradientDescentFactor, "Gradient Descent Factor"); Property(DualContouringHighAccuracyMode, "(*) High Accuracy", "(*): Impacts performance." ); break; } break; } } Unindent(); } // end: advanced meshing options // advanced normal options if (gpuMode) { switch (renderer.MeshingMode) { case MudRendererBase.MeshingModeEnum.MarchingCubes: case MudRendererBase.MeshingModeEnum.SurfaceNets: case MudRendererBase.MeshingModeEnum.DualContouring: switch (renderer.RenderMode) { case MudRendererBase.RenderModeEnum.FlatMesh: case MudRendererBase.RenderModeEnum.SmoothMesh: Property(ShowAdvancedNormalOptions, "Advanced Normal Options"); if (ShowAdvancedNormalOptions.boolValue) { Indent(); if (Enable2dMode.boolValue) { Property(Normal2dFade, "2D Normal Fade", "How far inward to fade from outward-pointing normals to flat normals." ); Property(Normal2dStrength, "2D Normal Strength", "How strong the outward-pointing normals contribute to the final normals." ); } Property(NormalQuantization, "Normal Quantization"); if (!Enable2dMode.boolValue) Property(EnableAutoSmoothing, "Enable Auto-Smoothing (^)"); if (EnableAutoSmoothing.boolValue && !Enable2dMode.boolValue) { Indent(); Property(AutoSmoothingMaxAngle, "Auto-Smoothing Max Angle"); if (renderer.MeshingMode == MudRendererBase.MeshingModeEnum.DualContouring) { Property(EnableSmoothCorner, "(*) Enable Smooth Corner", "(*): Impacts performance." ); if (EnableSmoothCorner.boolValue) { Indent(); Property(SmoothCornerSubdivision, "(*) Subdivision", "(*): Impacts performance." ); Property(SmoothCornerNormalBlur, "Blur"); Property(SmoothCornerFade, "Fade"); Unindent(); } } Unindent(); } else { if (renderer.RenderMode == MudRendererBase.RenderModeEnum.SmoothMesh) Property(SmoothNormalBlurAbsolute, "Smooth Normal Blur"); } Unindent(); } break; } break; } } // end: advanced normal options // advanced splat options switch (renderer.RenderModeCategory) { case MudRendererBase.RenderModeCategoryEnum.Splats: Property(ShowAdvancedSplatOptions, "Advanced Splats Options"); if (ShowAdvancedSplatOptions.boolValue) { Indent(); Property(SplatSize, "Size"); Property(SplatSizeJitter, "Size Jitter"); Property(SplatNormalShift, "Normal Shift"); Property(SplatNormalShiftJitter, "Normal Shift Jitter"); Property(SplatColorJitter, "Color Jitter"); Property(SplatPositionJitter, "Position Jitter"); Property(SplatRotationJitter, "Rotation Jitter"); Property(SplatOrientationJitter, "Orientation Jitter"); Property(SplatOriginalNormalBlend, " Original Normal Blend"); Property(SplatJitterNoisiness, "Jitter Noisiness"); Property(SplatCameraFacing, "Camera Facing"); Property(SplatNormalsMatchCameraFacing, " Normals Match Camera Facing"); Property(SplatShadowsMatchCameraFacing, " Shadows Match Camera Facing"); Property(SplatScreenSpaceFlattening, "Screen-Space Flattening"); //Property(SplatSmoothNormalBlend, "Smooth Normal Blend"); Unindent(); } break; } // end: advanced splat options Space(); } // end: render ShowMaterialOptions(); // shaders { Header("Resources"); if (GUILayout.Button(new GUIContent("Reload (Needed After Shader Edits)", "Reload shaders and GPU resources. This is necessary after editing shaders."))) { MudRendererBase.ReloadAllShaders(); } Space(); } // end: shaders // mesh utilities if (gpuMode && !Enable2dMode.boolValue) { Header("Mesh Utilities"); if (MudBun.IsFreeVersion) { Text("Trial Version Limitations:"); Text(" - Voxel density limited to " + ((int) MudRendererBase.MaxMeshGenerationVoxelDensityFreeVersion)); Text(" - Triangle count limited to " + MudRendererBase.MaxMeshGenerationTrianglesFreeVersion); } Property(MeshGenerationLockOnStart, "Lock On Start", "Lock mesh on start in play mode. This can save file size by not saving the mesh, but the performance will take a hit on lock." ); Property(MeshGenerationCreateNewObject, "Create New Object", "Check to create a new object when locking mesh." ); Property(MeshGenerationCreateCollider, "Create Collider", "Check to create a collider when locking mesh." ); if (MeshGenerationCreateCollider.boolValue) { Indent(); Property(MeshGenerationColliderVoxelDensity, "Collider Voxel Density", "Voxel density used for creating collider." ); Property(MeshGenerationForceConvexCollider, "Force Convex Collider" ); Property(MeshGenerationColliderSurfaceShift, "Surface Shift" ); Property(MeshGenerationCreateRigidBody, "Create Rigid Body (Convex)" ); Property(GenerateColliderMeshAssetByEditor, "Generate Mesh Asset", "Generate a mesh asset. This is needed for prefabs that contain collider meshes." ); if (GenerateColliderMeshAssetByEditor.boolValue) { if (renderer.GenerateColliderMeshAssetByEditorName.Equals("")) { serializedObject.ApplyModifiedProperties(); renderer.ValidateAssetNames(); serializedObject.Update(); } Indent(); Property(GenerateColliderMeshAssetByEditorName, "File Name" ); Unindent(); } Unindent(); } Property(MeshGenerationRenderableMeshMode, "Renderable Mesh Mode", "None - No renderable mesh is created.\n\n" + "Procedural - Run compute shader once and keep rendering from computed data. No standard mesh will be generated nor serialized.\n\n" + "Mesh Renderer - Run compute shader once and generate a standard mesh. The mesh will be serialized and rendererd using Unity's MeshRenderer or SkinnedMeshRenderer." ); if ((MudRendererBase.RenderableMeshMode) MeshGenerationRenderableMeshMode.intValue == MudRendererBase.RenderableMeshMode.MeshRenderer) { Indent(); Property(MeshGenerationAutoRigging, "Auto-Rigging", "Check to auto-rig locked mesh with brushes flagged as bones." ); /* if (MeshGenerationAutoRigging.boolValue) { Property(MeshGenerationAutoRiggingAlgorithm, " Algorithm", "Default - Takes brush blends into account.\n\n" + "Legacy - Ignores brush blends and computes bone weights differently."); } */ Property(MeshGenerationAutoRiggingMaxBonesPerVertex, "Max Bones Per Vertex", "Maximum number of bones each vertex can be bound to." ); Property(MeshGenerationGenerateTextureUV, "Generate Texture UV (UV1)" ); Property(MeshGenerationGenerateLightMapUV, "Generate Light Map UV (UV2)" ); Property(MeshGenerationWeldVertices, "Weld Vertices" ); Property(GenerateMeshAssetByEditor, "Generate Mesh Asset", "Generate a mesh asset. This is needed for prefabs that contain locked meshes." ); if (GenerateMeshAssetByEditor.boolValue) { if (renderer.GenerateMeshAssetByEditorName.Equals("")) { serializedObject.ApplyModifiedProperties(); renderer.ValidateAssetNames(); serializedObject.Update(); } Indent(); Property(GenerateMeshAssetByEditorName, "File Name" ); Unindent(); } Unindent(); } Property(RecursiveLockMeshByEditor, "Recursive Lock", "Recursively lock all renderers within this renderer's hierarchy." ); /* if (GUILayout.Button("Rectify Non-Unit-Scaled Parents")) { renderer.RectifyNonUnitScaledParents(); } */ if (GUILayout.Button("Lock Mesh")) { LockMesh(); renderer.MeshGenerationLockOnStartByEditor = true; InitSerializedProperties(); } Space(); } // end: mesh utilities // SDF utilities if (gpuMode) { Header("SDF Utilities"); Property(DrawGenerateSdfGizmos, "Draw SDF Generation Gizmos" ); if (renderer.GenerateSdfByEditorName.Equals("")) { serializedObject.ApplyModifiedProperties(); renderer.ValidateAssetNames(); serializedObject.Update(); } Property(GenerateSdfByEditorName, "File Name" ); Property(GenerateSdfCenter, "SDF Center", "Point in SDF mapped to center of SDF texture" ); Property(GenerateSdfDimension, "SDF Dimension", "Dimension in SDF mapped to size of SDF texture" ); Property(GenerateSdfTextureSize, "SDF Texture Size" ); if (GUILayout.Button("Generate SDF Texture")) { GenerateSdf(); } Space(); } // end: SDF utilities // optimization { // TODO /* Header("Optimization"); Property(UseCutoffVolume, "Use Cutoff Volume"); if (UseCutoffVolume.boolValue) { Indent(); Property(CutoffVolumeCenter, "Center"); Property(CutoffVolumeSize, "Size"); Unindent(); } Space(); */ } // end: optimziation // debug { Header("Debug"); Property(AlwaysDrawGizmos, "Always Draw Gizmos"); Property(DrawRawBrushBounds, "Draw Raw Brush Bounds", "Draw raw bounding volumes for each brush." ); Property(DrawComputeBrushBounds, "Draw Compute Bounds", "Draw the expanded bounding volume actually used for computation for each brush." ); Property(DrawRenderBounds, "Draw Render Bounds", "Draw the bounding volume used for culling the renderer." ); Property(DrawVoxelNodes, "Draw Voxel Nodes", "Draw hierarchical voxel nodes." ); Indent(); Property(DrawVoxelNodesDepth, "Node Depth", "Draw voxel nodes at a specific hierarchical depth.\n\n" + "-1 means drawing all depths." ); Property(DrawVoxelNodesScale, "Node Scale"); Unindent(); Space(); } // end: debug // extra info { Header("Extra Info"); Text($"Current Tracked Version: {renderer.CurrentTrackedVersion}"); Text($"Previous Tracked Version: {renderer.PreviousTrackedVersion}"); Text($"First Tracked Version: {renderer.FirstTrackedVersion}"); Space(); } // end: extra info // legends { Header("Legends"); Text("(*): Impacts performance."); Text("(^): Impacts GPU memory usage."); } // end: legends } else // mesh locked? { if (MeshGenerationRenderableMeshMode.intValue == (int) MudRendererBase.RenderableMeshMode.Procedural) ShowMaterialOptions(); Header("Mesh Utilities"); Property(RecursiveLockMeshByEditor, "Recursive Unlock", "Recursively unlock all renderers within this renderer's hierarchy." ); Property(RememberLockedMeshMaterialByEditor, "Remember Material"); if (GUILayout.Button("Unlock Mesh")) { foreach (var r in targets.Select(x => (MudRenderer) x)) { if (r == null) continue; if (!r.MeshLocked) continue; if (RememberLockedMeshMaterialByEditor.boolValue) renderer.RememberLockedMeshMaterial(); UnlockMesh(r.transform, RecursiveLockMeshByEditor.boolValue); } } } serializedObject.ApplyModifiedProperties(); } protected virtual void LockMesh() { } private void UnlockMesh(Transform t, bool recursive) { if (t == null) return; if (recursive) { for (int i = 0; i < t.childCount; ++i) UnlockMesh(t.GetChild(i), recursive); } var renderer = t.GetComponent(); if (renderer == null) return; if (!renderer.MeshLocked) return; renderer.UnlockMesh(); } protected virtual void GenerateSdf() { } private static readonly float RepaintInterval = 0.2f; private float m_repaintTimer = 0.0f; private void Update() { m_repaintTimer += Time.deltaTime; if (m_repaintTimer < RepaintInterval) return; m_repaintTimer = Mathf.Repeat(m_repaintTimer, RepaintInterval); try { if (Selection.activeGameObject != ((MudRendererBase) target).gameObject) return; } catch (MissingReferenceException) { // renderer has been destroyed return; } UpdateGpuMemoryUsage(); } private void UpdateGpuMemoryUsage() { if (!ShowGpuMemoryUsage.boolValue) return; Repaint(); } protected void DestroyAllChildren(Transform t, bool isRoot = true) { if (t == null) return; var aChild = new Transform[t.childCount]; for (int i = 0; i < t.childCount; ++i) aChild[i] = t.GetChild(i); foreach (var child in aChild) DestroyAllChildren(child, false); if (!isRoot) DestroyImmediate(t.gameObject); } public bool HasFrameBounds() { return true; } public Bounds OnGetFrameBounds() { var renderer = (MudRendererBase) target; var bounds = Aabb.Empty; foreach (var brush in renderer.Brushes) if (brush != null) bounds.Include(brush.BoundsRs); if (bounds.IsEmpty) return new Bounds(renderer.transform.position, Vector3.one); bounds.Expand(renderer.SurfaceShift); bounds.Transform(renderer.transform); return new Bounds(bounds.Center, bounds.Size); } } }