Add factories for finer color management
This commit is contained in:
parent
ff6943169b
commit
5ff15fa58f
14 changed files with 644 additions and 391 deletions
|
|
@ -7,7 +7,7 @@ import { Drawable } from './drawable';
|
||||||
* Each [[Drawable]] must have a static property of this type, called descriptor.
|
* 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
|
* 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 {
|
export interface DrawableDescriptor {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
18
src/drawables/empty-drawable.ts
Normal file
18
src/drawables/empty-drawable.ts
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/drawables/shapes/circle-factory.ts
Normal file
61
src/drawables/shapes/circle-factory.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
100
src/drawables/shapes/droplet-factory.ts
Normal file
100
src/drawables/shapes/droplet-factory.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
123
src/drawables/shapes/inverted-tunnel-factory.ts
Normal file
123
src/drawables/shapes/inverted-tunnel-factory.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
62
src/drawables/shapes/meta-circle-factory.ts
Normal file
62
src/drawables/shapes/meta-circle-factory.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
169
src/drawables/shapes/noisy-polygon-factory.ts
Normal file
169
src/drawables/shapes/noisy-polygon-factory.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -2,14 +2,25 @@ import { mat2d, vec2 } from 'gl-matrix';
|
||||||
import { clamp01 } from '../../helper/clamp';
|
import { clamp01 } from '../../helper/clamp';
|
||||||
import { Drawable } from '../drawable';
|
import { Drawable } from '../drawable';
|
||||||
import { DrawableDescriptor } from '../drawable-descriptor';
|
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
|
* @category Drawable
|
||||||
*/
|
*/
|
||||||
export const PolygonFactory = (
|
export const PolygonFactory = (
|
||||||
vertexCount: number
|
vertexCount: number,
|
||||||
): { new (vertices: Array<vec2>): Drawable; descriptor: DrawableDescriptor } => {
|
colorIndex: number
|
||||||
class Polygon extends Drawable {
|
): typeof PolygonBase => {
|
||||||
|
class Polygon extends PolygonBase {
|
||||||
public static descriptor: DrawableDescriptor = {
|
public static descriptor: DrawableDescriptor = {
|
||||||
sdf: {
|
sdf: {
|
||||||
shader: `
|
shader: `
|
||||||
|
|
@ -30,8 +41,9 @@ export const PolygonFactory = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
float polygon${vertexCount}MinDistance(vec2 target, out float colorIndex) {
|
float polygon${vertexCount}MinDistance(vec2 target, out vec4 color) {
|
||||||
colorIndex = 1.0;
|
color = readFromPalette(${colorIndex});
|
||||||
|
|
||||||
float minDistance = 100.0;
|
float minDistance = 100.0;
|
||||||
|
|
||||||
for (int j = 0; j < POLGYON${vertexCount}_COUNT; j++) {
|
for (int j = 0; j < POLGYON${vertexCount}_COUNT; j++) {
|
||||||
|
|
@ -54,7 +66,6 @@ export const PolygonFactory = (
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 ds = polygon${vertexCount}LineDistance(target, vb, startEnd);
|
vec2 ds = polygon${vertexCount}LineDistance(target, vb, startEnd);
|
||||||
|
|
||||||
bvec3 cond = bvec3(target.y >= vb.y, target.y < startEnd.y, ds.y > 0.0);
|
bvec3 cond = bvec3(target.y >= vb.y, target.y < startEnd.y, ds.y > 0.0);
|
||||||
if (all(cond) || all(not(cond))) {
|
if (all(cond) || all(not(cond))) {
|
||||||
s *= -1.0;
|
s *= -1.0;
|
||||||
|
|
@ -78,8 +89,8 @@ export const PolygonFactory = (
|
||||||
empty: new Polygon(new Array(vertexCount).fill(vec2.create())),
|
empty: new Polygon(new Array(vertexCount).fill(vec2.create())),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(public vertices: Array<vec2>) {
|
constructor(vertices: Array<vec2>) {
|
||||||
super();
|
super(vertices);
|
||||||
|
|
||||||
if (vertices.length > vertexCount) {
|
if (vertices.length > vertexCount) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
||||||
91
src/drawables/shapes/rotated-rectangle-factory.ts
Normal file
91
src/drawables/shapes/rotated-rectangle-factory.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue