Skip to content

Commit 85a88df

Browse files
author
Cory Leach
committed
Wave function collapse methods
1 parent 5d05717 commit 85a88df

28 files changed

+831
-108
lines changed

Runtime/Utility/IRandomNumberGenerator.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ public interface IRandomNumberGenerator
1616
float NextFloatZeroToOne();
1717
float NextFloatNegOneToOne();
1818
float NextFloatRange(float min, float max);
19+
double NextDoubleZeroToOne();
20+
double NextDoubleNegOneToOne();
21+
double NextDoubleRange(double min, double max);
1922
bool RollChance(float probabilityOfReturningTrue);
2023
Vector2 NextDirection2D();
2124
Vector3 NextDirection3D();
2225
}
23-
}
26+
}

Runtime/Utility/Noise.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ public static class Noise
5454

5555
public static float GenerateFalloffPoint(int x, int y, int width, int height, float a = 3f, float b = 2.2f, Vector2 offset = default)
5656
{
57-
x += Mathf.RoundToInt(width * offset.x);
58-
y += Mathf.RoundToInt(height * offset.y);
57+
var x2 = x + Mathf.RoundToInt(width * offset.x);
58+
var y2 = y + Mathf.RoundToInt(height * offset.y);
5959

60-
var valueY = (y + 0.5f) / (float) height * 2 - 1;
61-
var valueX = (x + 0.5f) / (float) width * 2 - 1;
60+
var valueY = (y2 + 0.5f) / (float) height * 2 - 1;
61+
var valueX = (x2 + 0.5f) / (float) width * 2 - 1;
6262
var value = Mathf.Max(Mathf.Abs(valueX), Mathf.Abs(valueY));
6363
return 1 - FalloffCurve(value, a, b);
6464
}

Runtime/Utility/RandomGenerator.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
namespace Gameframe.Procgen
55
{
66
/// <summary>
7-
/// Noise based random number generator (Struct version)
7+
/// Noise based random number generator Struct version
8+
/// If this will be passed as a method arg use class version to avoid generating duplicate values
9+
///
810
/// Allows for fast random-access within a sequence of generated numbers
911
/// Based on GDC 2017 talk and code by Squirrel Eiserloh
1012
/// </summary>
@@ -125,6 +127,36 @@ public float NextFloatRange(float min, float max)
125127
return min + (max - min) * v;
126128
}
127129

130+
/// <summary>
131+
/// Next random double in the range 0 to 1
132+
/// </summary>
133+
/// <returns>Next random double in the range 0 to 1 (inclusive, inclusive)</returns>
134+
public double NextDoubleZeroToOne()
135+
{
136+
return SquirrelEiserloh.Get1dNoiseZeroToOne_Double(position++, seed);
137+
}
138+
139+
/// <summary>
140+
/// Next random double in the range -1 to 1
141+
/// </summary>
142+
/// <returns>Next random double in the range -1 to 1 (inclusive, inclusive)</returns>
143+
public double NextDoubleNegOneToOne()
144+
{
145+
return SquirrelEiserloh.Get1dNoiseNegOneToOne_Double(position++, seed);
146+
}
147+
148+
/// <summary>
149+
/// Next random double in the range min to max
150+
/// </summary>
151+
/// <param name="min">min return value</param>
152+
/// <param name="max">max return value</param>
153+
/// <returns>Next random double in the range min to max (inclusive, inclusive)</returns>
154+
public double NextDoubleRange(double min, double max)
155+
{
156+
var v= SquirrelEiserloh.Get1dNoiseZeroToOne_Double(position++, seed);
157+
return min + (max - min) * v;
158+
}
159+
128160
/// <summary>
129161
/// Next random integer in the range min to max
130162
/// </summary>
@@ -285,6 +317,21 @@ public int Position
285317
/// <returns>Next random float in the range min to max (inclusive, inclusive)</returns>
286318
public float NextFloatRange(float min, float max) => _randomGeneratorStruct.NextFloatRange(min,max);
287319

320+
public double NextDoubleZeroToOne()
321+
{
322+
return _randomGeneratorStruct.NextDoubleZeroToOne();
323+
}
324+
325+
public double NextDoubleNegOneToOne()
326+
{
327+
return _randomGeneratorStruct.NextDoubleNegOneToOne();
328+
}
329+
330+
public double NextDoubleRange(double min, double max)
331+
{
332+
return _randomGeneratorStruct.NextDoubleRange(min, max);
333+
}
334+
288335
/// <summary>
289336
/// Next random integer in the range min to max
290337
/// </summary>

