sdf-2d/src/drawables/shapes/noisy-polygon-factory.ts

178 lines
6.1 KiB
TypeScript

import { mat2d, vec2, vec3, vec4 } from 'gl-matrix';
import { codeForColorAccess } from '../../helper/code-for-color-access';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor';
import { PolygonBase, PolygonFactory } from './polygon-factory';
interface NoisyPolygonBase extends PolygonBase {
randomOffset: number;
}
/**
* @category Drawable
*/
export const NoisyPolygonFactory = (
vertexCount: number,
color: vec3 | vec4 | number
): typeof PolygonBase & NoisyPolygonBase => {
class NoisyPolygon extends PolygonFactory(vertexCount, color) {
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 sampler2D noiseTexture;
#ifdef WEBGL2_IS_AVAILABLE
float myTerrain(vec2 h) {
return texture(noiseTexture, h)[0] - 0.5;
}
#else
float myTerrain(vec2 h) {
return texture2D(noiseTexture, h)[0] - 0.5;
}
#endif
vec2 noisyPolygon${vertexCount}LineDistance(vec2 target, vec2 from, vec2 to) {
vec2 targetFromDelta = target - from;
vec2 toFromDelta = to - from;
float h = clamp(
dot(targetFromDelta, toFromDelta)
/ max(dot(toFromDelta, toFromDelta), 0.00000001),
0.0, 1.0
);
vec2 diff = targetFromDelta - toFromDelta * h;
return vec2(
dot(diff, diff),
toFromDelta.x * targetFromDelta.y - toFromDelta.y * targetFromDelta.x
);
}
float noisyPolygon${vertexCount}MinDistance(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}];
vec2 vb = startEnd;
vec2 center = noisyPolygon${vertexCount}Centers[j];
float l = noisyPolygon${vertexCount}Lengths[j];
float randomOffset = noisyPolygon${vertexCount}Randoms[j];
vec2 targetCenterDelta = target - center;
float targetDistance = length(targetCenterDelta);
vec2 targetTangent = targetCenterDelta / clamp(targetDistance, 0.01, 1000.0);
vec2 noisyTarget = target - (
targetTangent * myTerrain(vec2(
l * abs(atan(targetTangent.y, targetTangent.x)),
randomOffset
)) / 12.0
);
float d = 10000.0;
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);
bvec3 cond = bvec3(noisyTarget.y >= va.y, noisyTarget.y < vb.y, ds.y > 0.0);
if (all(cond) || all(not(cond))) {
s *= -1.0;
}
d = min(d, ds.x);
}
vec2 ds = noisyPolygon${vertexCount}LineDistance(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;
}
d = min(d, ds.x);
minDistance = min(minDistance, s * sqrt(d));
}
return minDistance;
}
`,
distanceFunctionName: `noisyPolygon${vertexCount}MinDistance`,
},
propertyUniformMapping: {
length: `noisyPolygon${vertexCount}Lengths`,
random: `noisyPolygon${vertexCount}Randoms`,
center: `noisyPolygon${vertexCount}Centers`,
vertices: `noisyPolygon${vertexCount}Vertices`,
},
uniformCountMacroName: `NOISY_POLYGON${vertexCount}_COUNT`,
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new NoisyPolygon(
new Array(vertexCount).fill(vec2.create())
) as unknown as Drawable,
};
public randomOffset = 0;
constructor(public vertices: Array<vec2>) {
super(vertices);
}
protected getObjectToSerialize(transform2d: mat2d, _: number): any {
const transformedVertices = (this as any).actualVertices.map((v: vec2) =>
vec2.transformMat2d(vec2.create(), v, transform2d)
);
const center = transformedVertices.reduce(
(sum: vec2, v: vec2) => vec2.add(sum, sum, v),
vec2.create()
);
vec2.scale(center, center, 1 / transformedVertices.length);
let length = 0;
for (let i = 1; i < transformedVertices.length; i++) {
length += vec2.distance(transformedVertices[i - 1], transformedVertices[i]);
}
length += vec2.distance(
transformedVertices[transformedVertices.length - 1],
transformedVertices[0]
);
return {
vertices: transformedVertices,
center,
length,
random: this.randomOffset,
};
}
public serializeToUniforms(
uniforms: any,
transform2d: mat2d,
transform1d: number
): void {
const { propertyUniformMapping } = (this.constructor as typeof Drawable).descriptor;
const serialized = this.getObjectToSerialize(transform2d, transform1d);
Object.entries(propertyUniformMapping).forEach(([k, v]) => {
if (!Object.prototype.hasOwnProperty.call(uniforms, v)) {
uniforms[v] = [];
}
if (k === 'vertices') {
uniforms[v].push(...serialized[k]);
} else {
uniforms[v].push(serialized[k]);
}
});
}
}
return NoisyPolygon as any;
};