Ensure all shapes are unique

This commit is contained in:
Andras Schmelczer 2026-06-10 22:29:40 +01:00
parent c2f61a2f7e
commit 4d94fdeec8
7 changed files with 135 additions and 108 deletions

View file

@ -15,8 +15,6 @@ class CircleBase extends EmptyDrawable {
}
}
// Suffixing the GLSL names with a per-factory-invocation id lets multiple
// circle types (e.g. different colors) coexist in one compiled shader.
let _id = 0;
/**

View file

@ -19,6 +19,8 @@ class DropletBase extends EmptyDrawable {
}
}
let _id = 0;
/**
* @category Drawable
*/
@ -27,28 +29,28 @@ export const DropletFactory = (color: vec3 | vec4 | number): typeof DropletBase
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 froms[DROPLET_COUNT];
uniform vec2 toFromDeltas[DROPLET_COUNT];
uniform float fromRadii[DROPLET_COUNT];
uniform float toRadii[DROPLET_COUNT];
uniform vec2 dropletFroms${_id}[DROPLET_COUNT${_id}];
uniform vec2 dropletToFromDeltas${_id}[DROPLET_COUNT${_id}];
uniform float dropletFromRadii${_id}[DROPLET_COUNT${_id}];
uniform float dropletToRadii${_id}[DROPLET_COUNT${_id}];
float dropletMinDistance(vec2 target, out vec4 color) {
float dropletMinDistance${_id}(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = 1000.0;
for (int i = 0; i < DROPLET_COUNT; i++) {
vec2 targetFromDelta = target - froms[i];
for (int i = 0; i < DROPLET_COUNT${_id}; i++) {
vec2 targetFromDelta = target - dropletFroms${_id}[i];
float h = clamp(
dot(targetFromDelta, toFromDeltas[i])
/ max(dot(toFromDeltas[i], toFromDeltas[i]), 0.00000001),
dot(targetFromDelta, dropletToFromDeltas${_id}[i])
/ max(dot(dropletToFromDeltas${_id}[i], dropletToFromDeltas${_id}[i]), 0.00000001),
0.0, 1.0
);
float currentDistance = -mix(
fromRadii[i], toRadii[i], h
dropletFromRadii${_id}[i], dropletToRadii${_id}[i], h
) + distance(
targetFromDelta, toFromDeltas[i] * h
targetFromDelta, dropletToFromDeltas${_id}[i] * h
);
minDistance = min(minDistance, currentDistance);
@ -57,15 +59,15 @@ export const DropletFactory = (color: vec3 | vec4 | number): typeof DropletBase
return minDistance;
}
`,
distanceFunctionName: 'dropletMinDistance',
distanceFunctionName: `dropletMinDistance${_id}`,
},
propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
from: `dropletFroms${_id}`,
toFromDelta: `dropletToFromDeltas${_id}`,
fromRadius: `dropletFromRadii${_id}`,
toRadius: `dropletToRadii${_id}`,
},
uniformCountMacroName: 'DROPLET_COUNT',
uniformCountMacroName: `DROPLET_COUNT${_id}`,
shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new Droplet(vec2.create(), vec2.create(), 0, 0),
};
@ -96,5 +98,7 @@ export const DropletFactory = (color: vec3 | vec4 | number): typeof DropletBase
}
}
_id++;
return Droplet;
};

View file

@ -15,6 +15,8 @@ class HexagonBase extends EmptyDrawable {
}
}
let _id = 0;
/**
* @category Drawable
*/
@ -23,16 +25,16 @@ export const HexagonFactory = (color: vec3 | vec4 | number): typeof HexagonBase
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 hexagonCenters[HEXAGON_COUNT];
uniform float hexagonSize[HEXAGON_COUNT];
uniform vec2 hexagonCenters${_id}[HEXAGON_COUNT${_id}];
uniform float hexagonSize${_id}[HEXAGON_COUNT${_id}];
float hexagonMinDistance(vec2 target, out vec4 color) {
float hexagonMinDistance${_id}(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = 1000.0;
for (int i = 0; i < HEXAGON_COUNT; i++) {
for (int i = 0; i < HEXAGON_COUNT${_id}; i++) {
const vec3 k = vec3(-0.866025404,0.5,0.577350269);
float r = hexagonSize[i];
vec2 p = abs(target - hexagonCenters[i]);
float r = hexagonSize${_id}[i];
vec2 p = abs(target - hexagonCenters${_id}[i]);
float cosa = 0.8660;
float sina = 0.5;
p = vec2(cosa * p.x - sina * p.y, sina * p.x + cosa * p.y);
@ -45,13 +47,13 @@ export const HexagonFactory = (color: vec3 | vec4 | number): typeof HexagonBase
return minDistance;
}
`,
distanceFunctionName: 'hexagonMinDistance',
distanceFunctionName: `hexagonMinDistance${_id}`,
},
propertyUniformMapping: {
center: 'hexagonCenters',
radius: 'hexagonSize',
center: `hexagonCenters${_id}`,
radius: `hexagonSize${_id}`,
},
uniformCountMacroName: 'HEXAGON_COUNT',
uniformCountMacroName: `HEXAGON_COUNT${_id}`,
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new Hexagon(vec2.create(), 0),
};
@ -68,5 +70,7 @@ export const HexagonFactory = (color: vec3 | vec4 | number): typeof HexagonBase
}
}
_id++;
return Hexagon;
};