Runtime/Utility/SquirrelEiserloh.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,18 @@ public static uint Get4dNoiseUint(int indexX, int indexY, int indexZ, int indexT
153153
/// <returns>random float between 0 and 1</returns>
154154
public static float Get1dNoiseZeroToOne(int index, uint seed)
155155
{
156-
return (float) (ONE_OVER_MAX_UINT * SquirrelNoise5(index, seed));
156+
return (float) Get1dNoiseZeroToOne_Double(index, seed);
157+
}
158+
159+
/// <summary>
160+
/// Get random value between 0 and 1 from a 1d noise map
161+
/// </summary>
162+
/// <param name="index">index of X position in noise map</param>
163+
/// <param name="seed">random seed</param>
164+
/// <returns>random double between 0 and 1</returns>
165+
public static double Get1dNoiseZeroToOne_Double(int index, uint seed)
166+
{
167+
return (ONE_OVER_MAX_UINT * SquirrelNoise5(index, seed));
157168
}
158169

159170
/// <summary>
@@ -207,7 +218,18 @@ public static float Get4dNoiseZeroToOne(int indexX, int indexY, int indexZ, int
207218
/// <returns>random float between -1 and 1</returns>
208219
public static float Get1dNoiseNegOneToOne(int index, uint seed)
209220
{
210-
return (float) (ONE_OVER_MAX_INT * unchecked((int) SquirrelNoise5(index, seed)));
221+
return (float)Get1dNoiseNegOneToOne_Double(index, seed);
222+
}
223+
224+
/// <summary>
225+
/// Get random value between 0 and 1 from a 1d noise map
226+
/// </summary>
227+
/// <param name="index">index of position in noise map</param>
228+
/// <param name="seed">random seed</param>
229+
/// <returns>random double between -1 and 1</returns>
230+
public static double Get1dNoiseNegOneToOne_Double(int index, uint seed)
231+
{
232+
return (ONE_OVER_MAX_INT * unchecked((int) SquirrelNoise5(index, seed)));
211233
}
212234

213235
/// <summary>

Runtime/Utility/TextureModel.cs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Originally by Maxim Gumin
2+
// Adapted from https://github.com/mxgmn/WaveFunctionCollapse
3+
// Copyright (C) 2023 Cory Leach, The MIT License (MIT)
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using UnityEngine;
8+
9+
namespace Gameframe.Procgen
10+
{
11+
public class TextureModel : WaveCollapseModel
12+
{
13+
readonly List<byte[]> _patterns;
14+
readonly List<Color> _colors;
15+
16+
public TextureModel(Texture2D sourceTexture, int N, int width, int height, bool periodicInput, bool periodic, int symmetry, bool ground, Heuristic heuristic) : base(width, height, N, periodic, heuristic)
17+
{
18+
var bitmap = sourceTexture.GetPixels();
19+
var sourceWidth = sourceTexture.width;
20+
var sourceHeight = sourceTexture.height;
21+
22+
var sample = new byte[bitmap.Length];
23+
_colors = new List<Color>();
24+
for (var i = 0; i < sample.Length; i++)
25+
{
26+
var color = bitmap[i];
27+
var k = 0;
28+
for (; k < _colors.Count; k++)
29+
{
30+
if (_colors[k] == color)
31+
{
32+
break;
33+
}
34+
}
35+
36+
if (k == _colors.Count)
37+
{
38+
_colors.Add(color);
39+
}
40+
41+
sample[i] = (byte) k;
42+
}
43+
44+
_patterns = new();
45+
Dictionary<long, int> patternIndices = new();
46+
List<double> weightList = new();
47+
48+
var colorsCount = _colors.Count;
49+
var xMax = periodicInput ? sourceWidth : sourceWidth - N + 1;
50+
var yMax = periodicInput ? sourceHeight : sourceHeight - N + 1;
51+
52+
for (var y = 0; y < yMax; y++)
53+
for (var x = 0; x < xMax; x++)
54+
{
55+
byte[][] ps = new byte[8][];
56+
57+
ps[0] = Pattern((dx, dy) => sample[(x + dx) % sourceWidth + (y + dy) % sourceHeight * sourceWidth], N);
58+
ps[1] = Reflect(ps[0], N);
59+
ps[2] = Rotate(ps[0], N);
60+
ps[3] = Reflect(ps[2], N);
61+
ps[4] = Rotate(ps[2], N);
62+
ps[5] = Reflect(ps[4], N);
63+
ps[6] = Rotate(ps[4], N);
64+
ps[7] = Reflect(ps[6], N);
65+
66+
for (var k = 0; k < symmetry; k++)
67+
{
68+
var p = ps[k];
69+
var h = Hash(p, colorsCount);
70+
if (patternIndices.TryGetValue(h, out int index))
71+
{
72+
weightList[index] = weightList[index] + 1;
73+
}
74+
else
75+
{
76+
patternIndices.Add(h, weightList.Count);
77+
weightList.Add(1.0);
78+
_patterns.Add(p);
79+
}
80+
}
81+
}
82+
83+
weights = weightList.ToArray();
84+
T = weights.Length;
85+
this.ground = ground;
86+
87+
propagator = new int[4][][];
88+
for (int d = 0; d < 4; d++)
89+
{
90+
propagator[d] = new int[T][];
91+
for (var t = 0; t < T; t++)
92+
{
93+
List<int> list = new();
94+
for (var t2 = 0; t2 < T; t2++)
95+
{
96+
if (Agrees(_patterns[t], _patterns[t2], dx[d], dy[d], N))
97+
{
98+
list.Add(t2);
99+
}
100+
}
101+
102+
propagator[d][t] = new int[list.Count];
103+
for (var c = 0; c < list.Count; c++)
104+
{
105+
propagator[d][t][c] = list[c];
106+
}
107+
}
108+
}
109+
}
110+
111+
private static byte[] Reflect(byte[] p, int N) => Pattern((x, y) => p[N - 1 - x + y * N], N);
112+
113+
private static byte[] Rotate(byte[] p, int N) => Pattern((x, y) => p[N - 1 - y + x * N], N);
114+
115+
private static long Hash(byte[] p, int C)
116+
{
117+
long result = 0, power = 1;
118+
for (var i = 0; i < p.Length; i++)
119+
{
120+
result += p[p.Length - 1 - i] * power;
121+
power *= C;
122+
}
123+
124+
return result;
125+
}
126+
127+
private static bool Agrees(byte[] p1, byte[] p2, int dx, int dy, int N)
128+
{
129+
int xMin = dx < 0 ? 0 : dx, xMax = dx < 0 ? dx + N : N, yMin = dy < 0 ? 0 : dy, yMax = dy < 0 ? dy + N : N;
130+
for (var y = yMin; y < yMax; y++)
131+
for (var x = xMin; x < xMax; x++)
132+
{
133+
if (p1[x + N * y] != p2[x - dx + N * (y - dy)])
134+
{
135+
return false;
136+
}
137+
}
138+
139+
return true;
140+
}
141+
142+
private static byte[] Pattern(Func<int, int, byte> f, int N)
143+
{
144+
var result = new byte[N * N];
145+
for (var y = 0; y < N; y++)
146+
for (var x = 0; x < N; x++)
147+
{
148+
result[x + y * N] = f(x, y);
149+
}
150+
return result;
151+
}
152+
}
153+
}

Runtime/Utility/TextureModel.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace Gameframe.Procgen
2+
{
3+
public static class WaveCollapseExtensions
4+
{
5+
public static int Random(this double[] weights, double r)
6+
{
7+
double sum = 0;
8+
for (var i = 0; i < weights.Length; i++)
9+
{
10+
sum += weights[i];
11+
}
12+
13+
var threshold = r * sum;
14+
15+
double partialSum = 0;
16+
for (var i = 0; i < weights.Length; i++)
17+
{
18+
partialSum += weights[i];
19+
if (partialSum >= threshold)
20+
{
21+
return i;
22+
}
23+
}
24+
25+
return 0;
26+
}
27+
28+
public static long ToPower(this int a, int n)
29+
{
30+
long product = 1;
31+
for (var i = 0; i < n; i++)
32+
{
33+
product *= a;
34+
}
35+
return product;
36+
}
37+
}
38+
}

Runtime/Utility/WaveCollapseExtensions.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)