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) { 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; };