diff --git a/Components/Components/Base/ChiselGeneratorComponent.cs b/Components/Components/Base/ChiselGeneratorComponent.cs index b1c72370..c3155cfe 100644 --- a/Components/Components/Base/ChiselGeneratorComponent.cs +++ b/Components/Components/Base/ChiselGeneratorComponent.cs @@ -185,6 +185,40 @@ public abstract class ChiselGeneratorComponent : ChiselNodeComponent, IChiselHas // Scenes saved before contents existed load as Solid. [SerializeField, HideInInspector] protected int contents; // NOTE: do not rename, name is directly used in editors [SerializeField] protected Vector3 pivotOffset = Vector3.zero; + // The paint of the generator's surfaces, a grid per surface and vertex attribute (ChiselSurfacePaint) + [SerializeField, HideInInspector] System.Collections.Generic.List surfacePaint = new(); + + public System.Collections.Generic.IReadOnlyList SurfacePaint => surfacePaint; + + /// The paint of a surface in an attribute, or null + public ChiselSurfacePaint GetSurfacePaint(int surfaceIndex, ChiselPaintAttribute attribute) + { + foreach (var paint in surfacePaint) + { + if (paint != null && paint.surfaceIndex == surfaceIndex && paint.attribute == attribute) + return paint; + } + return null; + } + + /// Gives a surface this paint in its attribute, in place of what it had + public void SetSurfacePaint(ChiselSurfacePaint paint) + { + if (paint == null) + return; + surfacePaint.RemoveAll(other => other == null || (other.surfaceIndex == paint.surfaceIndex && other.attribute == paint.attribute)); + surfacePaint.Add(paint); + ChiselPaintManager.SetDirty(this); + } + + public void RemoveSurfacePaint(int surfaceIndex, ChiselPaintAttribute attribute) + { + if (surfacePaint.RemoveAll(other => other == null || (other.surfaceIndex == surfaceIndex && other.attribute == attribute)) > 0) + ChiselPaintManager.SetDirty(this); + } + + /// Called after the values of a surface's paint were changed in place + public void PaintChanged() => ChiselPaintManager.SetDirty(this); public override CSGTreeNode TopTreeNode { @@ -416,9 +450,16 @@ protected override void OnValidateState() if (TopTreeNode.Dirty) ChiselNodeHierarchyManager.NotifyContentsModified(this); + ChiselPaintManager.SetDirty(this); base.OnValidateState(); } + protected override void OnEnable() + { + base.OnEnable(); + ChiselPaintManager.SetDirty(this); + } + public override void UpdateTransformation() { // TODO: recalculate transformation based on hierarchy up to (but not including) model @@ -444,6 +485,7 @@ protected override void OnDisable() { base.OnDisable(); DestroyChildTreeNodes(); + ChiselPaintManager.SetDirty(this); } [HideInInspector] int prevMaterialHash; diff --git a/Components/Components/Paint.meta b/Components/Components/Paint.meta new file mode 100644 index 00000000..05a30b69 --- /dev/null +++ b/Components/Components/Paint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1f13d96dc60716f4d908846d749ad2d0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Components/Components/Paint/ChiselPaintBrush.cs b/Components/Components/Paint/ChiselPaintBrush.cs new file mode 100644 index 00000000..d062629e --- /dev/null +++ b/Components/Components/Paint/ChiselPaintBrush.cs @@ -0,0 +1,321 @@ +using System.Collections.Generic; +using Chisel.Core; +using Unity.Mathematics; +using UnityEngine; + +namespace Chisel.Components +{ + /// What a dab of the paint brush does to the paint under it + public enum ChiselPaintOperation : byte + { + /// Towards the brush's color or value + Paint = 0, + /// Up by the brush's value (a value mode) + Add = 1, + /// Down by the brush's value (a value mode) + Subtract = 2, + /// Towards the average of the neighbouring grid points + Smooth = 3, + /// Back towards the mode's default + Erase = 4, + /// The whole surface set to the brush's color or value + Fill = 5 + } + + /// + /// The paint brush's work on a generator's surface paint: the grid a surface gets when it is first painted, and what + /// a dab does to the grid points under it. A dab weighs each point by the brush's strength, fully within its hardness + /// (a part of its radius) and falling off smoothly to its edge. + /// + public static class ChiselPaintBrush + { + /// Most grid points a surface gets: the cells grow until it fits (PaintSubdivision takes as many) + public const int kMaxGridPoints = 4096; + + static readonly List s_Corners = new(); + + /// + /// The corners of a generator's surface, in the generator's own space, and its normal: of every brush polygon the + /// generator gives that surface. False when it has none. + /// + public static bool SurfaceOutline(ChiselGeneratorComponent generator, int surfaceIndex, List corners, out Vector3 normal) + { + corners.Clear(); + normal = Vector3.zero; + var model = generator ? generator.hierarchyItem?.Model : null; + if (!model) + return false; + var modelToGenerator = (model.transform.worldToLocalMatrix * generator.transform.localToWorldMatrix).inverse; + AddOutline(generator.TopTreeNode, surfaceIndex, modelToGenerator, corners, ref normal); + if (corners.Count < 3 || normal == Vector3.zero) + return false; + normal.Normalize(); + return true; + } + + static void AddOutline(CSGTreeNode node, int surfaceIndex, Matrix4x4 treeToGenerator, List corners, ref Vector3 normal) + { + if (!node.Valid) + return; + if (node.Type != CSGNodeType.Brush) + { + for (int i = 0; i < node.Count; i++) + AddOutline(node[i], surfaceIndex, treeToGenerator, corners, ref normal); + return; + } + var brush = (CSGTreeBrush)node; + var blob = BrushMeshManager.GetBrushMeshBlob(brush.BrushMesh); + if (!blob.IsCreated) + return; + ref var mesh = ref blob.Value; + var toGenerator = treeToGenerator * (Matrix4x4)brush.NodeToTreeSpaceMatrix; + for (int p = 0; p < mesh.polygons.Length; p++) + { + if (mesh.polygons[p].descriptionIndex != surfaceIndex) + continue; + if (normal == Vector3.zero && p < mesh.localPlanes.Length) + normal = toGenerator.inverse.transpose.MultiplyVector((Vector3)mesh.localPlanes[p].xyz); + var polygon = mesh.polygons[p]; + for (int e = polygon.firstEdge; e < polygon.firstEdge + polygon.edgeCount; e++) + corners.Add(toGenerator.MultiplyPoint3x4(mesh.localVertices[mesh.halfEdges[e].vertexIndex])); + } + } + + /// + /// A grid over a surface of a generator, in the surface's plane, its cells about spacing (larger when it would + /// have more than ), every point at the default. Null when the surface has no outline. + /// + public static ChiselSurfacePaint CreateGrid(ChiselGeneratorComponent generator, int surfaceIndex, ChiselPaintAttribute attribute, + ChiselPaintComponents components, Vector4 defaultValue, float spacing) + { + var cellSize = spacing; + if (!SurfaceOutline(generator, surfaceIndex, s_Corners, out var normal)) + return null; + var axisU = Vector3.Cross(normal, Mathf.Abs(normal.y) < 0.99f ? Vector3.up : Vector3.forward).normalized; + var axisV = Vector3.Cross(normal, axisU).normalized; + var min = new Vector2(float.PositiveInfinity, float.PositiveInfinity); + var max = new Vector2(float.NegativeInfinity, float.NegativeInfinity); + foreach (var corner in s_Corners) + { + var flat = new Vector2(Vector3.Dot(corner, axisU), Vector3.Dot(corner, axisV)); + min = Vector2.Min(min, flat); + max = Vector2.Max(max, flat); + } + var size = max - min; + cellSize = Mathf.Max(cellSize, 1e-3f); + // grow the cells until the grid fits + var resolution = Vector2Int.one; + for (int tries = 0; tries < 64; tries++) + { + resolution = new Vector2Int(Mathf.CeilToInt(size.x / cellSize) + 1, Mathf.CeilToInt(size.y / cellSize) + 1); + if (resolution.x * resolution.y <= kMaxGridPoints) + break; + cellSize *= 1.25f; + } + var planePoint = s_Corners[0]; + var origin = planePoint + (min.x - Vector3.Dot(planePoint, axisU)) * axisU + (min.y - Vector3.Dot(planePoint, axisV)) * axisV; + var paint = ChiselSurfacePaint.Create(surfaceIndex, attribute, components, defaultValue, origin, axisU * cellSize, axisV * cellSize, resolution); + paint.spacing = spacing; + return paint; + } + + /// The paint spacing of a generator's surface () + public static float SpacingOf(ChiselGeneratorComponent generator, int surfaceIndex) + { + var surfaces = generator ? generator.SurfaceDefinition?.surfaces : null; + return surfaces != null && surfaceIndex >= 0 && surfaceIndex < surfaces.Length && surfaces[surfaceIndex] != null + ? surfaces[surfaceIndex].PaintSpacing : ChiselSurface.kDefaultPaintSpacing; + } + + /// + /// Puts the paint of every surface of a generator whose paint spacing changed on a new grid of that spacing, + /// resampled from the old one. True when any changed. + /// + public static bool Respace(ChiselGeneratorComponent generator) + { + var changed = false; + var all = generator.SurfacePaint; + for (int p = 0; p < all.Count; p++) + { + var paint = all[p]; + if (paint == null) + continue; + var spacing = SpacingOf(generator, paint.surfaceIndex); + if (paint.spacing == spacing) + continue; + if (!(paint.spacing > 0)) + { + paint.spacing = spacing; + changed = true; + continue; + } + var grid = CreateGrid(generator, paint.surfaceIndex, paint.attribute, paint.components, paint.defaultValue, spacing); + if (grid == null) + continue; + for (int j = 0; j < grid.resolution.y; j++) + { + for (int i = 0; i < grid.resolution.x; i++) + grid[i, j] = paint.Sample(grid.origin + i * grid.cellU + j * grid.cellV); + } + // the old grid leaves the list, the new one goes to its end: the next paint is at p now + generator.SetSurfacePaint(grid); + p--; + changed = true; + } + return changed; + } + + /// + /// The paint a mode writes on a surface: the surface's paint in the mode's attribute, with the mode's components + /// (at its default where they weren't painted yet), made with a grid of the surface's paint spacing when it has none + /// + public static ChiselSurfacePaint PaintFor(ChiselGeneratorComponent generator, int surfaceIndex, in ChiselPaintMode mode) + { + Respace(generator); + var paint = generator.GetSurfacePaint(surfaceIndex, mode.attribute); + // a grid that isn't whole (its values didn't load) starts again + if (paint == null || !paint.IsValid) + { + paint = CreateGrid(generator, surfaceIndex, mode.attribute, mode.components, mode.defaultValue, SpacingOf(generator, surfaceIndex)); + if (paint != null) + generator.SetSurfacePaint(paint); + return paint; + } + var added = mode.components & ~paint.components; + if (added != ChiselPaintComponents.None) + { + // another mode of the attribute painted it already: its components stay, the mode's start at its default + paint.AddComponents(added, mode.defaultValue); + generator.PaintChanged(); + } + return paint; + } + + /// How much a dab counts at a distance from its middle: strength within hardness of the radius, then + /// falling off smoothly to nothing at the radius + public static float Weight(float distance, float radius, float strength, float hardness) + { + if (!(radius > 0) || distance >= radius) + return 0; + var f = distance / radius; + hardness = Mathf.Clamp01(hardness); + if (f <= hardness) + return strength; + var t = (f - hardness) / Mathf.Max(1 - hardness, 1e-5f); + return strength * (1 - t * t * (3 - 2 * t)); + } + + /// + /// One dab on the paint, round a point in the generator's space (measured in the surface's plane), in the mode's + /// components. target is the brush's color or value. False when no grid point was in reach. + /// + public static bool Dab(ChiselSurfacePaint paint, in ChiselPaintMode mode, ChiselPaintOperation operation, Vector4 target, + Vector3 center, float radius, float strength, float hardness, + ChiselPaintComponents group = ChiselPaintComponents.None) + { + if (paint == null || !paint.IsValid) + return false; + if (operation == ChiselPaintOperation.Fill) + { + Fill(paint, mode, target, group); + return true; + } + var normal = Vector3.Cross(paint.cellU, paint.cellV).normalized; + Vector4[] before = null; + if (operation == ChiselPaintOperation.Smooth) + { + before = new Vector4[paint.PointCount]; + for (int p = 0; p < before.Length; p++) + before[p] = paint[p]; + } + var changed = false; + for (int j = 0; j < paint.resolution.y; j++) + { + for (int i = 0; i < paint.resolution.x; i++) + { + var offset = paint.origin + i * paint.cellU + j * paint.cellV - center; + offset -= normal * Vector3.Dot(offset, normal); + var weight = Weight(offset.magnitude, radius, strength, hardness); + if (weight <= 0) + continue; + var value = paint[i, j]; + Vector4 result; + switch (operation) + { + case ChiselPaintOperation.Add: result = value + target * weight; break; + case ChiselPaintOperation.Subtract: result = value - target * weight; break; + case ChiselPaintOperation.Erase: result = Vector4.Lerp(value, mode.defaultValue, weight); break; + case ChiselPaintOperation.Smooth: result = Vector4.Lerp(value, Average(before, paint.resolution, i, j), weight); break; + default: result = Vector4.Lerp(value, target, weight); break; + } + if (mode.kind == ChiselPaintKind.Value) + result = Vector4.Max(Vector4.Min(result, Vector4.one * mode.range.y), Vector4.one * mode.range.x); + result = ChiselPaintLayer.Apply(result, value, mode.components); + if (group != ChiselPaintComponents.None) + result = Normalize(result, mode.components, group); + paint[i, j] = result; + changed = true; + } + } + return changed; + } + + /// The whole surface set to the brush's color or value, in the mode's components + public static void Fill(ChiselSurfacePaint paint, in ChiselPaintMode mode, Vector4 target, ChiselPaintComponents group = ChiselPaintComponents.None) + { + if (paint == null || !paint.IsValid) + return; + for (int i = 0; i < paint.PointCount; i++) + { + Vector4 result = ChiselPaintLayer.Apply(target, paint[i], mode.components); + if (group != ChiselPaintComponents.None) + result = Normalize(result, mode.components, group); + paint[i] = result; + } + } + + // The weights of a group made to sum to one again after the painted components changed: the others share what is + // left, in the proportion they had (equally when they were all 0) + static Vector4 Normalize(Vector4 value, ChiselPaintComponents painted, ChiselPaintComponents others) + { + float paintedSum = 0, othersSum = 0; + int otherCount = 0; + for (int c = 0; c < 4; c++) + { + var bit = (ChiselPaintComponents)(1 << c); + if ((painted & bit) != 0) paintedSum += value[c]; + else if ((others & bit) != 0) { othersSum += value[c]; otherCount++; } + } + if (otherCount == 0) + return value; + var left = Mathf.Max(0, 1 - paintedSum); + for (int c = 0; c < 4; c++) + { + var bit = (ChiselPaintComponents)(1 << c); + if ((painted & bit) != 0 || (others & bit) == 0) + continue; + value[c] = othersSum > 1e-6f ? value[c] * left / othersSum : left / otherCount; + } + return value; + } + + // The average of a grid point and its neighbours + static Vector4 Average(Vector4[] values, Vector2Int resolution, int i, int j) + { + var sum = Vector4.zero; + var count = 0; + for (int dj = -1; dj <= 1; dj++) + { + for (int di = -1; di <= 1; di++) + { + int x = i + di, y = j + dj; + if (x < 0 || y < 0 || x >= resolution.x || y >= resolution.y) + continue; + sum += values[y * resolution.x + x]; + count++; + } + } + return sum / count; + } + } +} diff --git a/Components/Components/Paint/ChiselPaintBrush.cs.meta b/Components/Components/Paint/ChiselPaintBrush.cs.meta new file mode 100644 index 00000000..90edea46 --- /dev/null +++ b/Components/Components/Paint/ChiselPaintBrush.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a14984064c563544b8de74d1a2bd441f \ No newline at end of file diff --git a/Components/Components/Paint/ChiselPaintProfile.cs b/Components/Components/Paint/ChiselPaintProfile.cs new file mode 100644 index 00000000..d4ee97b1 --- /dev/null +++ b/Components/Components/Paint/ChiselPaintProfile.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using Chisel.Core; +using UnityEngine; + +namespace Chisel.Components +{ + /// What a paint mode writes: a color the brush paints with, or a value per component + public enum ChiselPaintKind : byte + { + /// A color, written to the mode's components (rgba, or rgb) + Color = 0, + /// A number (a height, wetness, a layer's weight): painted up or down with strength and falloff + Value = 1 + } + + /// One thing a shader takes from paint: what it is called, and where it goes in the vertices + [Serializable] + public struct ChiselPaintMode + { + public string name; + public ChiselPaintKind kind; + public ChiselPaintAttribute attribute; + public ChiselPaintComponents components; + /// What an unpainted surface has, in all four components + public Vector4 defaultValue; + /// A value mode's lowest and highest value + public Vector2 range; + /// Modes of one group (above 0), in components of one attribute, are weights that sum to one: painting + /// one of them takes from the others (blending material layers) + public int group; + + public static ChiselPaintMode Color(string name, ChiselPaintAttribute attribute = ChiselPaintAttribute.Color, + ChiselPaintComponents components = ChiselPaintComponents.XYZW) + { + return new ChiselPaintMode { name = name, kind = ChiselPaintKind.Color, attribute = attribute, components = components, + defaultValue = Vector4.one, range = new Vector2(0, 1) }; + } + + public static ChiselPaintMode Value(string name, ChiselPaintAttribute attribute, ChiselPaintComponents components, + float defaultValue = 0, float min = 0, float max = 1) + { + return new ChiselPaintMode { name = name, kind = ChiselPaintKind.Value, attribute = attribute, components = components, + defaultValue = new Vector4(defaultValue, defaultValue, defaultValue, defaultValue), + range = new Vector2(min, max) }; + } + } + + /// + /// What paint means to a shader: the modes a surface drawn with it can be painted in. The shader shows the paint; + /// Chisel only writes it to the vertex attributes the modes name. Two modes don't write one component of one attribute. + /// + [Serializable] + public sealed class ChiselPaintProfile + { + public Shader shader; + public List modes = new(); + + /// The components of the other modes in a mode's group (in its attribute); None when it has no group + public ChiselPaintComponents GroupOf(int mode) + { + var result = ChiselPaintComponents.None; + if (mode < 0 || mode >= modes.Count || modes[mode].group <= 0) + return result; + for (int i = 0; i < modes.Count; i++) + { + if (i != mode && modes[i].group == modes[mode].group && modes[i].attribute == modes[mode].attribute) + result |= modes[i].components; + } + return result & ~modes[mode].components; + } + + /// The first two modes that write one component of one attribute; false when there are none + public bool FindOverlap(out int first, out int second) + { + for (first = 0; first < modes.Count; first++) + { + for (second = first + 1; second < modes.Count; second++) + { + if (modes[first].attribute == modes[second].attribute && + (modes[first].components & modes[second].components) != 0) + return true; + } + } + first = second = -1; + return false; + } + } +} diff --git a/Components/Components/Paint/ChiselPaintProfile.cs.meta b/Components/Components/Paint/ChiselPaintProfile.cs.meta new file mode 100644 index 00000000..8cc65b03 --- /dev/null +++ b/Components/Components/Paint/ChiselPaintProfile.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 83ee62a399ce44d41abc4152089d341e \ No newline at end of file diff --git a/Components/Components/Paint/ChiselPaintProfiles.cs b/Components/Components/Paint/ChiselPaintProfiles.cs new file mode 100644 index 00000000..38e08970 --- /dev/null +++ b/Components/Components/Paint/ChiselPaintProfiles.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; +using Chisel.Core; +using UnityEngine; + +namespace Chisel.Components +{ + /// The project's paint profiles (), by the shader they are for + public static class ChiselPaintProfiles + { + static Dictionary s_ByShader; + // profiles registered in code, which come first + static readonly List s_Registered = new(); + + public static void Register(ChiselPaintProfile profile) + { + if (profile != null && !s_Registered.Contains(profile)) + s_Registered.Add(profile); + SetDirty(); + ChiselPaintManager.SetDirty(); + } + + public static void Unregister(ChiselPaintProfile profile) + { + s_Registered.Remove(profile); + SetDirty(); + ChiselPaintManager.SetDirty(); + } + + /// Called when profiles may have been added, removed or changed + public static void SetDirty() + { + s_ByShader = null; + ChiselPaintMaterials.Changed(); + } + + // The attributes a material's shader takes paint in, each with the defaults of its modes + static void Resolve(Material material, List slots) + { + var profile = Find(material.shader); + if (profile == null) + return; + foreach (var mode in profile.modes) + { + var index = slots.FindIndex(slot => slot.attribute == mode.attribute); + if (index < 0) + { + slots.Add(new ChiselPaintMaterialSlot { attribute = mode.attribute }); + index = slots.Count - 1; + } + var slot = slots[index]; + slot.defaultValue = ChiselPaintLayer.Apply(mode.defaultValue, slot.defaultValue, mode.components); + slot.components |= mode.components; + slots[index] = slot; + } + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void Hook() + { + ChiselPaintMaterials.Resolver = Resolve; + ChiselPaintMaterials.Changed(); + } + + public static ChiselPaintProfile Find(Shader shader) + { + if (shader == null) + return null; + foreach (var registered in s_Registered) + { + if (registered != null && registered.shader == shader) + return registered; + } + Load(); + return s_ByShader.TryGetValue(shader, out var found) ? found : null; + } + + /// Every profile: those registered in code, then the project's (unless one in code is for its shader) + public static IEnumerable All + { + get + { + foreach (var registered in s_Registered) + { + if (registered != null) + yield return registered; + } + Load(); + foreach (var profile in s_ByShader.Values) + { + if (profile != null && Find(profile.shader) == profile) + yield return profile; + } + } + } + + static void Load() + { + if (s_ByShader == null) + { + s_ByShader = new Dictionary(); + var settings = ChiselPaintSettings.Project; + if (settings) + { + foreach (var profile in settings.profiles) + { + if (profile != null && profile.shader != null) + s_ByShader.TryAdd(profile.shader, profile); + } + } + } + } + + /// Whether surfaces drawn with this material take paint in this attribute: its shader's profile has a mode + /// in it. Paint of a surface whose material doesn't is kept, but not drawn and not subdividing it. + public static bool TakesPaint(Material material, ChiselPaintAttribute attribute) + { + var profile = material ? Find(material.shader) : null; + if (profile == null) + return false; + foreach (var mode in profile.modes) + { + if (mode.attribute == attribute) + return true; + } + return false; + } + +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoadMethod] + static void Initialize() + { + Hook(); + UnityEditor.EditorApplication.projectChanged += () => + { + SetDirty(); + ChiselPaintManager.SetDirty(); + }; + } +#endif + } +} diff --git a/Components/Components/Paint/ChiselPaintProfiles.cs.meta b/Components/Components/Paint/ChiselPaintProfiles.cs.meta new file mode 100644 index 00000000..94b3a79f --- /dev/null +++ b/Components/Components/Paint/ChiselPaintProfiles.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9af8a5779d3706a4f8ec01a1daf34639 \ No newline at end of file diff --git a/Components/Components/Paint/ChiselPaintSettings.cs b/Components/Components/Paint/ChiselPaintSettings.cs new file mode 100644 index 00000000..4800cd8b --- /dev/null +++ b/Components/Components/Paint/ChiselPaintSettings.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Chisel.Components +{ + /// + /// The project's paint profiles: one per shader whose surfaces can be painted, in one asset, edited in + /// Project Settings > Chisel > Paint + /// + public sealed class ChiselPaintSettings : ScriptableObject + { + public const string kAssetPath = "Assets/Chisel/ChiselPaintSettings.asset"; + + public List profiles = new(); + + static ChiselPaintSettings s_Project; + + /// The project's paint settings, null when it has none + public static ChiselPaintSettings Project + { + get + { +#if UNITY_EDITOR + if (!s_Project) + { + s_Project = UnityEditor.AssetDatabase.LoadAssetAtPath(kAssetPath); + if (!s_Project) + { + foreach (var guid in UnityEditor.AssetDatabase.FindAssets("t:" + nameof(ChiselPaintSettings))) + { + s_Project = UnityEditor.AssetDatabase.LoadAssetAtPath(UnityEditor.AssetDatabase.GUIDToAssetPath(guid)); + if (s_Project) + break; + } + } + } +#endif + return s_Project; + } + set + { + s_Project = value; + Changed(); + } + } + + /// The first profile for a shader, or null + public ChiselPaintProfile Find(Shader shader) + { + if (!shader) + return null; + foreach (var profile in profiles) + { + if (profile != null && profile.shader == shader) + return profile; + } + return null; + } + + /// Called after the profiles changed + public static void Changed() + { + ChiselPaintProfiles.SetDirty(); + ChiselPaintManager.SetDirty(); + } + + void OnEnable() + { + if (!s_Project) + s_Project = this; + Changed(); + } + + void OnValidate() => Changed(); + } +} diff --git a/Components/Components/Paint/ChiselPaintSettings.cs.meta b/Components/Components/Paint/ChiselPaintSettings.cs.meta new file mode 100644 index 00000000..dac6f671 --- /dev/null +++ b/Components/Components/Paint/ChiselPaintSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5b91d9b1dbe48f4bddbe9e18c8945c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Components/Components/Paint/ChiselSurfacePaint.cs b/Components/Components/Paint/ChiselSurfacePaint.cs new file mode 100644 index 00000000..e6f613d6 --- /dev/null +++ b/Components/Components/Paint/ChiselSurfacePaint.cs @@ -0,0 +1,132 @@ +using System; +using Chisel.Core; +using UnityEngine; + +namespace Chisel.Components +{ + /// + /// The paint of one surface of a generator in one vertex attribute: a grid of values in the surface's plane, in the + /// generator's own space. Grid point (i, j) is at origin + i * cellU + j * cellV; holds + /// them row by row along cellU, each as only the components the paint has (x, y, z, w in that order). The cells are + /// also how finely the surface is cut to show the paint. + /// + [Serializable] + public sealed class ChiselSurfacePaint + { + /// The surface, as the generator's surface array numbers it + public int surfaceIndex; + public ChiselPaintAttribute attribute; + public ChiselPaintComponents components; + public Vector4 defaultValue; + public Vector3 origin; + public Vector3 cellU; + public Vector3 cellV; + public Vector2Int resolution; + /// The surface's paint spacing the grid was made for (its cells are larger when that would have been + /// too many points); 0 when not known + public float spacing; + public float[] values = Array.Empty(); + + /// A grid of resolution points from origin along cellU and cellV, every point at the default + public static ChiselSurfacePaint Create(int surfaceIndex, ChiselPaintAttribute attribute, ChiselPaintComponents components, + Vector4 defaultValue, Vector3 origin, Vector3 cellU, Vector3 cellV, Vector2Int resolution) + { + resolution = Vector2Int.Max(resolution, Vector2Int.one); + var paint = new ChiselSurfacePaint + { + surfaceIndex = surfaceIndex, + attribute = attribute, + components = components, + defaultValue = defaultValue, + origin = origin, + cellU = cellU, + cellV = cellV, + resolution = resolution + }; + paint.values = new float[paint.PointCount * paint.Stride]; + for (int i = 0; i < paint.PointCount; i++) + paint[i] = defaultValue; + return paint; + } + + /// How many values each grid point has: one per component + public int Stride => Unity.Mathematics.math.countbits((int)components & 15); + + public int PointCount => Mathf.Max(resolution.x, 0) * Mathf.Max(resolution.y, 0); + + public bool IsValid => resolution.x > 0 && resolution.y > 0 && Stride > 0 && values != null && values.Length == PointCount * Stride; + + /// A grid point's value, the default in the components the paint doesn't have + public Vector4 this[int point] + { + get + { + var value = defaultValue; + var at = point * Stride; + for (int c = 0; c < 4; c++) + { + if (((int)components & (1 << c)) != 0) + value[c] = values[at++]; + } + return value; + } + set + { + var at = point * Stride; + for (int c = 0; c < 4; c++) + { + if (((int)components & (1 << c)) != 0) + values[at++] = value[c]; + } + } + } + + public Vector4 this[int i, int j] + { + get => this[j * resolution.x + i]; + set => this[j * resolution.x + i] = value; + } + + /// The paint takes more components, each at value at every grid point + public void AddComponents(ChiselPaintComponents added, Vector4 value) + { + added &= ~components; + if (added == ChiselPaintComponents.None) + return; + var points = new Vector4[PointCount]; + for (int i = 0; i < points.Length; i++) + points[i] = this[i]; + defaultValue = ChiselPaintLayer.Apply(value, defaultValue, added); + components |= added; + values = new float[PointCount * Stride]; + for (int i = 0; i < points.Length; i++) + this[i] = ChiselPaintLayer.Apply(value, points[i], added); + } + + /// The paint at a point (in the generator's space), between the four grid points round it; the grid is + /// projected along its plane's normal, and a point beyond it takes the nearest edge's paint + public Vector4 Sample(Vector3 point) + { + if (!IsValid) + return defaultValue; + var offset = point - origin; + var uu = Vector3.Dot(cellU, cellU); + var uv = Vector3.Dot(cellU, cellV); + var vv = Vector3.Dot(cellV, cellV); + var determinant = uu * vv - uv * uv; + if (!(Mathf.Abs(determinant) > 1e-12f)) + return defaultValue; + var du = Vector3.Dot(offset, cellU); + var dv = Vector3.Dot(offset, cellV); + var x = Mathf.Clamp((vv * du - uv * dv) / determinant, 0, resolution.x - 1); + var y = Mathf.Clamp((uu * dv - uv * du) / determinant, 0, resolution.y - 1); + var i = Mathf.Min((int)x, Mathf.Max(resolution.x - 2, 0)); + var j = Mathf.Min((int)y, Mathf.Max(resolution.y - 2, 0)); + var i1 = Mathf.Min(i + 1, resolution.x - 1); + var j1 = Mathf.Min(j + 1, resolution.y - 1); + var bottom = Vector4.Lerp(this[i, j], this[i1, j], x - i); + var top = Vector4.Lerp(this[i, j1], this[i1, j1], x - i); + return Vector4.Lerp(bottom, top, y - j); + } + } +} diff --git a/Components/Components/Paint/ChiselSurfacePaint.cs.meta b/Components/Components/Paint/ChiselSurfacePaint.cs.meta new file mode 100644 index 00000000..092ae539 --- /dev/null +++ b/Components/Components/Paint/ChiselSurfacePaint.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 50085d8d0ae24854194da1accfa2de4d \ No newline at end of file diff --git a/Components/Managers/ChiselModelManager.cs b/Components/Managers/ChiselModelManager.cs index 77ea8b26..e8fe79d6 100644 --- a/Components/Managers/ChiselModelManager.cs +++ b/Components/Managers/ChiselModelManager.cs @@ -296,6 +296,8 @@ public void UpdateModels() ChiselDecalManager.Update(); // and the surfaces connected or disconnected by hand ChiselHotspotLinks.Update(); + // and the paint of the generators' surfaces + ChiselPaintManager.Update(); // Update the tree meshes Profiler.BeginSample("Flush"); diff --git a/Components/Managers/ChiselPaintManager.cs b/Components/Managers/ChiselPaintManager.cs new file mode 100644 index 00000000..ac1cae02 --- /dev/null +++ b/Components/Managers/ChiselPaintManager.cs @@ -0,0 +1,168 @@ +using System.Collections.Generic; +using Chisel.Core; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; + +namespace Chisel.Components +{ + /// + /// Gives each model's tree the paint of its generators' surfaces (), + /// in the model's space, and rebuilds the brushes whose paint changed. Called before the trees are built. Only the + /// generators that said their paint changed, or that moved, are looked at. + /// + public static class ChiselPaintManager + { + // a generator whose paint a tree has: which tree, and the generator's space in its model's as the paint was given in + struct Given + { + public ulong entityID; + public CSGTree tree; + public Matrix4x4 space; + } + + static bool s_All = true; + static readonly HashSet s_Changed = new(); + static readonly Dictionary s_Given = new(); + static readonly List s_Work = new(); + + /// Every generator's paint is looked at again (what shaders take may have changed) + public static void SetDirty() => s_All = true; + + /// A generator's paint, or what it is drawn with, may have changed + public static void SetDirty(ChiselGeneratorComponent generator) + { + // only one with paint, or whose paint a tree has (it may have lost it): no other generator is held on to + if (generator && (generator.SurfacePaint.Count > 0 || s_Given.ContainsKey(generator))) + s_Changed.Add(generator); + } + + static Matrix4x4 SpaceOf(ChiselGeneratorComponent generator, ChiselModelComponent model) + { + return model.transform.worldToLocalMatrix * generator.transform.localToWorldMatrix; + } + + public static void Update() + { + ChiselPaintMaterials.Update(); + + s_Work.Clear(); + // the painted generators that moved, or are gone + foreach (var entry in s_Given) + { + var generator = entry.Key; + var model = generator ? generator.hierarchyItem?.Model : null; + if (s_All || !generator || !model || entry.Value.space != SpaceOf(generator, model)) + s_Work.Add(generator); + } + if (s_All) + { + foreach (var generator in Object.FindObjectsByType(FindObjectsInactive.Exclude)) + { + if (generator.SurfacePaint.Count > 0 && !s_Given.ContainsKey(generator)) + s_Work.Add(generator); + } + s_All = false; + } + foreach (var generator in s_Changed) + { + if (!s_Work.Contains(generator)) + s_Work.Add(generator); + } + s_Changed.Clear(); + + foreach (var generator in s_Work) + Give(generator); + } + + // The generator's paint given to its model's tree, and taken from the tree it was in before + static void Give(ChiselGeneratorComponent generator) + { + s_Given.TryGetValue(generator, out var before); + var given = s_Given.ContainsKey(generator); + var model = generator ? generator.hierarchyItem?.Model : null; + if (!generator || !generator.isActiveAndEnabled || !model || !model.Node.Valid || generator.SurfacePaint.Count == 0) + { + if (given) + { + // built again without it, if it is still there + if (before.tree.Valid && before.tree.SetPaint(before.entityID, default, default) && generator) + SetBrushesDirty(generator.TopTreeNode); + s_Given.Remove(generator); + } + return; + } + + var entityID = EntityId.ToULong(generator.GetEntityId()); + var tree = model.Node; + if (given && (before.tree != tree || before.entityID != entityID) && before.tree.Valid) + before.tree.SetPaint(before.entityID, default, default); + + ChiselPaintBrush.Respace(generator); + var space = SpaceOf(generator, model); + var layerCount = 0; + var valueCount = 0; + foreach (var paint in generator.SurfacePaint) + { + if (!Drawn(generator, paint)) + continue; + layerCount++; + valueCount += paint.values.Length; + } + + NativeArray layers; + using var _layers = layers = new NativeArray(layerCount, Allocator.Temp); + NativeArray values; + using var _values = values = new NativeArray(valueCount, Allocator.Temp); + int l = 0, v = 0; + foreach (var paint in generator.SurfacePaint) + { + if (!Drawn(generator, paint)) + continue; + layers[l++] = new ChiselPaintLayer + { + entityID = entityID, + surfaceIndex = paint.surfaceIndex, + attribute = paint.attribute, + components = paint.components, + defaultValue = paint.defaultValue, + origin = space.MultiplyPoint3x4(paint.origin), + cellU = space.MultiplyVector(paint.cellU), + cellV = space.MultiplyVector(paint.cellV), + resolution = new int2(paint.resolution.x, paint.resolution.y), + valueStart = v + }; + NativeArray.Copy(paint.values, 0, values, v, paint.values.Length); + v += paint.values.Length; + } + s_Given[generator] = new Given { entityID = entityID, tree = tree, space = space }; + // the brushes of a generator whose paint changed are built again, with it + if (tree.SetPaint(entityID, layers, values)) + SetBrushesDirty(generator.TopTreeNode); + } + + // Whether the surface's shader takes paint in this attribute; paint it doesn't take is kept, but not drawn + static bool Drawn(ChiselGeneratorComponent generator, ChiselSurfacePaint paint) + { + if (paint == null || !paint.IsValid) + return false; + var surfaces = generator.SurfaceDefinition?.surfaces; + if (surfaces == null || paint.surfaceIndex < 0 || paint.surfaceIndex >= surfaces.Length) + return false; + return ChiselPaintProfiles.TakesPaint(surfaces[paint.surfaceIndex].RenderMaterial, paint.attribute); + } + + static void SetBrushesDirty(CSGTreeNode node) + { + if (!node.Valid) + return; + if (node.Type == CSGNodeType.Brush) + { + node.SetSurfacesDirty(); + return; + } + for (int i = 0; i < node.Count; i++) + SetBrushesDirty(node[i]); + } + } +} diff --git a/Components/Managers/ChiselPaintManager.cs.meta b/Components/Managers/ChiselPaintManager.cs.meta new file mode 100644 index 00000000..e8cf85d1 --- /dev/null +++ b/Components/Managers/ChiselPaintManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 77ec5d2660bf0364e9b5e3371ea35484 \ No newline at end of file diff --git a/Components/Tests/ChiselPaintTests.cs b/Components/Tests/ChiselPaintTests.cs new file mode 100644 index 00000000..e58f643b --- /dev/null +++ b/Components/Tests/ChiselPaintTests.cs @@ -0,0 +1,475 @@ +using System.Collections.Generic; +using Chisel.Core; +using NUnit.Framework; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Chisel.Components.Tests +{ + // Surface paint (ChiselSurfacePaint): a grid of values on a surface, written to the vertex attribute it names in the + // render meshes of its material + [TestFixture] + public class ChiselPaintTests + { + readonly List created = new(); + ChiselPaintProfile profile; + static Material s_Material; + + [SetUp] + public void SetUp() + { + if (Object.FindObjectsByType(FindObjectsInactive.Include).Length > 0) + Assert.Ignore("A Chisel model is loaded; run these in a scene without one"); + // the boxes' material takes paint in the attributes these paint in (its own, so a project's materials don't matter) + s_Material = new Material(Shader.Find("Universal Render Pipeline/Lit")) { name = "PaintTestMaterial" }; + created.Add(s_Material); + profile = new ChiselPaintProfile { shader = s_Material.shader }; + profile.modes.Add(ChiselPaintMode.Color("Color")); + profile.modes.Add(ChiselPaintMode.Value("Value", ChiselPaintAttribute.TexCoord2, ChiselPaintComponents.XYZW)); + profile.modes.Add(ChiselPaintMode.Value("Mask", ChiselPaintAttribute.TexCoord3, ChiselPaintComponents.XYZW)); + ChiselPaintProfiles.Register(profile); + } + + [TearDown] + public void TearDown() + { + ChiselPaintProfiles.Unregister(profile); + foreach (var item in created) + { + if (item) + Object.DestroyImmediate(item); + } + created.Clear(); + Update(); + } + + static void Update() + { + for (int i = 0; i < 3; i++) + { + ChiselNodeHierarchyManager.Update(); + ChiselModelManager.Instance.UpdateModels(); + } + } + + ChiselModelComponent Model() + { + var root = new GameObject("paint test"); + created.Add(root); + return root.AddComponent(); + } + + // A unit box with its lowest corner at position + static ChiselBoxComponent Box(ChiselModelComponent model, Vector3 position) + { + var go = new GameObject("Box"); + go.transform.SetParent(model.transform, false); + go.transform.localPosition = position; + var box = go.AddComponent(); + box.Max = new float3(1e4f); + box.Min = float3.zero; + box.Max = new float3(1); + foreach (var surface in box.SurfaceDefinition.surfaces) + surface.SetMaterial(s_Material); + return box; + } + + static int SurfaceFacing(ChiselBoxComponent box, float3 normal) + { + Update(); + var brush = (CSGTreeBrush)box.TopTreeNode; + ref var mesh = ref BrushMeshManager.GetBrushMeshBlob(brush.BrushMesh).Value; + for (int s = 0; s < mesh.polygons.Length; s++) + { + if (math.dot(math.normalize(mesh.localPlanes[s].xyz), normal) > 0.999f) + return mesh.polygons[s].descriptionIndex; + } + Assert.Fail($"the box has no surface facing {normal}"); + return -1; + } + + // The box's top painted on its corners: (0,0) red, (1,0) green, (0,1) blue, (1,1) black + static ChiselSurfacePaint PaintTop(ChiselBoxComponent box, ChiselPaintAttribute attribute, ChiselPaintComponents components) + { + var paint = ChiselSurfacePaint.Create(SurfaceFacing(box, new float3(0, 1, 0)), attribute, components, Vector4.one, + new Vector3(0, 1, 0), Vector3.right, Vector3.forward, new Vector2Int(2, 2)); + paint[0, 0] = new Vector4(1, 0, 0, 1); + paint[1, 0] = new Vector4(0, 1, 0, 1); + paint[0, 1] = new Vector4(0, 0, 1, 1); + paint[1, 1] = new Vector4(0, 0, 0, 0); + box.SetSurfacePaint(paint); + return paint; + } + + static Mesh RenderMesh(ChiselModelComponent model) + { + foreach (var renderable in model.generated.renderables) + { + if (renderable != null && renderable.HasGeometry && (renderable.query & SurfaceDestinationFlags.Renderable) != 0) + return renderable.sharedMesh; + } + Assert.Fail("the model has no render mesh"); + return null; + } + + // The attribute's value at every vertex of an upward surface at this position (a corner is the side faces' too) + static List ValuesAt(Mesh mesh, VertexAttribute attribute, Vector3 position) + { + var values = new List(); + if (attribute == VertexAttribute.Color) + { + foreach (var color in mesh.colors) + values.Add(color); + } else + mesh.GetUVs(attribute - VertexAttribute.TexCoord0, values); + var vertices = mesh.vertices; + var normals = mesh.normals; + var found = new List(); + for (int v = 0; v < vertices.Length; v++) + { + if ((vertices[v] - position).sqrMagnitude < 1e-8f && normals[v].y > 0.99f) + found.Add(values[v]); + } + Assert.That(found, Is.Not.Empty, $"no vertex at {position}"); + return found; + } + + static void AssertValuesAt(Mesh mesh, VertexAttribute attribute, Vector3 position, Vector4 expected, string message) + { + foreach (var value in ValuesAt(mesh, attribute, position)) + Assert.That(Vector4.Distance(value, expected), Is.LessThan(1e-5f), $"{message}: {value} at {position}"); + } + + // The top's corners take the grid's corners; the box's other surfaces, under the same material, the default + [Test] + public void PaintedColors_AreTheVertexColorsOfTheirSurface() + { + var model = Model(); + var box = Box(model, new Vector3(2, 0, 3)); + Update(); + AssertValuesAt(RenderMesh(model), VertexAttribute.Color, new Vector3(2, 1, 3), Vector4.one, "nothing painted: the default"); + + PaintTop(box, ChiselPaintAttribute.Color, ChiselPaintComponents.XYZW); + Update(); + var mesh = RenderMesh(model); + Assert.That(mesh.HasVertexAttribute(VertexAttribute.Color), Is.True); + AssertValuesAt(mesh, VertexAttribute.Color, new Vector3(2, 1, 3), new Vector4(1, 0, 0, 1), "the top's (0,0)"); + AssertValuesAt(mesh, VertexAttribute.Color, new Vector3(3, 1, 3), new Vector4(0, 1, 0, 1), "the top's (1,0)"); + AssertValuesAt(mesh, VertexAttribute.Color, new Vector3(2, 1, 4), new Vector4(0, 0, 1, 1), "the top's (0,1)"); + var colors = mesh.colors; + var normals = mesh.normals; + for (int v = 0; v < colors.Length; v++) + { + if (normals[v].y < 0.99f) + Assert.That((Vector4)colors[v], Is.EqualTo(Vector4.one), "the other surfaces aren't painted: the default"); + } + + box.RemoveSurfacePaint(SurfaceFacing(box, new float3(0, 1, 0)), ChiselPaintAttribute.Color); + Update(); + AssertValuesAt(RenderMesh(model), VertexAttribute.Color, new Vector3(2, 1, 3), Vector4.one, "the paint removed: the default"); + } + + // Paint in one component of a texture coordinate leaves the others at the default + [Test] + public void PaintInOneComponent_LeavesTheOthersAtTheDefault() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var paint = PaintTop(box, ChiselPaintAttribute.TexCoord3, ChiselPaintComponents.Y); + Update(); + var mesh = RenderMesh(model); + Assert.That(mesh.HasVertexAttribute(VertexAttribute.TexCoord3), Is.True); + // the other components take the profile's default (the mask mode's 0) + AssertValuesAt(mesh, VertexAttribute.TexCoord3, new Vector3(1, 1, 0), new Vector4(0, 1, 0, 0), "the green corner's y, the default elsewhere"); + AssertValuesAt(mesh, VertexAttribute.TexCoord3, new Vector3(0, 1, 0), new Vector4(0, 0, 0, 0), "the red corner's y"); + + // a value changed in place shows at the next update + paint[0, 0] = new Vector4(0.25f, 0.25f, 0.25f, 0.25f); + box.PaintChanged(); + Update(); + AssertValuesAt(RenderMesh(model), VertexAttribute.TexCoord3, new Vector3(0, 1, 0), new Vector4(0, 0.25f, 0, 0), "changed"); + } + + // A grid of quarter-metre cells on the top: all of its 25 points are vertices, the top is the grid's 32 half cells, + // and the sides, which aren't painted, keep their two triangles each + [Test] + public void APaintedSurface_IsSubdividedAtItsGridPointsInsideIt() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var paint = ChiselSurfacePaint.Create(top, ChiselPaintAttribute.Color, ChiselPaintComponents.XYZW, Vector4.one, + new Vector3(0, 1, 0), Vector3.right * 0.25f, Vector3.forward * 0.25f, new Vector2Int(5, 5)); + paint[2, 2] = new Vector4(0, 0, 0, 1); + box.SetSurfacePaint(paint); + Update(); + + var mesh = RenderMesh(model); + var vertices = mesh.vertices; + var normals = mesh.normals; + var triangles = mesh.triangles; + int topVertices = 0, topTriangles = 0, sideTriangles = 0; + for (int v = 0; v < vertices.Length; v++) + { + if (normals[v].y > 0.99f) + topVertices++; + } + for (int t = 0; t < triangles.Length; t += 3) + { + var normal = Vector3.Cross(vertices[triangles[t + 1]] - vertices[triangles[t]], vertices[triangles[t + 2]] - vertices[triangles[t]]).normalized; + if (normal.y > 0.99f) topTriangles++; + else if (Mathf.Abs(normal.y) < 0.01f) sideTriangles++; + } + Assert.That(topVertices, Is.EqualTo(25), "every grid point"); + Assert.That(topTriangles, Is.EqualTo(32)); + Assert.That(sideTriangles, Is.EqualTo(8), "the sides aren't cut"); + AssertValuesAt(mesh, VertexAttribute.Color, new Vector3(0.5f, 1, 0.5f), new Vector4(0, 0, 0, 1), "the middle point's own value"); + } + + // Painted for the first time, a surface gets a grid of its paint spacing; a dab in its middle paints there, and + // the mesh shows it + [Test] + public void TheBrush_GivesASurfaceAGrid_AndPaintsWhereItDabs() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var mode = ChiselPaintMode.Color("Color"); + var paint = ChiselPaintBrush.PaintFor(box, top, mode); + Assert.That(paint, Is.Not.Null); + Assert.That(paint.resolution, Is.EqualTo(new Vector2Int(5, 5)), "a metre in quarters: 5 points along each side"); + Assert.That(paint.cellU.magnitude, Is.EqualTo(0.25f).Within(1e-5f)); + Assert.That(Mathf.Abs(Vector3.Dot(Vector3.Cross(paint.cellU, paint.cellV).normalized, Vector3.up)), Is.EqualTo(1).Within(1e-5f), "in the top's plane"); + Assert.That(box.GetSurfacePaint(top, ChiselPaintAttribute.Color), Is.SameAs(paint)); + + Assert.That(ChiselPaintBrush.Dab(paint, mode, ChiselPaintOperation.Paint, new Vector4(1, 0, 0, 1), new Vector3(0.5f, 1, 0.5f), 0.3f, 1, 1), Is.True); + box.PaintChanged(); + Update(); + var mesh = RenderMesh(model); + AssertValuesAt(mesh, VertexAttribute.Color, new Vector3(0.5f, 1, 0.5f), new Vector4(1, 0, 0, 1), "the middle"); + AssertValuesAt(mesh, VertexAttribute.Color, new Vector3(0.25f, 1, 0.25f), Vector4.one, "out of reach (0.35 away)"); + } + + // Two modes in different components of one attribute share its paint: painting the second keeps the first + [Test] + public void ModesOfOneAttribute_ShareItsPaint() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var height = ChiselPaintMode.Value("Height", ChiselPaintAttribute.TexCoord2, ChiselPaintComponents.X, 0, 0, 1); + var wet = ChiselPaintMode.Value("Wetness", ChiselPaintAttribute.TexCoord2, ChiselPaintComponents.Y, 0, 0, 1); + var paint = ChiselPaintBrush.PaintFor(box, top, height); + ChiselPaintBrush.Dab(paint, height, ChiselPaintOperation.Add, Vector4.one * 0.5f, new Vector3(0.5f, 1, 0.5f), 2, 1, 1); + Assert.That(ChiselPaintBrush.PaintFor(box, top, wet), Is.SameAs(paint)); + ChiselPaintBrush.Dab(paint, wet, ChiselPaintOperation.Paint, Vector4.one * 0.75f, new Vector3(0.5f, 1, 0.5f), 2, 1, 1); + Assert.That(paint.components, Is.EqualTo(ChiselPaintComponents.X | ChiselPaintComponents.Y)); + Assert.That(paint[2, 2].x, Is.EqualTo(0.5f).Within(1e-5f), "the height stays"); + Assert.That(paint[2, 2].y, Is.EqualTo(0.75f).Within(1e-5f)); + + ChiselPaintBrush.Dab(paint, height, ChiselPaintOperation.Add, Vector4.one, new Vector3(0.5f, 1, 0.5f), 2, 1, 1); + Assert.That(paint[2, 2].x, Is.EqualTo(1).Within(1e-5f), "clamped to the mode's range"); + ChiselPaintBrush.Dab(paint, wet, ChiselPaintOperation.Erase, Vector4.zero, new Vector3(0.5f, 1, 0.5f), 2, 1, 1); + Assert.That(paint[2, 2], Is.EqualTo(new Vector4(1, 0, 0, 0)), "only the wetness erased"); + } + + // Smoothing evens out a spike + [Test] + public void Smoothing_EvensOutASpike() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var mode = ChiselPaintMode.Value("Value", ChiselPaintAttribute.TexCoord2, ChiselPaintComponents.X); + var paint = ChiselPaintBrush.PaintFor(box, top, mode); + paint[2, 2] = new Vector4(1, 0, 0, 0); + ChiselPaintBrush.Dab(paint, mode, ChiselPaintOperation.Smooth, Vector4.zero, new Vector3(0.5f, 1, 0.5f), 2, 1, 1); + Assert.That(paint[2, 2].x, Is.EqualTo(1f / 9).Within(1e-5f), "the average of it and its 8 neighbours"); + Assert.That(paint[1, 1].x, Is.EqualTo(1f / 9).Within(1e-5f)); + } + + // Three layer weights in one group: painting one takes from the other two, in their proportion, so they always + // sum to one + [Test] + public void TheWeightsOfAGroup_AlwaysSumToOne() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var profile = new ChiselPaintProfile(); + profile.modes.Add(ChiselPaintMode.Value("Rock", ChiselPaintAttribute.TexCoord4, ChiselPaintComponents.X, 1)); + profile.modes.Add(ChiselPaintMode.Value("Moss", ChiselPaintAttribute.TexCoord4, ChiselPaintComponents.Y, 0)); + profile.modes.Add(ChiselPaintMode.Value("Sand", ChiselPaintAttribute.TexCoord4, ChiselPaintComponents.Z, 0)); + for (int i = 0; i < 3; i++) + { + var weighted = profile.modes[i]; + weighted.group = 1; + profile.modes[i] = weighted; + } + Assert.That(profile.GroupOf(1), Is.EqualTo(ChiselPaintComponents.X | ChiselPaintComponents.Z)); + + var paint = ChiselPaintBrush.PaintFor(box, top, profile.modes[0]); + ChiselPaintBrush.PaintFor(box, top, profile.modes[1]); + ChiselPaintBrush.PaintFor(box, top, profile.modes[2]); + // the weights are x, y and z; w is no mode's + Assert.That((Vector3)paint[2, 2], Is.EqualTo(new Vector3(1, 0, 0)), "all rock"); + + ChiselPaintBrush.Fill(paint, profile.modes[1], Vector4.one * 0.5f, profile.GroupOf(1)); + Assert.That(Vector3.Distance(paint[2, 2], new Vector3(0.5f, 0.5f, 0)), Is.LessThan(1e-5f), "half moss: the rock gives it"); + ChiselPaintBrush.Dab(paint, profile.modes[2], ChiselPaintOperation.Paint, Vector4.one * 0.5f, new Vector3(0.5f, 1, 0.5f), 0.1f, 1, 1, profile.GroupOf(2)); + Assert.That(Vector3.Distance(paint[2, 2], new Vector3(0.25f, 0.25f, 0.5f)), Is.LessThan(1e-5f), "half sand: rock and moss give it alike"); + } + + // Sampling takes the paint between grid points + [Test] + public void Sampling_TakesThePaintBetweenGridPoints() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var mode = ChiselPaintMode.Color("Color"); + box.SurfaceDefinition.surfaces[top].paintSpacing = 1; + var paint = ChiselPaintBrush.PaintFor(box, top, mode); + paint[0] = new Vector4(0, 0, 0, 1); paint[1] = new Vector4(1, 0, 0, 1); paint[2] = new Vector4(0, 1, 0, 1); paint[3] = new Vector4(1, 1, 0, 1); + var middle = paint.Sample(paint.origin + (paint.cellU + paint.cellV) * 0.5f + Vector3.up * 3); + Assert.That(Vector4.Distance(middle, new Vector4(0.5f, 0.5f, 0, 1)), Is.LessThan(1e-5f), "between the four, the grid projected along its normal"); + } + + // Paint under a shader that doesn't take it (its profile gone, or another material) is kept, but neither drawn nor + // subdividing the surface; it comes back with a shader that does + [Test] + public void PaintUnderAShaderWithoutAProfile_IsKeptButNotDrawn() + { + var model = Model(); + var box = Box(model, Vector3.zero); + PaintTop(box, ChiselPaintAttribute.Color, ChiselPaintComponents.XYZW); + Update(); + Assert.That(RenderMesh(model).HasVertexAttribute(VertexAttribute.Color), Is.True); + + ChiselPaintProfiles.Unregister(profile); + Update(); + Assert.That(RenderMesh(model).HasVertexAttribute(VertexAttribute.Color), Is.False, "no profile: not drawn"); + Assert.That(box.SurfacePaint.Count, Is.EqualTo(1), "but kept"); + + ChiselPaintProfiles.Register(profile); + Update(); + Assert.That(RenderMesh(model).HasVertexAttribute(VertexAttribute.Color), Is.True, "drawn again"); + } + + // A surface's paint is on a grid of its paint spacing; changing the spacing resamples the paint onto a new grid + [Test] + public void ChangingThePaintSpacing_ResamplesThePaint() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var paint = ChiselPaintBrush.PaintFor(box, top, ChiselPaintMode.Color("Color")); + Assert.That(paint.resolution, Is.EqualTo(new Vector2Int(5, 5)), "the default spacing, 25 cm"); + Assert.That(ChiselPaintBrush.Respace(box), Is.False, "nothing changed"); + + var old = PaintTop(box, ChiselPaintAttribute.Color, ChiselPaintComponents.XYZW); + ChiselPaintBrush.Respace(box); + Assert.That(box.GetSurfacePaint(top, ChiselPaintAttribute.Color), Is.SameAs(old), "a grid of unknown spacing takes the surface's"); + + box.SurfaceDefinition.surfaces[top].paintSpacing = 0.5f; + Assert.That(ChiselPaintBrush.Respace(box), Is.True); + var respaced = box.GetSurfacePaint(top, ChiselPaintAttribute.Color); + Assert.That(respaced, Is.Not.SameAs(old)); + Assert.That(respaced.resolution, Is.EqualTo(new Vector2Int(3, 3))); + Assert.That(respaced.spacing, Is.EqualTo(0.5f)); + for (int j = 0; j < 3; j++) + { + for (int i = 0; i < 3; i++) + { + var point = respaced.origin + i * respaced.cellU + j * respaced.cellV; + Assert.That(Vector4.Distance(respaced[i, j], old.Sample(point)), Is.LessThan(1e-5f), $"point {i}, {j}"); + } + } + Assert.That(box.SurfacePaint.Count, Is.EqualTo(1)); + } + + // A fill sets the whole surface, wherever it is clicked and whatever the brush's radius + [Test] + public void AFill_SetsTheWholeSurface() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var mode = ChiselPaintMode.Color("Color"); + var paint = ChiselPaintBrush.PaintFor(box, top, mode); + Assert.That(ChiselPaintBrush.Dab(paint, mode, ChiselPaintOperation.Fill, new Vector4(0, 1, 0, 1), new Vector3(0.1f, 1, 0.1f), 0.01f, 0.1f, 0), Is.True); + for (int p = 0; p < paint.PointCount; p++) + Assert.That(paint[p], Is.EqualTo(new Vector4(0, 1, 0, 1))); + } + + // A surface under a shader that takes paint has its attributes before it is painted, at their modes' defaults + [Test] + public void AnUnpaintedSurface_HasItsShadersAttributesAtTheirDefaults() + { + var model = Model(); + Box(model, Vector3.zero); + Update(); + var mesh = RenderMesh(model); + Assert.That(mesh.HasVertexAttribute(VertexAttribute.Color), Is.True); + Assert.That(mesh.HasVertexAttribute(VertexAttribute.TexCoord2), Is.True); + Assert.That(mesh.HasVertexAttribute(VertexAttribute.TexCoord3), Is.True); + AssertValuesAt(mesh, VertexAttribute.Color, new Vector3(0, 1, 0), new Vector4(1, 1, 1, 1), "the color mode's default"); + AssertValuesAt(mesh, VertexAttribute.TexCoord2, new Vector3(0, 1, 0), Vector4.zero, "the value mode's default"); + } + + // A disabled generator's paint leaves its tree, and comes back with it + [Test] + public void ADisabledGenerator_TakesItsPaintWithIt() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + PaintTop(box, ChiselPaintAttribute.Color, ChiselPaintComponents.XYZW); + Update(); + var entityID = EntityId.ToULong(box.GetEntityId()); + bool InTree() => ChiselPaintLookup.Of(ChiselTreeLookup.Value[model.Node]).TryGetRange(entityID, top, out _); + Assert.That(InTree(), Is.True); + + box.enabled = false; + Update(); + Assert.That(InTree(), Is.False, "disabled"); + + box.enabled = true; + Update(); + Assert.That(InTree(), Is.True, "enabled again"); + } + + // A grid whose values didn't load is made again when the surface is painted + [Test] + public void AGridWithoutItsValues_IsMadeAgainWhenPainted() + { + var model = Model(); + var box = Box(model, Vector3.zero); + var top = SurfaceFacing(box, new float3(0, 1, 0)); + var mode = ChiselPaintMode.Color("Color"); + var broken = ChiselPaintBrush.PaintFor(box, top, mode); + broken.values = System.Array.Empty(); + Assert.That(broken.IsValid, Is.False); + + var paint = ChiselPaintBrush.PaintFor(box, top, mode); + Assert.That(paint, Is.Not.SameAs(broken)); + Assert.That(paint.IsValid, Is.True); + Assert.That(box.GetSurfacePaint(top, ChiselPaintAttribute.Color), Is.SameAs(paint)); + } + + // The grid moves with its generator + [Test] + public void Paint_MovesWithItsGenerator() + { + var model = Model(); + var box = Box(model, Vector3.zero); + PaintTop(box, ChiselPaintAttribute.Color, ChiselPaintComponents.XYZW); + Update(); + box.transform.localPosition = new Vector3(5, 0, 0); + ChiselNodeHierarchyManager.UpdateTreeNodeTransformation(box); + Update(); + AssertValuesAt(RenderMesh(model), VertexAttribute.Color, new Vector3(5, 1, 0), new Vector4(1, 0, 0, 1), "moved with it"); + } + } +} diff --git a/Components/Tests/ChiselPaintTests.cs.meta b/Components/Tests/ChiselPaintTests.cs.meta new file mode 100644 index 00000000..9a993118 --- /dev/null +++ b/Components/Tests/ChiselPaintTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8bfb8245d6011a48a48dd9abe60296f \ No newline at end of file diff --git a/Core/1.Input/BrushMesh/BrushMeshBlobLayout.cs b/Core/1.Input/BrushMesh/BrushMeshBlobLayout.cs new file mode 100644 index 00000000..b8023d3d --- /dev/null +++ b/Core/1.Input/BrushMesh/BrushMeshBlobLayout.cs @@ -0,0 +1,106 @@ +using System.Reflection; +using System.Text; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using UnityEngine; + +namespace Chisel.Core +{ + // Sizes and offsets of the structs every brush blob is made of. Native code compiled for + // another layout than the managed one writes blobs that managed code misreads. + public struct BrushMeshBlobLayout + { + public int polygonSize; + public int polygonFirstEdge; + public int polygonEdgeCount; + public int polygonDescriptionIndex; + public int polygonSurface; + + public int surfaceSize; + public int surfaceDetails; + public int surfaceParameters; + public int surfaceDestinationFlags; + public int surfaceOutputFlags; + public int surfaceHotspotLayout; + + public int detailsSize; + + public static unsafe BrushMeshBlobLayout Measure() + { + var polygon = default(BrushMeshBlob.Polygon); + var p = (byte*)&polygon; + var s = (byte*)&polygon.surface; + return new BrushMeshBlobLayout + { + polygonSize = UnsafeUtility.SizeOf(), + polygonFirstEdge = (int)((byte*)&polygon.firstEdge - p), + polygonEdgeCount = (int)((byte*)&polygon.edgeCount - p), + polygonDescriptionIndex = (int)((byte*)&polygon.descriptionIndex - p), + polygonSurface = (int)(s - p), + + surfaceSize = UnsafeUtility.SizeOf(), + surfaceDetails = (int)((byte*)&polygon.surface.details - s), + surfaceParameters = (int)((byte*)&polygon.surface.parameters - s), + surfaceDestinationFlags = (int)((byte*)&polygon.surface.destinationFlags - s), + surfaceOutputFlags = (int)((byte*)&polygon.surface.outputFlags - s), + surfaceHotspotLayout = (int)((byte*)&polygon.surface.hotspotLayout - s), + + detailsSize = UnsafeUtility.SizeOf() + }; + } + + [BurstCompile(CompileSynchronously = true)] + struct MeasureJob : IJob + { + [WriteOnly] public NativeReference layout; + public void Execute() { layout.Value = Measure(); } + } + + // Measured by the job system: Burst-compiled unless Burst is off + public static BrushMeshBlobLayout MeasureCompiled() + { + using var layout = new NativeReference(Allocator.TempJob); + new MeasureJob { layout = layout }.Run(); + return layout.Value; + } + + // The fields that differ, as "name managed/compiled"; empty when none do + public static string Differences(in BrushMeshBlobLayout managed, in BrushMeshBlobLayout compiled) + { + var text = new StringBuilder(); + object a = managed, b = compiled; + foreach (var field in typeof(BrushMeshBlobLayout).GetFields(BindingFlags.Instance | BindingFlags.Public)) + { + var x = (int)field.GetValue(a); + var y = (int)field.GetValue(b); + if (x != y) + text.Append(text.Length > 0 ? ", " : "").Append(field.Name).Append(' ').Append(x).Append('/').Append(y); + } + return text.ToString(); + } + + // Logs an error when the layouts differ + internal static bool Check(in BrushMeshBlobLayout managed, in BrushMeshBlobLayout compiled) + { + var differences = Differences(managed, compiled); + if (differences.Length == 0) + return true; + Debug.LogError("Chisel: compiled code has another brush blob layout than managed code (managed/compiled: " + differences + + "); every brush blob it writes is misread"); + return false; + } + + static bool s_Checked; + + // Once per domain, before the first brush registration + public static bool CheckOnce() + { + if (s_Checked) + return true; + s_Checked = true; + return Check(Measure(), MeasureCompiled()); + } + } +} diff --git a/Core/1.Input/BrushMesh/BrushMeshBlobLayout.cs.meta b/Core/1.Input/BrushMesh/BrushMeshBlobLayout.cs.meta new file mode 100644 index 00000000..08bb41c8 --- /dev/null +++ b/Core/1.Input/BrushMesh/BrushMeshBlobLayout.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 14827083611189347a271b52d513ab06 \ No newline at end of file diff --git a/Core/1.Input/BrushMesh/BrushMeshManager.Internal.cs b/Core/1.Input/BrushMesh/BrushMeshManager.Internal.cs index 7c9f0c58..a8e6cea3 100644 --- a/Core/1.Input/BrushMesh/BrushMeshManager.Internal.cs +++ b/Core/1.Input/BrushMesh/BrushMeshManager.Internal.cs @@ -867,6 +867,7 @@ internal static JobHandle ScheduleBrushRegistration(bool runInParallel, { Debug.Assert(generatedNodeDefinitions.IsCreated); } + BrushMeshBlobLayout.CheckOnce(); var brushMeshBlobCache = ChiselMeshLookup.Value.brushMeshBlobCache; var registerBrushesJob = new RegisterBrushMeshesJob { diff --git a/Core/1.Input/CompactHierarchy/CompactHierarchy.Internal.cs b/Core/1.Input/CompactHierarchy/CompactHierarchy.Internal.cs index 0b69dec2..0a7af7b5 100644 --- a/Core/1.Input/CompactHierarchy/CompactHierarchy.Internal.cs +++ b/Core/1.Input/CompactHierarchy/CompactHierarchy.Internal.cs @@ -1409,6 +1409,20 @@ internal void SetBrushesBeforeIntersectingNodeDirty(CompactNodeID compactNodeID) SetBrushesBeforeDirty(compactNodeID); } + // A brush whose surfaces change but not its shape: only it is built again, not the brushes touching it + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool SetSurfacesDirty(CompactNodeID compactNodeID) + { + if (!IsValidCompactNodeID(compactNodeID) || RootID == compactNodeID) + return false; + ref var node = ref GetChildRef(compactNodeID); + if (node.brushMeshHash == Int32.MaxValue) + return false; + node.flags |= NodeStatusFlags.OutlineModified; + GetChildRef(RootID).flags |= NodeStatusFlags.TreeNeedsUpdate; + return true; + } + // This method might be removed/renamed in the future [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool SetDirty(CompactNodeID compactNodeID) diff --git a/Core/1.Input/Nodes/CSGTreeNode.cs b/Core/1.Input/Nodes/CSGTreeNode.cs index 79c36b9c..2fa12525 100644 --- a/Core/1.Input/Nodes/CSGTreeNode.cs +++ b/Core/1.Input/Nodes/CSGTreeNode.cs @@ -105,6 +105,10 @@ internal static CSGTreeNode Encapsulate(NodeID nodeID) /// Force set the dirty flag of the . public void SetDirty () { CompactHierarchyManager.SetDirty(nodeID); } + /// Builds a brush's surfaces again without its shape having changed (its paint did): the brushes + /// touching it are left as they are. + public void SetSurfacesDirty() { CompactHierarchyManager.SetSurfacesDirty(nodeID); } + /// Destroy this . Sets the state to invalid. /// true on success, false on failure public bool Destroy () { var prevNodeID = nodeID; this = Invalid; return CompactHierarchyManager.DestroyNode(prevNodeID); } diff --git a/Core/1.Input/Paint.meta b/Core/1.Input/Paint.meta new file mode 100644 index 00000000..55d7abba --- /dev/null +++ b/Core/1.Input/Paint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e2c297f479280d74ea5b2c520191da84 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/1.Input/Paint/ChiselPaint.cs b/Core/1.Input/Paint/ChiselPaint.cs new file mode 100644 index 00000000..07279bd4 --- /dev/null +++ b/Core/1.Input/Paint/ChiselPaint.cs @@ -0,0 +1,159 @@ +using System; +using Unity.Collections; +using Unity.Mathematics; + +namespace Chisel.Core +{ + /// The vertex attribute paint is written to. What it means is up to the shader of the surface's material. + /// TexCoord0 and TexCoord1 are taken (texture mapping, lightmaps). + public enum ChiselPaintAttribute : byte + { + Color = 0, + TexCoord2 = 2, + TexCoord3 = 3, + TexCoord4 = 4, + TexCoord5 = 5, + TexCoord6 = 6, + TexCoord7 = 7 + } + + /// The components of a vertex attribute paint writes; the others keep their default + [Flags] + public enum ChiselPaintComponents : byte + { + None = 0, + X = 1, + Y = 2, + Z = 4, + W = 8, + XYZ = X | Y | Z, + XYZW = X | Y | Z | W + } + + /// + /// The paint of one surface in one vertex attribute: values on a grid in the surface's plane, in the tree's (model's) + /// space. Grid point (i, j) is at origin + i * cellU + j * cellV; a vertex takes the value at its place on the + /// grid, interpolated between the four points round it, in , and + /// in the others. The grid's cells are also how finely the surface is cut to show it. + /// + public struct ChiselPaintLayer : IEquatable + { + /// The node the surface's brush was created with, as for + public ulong entityID; + /// The surface, as the brush's surface array numbers it + public int surfaceIndex; + public ChiselPaintAttribute attribute; + public ChiselPaintComponents components; + public float4 defaultValue; + public float3 origin; + public float3 cellU; + public float3 cellV; + /// How many grid points there are along cellU and cellV + public int2 resolution; + /// Where the grid's values start in the values given with the layers: row by row along cellU, each point + /// as only its components (x, y, z, w in that order) + public int valueStart; + + /// How many values each grid point has: one per component + public readonly int Stride => math.countbits((int)components & 15); + public readonly int PointCount => math.max(resolution.x, 0) * math.max(resolution.y, 0); + public readonly int ValueCount => PointCount * Stride; + + public readonly bool Equals(ChiselPaintLayer other) + { + return entityID == other.entityID && surfaceIndex == other.surfaceIndex && + attribute == other.attribute && components == other.components && + math.all(defaultValue == other.defaultValue) && math.all(origin == other.origin) && + math.all(cellU == other.cellU) && math.all(cellV == other.cellV) && + math.all(resolution == other.resolution) && valueStart == other.valueStart; + } + + public override readonly bool Equals(object obj) => obj is ChiselPaintLayer other && Equals(other); + public override readonly int GetHashCode() => (int)math.hash(new uint4((uint)entityID, (uint)surfaceIndex, (uint)attribute, math.hash(origin))); + + /// The layer's value at a point, in all four components (its grid projected along the plane's normal) + public readonly unsafe float4 Sample(NativeArray values, float3 point) + { + return Sample((float*)Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(values), point); + } + + internal readonly unsafe float4 Sample(float* values, float3 point) + { + if (!GridPosition(point, out var cell, out var t)) + return defaultValue; + var row0 = cell.y * resolution.x + cell.x; + var row1 = row0 + (resolution.y > 1 ? resolution.x : 0); + var next = resolution.x > 1 ? 1 : 0; + var bottom = math.lerp(Load(values, row0), Load(values, row0 + next), t.x); + var top = math.lerp(Load(values, row1), Load(values, row1 + next), t.x); + return math.lerp(bottom, top, t.y); + } + + // A grid point's value, the default in the components the layer doesn't have + readonly unsafe float4 Load(float* values, int point) + { + var value = defaultValue; + var at = values + valueStart + point * Stride; + if ((components & ChiselPaintComponents.X) != 0) value.x = *at++; + if ((components & ChiselPaintComponents.Y) != 0) value.y = *at++; + if ((components & ChiselPaintComponents.Z) != 0) value.z = *at++; + if ((components & ChiselPaintComponents.W) != 0) value.w = *at; + return value; + } + + // Where a point is on the grid: the cell (its lower grid point) and how far across it, clamped to the grid + readonly bool GridPosition(float3 point, out int2 cell, out float2 t) + { + cell = int2.zero; + t = float2.zero; + if (math.any(resolution < 1)) + return false; + // the point's coordinates along cellU and cellV, which needn't be at right angles + var offset = point - origin; + var uu = math.dot(cellU, cellU); + var uv = math.dot(cellU, cellV); + var vv = math.dot(cellV, cellV); + var determinant = uu * vv - uv * uv; + if (!(math.abs(determinant) > 1e-12f)) + return false; + var du = math.dot(offset, cellU); + var dv = math.dot(offset, cellV); + var grid = new float2((vv * du - uv * dv) / determinant, (uu * dv - uv * du) / determinant); + grid = math.clamp(grid, float2.zero, (float2)(resolution - 1)); + cell = math.min((int2)math.floor(grid), math.max(resolution - 2, 0)); + t = grid - cell; + return true; + } + + /// A value written in the layer's components over a default + public static float4 Apply(float4 value, float4 over, ChiselPaintComponents components) + { + return math.select(over, value, new bool4((components & ChiselPaintComponents.X) != 0, + (components & ChiselPaintComponents.Y) != 0, + (components & ChiselPaintComponents.Z) != 0, + (components & ChiselPaintComponents.W) != 0)); + } + } + + public static class CSGTreePaintExtensions + { + /// + /// Replaces all paint of the tree at once: the layers, and the values their grids take from (each layer's + /// ). Returns the entities whose paint changed; their brushes have to be + /// rebuilt to show it (the caller marks them dirty). + /// + public static bool SetPaint(this CSGTree tree, NativeArray layers, NativeArray values, NativeList changedEntities) + { + return ChiselPaintStore.SetPaint(tree, layers, values, changedEntities); + } + + /// + /// Replaces the paint of one entity: its layers, and the values their grids take from. True when it changed; the + /// entity's brushes then have to be rebuilt to show it (). + /// + public static bool SetPaint(this CSGTree tree, ulong entityID, NativeArray layers, NativeArray values) + { + return ChiselPaintStore.SetPaint(tree, entityID, layers, values); + } + } +} diff --git a/Core/1.Input/Paint/ChiselPaint.cs.meta b/Core/1.Input/Paint/ChiselPaint.cs.meta new file mode 100644 index 00000000..446cb471 --- /dev/null +++ b/Core/1.Input/Paint/ChiselPaint.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d8b12b16d5b1c9146bf8cfec31e43289 \ No newline at end of file diff --git a/Core/1.Input/Paint/ChiselPaintMaterials.cs b/Core/1.Input/Paint/ChiselPaintMaterials.cs new file mode 100644 index 00000000..b719039a --- /dev/null +++ b/Core/1.Input/Paint/ChiselPaintMaterials.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; + +namespace Chisel.Core +{ + /// An attribute a material's shader takes paint in, and what it has where nothing is painted + public struct ChiselPaintMaterialSlot + { + public ulong material; + public ChiselPaintAttribute attribute; + public ChiselPaintComponents components; + public float4 defaultValue; + } + + /// + /// The paint attributes of the materials surfaces are drawn with: a render mesh with such a material has those + /// attributes, at their defaults where nothing is painted. Materials are registered as surfaces take them; what + /// their shaders take comes from . + /// + public static class ChiselPaintMaterials + { + /// Adds the attributes a material's shader takes paint in, with their defaults + public static Action> Resolver; + + static readonly Dictionary s_Materials = new(); + static readonly List s_Slots = new(); + static readonly List s_Resolved = new(); + static readonly List s_Scratch = new(); + static readonly List s_Gone = new(); + static bool s_Dirty; + static int s_Version; + static bool s_Changed; + + public static void Register(Material material) + { + if (!material) + return; + if (s_Materials.TryAdd(EntityId.ToULong(material.GetEntityId()), material)) + s_Dirty = true; + } + + /// What shaders take may have changed; looked into at the next + public static void Changed() => s_Changed = true; + + /// Before the trees are built: every brush is built again when a registered material's attributes changed + public static void Update() + { + if (!s_Changed) + return; + s_Changed = false; + s_Dirty = true; + var before = s_Slots.Count; + s_Scratch.Clear(); + s_Scratch.AddRange(s_Slots); + Resolve(); + var same = before == s_Slots.Count; + for (int i = 0; same && i < before; i++) + { + same = s_Scratch[i].material == s_Slots[i].material && s_Scratch[i].attribute == s_Slots[i].attribute && + s_Scratch[i].components == s_Slots[i].components && s_Scratch[i].defaultValue.Equals(s_Slots[i].defaultValue); + } + if (same) + return; + using var trees = new NativeList(Allocator.Temp); + CompactHierarchyManager.GetAllTrees(trees); + foreach (var tree in trees) + { + for (int i = 0; tree.Valid && i < tree.Count; i++) + SetBrushesDirty(tree[i]); + } + } + + static void SetBrushesDirty(CSGTreeNode node) + { + if (!node.Valid) + return; + if (node.Type == CSGNodeType.Brush) + { + node.SetSurfacesDirty(); + return; + } + for (int i = 0; i < node.Count; i++) + SetBrushesDirty(node[i]); + } + + // the slots of every registered material, its slots together + static void Resolve() + { + if (!s_Dirty) + return; + s_Dirty = false; + s_Version++; + s_Slots.Clear(); + s_Gone.Clear(); + foreach (var entry in s_Materials) + { + if (!entry.Value) + { + s_Gone.Add(entry.Key); + continue; + } + if (Resolver == null) + continue; + s_Resolved.Clear(); + Resolver(entry.Value, s_Resolved); + foreach (var slot in s_Resolved) + s_Slots.Add(new ChiselPaintMaterialSlot { material = entry.Key, attribute = slot.attribute, components = slot.components, defaultValue = slot.defaultValue }); + } + foreach (var key in s_Gone) + s_Materials.Remove(key); + } + + // The slots as they are now, and a number that changes when they do + internal static int Refresh() + { + Resolve(); + return s_Version; + } + + internal static void Gather(NativeList slots) + { + Resolve(); + foreach (var slot in s_Slots) + slots.Add(slot); + } + } +} diff --git a/Core/1.Input/Paint/ChiselPaintMaterials.cs.meta b/Core/1.Input/Paint/ChiselPaintMaterials.cs.meta new file mode 100644 index 00000000..500f1e01 --- /dev/null +++ b/Core/1.Input/Paint/ChiselPaintMaterials.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cd8264abae7685049b5303f70a86f97b \ No newline at end of file diff --git a/Core/1.Input/Surfaces/ChiselSurface.cs b/Core/1.Input/Surfaces/ChiselSurface.cs index f5157db3..5e47b621 100644 --- a/Core/1.Input/Surfaces/ChiselSurface.cs +++ b/Core/1.Input/Surfaces/ChiselSurface.cs @@ -70,6 +70,14 @@ public sealed class ChiselSurface [SerializeField] internal ChiselMaterial chiselMaterial = new(); public SurfaceDetails surfaceDetails = SurfaceDetails.Default; + // How far apart the points the surface is painted at (and cut at) are, in metres; 0 for the default. Only painting + // reads it, so it stays out of the details that go into every brush polygon. Drawn with the details, and only for + // a shader that takes paint. + [HideInInspector] public float paintSpacing; + + public const string kPaintSpacingName = nameof(paintSpacing); + public const float kDefaultPaintSpacing = 0.25f; + public float PaintSpacing => paintSpacing > 0 ? paintSpacing : kDefaultPaintSpacing; public bool HasMaterial { get { return chiselMaterial.material != null; } } diff --git a/Core/1.Input/Surfaces/InternalChiselSurface.cs b/Core/1.Input/Surfaces/InternalChiselSurface.cs index e99991ab..6dbff92b 100644 --- a/Core/1.Input/Surfaces/InternalChiselSurface.cs +++ b/Core/1.Input/Surfaces/InternalChiselSurface.cs @@ -26,6 +26,7 @@ public struct InternalChiselSurface [MethodImpl(MethodImplOptions.AggressiveInlining)] public static InternalChiselSurface Convert(ChiselSurface surface) { + ChiselPaintMaterials.Register(surface.RenderMaterial); return new InternalChiselSurface { details = surface.surfaceDetails, diff --git a/Core/2.Processing/Cache/ChiselLookup.cs b/Core/2.Processing/Cache/ChiselLookup.cs index 9ebdfc42..06b32056 100644 --- a/Core/2.Processing/Cache/ChiselLookup.cs +++ b/Core/2.Processing/Cache/ChiselLookup.cs @@ -39,6 +39,16 @@ public class Data public NativeList changedDecalBounds; // The surfaces connected or disconnected by hand (ChiselHotspotLinkStore) public NativeList hotspotLinks; + // The painted surfaces' layers and their grids' values, and what an update looks up in them (ChiselPaintStore) + public NativeList paintLayers; + public NativeList paintLayerHashes; + public NativeList paintValues; + public int paintUnusedValues; + public NativeParallelHashMap paintEntityHashes; + public NativeParallelHashMap paintRanges; + public NativeList paintMaterialSlots; + public NativeParallelHashMap paintMaterials; + public int paintMaterialsVersion = -1; public NativeParallelHashMap> brushRenderBufferLookup; @@ -80,6 +90,14 @@ internal void Initialize() decalBounds = new NativeList(16, Allocator.Persistent); decalVolumes = new NativeList(16, Allocator.Persistent); hotspotLinks = new NativeList(4, Allocator.Persistent); + paintLayers = new NativeList(4, Allocator.Persistent); + paintLayerHashes = new NativeList(4, Allocator.Persistent); + paintValues = new NativeList(64, Allocator.Persistent); + paintEntityHashes = new NativeParallelHashMap(4, Allocator.Persistent); + paintRanges = new NativeParallelHashMap(4, Allocator.Persistent); + paintMaterialSlots = new NativeList(4, Allocator.Persistent); + paintMaterials = new NativeParallelHashMap(4, Allocator.Persistent); + paintMaterialsVersion = -1; hotspotLayoutHashes = new NativeList(4, Allocator.Persistent); collapseMoves = new NativeList(4, Allocator.Persistent); hotspotCutCache = new NativeParallelHashMap(16, Allocator.Persistent); @@ -233,6 +251,21 @@ internal void Dispose() if (changedDecalBounds.IsCreated) changedDecalBounds.Dispose(); if (hotspotLinks.IsCreated) hotspotLinks.Dispose(); hotspotLinks = default; + if (paintLayers.IsCreated) paintLayers.Dispose(); + paintLayers = default; + if (paintValues.IsCreated) paintValues.Dispose(); + paintValues = default; + if (paintLayerHashes.IsCreated) paintLayerHashes.Dispose(); + paintLayerHashes = default; + if (paintEntityHashes.IsCreated) paintEntityHashes.Dispose(); + paintEntityHashes = default; + if (paintRanges.IsCreated) paintRanges.Dispose(); + paintRanges = default; + if (paintMaterialSlots.IsCreated) paintMaterialSlots.Dispose(); + paintMaterialSlots = default; + if (paintMaterials.IsCreated) paintMaterials.Dispose(); + paintMaterials = default; + paintUnusedValues = 0; if (hotspotLayoutHashes.IsCreated) hotspotLayoutHashes.Dispose(); hotspotLayoutHashes = default; if (collapseMoves.IsCreated) collapseMoves.Dispose(); diff --git a/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs b/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs index b3a32cdd..7b2f52bd 100644 --- a/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs +++ b/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs @@ -33,6 +33,8 @@ struct GenerateSurfaceTrianglesJob : IJobParallelForDefer [NoAlias, ReadOnly] public NativeArray hotspotLayouts; // the materials layouts with variants are drawn with (HotspotLayoutData.variantStart) [NoAlias, ReadOnly] public NativeArray hotspotVariants; + // The tree's paint, by surface + [NoAlias, ReadOnly] public ChiselPaintLookup paint; // Write [NativeDisableParallelForRestriction] @@ -59,6 +61,9 @@ unsafe void FinishSurface(int surf, int brushNodeOrder, ulong entityID, ref Blob NativeArray decalCandidateStart, SlotLayout slots) { var baseSurfaceCount = slots.baseSurfaceCount; + // a painted surface gets its paint's grid points as vertices, before anything is worked out from them + if (paint.TryGetRange(entityID, baseSurfaces[surf].descriptionIndex, out var subdivision)) + PaintSubdivision.Subdivide(surfaceIndexList, ref uniqueVertexMapper, finalFaceNormal, paint, subdivision); if (normalSmoothingAngle > 0.0001f) { var renderVertices = uniqueVertexMapper.surfaceRenderVertices; @@ -307,6 +312,17 @@ unsafe void FinishSurface(int surf, int brushNodeOrder, ulong entityID, ref Blob buf.hotspot = hotspotInfo; buf.hotspotSource = hotspotSource; + // paint is found by the brush's entity and the surface's index, when the surface's mesh is copied + var paintSurface = baseSurfaces[surf].descriptionIndex; + if (paint.TryGetRange(entityID, paintSurface, out var paintRange)) + { + buf.paintSurface = paintSurface; + buf.surfaceHashValue = math.hash(new uint2(buf.surfaceHashValue, paintRange.hash)); + } + // the attributes its material's shader takes paint in are in its mesh, painted or not + if (paint.TryGetMaterial(parms.parameter1, out var materialRange)) + buf.surfaceHashValue = math.hash(new uint2(buf.surfaceHashValue, materialRange.hash)); + if (HasBackFace(flags, outputFlags)) FinishBackFace(surf, entityID, flags, parms, outputFlags, surfaceIndexList, ref uniqueVertexMapper, builder, surfaceBuffers, slots); } @@ -660,6 +676,7 @@ public unsafe void Execute(int index) FinishSurface(surf, brushNodeOrder, entityID, ref baseSurfaces, treeToPlane, transform.nodeToTree, finalFaceNormal, surfaceIndexList, ref uniqueVertexMapper, builder, surfaceBuffers, ref decalBuilder, decalCandidates, decalTargeted, decalCandidateStart, layout); } + root.entityID = entityID; FinishBrush(brushIndexOrder, brushNodeOrder, builder, ref root, surfaceBuffers, decalCandidateStart, layout); } finally diff --git a/Core/2.Processing/Jobs/JobData/ChiselBrushRenderBuffer.cs b/Core/2.Processing/Jobs/JobData/ChiselBrushRenderBuffer.cs index d8b5df8a..984487d2 100644 --- a/Core/2.Processing/Jobs/JobData/ChiselBrushRenderBuffer.cs +++ b/Core/2.Processing/Jobs/JobData/ChiselBrushRenderBuffer.cs @@ -19,6 +19,8 @@ internal struct ChiselSurfaceRenderBuffer public int decalStack; // a transparent decal's piece that fades (DecalVolume.Fades): its mesh is its decal's own public bool decalFades; + // a painted surface's index, which its paint is found by with its brush's entity (ChiselPaintLookup); -1 without + public int paintSurface; public SurfaceDestinationFlags destinationFlags; public SurfaceDestinationParameters destinationParameters; // How many lightmap texels the surface gets (SurfaceOutputFlags.NoLightmap, SingleLightmapTexel) @@ -198,6 +200,7 @@ public void Construct(BlobBuilder builder, this.decalEntityID = 0; this.decalStack = 0; this.decalFades = false; + this.paintSurface = -1; this.destinationFlags = destinationFlags; this.destinationParameters = destinationParameters; this.outputFlags = outputFlags; @@ -224,6 +227,7 @@ public void Construct(BlobBuilder builder, this.decalEntityID = 0; this.decalStack = 0; this.decalFades = false; + this.paintSurface = -1; this.destinationFlags = destinationFlags; this.destinationParameters = destinationParameters; @@ -306,6 +310,8 @@ internal struct ChiselBrushRenderBuffer { public BlobArray surfaces; public BlobArray querySurfaces; + // the node the brush was created with + public ulong entityID; public int surfaceOffset; public int surfaceCount; }; diff --git a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs index 699a5b6f..60f93bde 100644 --- a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs +++ b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs @@ -290,6 +290,7 @@ internal struct TemporariesStruct public NativeArray changedHotspotLayouts; // and the surfaces connected or disconnected by hand (ChiselHotspotLinkStore) public NativeArray hotspotLinks; + public ChiselPaintLookup paint; } internal TemporariesStruct Temporaries; #endregion @@ -563,6 +564,8 @@ public void Initialize() Temporaries.hotspotTileSides = new NativeArray(hotspotTileSides.AsArray(), defaultAllocator); Temporaries.hotspotVariants = new NativeArray(hotspotVariants.AsArray(), defaultAllocator); Temporaries.hotspotLinks = new NativeArray(chiselLookupValues.hotspotLinks.AsArray(), defaultAllocator); + ChiselPaintStore.UpdateMaterials(chiselLookupValues); + Temporaries.paint = ChiselPaintLookup.Of(chiselLookupValues); } Temporaries.subMeshDescriptions = new NativeList(defaultAllocator); @@ -1861,6 +1864,7 @@ public void RunMeshUpdateJobs() hotspotTiles = Temporaries.hotspotTiles, hotspotLayouts = Temporaries.hotspotLayouts, hotspotVariants = Temporaries.hotspotVariants, + paint = Temporaries.paint, // Write brushRenderBufferCache = chiselLookupValues.brushRenderBufferCache @@ -2396,7 +2400,8 @@ public void RunMeshCopyJobs() { subMeshSurfaces = Temporaries.subMeshSurfaces, // PrepareSubSectionsJob -> meshQueries / brushRenderData (FindBrushRenderBuffersJob) subMeshDescriptions = Temporaries.subMeshDescriptions, // SortSurfacesParallelJob -> meshQueries / subMeshSurfaces (PrepareSubSectionsJob) - lightmapUVSettings = lightmapUVSettings + lightmapUVSettings = lightmapUVSettings, + paint = Temporaries.paint }; // the render meshes made ready, then every surface of them copied at once diff --git a/Core/2.Processing/Managers/CompactHierarchyManager.cs b/Core/2.Processing/Managers/CompactHierarchyManager.cs index e608c3bd..fa57724a 100644 --- a/Core/2.Processing/Managers/CompactHierarchyManager.cs +++ b/Core/2.Processing/Managers/CompactHierarchyManager.cs @@ -792,6 +792,15 @@ internal bool SetDirty(NodeID nodeID) return SetDirty(nodes[index]); } + [return: MarshalAs(UnmanagedType.U1)] + internal bool SetSurfacesDirty(NodeID nodeID) + { + if (!IsValidNodeID(nodeID, out var index) || !IsValidCompactNodeID(nodes[index])) + return false; + ref var hierarchy = ref GetHierarchy(nodes[index]); + return hierarchy.SetSurfacesDirty(nodes[index]); + } + // Do not use. This method might be removed/renamed in the future [EditorBrowsable(EditorBrowsableState.Never)] [return: MarshalAs(UnmanagedType.U1)] @@ -2311,6 +2320,9 @@ public static bool IsBrushSelectable(NodeID nodeID) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool SetDirty(NodeID nodeID) { return instance.SetDirty(nodeID); } + [return: MarshalAs(UnmanagedType.U1)] + internal static bool SetSurfacesDirty(NodeID nodeID) { return instance.SetSurfacesDirty(nodeID); } + // Do not use. This method might be removed/renamed in the future [EditorBrowsable(EditorBrowsableState.Never)] [return: MarshalAs(UnmanagedType.U1)] diff --git a/Core/2.Processing/Paint.meta b/Core/2.Processing/Paint.meta new file mode 100644 index 00000000..29e4f274 --- /dev/null +++ b/Core/2.Processing/Paint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0ac4546c6d72d08448ff86c988976d39 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Core/2.Processing/Paint/ChiselPaintStore.cs b/Core/2.Processing/Paint/ChiselPaintStore.cs new file mode 100644 index 00000000..d4e8247e --- /dev/null +++ b/Core/2.Processing/Paint/ChiselPaintStore.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace Chisel.Core +{ + // The surface a paint layer belongs to + internal struct ChiselPaintKey : IEquatable + { + public ulong entityID; + public int surfaceIndex; + + public readonly bool Equals(ChiselPaintKey other) => entityID == other.entityID && surfaceIndex == other.surfaceIndex; + public override readonly int GetHashCode() => (int)math.hash(new uint3((uint)entityID, (uint)(entityID >> 32), (uint)surfaceIndex)); + } + + // A surface's layers in the tree's layer list, and a hash of them and their values + internal struct ChiselPaintRange + { + public int start; + public int count; + public uint hash; + } + + // The tree's paint as an update reads it: views of what the tree keeps (ChiselPaintStore) + internal struct ChiselPaintLookup + { + [ReadOnly] public NativeParallelHashMap ranges; + [ReadOnly] public NativeArray layers; + [ReadOnly] public NativeArray values; + public bool hasPaint; + // the attributes of the materials whose shaders take paint, by material (ChiselPaintMaterials) + [ReadOnly] public NativeParallelHashMap materials; + [ReadOnly] public NativeArray materialSlots; + public bool hasMaterials; + + public readonly bool TryGetRange(ulong entityID, int surfaceIndex, out ChiselPaintRange range) + { + range = default; + return hasPaint && ranges.TryGetValue(new ChiselPaintKey { entityID = entityID, surfaceIndex = surfaceIndex }, out range); + } + + public readonly bool TryGetMaterial(ulong material, out ChiselPaintRange range) + { + range = default; + return hasMaterials && materials.TryGetValue(material, out range); + } + + public static ChiselPaintLookup Of(ChiselTreeLookup.Data data) + { + return new ChiselPaintLookup + { + ranges = data.paintRanges, + layers = data.paintLayers.AsArray(), + values = data.paintValues.AsArray(), + hasPaint = data.paintLayers.Length > 0, + materials = data.paintMaterials, + materialSlots = data.paintMaterialSlots.AsArray(), + hasMaterials = data.paintMaterialSlots.Length > 0 + }; + } + } + + // The tree keeps its paint between updates: each entity's layers together, their values appended as they come (the + // values of replaced layers are left until they are half of them), and the ranges and hashes an update looks up + internal static class ChiselPaintStore + { + struct BySurface : IComparer + { + public int Compare(ChiselPaintLayer x, ChiselPaintLayer y) + { + if (x.surfaceIndex != y.surfaceIndex) return x.surfaceIndex.CompareTo(y.surfaceIndex); + return ((int)x.attribute).CompareTo((int)y.attribute); + } + } + + // A layer and its values + public static unsafe uint HashOf(in ChiselPaintLayer layer, float* values) + { + var copy = layer; + copy.valueStart = 0; + var hash = math.hash(©, UnsafeUtility.SizeOf()); + return math.hash(new uint2(hash, math.hash(values + layer.valueStart, layer.ValueCount * sizeof(float)))); + } + + /// Replaces the paint of one entity: its layers, and the values their grids take from. True when it changed; + /// its brushes then have to be rebuilt to show it. + public static unsafe bool SetPaint(CSGTree tree, ulong entityID, NativeArray layers, NativeArray values) + { + if (!tree.Valid) + return false; + var data = ChiselTreeLookup.Value[tree]; + + NativeList sorted; + using var _sorted = sorted = new NativeList(math.max(1, layers.IsCreated ? layers.Length : 0), Allocator.Temp); + for (int l = 0; layers.IsCreated && l < layers.Length; l++) + { + var layer = layers[l]; + if (layer.ValueCount <= 0 || layer.valueStart < 0 || layer.valueStart + layer.ValueCount > values.Length) + continue; + layer.entityID = entityID; + sorted.Add(layer); + } + sorted.Sort(new BySurface()); + + NativeList hashes; + using var _hashes = hashes = new NativeList(math.max(1, sorted.Length), Allocator.Temp); + var entityHash = 0u; + var valuesPointer = values.IsCreated ? (float*)values.GetUnsafeReadOnlyPtr() : null; + for (int l = 0; l < sorted.Length; l++) + { + hashes.Add(HashOf(sorted[l], valuesPointer)); + entityHash = math.hash(new uint2(entityHash, hashes[l])); + } + data.paintEntityHashes.TryGetValue(entityID, out var previous); + if (sorted.Length == 0 ? !data.paintEntityHashes.ContainsKey(entityID) : previous == entityHash && data.paintEntityHashes.ContainsKey(entityID)) + return false; + + data.lastJobHandle.Complete(); + + // its old layers out, their values left behind + var first = 0; + while (first < data.paintLayers.Length && data.paintLayers[first].entityID != entityID) + first++; + var last = first; + while (last < data.paintLayers.Length && data.paintLayers[last].entityID == entityID) + { + data.paintUnusedValues += data.paintLayers[last].ValueCount; + last++; + } + data.paintLayers.RemoveRange(first, last - first); + data.paintLayerHashes.RemoveRange(first, last - first); + + // its new layers at the end, their values after the others + for (int l = 0; l < sorted.Length; l++) + { + var layer = sorted[l]; + var start = data.paintValues.Length; + data.paintValues.AddRange(valuesPointer + layer.valueStart, layer.ValueCount); + layer.valueStart = start; + data.paintLayers.Add(layer); + data.paintLayerHashes.Add(hashes[l]); + } + if (sorted.Length == 0) + data.paintEntityHashes.Remove(entityID); + else + data.paintEntityHashes[entityID] = entityHash; + + if (data.paintUnusedValues > 0 && data.paintUnusedValues * 2 >= data.paintValues.Length) + Compact(data); + UpdateRanges(data); + return true; + } + + // The values of the layers only, in their order + static void Compact(ChiselTreeLookup.Data data) + { + using var compacted = new NativeList(math.max(1, data.paintValues.Length - data.paintUnusedValues), Allocator.Temp); + for (int l = 0; l < data.paintLayers.Length; l++) + { + var layer = data.paintLayers[l]; + var start = compacted.Length; + compacted.AddRange(data.paintValues.AsArray().GetSubArray(layer.valueStart, layer.ValueCount)); + layer.valueStart = start; + data.paintLayers[l] = layer; + } + data.paintValues.Clear(); + data.paintValues.AddRange(compacted.AsArray()); + data.paintUnusedValues = 0; + } + + // Each surface's layers (an entity's are together, by surface) and their hash + static void UpdateRanges(ChiselTreeLookup.Data data) + { + var layers = data.paintLayers; + data.paintRanges.Clear(); + if (data.paintRanges.Capacity < layers.Length) + data.paintRanges.Capacity = layers.Length; + for (int start = 0; start < layers.Length;) + { + var key = new ChiselPaintKey { entityID = layers[start].entityID, surfaceIndex = layers[start].surfaceIndex }; + var end = start; + var hash = 0u; + for (; end < layers.Length && layers[end].entityID == key.entityID && layers[end].surfaceIndex == key.surfaceIndex; end++) + hash = math.hash(new uint2(hash, data.paintLayerHashes[end])); + data.paintRanges.TryAdd(key, new ChiselPaintRange { start = start, count = end - start, hash = hash }); + start = end; + } + } + + /// Replaces all paint of the tree: the entities in the layers get theirs, every other entity loses its paint. + /// Returns the entities whose paint changed. + public static bool SetPaint(CSGTree tree, NativeArray layers, NativeArray values, NativeList changedEntities) + { + if (changedEntities.IsCreated) + changedEntities.Clear(); + if (!tree.Valid) + return false; + var data = ChiselTreeLookup.Value[tree]; + + var entities = new List(); + for (int l = 0; layers.IsCreated && l < layers.Length; l++) + { + if (!entities.Contains(layers[l].entityID)) + entities.Add(layers[l].entityID); + } + using var gone = data.paintEntityHashes.GetKeyArray(Allocator.Temp); + foreach (var entityID in gone) + { + if (!entities.Contains(entityID) && SetPaint(tree, entityID, default, default) && changedEntities.IsCreated) + changedEntities.Add(entityID); + } + + using var own = new NativeList(Allocator.Temp); + foreach (var entityID in entities) + { + own.Clear(); + for (int l = 0; l < layers.Length; l++) + { + if (layers[l].entityID == entityID) + own.Add(layers[l]); + } + if (SetPaint(tree, entityID, own.AsArray(), values) && changedEntities.IsCreated) + changedEntities.Add(entityID); + } + return changedEntities.IsCreated && changedEntities.Length > 0; + } + + // The tree's copy of the materials' paint attributes, made again when they changed + public static void UpdateMaterials(ChiselTreeLookup.Data data) + { + var version = ChiselPaintMaterials.Refresh(); + if (data.paintMaterialsVersion == version) + return; + data.lastJobHandle.Complete(); + data.paintMaterialsVersion = version; + var slots = data.paintMaterialSlots; + slots.Clear(); + ChiselPaintMaterials.Gather(slots); + data.paintMaterials.Clear(); + if (data.paintMaterials.Capacity < slots.Length) + data.paintMaterials.Capacity = slots.Length; + // a material's slots are together (ChiselPaintMaterials) + for (int start = 0; start < slots.Length;) + { + var material = slots[start].material; + var end = start; + var hash = 0u; + for (; end < slots.Length && slots[end].material == material; end++) + hash = math.hash(new uint4(hash, (uint)slots[end].attribute, (uint)slots[end].components, math.hash(math.asuint(slots[end].defaultValue)))); + data.paintMaterials.TryAdd(material, new ChiselPaintRange { start = start, count = end - start, hash = hash }); + start = end; + } + } + } +} diff --git a/Core/2.Processing/Paint/ChiselPaintStore.cs.meta b/Core/2.Processing/Paint/ChiselPaintStore.cs.meta new file mode 100644 index 00000000..3201fb30 --- /dev/null +++ b/Core/2.Processing/Paint/ChiselPaintStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 545936201c55cb54cb80de9c9199fa4e \ No newline at end of file diff --git a/Core/2.Processing/Paint/PaintSubdivision.cs b/Core/2.Processing/Paint/PaintSubdivision.cs new file mode 100644 index 00000000..029f6403 --- /dev/null +++ b/Core/2.Processing/Paint/PaintSubdivision.cs @@ -0,0 +1,362 @@ +using Unity.Collections; +using Unity.Mathematics; + +namespace Chisel.Core +{ + // Subdivides a painted surface at its paint's grid: every grid point inside it, and every place a grid line + // crosses its outline, is inserted as in an incremental Delaunay triangulation. Cutting the outline leaves + // T-junctions with the neighbouring surfaces. + internal static class PaintSubdivision + { + // Most points one surface gets; a grid of tiny cells on a big surface stops here + public const int kMaxPoints = 4096; + // How close to an edge or a corner a grid point counts as on it, in parts of the grid's cell + const float kSnap = 0.05f; + + public static void Subdivide(NativeList indices, ref UniqueVertexMapper mapper, float3 surfaceNormal, + in ChiselPaintLookup paint, in ChiselPaintRange range) + { + if (range.count <= 0 || indices.Length < 3) + return; + var snap = float.PositiveInfinity; + using var points = new NativeList(64, Allocator.Temp); + using var seen = new NativeParallelHashSet(64, Allocator.Temp); + for (int l = range.start; l < range.start + range.count; l++) + { + var layer = paint.layers[l]; + var normal = math.normalizesafe(math.cross(layer.cellU, layer.cellV)); + // only a surface in the grid's plane, or parallel to it + if (math.abs(math.dot(normal, surfaceNormal)) < 0.999f) + continue; + snap = math.min(snap, kSnap * math.min(math.length(layer.cellU), math.length(layer.cellV))); + for (int j = 0; j < layer.resolution.y && points.Length < kMaxPoints; j++) + { + for (int i = 0; i < layer.resolution.x && points.Length < kMaxPoints; i++) + { + var point = layer.origin + i * layer.cellU + j * layer.cellV; + if (seen.Add((int3)math.round(point * 1e4f))) + points.Add(point); + } + } + } + if (points.Length == 0) + return; + + // where the grid's lines cross the outline, before the points: an outline that isn't along the grid's lines + // is cut where they meet it + using var crossings = new NativeList(16, Allocator.Temp); + for (int l = range.start; l < range.start + range.count; l++) + { + var layer = paint.layers[l]; + var normal = math.normalizesafe(math.cross(layer.cellU, layer.cellV)); + if (math.abs(math.dot(normal, surfaceNormal)) < 0.999f) + continue; + AddOutlineCrossings(indices, mapper.surfaceColliderVertices, layer, crossings); + } + + var triangulation = new Triangulation(indices, ref mapper, surfaceNormal, snap); + for (int p = 0; p < crossings.Length; p++) + triangulation.Insert(ref mapper, crossings[p]); + for (int p = 0; p < points.Length; p++) + triangulation.Insert(ref mapper, points[p]); + triangulation.Dispose(); + } + + // Where the grid's lines (whole numbers of cells along cellU or cellV) cross the edges of the outline (the edges + // only one triangle has), away from the edges' ends + static void AddOutlineCrossings(NativeList indices, NativeList positions, in ChiselPaintLayer layer, NativeList crossings) + { + using var edges = new NativeParallelHashSet(indices.Length, Allocator.Temp); + for (int t = 0; t + 2 < indices.Length; t += 3) + { + for (int k = 0; k < 3; k++) + edges.Add(new int2(indices[t + k], indices[t + (k + 1) % 3])); + } + const double kAwayFromEnds = 0.05; // in cells + for (int t = 0; t + 2 < indices.Length; t += 3) + { + for (int k = 0; k < 3 && crossings.Length < kMaxPoints; k++) + { + var a = indices[t + k]; + var b = indices[t + (k + 1) % 3]; + if (edges.Contains(new int2(b, a))) + continue; // inside: two triangles have it + var from = GridCoordinates(layer, positions[a]); + var to = GridCoordinates(layer, positions[b]); + for (int axis = 0; axis < 2; axis++) + { + var lo = math.min(from[axis], to[axis]); + var hi = math.max(from[axis], to[axis]); + if (!(hi - lo > 2 * kAwayFromEnds)) + continue; + for (var line = math.ceil(lo + kAwayFromEnds); line < hi - kAwayFromEnds && crossings.Length < kMaxPoints; line++) + { + var along = (line - from[axis]) / (to[axis] - from[axis]); + crossings.Add((float3)math.lerp((double3)positions[a], (double3)positions[b], along)); + } + } + } + } + } + + // A point's place on the grid, in cells along cellU and cellV (which needn't be at right angles) + static double2 GridCoordinates(in ChiselPaintLayer layer, float3 point) + { + var offset = (double3)(point - layer.origin); + double3 u = layer.cellU, v = layer.cellV; + var uu = math.dot(u, u); var uv = math.dot(u, v); var vv = math.dot(v, v); + var determinant = uu * vv - uv * uv; + if (!(math.abs(determinant) > 1e-18)) + return double2.zero; + var du = math.dot(offset, u); var dv = math.dot(offset, v); + return new double2((vv * du - uv * dv) / determinant, (uu * dv - uv * du) / determinant); + } + + struct Triangulation + { + enum Result { NotHere, Inserted, Existing } + + NativeList indices; + NativeList flat; // every vertex in the surface's plane + NativeList pending; // edges (a, b) of triangles (a, b, v) round a new vertex v, to be made Delaunay + // the triangle each edge (a, b) is in, in its winding + NativeParallelHashMap edges; + double3 axisU, axisV; + double snap; + int last; // where the last point went: the next is usually near it + + public Triangulation(NativeList indices, ref UniqueVertexMapper mapper, float3 normal, double snap) + { + this.indices = indices; + this.snap = snap; + last = 0; + axisU = math.normalizesafe(math.cross((double3)normal, math.abs(normal.x) < 0.9f ? new double3(1, 0, 0) : new double3(0, 1, 0))); + axisV = math.normalizesafe(math.cross((double3)normal, axisU)); + flat = new NativeList(mapper.surfaceColliderVertices.Length, Allocator.Temp); + pending = new NativeList(16, Allocator.Temp); + edges = new NativeParallelHashMap(math.max(16, indices.Length * 4), Allocator.Temp); + for (int i = 0; i < mapper.surfaceColliderVertices.Length; i++) + flat.Add(Flat(mapper.surfaceColliderVertices[i])); + for (int t = 0; t + 2 < indices.Length; t += 3) + { + for (int k = 0; k < 3; k++) + edges[new int2(indices[t + k], indices[t + (k + 1) % 3])] = t; + } + } + + public void Dispose() + { + flat.Dispose(); + pending.Dispose(); + edges.Dispose(); + } + + readonly double2 Flat(float3 position) => new double2(math.dot((double3)position, axisU), math.dot((double3)position, axisV)); + + void SetTriangle(int t, int a, int b, int c) + { + for (int k = 0; k < 3; k++) + { + var edge = new int2(indices[t + k], indices[t + (k + 1) % 3]); + if (edges.TryGetValue(edge, out var owner) && owner == t) + edges.Remove(edge); + } + indices[t] = a; indices[t + 1] = b; indices[t + 2] = c; + edges[new int2(a, b)] = t; + edges[new int2(b, c)] = t; + edges[new int2(c, a)] = t; + } + + void AddTriangle(int a, int b, int c) + { + var t = indices.Length; + indices.Add(a); indices.Add(b); indices.Add(c); + edges[new int2(a, b)] = t; + edges[new int2(b, c)] = t; + edges[new int2(c, a)] = t; + } + + // The triangle the point is in, walking from where the last point went; -1 when the walk leaves the surface + readonly int Locate(double2 point) + { + var t = last + 2 < indices.Length ? last : 0; + for (int steps = 0; steps <= indices.Length / 3; steps++) + { + int i0 = indices[t], i1 = indices[t + 1], i2 = indices[t + 2]; + var a = flat[i0]; var b = flat[i1]; var c = flat[i2]; + var twiceArea = Cross(a, b, c); + if (!(math.abs(twiceArea) > 1e-18)) + return -1; + var w = new double3(Cross(point, b, c), Cross(a, point, c), Cross(a, b, point)) / twiceArea; + // on to the triangle across the edge it is furthest beyond + var k = w.x <= w.y && w.x <= w.z ? 0 : w.y <= w.z ? 1 : 2; + if (w[k] >= 0) + return t; + int e0 = k == 0 ? i1 : k == 1 ? i2 : i0; + int e1 = k == 0 ? i2 : k == 1 ? i0 : i1; + if (!edges.TryGetValue(new int2(e1, e0), out t)) + return -1; + } + return -1; + } + + public void Insert(ref UniqueVertexMapper mapper, float3 position) + { + var point = Flat(position); + var found = Locate(point); + if (found >= 0 && TryInsert(ref mapper, point, found) != Result.NotHere) + return; + // near the outline, or where the walk stopped: every triangle + for (int t = 0; t + 2 < indices.Length; t += 3) + { + if (t != found && TryInsert(ref mapper, point, t) != Result.NotHere) + return; + } + } + + Result TryInsert(ref UniqueVertexMapper mapper, double2 point, int t) + { + int i0 = indices[t], i1 = indices[t + 1], i2 = indices[t + 2]; + var a = flat[i0]; var b = flat[i1]; var c = flat[i2]; + var twiceArea = Cross(a, b, c); + if (!(math.abs(twiceArea) > 1e-18)) + return Result.NotHere; + // the point's weights, and how far it is from each edge (the one opposite each corner) + var w = new double3(Cross(point, b, c), Cross(a, point, c), Cross(a, b, point)) / twiceArea; + var distance = w * math.abs(twiceArea) / new double3(math.distance(b, c), math.distance(c, a), math.distance(a, b)); + if (math.cmin(distance) < -snap) + return Result.NotHere; + if (math.distance(point, a) < snap || math.distance(point, b) < snap || math.distance(point, c) < snap) + return Result.Existing; + + // snapped onto the outline when near it (a sliver there can't be flipped away), onto an inner edge + // only when on it + var nearest = distance.x <= distance.y && distance.x <= distance.z ? 0 : distance.y <= distance.z ? 1 : 2; + int opposite = nearest == 0 ? i0 : nearest == 1 ? i1 : i2; + int e0 = nearest == 0 ? i1 : nearest == 1 ? i2 : i0; + int e1 = nearest == 0 ? i2 : nearest == 1 ? i0 : i1; + var other = FindEdge(e1, e0, out var otherOpposite); + var onEdge = other < 0 ? snap : snap * 1e-3; + if (distance[nearest] < -onEdge) + return Result.NotHere; // across an edge inside: in the triangle on its other side + + last = t; + if (distance[nearest] >= onEdge) + { + // inside: three triangles round it + var v = AddVertex(ref mapper, i0, i1, i2, w); + SetTriangle(t, i0, i1, v); + AddTriangle(i1, i2, v); + AddTriangle(i2, i0, v); + pending.Add(new int2(i0, i1)); + pending.Add(new int2(i1, i2)); + pending.Add(new int2(i2, i0)); + Legalize(v); + return Result.Inserted; + } + + // on the edge: it splits both of its triangles, or the one on the surface's outline + w = math.max(w, 0); + w[nearest] = 0; + w /= math.csum(w); + var vertex = AddVertex(ref mapper, i0, i1, i2, w); + SetTriangle(t, opposite, e0, vertex); + AddTriangle(opposite, vertex, e1); + pending.Add(new int2(opposite, e0)); + pending.Add(new int2(e1, opposite)); + if (other >= 0) + { + SetTriangle(other, otherOpposite, e1, vertex); + AddTriangle(otherOpposite, vertex, e0); + pending.Add(new int2(otherOpposite, e1)); + pending.Add(new int2(e0, otherOpposite)); + } + Legalize(vertex); + return Result.Inserted; + } + + // Each edge (a, b) of a triangle (a, b, v) is flipped when the far corner d of the triangle across it is in + // the circle through a, b and v; the two edges that then face v are looked at in turn + void Legalize(int v) + { + while (pending.Length > 0) + { + var edge = pending[pending.Length - 1]; + pending.RemoveAt(pending.Length - 1); + int a = edge.x, b = edge.y; + var t = FindEdge(a, b, out var third); + if (t < 0 || third != v) + continue; + var u = FindEdge(b, a, out var d); + if (u < 0) + continue; // the outline + if (!InCircle(flat[a], flat[b], flat[v], flat[d]) || !Convex(flat[a], flat[d], flat[b], flat[v])) + continue; + // (a, b, v) and (b, a, d) become (v, a, d) and (d, b, v) + SetTriangle(t, v, a, d); + SetTriangle(u, d, b, v); + pending.Add(new int2(a, d)); + pending.Add(new int2(d, b)); + } + } + + // The triangle with the edge from a to b (in its winding), and its third corner; -1 when there is none + readonly int FindEdge(int a, int b, out int opposite) + { + if (!edges.TryGetValue(new int2(a, b), out var t)) + { + opposite = -1; + return -1; + } + var k = indices[t] == a ? 0 : indices[t + 1] == a ? 1 : 2; + opposite = indices[t + (k + 2) % 3]; + return t; + } + + // A vertex where the weights put it in the triangle, every stream interpolated + int AddVertex(ref UniqueVertexMapper mapper, int i0, int i1, int i2, double3 w) + { + var f = (float3)w; + var r0 = mapper.surfaceRenderVertices[i0]; + var r1 = mapper.surfaceRenderVertices[i1]; + var r2 = mapper.surfaceRenderVertices[i2]; + var render = new RenderVertex + { + position = r0.position * f.x + r1.position * f.y + r2.position * f.z, + normal = math.normalizesafe(r0.normal * f.x + r1.normal * f.y + r2.normal * f.z), + tangent = r0.tangent * f.x + r1.tangent * f.y + r2.tangent * f.z, + uv0 = r0.uv0 * f.x + r1.uv0 * f.y + r2.uv0 * f.z, + uv1 = r0.uv1 * f.x + r1.uv1 * f.y + r2.uv1 * f.z + }; + var select = mapper.surfaceSelectVertices[i0]; + select.position = render.position; + var index = mapper.surfaceRenderVertices.Length; + mapper.surfaceRenderVertices.Add(render); + mapper.surfaceColliderVertices.Add(render.position); + mapper.surfaceSelectVertices.Add(select); + flat.Add(flat[i0] * w.x + flat[i1] * w.y + flat[i2] * w.z); + return index; + } + } + + static double Cross(double2 o, double2 p, double2 q) => (p.x - o.x) * (q.y - o.y) - (p.y - o.y) * (q.x - o.x); + + // Whether d is inside the circle through a, b and c, whichever way round they go (cocircular points are not) + static bool InCircle(double2 a, double2 b, double2 c, double2 d) + { + var ad = a - d; var bd = b - d; var cd = c - d; + var det = math.lengthsq(ad) * (bd.x * cd.y - cd.x * bd.y) - + math.lengthsq(bd) * (ad.x * cd.y - cd.x * ad.y) + + math.lengthsq(cd) * (ad.x * bd.y - bd.x * ad.y); + var scale = math.lengthsq(ad) * math.lengthsq(bd) + 1e-30; + return Cross(a, b, c) > 0 ? det > 1e-9 * scale : det < -1e-9 * scale; + } + + // Whether the quadrilateral a, d, b, c (in order) is convex, so its diagonal c-d lies inside it + static bool Convex(double2 a, double2 d, double2 b, double2 c) + { + var s0 = Cross(a, d, b); var s1 = Cross(d, b, c); var s2 = Cross(b, c, a); var s3 = Cross(c, a, d); + return (s0 > 0 && s1 > 0 && s2 > 0 && s3 > 0) || (s0 < 0 && s1 < 0 && s2 < 0 && s3 < 0); + } + } +} diff --git a/Core/2.Processing/Paint/PaintSubdivision.cs.meta b/Core/2.Processing/Paint/PaintSubdivision.cs.meta new file mode 100644 index 00000000..916d3c2b --- /dev/null +++ b/Core/2.Processing/Paint/PaintSubdivision.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 68ea7d5ea6ef55849b37801acc96d539 \ No newline at end of file diff --git a/Core/3.Output/OutputMeshes/ChiselOutputRenderable.cs b/Core/3.Output/OutputMeshes/ChiselOutputRenderable.cs index ef60fd75..b0835389 100644 --- a/Core/3.Output/OutputMeshes/ChiselOutputRenderable.cs +++ b/Core/3.Output/OutputMeshes/ChiselOutputRenderable.cs @@ -129,12 +129,57 @@ public static unsafe void Prepare([NoAlias, ReadOnly] NativeArray(); + for (int slot = 0; slot < ChiselPaintOutput.kSlotCount; slot++) + paintDefaults.Add(float4.zero); + for (int d = startIndex; d < endIndex; d++) + { + var description = subMeshSource.subMeshDescriptions[d]; + var surfaceArray = subMeshSource.subMeshSurfaces[description.meshQueryIndex]; + for (int s = description.surfacesOffset, last = s + description.surfacesCount; s < last; s++) + { + ref var brush = ref surfaceArray[s].brushRenderBuffer.Value; + ref var surface = ref brush.surfaces[surfaceArray[s].surfaceIndex]; + if (subMeshSource.paint.TryGetMaterial(surface.destinationParameters.parameter1, out var materialRange)) + { + for (int m = materialRange.start; m < materialRange.start + materialRange.count; m++) + { + var slot = ChiselPaintOutput.SlotOf(subMeshSource.paint.materialSlots[m].attribute); + if ((paintSlots & (1 << slot)) == 0) + paintDefaults[slot] = subMeshSource.paint.materialSlots[m].defaultValue; + paintSlots |= 1 << slot; + paintComponents |= (int)subMeshSource.paint.materialSlots[m].components << (slot * 4); + } + } + if (surface.paintSurface < 0 || !subMeshSource.paint.TryGetRange(brush.entityID, surface.paintSurface, out var range)) + continue; + for (int l = range.start; l < range.start + range.count; l++) + { + var slot = ChiselPaintOutput.SlotOf(subMeshSource.paint.layers[l].attribute); + if ((paintSlots & (1 << slot)) == 0) + paintDefaults[slot] = subMeshSource.paint.layers[l].defaultValue; + paintSlots |= 1 << slot; + paintComponents |= (int)subMeshSource.paint.layers[l].components << (slot * 4); + } + } + } + var paintStride = ChiselPaintOutput.StrideOf(paintSlots, paintComponents); + if (paintSlots != 0) + { + var painted = ChiselPaintOutput.Descriptors(descriptors, paintSlots, paintComponents); + meshData.SetVertexBufferParams(totalVertexCount, painted); + painted.Dispose(); + } else + meshData.SetVertexBufferParams(totalVertexCount, descriptors); meshData.SetIndexBufferParams(totalIndexCount, IndexFormat.UInt32); meshData.subMeshCount = numberOfSubMeshes; var indices = meshData.GetIndexData(); var vertices = meshData.GetVertexData(stream: 0); + var paintPointer = paintSlots != 0 ? (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(meshData.GetVertexData(stream: 1)) : null; var indexBufferLength = indices.Length; var vertexBufferLength = vertices.Length; // the ranges write through these, so no copy asks the mesh for its buffers again @@ -219,8 +264,19 @@ public static unsafe void Prepare([NoAlias, ReadOnly] NativeArray= 0) + subMeshSource.paint.TryGetRange(subMeshSurface.brushRenderBuffer.Value.entityID, sourceBuffer.paintSurface, out paintRange); ranges.Add(new RenderCopyRange { + paint = paintPointer, + paintSlots = paintSlots, + paintComponents = paintComponents, + paintStride = paintStride, + paintDefaults = paintDefaults, + paintLayers = paintRange.count > 0 ? (ChiselPaintLayer*)subMeshSource.paint.layers.GetUnsafeReadOnlyPtr() + paintRange.start : null, + paintLayerCount = paintRange.count, + paintValues = paintRange.count > 0 ? (float*)subMeshSource.paint.values.GetUnsafeReadOnlyPtr() : null, meshIndex = meshIndex, indices = indexPointer, vertices = vertexPointer, @@ -285,6 +341,8 @@ public static unsafe void CopyRange(RenderCopyRange range) for (int v = 0; v < sourceVertices.Length; v++) { var vertex = sourceVertices[v]; + if (range.paint != null) + ChiselPaintOutput.Write(range, vertex.position, range.paint + (range.baseVertex + v) * range.paintStride); vertex.uv1 = range.placement.Place(vertex.uv1, range.chartRect) * range.lightmapScale; vertices[range.baseVertex + v] = vertex; } @@ -303,6 +361,96 @@ internal unsafe struct RenderCopyRange public LightmapChartPlacement placement; public float4 chartRect; public float lightmapScale; + // The mesh's paint stream (null without one): the slots it has, their components (4 bits a slot), a vertex's bytes, + // the slots' defaults, and the surface's layers + [NativeDisableUnsafePtrRestriction] public byte* paint; + public int paintSlots; + public int paintComponents; + public int paintStride; + public FixedList128Bytes paintDefaults; + [NativeDisableUnsafePtrRestriction] public ChiselPaintLayer* paintLayers; + public int paintLayerCount; + [NativeDisableUnsafePtrRestriction] public float* paintValues; + } + + // How paint goes into a render mesh: a second vertex stream with every vertex attribute painted on its surfaces (a + // slot), in the order of Unity's attributes: the color as 4 bytes, a texture coordinate as floats up to its last + // painted component + internal static unsafe class ChiselPaintOutput + { + public const int kSlotCount = 7; + const int kColorBytes = 4; + + public static int SlotOf(ChiselPaintAttribute attribute) => attribute == ChiselPaintAttribute.Color ? 0 : (int)attribute - 1; + + static VertexAttribute AttributeOf(int slot) => slot == 0 ? VertexAttribute.Color : VertexAttribute.TexCoord0 + (slot + 1); + + // How many floats a texture coordinate slot has: up to its last painted component + static int DimensionOf(int components, int slot) + { + var mask = (components >> (slot * 4)) & 15; + return mask == 0 ? 0 : 32 - math.lzcnt(mask); + } + + public static int StrideOf(int slots, int components) + { + var stride = (slots & 1) != 0 ? kColorBytes : 0; + for (int slot = 1; slot < kSlotCount; slot++) + { + if ((slots & (1 << slot)) != 0) + stride += DimensionOf(components, slot) * sizeof(float); + } + return stride; + } + + // The mesh's attributes with the painted ones: Color after Tangent, the texture coordinates at the end + public static NativeArray Descriptors(NativeArray descriptors, int slots, int components) + { + var result = new NativeList(descriptors.Length + kSlotCount, Allocator.Temp); + for (int i = 0; i < descriptors.Length; i++) + { + result.Add(descriptors[i]); + if (descriptors[i].attribute == VertexAttribute.Tangent && (slots & 1) != 0) + result.Add(new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.UNorm8, 4, stream: 1)); + } + for (int slot = 1; slot < kSlotCount; slot++) + { + if ((slots & (1 << slot)) != 0) + result.Add(new VertexAttributeDescriptor(AttributeOf(slot), VertexAttributeFormat.Float32, DimensionOf(components, slot), stream: 1)); + } + var array = new NativeArray(result.AsArray(), Allocator.Temp); + result.Dispose(); + return array; + } + + // A vertex's paint: each slot's default, with every layer of the surface in it written over that + public static void Write(in RenderCopyRange range, float3 position, byte* destination) + { + for (int slot = 0; slot < kSlotCount; slot++) + { + if ((range.paintSlots & (1 << slot)) == 0) + continue; + var value = range.paintDefaults[slot]; + for (int l = 0; l < range.paintLayerCount; l++) + { + var layer = range.paintLayers[l]; + if (SlotOf(layer.attribute) == slot) + value = ChiselPaintLayer.Apply(layer.Sample(range.paintValues, position), value, layer.components); + } + if (slot == 0) + { + var color = (uint4)math.round(math.saturate(value) * 255); + destination[0] = (byte)color.x; destination[1] = (byte)color.y; destination[2] = (byte)color.z; destination[3] = (byte)color.w; + destination += kColorBytes; + continue; + } + var floats = (float*)destination; + var dimension = DimensionOf(range.paintComponents, slot); + for (int c = 0; c < dimension; c++) + floats[c] = value[c]; + destination += dimension * sizeof(float); + } + } } diff --git a/Core/3.Output/OutputMeshes/IChiselOutputMeshCopier.cs b/Core/3.Output/OutputMeshes/IChiselOutputMeshCopier.cs index 3f68370f..3ef7f892 100644 --- a/Core/3.Output/OutputMeshes/IChiselOutputMeshCopier.cs +++ b/Core/3.Output/OutputMeshes/IChiselOutputMeshCopier.cs @@ -12,6 +12,8 @@ struct SubMeshSource [NoAlias, ReadOnly] public NativeArray> subMeshSurfaces; // How the meshes' lightmap coordinates are laid out: the model's (LightmapUVLayout) public LightmapUVSettings lightmapUVSettings; + // The tree's paint: a render mesh with painted surfaces takes it in a second vertex stream (ChiselOutputRenderable) + public ChiselPaintLookup paint; } internal interface IChiselOutputMeshCopier diff --git a/Core/3.Output/OutputMeshes/OutputHotspotSections.cs b/Core/3.Output/OutputMeshes/OutputHotspotSections.cs index ef65fd24..1a41cf76 100644 --- a/Core/3.Output/OutputMeshes/OutputHotspotSections.cs +++ b/Core/3.Output/OutputMeshes/OutputHotspotSections.cs @@ -492,6 +492,7 @@ static BlobAssetReference Cut(BlobAssetReference(); + root.entityID = original.entityID; var surfaces = builder.Allocate(ref root.surfaces, original.surfaces.Length); for (int s = 0; s < original.surfaces.Length; s++) { @@ -502,6 +503,7 @@ static BlobAssetReference Cut(BlobAssetReference Copy(BlobAssetReferenc { ref var original = ref source.Value; ref var root = ref builder.ConstructRoot(); + root.entityID = original.entityID; var surfaces = builder.Allocate(ref root.surfaces, original.surfaces.Length); for (int s = 0; s < original.surfaces.Length; s++) { @@ -1248,6 +1249,7 @@ static unsafe BlobAssetReference Copy(BlobAssetReferenc to.decalEntityID = from.decalEntityID; to.decalStack = from.decalStack; to.decalFades = from.decalFades; + to.paintSurface = from.paintSurface; to.destinationFlags = from.destinationFlags; to.destinationParameters = from.destinationParameters; to.outputFlags = from.outputFlags; diff --git a/Core/Tests/BrushMeshBlobLayoutTests.cs b/Core/Tests/BrushMeshBlobLayoutTests.cs new file mode 100644 index 00000000..27a9e1f1 --- /dev/null +++ b/Core/Tests/BrushMeshBlobLayoutTests.cs @@ -0,0 +1,51 @@ +using System.Text.RegularExpressions; +using NUnit.Framework; +using Unity.Burst; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Chisel.Core.Tests +{ + [TestFixture] + public sealed class BrushMeshBlobLayoutTests + { + // Burst-compiled jobs lay out brush blobs exactly as managed code reads them + [Test] + public void CompiledLayout_IsTheManagedLayout() + { + Assume.That(BurstCompiler.IsEnabled, Is.True, "Burst is off: the compiled layout is the managed one"); + var managed = BrushMeshBlobLayout.Measure(); + var compiled = BrushMeshBlobLayout.MeasureCompiled(); + Assert.That(BrushMeshBlobLayout.Differences(managed, compiled), Is.Empty); + } + + [Test] + public void Measure_ReadsTheManagedStructs() + { + var layout = BrushMeshBlobLayout.Measure(); + Assert.That(layout.polygonSize, Is.EqualTo(UnsafeUtility.SizeOf())); + Assert.That(layout.surfaceSize, Is.EqualTo(UnsafeUtility.SizeOf())); + Assert.That(layout.detailsSize, Is.EqualTo(UnsafeUtility.SizeOf())); + Assert.That(layout.polygonFirstEdge, Is.EqualTo(0)); + Assert.That(layout.polygonSurface + layout.surfaceSize, Is.LessThanOrEqualTo(layout.polygonSize)); + Assert.That(layout.surfaceDetails + layout.detailsSize, Is.LessThanOrEqualTo(layout.surfaceParameters)); + } + + // A stale layout: polygons and surfaces 8 bytes short + [Test] + public void Check_LogsEveryFieldThatDiffers() + { + var managed = BrushMeshBlobLayout.Measure(); + var stale = managed; + stale.polygonSize -= 8; + stale.surfaceParameters -= 8; + Assert.That(BrushMeshBlobLayout.Differences(managed, stale), + Is.EqualTo($"polygonSize {managed.polygonSize}/{stale.polygonSize}, surfaceParameters {managed.surfaceParameters}/{stale.surfaceParameters}")); + + LogAssert.Expect(LogType.Error, new Regex("another brush blob layout.*polygonSize")); + Assert.That(BrushMeshBlobLayout.Check(managed, stale), Is.False); + Assert.That(BrushMeshBlobLayout.Check(managed, managed), Is.True); + } + } +} diff --git a/Core/Tests/BrushMeshBlobLayoutTests.cs.meta b/Core/Tests/BrushMeshBlobLayoutTests.cs.meta new file mode 100644 index 00000000..0f6fe193 --- /dev/null +++ b/Core/Tests/BrushMeshBlobLayoutTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 032a40a01d5552d459f736166922ec9c \ No newline at end of file diff --git a/Core/Tests/ChiselPaintStoreTests.cs b/Core/Tests/ChiselPaintStoreTests.cs new file mode 100644 index 00000000..b5a2a6ff --- /dev/null +++ b/Core/Tests/ChiselPaintStoreTests.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Mathematics; + +namespace Chisel.Core.Tests +{ + // The paint a tree keeps between updates (ChiselPaintStore): replaced an entity at a time + [TestFixture] + public sealed class ChiselPaintStoreTests + { + CSGTree tree; + + [SetUp] + public void SetUp() { tree = CSGTree.Create(default(UnityEngine.EntityId)); } + + [TearDown] + public void TearDown() + { + if (tree.Valid) + { + ChiselTreeLookup.Value[tree].lastJobHandle.Complete(); + tree.Destroy(); + } + } + + // A 2 x 2 grid on a surface, every point at value + static bool Paint(CSGTree tree, ulong entityID, int surfaceIndex, float value) + { + NativeArray layers; + using var _layers = layers = new NativeArray(1, Allocator.Temp); + NativeArray values; + using var _values = values = new NativeArray(4 * 4, Allocator.Temp); + layers[0] = new ChiselPaintLayer { surfaceIndex = surfaceIndex, attribute = ChiselPaintAttribute.Color, components = ChiselPaintComponents.XYZW, + cellU = new float3(1, 0, 0), cellV = new float3(0, 0, 1), resolution = new int2(2, 2) }; + for (int i = 0; i < values.Length; i++) + values[i] = value; + return ChiselPaintStore.SetPaint(tree, entityID, layers, values); + } + + static float ValueOf(CSGTree tree, ulong entityID, int surfaceIndex) + { + var lookup = ChiselPaintLookup.Of(ChiselTreeLookup.Value[tree]); + Assert.That(lookup.TryGetRange(entityID, surfaceIndex, out var range), Is.True, $"surface {surfaceIndex} of {entityID}"); + return lookup.values[lookup.layers[range.start].valueStart]; + } + + [Test] + public void AnEntitysPaint_IsReplacedWithoutTouchingTheOthers() + { + Assert.That(Paint(tree, 1, 0, 0.25f), Is.True); + Assert.That(Paint(tree, 2, 3, 0.5f), Is.True); + Assert.That(Paint(tree, 1, 0, 0.25f), Is.False, "the same paint: no change"); + + Assert.That(Paint(tree, 1, 0, 0.75f), Is.True); + Assert.That(ValueOf(tree, 1, 0), Is.EqualTo(0.75f)); + Assert.That(ValueOf(tree, 2, 3), Is.EqualTo(0.5f)); + + using var none = new NativeArray(0, Allocator.Temp); + Assert.That(ChiselPaintStore.SetPaint(tree, 1, none, default), Is.True, "its paint removed"); + Assert.That(ChiselPaintLookup.Of(ChiselTreeLookup.Value[tree]).TryGetRange(1, 0, out _), Is.False); + Assert.That(ValueOf(tree, 2, 3), Is.EqualTo(0.5f)); + } + + // Values of replaced layers are dropped once they are half of all of them + [Test] + public void ReplacedValues_AreCompactedAway() + { + Paint(tree, 1, 0, 0); + for (int i = 1; i <= 10; i++) + Paint(tree, 1, 0, i); + var data = ChiselTreeLookup.Value[tree]; + Assert.That(data.paintValues.Length, Is.LessThanOrEqualTo(2 * 4 * 4)); + Assert.That(ValueOf(tree, 1, 0), Is.EqualTo(10)); + } + } +} diff --git a/Core/Tests/ChiselPaintStoreTests.cs.meta b/Core/Tests/ChiselPaintStoreTests.cs.meta new file mode 100644 index 00000000..638bba9d --- /dev/null +++ b/Core/Tests/ChiselPaintStoreTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: afdab3d64f8a8414f8f45eee2c3a4b16 \ No newline at end of file diff --git a/Core/Tests/Contents/SetStateTests.cs b/Core/Tests/Contents/SetStateTests.cs index 832f2779..143b4fb5 100644 --- a/Core/Tests/Contents/SetStateTests.cs +++ b/Core/Tests/Contents/SetStateTests.cs @@ -73,5 +73,25 @@ public void SetState_WithAMovedBrush_FlagsItAndWhatItTouches() Assert.That(hierarchy.GetLocalTransformation(brushID), Is.EqualTo(moved)); } } + + // Surfaces built again without a change of shape (paint): the brush alone, not what it touches + [Test] + public void SetSurfacesDirty_FlagsTheBrushOnly() + { + using (var harness = BuildAndUpdate()) + { + var brush = (CSGTreeBrush)harness.Tree[0]; + var brushID = brush.CompactNodeID; + ref var hierarchy = ref CompactHierarchyManager.GetHierarchy(brushID); + + ((CSGTreeNode)brush).SetSurfacesDirty(); + + Assert.That(hierarchy.IsStatusFlagSet(brushID, NodeStatusFlags.OutlineModified), Is.True); + Assert.That(hierarchy.IsStatusFlagSet(brushID, NodeStatusFlags.NeedAllTouchingUpdated), Is.False); + Assert.That(hierarchy.IsStatusFlagSet(brushID, NodeStatusFlags.ShapeModified), Is.False); + Assert.That(harness.Update(), Is.True, "the CSG update did not run"); + Assert.That(hierarchy.IsAnyStatusFlagSet(brushID), Is.False, "the update left flags on the brush"); + } + } } } diff --git a/Core/Tests/PaintSubdivisionTests.cs b/Core/Tests/PaintSubdivisionTests.cs new file mode 100644 index 00000000..f1ae378b --- /dev/null +++ b/Core/Tests/PaintSubdivisionTests.cs @@ -0,0 +1,90 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Mathematics; + +namespace Chisel.Core.Tests +{ + // A painted surface subdivided at its grid (PaintSubdivision): every grid point in it a vertex, its outline cut where + // the grid meets it, and no sliver left by the flips + public sealed class PaintSubdivisionTests + { + // A square of side 2 in the xz plane at offset, two triangles, and a grid of cells x cells over it exactly + static void Subdivide(float3 offset, bool otherDiagonal, int cells, out int vertexCount, out int triangleCount, out float minAngle, out string worst, out string offGrid) + { + var mapper = new UniqueVertexMapper + { + surfaceColliderVertices = new NativeList(Allocator.Temp), + surfaceSelectVertices = new NativeList(Allocator.Temp), + surfaceRenderVertices = new NativeList(Allocator.Temp) + }; + using var indices = new NativeList(Allocator.Temp); + var corners = new[] { new float3(0, 0, 0), new float3(0, 0, 2), new float3(2, 0, 2), new float3(2, 0, 0) }; + foreach (var corner in corners) + { + mapper.surfaceColliderVertices.Add(offset + corner); + mapper.surfaceSelectVertices.Add(new SelectVertex { position = offset + corner }); + mapper.surfaceRenderVertices.Add(new RenderVertex { position = offset + corner, normal = new float3(0, 1, 0) }); + } + if (otherDiagonal) { indices.Add(0); indices.Add(1); indices.Add(3); indices.Add(1); indices.Add(2); indices.Add(3); } + else { indices.Add(0); indices.Add(1); indices.Add(2); indices.Add(0); indices.Add(2); indices.Add(3); } + + NativeArray layers; + using var _layers = layers = new NativeArray(1, Allocator.Temp); + var size = 2f / cells; + using var values = new NativeArray(1, Allocator.Temp); + layers[0] = new ChiselPaintLayer { origin = offset, cellU = new float3(size, 0, 0), cellV = new float3(0, 0, size), resolution = new int2(cells + 1, cells + 1) }; + var lookup = new ChiselPaintLookup { layers = layers, values = values }; + PaintSubdivision.Subdivide(indices, ref mapper, new float3(0, 1, 0), lookup, new ChiselPaintRange { start = 0, count = 1 }); + + vertexCount = mapper.surfaceColliderVertices.Length; + offGrid = ""; + for (int v = 0; v < vertexCount; v++) + { + var cell = (mapper.surfaceColliderVertices[v] - offset) / size; + if (math.any(math.abs(cell - math.round(cell)) > 0.01f)) + offGrid += " " + (mapper.surfaceColliderVertices[v] - offset); + } + triangleCount = indices.Length / 3; + minAngle = 180; + worst = null; + for (int t = 0; t < indices.Length; t += 3) + { + var a = mapper.surfaceColliderVertices[indices[t]]; + var b = mapper.surfaceColliderVertices[indices[t + 1]]; + var c = mapper.surfaceColliderVertices[indices[t + 2]]; + var angle = math.min(Angle(a, b, c), math.min(Angle(b, c, a), Angle(c, a, b))); + if (angle < minAngle) + { + minAngle = angle; + worst = $"{a - offset} {b - offset} {c - offset}"; + } + } + mapper.surfaceColliderVertices.Dispose(); + mapper.surfaceSelectVertices.Dispose(); + mapper.surfaceRenderVertices.Dispose(); + } + + static float Angle(float3 o, float3 p, float3 q) => math.degrees(math.acos(math.clamp(math.dot(math.normalize(p - o), math.normalize(q - o)), -1, 1))); + + // All 21 x 21 points, the outline's too: the triangles are the grid's half cells, whichever way the square was cut and + // however far from the origin it is + [Test] + public void EveryGridPointInside_IsAVertex_AndNoTriangleIsASliver([Values(false, true)] bool otherDiagonal, [Values(0, 5000)] float offset) + { + Subdivide(new float3(offset, offset * 0.5f, -offset), otherDiagonal, 20, out var vertices, out var triangles, out var minAngle, out var worst, out var offGrid); + Assert.That(vertices, Is.EqualTo(21 * 21), "not on a grid point:" + offGrid); + Assert.That(triangles, Is.EqualTo(2 * 20 * 20)); + Assert.That(minAngle, Is.GreaterThan(30), "no sliver; the thinnest: " + worst); + } + + // A grid of as many points as a surface gets (64 x 64): every one a vertex, still the grid's half cells + [Test] + public void AGridOfTheMostPoints_IsSubdividedAsASmallOneIs() + { + Subdivide(float3.zero, false, 63, out var vertices, out var triangles, out var minAngle, out var worst, out var offGrid); + Assert.That(vertices, Is.EqualTo(PaintSubdivision.kMaxPoints), "not on a grid point:" + offGrid); + Assert.That(triangles, Is.EqualTo(2 * 63 * 63)); + Assert.That(minAngle, Is.GreaterThan(30), "no sliver; the thinnest: " + worst); + } + } +} diff --git a/Core/Tests/PaintSubdivisionTests.cs.meta b/Core/Tests/PaintSubdivisionTests.cs.meta new file mode 100644 index 00000000..41b6757f --- /dev/null +++ b/Core/Tests/PaintSubdivisionTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c47d53bfb2073d1478eb870c47b3f8b6 \ No newline at end of file diff --git a/Editor Resources/Icons/d_paint_add On.png b/Editor Resources/Icons/d_paint_add On.png new file mode 100644 index 00000000..89e4ca8b Binary files /dev/null and b/Editor Resources/Icons/d_paint_add On.png differ diff --git a/Editor Resources/Icons/d_paint_add On.png.meta b/Editor Resources/Icons/d_paint_add On.png.meta new file mode 100644 index 00000000..264ec84d --- /dev/null +++ b/Editor Resources/Icons/d_paint_add On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: c3443b72b9844bf1b5e0465ecaadcc2f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_add On@2x.png b/Editor Resources/Icons/d_paint_add On@2x.png new file mode 100644 index 00000000..9daa98ca Binary files /dev/null and b/Editor Resources/Icons/d_paint_add On@2x.png differ diff --git a/Editor Resources/Icons/d_paint_add On@2x.png.meta b/Editor Resources/Icons/d_paint_add On@2x.png.meta new file mode 100644 index 00000000..0b2ea81c --- /dev/null +++ b/Editor Resources/Icons/d_paint_add On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: b623d48380aa4c51be7fda749d92d58b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_add.png b/Editor Resources/Icons/d_paint_add.png new file mode 100644 index 00000000..86582aa6 Binary files /dev/null and b/Editor Resources/Icons/d_paint_add.png differ diff --git a/Editor Resources/Icons/d_paint_add.png.meta b/Editor Resources/Icons/d_paint_add.png.meta new file mode 100644 index 00000000..ce62b28d --- /dev/null +++ b/Editor Resources/Icons/d_paint_add.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 20df1199ee464d91b95351397b9e6105 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_add@2x.png b/Editor Resources/Icons/d_paint_add@2x.png new file mode 100644 index 00000000..7051013b Binary files /dev/null and b/Editor Resources/Icons/d_paint_add@2x.png differ diff --git a/Editor Resources/Icons/d_paint_add@2x.png.meta b/Editor Resources/Icons/d_paint_add@2x.png.meta new file mode 100644 index 00000000..80e5c08e --- /dev/null +++ b/Editor Resources/Icons/d_paint_add@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: bc0386e8f7ba4b239af7def8f3521c47 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_erase On.png b/Editor Resources/Icons/d_paint_erase On.png new file mode 100644 index 00000000..9b805386 Binary files /dev/null and b/Editor Resources/Icons/d_paint_erase On.png differ diff --git a/Editor Resources/Icons/d_paint_erase On.png.meta b/Editor Resources/Icons/d_paint_erase On.png.meta new file mode 100644 index 00000000..df31fdca --- /dev/null +++ b/Editor Resources/Icons/d_paint_erase On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 19a4a1e941b44564ae4e5b99d1c6310c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_erase On@2x.png b/Editor Resources/Icons/d_paint_erase On@2x.png new file mode 100644 index 00000000..68031153 Binary files /dev/null and b/Editor Resources/Icons/d_paint_erase On@2x.png differ diff --git a/Editor Resources/Icons/d_paint_erase On@2x.png.meta b/Editor Resources/Icons/d_paint_erase On@2x.png.meta new file mode 100644 index 00000000..4abd817b --- /dev/null +++ b/Editor Resources/Icons/d_paint_erase On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 2ec66989b1e948e8bb2eb98cfd5ef902 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_erase.png b/Editor Resources/Icons/d_paint_erase.png new file mode 100644 index 00000000..2f62b7a0 Binary files /dev/null and b/Editor Resources/Icons/d_paint_erase.png differ diff --git a/Editor Resources/Icons/d_paint_erase.png.meta b/Editor Resources/Icons/d_paint_erase.png.meta new file mode 100644 index 00000000..acaa6958 --- /dev/null +++ b/Editor Resources/Icons/d_paint_erase.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 9dc2cef8ce2845d9b0b982143b21fe18 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_erase@2x.png b/Editor Resources/Icons/d_paint_erase@2x.png new file mode 100644 index 00000000..100939d4 Binary files /dev/null and b/Editor Resources/Icons/d_paint_erase@2x.png differ diff --git a/Editor Resources/Icons/d_paint_erase@2x.png.meta b/Editor Resources/Icons/d_paint_erase@2x.png.meta new file mode 100644 index 00000000..bbc85ca5 --- /dev/null +++ b/Editor Resources/Icons/d_paint_erase@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: e06ede7264f34993886d1e8f364effde +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_fill On.png b/Editor Resources/Icons/d_paint_fill On.png new file mode 100644 index 00000000..b177fc08 Binary files /dev/null and b/Editor Resources/Icons/d_paint_fill On.png differ diff --git a/Editor Resources/Icons/d_paint_fill On.png.meta b/Editor Resources/Icons/d_paint_fill On.png.meta new file mode 100644 index 00000000..89dd31c5 --- /dev/null +++ b/Editor Resources/Icons/d_paint_fill On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 15a60df254c94ef78170f824df9d74fa +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_fill On@2x.png b/Editor Resources/Icons/d_paint_fill On@2x.png new file mode 100644 index 00000000..46debd15 Binary files /dev/null and b/Editor Resources/Icons/d_paint_fill On@2x.png differ diff --git a/Editor Resources/Icons/d_paint_fill On@2x.png.meta b/Editor Resources/Icons/d_paint_fill On@2x.png.meta new file mode 100644 index 00000000..27ce9ab1 --- /dev/null +++ b/Editor Resources/Icons/d_paint_fill On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 907888bab5024887bf3fc85918d1533c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_fill.png b/Editor Resources/Icons/d_paint_fill.png new file mode 100644 index 00000000..45f14ac4 Binary files /dev/null and b/Editor Resources/Icons/d_paint_fill.png differ diff --git a/Editor Resources/Icons/d_paint_fill.png.meta b/Editor Resources/Icons/d_paint_fill.png.meta new file mode 100644 index 00000000..408180e1 --- /dev/null +++ b/Editor Resources/Icons/d_paint_fill.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 6d66485304e34da9a58b74c95bbe2124 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_fill@2x.png b/Editor Resources/Icons/d_paint_fill@2x.png new file mode 100644 index 00000000..e842c1c5 Binary files /dev/null and b/Editor Resources/Icons/d_paint_fill@2x.png differ diff --git a/Editor Resources/Icons/d_paint_fill@2x.png.meta b/Editor Resources/Icons/d_paint_fill@2x.png.meta new file mode 100644 index 00000000..8c4961d4 --- /dev/null +++ b/Editor Resources/Icons/d_paint_fill@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 86ffe53716764a13b6ca64b212cc383e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_paint On.png b/Editor Resources/Icons/d_paint_paint On.png new file mode 100644 index 00000000..fde38083 Binary files /dev/null and b/Editor Resources/Icons/d_paint_paint On.png differ diff --git a/Editor Resources/Icons/d_paint_paint On.png.meta b/Editor Resources/Icons/d_paint_paint On.png.meta new file mode 100644 index 00000000..6aaf67ed --- /dev/null +++ b/Editor Resources/Icons/d_paint_paint On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 00eb6e2e9b2e413a8703d1c41c9014af +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_paint On@2x.png b/Editor Resources/Icons/d_paint_paint On@2x.png new file mode 100644 index 00000000..a9d9ed91 Binary files /dev/null and b/Editor Resources/Icons/d_paint_paint On@2x.png differ diff --git a/Editor Resources/Icons/d_paint_paint On@2x.png.meta b/Editor Resources/Icons/d_paint_paint On@2x.png.meta new file mode 100644 index 00000000..6ff3d877 --- /dev/null +++ b/Editor Resources/Icons/d_paint_paint On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: b51104cfb56949a78a38f9eec661b982 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_paint.png b/Editor Resources/Icons/d_paint_paint.png new file mode 100644 index 00000000..cfbf6893 Binary files /dev/null and b/Editor Resources/Icons/d_paint_paint.png differ diff --git a/Editor Resources/Icons/d_paint_paint.png.meta b/Editor Resources/Icons/d_paint_paint.png.meta new file mode 100644 index 00000000..0f25c246 --- /dev/null +++ b/Editor Resources/Icons/d_paint_paint.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 9ad9586c630b4206ba78bd61141b2104 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_paint@2x.png b/Editor Resources/Icons/d_paint_paint@2x.png new file mode 100644 index 00000000..402e22da Binary files /dev/null and b/Editor Resources/Icons/d_paint_paint@2x.png differ diff --git a/Editor Resources/Icons/d_paint_paint@2x.png.meta b/Editor Resources/Icons/d_paint_paint@2x.png.meta new file mode 100644 index 00000000..f9879b44 --- /dev/null +++ b/Editor Resources/Icons/d_paint_paint@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 40775031242c4d1284315d8595a17b10 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_smooth On.png b/Editor Resources/Icons/d_paint_smooth On.png new file mode 100644 index 00000000..d1f9b5f0 Binary files /dev/null and b/Editor Resources/Icons/d_paint_smooth On.png differ diff --git a/Editor Resources/Icons/d_paint_smooth On.png.meta b/Editor Resources/Icons/d_paint_smooth On.png.meta new file mode 100644 index 00000000..158a5044 --- /dev/null +++ b/Editor Resources/Icons/d_paint_smooth On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: cc981cf1040a4bd1992ad49900e61d6b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_smooth On@2x.png b/Editor Resources/Icons/d_paint_smooth On@2x.png new file mode 100644 index 00000000..42760bfc Binary files /dev/null and b/Editor Resources/Icons/d_paint_smooth On@2x.png differ diff --git a/Editor Resources/Icons/d_paint_smooth On@2x.png.meta b/Editor Resources/Icons/d_paint_smooth On@2x.png.meta new file mode 100644 index 00000000..dec457fb --- /dev/null +++ b/Editor Resources/Icons/d_paint_smooth On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: d76d31cea9c14bbd8ddf5481a5e4101d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_smooth.png b/Editor Resources/Icons/d_paint_smooth.png new file mode 100644 index 00000000..4725b3e8 Binary files /dev/null and b/Editor Resources/Icons/d_paint_smooth.png differ diff --git a/Editor Resources/Icons/d_paint_smooth.png.meta b/Editor Resources/Icons/d_paint_smooth.png.meta new file mode 100644 index 00000000..2bc33c03 --- /dev/null +++ b/Editor Resources/Icons/d_paint_smooth.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 03deb3be725549f4ac62e768f30fa2b2 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_smooth@2x.png b/Editor Resources/Icons/d_paint_smooth@2x.png new file mode 100644 index 00000000..bcb36b35 Binary files /dev/null and b/Editor Resources/Icons/d_paint_smooth@2x.png differ diff --git a/Editor Resources/Icons/d_paint_smooth@2x.png.meta b/Editor Resources/Icons/d_paint_smooth@2x.png.meta new file mode 100644 index 00000000..15a25544 --- /dev/null +++ b/Editor Resources/Icons/d_paint_smooth@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: a5cc05f531f74511a0c876c70057a4da +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_subtract On.png b/Editor Resources/Icons/d_paint_subtract On.png new file mode 100644 index 00000000..3d39c075 Binary files /dev/null and b/Editor Resources/Icons/d_paint_subtract On.png differ diff --git a/Editor Resources/Icons/d_paint_subtract On.png.meta b/Editor Resources/Icons/d_paint_subtract On.png.meta new file mode 100644 index 00000000..686d2cef --- /dev/null +++ b/Editor Resources/Icons/d_paint_subtract On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: fd378fe425624525b31016e599baa716 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_subtract On@2x.png b/Editor Resources/Icons/d_paint_subtract On@2x.png new file mode 100644 index 00000000..6bb4963d Binary files /dev/null and b/Editor Resources/Icons/d_paint_subtract On@2x.png differ diff --git a/Editor Resources/Icons/d_paint_subtract On@2x.png.meta b/Editor Resources/Icons/d_paint_subtract On@2x.png.meta new file mode 100644 index 00000000..7937664c --- /dev/null +++ b/Editor Resources/Icons/d_paint_subtract On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: c1430c9c5523401cb76a13d7ddd3bbbb +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_subtract.png b/Editor Resources/Icons/d_paint_subtract.png new file mode 100644 index 00000000..3bc8c8cf Binary files /dev/null and b/Editor Resources/Icons/d_paint_subtract.png differ diff --git a/Editor Resources/Icons/d_paint_subtract.png.meta b/Editor Resources/Icons/d_paint_subtract.png.meta new file mode 100644 index 00000000..dacad6f1 --- /dev/null +++ b/Editor Resources/Icons/d_paint_subtract.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 89de5aa944e445539e5ffb80239e7f98 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_subtract@2x.png b/Editor Resources/Icons/d_paint_subtract@2x.png new file mode 100644 index 00000000..0958181c Binary files /dev/null and b/Editor Resources/Icons/d_paint_subtract@2x.png differ diff --git a/Editor Resources/Icons/d_paint_subtract@2x.png.meta b/Editor Resources/Icons/d_paint_subtract@2x.png.meta new file mode 100644 index 00000000..928b18cb --- /dev/null +++ b/Editor Resources/Icons/d_paint_subtract@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 4e75c5c6b0e24f9e881ca1850598e9af +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_tool On.png b/Editor Resources/Icons/d_paint_tool On.png new file mode 100644 index 00000000..affb6cf7 Binary files /dev/null and b/Editor Resources/Icons/d_paint_tool On.png differ diff --git a/Editor Resources/Icons/d_paint_tool On.png.meta b/Editor Resources/Icons/d_paint_tool On.png.meta new file mode 100644 index 00000000..489ed423 --- /dev/null +++ b/Editor Resources/Icons/d_paint_tool On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: aff3c09b63ec41b5a6b5f4b5df21411c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/d_paint_tool.png b/Editor Resources/Icons/d_paint_tool.png new file mode 100644 index 00000000..5fcf7710 Binary files /dev/null and b/Editor Resources/Icons/d_paint_tool.png differ diff --git a/Editor Resources/Icons/d_paint_tool.png.meta b/Editor Resources/Icons/d_paint_tool.png.meta new file mode 100644 index 00000000..162030ff --- /dev/null +++ b/Editor Resources/Icons/d_paint_tool.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 80cfd2c77b80461eb8644a13b3a6888c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_add On.png b/Editor Resources/Icons/paint_add On.png new file mode 100644 index 00000000..89e4ca8b Binary files /dev/null and b/Editor Resources/Icons/paint_add On.png differ diff --git a/Editor Resources/Icons/paint_add On.png.meta b/Editor Resources/Icons/paint_add On.png.meta new file mode 100644 index 00000000..24eed2bd --- /dev/null +++ b/Editor Resources/Icons/paint_add On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 94520502ee1240849707f3f9082aea34 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_add On@2x.png b/Editor Resources/Icons/paint_add On@2x.png new file mode 100644 index 00000000..9daa98ca Binary files /dev/null and b/Editor Resources/Icons/paint_add On@2x.png differ diff --git a/Editor Resources/Icons/paint_add On@2x.png.meta b/Editor Resources/Icons/paint_add On@2x.png.meta new file mode 100644 index 00000000..e1b43fdc --- /dev/null +++ b/Editor Resources/Icons/paint_add On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 134fe52db2b14593b42d0d0b9748595f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_add.png b/Editor Resources/Icons/paint_add.png new file mode 100644 index 00000000..95899e79 Binary files /dev/null and b/Editor Resources/Icons/paint_add.png differ diff --git a/Editor Resources/Icons/paint_add.png.meta b/Editor Resources/Icons/paint_add.png.meta new file mode 100644 index 00000000..7db630aa --- /dev/null +++ b/Editor Resources/Icons/paint_add.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: a4deb5a7ab534b2480bff519dd5a7f4a +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_add@2x.png b/Editor Resources/Icons/paint_add@2x.png new file mode 100644 index 00000000..b6f2d6f2 Binary files /dev/null and b/Editor Resources/Icons/paint_add@2x.png differ diff --git a/Editor Resources/Icons/paint_add@2x.png.meta b/Editor Resources/Icons/paint_add@2x.png.meta new file mode 100644 index 00000000..3156a0ed --- /dev/null +++ b/Editor Resources/Icons/paint_add@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: f5a53f297efc4643a1c121f1d9b78c1b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_erase On.png b/Editor Resources/Icons/paint_erase On.png new file mode 100644 index 00000000..9b805386 Binary files /dev/null and b/Editor Resources/Icons/paint_erase On.png differ diff --git a/Editor Resources/Icons/paint_erase On.png.meta b/Editor Resources/Icons/paint_erase On.png.meta new file mode 100644 index 00000000..9dfd36bd --- /dev/null +++ b/Editor Resources/Icons/paint_erase On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 8d60dee2ab074adca6beb6b156b1a40d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_erase On@2x.png b/Editor Resources/Icons/paint_erase On@2x.png new file mode 100644 index 00000000..68031153 Binary files /dev/null and b/Editor Resources/Icons/paint_erase On@2x.png differ diff --git a/Editor Resources/Icons/paint_erase On@2x.png.meta b/Editor Resources/Icons/paint_erase On@2x.png.meta new file mode 100644 index 00000000..b4e966b0 --- /dev/null +++ b/Editor Resources/Icons/paint_erase On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 348b001c443443a58ff2403c71ff3c95 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_erase.png b/Editor Resources/Icons/paint_erase.png new file mode 100644 index 00000000..5e14bdcf Binary files /dev/null and b/Editor Resources/Icons/paint_erase.png differ diff --git a/Editor Resources/Icons/paint_erase.png.meta b/Editor Resources/Icons/paint_erase.png.meta new file mode 100644 index 00000000..c78c96fb --- /dev/null +++ b/Editor Resources/Icons/paint_erase.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: f2fcb74177804ca798dc5eb421be6731 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_erase@2x.png b/Editor Resources/Icons/paint_erase@2x.png new file mode 100644 index 00000000..b428ac9b Binary files /dev/null and b/Editor Resources/Icons/paint_erase@2x.png differ diff --git a/Editor Resources/Icons/paint_erase@2x.png.meta b/Editor Resources/Icons/paint_erase@2x.png.meta new file mode 100644 index 00000000..16430c3b --- /dev/null +++ b/Editor Resources/Icons/paint_erase@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 6733b5cf20ce4213968669ba7a32c59f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_fill On.png b/Editor Resources/Icons/paint_fill On.png new file mode 100644 index 00000000..b177fc08 Binary files /dev/null and b/Editor Resources/Icons/paint_fill On.png differ diff --git a/Editor Resources/Icons/paint_fill On.png.meta b/Editor Resources/Icons/paint_fill On.png.meta new file mode 100644 index 00000000..481cb250 --- /dev/null +++ b/Editor Resources/Icons/paint_fill On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: f053877cf4014ab0a1b73a5a2b0694d4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_fill On@2x.png b/Editor Resources/Icons/paint_fill On@2x.png new file mode 100644 index 00000000..46debd15 Binary files /dev/null and b/Editor Resources/Icons/paint_fill On@2x.png differ diff --git a/Editor Resources/Icons/paint_fill On@2x.png.meta b/Editor Resources/Icons/paint_fill On@2x.png.meta new file mode 100644 index 00000000..d4ce17a3 --- /dev/null +++ b/Editor Resources/Icons/paint_fill On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 910a545eeeea4a42bb0285bbe22285e4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_fill.png b/Editor Resources/Icons/paint_fill.png new file mode 100644 index 00000000..e6a65ba9 Binary files /dev/null and b/Editor Resources/Icons/paint_fill.png differ diff --git a/Editor Resources/Icons/paint_fill.png.meta b/Editor Resources/Icons/paint_fill.png.meta new file mode 100644 index 00000000..7501a35b --- /dev/null +++ b/Editor Resources/Icons/paint_fill.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 955870797acd48309a7cb7c5f28954f5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_fill@2x.png b/Editor Resources/Icons/paint_fill@2x.png new file mode 100644 index 00000000..c9aad24c Binary files /dev/null and b/Editor Resources/Icons/paint_fill@2x.png differ diff --git a/Editor Resources/Icons/paint_fill@2x.png.meta b/Editor Resources/Icons/paint_fill@2x.png.meta new file mode 100644 index 00000000..237c03a4 --- /dev/null +++ b/Editor Resources/Icons/paint_fill@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: bd4bebc75b9741f9b4ca13235c9fcb0e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_paint On.png b/Editor Resources/Icons/paint_paint On.png new file mode 100644 index 00000000..fde38083 Binary files /dev/null and b/Editor Resources/Icons/paint_paint On.png differ diff --git a/Editor Resources/Icons/paint_paint On.png.meta b/Editor Resources/Icons/paint_paint On.png.meta new file mode 100644 index 00000000..807e7b31 --- /dev/null +++ b/Editor Resources/Icons/paint_paint On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 0b51fa44c4284e59ad42c4bedf4fbca0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_paint On@2x.png b/Editor Resources/Icons/paint_paint On@2x.png new file mode 100644 index 00000000..a9d9ed91 Binary files /dev/null and b/Editor Resources/Icons/paint_paint On@2x.png differ diff --git a/Editor Resources/Icons/paint_paint On@2x.png.meta b/Editor Resources/Icons/paint_paint On@2x.png.meta new file mode 100644 index 00000000..d86e6a17 --- /dev/null +++ b/Editor Resources/Icons/paint_paint On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 23287753167d458794d561eb7f1f00de +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_paint.png b/Editor Resources/Icons/paint_paint.png new file mode 100644 index 00000000..bedc2c9a Binary files /dev/null and b/Editor Resources/Icons/paint_paint.png differ diff --git a/Editor Resources/Icons/paint_paint.png.meta b/Editor Resources/Icons/paint_paint.png.meta new file mode 100644 index 00000000..f1037708 --- /dev/null +++ b/Editor Resources/Icons/paint_paint.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 7dabb9cabd4f4ec28af487c2524a5cc5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_paint@2x.png b/Editor Resources/Icons/paint_paint@2x.png new file mode 100644 index 00000000..a264c066 Binary files /dev/null and b/Editor Resources/Icons/paint_paint@2x.png differ diff --git a/Editor Resources/Icons/paint_paint@2x.png.meta b/Editor Resources/Icons/paint_paint@2x.png.meta new file mode 100644 index 00000000..b20fab3b --- /dev/null +++ b/Editor Resources/Icons/paint_paint@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 11176c3f4fee4800bbc95fc646106a67 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_smooth On.png b/Editor Resources/Icons/paint_smooth On.png new file mode 100644 index 00000000..d1f9b5f0 Binary files /dev/null and b/Editor Resources/Icons/paint_smooth On.png differ diff --git a/Editor Resources/Icons/paint_smooth On.png.meta b/Editor Resources/Icons/paint_smooth On.png.meta new file mode 100644 index 00000000..aac49572 --- /dev/null +++ b/Editor Resources/Icons/paint_smooth On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: eac0358db660480c9c4a1189f3e4b3a7 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_smooth On@2x.png b/Editor Resources/Icons/paint_smooth On@2x.png new file mode 100644 index 00000000..42760bfc Binary files /dev/null and b/Editor Resources/Icons/paint_smooth On@2x.png differ diff --git a/Editor Resources/Icons/paint_smooth On@2x.png.meta b/Editor Resources/Icons/paint_smooth On@2x.png.meta new file mode 100644 index 00000000..50579055 --- /dev/null +++ b/Editor Resources/Icons/paint_smooth On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: c48acb41ff5847f29236bf04a6af63e8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_smooth.png b/Editor Resources/Icons/paint_smooth.png new file mode 100644 index 00000000..23220337 Binary files /dev/null and b/Editor Resources/Icons/paint_smooth.png differ diff --git a/Editor Resources/Icons/paint_smooth.png.meta b/Editor Resources/Icons/paint_smooth.png.meta new file mode 100644 index 00000000..4c89fe6a --- /dev/null +++ b/Editor Resources/Icons/paint_smooth.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 4f45eb8abc7a4fac9af8d5fd95ef0010 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_smooth@2x.png b/Editor Resources/Icons/paint_smooth@2x.png new file mode 100644 index 00000000..37077b1e Binary files /dev/null and b/Editor Resources/Icons/paint_smooth@2x.png differ diff --git a/Editor Resources/Icons/paint_smooth@2x.png.meta b/Editor Resources/Icons/paint_smooth@2x.png.meta new file mode 100644 index 00000000..5c10142f --- /dev/null +++ b/Editor Resources/Icons/paint_smooth@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 448080f7227a4fe59d43047b8911e05b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_subtract On.png b/Editor Resources/Icons/paint_subtract On.png new file mode 100644 index 00000000..3d39c075 Binary files /dev/null and b/Editor Resources/Icons/paint_subtract On.png differ diff --git a/Editor Resources/Icons/paint_subtract On.png.meta b/Editor Resources/Icons/paint_subtract On.png.meta new file mode 100644 index 00000000..3e5f32b2 --- /dev/null +++ b/Editor Resources/Icons/paint_subtract On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 2bfe2d983bed47968321a163048c38d9 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_subtract On@2x.png b/Editor Resources/Icons/paint_subtract On@2x.png new file mode 100644 index 00000000..6bb4963d Binary files /dev/null and b/Editor Resources/Icons/paint_subtract On@2x.png differ diff --git a/Editor Resources/Icons/paint_subtract On@2x.png.meta b/Editor Resources/Icons/paint_subtract On@2x.png.meta new file mode 100644 index 00000000..025bb7c0 --- /dev/null +++ b/Editor Resources/Icons/paint_subtract On@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 68442b83d534442a9b787c00956b52a9 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_subtract.png b/Editor Resources/Icons/paint_subtract.png new file mode 100644 index 00000000..81c0225e Binary files /dev/null and b/Editor Resources/Icons/paint_subtract.png differ diff --git a/Editor Resources/Icons/paint_subtract.png.meta b/Editor Resources/Icons/paint_subtract.png.meta new file mode 100644 index 00000000..c0b733a7 --- /dev/null +++ b/Editor Resources/Icons/paint_subtract.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 2a0a438dd7f54880982d03057866af29 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 2 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_subtract@2x.png b/Editor Resources/Icons/paint_subtract@2x.png new file mode 100644 index 00000000..1841a977 Binary files /dev/null and b/Editor Resources/Icons/paint_subtract@2x.png differ diff --git a/Editor Resources/Icons/paint_subtract@2x.png.meta b/Editor Resources/Icons/paint_subtract@2x.png.meta new file mode 100644 index 00000000..a9d99d1e --- /dev/null +++ b/Editor Resources/Icons/paint_subtract@2x.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 8767abde21b84f5db46c250af673a67f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_tool On.png b/Editor Resources/Icons/paint_tool On.png new file mode 100644 index 00000000..253ece0a Binary files /dev/null and b/Editor Resources/Icons/paint_tool On.png differ diff --git a/Editor Resources/Icons/paint_tool On.png.meta b/Editor Resources/Icons/paint_tool On.png.meta new file mode 100644 index 00000000..e396746f --- /dev/null +++ b/Editor Resources/Icons/paint_tool On.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 2d0065cdb5554763b3d8f951cbb6ecb1 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor Resources/Icons/paint_tool.png b/Editor Resources/Icons/paint_tool.png new file mode 100644 index 00000000..d63b4c86 Binary files /dev/null and b/Editor Resources/Icons/paint_tool.png differ diff --git a/Editor Resources/Icons/paint_tool.png.meta b/Editor Resources/Icons/paint_tool.png.meta new file mode 100644 index 00000000..75f746c6 --- /dev/null +++ b/Editor Resources/Icons/paint_tool.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: fe450bfcb18643728ca3584a60732c57 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ComponentEditors/Decals/ChiselDecalEditor.cs b/Editor/ComponentEditors/Decals/ChiselDecalEditor.cs index 3a915543..01b11afc 100644 --- a/Editor/ComponentEditors/Decals/ChiselDecalEditor.cs +++ b/Editor/ComponentEditors/Decals/ChiselDecalEditor.cs @@ -313,7 +313,7 @@ static void PlaceWhereMaterialsMeet(ChiselDecalComponent template) return; var existing = new HashSet<(ChiselNodeComponent, int, ChiselNodeComponent, int)>(); - foreach (var decal in Object.FindObjectsByType(FindObjectsSortMode.None)) + foreach (var decal in Object.FindObjectsByType(FindObjectsInactive.Exclude)) { if (decal.Shape == ChiselDecalShape.Trim && decal.Targets.Count >= 2) existing.Add((decal.Targets[0].brush, decal.Targets[0].surfaceIndex, decal.Targets[1].brush, decal.Targets[1].surfaceIndex)); diff --git a/Editor/EditorTools/ChiselPaintTool.cs b/Editor/EditorTools/ChiselPaintTool.cs new file mode 100644 index 00000000..0d99f02f --- /dev/null +++ b/Editor/EditorTools/ChiselPaintTool.cs @@ -0,0 +1,698 @@ +using System.Collections.Generic; +using Chisel.Components; +using Chisel.Core; +using UnityEditor; +using UnityEditor.EditorTools; +using UnityEngine; + +namespace Chisel.Editors +{ + /// + /// Paints the surface under the mouse in a mode of the paint profile of its material's shader: a color, or a named + /// value, with a round brush. Only the surface that is hit is painted; a stroke is one undo. + /// + sealed class ChiselPaintTool : EditorTool + { + const string kModeTooltip = "A surface takes this mode when the paint profile of its shader has a mode of this name"; + static readonly GUIContent kOperationContent = new("Operations", "Paint: towards the color or value. Add / Subtract: a value up or down. Smooth: towards the neighbouring paint. Erase: back to the default."); + static readonly GUIContent kColorContent = new("Color"); + static readonly GUIContent kRadiusContent = new("Radius", "The brush's radius, in metres."); + static readonly GUIContent kStrengthContent = new("Strength", "How much a dab moves the paint towards what it paints."); + static readonly GUIContent kHardnessContent = new("Hardness", "How much of the radius is painted at full strength; beyond it the brush falls off smoothly."); + + // the mode painted in, by name: a surface takes it when its shader's profile has a mode of that name + static string s_ModeName; + static ChiselPaintOperation s_Operation = ChiselPaintOperation.Paint; + static Color s_Color = Color.red; + static float s_Value = 1; + static float s_Radius = 0.5f; + static float s_Strength = 0.5f; + static float s_Hardness = 0.5f; + + // the surface last under the mouse, whose mode's color or range the panel shows + static Hit s_Last; + + static readonly List s_Intersections = new(); + // the surface under the mouse, as it was last picked + static Hit s_Hover; + // where the mouse was in the scene when it was picked (an overlay takes the mouse events over it) + static Vector2 s_HoverMouse; + static int s_UndoGroup = -1; + + static GUIContent s_ToolbarIcon; + public override GUIContent toolbarIcon => s_ToolbarIcon ??= new GUIContent(ChiselEditorResources.LoadIconImages("paint_tool")?[0], + "Chisel Paint Tool: paint vertex data onto surfaces, in the modes of their shaders' paint profiles"); + + [MenuItem("Tools/Chisel/Paint")] + static void Activate() => ToolManager.SetActiveTool(); + + struct Hit + { + public bool valid; + public ChiselGeneratorComponent generator; + public int surfaceIndex; + public Vector3 point; + public Vector3 normal; + public ChiselPaintProfile profile; + public Shader shader; + } + + static Hit Pick(Vector2 mousePosition) + { + ChiselSelectionManager.GetOverlappingObjects(mousePosition, s_Intersections); + foreach (var intersection in s_Intersections) + { + if (intersection.treeNode is not ChiselGeneratorComponent generator || intersection.model == null || + !intersection.brushIntersection.brush.Valid) + continue; + var surfaceIndex = ChiselTrimDecals.SurfaceOf(intersection.brushIntersection.brush, intersection.brushIntersection.surfaceIndex); + var surfaces = generator.SurfaceDefinition?.surfaces; + if (surfaceIndex < 0 || surfaces == null || surfaceIndex >= surfaces.Length) + continue; + var shader = surfaces[surfaceIndex].RenderMaterial ? surfaces[surfaceIndex].RenderMaterial.shader : null; + return new Hit + { + valid = true, + generator = generator, + surfaceIndex = surfaceIndex, + point = intersection.worldPlaneIntersection, + normal = intersection.worldPlane.normal, + shader = shader, + profile = ChiselPaintProfiles.Find(shader) + }; + } + return default; + } + + static string NameOf(ChiselPaintProfile profile, int index) + { + var name = profile.modes[index].name; + return string.IsNullOrEmpty(name) ? $"Mode {index}" : name; + } + + // The profile's mode of the chosen name, -1 when it has none + static int ModeIndex(ChiselPaintProfile profile) + { + if (profile == null) + return -1; + for (int i = 0; i < profile.modes.Count; i++) + { + if (NameOf(profile, i) == s_ModeName) + return i; + } + return -1; + } + + static bool Paintable(Hit hit) => hit.valid && ModeIndex(hit.profile) >= 0; + + public override void OnToolGUI(EditorWindow window) + { + if (window is not SceneView) + return; + var evt = Event.current; + var id = GUIUtility.GetControlID(FocusType.Passive); + // picking renders: only on the mouse's own events, never inside the scene view's layout or repaint + var type = evt.GetTypeForControl(id); + if (type == EventType.MouseLeaveWindow) + { + s_Hover = default; + HandleUtility.Repaint(); + } + if (type == EventType.MouseMove || type == EventType.MouseDown || type == EventType.MouseDrag) + { + s_Hover = Pick(evt.mousePosition); + s_HoverMouse = evt.mousePosition; + if (s_Hover.valid && s_Hover.profile != null) + { + s_Last = s_Hover; + if (string.IsNullOrEmpty(s_ModeName) && s_Hover.profile.modes.Count > 0) + s_ModeName = NameOf(s_Hover.profile, 0); + } + } + var hit = s_Hover; + if (Paintable(hit)) + { + if (s_Operation != ChiselPaintOperation.Fill) + { + Handles.color = new Color(1, 1, 1, 0.8f); + Handles.DrawWireDisc(hit.point, hit.normal, s_Radius); + Handles.DrawWireDisc(hit.point, hit.normal, s_Radius * Mathf.Clamp01(s_Hardness)); + } + } else if (hit.valid) + NotPaintable(window, hit, s_HoverMouse); + + switch (evt.GetTypeForControl(id)) + { + case EventType.Layout: + HandleUtility.AddDefaultControl(id); + break; + case EventType.MouseMove: + HandleUtility.Repaint(); + break; + case EventType.MouseDown: + if (evt.button != 0 || evt.alt || !Paintable(hit)) + break; + // control-click takes the paint under the mouse as the brush's color or value + if (evt.control) + { + Sample(hit); + evt.Use(); + break; + } + GUIUtility.hotControl = id; + Undo.IncrementCurrentGroup(); + Undo.SetCurrentGroupName("Chisel Paint"); + s_UndoGroup = Undo.GetCurrentGroup(); + s_Filled.Clear(); + Dab(hit); + evt.Use(); + break; + case EventType.MouseDrag: + if (GUIUtility.hotControl != id) + break; + if (Paintable(hit)) + Dab(hit); + evt.Use(); + break; + case EventType.MouseUp: + if (GUIUtility.hotControl != id) + break; + GUIUtility.hotControl = 0; + if (s_UndoGroup >= 0) + Undo.CollapseUndoOperations(s_UndoGroup); + s_UndoGroup = -1; + evt.Use(); + break; + } + } + + static void Sample(Hit hit) + { + var index = ModeIndex(hit.profile); + if (index < 0) + return; + var mode = hit.profile.modes[index]; + var paint = hit.generator.GetSurfacePaint(hit.surfaceIndex, mode.attribute); + var value = paint != null ? paint.Sample(hit.generator.transform.InverseTransformPoint(hit.point)) : mode.defaultValue; + if (mode.kind == ChiselPaintKind.Color) + s_Color = value; + else + { + // the mode's first component + for (int c = 0; c < 4; c++) + { + if (((int)mode.components & (1 << c)) != 0) + { + s_Value = value[c]; + break; + } + } + } + } + + static readonly HashSet<(ChiselGeneratorComponent, int)> s_Filled = new(); + + static void Dab(Hit hit) + { + var index = ModeIndex(hit.profile); + if (index < 0) + return; + var mode = hit.profile.modes[index]; + if (s_Operation == ChiselPaintOperation.Fill) + { + // a stroke fills each surface it goes over once + if (!s_Filled.Add((hit.generator, hit.surfaceIndex))) + return; + } + Undo.RecordObject(hit.generator, "Chisel Paint"); + var paint = ChiselPaintBrush.PaintFor(hit.generator, hit.surfaceIndex, mode); + if (paint == null) + return; + var target = mode.kind == ChiselPaintKind.Color ? (Vector4)s_Color : Vector4.one * s_Value; + var scale = hit.generator.transform.lossyScale; + var radius = s_Radius / Mathf.Max(Mathf.Abs(scale.x), Mathf.Max(Mathf.Abs(scale.y), Mathf.Abs(scale.z)), 1e-5f); + var center = hit.generator.transform.InverseTransformPoint(hit.point); + if (ChiselPaintBrush.Dab(paint, mode, s_Operation, target, center, radius, s_Strength, s_Hardness, hit.profile.GroupOf(index))) + { + hit.generator.PaintChanged(); + EditorUtility.SetDirty(hit.generator); + } + } + + static GUIStyle s_TooltipStyle; + + // Over a surface that can't be painted: no brush, a cursor that says so, and why next to it + static void NotPaintable(EditorWindow window, Hit hit, Vector2 mousePosition) + { + // only round where the scene last had the mouse: over an overlay (which takes the mouse events) it isn't + EditorGUIUtility.AddCursorRect(new Rect(mousePosition.x - 4, mousePosition.y - 4, 8, 8), MouseCursor.NotAllowed); + if (Event.current.type != EventType.Repaint) + return; + // solid, as a tooltip is: a panel's color with a border round it + s_TooltipStyle ??= new GUIStyle(EditorStyles.label) { padding = new RectOffset(6, 6, 3, 3), wordWrap = false }; + var content = new GUIContent(!hit.shader ? "The surface\nhas no shader\nto paint for" + : hit.profile == null ? $"The shader {hit.shader.name}\nis not set up to be paintable\nit has no paint profile" + : $"The shader {hit.shader.name}\nhas no paint mode \"{s_ModeName}\"\nin its paint profile"); + var size = s_TooltipStyle.CalcSize(content); + var rect = new Rect(mousePosition + new Vector2(16, 20), size); + Handles.BeginGUI(); + EditorGUI.DrawRect(new Rect(rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 2), EditorGUIUtility.isProSkin ? new Color(0.1f, 0.1f, 0.1f, 1) : new Color(0.5f, 0.5f, 0.5f, 1)); + EditorGUI.DrawRect(rect, EditorGUIUtility.isProSkin ? new Color(0.24f, 0.24f, 0.24f, 1) : new Color(0.94f, 0.94f, 0.94f, 1)); + s_TooltipStyle.Draw(rect, content, false, false, false, false); + Handles.EndGUI(); + } + + // The overlay's width, and what its rows have of it + internal const float kPanelWidth = 260; + const float kRowWidth = kPanelWidth - 6; + + static readonly List s_ModeNames = new(); + + // The mode of the chosen name: the last painted surface's, else the first profile's that has one + static bool ChosenMode(out ChiselPaintMode mode) + { + var index = ModeIndex(s_Last.profile); + if (index >= 0) + { + mode = s_Last.profile.modes[index]; + return true; + } + foreach (var profile in ChiselPaintProfiles.All) + { + index = ModeIndex(profile); + if (index >= 0) + { + mode = profile.modes[index]; + return true; + } + } + mode = default; + return false; + } + + // Every mode name of the project's profiles, as toggle buttons wrapping within the panel + static void ModeButtons() + { + s_ModeNames.Clear(); + foreach (var profile in ChiselPaintProfiles.All) + { + for (int i = 0; i < profile.modes.Count; i++) + { + var name = NameOf(profile, i); + if (!s_ModeNames.Contains(name)) + s_ModeNames.Add(name); + } + } + if (s_ModeNames.Count == 0) + { + EditorGUILayout.HelpBox("The project has no paint profiles.", MessageType.None); + return; + } + if (!s_ModeNames.Contains(s_ModeName)) + s_ModeName = s_ModeNames[0]; + + if (s_ModeContents.Length != s_ModeNames.Count) + s_ModeContents = new GUIContent[s_ModeNames.Count]; + var chosen = -1; + for (int n = 0; n < s_ModeNames.Count; n++) + { + s_ModeContents[n] ??= new GUIContent(); + s_ModeContents[n].text = s_ModeNames[n]; + s_ModeContents[n].tooltip = kModeTooltip; + if (s_ModeNames[n] == s_ModeName) + chosen = n; + } + s_ModeName = s_ModeNames[Toggles(chosen, s_ModeContents, null)]; + } + + static GUIContent[] s_ModeContents = System.Array.Empty(); + + // Toggle buttons wrapping within the panel, one of them on; disabled (when given) are drawn but can't be chosen + static int Toggles(int chosen, GUIContent[] contents, bool[] disabled) + { + var style = EditorStyles.miniButton; + for (int n = 0; n < contents.Length;) + { + using (new EditorGUILayout.HorizontalScope()) + { + var used = 0f; + do + { + var width = Mathf.Min(kRowWidth, style.CalcSize(contents[n]).x + 8); + if (used > 0 && used + width > kRowWidth) + break; + using (new EditorGUI.DisabledScope(disabled != null && disabled[n])) + { + if (GUILayout.Toggle(n == chosen, contents[n], style, GUILayout.Width(width), GUILayout.Height(20))) + chosen = n; + } + used += width + style.margin.horizontal; + n++; + } while (n < contents.Length); + GUILayout.FlexibleSpace(); + } + } + return Mathf.Max(chosen, 0); + } + + static readonly (ChiselPaintOperation operation, string icon, string name, string tooltip)[] kOperations = + { + (ChiselPaintOperation.Paint, "paint_paint", "Paint", "Towards the color or value"), + (ChiselPaintOperation.Fill, "paint_fill", "Fill", "The whole surface clicked on, to the color or value"), + (ChiselPaintOperation.Add, "paint_add", "Add", "Raises the value"), + (ChiselPaintOperation.Subtract, "paint_subtract", "Subtract", "Lowers the value"), + (ChiselPaintOperation.Smooth, "paint_smooth", "Smooth", "Towards the neighbouring paint"), + (ChiselPaintOperation.Erase, "paint_erase", "Erase", "Back to the default"), + }; + static readonly GUIContent[] s_OperationContents = new GUIContent[kOperations.Length]; + static readonly bool[] s_OperationDisabled = new bool[kOperations.Length]; + + // The operations, with their icons; adding and subtracting only for a value + static void OperationButtons(bool isValue) + { + var chosen = 0; + for (int i = 0; i < kOperations.Length; i++) + { + var icons = ChiselEditorResources.GetIconContent(kOperations[i].icon, kOperations[i].tooltip); + var on = kOperations[i].operation == s_Operation; + s_OperationContents[i] ??= new GUIContent(); + s_OperationContents[i].text = null; + s_OperationContents[i].image = icons[on ? 1 : 0].image; + s_OperationContents[i].tooltip = $"{kOperations[i].name}: {kOperations[i].tooltip}"; + s_OperationDisabled[i] = !isValue && (kOperations[i].operation == ChiselPaintOperation.Add || kOperations[i].operation == ChiselPaintOperation.Subtract); + if (on) + chosen = i; + } + if (s_OperationDisabled[chosen]) + s_Operation = ChiselPaintOperation.Paint; + EditorGUILayout.LabelField(kOperationContent); + s_Operation = kOperations[Toggles(chosen, s_OperationContents, s_OperationDisabled)].operation; + } + + const int kFalloffSize = 40; + static Texture2D s_Falloff; + static float s_FalloffStrength = -1, s_FalloffHardness = -1; + + // A blob as a dab paints it: how much the brush counts across its radius + static Texture2D Falloff() + { + if (s_Falloff && s_FalloffStrength == s_Strength && s_FalloffHardness == s_Hardness) + return s_Falloff; + if (!s_Falloff) + s_Falloff = new Texture2D(kFalloffSize, kFalloffSize, TextureFormat.RGBA32, false) { hideFlags = HideFlags.HideAndDontSave, filterMode = FilterMode.Bilinear }; + s_FalloffStrength = s_Strength; + s_FalloffHardness = s_Hardness; + var pixels = new Color32[kFalloffSize * kFalloffSize]; + var radius = kFalloffSize * 0.5f; + for (int y = 0; y < kFalloffSize; y++) + { + for (int x = 0; x < kFalloffSize; x++) + { + var distance = Vector2.Distance(new Vector2(x + 0.5f, y + 0.5f), new Vector2(radius, radius)); + var weight = ChiselPaintBrush.Weight(distance, radius, s_Strength, s_Hardness); + var shade = (byte)Mathf.RoundToInt(Mathf.Lerp(32, 255, weight)); + pixels[y * kFalloffSize + x] = new Color32(shade, shade, shade, 255); + } + } + s_Falloff.SetPixels32(pixels); + s_Falloff.Apply(); + return s_Falloff; + } + + static readonly Color[] kDefaultSwatches = + { + Color.white, new(0.75f, 0.75f, 0.75f), new(0.5f, 0.5f, 0.5f), new(0.25f, 0.25f, 0.25f), Color.black, + new(0.9f, 0.1f, 0.1f), new(1, 0.5f, 0), new(1, 0.9f, 0.1f), new(0.2f, 0.8f, 0.2f), new(0.1f, 0.8f, 0.8f), + new(0.15f, 0.35f, 0.95f), new(0.55f, 0.25f, 0.85f), new(0.95f, 0.3f, 0.7f), new(0.5f, 0.3f, 0.15f), + }; + const string kSwatchesPref = "Chisel.Paint.Swatches"; + const float kSwatchSize = 14; + // neighbours share their border + const float kSwatchGap = -1; + static List s_Swatches; + static readonly GUIContent kAddSwatchContent = new(string.Empty, "Adds the current color as a swatch, and picks its color (middle-click or right-click a swatch to remove it)"); + + // the swatches, kept per user + static List Swatches + { + get + { + if (s_Swatches != null) + return s_Swatches; + s_Swatches = new List(); + foreach (var hex in EditorPrefs.GetString(kSwatchesPref, string.Empty).Split(';')) + { + if (UnityEngine.ColorUtility.TryParseHtmlString("#" + hex, out var color) && !s_Swatches.Contains(color)) + s_Swatches.Add(color); + } + if (s_Swatches.Count == 0 && !EditorPrefs.HasKey(kSwatchesPref)) + s_Swatches.AddRange(kDefaultSwatches); + return s_Swatches; + } + } + + static void SaveSwatches() + { + var hexes = new string[Swatches.Count]; + for (int i = 0; i < hexes.Length; i++) + hexes[i] = UnityEngine.ColorUtility.ToHtmlStringRGBA(Swatches[i]); + EditorPrefs.SetString(kSwatchesPref, string.Join(";", hexes)); + } + + static int ChosenSwatch() => SwatchOf(s_Color); + + // The swatch of a color, as the swatches are kept (8 bits a component); -1 when it has none + static int SwatchOf(Color color) + { + Color32 wanted = color; + for (int i = 0; i < Swatches.Count; i++) + { + Color32 swatch = Swatches[i]; + if (swatch.r == wanted.r && swatch.g == wanted.g && swatch.b == wanted.b && swatch.a == wanted.a) + return i; + } + return -1; + } + + // Unity's color picker, giving every change to the callback + static void PickColor(Color color, System.Action changed) + { + var picker = typeof(EditorWindow).Assembly.GetType("UnityEditor.ColorPicker"); + foreach (var method in picker?.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) ?? System.Array.Empty()) + { + var parameters = method.GetParameters(); + if (method.Name != "Show" || parameters.Length != 4 || parameters[0].ParameterType != typeof(System.Action)) + continue; + method.Invoke(null, new object[] { changed, color, true, false }); + return; + } + } + + // The swatches, wrapping over as many rows as they need, then the current color (adds it as a swatch, picked); + // a drag reorders them, a middle-click removes one + static int s_DraggedSwatch = -1; + + static void RemoveSwatch(int index) + { + if (index < 0 || index >= Swatches.Count) + return; + Swatches.RemoveAt(index); + SaveSwatches(); + SceneView.RepaintAll(); + } + + // The current color as a new swatch, then Unity's color picker on it; a color some other swatch has already + // isn't added twice, that swatch is chosen instead + static void AddSwatch() + { + var index = Swatches.Count; + Swatches.Add(s_Color); + SaveSwatches(); + PickColor(s_Color, color => + { + s_Color = color; + var existing = SwatchOf(color); + if (existing >= 0 && existing != index) + { + if (index >= 0 && index < Swatches.Count) + Swatches.RemoveAt(index); + index = -1; + } else if (index < 0) + { + index = Swatches.Count; + Swatches.Add(color); + } else if (index < Swatches.Count) + Swatches[index] = color; + SaveSwatches(); + SceneView.RepaintAll(); + }); + } + + static void SwatchesGUI() + { + var swatches = Swatches; + var chosen = ChosenSwatch(); + var count = swatches.Count + 1; + // starting where the labels' text does + var indent = EditorStyles.label.margin.left + EditorStyles.label.padding.left; + var perRow = Mathf.Max(1, Mathf.FloorToInt((kRowWidth - indent + kSwatchGap) / (kSwatchSize + kSwatchGap))); + var rows = (count + perRow - 1) / perRow; + var area = GUILayoutUtility.GetRect(kRowWidth, rows * (kSwatchSize + kSwatchGap) - kSwatchGap + 2); + area.xMin += indent; + var evt = Event.current; + var id = GUIUtility.GetControlID(FocusType.Passive); + var border = EditorGUIUtility.isProSkin ? new Color(0.36f, 0.36f, 0.36f) : new Color(0.58f, 0.58f, 0.58f); + var highlight = new Color(0.25f, 0.6f, 1f); + Rect CellOf(int i) => new(area.x + (i % perRow) * (kSwatchSize + kSwatchGap), area.y + (i / perRow) * (kSwatchSize + kSwatchGap) + 1, kSwatchSize, kSwatchSize); + + // dragging a swatch moves it to the cell under the mouse + if (GUIUtility.hotControl == id) + { + if (evt.type == EventType.MouseDrag && s_DraggedSwatch >= 0 && s_DraggedSwatch < swatches.Count) + { + for (int i = 0; i < swatches.Count; i++) + { + var cell = CellOf(i); + cell.xMax += kSwatchGap; + cell.yMax += kSwatchGap; + if (i == s_DraggedSwatch || !cell.Contains(evt.mousePosition)) + continue; + var moved = swatches[s_DraggedSwatch]; + swatches.RemoveAt(s_DraggedSwatch); + swatches.Insert(i, moved); + s_DraggedSwatch = i; + SaveSwatches(); + break; + } + evt.Use(); + } else if (evt.type == EventType.MouseUp) + { + GUIUtility.hotControl = 0; + s_DraggedSwatch = -1; + evt.Use(); + } + } + + for (int i = 0; i < count; i++) + { + var cell = CellOf(i); + var inner = new Rect(cell.x + 1, cell.y + 1, cell.width - 2, cell.height - 2); + if (i == swatches.Count) + { + // as the color picker's own: the current color, smaller than a swatch, in an empty cell + if (evt.type == EventType.Repaint) + { + EditorGUI.DrawRect(cell, border); + EditorGUI.DrawRect(inner, EditorGUIUtility.isProSkin ? new Color(0.22f, 0.22f, 0.22f) : new Color(0.76f, 0.76f, 0.76f)); + EditorGUI.DrawRect(new Rect(cell.x + 3, cell.y + 3, cell.width - 6, cell.height - 6), s_Color); + } + GUI.Label(cell, kAddSwatchContent, GUIStyle.none); + if (evt.type == EventType.MouseDown && evt.button == 0 && cell.Contains(evt.mousePosition)) + { + evt.Use(); + // a color that has a swatch already is chosen there + if (chosen < 0) + AddSwatch(); + } + continue; + } + if (evt.type == EventType.Repaint) + { + if (i == chosen) + { + // inside the cell: there is no room between the swatches + EditorGUI.DrawRect(cell, highlight); + EditorGUI.DrawRect(new Rect(cell.x + 2, cell.y + 2, cell.width - 4, cell.height - 4), swatches[i]); + } else + { + EditorGUI.DrawRect(cell, border); + EditorGUI.DrawRect(inner, swatches[i]); + } + } + EditorGUIUtility.AddCursorRect(cell, GUIUtility.hotControl == id ? MouseCursor.Pan : MouseCursor.Link); + if (evt.type == EventType.MouseDown && cell.Contains(evt.mousePosition)) + { + if (evt.button == 0) + { + // a click takes the color; a drag moves the swatch + s_Color = swatches[i]; + s_DraggedSwatch = i; + GUIUtility.hotControl = id; + GUI.changed = true; + evt.Use(); + } else if (evt.button == 2) + { + RemoveSwatch(i); + evt.Use(); + GUIUtility.ExitGUI(); + } else if (evt.button == 1) + { + // for those without a middle mouse button + var index = i; + var menu = new GenericMenu(); + menu.AddItem(new GUIContent("Delete"), false, () => RemoveSwatch(index)); + menu.ShowAsContext(); + evt.Use(); + } + } + } + } + // The tool's settings, in its overlay + internal static void PanelGUI() + { + var labelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 90; + { + ModeButtons(); + var hasMode = ChosenMode(out var chosen); + OperationButtons(hasMode && chosen.kind == ChiselPaintKind.Value); + EditorGUILayout.Space(2); + if (hasMode) + { + if (chosen.kind == ChiselPaintKind.Color) + { + s_Color = EditorGUILayout.ColorField(kColorContent, s_Color, true, true, false); + SwatchesGUI(); + } + else + s_Value = EditorGUILayout.Slider(new GUIContent(s_ModeName), s_Value, chosen.range.x, chosen.range.y); + } + // a fill takes the whole surface: the brush doesn't count + using (new EditorGUI.DisabledScope(s_Operation == ChiselPaintOperation.Fill)) + using (new EditorGUILayout.VerticalScope()) + { + s_Radius = Mathf.Max(0.01f, EditorGUILayout.FloatField(kRadiusContent, s_Radius)); + using (new EditorGUILayout.HorizontalScope()) + { + using (new EditorGUILayout.VerticalScope()) + { + s_Strength = EditorGUILayout.Slider(kStrengthContent, s_Strength, 0, 1); + s_Hardness = EditorGUILayout.Slider(kHardnessContent, s_Hardness, 0, 1); + } + var falloff = GUILayoutUtility.GetRect(kFalloffSize, kFalloffSize, GUILayout.Width(kFalloffSize), GUILayout.Height(kFalloffSize)); + if (Event.current.type == EventType.Repaint) + GUI.DrawTexture(falloff, Falloff(), ScaleMode.ScaleToFit); + } + } + EditorGUILayout.HelpBox("Control-click takes the paint under the mouse.", MessageType.None); + } + EditorGUIUtility.labelWidth = labelWidth; + } + } + + /// The Chisel Paint tool's settings, while it is the active tool + [UnityEditor.Overlays.Overlay(typeof(SceneView), kOverlayTitle)] + sealed class ChiselPaintOverlay : UnityEditor.Overlays.IMGUIOverlay, UnityEditor.Overlays.ITransientOverlay + { + const string kOverlayTitle = "Chisel Paint"; + + public bool visible => ToolManager.activeToolType == typeof(ChiselPaintTool); + + public override void OnGUI() + { + // one width, whatever it shows + using (new EditorGUILayout.VerticalScope(GUILayout.Width(ChiselPaintTool.kPanelWidth))) + ChiselPaintTool.PanelGUI(); + } + } +} diff --git a/Editor/EditorTools/ChiselPaintTool.cs.meta b/Editor/EditorTools/ChiselPaintTool.cs.meta new file mode 100644 index 00000000..58a1737d --- /dev/null +++ b/Editor/EditorTools/ChiselPaintTool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b3c00758d6aaaca4eaa8a1ee19c04765 \ No newline at end of file diff --git a/Editor/EditorTools/ChiselUVToolCommon.cs b/Editor/EditorTools/ChiselUVToolCommon.cs index 0f96290d..d7bc1e86 100644 --- a/Editor/EditorTools/ChiselUVToolCommon.cs +++ b/Editor/EditorTools/ChiselUVToolCommon.cs @@ -92,6 +92,7 @@ void Reset() surfaceFlagsProp = null; UV0Prop = null; hotspotProp = null; + paintSpacingProp = null; initialized = false; } @@ -129,6 +130,7 @@ void UpdateSurfaceSelection() hotspotProp = surfaceDetailsProp.FindPropertyRelative(nameof(SurfaceDetails.hotspot)); } chiselMaterialProp = surfaceProp.FindPropertyRelative(ChiselSurface.kChiselMaterialName); + paintSpacingProp = surfaceProp.FindPropertyRelative(ChiselSurface.kPaintSpacingName); initialized = true; } @@ -147,6 +149,7 @@ void UpdateAllSurfaces() SerializedProperty surfaceFlagsProp; SerializedProperty UV0Prop; SerializedProperty hotspotProp; + SerializedProperty paintSpacingProp; [SerializeField] UnityEngine.Object[] undoableObjects = Array.Empty(); [SerializeField] ChiselSelectedSurface[] surfaces = Array.Empty(); @@ -536,6 +539,12 @@ public void OnSceneSettingsGUI(SceneView sceneView) EditorGUI.PropertyField(position, UV0Prop, SurfaceDetailsPropertyDrawer.kUV0Contents, true); } + if (paintSpacingProp != null && SurfaceDetailsPropertyDrawer.TakesPaint(surfaces[0].Surface.RenderMaterial)) + { + position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight); + SurfaceDetailsPropertyDrawer.PaintSpacingField(position, paintSpacingProp, SurfaceDetailsPropertyDrawer.kPaintSpacingContents); + } + EditorGUIUtility.labelWidth = prevLabelWidth; } if (EditorGUI.EndChangeCheck() && undoableObjects.Length > 0) @@ -555,6 +564,7 @@ public void OnSceneSettingsGUI(SceneView sceneView) serializedObject.Update(); } ChiselHotspotGroupEdit.Spread(edited, undoableObjects.Length > 1 ? "Modified materials" : "Modified material"); + SurfaceDetailsPropertyDrawer.Respace(edited, undoableObjects.Length > 1 ? "Modified materials" : "Modified material"); } } diff --git a/Editor/PropertyDrawers/ChiselPaintModeDrawer.cs b/Editor/PropertyDrawers/ChiselPaintModeDrawer.cs new file mode 100644 index 00000000..737451b3 --- /dev/null +++ b/Editor/PropertyDrawers/ChiselPaintModeDrawer.cs @@ -0,0 +1,114 @@ +using Chisel.Components; +using Chisel.Core; +using UnityEditor; +using UnityEngine; + +namespace Chisel.Editors +{ + // A paint mode shows what its kind uses: a color mode its default color, a value mode its default, range and group, + // and the components as toggles (named RGBA for the Color attribute) + [CustomPropertyDrawer(typeof(ChiselPaintMode))] + sealed class ChiselPaintModeDrawer : PropertyDrawer + { + static readonly GUIContent kNameContent = new("Name", "What the mode is called in the paint tool."); + static readonly GUIContent kKindContent = new("Kind", "Color: the brush paints a color. Value: the brush paints a number (a height, a layer's weight), up or down."); + static readonly GUIContent kAttributeContent = new("Attribute", "The vertex attribute the paint is written to; the shader reads it from there. TexCoord0 and TexCoord1 are taken."); + static readonly GUIContent kComponentsContent = new("Components", "The components of the attribute the mode writes; the others keep their default."); + static readonly GUIContent kDefaultColor = new("Default", "What an unpainted surface has."); + static readonly GUIContent kDefaultValue = new("Default", "What an unpainted surface has, in each of the mode's components."); + static readonly GUIContent kRangeContent = new("Range", "The lowest and highest value: the brush keeps paint between them."); + static readonly GUIContent kGroupContent = new("Group", "Value modes with the same group (above 0), in one attribute, are weights that always sum to one: painting one takes from the others. 0 is no group."); + static readonly string[] kXYZW = { "X", "Y", "Z", "W" }; + static readonly string[] kRGBA = { "R", "G", "B", "A" }; + + static float Line => EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + if (!property.isExpanded) + return EditorGUIUtility.singleLineHeight; + var kind = property.FindPropertyRelative(nameof(ChiselPaintMode.kind)); + var value = kind.hasMultipleDifferentValues || kind.enumValueIndex == (int)ChiselPaintKind.Value; + // foldout, name, kind, attribute, components, default, and range and group for a value + return Line * (value ? 8 : 6); + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var name = property.FindPropertyRelative(nameof(ChiselPaintMode.name)); + var kind = property.FindPropertyRelative(nameof(ChiselPaintMode.kind)); + var attribute = property.FindPropertyRelative(nameof(ChiselPaintMode.attribute)); + var components = property.FindPropertyRelative(nameof(ChiselPaintMode.components)); + var defaults = property.FindPropertyRelative(nameof(ChiselPaintMode.defaultValue)); + var range = property.FindPropertyRelative(nameof(ChiselPaintMode.range)); + var group = property.FindPropertyRelative(nameof(ChiselPaintMode.group)); + + var row = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + var title = string.IsNullOrEmpty(name.stringValue) ? label : new GUIContent(name.stringValue); + property.isExpanded = EditorGUI.Foldout(row, property.isExpanded, title, true); + if (!property.isExpanded) + return; + + using (new EditorGUI.IndentLevelScope()) + { + row.y += Line; EditorGUI.PropertyField(row, name, kNameContent); + row.y += Line; EditorGUI.PropertyField(row, kind, kKindContent); + row.y += Line; EditorGUI.PropertyField(row, attribute, kAttributeContent); + row.y += Line; ComponentToggles(row, components, attribute.enumValueIndex == 0 ? kRGBA : kXYZW); + + var isValue = kind.hasMultipleDifferentValues || kind.enumValueIndex == (int)ChiselPaintKind.Value; + row.y += Line; + if (!isValue) + { + EditorGUI.BeginChangeCheck(); + var color = EditorGUI.ColorField(row, kDefaultColor, (Vector4)defaults.vector4Value, true, true, false); + if (EditorGUI.EndChangeCheck()) + defaults.vector4Value = color; + return; + } + + // one default for all of a value mode's components + EditorGUI.BeginChangeCheck(); + var first = FirstComponent(components.intValue); + var value = EditorGUI.FloatField(row, kDefaultValue, defaults.vector4Value[first]); + if (EditorGUI.EndChangeCheck()) + defaults.vector4Value = new Vector4(value, value, value, value); + row.y += Line; EditorGUI.PropertyField(row, range, kRangeContent); + row.y += Line; group.intValue = Mathf.Max(0, EditorGUI.IntField(row, kGroupContent, group.intValue)); + } + } + + static int FirstComponent(int components) + { + for (int c = 0; c < 4; c++) + { + if ((components & (1 << c)) != 0) + return c; + } + return 0; + } + + // The components as four toggle buttons + static void ComponentToggles(Rect row, SerializedProperty components, string[] names) + { + using (new EditorGUI.PropertyScope(row, kComponentsContent, components)) + { + var field = EditorGUI.PrefixLabel(row, kComponentsContent); + var indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + var width = field.width / 4; + var value = components.intValue; + EditorGUI.BeginChangeCheck(); + for (int c = 0; c < 4; c++) + { + var style = c == 0 ? EditorStyles.miniButtonLeft : c == 3 ? EditorStyles.miniButtonRight : EditorStyles.miniButtonMid; + var on = GUI.Toggle(new Rect(field.x + width * c, field.y, width, field.height), (value & (1 << c)) != 0, names[c], style); + value = on ? value | (1 << c) : value & ~(1 << c); + } + if (EditorGUI.EndChangeCheck()) + components.intValue = value; + EditorGUI.indentLevel = indent; + } + } + } +} diff --git a/Editor/PropertyDrawers/ChiselPaintModeDrawer.cs.meta b/Editor/PropertyDrawers/ChiselPaintModeDrawer.cs.meta new file mode 100644 index 00000000..3e5de774 --- /dev/null +++ b/Editor/PropertyDrawers/ChiselPaintModeDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 45cefae5bc727ed47b878a87fc36bf11 \ No newline at end of file diff --git a/Editor/PropertyDrawers/SurfaceDetailsPropertyDrawer.cs b/Editor/PropertyDrawers/SurfaceDetailsPropertyDrawer.cs index 231045d1..26dd5eb5 100644 --- a/Editor/PropertyDrawers/SurfaceDetailsPropertyDrawer.cs +++ b/Editor/PropertyDrawers/SurfaceDetailsPropertyDrawer.cs @@ -13,6 +13,39 @@ public sealed class SurfaceDetailsPropertyDrawer : PropertyDrawer public readonly static GUIContent kUV0Contents = new("UV"); public readonly static GUIContent kSurfaceFlagsContents = new("Surface Flags"); public readonly static GUIContent kSmoothingGroupContents = new("Smoothing Groups"); + public readonly static GUIContent kPaintSpacingContents = new("Paint Spacing", "How far apart the points this surface is painted at are, in metres: the surface is cut at them. Changing it resamples the paint it has."); + + // Whether the material's shader takes paint (has a paint profile) + public static bool TakesPaint(Material material) + { + return material && ChiselPaintProfiles.Find(material.shader) != null; + } + + // A paint spacing field; 0 (not set) shows the default + public static void PaintSpacingField(Rect position, SerializedProperty spacingProp, GUIContent label) + { + EditorGUI.BeginProperty(position, label, spacingProp); + EditorGUI.showMixedValue = spacingProp.hasMultipleDifferentValues; + EditorGUI.BeginChangeCheck(); + var spacing = EditorGUI.FloatField(position, label, spacingProp.floatValue > 0 ? spacingProp.floatValue : ChiselSurface.kDefaultPaintSpacing); + if (EditorGUI.EndChangeCheck()) + spacingProp.floatValue = Mathf.Max(0.01f, spacing); + EditorGUI.showMixedValue = false; + EditorGUI.EndProperty(); + } + + // Resamples the paint of the generators these details are on, after their paint spacing changed + public static void Respace(System.Collections.Generic.List surfaces, string undoName) + { + foreach (var surface in surfaces) + { + if (surface.node is not ChiselGeneratorComponent generator || generator.SurfacePaint.Count == 0) + continue; + Undo.RecordObject(generator, undoName); + if (ChiselPaintBrush.Respace(generator)) + EditorUtility.SetDirty(generator); + } + } // The material beside these details (the first selected surface's) static Material MaterialOf(SerializedProperty property) @@ -34,6 +67,15 @@ static ChiselHotspotLayout LayoutOf(SerializedProperty property) return materialProp != null ? HotspotTransformGUI.LayoutOf(materialProp.objectReferenceValue as Material) : null; } + // The paint spacing beside these details (on their surface) + static SerializedProperty PaintSpacingOf(SerializedProperty property) + { + var path = property.propertyPath; + var dot = path.LastIndexOf('.'); + var parent = dot < 0 ? string.Empty : path.Substring(0, dot + 1); + return property.serializedObject.FindProperty(parent + ChiselSurface.kPaintSpacingName); + } + static readonly System.Text.RegularExpressions.Regex kDescriptionIndex = new(@"data\[(\d+)\]\.[^.\[]+$"); static readonly System.Collections.Generic.List s_Surfaces = new(); @@ -77,7 +119,8 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent kSpacing + SurfaceFlagsPropertyDrawer.DefaultHeight + kSpacing + - SmoothingGroupPropertyDrawer.GetDefaultHeight(smoothingGroupProp.propertyPath, true); + SmoothingGroupPropertyDrawer.GetDefaultHeight(smoothingGroupProp.propertyPath, true) + + (TakesPaint(MaterialOf(property)) && PaintSpacingOf(property) != null ? kSpacing + EditorGUIUtility.singleLineHeight : 0); } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) @@ -126,6 +169,14 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten position.height = SmoothingGroupPropertyDrawer.GetDefaultHeight(smoothingGroupProp.propertyPath, true); EditorGUI.PropertyField(position, smoothingGroupProp, !hasLabel ? GUIContent.none : kSmoothingGroupContents, false); + + var paintSpacingProp = PaintSpacingOf(property); + if (paintSpacingProp != null && TakesPaint(MaterialOf(property))) + { + position.y += position.height + kSpacing; + position.height = EditorGUIUtility.singleLineHeight; + PaintSpacingField(position, paintSpacingProp, !hasLabel ? GUIContent.none : kPaintSpacingContents); + } } if (EditorGUI.EndChangeCheck()) { @@ -134,6 +185,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten if (ChiselTextureLockEdit.KeepLook(surfaces, wasWorld, "Modified surface")) property.serializedObject.Update(); SpreadToConnected(property); + Respace(surfaces, "Modified surface"); } } catch (ExitGUIException) { } diff --git a/Editor/SceneView/Overlays/ChiselGlobal.cs b/Editor/SceneView/Overlays/ChiselGlobal.cs index 2855c7db..b9a3b8d6 100644 --- a/Editor/SceneView/Overlays/ChiselGlobal.cs +++ b/Editor/SceneView/Overlays/ChiselGlobal.cs @@ -25,7 +25,8 @@ public override IEnumerable GetAdditionalToolTypes() typeof(ChiselUVMoveTool), typeof(ChiselUVRotateTool), typeof(ChiselUVScaleTool), - typeof(ChiselUVConnectTool) + typeof(ChiselUVConnectTool), + typeof(ChiselPaintTool) }; } diff --git a/Editor/Settings/ChiselPaintSettingsProvider.cs b/Editor/Settings/ChiselPaintSettingsProvider.cs new file mode 100644 index 00000000..0ecd1b2e --- /dev/null +++ b/Editor/Settings/ChiselPaintSettingsProvider.cs @@ -0,0 +1,189 @@ +using System.Collections.Generic; +using Chisel.Components; +using Chisel.Core; +using UnityEditor; +using UnityEngine; + +namespace Chisel.Editors +{ + // The project's paint profiles: a list of the shaders that can be painted, and the modes of the one chosen + static class ChiselPaintSettingsGUI + { + static readonly GUIContent kExplanation = EditorGUIUtility.TrTextContent( + "Surfaces drawn with one of these shaders can be painted with the Chisel Paint tool, in its modes. " + + "The shader shows the paint; Chisel writes it to the vertex attributes the modes name."); + static readonly GUIContent kAddContent = EditorGUIUtility.TrTextContent("Add Shader", "Makes surfaces drawn with this shader paintable"); + static readonly GUIContent kRemoveContent = EditorGUIUtility.TrTextContent("Remove", "Surfaces drawn with this shader can't be painted any more (their paint is kept)"); + static readonly GUIContent kModesContent = EditorGUIUtility.TrTextContent("Modes", "What a surface drawn with the shader can be painted with"); + + internal static int s_Selected; + static UnityEditorInternal.ReorderableList s_ModesList; + + // The modes of the chosen profile: a list with a header, not a foldout + static UnityEditorInternal.ReorderableList ModesList(SerializedProperty modesProp) + { + if (s_ModesList != null && s_ModesList.serializedProperty.propertyPath == modesProp.propertyPath && + s_ModesList.serializedProperty.serializedObject == modesProp.serializedObject) + return s_ModesList; + s_ModesList = new UnityEditorInternal.ReorderableList(modesProp.serializedObject, modesProp, true, true, true, true) + { + drawHeaderCallback = rect => EditorGUI.LabelField(rect, kModesContent), + elementHeightCallback = index => EditorGUI.GetPropertyHeight(modesProp.GetArrayElementAtIndex(index), true) + EditorGUIUtility.standardVerticalSpacing, + drawElementCallback = (rect, index, active, focused) => + { + rect.xMin += 10; + rect.height = EditorGUI.GetPropertyHeight(modesProp.GetArrayElementAtIndex(index), true); + EditorGUI.PropertyField(rect, modesProp.GetArrayElementAtIndex(index), true); + } + }; + return s_ModesList; + } + static SerializedObject s_Serialized; + + public static void OnGUI(ChiselPaintSettings settings) + { + EditorGUILayout.LabelField(kExplanation, EditorStyles.wordWrappedMiniLabel); + EditorGUILayout.Space(); + + if (s_Serialized == null || s_Serialized.targetObject != settings) + s_Serialized = new SerializedObject(settings); + s_Serialized.Update(); + var profilesProp = s_Serialized.FindProperty(nameof(ChiselPaintSettings.profiles)); + + var added = (Shader)EditorGUILayout.ObjectField(kAddContent, null, typeof(Shader), false); + if (added) + { + s_Selected = ChiselPaintSettingsProvider.Add(settings, added); + s_Serialized.Update(); + } + for (int i = 0; i < profilesProp.arraySize; i++) + { + var shader = profilesProp.GetArrayElementAtIndex(i).FindPropertyRelative(nameof(ChiselPaintProfile.shader)).objectReferenceValue as Shader; + using (new EditorGUILayout.HorizontalScope()) + { + if (GUILayout.Toggle(i == s_Selected, shader ? shader.name : "(no shader)", EditorStyles.toolbarButton)) + s_Selected = i; + if (GUILayout.Button(kRemoveContent, EditorStyles.miniButton, GUILayout.Width(60))) + { + profilesProp.DeleteArrayElementAtIndex(i); + s_Selected = Mathf.Min(s_Selected, profilesProp.arraySize - 1); + s_Serialized.ApplyModifiedProperties(); + ChiselPaintSettings.Changed(); + GUIUtility.ExitGUI(); + } + } + } + + EditorGUI.BeginChangeCheck(); + if (s_Selected >= 0 && s_Selected < profilesProp.arraySize) + { + EditorGUILayout.Space(); + ModesList(profilesProp.GetArrayElementAtIndex(s_Selected).FindPropertyRelative(nameof(ChiselPaintProfile.modes))).DoLayoutList(); + } + if (EditorGUI.EndChangeCheck()) + { + s_Serialized.ApplyModifiedProperties(); + ChiselPaintSettings.Changed(); + } + + if (s_Selected >= 0 && s_Selected < settings.profiles.Count) + { + var profile = settings.profiles[s_Selected]; + if (profile.shader != null && settings.Find(profile.shader) != profile) + EditorGUILayout.HelpBox("Another profile is for this shader too; only the first one is used.", MessageType.Warning); + if (profile.FindOverlap(out var first, out var second)) + EditorGUILayout.HelpBox($"Modes {first} and {second} both write a component of {profile.modes[first].attribute}: one of them overwrites the other.", MessageType.Warning); + } + } + } + + // Project Settings > Chisel > Paint + class ChiselPaintSettingsProvider : SettingsProvider + { + public const string kPath = "Project/Chisel/Paint"; + + ChiselPaintSettingsProvider(string path, SettingsScope scope) : base(path, scope) { } + + [SettingsProvider] + public static SettingsProvider CreatePaintSettingsProvider() + { + return new ChiselPaintSettingsProvider(kPath, SettingsScope.Project) + { + label = "Paint", + keywords = new HashSet(new[] { "chisel", "paint", "vertex", "color", "shader", "profile" }) + }; + } + + // The project's settings, with the package's test shaders in them + public static ChiselPaintSettings CreateProjectAsset() + { + var folder = System.IO.Path.GetDirectoryName(ChiselPaintSettings.kAssetPath).Replace('\\', '/'); + if (!AssetDatabase.IsValidFolder(folder)) + AssetDatabase.CreateFolder("Assets", System.IO.Path.GetFileName(folder)); + var settings = ScriptableObject.CreateInstance(); + var color = Shader.Find("Chisel/Test/Paint Color"); + if (color) + settings.profiles.Add(new ChiselPaintProfile { shader = color, modes = { ChiselPaintMode.Color("Color") } }); + var value = Shader.Find("Chisel/Test/Paint Value"); + if (value) + settings.profiles.Add(new ChiselPaintProfile { shader = value, modes = { ChiselPaintMode.Value("Value", ChiselPaintAttribute.TexCoord2, ChiselPaintComponents.X) } }); + AssetDatabase.CreateAsset(settings, AssetDatabase.GenerateUniqueAssetPath(ChiselPaintSettings.kAssetPath)); + AssetDatabase.SaveAssets(); + ChiselPaintSettings.Project = settings; + return settings; + } + + // The index of the shader's profile, added when it has none + public static int Add(ChiselPaintSettings settings, Shader shader) + { + var index = settings.profiles.FindIndex(profile => profile != null && profile.shader == shader); + if (index >= 0) + return index; + Undo.RecordObject(settings, "Make Shader Paintable"); + settings.profiles.Add(new ChiselPaintProfile { shader = shader }); + EditorUtility.SetDirty(settings); + ChiselPaintSettings.Changed(); + return settings.profiles.Count - 1; + } + + // Opens the page on the shader's profile, adding one when it has none + public static void Configure(Shader shader) + { + var settings = ChiselPaintSettings.Project; + if (!settings) + settings = CreateProjectAsset(); + if (shader) + ChiselPaintSettingsGUI.s_Selected = Add(settings, shader); + SettingsService.OpenProjectSettings(kPath); + } + + public override void OnGUI(string searchContext) + { + EditorGUIUtility.labelWidth = 120; + EditorGUILayout.Space(); + var settings = ChiselPaintSettings.Project; + if (!settings) + { + EditorGUILayout.HelpBox("This project has no paint settings, so no surface can be painted.", MessageType.Info); + if (GUILayout.Button("Create Paint Settings", GUILayout.Width(200))) + CreateProjectAsset(); + return; + } + ChiselPaintSettingsGUI.OnGUI(settings); + } + } + + [CustomEditor(typeof(ChiselPaintSettings))] + class ChiselPaintSettingsEditor : Editor + { + public override void OnInspectorGUI() + { + var settings = (ChiselPaintSettings)target; + if (settings != ChiselPaintSettings.Project) + EditorGUILayout.HelpBox("These aren't the project's paint settings, so nothing uses them.", MessageType.Info); + if (GUILayout.Button("Open In Project Settings")) + SettingsService.OpenProjectSettings(ChiselPaintSettingsProvider.kPath); + ChiselPaintSettingsGUI.OnGUI(settings); + } + } +} diff --git a/Editor/Settings/ChiselPaintSettingsProvider.cs.meta b/Editor/Settings/ChiselPaintSettingsProvider.cs.meta new file mode 100644 index 00000000..5021eec5 --- /dev/null +++ b/Editor/Settings/ChiselPaintSettingsProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ac853b3745e48dcbc1c4cec153cf607 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/SurfaceEditors/ChiselSurfaceMetadataEditor.cs b/Editor/SurfaceEditors/ChiselSurfaceMetadataEditor.cs index a4067b38..1c25bcdd 100644 --- a/Editor/SurfaceEditors/ChiselSurfaceMetadataEditor.cs +++ b/Editor/SurfaceEditors/ChiselSurfaceMetadataEditor.cs @@ -2,6 +2,7 @@ using UnityEditor; using System; using Chisel.Core; +using Chisel.Components; using UnityEngine.Profiling; namespace Chisel.Editors @@ -158,6 +159,22 @@ void HotspotGUI() ChiselHotspotLayoutWindow.RefreshSurfacesUsing(MaterialOf(metadata)); } + static readonly GUIContent kConfigurePaintContents = new("Configure Paintability Of Shader", "Opens Project Settings > Chisel > Paint on the paint profile of the material's shader, adding one when it has none"); + + // Whether the material's shader can be painted, and the way to its paint profile + void PaintGUI() + { + var material = MaterialOf(target); + if (!material || !material.shader) + return; + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.PrefixLabel(ChiselPaintProfiles.Find(material.shader) != null ? "Paintable" : "Not Paintable"); + if (GUILayout.Button(kConfigurePaintContents, EditorStyles.miniButton)) + ChiselPaintSettingsProvider.Configure(material.shader); + } + } + public override void OnInspectorGUI() { Profiler.BeginSample("OnInspectorGUI"); @@ -188,6 +205,7 @@ public override void OnInspectorGUI() foreach (var metadata in targets) ChiselHotspotLayoutWindow.RefreshSurfacesUsing(MaterialOf(metadata)); } + PaintGUI(); } catch (ExitGUIException) { } catch (Exception ex) { Debug.LogException(ex); } diff --git a/Package Resources/Materials/URP/Paint.meta b/Package Resources/Materials/URP/Paint.meta new file mode 100644 index 00000000..78c7c84a --- /dev/null +++ b/Package Resources/Materials/URP/Paint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2e49474b03343f04a95222d1f53321ac +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Package Resources/Materials/URP/Paint/ChiselPaintColor.shader b/Package Resources/Materials/URP/Paint/ChiselPaintColor.shader new file mode 100644 index 00000000..49a2c647 --- /dev/null +++ b/Package Resources/Materials/URP/Paint/ChiselPaintColor.shader @@ -0,0 +1,68 @@ +// Test shader for a Color paint mode: the base color times the vertex color +Shader "Chisel/Test/Paint Color" +{ + Properties + { + _BaseMap ("Base Map", 2D) = "white" {} + _BaseColor ("Base Color", Color) = (1, 1, 1, 1) + [HideInInspector] _ValueColor ("Value Color", Color) = (1, 0, 0, 1) + } + SubShader + { + Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Geometry" } + + Pass + { + Name "ForwardOnly" + Tags { "LightMode" = "UniversalForwardOnly" } + HLSLPROGRAM + #pragma vertex PaintVertex + #pragma fragment Fragment + #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN + #pragma multi_compile_fragment _ _SHADOWS_SOFT + #include "ChiselPaintCommon.hlsl" + + half4 Fragment(PaintVaryings input) : SV_Target + { + half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor * input.color; + return half4(PaintLighting(albedo.rgb, input.positionWS, input.normalWS), 1); + } + ENDHLSL + } + + Pass + { + Name "DepthOnly" + Tags { "LightMode" = "DepthOnly" } + ColorMask R + HLSLPROGRAM + #pragma vertex DepthVertex + #pragma fragment DepthFragment + #include "ChiselPaintCommon.hlsl" + ENDHLSL + } + + Pass + { + Name "DepthNormalsOnly" + Tags { "LightMode" = "DepthNormalsOnly" } + HLSLPROGRAM + #pragma vertex DepthVertex + #pragma fragment DepthNormalsFragment + #include "ChiselPaintCommon.hlsl" + ENDHLSL + } + + Pass + { + Name "ShadowCaster" + Tags { "LightMode" = "ShadowCaster" } + ColorMask 0 + HLSLPROGRAM + #pragma vertex ShadowVertex + #pragma fragment DepthFragment + #include "ChiselPaintCommon.hlsl" + ENDHLSL + } + } +} diff --git a/Package Resources/Materials/URP/Paint/ChiselPaintColor.shader.meta b/Package Resources/Materials/URP/Paint/ChiselPaintColor.shader.meta new file mode 100644 index 00000000..83a757c7 --- /dev/null +++ b/Package Resources/Materials/URP/Paint/ChiselPaintColor.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 7dfc31081b24da14a97e061b8ca75c94 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Package Resources/Materials/URP/Paint/ChiselPaintCommon.hlsl b/Package Resources/Materials/URP/Paint/ChiselPaintCommon.hlsl new file mode 100644 index 00000000..1109e48e --- /dev/null +++ b/Package Resources/Materials/URP/Paint/ChiselPaintCommon.hlsl @@ -0,0 +1,96 @@ +// What the Chisel paint test shaders share: simple lighting (the main light and the ambient), and the passes URP's +// deferred and forward renderers need from an opaque shader +#ifndef CHISEL_PAINT_COMMON_INCLUDED +#define CHISEL_PAINT_COMMON_INCLUDED + +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" + +CBUFFER_START(UnityPerMaterial) + float4 _BaseMap_ST; + half4 _BaseColor; + half4 _ValueColor; +CBUFFER_END + +TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); + +struct PaintAttributes +{ + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + float2 uv : TEXCOORD0; + float4 color : COLOR; + float4 paint : TEXCOORD2; +}; + +struct PaintVaryings +{ + float4 positionCS : SV_POSITION; + float3 positionWS : TEXCOORD0; + float3 normalWS : TEXCOORD1; + float2 uv : TEXCOORD2; + float4 color : TEXCOORD3; + float4 paint : TEXCOORD4; +}; + +PaintVaryings PaintVertex(PaintAttributes input) +{ + PaintVaryings output; + output.positionWS = TransformObjectToWorld(input.positionOS.xyz); + output.positionCS = TransformWorldToHClip(output.positionWS); + output.normalWS = TransformObjectToWorldNormal(input.normalOS); + output.uv = TRANSFORM_TEX(input.uv, _BaseMap); + output.color = input.color; + output.paint = input.paint; + return output; +} + +half3 PaintLighting(half3 albedo, float3 positionWS, float3 normalWS) +{ + float3 normal = normalize(normalWS); + Light light = GetMainLight(TransformWorldToShadowCoord(positionWS)); + half3 direct = light.color * saturate(dot(normal, light.direction)) * light.shadowAttenuation; + return albedo * (direct + SampleSH(normal)); +} + +// Depth, depth-normals and shadows: the position and normal only +struct DepthVaryings +{ + float4 positionCS : SV_POSITION; + float3 normalWS : TEXCOORD0; +}; + +DepthVaryings DepthVertex(PaintAttributes input) +{ + DepthVaryings output; + output.positionCS = TransformObjectToHClip(input.positionOS.xyz); + output.normalWS = TransformObjectToWorldNormal(input.normalOS); + return output; +} + +half4 DepthFragment(DepthVaryings input) : SV_Target { return 0; } + +half4 DepthNormalsFragment(DepthVaryings input) : SV_Target +{ + return half4(NormalizeNormalPerPixel(input.normalWS), 0.0); +} + +float3 _LightDirection; + +DepthVaryings ShadowVertex(PaintAttributes input) +{ + DepthVaryings output; + float3 positionWS = TransformObjectToWorld(input.positionOS.xyz); + float3 normalWS = TransformObjectToWorldNormal(input.normalOS); + float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection)); +#if UNITY_REVERSED_Z + positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE); +#else + positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE); +#endif + output.positionCS = positionCS; + output.normalWS = normalWS; + return output; +} + +#endif diff --git a/Package Resources/Materials/URP/Paint/ChiselPaintCommon.hlsl.meta b/Package Resources/Materials/URP/Paint/ChiselPaintCommon.hlsl.meta new file mode 100644 index 00000000..712f322f --- /dev/null +++ b/Package Resources/Materials/URP/Paint/ChiselPaintCommon.hlsl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 25fc8f6cb268ad94491a52ca25745f31 +ShaderIncludeImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Package Resources/Materials/URP/Paint/ChiselPaintValue.shader b/Package Resources/Materials/URP/Paint/ChiselPaintValue.shader new file mode 100644 index 00000000..0d5d7099 --- /dev/null +++ b/Package Resources/Materials/URP/Paint/ChiselPaintValue.shader @@ -0,0 +1,69 @@ +// Test shader for a Value paint mode: TEXCOORD2.x from 0 to 1, from the base color to the value color +Shader "Chisel/Test/Paint Value" +{ + Properties + { + _BaseMap ("Base Map", 2D) = "white" {} + _BaseColor ("Base Color", Color) = (1, 1, 1, 1) + _ValueColor ("Value Color", Color) = (1, 0.3, 0, 1) + } + SubShader + { + Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Geometry" } + + Pass + { + Name "ForwardOnly" + Tags { "LightMode" = "UniversalForwardOnly" } + HLSLPROGRAM + #pragma vertex PaintVertex + #pragma fragment Fragment + #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN + #pragma multi_compile_fragment _ _SHADOWS_SOFT + #include "ChiselPaintCommon.hlsl" + + half4 Fragment(PaintVaryings input) : SV_Target + { + half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; + albedo.rgb = lerp(albedo.rgb, _ValueColor.rgb, saturate(input.paint.x)); + return half4(PaintLighting(albedo.rgb, input.positionWS, input.normalWS), 1); + } + ENDHLSL + } + + Pass + { + Name "DepthOnly" + Tags { "LightMode" = "DepthOnly" } + ColorMask R + HLSLPROGRAM + #pragma vertex DepthVertex + #pragma fragment DepthFragment + #include "ChiselPaintCommon.hlsl" + ENDHLSL + } + + Pass + { + Name "DepthNormalsOnly" + Tags { "LightMode" = "DepthNormalsOnly" } + HLSLPROGRAM + #pragma vertex DepthVertex + #pragma fragment DepthNormalsFragment + #include "ChiselPaintCommon.hlsl" + ENDHLSL + } + + Pass + { + Name "ShadowCaster" + Tags { "LightMode" = "ShadowCaster" } + ColorMask 0 + HLSLPROGRAM + #pragma vertex ShadowVertex + #pragma fragment DepthFragment + #include "ChiselPaintCommon.hlsl" + ENDHLSL + } + } +} diff --git a/Package Resources/Materials/URP/Paint/ChiselPaintValue.shader.meta b/Package Resources/Materials/URP/Paint/ChiselPaintValue.shader.meta new file mode 100644 index 00000000..6b8652e4 --- /dev/null +++ b/Package Resources/Materials/URP/Paint/ChiselPaintValue.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a553dbbcc51572e46a6e4778d37abca7 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Package Resources/Materials/URP/Paint/Paint Color Test.mat b/Package Resources/Materials/URP/Paint/Paint Color Test.mat new file mode 100644 index 00000000..9911cf25 --- /dev/null +++ b/Package Resources/Materials/URP/Paint/Paint Color Test.mat @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Paint Color Test + m_Shader: {fileID: 4800000, guid: 7dfc31081b24da14a97e061b8ca75c94, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: [] + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _ValueColor: {r: 1, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Package Resources/Materials/URP/Paint/Paint Color Test.mat.meta b/Package Resources/Materials/URP/Paint/Paint Color Test.mat.meta new file mode 100644 index 00000000..5fda1aa6 --- /dev/null +++ b/Package Resources/Materials/URP/Paint/Paint Color Test.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9ad9332ac60226d43a844ce2a96b2f80 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Package Resources/Materials/URP/Paint/Paint Value Test.mat b/Package Resources/Materials/URP/Paint/Paint Value Test.mat new file mode 100644 index 00000000..189d87df --- /dev/null +++ b/Package Resources/Materials/URP/Paint/Paint Value Test.mat @@ -0,0 +1,36 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Paint Value Test + m_Shader: {fileID: 4800000, guid: a553dbbcc51572e46a6e4778d37abca7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: [] + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _ValueColor: {r: 1, g: 0.29999998, b: 0, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Package Resources/Materials/URP/Paint/Paint Value Test.mat.meta b/Package Resources/Materials/URP/Paint/Paint Value Test.mat.meta new file mode 100644 index 00000000..9bfd76c1 --- /dev/null +++ b/Package Resources/Materials/URP/Paint/Paint Value Test.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bccd75b0f7957884caa3b4fd03bc1c09 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: