Add factories for finer color management

This commit is contained in:
schmelczerandras 2020-10-13 22:31:50 +02:00
parent ff6943169b
commit 5ff15fa58f
14 changed files with 644 additions and 391 deletions

View file

@ -7,7 +7,7 @@ import { Drawable } from './drawable';
* Each [[Drawable]] must have a static property of this type, called descriptor.
*
* For more information on how to create your own DrawableDescriptor-s, look at the
* code of [[Circle]] or [[InvertedTunnel]].
* code of [[CircleFactory]] or [[InvertedTunnelFactory]].
*/
export interface DrawableDescriptor {
/**

View file

@ -0,0 +1,18 @@
import { mat2d, vec2 } from 'gl-matrix';
import { Drawable } from './drawable';
import { DrawableDescriptor } from './drawable-descriptor';
/**
* @internal
*/
export class EmptyDrawable extends Drawable {
public static readonly descriptor: DrawableDescriptor;
public minDistance(target: vec2): number {
throw new Error('Unimplemented');
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
throw new Error('Unimplemented');
}
}

View file

@ -0,0 +1,61 @@
import { mat2d, vec2, vec3, vec4 } from 'gl-matrix';
import { codeForColorAccess } from '../../helper/code-for-color-access';
import { DrawableDescriptor } from '../drawable-descriptor';
import { EmptyDrawable } from '../empty-drawable';
/**
* @category Drawable
*/
class CircleBase extends EmptyDrawable {
constructor(public center: vec2, public radius: number) {
super();
}
}
/**
* @category Drawable
*/
export const CircleFactory = (color: vec3 | vec4 | number): typeof CircleBase => {
class Circle extends CircleBase {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 circleCenters[CIRCLE_COUNT];
uniform float circleRadii[CIRCLE_COUNT];
float circleMinDistance(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = 1000.0;
for (int i = 0; i < CIRCLE_COUNT; i++) {
float dist = distance(circleCenters[i], target) - circleRadii[i];
minDistance = min(minDistance, dist);
}
return minDistance;
}
`,
distanceFunctionName: 'circleMinDistance',
},
propertyUniformMapping: {
center: 'circleCenters',
radius: 'circleRadii',
},
uniformCountMacroName: 'CIRCLE_COUNT',
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new Circle(vec2.create(), 0),
};
public minDistance(target: vec2): number {
return vec2.dist(this.center, target) - this.radius;
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d),
radius: this.radius * transform1d,
};
}
}
return Circle;
};

View file

@ -1,51 +0,0 @@
import { mat2d, vec2 } from 'gl-matrix';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor';
/**
* @category Drawable
*/
export class Circle extends Drawable {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 circleCenters[CIRCLE_COUNT];
uniform float circleRadii[CIRCLE_COUNT];
float circleMinDistance(vec2 target, out float colorIndex) {
colorIndex = 2.0;
float minDistance = 1000.0;
for (int i = 0; i < CIRCLE_COUNT; i++) {
float dist = distance(circleCenters[i], target) - circleRadii[i];
minDistance = min(minDistance, dist);
}
return minDistance;
}
`,
distanceFunctionName: 'circleMinDistance',
},
propertyUniformMapping: {
center: 'circleCenters',
radius: 'circleRadii',
},
uniformCountMacroName: 'CIRCLE_COUNT',
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new Circle(vec2.create(), 0),
};
constructor(public center: vec2, public radius: number) {
super();
}
public minDistance(target: vec2): number {
return vec2.dist(this.center, target) - this.radius;
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d),
radius: this.radius * transform1d,
};
}
}

View file

@ -0,0 +1,100 @@
import { mat2d, vec2, vec3, vec4 } from 'gl-matrix';
import { clamp01 } from '../../helper/clamp';
import { codeForColorAccess } from '../../helper/code-for-color-access';
import { mix } from '../../helper/mix';
import { DrawableDescriptor } from '../drawable-descriptor';
import { EmptyDrawable } from '../empty-drawable';
/**
* @category Drawable
*/
class DropletBase extends EmptyDrawable {
constructor(
public readonly from: vec2,
public readonly to: vec2,
public readonly fromRadius: number,
public readonly toRadius: number
) {
super();
}
}
/**
* @category Drawable
*/
export const DropletFactory = (color: vec3 | vec4 | number): typeof DropletBase => {
class Droplet extends 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];
float dropletMinDistance(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];
float h = clamp(
dot(targetFromDelta, toFromDeltas[i])
/ dot(toFromDeltas[i], toFromDeltas[i]),
0.0, 1.0
);
float currentDistance = -mix(
fromRadii[i], toRadii[i], h
) + distance(
targetFromDelta, toFromDeltas[i] * h
);
minDistance = min(minDistance, currentDistance);
}
return minDistance;
}
`,
distanceFunctionName: 'dropletMinDistance',
},
propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
},
uniformCountMacroName: 'DROPLET_COUNT',
shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new Droplet(vec2.create(), vec2.create(), 0, 0),
};
public minDistance(target: vec2): number {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01(
vec2.dot(targetFromDelta, toFromDelta) / vec2.dot(toFromDelta, toFromDelta)
);
return (
vec2.distance(targetFromDelta, vec2.scale(vec2.create(), toFromDelta, h)) -
mix(this.fromRadius, this.toRadius, h)
);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
return {
from: vec2.transformMat2d(vec2.create(), this.from, transform2d),
toFromDelta: vec2.scale(vec2.create(), toFromDelta, transform1d),
fromRadius: this.fromRadius * transform1d,
toRadius: this.toRadius * transform1d,
};
}
}
return Droplet;
};

View file

@ -1,90 +0,0 @@
import { mat2d, vec2 } from 'gl-matrix';
import { clamp01 } from '../../helper/clamp';
import { mix } from '../../helper/mix';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor';
/**
* @category Drawable
*/
export class Droplet extends Drawable {
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];
float dropletMinDistance(vec2 target, out float colorIndex) {
colorIndex = 1.0;
float minDistance = 1000.0;
for (int i = 0; i < DROPLET_COUNT; i++) {
vec2 targetFromDelta = target - froms[i];
float h = clamp(
dot(targetFromDelta, toFromDeltas[i])
/ dot(toFromDeltas[i], toFromDeltas[i]),
0.0, 1.0
);
float currentDistance = -mix(
fromRadii[i], toRadii[i], h
) + distance(
targetFromDelta, toFromDeltas[i] * h
);
minDistance = min(minDistance, currentDistance);
}
return minDistance;
}
`,
distanceFunctionName: 'dropletMinDistance',
},
propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
},
uniformCountMacroName: 'DROPLET_COUNT',
shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new Droplet(vec2.create(), vec2.create(), 0, 0),
};
constructor(
public readonly from: vec2,
public readonly to: vec2,
public readonly fromRadius: number,
public readonly toRadius: number
) {
super();
}
public minDistance(target: vec2): number {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01(
vec2.dot(targetFromDelta, toFromDelta) / vec2.dot(toFromDelta, toFromDelta)
);
return (
vec2.distance(targetFromDelta, vec2.scale(vec2.create(), toFromDelta, h)) -
mix(this.fromRadius, this.toRadius, h)
);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
return {
from: vec2.transformMat2d(vec2.create(), this.from, transform2d),
toFromDelta: vec2.scale(vec2.create(), toFromDelta, transform1d),
fromRadius: this.fromRadius * transform1d,
toRadius: this.toRadius * transform1d,
};
}
}

View file

@ -0,0 +1,123 @@
import { mat2d, vec2, vec3, vec4 } from 'gl-matrix';
import { clamp01 } from '../../helper/clamp';
import { codeForColorAccess } from '../../helper/code-for-color-access';
import { mix } from '../../helper/mix';
import { DrawableDescriptor } from '../drawable-descriptor';
import { EmptyDrawable } from '../empty-drawable';
/**
* @category Drawable
*/
class InvertedTunnelBase extends EmptyDrawable {
constructor(
public readonly from: vec2,
public readonly to: vec2,
public readonly fromRadius: number,
public readonly toRadius: number
) {
super();
}
}
/**
* Providing a noise texture is required for this drawable.
*
* @category Drawable
*/
export const InvertedTunnelFactory = (
color: vec3 | vec4 | number
): typeof InvertedTunnelBase => {
class InvertedTunnel extends InvertedTunnelBase {
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 sampler2D noiseTexture;
#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
float invertedTunnelMinDistance(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float minDistance = -1000.0;
for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) {
vec2 targetFromDelta = target - froms[i];
float h = dot(targetFromDelta, toFromDeltas[i])
/ dot(toFromDeltas[i], toFromDeltas[i]);
float clampedH = clamp(h, 0.0, 1.0);
float currentDistance = -mix(
fromRadii[i], toRadii[i], clampedH
) + distance(
targetFromDelta, toFromDeltas[i] * clampedH
) - invertedTunnelTerrain(h) / 12.0;
minDistance = max(minDistance, -currentDistance);
}
return minDistance;
}
`,
isInverted: true,
distanceFunctionName: 'invertedTunnelMinDistance',
},
propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
},
uniformCountMacroName: 'INVERTED_TUNNEL_COUNT',
shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new InvertedTunnel(vec2.create(), vec2.create(), 0, 0),
};
public minDistance(target: vec2): number {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01(
vec2.dot(targetFromDelta, toFromDelta) / vec2.dot(toFromDelta, toFromDelta)
);
return (
vec2.distance(targetFromDelta, vec2.scale(vec2.create(), toFromDelta, h)) -
mix(this.fromRadius, this.toRadius, h)
);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
return {
from: vec2.transformMat2d(vec2.create(), this.from, transform2d),
toFromDelta: vec2.scale(vec2.create(), toFromDelta, transform1d),
fromRadius: this.fromRadius * transform1d,
toRadius: this.toRadius * transform1d,
};
}
}
return InvertedTunnel;
};

View file

@ -1,111 +0,0 @@
import { mat2d, vec2 } from 'gl-matrix';
import { clamp01 } from '../../helper/clamp';
import { mix } from '../../helper/mix';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor';
/**
* @category Drawable
*
* Providing a noise texture is required for this drawable.
*/
export class InvertedTunnel extends Drawable {
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 sampler2D noiseTexture;
#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
float invertedTunnelMinDistance(vec2 target, out float colorIndex) {
colorIndex = 3.0;
float minDistance = -1000.0;
for (int i = 0; i < INVERTED_TUNNEL_COUNT; i++) {
vec2 targetFromDelta = target - froms[i];
float h = dot(targetFromDelta, toFromDeltas[i])
/ dot(toFromDeltas[i], toFromDeltas[i]);
float clampedH = clamp(h, 0.0, 1.0);
float currentDistance = -mix(
fromRadii[i], toRadii[i], clampedH
) + distance(
targetFromDelta, toFromDeltas[i] * clampedH
) - invertedTunnelTerrain(h) / 12.0;
minDistance = max(minDistance, -currentDistance);
}
return minDistance;
}
`,
isInverted: true,
distanceFunctionName: 'invertedTunnelMinDistance',
},
propertyUniformMapping: {
from: 'froms',
toFromDelta: 'toFromDeltas',
fromRadius: 'fromRadii',
toRadius: 'toRadii',
},
uniformCountMacroName: 'INVERTED_TUNNEL_COUNT',
shaderCombinationSteps: [0, 1, 4, 16, 32],
empty: new InvertedTunnel(vec2.create(), vec2.create(), 0, 0),
};
constructor(
public readonly from: vec2,
public readonly to: vec2,
public readonly fromRadius: number,
public readonly toRadius: number
) {
super();
}
public minDistance(target: vec2): number {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01(
vec2.dot(targetFromDelta, toFromDelta) / vec2.dot(toFromDelta, toFromDelta)
);
return (
vec2.distance(targetFromDelta, vec2.scale(vec2.create(), toFromDelta, h)) -
mix(this.fromRadius, this.toRadius, h)
);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
return {
from: vec2.transformMat2d(vec2.create(), this.from, transform2d),
toFromDelta: vec2.scale(vec2.create(), toFromDelta, transform1d),
fromRadius: this.fromRadius * transform1d,
toRadius: this.toRadius * transform1d,
};
}
}

View file

