diff --git a/Assets/Prefabs/PaintHudPanel.prefab b/Assets/Prefabs/PaintHudPanel.prefab index b2083b7..59bddde 100644 --- a/Assets/Prefabs/PaintHudPanel.prefab +++ b/Assets/Prefabs/PaintHudPanel.prefab @@ -691,6 +691,81 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 1 +--- !u!1 &2796953647161411337 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1063095855848914906} + - component: {fileID: 4779579190783925503} + - component: {fileID: 7768445624927669295} + m_Layer: 0 + m_Name: Check + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1063095855848914906 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2796953647161411337} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2436255323372548463} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -6, y: -6} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4779579190783925503 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2796953647161411337} + m_CullTransparentMesh: 1 +--- !u!114 &7768445624927669295 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2796953647161411337} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 0.4, g: 0.75, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &2975656316574628353 GameObject: m_ObjectHideFlags: 0 @@ -845,6 +920,7 @@ RectTransform: - {fileID: 7851905535026198058} - {fileID: 8454988543450548733} - {fileID: 8376856457293460334} + - {fileID: 2492869794191913777} m_Father: {fileID: 7387928529902370415} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} @@ -970,6 +1046,7 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: + - {fileID: 3634224269323718290} - {fileID: 4342959377125151652} - {fileID: 4923756974383574983} - {fileID: 3665381071208701621} @@ -3028,6 +3105,250 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 1 +--- !u!1 &7726549018629373785 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2942748398039547410} + - component: {fileID: 1892836100004737345} + - component: {fileID: 7199706241071600495} + m_Layer: 0 + m_Name: Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2942748398039547410 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7726549018629373785} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3098102965295711989} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 12, y: 0} + m_SizeDelta: {x: -24, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1892836100004737345 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7726549018629373785} + m_CullTransparentMesh: 1 +--- !u!114 &7199706241071600495 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7726549018629373785} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Spray + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 964ae5cd392ba08499b88de012ff8213, type: 2} + m_sharedMaterial: {fileID: 8234806113954670000, guid: 964ae5cd392ba08499b88de012ff8213, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 14 + m_fontSizeBase: 14 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 8192 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &7935444948555430782 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3098102965295711989} + - component: {fileID: 7542572879761213426} + - component: {fileID: 4136943736630015774} + m_Layer: 0 + m_Name: Toggle_Spray + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3098102965295711989 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935444948555430782} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2436255323372548463} + - {fileID: 2942748398039547410} + m_Father: {fileID: 3634224269323718290} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &7542572879761213426 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935444948555430782} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement + m_IgnoreLayout: 0 + m_MinWidth: -1 + m_MinHeight: 22 + m_PreferredWidth: -1 + m_PreferredHeight: 22 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 +--- !u!114 &4136943736630015774 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935444948555430782} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Toggle + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 2223657820663403196} + toggleTransition: 1 + graphic: {fileID: 7768445624927669295} + m_Group: {fileID: 0} + onValueChanged: + m_PersistentCalls: + m_Calls: [] + m_IsOn: 0 --- !u!1 &7992086713309035181 GameObject: m_ObjectHideFlags: 0 @@ -3315,6 +3636,213 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 1 +--- !u!1 &8719893497228303187 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3634224269323718290} + - component: {fileID: 3171558510481401849} + - component: {fileID: 2983136256694628734} + - component: {fileID: 8174275387821514335} + - component: {fileID: 7781015511497060513} + - component: {fileID: 2733789635056426743} + m_Layer: 0 + m_Name: Panel_Spray + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3634224269323718290 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8719893497228303187} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3098102965295711989} + m_Father: {fileID: 7387928529902370415} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 0.5, y: 0} + m_AnchoredPosition: {x: -900, y: 20} + m_SizeDelta: {x: 280, y: 0} + m_Pivot: {x: 0.5, y: 0} +--- !u!222 &3171558510481401849 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8719893497228303187} + m_CullTransparentMesh: 1 +--- !u!114 &2983136256694628734 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8719893497228303187} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 0.09, g: 0.09, b: 0.11, a: 0.9} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &8174275387821514335 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8719893497228303187} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup + m_Padding: + m_Left: 12 + m_Right: 12 + m_Top: 12 + m_Bottom: 12 + m_ChildAlignment: 0 + m_Spacing: 10 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 0 + m_ChildControlWidth: 1 + m_ChildControlHeight: 1 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &7781015511497060513 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8719893497228303187} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter + m_HorizontalFit: 0 + m_VerticalFit: 2 +--- !u!225 &2733789635056426743 +CanvasGroup: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8719893497228303187} + m_Enabled: 1 + m_Alpha: 0.5 + m_Interactable: 1 + m_BlocksRaycasts: 1 + m_IgnoreParentGroups: 0 +--- !u!1 &8749262302241821053 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2436255323372548463} + - component: {fileID: 3347812339218018783} + - component: {fileID: 2223657820663403196} + m_Layer: 0 + m_Name: Box + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2436255323372548463 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8749262302241821053} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1063095855848914906} + m_Father: {fileID: 3098102965295711989} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 0, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 18, y: 18} + m_Pivot: {x: 0, y: 0.5} +--- !u!222 &3347812339218018783 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8749262302241821053} + m_CullTransparentMesh: 1 +--- !u!114 &2223657820663403196 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8749262302241821053} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 0.2, g: 0.2, b: 0.24, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &8994899293704923971 GameObject: m_ObjectHideFlags: 0 @@ -3350,6 +3878,7 @@ RectTransform: m_Children: - {fileID: 4936290884599347572} - {fileID: 265579357309326112} + - {fileID: 5867721996605831938} m_Father: {fileID: 7387928529902370415} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} @@ -3814,6 +4343,140 @@ RectTransform: m_CorrespondingSourceObject: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} m_PrefabInstance: {fileID: 889416535110432801} m_PrefabAsset: {fileID: 0} +--- !u!1001 &1796302450561085447 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 8239117552197655738} + m_Modifications: + - target: {fileID: 3983135770763320344, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_text + value: Alpha + objectReference: {fileID: 0} + - target: {fileID: 4562821246304388210, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_text + value: 1,00 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Pivot.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Pivot.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_SizeDelta.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_SizeDelta.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5381822788309442337, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5381822788309442337, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6369511037010461285, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Value + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6993348405844982934, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6993348405844982934, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6993348405844982934, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7273293964960786644, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Name + value: Slider_OcclusionAlpha + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} +--- !u!224 &5867721996605831938 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + m_PrefabInstance: {fileID: 1796302450561085447} + m_PrefabAsset: {fileID: 0} --- !u!1001 &4383637231553397496 PrefabInstance: m_ObjectHideFlags: 0 @@ -4188,3 +4851,137 @@ RectTransform: m_CorrespondingSourceObject: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} m_PrefabInstance: {fileID: 5344768466403301413} m_PrefabAsset: {fileID: 0} +--- !u!1001 &7717931987048658996 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 4923756974383574983} + m_Modifications: + - target: {fileID: 3983135770763320344, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_text + value: Alpha + objectReference: {fileID: 0} + - target: {fileID: 4562821246304388210, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_text + value: 1,00 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Pivot.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Pivot.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_SizeDelta.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_SizeDelta.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5381822788309442337, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5381822788309442337, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6369511037010461285, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Value + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6993348405844982934, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6993348405844982934, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6993348405844982934, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7273293964960786644, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + propertyPath: m_Name + value: Slider_MetallicSmoothnessAlpha + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} +--- !u!224 &2492869794191913777 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 5297334453724826373, guid: ddd3fe86b03da644faf6d52ae79646b6, type: 3} + m_PrefabInstance: {fileID: 7717931987048658996} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 639b1f7..5bcbbf3 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -446,6 +446,12 @@ MonoBehaviour: brushShader: {fileID: 0} dilateShader: {fileID: 0} packMsShader: {fileID: 0} + spray: 0 + sprayDots: 24 + sprayDotSizeMin: 0.05 + sprayDotSizeMax: 0.18 + sprayDotAlphaMin: 0.25 + sprayDotAlphaMax: 1 useGpu: 1 paintAlbedo: 1 paintMetallicSmoothness: 0 @@ -465,6 +471,7 @@ MonoBehaviour: brushAlignment: 0 brushRotation: 0 paintOnMoveOnly: 1 + paintInterval: 0 previewMode: 2 previewOpacity: 1 textureSize: 1024 @@ -1412,6 +1419,12 @@ MonoBehaviour: brushShader: {fileID: 0} dilateShader: {fileID: 0} packMsShader: {fileID: 0} + spray: 0 + sprayDots: 24 + sprayDotSizeMin: 0.05 + sprayDotSizeMax: 0.18 + sprayDotAlphaMin: 0.25 + sprayDotAlphaMax: 1 useGpu: 1 paintAlbedo: 1 paintMetallicSmoothness: 0 @@ -1431,6 +1444,7 @@ MonoBehaviour: brushAlignment: 0 brushRotation: 0 paintOnMoveOnly: 1 + paintInterval: 0 previewMode: 2 previewOpacity: 1 textureSize: 1024 @@ -1605,9 +1619,9 @@ Transform: m_GameObject: {fileID: 721670969} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 10, y: 10, z: 10} - m_ConstrainProportionsScale: 0 + m_LocalPosition: {x: 0, y: 0, z: 11.5} + m_LocalScale: {x: 5, y: 5, z: 5} + m_ConstrainProportionsScale: 1 m_Children: [] m_Father: {fileID: 686187287} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -2756,6 +2770,12 @@ MonoBehaviour: brushShader: {fileID: 0} dilateShader: {fileID: 0} packMsShader: {fileID: 0} + spray: 0 + sprayDots: 24 + sprayDotSizeMin: 0.05 + sprayDotSizeMax: 0.18 + sprayDotAlphaMin: 0.25 + sprayDotAlphaMax: 1 useGpu: 1 paintAlbedo: 1 paintMetallicSmoothness: 0 @@ -2775,6 +2795,7 @@ MonoBehaviour: brushAlignment: 0 brushRotation: 0 paintOnMoveOnly: 1 + paintInterval: 0 previewMode: 2 previewOpacity: 1 textureSize: 1024 diff --git a/Assets/Scripts/HsvColorPicker.cs b/Assets/Scripts/HsvColorPicker.cs index 087d907..9d67ef5 100644 --- a/Assets/Scripts/HsvColorPicker.cs +++ b/Assets/Scripts/HsvColorPicker.cs @@ -3,27 +3,35 @@ using UnityEngine; using UnityEngine.UI; /// -/// Compact runtime HSV colour picker: a saturation/value square, a hue bar and a live swatch. -/// The square is a pure-hue base with two static gradient overlays on top (white-out -> -/// saturation, black-out -> value), so only the base tint changes per hue - no per-frame +/// Compact runtime HSV colour picker: a saturation/value square, a hue bar, an alpha bar and a +/// live swatch. The square is a pure-hue base with two static gradient overlays on top (white-out +/// -> saturation, black-out -> value), so only the base tint changes per hue - no per-frame /// texture rebuilds. Hue is never lost while dragging the square (the square only edits S/V, -/// the bar only edits H). +/// the bar only edits H, the alpha bar only edits A). /// /// creates the child structure (stable names, no sprites or hooks); it can /// be baked into a prefab. then (re)assigns the runtime gradient sprites /// (which a prefab cannot store) and (re)hooks the drag relays - it runs for both the code-built -/// picker and one instantiated from a prefab, so the two are behaviourally identical. +/// picker and one instantiated from a prefab, so the two are behaviourally identical. For a prefab +/// baked before the alpha bar existed it also builds that bar on the fly (and grows the picker's +/// reserved layout height), so no re-bake is required. /// public class HsvColorPicker : MonoBehaviour { - /// Raised with the new colour when the user moves the square or hue bar. + /// Raised with the new colour when the user moves the square, hue bar or alpha bar. public event Action Changed; private float _h, _s, _v, _a = 1f; + // Gap between the stacked square / hue / alpha rows (matches the layout in Build). + private const float RowGap = 6f; + private static readonly Color AlphaBackdrop = new Color(0.5f, 0.5f, 0.5f, 1f); + private Image _hueBase; // pure-hue background of the SV square private RectTransform _svHandle; private RectTransform _hueHandle; + private Image _alphaGrad; // colour-tinted transparency ramp of the alpha bar + private RectTransform _alphaHandle; private Image _swatch; /// Current colour (HSV -> RGB, with the preserved alpha). @@ -46,9 +54,10 @@ public class HsvColorPicker : MonoBehaviour } /// - /// Builds the picker structure (square + hue bar + swatch) under this object, top-left aligned. - /// Layout only - no gradient sprites and no event hooks, so the result is safe to bake into a - /// prefab. Call afterwards (at runtime) to make it functional. + /// Builds the picker structure (square + hue bar + alpha bar + swatch) under this object, + /// top-left aligned. Layout only - no gradient sprites and no event hooks, so the result is + /// safe to bake into a prefab. Call afterwards (at runtime) to make it + /// functional. /// public void Build(float squareSize, float hueHeight) { @@ -68,7 +77,7 @@ public class HsvColorPicker : MonoBehaviour // Hue bar below the square. var hue = PaintHud.NewRect("Hue", transform); - TopLeft(hue, new Vector2(0f, -(squareSize + 6f)), new Vector2(squareSize, hueHeight)); + TopLeft(hue, new Vector2(0f, -(squareSize + RowGap)), new Vector2(squareSize, hueHeight)); var hueImg = hue.gameObject.AddComponent(); hueImg.type = Image.Type.Simple; @@ -78,12 +87,41 @@ public class HsvColorPicker : MonoBehaviour hueHandle.sizeDelta = new Vector2(4f, hueHeight + 4f); PaintHud.AddImage(hueHandle.gameObject, Color.white).raycastTarget = false; + // Alpha bar below the hue bar. + BuildAlphaBar(sv, hue); + // Swatch, to the right of the square. var sw = PaintHud.NewRect("Swatch", transform); TopLeft(sw, new Vector2(squareSize + 10f, 0f), new Vector2(34f, 34f)); PaintHud.AddImage(sw.gameObject, Color.white).raycastTarget = false; } + /// + /// Builds the alpha bar (grey backdrop + colour-tinted transparency ramp + drag relay + handle) + /// directly below the hue bar, sized/positioned from the existing SV square and hue bar so it + /// works for both a fresh build and an older baked prefab. Sprites/hooks are applied by + /// . + /// + private RectTransform BuildAlphaBar(RectTransform sv, RectTransform hue) + { + float width = sv.sizeDelta.x; + float barHeight = hue.sizeDelta.y; + float y = hue.anchoredPosition.y - (barHeight + RowGap); + + var alpha = PaintHud.NewRect("Alpha", transform); + TopLeft(alpha, new Vector2(0f, y), new Vector2(width, barHeight)); + PaintHud.AddImage(alpha.gameObject, AlphaBackdrop); // raycast target for the drag relay + + PaintHud.AddOverlay(alpha, "AlphaGrad"); // tinted alpha ramp, sprite assigned in Rebind + + alpha.gameObject.AddComponent(); + + var handle = PaintHud.NewRect("Handle", alpha); + handle.sizeDelta = new Vector2(4f, barHeight + 4f); + PaintHud.AddImage(handle.gameObject, Color.white).raycastTarget = false; + return alpha; + } + /// /// Re-finds the child widgets, (re)assigns the runtime gradient sprites and (re)hooks the drag /// relays. Idempotent (unsubscribe-then-subscribe), so it is safe whether the structure was just @@ -110,7 +148,7 @@ public class HsvColorPicker : MonoBehaviour } } - var hue = transform.Find("Hue"); + var hue = transform.Find("Hue") as RectTransform; if (hue) { if (hue.TryGetComponent(out Image hueImg)) hueImg.sprite = PaintHud.HueSprite(); @@ -123,12 +161,48 @@ public class HsvColorPicker : MonoBehaviour } } + // Alpha bar: build it if the (older) prefab lacks it, then (re)assign sprite + relay. + var alpha = transform.Find("Alpha") as RectTransform; + bool builtAlpha = false; + if (!alpha && sv is RectTransform svRect && hue) + { + alpha = BuildAlphaBar(svRect, hue); + builtAlpha = true; + } + if (alpha) + { + var grad = alpha.Find("AlphaGrad"); + if (grad && grad.TryGetComponent(out Image gradImg)) + { + gradImg.sprite = PaintHud.AlphaSprite(); + _alphaGrad = gradImg; + } + _alphaHandle = alpha.Find("Handle") as RectTransform; + + if (alpha.TryGetComponent(out UiPointerSurface alphaPointer)) + { + alphaPointer.Picked -= OnAlphaPicked; + alphaPointer.Picked += OnAlphaPicked; + } + + if (builtAlpha) GrowReservedHeight(alpha.sizeDelta.y + RowGap); + } + var swatch = transform.Find("Swatch"); if (swatch) _swatch = swatch.GetComponent(); Refresh(); } + /// Grows this picker's LayoutElement so a runtime-added alpha bar does not overlap the + /// widget below it; the panel's ContentSizeFitter then expands to match. No-op without a LayoutElement. + private void GrowReservedHeight(float extra) + { + if (!TryGetComponent(out LayoutElement le)) return; + if (le.minHeight > 0f) le.minHeight += extra; + if (le.preferredHeight > 0f) le.preferredHeight += extra; + } + private void OnSvPicked(Vector2 p) { _s = p.x; @@ -144,6 +218,13 @@ public class HsvColorPicker : MonoBehaviour Changed?.Invoke(Value); } + private void OnAlphaPicked(Vector2 p) + { + _a = p.x; + Refresh(); + Changed?.Invoke(Value); + } + private void Refresh() { if (_hueBase) _hueBase.color = Color.HSVToRGB(_h, 1f, 1f); @@ -157,6 +238,12 @@ public class HsvColorPicker : MonoBehaviour _hueHandle.anchorMin = _hueHandle.anchorMax = new Vector2(_h, 0.5f); _hueHandle.anchoredPosition = Vector2.zero; } + if (_alphaGrad) _alphaGrad.color = Color.HSVToRGB(_h, _s, _v); // tint the transparency ramp to the current colour + if (_alphaHandle) + { + _alphaHandle.anchorMin = _alphaHandle.anchorMax = new Vector2(_a, 0.5f); + _alphaHandle.anchoredPosition = Vector2.zero; + } if (_swatch) _swatch.color = Color.HSVToRGB(_h, _s, _v); } diff --git a/Assets/Scripts/MeshPainter.Cpu.cs b/Assets/Scripts/MeshPainter.Cpu.cs index bb7697f..6e910c7 100644 --- a/Assets/Scripts/MeshPainter.Cpu.cs +++ b/Assets/Scripts/MeshPainter.Cpu.cs @@ -112,6 +112,8 @@ public partial class MeshPainter float msoR = Mathf.Clamp01(metallic) * 255f; // metallic float msoG = Mathf.Clamp01(occlusion) * 255f; // occlusion float msoA = Mathf.Clamp01(smoothness) * 255f; // smoothness + float msOpMul = Mathf.Clamp01(metallicSmoothnessOpacity); // metallic+smoothness paint opacity + float aoOpMul = Mathf.Clamp01(occlusionOpacity); // occlusion paint opacity CpuMap mso = _cpu[MAP_MSO]; bool aMSO = aMS || aAO; @@ -210,14 +212,16 @@ public partial class MeshPainter if (aMSO) { - // Mask map blend (op = mask, no extra alpha multiplier). Metallic(R) + - // smoothness(A) only when painting MS; occlusion(G) only when painting AO. - // Untouched channels keep their previous value; B stays 0. + // Mask map blend. Metallic(R) + smoothness(A) blend with the MS opacity, + // occlusion(G) with its own opacity - each = mask * the channel's opacity + // multiplier (the albedo brushColor.a equivalent). Untouched channels keep + // their previous value; B stays 0. Color32 dst = mso.pixels[idx]; - float it = 1f - maskOp; - byte mr = aMS ? (byte)(dst.r * it + msoR * maskOp) : dst.r; - byte mg = aAO ? (byte)(dst.g * it + msoG * maskOp) : dst.g; - byte ma = aMS ? (byte)(dst.a * it + msoA * maskOp) : dst.a; + float msOp = maskOp * msOpMul; + float aoOp = maskOp * aoOpMul; + byte mr = aMS ? (byte)(dst.r * (1f - msOp) + msoR * msOp) : dst.r; + byte mg = aAO ? (byte)(dst.g * (1f - aoOp) + msoG * aoOp) : dst.g; + byte ma = aMS ? (byte)(dst.a * (1f - msOp) + msoA * msOp) : dst.a; mso.pixels[idx] = new Color32(mr, mg, 0, ma); mso.painted[idx] = true; ExpandDirty(mso, x, y); diff --git a/Assets/Scripts/MeshPainter.Gpu.cs b/Assets/Scripts/MeshPainter.Gpu.cs index 0107177..842be15 100644 --- a/Assets/Scripts/MeshPainter.Gpu.cs +++ b/Assets/Scripts/MeshPainter.Gpu.cs @@ -182,27 +182,59 @@ public partial class MeshPainter Graphics.SetRenderTarget(gm.display); GL.sRGBWrite = gm.sRGB; // sRGB color map vs linear data map (MS) - SetBrushColorForMap(mi); - // Per-channel write mask: lets the shared mask map (R=metallic, G=smoothness work, - // B=occlusion) be painted by metallic/smoothness and occlusion INDEPENDENTLY - - // untouched channels keep their value while coverage (A) still accumulates. - _brushMat.SetFloat(ColorWriteMaskId, BrushColorMaskForMap(mi)); - _brushMat.SetTexture(MaskTexId, _resolvedMask); + // Stamp placement is shared by every draw on this map. + _brushMat.SetTexture(MaskTexId, _stampMask); _brushMat.SetVector(BrushCenterId, pLocal); _brushMat.SetFloat(BrushRadiusId, radiusLocal); _brushMat.SetVector(BrushUId, u); _brushMat.SetVector(BrushVId, v); _brushMat.SetVector(BrushNormalId, n); - if (!_brushMat.SetPass(0)) continue; - int sub = _bakedMesh.subMeshCount; - for (int i = 0; i < sub; i++) - Graphics.DrawMeshNow(_bakedMesh, Matrix4x4.identity, i); + if (mi == MAP_MSO) + { + // Metallic/smoothness and occlusion share one texture but paint with their OWN + // opacity, so each is a separate stamp (own _BrushColor.a + per-channel write + // mask). Untouched channels keep their value while coverage (A) accumulates. + if (paintMetallicSmoothness) + StampMso(Mathf.Clamp01(metallicSmoothnessOpacity), 13f); // R|G|A (skip B = occlusion) + if (paintOcclusion) + StampMso(Mathf.Clamp01(occlusionOpacity), 3f); // B|A (occlusion only) + } + else + { + // Albedo / emission: full RGBA, opacity carried by the colour's own alpha. + _brushMat.SetColor(BrushColorId, mi == MAP_ALBEDO ? brushColor : emissionColor); + _brushMat.SetFloat(ColorWriteMaskId, 15f); + DrawStamp(); + } gm.drew = true; } } + /// + /// Issues one mask-map stamp: raw (metallic, smoothness, occlusion) data with the given paint + /// opacity in alpha and a per-channel colour write mask, so metallic/smoothness and occlusion + /// stay independently paintable with their own opacity. EncodeData pre-compensates the Color + /// property's sRGB->linear upload so the values round-trip exactly (CPU parity); alpha is not converted. + /// + private void StampMso(float opacity, float colorMask) + { + _brushMat.SetVector(BrushColorId, new Vector4( + EncodeData(metallic), EncodeData(smoothness), EncodeData(occlusion), opacity)); + _brushMat.SetFloat(ColorWriteMaskId, colorMask); + DrawStamp(); + } + + /// Draws the baked mesh once per sub-mesh with the current brush pass (UV-space rasterization). + private void DrawStamp() + { + if (!_brushMat.SetPass(0)) return; + int sub = _bakedMesh.subMeshCount; + for (int i = 0; i < sub; i++) + Graphics.DrawMeshNow(_bakedMesh, Matrix4x4.identity, i); + } + private void EndGpuStroke() { Graphics.SetRenderTarget(null); @@ -230,33 +262,6 @@ public partial class MeshPainter GL.sRGBWrite = _prevSRGB; } - /// Sets _BrushColor for the given map. Albedo/emission are colors; the mask map packs (metallic, smoothness, occlusion) as raw data. - private void SetBrushColorForMap(int mi) - { - switch (mi) - { - case MAP_ALBEDO: - _brushMat.SetColor(BrushColorId, brushColor); - break; - case MAP_MSO: - // Raw data via SetVector. Work layout: R=metallic, G=smoothness, B=occlusion - // (ColorMask picks the channels). _BrushColor is a Color property, so Unity - // applies sRGB->linear on upload in a linear project - we pre-apply the inverse - // so the data round-trips EXACTLY and matches the CPU's raw byte writes. - _brushMat.SetVector(BrushColorId, new Vector4( - EncodeData(metallic), EncodeData(smoothness), EncodeData(occlusion), 1f)); - break; - default: // emission - _brushMat.SetColor(BrushColorId, emissionColor); - break; - } - } - - /// - /// ColorMask bitmask for the brush draw (ColorWriteMask: R=8, G=4, B=2, A=1). Color maps - /// write everything; the mask map writes only the active sub-channels so metallic/smoothness - /// and occlusion can be painted independently into one texture. Coverage (A) is always written. - /// /// /// Pre-compensates a raw data value (0..1) for the brush's Color property, which Unity /// converts sRGB->linear on upload in a linear project. Applying the inverse here makes @@ -268,13 +273,4 @@ public partial class MeshPainter v = Mathf.Clamp01(v); return QualitySettings.activeColorSpace == ColorSpace.Linear ? Mathf.LinearToGammaSpace(v) : v; } - - private float BrushColorMaskForMap(int mi) - { - if (mi != MAP_MSO) return 15f; // RGBA - bool ms = paintMetallicSmoothness, occ = paintOcclusion; - if (ms && occ) return 15f; // R|G|B|A - if (ms) return 13f; // R|G|A (skip B = occlusion) - return 3f; // B|A (skip R,G = metallic/smoothness) -> occlusion only - } } diff --git a/Assets/Scripts/MeshPainter.Spray.cs b/Assets/Scripts/MeshPainter.Spray.cs new file mode 100644 index 0000000..7f2df3f --- /dev/null +++ b/Assets/Scripts/MeshPainter.Spray.cs @@ -0,0 +1,163 @@ +using UnityEngine; + +public partial class MeshPainter +{ + // --- Spray can brush --- + // + // Instead of one solid stamp, the spray scatters many small dots (random position, + // size and alpha) within the brush footprint. It is implemented by baking those dots + // into a fresh procedural MASK each stamp and then painting that mask with ONE normal + // brush stamp. This reuses the entire seam-bridging / curvature-following / per-channel + // pipeline as-is and costs a single mesh draw per stamp (not one per dot). Because the + // per-dot alpha lives in the mask value, it flows into every channel (albedo, mask map, + // emission) automatically - on both the GPU and CPU backends. + + [Header("Brush - spray can")] + [Tooltip("Spray-can mode: scatter random dots (position, size, alpha) within the brush " + + "radius instead of a single solid stamp. The dots are baked into the brush mask, " + + "so this works on all channels and both backends.")] + public bool spray = false; + + [Tooltip("Number of dots scattered per stamp.")] + [Range(1, 128)] + public int sprayDots = 24; + + [Tooltip("Dot diameter as a fraction of the brush diameter (randomized per dot between min and max).")] + [Range(0.01f, 1f)] + public float sprayDotSizeMin = 0.05f; + + [Tooltip("Dot diameter as a fraction of the brush diameter (randomized per dot between min and max).")] + [Range(0.01f, 1f)] + public float sprayDotSizeMax = 0.18f; + + [Tooltip("Per-dot opacity (randomized per dot between min and max). Multiplied on top of the channel color's alpha.")] + [Range(0f, 1f)] + public float sprayDotAlphaMin = 0.25f; + + [Tooltip("Per-dot opacity (randomized per dot between min and max). Multiplied on top of the channel color's alpha.")] + [Range(0f, 1f)] + public float sprayDotAlphaMax = 1f; + + // Resolution of the per-stamp spray mask (square). Small + reused so regenerating it + // every stamp stays cheap. + private const int SprayRes = 128; + private const float SprayDotSoftEdge = 0.4f; // fraction of the dot radius that fades to 0 + + private Texture2D _sprayTex; // the baked spray mask uploaded to the brush + private Color32[] _sprayPixels; // reused upload buffer + private float[] _sprayOpacity; // reused per-texel opacity (CPU sampling) + + // --- Active stamp mask (indirection shared by both backends) --- + // The standard mask cache (_resolvedMask / _maskOpacity / _maskW / _maskH) stays the + // canonical resolved mask used by the preview. _stamp* is what the CURRENT stamp paints + // with - the resolved mask for normal painting, or a fresh spray mask in spray mode. + private Texture2D _stampMask; + private float[] _stampOpacity; + private int _stampW, _stampH; + + /// Points the active stamp mask at the resolved standard mask (normal painting). + private void UseResolvedStampMask() + { + _stampMask = _resolvedMask; + _stampOpacity = _maskOpacity; + _stampW = _maskW; + _stampH = _maskH; + } + + /// + /// Bakes a fresh random spray pattern into the spray mask and makes it the active stamp + /// mask. Each dot gets a random center inside the brush disc, a random diameter and a + /// random opacity; dots composite by max so overlaps never exceed full opacity. + /// + private void BuildSprayStampMask() + { + EnsureSprayBuffers(); + + System.Array.Clear(_sprayOpacity, 0, _sprayOpacity.Length); + + int dots = Mathf.Max(1, sprayDots); + float sizeLo = Mathf.Clamp(Mathf.Min(sprayDotSizeMin, sprayDotSizeMax), 0.01f, 1f); + float sizeHi = Mathf.Clamp(Mathf.Max(sprayDotSizeMin, sprayDotSizeMax), 0.01f, 1f); + float alphaLo = Mathf.Clamp01(Mathf.Min(sprayDotAlphaMin, sprayDotAlphaMax)); + float alphaHi = Mathf.Clamp01(Mathf.Max(sprayDotAlphaMin, sprayDotAlphaMax)); + + for (int k = 0; k < dots; k++) + { + // Diameter is a fraction of the brush diameter -> uv radius = fraction * 0.5. + float dotR = Mathf.Max(0.5f / SprayRes, Random.Range(sizeLo, sizeHi) * 0.5f); + float alpha = Random.Range(alphaLo, alphaHi); + // Keep the WHOLE dot inside the brush disc (uv radius 0.5 = the brush radius) so the + // spray footprint matches the normal brush instead of bleeding past it by dotR. + float centerR = Mathf.Max(0f, 0.5f - dotR); + Vector2 c = Random.insideUnitCircle * centerR + new Vector2(0.5f, 0.5f); + + SplatDot(c.x, c.y, dotR, alpha); + } + + for (int i = 0; i < _sprayOpacity.Length; i++) + { + byte g = (byte)(Mathf.Clamp01(_sprayOpacity[i]) * 255f + 0.5f); + _sprayPixels[i] = new Color32(g, g, g, 255); + } + _sprayTex.SetPixels32(_sprayPixels); + _sprayTex.Apply(false); + + _stampMask = _sprayTex; + _stampOpacity = _sprayOpacity; + _stampW = SprayRes; + _stampH = SprayRes; + } + + /// Rasterizes one soft dot (center/radius in uv, value = alpha) into the opacity buffer, compositing by max. + private void SplatDot(float cx, float cy, float r, float alpha) + { + if (alpha <= 0f) return; + + float inner = 1f - SprayDotSoftEdge; // solid core up to this normalized distance, then fade + int minX = Mathf.Max(0, Mathf.FloorToInt((cx - r) * SprayRes)); + int maxX = Mathf.Min(SprayRes - 1, Mathf.CeilToInt((cx + r) * SprayRes)); + int minY = Mathf.Max(0, Mathf.FloorToInt((cy - r) * SprayRes)); + int maxY = Mathf.Min(SprayRes - 1, Mathf.CeilToInt((cy + r) * SprayRes)); + float invR = 1f / r; + + for (int y = minY; y <= maxY; y++) + { + float vY = (y + 0.5f) / SprayRes; + int row = y * SprayRes; + for (int x = minX; x <= maxX; x++) + { + float vX = (x + 0.5f) / SprayRes; + float dx = vX - cx, dy = vY - cy; + float dist = Mathf.Sqrt(dx * dx + dy * dy) * invR; // 0 = center, 1 = edge + if (dist >= 1f) continue; + + float falloff = dist <= inner ? 1f : 1f - Mathf.SmoothStep(inner, 1f, dist); + float val = alpha * falloff; + int idx = row + x; + if (val > _sprayOpacity[idx]) _sprayOpacity[idx] = val; + } + } + } + + private void EnsureSprayBuffers() + { + if (_sprayTex) return; + + _sprayTex = new Texture2D(SprayRes, SprayRes, TextureFormat.RGBA32, false, true) // linear: raw mask values + { + name = "MeshPainter_SprayMask", + wrapMode = TextureWrapMode.Clamp, + filterMode = FilterMode.Bilinear, + hideFlags = HideFlags.HideAndDontSave + }; + _sprayPixels = new Color32[SprayRes * SprayRes]; + _sprayOpacity = new float[SprayRes * SprayRes]; + } + + private void TeardownSpray() + { + if (_sprayTex) { Destroy(_sprayTex); _sprayTex = null; } + _sprayPixels = null; + _sprayOpacity = null; + } +} diff --git a/Assets/Scripts/MeshPainter.Spray.cs.meta b/Assets/Scripts/MeshPainter.Spray.cs.meta new file mode 100644 index 0000000..7bd8192 --- /dev/null +++ b/Assets/Scripts/MeshPainter.Spray.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5b607cbaf7556b3489d0ccfbd4f926dc \ No newline at end of file diff --git a/Assets/Scripts/MeshPainter.cs b/Assets/Scripts/MeshPainter.cs index a330c2c..ddc045a 100644 --- a/Assets/Scripts/MeshPainter.cs +++ b/Assets/Scripts/MeshPainter.cs @@ -85,12 +85,24 @@ public partial class MeshPainter : MonoBehaviour [Range(0f, 1f)] public float smoothness = 0.5f; + [Tooltip("Paint opacity for metallic + smoothness on top of the mask (1 = mask opacity " + + "as-is, lower = blends the metallic/smoothness target in more faintly, 0 = no-op). " + + "The albedo equivalent of brushColor.a, but for the metallic/smoothness channels.")] + [Range(0f, 1f)] + public float metallicSmoothnessOpacity = 1f; + [Tooltip("Ambient occlusion painted into the mask map (1 = fully lit / no darkening, " + "0 = fully occluded / dark). NOTE: URP only darkens INDIRECT light (ambient + " + "reflections), so the effect is subtle under strong direct lighting. 1 = no-op.")] [Range(0f, 1f)] public float occlusion = 0.5f; + [Tooltip("Paint opacity for occlusion on top of the mask (1 = mask opacity as-is, lower = " + + "blends the occlusion target in more faintly, 0 = no-op). The albedo equivalent of " + + "brushColor.a, but for the occlusion channel.")] + [Range(0f, 1f)] + public float occlusionOpacity = 1f; + [Header("Brush - emission")] [Tooltip("Emission color painted into the map. Alpha = global opacity multiplier on top of the mask.")] public Color emissionColor = Color.black; @@ -123,6 +135,13 @@ public partial class MeshPainter : MonoBehaviour "stationary button no longer keeps re-stamping the same spot). OFF = continuous painting while held.")] public bool paintOnMoveOnly = false; + [Tooltip("Seconds between paint stamps while the button is held. 0 = paint every frame as " + + "an interpolated, continuous stroke (as before). Above 0 = deposit one stamp at the " + + "cursor every this many seconds (timed dabs, no movement interpolation - pairs well " + + "with the spray can).")] + [Min(0f)] + public float paintInterval = 0f; + [Header("Preview")] [Tooltip("Brush preview at the hover point (shows size + mask without painting). " + "Flat Decal = a flat quad in the tangent plane (cheap). On Surface = a real " + @@ -188,6 +207,7 @@ public partial class MeshPainter : MonoBehaviour private bool _hasLastScreen; private Vector2 _lastScreen; + private float _intervalTimer; // accumulates while held; gates timed-dab stamping (paintInterval > 0) private bool _gpuActive; private bool _setupValid; @@ -385,24 +405,24 @@ public partial class MeshPainter : MonoBehaviour return px; } - /// Bilinear sample of the resolved mask opacity at uv in [0,1]. Returns 1 (full) if no mask. + /// Bilinear sample of the active stamp mask opacity at uv in [0,1]. Returns 1 (full) if no mask. private float SampleMaskOpacity(float u, float v) { - if (_maskOpacity == null || _maskW <= 0 || _maskH <= 0) return 1f; + if (_stampOpacity == null || _stampW <= 0 || _stampH <= 0) return 1f; - float fx = Mathf.Clamp01(u) * (_maskW - 1); - float fy = Mathf.Clamp01(v) * (_maskH - 1); + float fx = Mathf.Clamp01(u) * (_stampW - 1); + float fy = Mathf.Clamp01(v) * (_stampH - 1); int x0 = Mathf.FloorToInt(fx); int y0 = Mathf.FloorToInt(fy); - int x1 = Mathf.Min(x0 + 1, _maskW - 1); - int y1 = Mathf.Min(y0 + 1, _maskH - 1); + int x1 = Mathf.Min(x0 + 1, _stampW - 1); + int y1 = Mathf.Min(y0 + 1, _stampH - 1); float tx = fx - x0; float ty = fy - y0; - float l00 = _maskOpacity[y0 * _maskW + x0]; - float l10 = _maskOpacity[y0 * _maskW + x1]; - float l01 = _maskOpacity[y1 * _maskW + x0]; - float l11 = _maskOpacity[y1 * _maskW + x1]; + float l00 = _stampOpacity[y0 * _stampW + x0]; + float l10 = _stampOpacity[y0 * _stampW + x1]; + float l01 = _stampOpacity[y1 * _stampW + x0]; + float l11 = _stampOpacity[y1 * _stampW + x1]; float a = Mathf.Lerp(l00, l10, tx); float b = Mathf.Lerp(l01, l11, tx); @@ -509,7 +529,9 @@ public partial class MeshPainter : MonoBehaviour return; _previewMat.SetColor(BrushColorId, brushColor); - _previewMat.SetTexture(MaskTexId, _resolvedMask); + // Spray paints scattered dots, but the preview shows the round footprint (the area + // the dots land in), so the hover ring stays a stable, readable size indicator. + _previewMat.SetTexture(MaskTexId, spray ? _proceduralMask : _resolvedMask); _previewMat.SetFloat(PreviewOpacityId, Mathf.Max(0f, previewOpacity) * Mathf.Clamp01(brushColor.a)); int layer = gameObject.layer; @@ -598,18 +620,39 @@ public partial class MeshPainter : MonoBehaviour if (!paintCamera) return; bool anyChannel = AnyChannelActive(); // includes occlusion (matches IsChannelActive / PaintStamp3D) - float moveDist = _hasLastScreen ? Vector2.Distance(_lastScreen, screenPos) : 0f; + bool firstPress = !_hasLastScreen; + float moveDist = firstPress ? 0f : Vector2.Distance(_lastScreen, screenPos); - // Move-only mode: stamp once on mouse-down, then only when the mouse actually - // moves - a held but stationary button no longer re-stamps the same spot. - bool stamp = anyChannel && (!_hasLastScreen || !paintOnMoveOnly || moveDist > MoveOnlyMinPixels); + bool stamp; + if (paintInterval > 0f) + { + // Timed-dab mode: one stamp on press, then one at the cursor every paintInterval + // seconds while held (discrete dabs, no movement interpolation). + if (firstPress) + { + _intervalTimer = 0f; + stamp = anyChannel; + } + else + { + _intervalTimer += Time.deltaTime; + stamp = anyChannel && _intervalTimer >= paintInterval; + if (stamp) _intervalTimer = 0f; + } + } + else + { + // Continuous mode: stamp every frame as an interpolated stroke. Move-only optionally + // gates it - a held but stationary button no longer re-stamps the same spot. + stamp = anyChannel && (firstPress || !paintOnMoveOnly || moveDist > MoveOnlyMinPixels); + } if (stamp) { if (_gpuActive) BeginGpuStroke(); else ResetCpuDirty(); - if (_hasLastScreen) + if (!firstPress && paintInterval <= 0f) { // Interpolate along the mouse movement and re-raycast at each step so the // stroke follows the mesh surface. @@ -619,6 +662,7 @@ public partial class MeshPainter : MonoBehaviour } else { + // First stamp of a stroke, or a timed dab: a single discrete stamp at the cursor. PaintAtScreen(screenPos); } @@ -637,6 +681,11 @@ public partial class MeshPainter : MonoBehaviour out Vector3 u, out Vector3 v, out Vector3 nLocal)) return; + // Pick what this stamp paints with: a fresh random spray pattern, or the resolved + // standard mask. Both backends read it through the _stamp* fields. + if (spray) BuildSprayStampMask(); + else UseResolvedStampMask(); + if (_gpuActive) DrawBrushGpu(pLocal, radiusLocal, u, v, nLocal); else PaintStamp3D(pLocal, radiusLocal, u, v, nLocal); } @@ -761,6 +810,7 @@ public partial class MeshPainter : MonoBehaviour if (_proceduralMask) Destroy(_proceduralMask); if (_previewMat) Destroy(_previewMat); if (_previewQuad) Destroy(_previewQuad); + TeardownSpray(); TeardownCpu(); TeardownGpu(); } diff --git a/Assets/Scripts/PaintHud.cs b/Assets/Scripts/PaintHud.cs index 6e13f81..517d465 100644 --- a/Assets/Scripts/PaintHud.cs +++ b/Assets/Scripts/PaintHud.cs @@ -51,7 +51,9 @@ public class PaintHud : MonoBehaviour private const string PickerEmission = "Picker_Emission"; private const string SliderMetallic = "Slider_Metallic"; private const string SliderSmoothness = "Slider_Smoothness"; + private const string SliderMetallicSmoothnessAlpha = "Slider_MetallicSmoothnessAlpha"; private const string SliderOcclusion = "Slider_Occlusion"; + private const string SliderOcclusionAlpha = "Slider_OcclusionAlpha"; private const string SliderEmissionIntensity = "Slider_EmissionIntensity"; private const string SliderBrushSize = "Slider_BrushSize"; @@ -168,14 +170,16 @@ public class PaintHud : MonoBehaviour WirePanelFade(panel, PanelEmission, ToggleEmission); WirePicker(panel, PickerAlbedo, painter ? painter.brushColor : Color.red, - c => { if (painter) painter.brushColor = new Color(c.r, c.g, c.b, painter.brushColor.a); }); + c => { if (painter) painter.brushColor = c; }); WireSlider(panel, SliderMetallic, painter ? painter.metallic : 0f, v => { if (painter) painter.metallic = v; }); WireSlider(panel, SliderSmoothness, painter ? painter.smoothness : 0.5f, v => { if (painter) painter.smoothness = v; }); + WireSlider(panel, SliderMetallicSmoothnessAlpha, painter ? painter.metallicSmoothnessOpacity : 1f, v => { if (painter) painter.metallicSmoothnessOpacity = v; }); WireSlider(panel, SliderOcclusion, painter ? painter.occlusion : 0.5f, v => { if (painter) painter.occlusion = v; }); + WireSlider(panel, SliderOcclusionAlpha, painter ? painter.occlusionOpacity : 1f, v => { if (painter) painter.occlusionOpacity = v; }); WirePicker(panel, PickerEmission, painter ? painter.emissionColor : Color.black, - c => { if (painter) painter.emissionColor = new Color(c.r, c.g, c.b, painter.emissionColor.a); }); + c => { if (painter) painter.emissionColor = c; }); WireSlider(panel, SliderEmissionIntensity, painter ? painter.emissionIntensity : 1f, v => { if (painter) painter.emissionIntensity = v; }); WireSlider(panel, SliderBrushSize, painter ? painter.brushSize : 16f, v => { if (painter) painter.brushSize = Mathf.RoundToInt(v); }); @@ -310,7 +314,7 @@ public class PaintHud : MonoBehaviour } // Cached gradient sprites for the colour picker. - private static Sprite _satSprite, _valSprite, _hueSprite; + private static Sprite _satSprite, _valSprite, _hueSprite, _alphaSprite; /// Horizontal white-out: opaque white (left) -> transparent (right) = saturation. public static Sprite SaturationSprite() @@ -344,6 +348,23 @@ public class PaintHud : MonoBehaviour return _valSprite; } + /// Horizontal white transparency ramp: transparent (left) -> opaque (right) = alpha. + /// Tinted to the current colour by the picker so it reads as a colour-over-backdrop fade. + public static Sprite AlphaSprite() + { + if (_alphaSprite) return _alphaSprite; + const int n = 64; + var tex = NewGradientTex(n, 1); + for (int x = 0; x < n; x++) + { + byte a = (byte)Mathf.RoundToInt(255f * (x / (float)(n - 1))); + tex.SetPixel(x, 0, new Color32(255, 255, 255, a)); + } + tex.Apply(false); + _alphaSprite = ToSprite(tex); + return _alphaSprite; + } + /// Horizontal full-saturation hue ramp 0..1. public static Sprite HueSprite() {