diff options
Diffstat (limited to 'Scripts/Editor')
| -rw-r--r-- | Scripts/Editor/GenerateNoise.cs | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/Scripts/Editor/GenerateNoise.cs b/Scripts/Editor/GenerateNoise.cs new file mode 100644 index 0000000..324b5b5 --- /dev/null +++ b/Scripts/Editor/GenerateNoise.cs @@ -0,0 +1,210 @@ +using UnityEngine; +using UnityEditor; + +public class GenerateNoise : EditorWindow +{ + enum NoiseType { WhiteNoise, WorleyNoise, WorleyEdgeSDF } + enum DistanceMetric { L1, L2, LInfinity } + + NoiseType _type = NoiseType.WhiteNoise; + int _resolution = 32; + int _texelsPerCell = 8; + DistanceMetric _metric = DistanceMetric.L2; + float _sdfRadius = 0.5f; + + [MenuItem("Tools/yum_food/Generate 3D Noise")] + static void Open() => GetWindow<GenerateNoise>("Generate 3D Noise"); + + void OnGUI() + { + EditorGUILayout.LabelField("Noise Settings", EditorStyles.boldLabel); + _type = (NoiseType)EditorGUILayout.EnumPopup("Type", _type); + if (_type == NoiseType.WhiteNoise) + { + _resolution = EditorGUILayout.IntField("Resolution", _resolution); + _resolution = Mathf.Clamp(_resolution, 2, 512); + } + else + { + _resolution = EditorGUILayout.IntField("Cell Count", _resolution); + _resolution = Mathf.Clamp(_resolution, 2, 512); + _texelsPerCell = EditorGUILayout.IntSlider("Texels Per Cell", _texelsPerCell, 2, 64); + _metric = (DistanceMetric)EditorGUILayout.EnumPopup("Distance Metric", _metric); + if (_type == NoiseType.WorleyEdgeSDF) + _sdfRadius = EditorGUILayout.Slider("SDF Radius (cells)", _sdfRadius, 0.1f, 2f); + EditorGUILayout.HelpBox($"Texture resolution: {_resolution * _texelsPerCell}\u00b3", MessageType.None); + } + + EditorGUILayout.Space(); + + if (GUILayout.Button("Generate")) + DoGenerate(); + } + + void DoGenerate() + { + int res = _type == NoiseType.WhiteNoise ? _resolution : _resolution * _texelsPerCell; + Texture3D tex; + + switch (_type) + { + case NoiseType.WorleyNoise: + { + tex = new Texture3D(res, res, res, TextureFormat.R8, false); + GenerateWorleyField(res, _texelsPerCell, _metric, out var f1, out _); + var pixels = new byte[res * res * res]; + for (int i = 0; i < pixels.Length; i++) + pixels[i] = (byte)Mathf.RoundToInt(f1[i] * 255f); + tex.SetPixelData(pixels, 0); + break; + } + case NoiseType.WorleyEdgeSDF: + { + tex = new Texture3D(res, res, res, TextureFormat.R8, false); + GenerateWorleyField(res, _texelsPerCell, _metric, out var f1, out var f2); + var pixels = new byte[res * res * res]; + GenerateEdgeSDF(pixels, f1, f2, _sdfRadius); + tex.SetPixelData(pixels, 0); + break; + } + default: + { + tex = new Texture3D(res, res, res, TextureFormat.RGBA32, false); + var pixels = new Color32[res * res * res]; + GenerateWhiteNoise(pixels); + tex.SetPixels32(pixels); + break; + } + } + + tex.wrapMode = TextureWrapMode.Repeat; + tex.filterMode = FilterMode.Bilinear; + tex.Apply(); + + string name; + switch (_type) + { + case NoiseType.WorleyNoise: name = $"WorleyNoise3D_{_metric}_{res}"; break; + case NoiseType.WorleyEdgeSDF: name = $"WorleyEdgeSDF3D_{_metric}_{res}"; break; + default: name = $"WhiteNoise3D_{res}"; break; + } + + string path = $"Assets/yum_food/3ner/Textures/{name}.asset"; + if (!AssetDatabase.IsValidFolder("Assets/yum_food/3ner/Textures")) + AssetDatabase.CreateFolder("Assets/yum_food/3ner", "Textures"); + + AssetDatabase.CreateAsset(tex, path); + AssetDatabase.SaveAssets(); + EditorGUIUtility.PingObject(tex); + Debug.Log($"[GenerateNoise] Saved {_type} ({res}\u00b3) to {path}"); + } + + static void GenerateWhiteNoise(Color32[] pixels) + { + for (int i = 0; i < pixels.Length; i++) + { + byte r = (byte)Random.Range(0, 256); + byte g = (byte)Random.Range(0, 256); + byte b = (byte)Random.Range(0, 256); + byte a = (byte)Random.Range(0, 256); + pixels[i] = new Color32(r, g, b, a); + } + } + + static float Distance(Vector3 a, Vector3 b, DistanceMetric metric) + { + float dx = Mathf.Abs(a.x - b.x); + float dy = Mathf.Abs(a.y - b.y); + float dz = Mathf.Abs(a.z - b.z); + switch (metric) + { + case DistanceMetric.L1: return dx + dy + dz; + case DistanceMetric.LInfinity: return Mathf.Max(dx, Mathf.Max(dy, dz)); + default: return Mathf.Sqrt(dx * dx + dy * dy + dz * dz); + } + } + + static void GenerateWorleyField(int res, int texelsPerCell, DistanceMetric metric, + out float[] f1, out float[] f2) + { + int cellCount = Mathf.Max(1, res / texelsPerCell); + float cellSize = (float)res / cellCount; + + var points = new Vector3[cellCount * cellCount * cellCount]; + for (int cz = 0; cz < cellCount; cz++) + for (int cy = 0; cy < cellCount; cy++) + for (int cx = 0; cx < cellCount; cx++) + { + int ci = cx + cellCount * (cy + cellCount * cz); + points[ci] = new Vector3( + (cx + Random.value) * cellSize, + (cy + Random.value) * cellSize, + (cz + Random.value) * cellSize); + } + + float maxDist = cellSize; + int count = res * res * res; + f1 = new float[count]; + f2 = new float[count]; + + for (int z = 0; z < res; z++) + for (int y = 0; y < res; y++) + for (int x = 0; x < res; x++) + { + var pos = new Vector3(x + 0.5f, y + 0.5f, z + 0.5f); + + int cx0 = Mathf.FloorToInt(pos.x / cellSize); + int cy0 = Mathf.FloorToInt(pos.y / cellSize); + int cz0 = Mathf.FloorToInt(pos.z / cellSize); + + float d1 = float.MaxValue; + float d2 = float.MaxValue; + + for (int dz = -1; dz <= 1; dz++) + for (int dy = -1; dy <= 1; dy++) + for (int dx = -1; dx <= 1; dx++) + { + int ncx = cx0 + dx; + int ncy = cy0 + dy; + int ncz = cz0 + dz; + + int wcx = ((ncx % cellCount) + cellCount) % cellCount; + int wcy = ((ncy % cellCount) + cellCount) % cellCount; + int wcz = ((ncz % cellCount) + cellCount) % cellCount; + + Vector3 pt = points[wcx + cellCount * (wcy + cellCount * wcz)]; + pt.x += (ncx - wcx) * cellSize; + pt.y += (ncy - wcy) * cellSize; + pt.z += (ncz - wcz) * cellSize; + + float dist = Distance(pos, pt, metric); + if (dist < d1) + { + d2 = d1; + d1 = dist; + } + else if (dist < d2) + { + d2 = dist; + } + } + + int i = x + res * (y + res * z); + f1[i] = Mathf.Clamp01(d1 / maxDist); + f2[i] = Mathf.Clamp01(d2 / maxDist); + } + } + + // Convert F2-F1 into a signed distance field relative to the cell boundary (F2-F1 = 0). + // Output: 0.5 = on the boundary, 0 = clamped near-side, 1 = clamped far-side. + // sdfRadius controls how much F2-F1 range maps to [0,1] — smaller = more precision near edges. + static void GenerateEdgeSDF(byte[] pixels, float[] f1, float[] f2, float sdfRadius) + { + for (int i = 0; i < pixels.Length; i++) + { + float sd = f2[i] - f1[i]; + float normalized = sd / sdfRadius * 0.5f + 0.5f; + pixels[i] = (byte)Mathf.RoundToInt(Mathf.Clamp01(normalized) * 255f); + } + } +} |