@ -0,0 +1,62 @@
import { mat2d, vec2, vec3, vec4 } from 'gl-matrix';
import { codeForColorAccess } from '../../helper/code-for-color-access';
import { DrawableDescriptor } from '../drawable-descriptor';
import { EmptyDrawable } from '../empty-drawable';
/**
* @category Drawable
*/
class MetaCircleBase extends EmptyDrawable {
constructor(public center: vec2, public radius: number) {
super();
}
}
/**
* @category Drawable
*/
export const MetaCircleFactory = (color: vec3 | vec4 | number): typeof MetaCircleBase => {
class MetaCircle extends MetaCircleBase {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 metaCircleCenters[META_CIRCLE_COUNT];
uniform float metaCircleRadii[META_CIRCLE_COUNT];
const float k = 32.0;
float metaCircleMinDistance(vec2 target, out vec4 color) {
color = ${codeForColorAccess(color)};
float res = 0.0;
for (int i = 0; i < META_CIRCLE_COUNT; i++) {
float dist = distance(metaCircleCenters[i], target) - metaCircleRadii[i];
res += exp2(-k * dist);
}
return -log2(res) / k;
}
`,
distanceFunctionName: 'metaCircleMinDistance',
},
propertyUniformMapping: {
center: 'metaCircleCenters',
radius: 'metaCircleRadii',
},
uniformCountMacroName: 'META_CIRCLE_COUNT',
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new MetaCircle(vec2.create(), 0),
};
public minDistance(target: vec2): number {
return vec2.dist(this.center, target) - this.radius * 2;
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d),
radius: this.radius * transform1d,
};
}
}
return MetaCircle;
};

View file

@ -1,52 +0,0 @@
import { mat2d, vec2 } from 'gl-matrix';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor';
/**
* @category Drawable
*/
export class MetaCircle extends Drawable {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 metaCircleCenters[META_CIRCLE_COUNT];
uniform float metaCircleRadii[META_CIRCLE_COUNT];
const float k = 32.0;
float metaCircleMinDistance(vec2 target, out float colorIndex) {
colorIndex = 5.0;
float res = 0.0;
for (int i = 0; i < META_CIRCLE_COUNT; i++) {
float dist = distance(metaCircleCenters[i], target) - metaCircleRadii[i];
res += exp2(-k * dist);
}
return -log2(res) / k;
}
`,
distanceFunctionName: 'metaCircleMinDistance',
},
propertyUniformMapping: {
center: 'metaCircleCenters',
radius: 'metaCircleRadii',
},
uniformCountMacroName: 'META_CIRCLE_COUNT',
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new MetaCircle(vec2.create(), 0),
};
constructor(public center: vec2, public radius: number) {
super();
}
public minDistance(target: vec2): number {
return vec2.dist(this.center, target) - this.radius * 2;
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
center: vec2.transformMat2d(vec2.create(), this.center, transform2d),
radius: this.radius * transform1d,
};
}
}

View file