View file

@ -19,6 +19,8 @@ class InvertedTunnelBase extends EmptyDrawable {
}
}
let _id = 0;
/**
* Providing a noise texture is required for this drawable.
*
@ -31,10 +33,10 @@ export const InvertedTunnelFactory = (
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 froms[INVERTED_TUNNEL_COUNT];
uniform vec2 toFromDeltas[INVERTED_TUNNEL_COUNT];
uniform float fromRadii[INVERTED_TUNNEL_COUNT];
uniform float toRadii[INVERTED_TUNNEL_COUNT];
uniform vec2 invertedTunnelFroms${_id}[INVERTED_TUNNEL_COUNT${_id}];
uniform vec2 invertedTunnelToFromDeltas${_id}[INVERTED_TUNNEL_COUNT${_id}];
uniform float invertedTunnelFromRadii${_id}[INVERTED_TUNNEL_COUNT${_id}];
uniform float invertedTunnelToRadii${_id}[INVERTED_TUNNEL_COUNT${_id}];
// Other drawables share this declaration; the include guard keeps
// the combined shader from declaring it twice.
@ -43,39 +45,44 @@ export const InvertedTunnelFactory = (
uniform sampler2D noiseTexture;
#endif
#ifdef WEBGL2_IS_AVAILABLE
float invertedTunnelTerrain(float h) {
return texture(
noiseTexture,
vec2(h, 0.5)
)[0] - 0.5;
}
#else
float invertedTunnelTerrain(float h) {
return texture2D(
noiseTexture,
vec2(h, 0.5)
)[0] - 0.5;
}
// Shared between every inverted-tunnel variant; the include guard
// keeps the combined shader from defining it twice.
#ifndef INVERTED_TUNNEL_TERRAIN_DECLARED
#define INVERTED_TUNNEL_TERRAIN_DECLARED
#ifdef WEBGL2_IS_AVAILABLE
float invertedTunnelTerrain(float h) {
return texture(
noiseTexture,
vec2(h, 0.5)
)[0] - 0.5;
}
#else
float invertedTunnelTerrain(float h) {
return texture2D(
noiseTexture,
vec2(h, 0.5)
)[0] - 0.5;
}
#endif
#endif
float invertedTunnelMinDistance(vec2 target, out vec4 color) {
float invertedTunnelMinDistance${_id}(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = -1000.0;
for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) {
for (int i = 0; i < INVERTED_TUNNEL_COUNT${_id}; i++) {
vec2 targetFromDelta = target - froms[i];
vec2 targetFromDelta = target - invertedTunnelFroms${_id}[i];
float h = dot(targetFromDelta, toFromDeltas[i])
/ max(dot(toFromDeltas[i], toFromDeltas[i]), 0.00000001);
float h = dot(targetFromDelta, invertedTunnelToFromDeltas${_id}[i])
/ max(dot(invertedTunnelToFromDeltas${_id}[i], invertedTunnelToFromDeltas${_id}[i]), 0.00000001);
float clampedH = clamp(h, 0.0, 1.0);
float currentDistance = -mix(
fromRadii[i], toRadii[i], clampedH
invertedTunnelFromRadii${_id}[i], invertedTunnelToRadii${_id}[i], clampedH
) + distance(
targetFromDelta, toFromDeltas[i] * clampedH
targetFromDelta, invertedTunnelToFromDeltas${_id}[i] * clampedH
) - invertedTunnelTerrain(h) / 12.0;
minDistance = max(minDistance, -currentDistance);
@ -85,15 +92,15 @@ export const InvertedTunnelFactory = (
}
`,
isInverted: true,
distanceFunctionName: 'invertedTunnelMinDistance',
distanceFunctionName: `invertedTunnelMinDistance${_id}`,
},
propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
from: `invertedTunnelFroms${_id}`,
toFromDelta: `invertedTunnelToFromDeltas${_id}`,
fromRadius: `invertedTunnelFromRadii${_id}`,
toRadius: `invertedTunnelToRadii${_id}`,
},
uniformCountMacroName: 'INVERTED_TUNNEL_COUNT',
uniformCountMacroName: `INVERTED_TUNNEL_COUNT${_id}`,
shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new InvertedTunnel(vec2.create(), vec2.create(), 0, 0),
};
@ -124,5 +131,7 @@ export const InvertedTunnelFactory = (
}
}
_id++;
return InvertedTunnel;
};

View file

@ -8,6 +8,8 @@ interface NoisyPolygonBase extends PolygonBase {
randomOffset: number;
}
let _id = 0;
/**
* @category Drawable
*/
@ -19,10 +21,10 @@ export const NoisyPolygonFactory = (
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 noisyPolygon${vertexCount}Vertices[NOISY_POLYGON${vertexCount}_COUNT * ${vertexCount}];
uniform vec2 noisyPolygon${vertexCount}Centers[NOISY_POLYGON${vertexCount}_COUNT];
uniform float noisyPolygon${vertexCount}Lengths[NOISY_POLYGON${vertexCount}_COUNT];
uniform float noisyPolygon${vertexCount}Randoms[NOISY_POLYGON${vertexCount}_COUNT];
uniform vec2 noisyPolygon${vertexCount}Vertices${_id}[NOISY_POLYGON${vertexCount}_COUNT${_id} * ${vertexCount}];
uniform vec2 noisyPolygon${vertexCount}Centers${_id}[NOISY_POLYGON${vertexCount}_COUNT${_id}];
uniform float noisyPolygon${vertexCount}Lengths${_id}[NOISY_POLYGON${vertexCount}_COUNT${_id}];
uniform float noisyPolygon${vertexCount}Randoms${_id}[NOISY_POLYGON${vertexCount}_COUNT${_id}];
// Other drawables (and other vertex-count variants of this one)
// share these declarations; the include guards keep the combined
@ -45,7 +47,7 @@ export const NoisyPolygonFactory = (
#endif
#endif
vec2 noisyPolygon${vertexCount}LineDistance(vec2 target, vec2 from, vec2 to) {
vec2 noisyPolygon${vertexCount}LineDistance${_id}(vec2 target, vec2 from, vec2 to) {
vec2 targetFromDelta = target - from;
vec2 toFromDelta = to - from;
float h = clamp(
@ -61,18 +63,18 @@ export const NoisyPolygonFactory = (
);
}
float noisyPolygon${vertexCount}MinDistance(vec2 target, out vec4 color) {
float noisyPolygon${vertexCount}MinDistance${_id}(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = 100.0;
for (int j = 0; j < NOISY_POLYGON${vertexCount}_COUNT; j++) {
vec2 startEnd = noisyPolygon${vertexCount}Vertices[j * ${vertexCount}];
for (int j = 0; j < NOISY_POLYGON${vertexCount}_COUNT${_id}; j++) {
vec2 startEnd = noisyPolygon${vertexCount}Vertices${_id}[j * ${vertexCount}];
vec2 vb = startEnd;
vec2 center = noisyPolygon${vertexCount}Centers[j];
float l = noisyPolygon${vertexCount}Lengths[j];
float randomOffset = noisyPolygon${vertexCount}Randoms[j];
vec2 center = noisyPolygon${vertexCount}Centers${_id}[j];
float l = noisyPolygon${vertexCount}Lengths${_id}[j];
float randomOffset = noisyPolygon${vertexCount}Randoms${_id}[j];
vec2 targetCenterDelta = target - center;
float targetDistance = length(targetCenterDelta);
vec2 targetTangent = targetCenterDelta / clamp(targetDistance, 0.01, 1000.0);
@ -88,8 +90,8 @@ export const NoisyPolygonFactory = (
float s = 1.0;
for (int k = 1; k < ${vertexCount}; k++) {
vec2 va = vb;
vb = noisyPolygon${vertexCount}Vertices[j * ${vertexCount} + k];
vec2 ds = noisyPolygon${vertexCount}LineDistance(noisyTarget, va, vb);
vb = noisyPolygon${vertexCount}Vertices${_id}[j * ${vertexCount} + k];
vec2 ds = noisyPolygon${vertexCount}LineDistance${_id}(noisyTarget, va, vb);
bvec3 cond = bvec3(noisyTarget.y >= va.y, noisyTarget.y < vb.y, ds.y > 0.0);
if (all(cond) || all(not(cond))) {
@ -99,7 +101,7 @@ export const NoisyPolygonFactory = (
d = min(d, ds.x);
}
vec2 ds = noisyPolygon${vertexCount}LineDistance(noisyTarget, vb, startEnd);
vec2 ds = noisyPolygon${vertexCount}LineDistance${_id}(noisyTarget, vb, startEnd);
bvec3 cond = bvec3(noisyTarget.y >= vb.y, noisyTarget.y < startEnd.y, ds.y > 0.0);
if (all(cond) || all(not(cond))) {
s *= -1.0;
@ -112,15 +114,15 @@ export const NoisyPolygonFactory = (
return minDistance;
}
`,
distanceFunctionName: `noisyPolygon${vertexCount}MinDistance`,
distanceFunctionName: `noisyPolygon${vertexCount}MinDistance${_id}`,
},
propertyUniformMapping: {
length: `noisyPolygon${vertexCount}Lengths`,
random: `noisyPolygon${vertexCount}Randoms`,
center: `noisyPolygon${vertexCount}Centers`,
vertices: `noisyPolygon${vertexCount}Vertices`,
length: `noisyPolygon${vertexCount}Lengths${_id}`,
random: `noisyPolygon${vertexCount}Randoms${_id}`,
center: `noisyPolygon${vertexCount}Centers${_id}`,
vertices: `noisyPolygon${vertexCount}Vertices${_id}`,
},
uniformCountMacroName: `NOISY_POLYGON${vertexCount}_COUNT`,
uniformCountMacroName: `NOISY_POLYGON${vertexCount}_COUNT${_id}`,
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new NoisyPolygon(
new Array(vertexCount).fill(vec2.create())
@ -183,5 +185,7 @@ export const NoisyPolygonFactory = (
}
}
_id++;
return NoisyPolygon as any;
};

View file

@ -14,6 +14,8 @@ export class PolygonBase extends EmptyDrawable {
}
}
let _id = 0;
/**
* @category Drawable
*/
@ -25,9 +27,9 @@ export const PolygonFactory = (
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 polygon${vertexCount}Vertices[POLYGON${vertexCount}_COUNT * ${vertexCount}];
uniform vec2 polygon${vertexCount}Vertices${_id}[POLYGON${vertexCount}_COUNT${_id} * ${vertexCount}];
vec2 polygon${vertexCount}LineDistance(vec2 target, vec2 from, vec2 to) {
vec2 polygon${vertexCount}LineDistance${_id}(vec2 target, vec2 from, vec2 to) {
vec2 targetFromDelta = target - from;
vec2 toFromDelta = to - from;
float h = clamp(
@ -43,21 +45,21 @@ export const PolygonFactory = (
);
}
float polygon${vertexCount}MinDistance(vec2 target, out vec4 color) {
float polygon${vertexCount}MinDistance${_id}(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = 100.0;
for (int j = 0; j < POLYGON${vertexCount}_COUNT; j++) {
vec2 startEnd = polygon${vertexCount}Vertices[j * ${vertexCount}];
for (int j = 0; j < POLYGON${vertexCount}_COUNT${_id}; j++) {
vec2 startEnd = polygon${vertexCount}Vertices${_id}[j * ${vertexCount}];
vec2 vb = startEnd;
float d = 10000.0;
float s = 1.0;
for (int k = 1; k < ${vertexCount}; k++) {
vec2 va = vb;
vb = polygon${vertexCount}Vertices[j * ${vertexCount} + k];
vec2 ds = polygon${vertexCount}LineDistance(target, va, vb);
vb = polygon${vertexCount}Vertices${_id}[j * ${vertexCount} + k];
vec2 ds = polygon${vertexCount}LineDistance${_id}(target, va, vb);
bvec3 cond = bvec3(target.y >= va.y, target.y < vb.y, ds.y > 0.0);
if (all(cond) || all(not(cond))) {
@ -67,7 +69,7 @@ export const PolygonFactory = (
d = min(d, ds.x);
}
vec2 ds = polygon${vertexCount}LineDistance(target, vb, startEnd);
vec2 ds = polygon${vertexCount}LineDistance${_id}(target, vb, startEnd);
bvec3 cond = bvec3(target.y >= vb.y, target.y < startEnd.y, ds.y > 0.0);
if (all(cond) || all(not(cond))) {
s *= -1.0;
@ -80,13 +82,13 @@ export const PolygonFactory = (
return minDistance;
}
`,
distanceFunctionName: `polygon${vertexCount}MinDistance`,
distanceFunctionName: `polygon${vertexCount}MinDistance${_id}`,
},
propertyUniformMapping: {
vertices: `polygon${vertexCount}Vertices`,
vertices: `polygon${vertexCount}Vertices${_id}`,
},
objectCountScaler: 1 / vertexCount,
uniformCountMacroName: `POLYGON${vertexCount}_COUNT`,
uniformCountMacroName: `POLYGON${vertexCount}_COUNT${_id}`,
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new Polygon(new Array(vertexCount).fill(vec2.create())),
};
@ -146,9 +148,9 @@ export const PolygonFactory = (
private get actualVertices(): Array<vec2> {
return this.vertices.length < vertexCount
? ([
...this.vertices,
...new Array(vertexCount - this.vertices.length).fill(this.vertices[0]),
] as Array<vec2>)
...this.vertices,
...new Array(vertexCount - this.vertices.length).fill(this.vertices[0]),
] as Array<vec2>)
: this.vertices;
}
@ -178,5 +180,7 @@ export const PolygonFactory = (
}
}
_id++;
return Polygon;
};

View file

@ -16,6 +16,8 @@ class RotatedRectangleBase extends EmptyDrawable {
}
}
let _id = 0;
/**
* @category Drawable
*/
@ -27,23 +29,23 @@ export const RotatedRectangleFactory = (
sdf: {
// Source: https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
shader: `
uniform vec2 rotatedRectangleTopCenters[ROTATED_RECTANGLE_COUNT];
uniform vec2 rotatedRectangleBottomCenters[ROTATED_RECTANGLE_COUNT];
uniform float rotatedRectangleWidths[ROTATED_RECTANGLE_COUNT];
uniform vec2 rotatedRectangleTopCenters${_id}[ROTATED_RECTANGLE_COUNT${_id}];
uniform vec2 rotatedRectangleBottomCenters${_id}[ROTATED_RECTANGLE_COUNT${_id}];
uniform float rotatedRectangleWidths${_id}[ROTATED_RECTANGLE_COUNT${_id}];
float rotatedRectangleMinDistance(vec2 target, out vec4 color) {
float rotatedRectangleMinDistance${_id}(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = 1000.0;
for (int i = 0; i < ROTATED_RECTANGLE_COUNT; i++) {
vec2 top = rotatedRectangleTopCenters[i];
vec2 bottom = rotatedRectangleBottomCenters[i];
for (int i = 0; i < ROTATED_RECTANGLE_COUNT${_id}; i++) {
vec2 top = rotatedRectangleTopCenters${_id}[i];
vec2 bottom = rotatedRectangleBottomCenters${_id}[i];
float height = length(bottom - top);
vec2 d = height > 0.00000001 ? (bottom - top) / height : vec2(0.0, 1.0);
vec2 q = (target - (top + bottom) * 0.5);
q = mat2(d.x, -d.y, d.y, d.x) * q;
q = abs(q) - vec2(height, rotatedRectangleWidths[i]) * 0.5;
q = abs(q) - vec2(height, rotatedRectangleWidths${_id}[i]) * 0.5;
float dist = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);
minDistance = min(minDistance, dist);
}
@ -51,20 +53,20 @@ export const RotatedRectangleFactory = (
return minDistance;
}
`,
distanceFunctionName: 'rotatedRectangleMinDistance',
distanceFunctionName: `rotatedRectangleMinDistance${_id}`,
},
propertyUniformMapping: {
topCenter: 'rotatedRectangleTopCenters',
bottomCenter: 'rotatedRectangleBottomCenters',
width: 'rotatedRectangleWidths',
topCenter: `rotatedRectangleTopCenters${_id}`,
bottomCenter: `rotatedRectangleBottomCenters${_id}`,
width: `rotatedRectangleWidths${_id}`,
},
uniformCountMacroName: 'ROTATED_RECTANGLE_COUNT',
uniformCountMacroName: `ROTATED_RECTANGLE_COUNT${_id}`,
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new RotatedRectangle(vec2.create(), vec2.create(), 0),
};
/**
* It is just an estimate by calculating a bounding circle
* This is only an estimate, computed from a bounding circle.
* @param target
*/
public minDistance(target: vec2): number {
@ -91,5 +93,7 @@ export const RotatedRectangleFactory = (
}
}
_id++;
return RotatedRectangle;
};