@ -0,0 +1,169 @@
import { mat2d, vec2 } from 'gl-matrix';
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,
colorIndex: number
): typeof PolygonBase & NoisyPolygonBase => {
class NoisyPolygon extends PolygonFactory(vertexCount, colorIndex) {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform vec2 noisyPolygon${vertexCount}Vertices[NOISY_POLGYON${vertexCount}_COUNT * ${vertexCount}];
uniform vec2 noisyPolygon${vertexCount}Centers[NOISY_POLGYON${vertexCount}_COUNT];
uniform float noisyPolygon${vertexCount}Lengths[NOISY_POLGYON${vertexCount}_COUNT];
uniform float noisyPolygon${vertexCount}Randoms[NOISY_POLGYON${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) / dot(toFromDelta, toFromDelta),
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 = readFromPalette(${colorIndex});
float minDistance = 100.0;
for (int j = 0; j < NOISY_POLGYON${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 targetTangent = normalize(target - center);
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: {
vertices: `noisyPolygon${vertexCount}Vertices`,
length: `noisyPolygon${vertexCount}Lengths`,
random: `noisyPolygon${vertexCount}Randoms`,
center: `noisyPolygon${vertexCount}Centers`,
},
objectCountScaler: 1 / vertexCount,
uniformCountMacroName: `NOISY_POLGYON${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, transform1d: 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 < this.vertices.length; i++) {
length += vec2.distance(transformedVertices[i - 1], transformedVertices[i]);
}
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;
};

View file

@ -2,14 +2,25 @@ import { mat2d, vec2 } from 'gl-matrix';
import { clamp01 } from '../../helper/clamp';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor';
import { EmptyDrawable } from '../empty-drawable';
/**
* @category Drawable
*/
export class PolygonBase extends EmptyDrawable {
constructor(public vertices: Array<vec2>) {
super();
}
}
/**
* @category Drawable
*/
export const PolygonFactory = (
vertexCount: number
): { new (vertices: Array<vec2>): Drawable; descriptor: DrawableDescriptor } => {
class Polygon extends Drawable {
vertexCount: number,
colorIndex: number
): typeof PolygonBase => {
class Polygon extends PolygonBase {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
@ -30,8 +41,9 @@ export const PolygonFactory = (
);
}
float polygon${vertexCount}MinDistance(vec2 target, out float colorIndex) {
colorIndex = 1.0;
float polygon${vertexCount}MinDistance(vec2 target, out vec4 color) {
color = readFromPalette(${colorIndex});
float minDistance = 100.0;
for (int j = 0; j < POLGYON${vertexCount}_COUNT; j++) {
@ -54,7 +66,6 @@ export const PolygonFactory = (
}
vec2 ds = polygon${vertexCount}LineDistance(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;
@ -78,8 +89,8 @@ export const PolygonFactory = (
empty: new Polygon(new Array(vertexCount).fill(vec2.create())),
};
constructor(public vertices: Array<vec2>) {
super();
constructor(vertices: Array<vec2>) {
super(vertices);
if (vertices.length > vertexCount) {
throw new Error(

View file

@ -0,0 +1,91 @@
import { mat2d, vec2, vec3, vec4 } from 'gl-matrix';
import { codeForColorAccess } from '../../helper/code-for-color-access';
import { DrawableDescriptor } from '../drawable-descriptor';
import { EmptyDrawable } from '../empty-drawable';
/**
* @category Drawable
*/
class RotatedRectangleBase extends EmptyDrawable {
constructor(public center: vec2, public size: vec2, public rotation: number) {
super();
}
}
/**
* @category Drawable
*/
export const RotatedRectangleFactory = (
color: vec3 | vec4 | number
): typeof RotatedRectangleBase => {
class RotatedRectangle extends RotatedRectangleBase {
public static descriptor: DrawableDescriptor = {
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];
float rotatedRectangleMinDistance(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];
float height = length(bottom - top);
vec2 d = (bottom - top) / height;
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;
float dist = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);
minDistance = min(minDistance, dist);
}
return minDistance;
}
`,
distanceFunctionName: 'rotatedRectangleMinDistance',
},
propertyUniformMapping: {
topCenter: 'rotatedRectangleTopCenters',
bottomCenter: 'rotatedRectangleBottomCenters',
width: 'rotatedRectangleWidths',
},
uniformCountMacroName: 'ROTATED_RECTANGLE_COUNT',
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
* @param target
*/
public minDistance(target: vec2): number {
return vec2.distance(this.center, target) - vec2.length(this.size) / 2;
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
const rotation = mat2d.fromRotation(mat2d.create(), this.rotation);
const halfHeight = vec2.fromValues(0, this.size.y / 2);
vec2.transformMat2d(halfHeight, halfHeight, rotation);
const topCenter = vec2.add(vec2.create(), this.center, halfHeight);
vec2.transformMat2d(topCenter, topCenter, transform2d);
const bottomCenter = vec2.subtract(vec2.create(), this.center, halfHeight);
vec2.transformMat2d(bottomCenter, bottomCenter, transform2d);
return {
topCenter,
bottomCenter,
width: this.size.x * transform1d,
};
}
}
return RotatedRectangle;
};

View file

@ -1,78 +0,0 @@
import { mat2d, vec2 } from 'gl-matrix';
import { Drawable } from '../drawable';
import { DrawableDescriptor } from '../drawable-descriptor';
/**
* @category Drawable
*/
export class RotatedRectangle extends Drawable {
public static descriptor: DrawableDescriptor = {
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];
float rotatedRectangleMinDistance(vec2 target, out float colorIndex) {
colorIndex = 4.0;
float minDistance = 1000.0;
for (int i = 0; i < ROTATED_RECTANGLE_COUNT; i++) {
vec2 top = rotatedRectangleTopCenters[i];
vec2 bottom = rotatedRectangleBottomCenters[i];
float height = length(bottom - top);
vec2 d = (bottom - top) / height;
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;
float dist = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);
minDistance = min(minDistance, dist);
}
return minDistance;
}
`,
distanceFunctionName: 'rotatedRectangleMinDistance',
},
propertyUniformMapping: {
topCenter: 'rotatedRectangleTopCenters',
bottomCenter: 'rotatedRectangleBottomCenters',
width: 'rotatedRectangleWidths',
},
uniformCountMacroName: 'ROTATED_RECTANGLE_COUNT',
shaderCombinationSteps: [0, 1, 2, 3, 8, 16],
empty: new RotatedRectangle(vec2.create(), vec2.create(), 0),
};
constructor(public center: vec2, public size: vec2, public rotation: number) {
super();
}
/**
* It is just an estimate by calculating a bounding circle
* @param target
*/
public minDistance(target: vec2): number {
return vec2.distance(this.center, target) - vec2.length(this.size) / 2;
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
const rotation = mat2d.fromRotation(mat2d.create(), this.rotation);
const halfHeight = vec2.fromValues(0, this.size.y / 2);
vec2.transformMat2d(halfHeight, halfHeight, rotation);
const topCenter = vec2.add(vec2.create(), this.center, halfHeight);
vec2.transformMat2d(topCenter, topCenter, transform2d);
const bottomCenter = vec2.subtract(vec2.create(), this.center, halfHeight);
vec2.transformMat2d(bottomCenter, bottomCenter, transform2d);
return {
topCenter,
bottomCenter,
width: this.size.x * transform1d,
};
}
}