@matboks/utilities 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/geometry/aabb.d.ts +40 -0
- package/dist/geometry/aabb.js +143 -0
- package/dist/geometry/cut-polygon.d.ts +7 -0
- package/dist/geometry/cut-polygon.js +116 -0
- package/dist/geometry/hollowgon.d.ts +24 -0
- package/dist/geometry/hollowgon.js +156 -0
- package/dist/geometry/index.d.ts +13 -0
- package/dist/geometry/index.js +14 -0
- package/dist/geometry/line.d.ts +10 -0
- package/dist/geometry/line.js +26 -0
- package/dist/geometry/polygon-operations.d.ts +31 -0
- package/dist/geometry/polygon-operations.js +159 -0
- package/dist/geometry/polygon.d.ts +55 -0
- package/dist/geometry/polygon.js +353 -0
- package/dist/geometry/polyline.d.ts +23 -0
- package/dist/geometry/polyline.js +100 -0
- package/dist/geometry/ray.d.ts +6 -0
- package/dist/geometry/ray.js +6 -0
- package/dist/geometry/ray3.d.ts +7 -0
- package/dist/geometry/ray3.js +10 -0
- package/dist/geometry/segment.d.ts +16 -0
- package/dist/geometry/segment.js +50 -0
- package/dist/geometry/shared.d.ts +5 -0
- package/dist/geometry/shared.js +60 -0
- package/dist/geometry/transformations.d.ts +7 -0
- package/dist/geometry/transformations.js +28 -0
- package/dist/geometry/vec2.d.ts +71 -0
- package/dist/geometry/vec2.js +225 -0
- package/dist/geometry/vec3.d.ts +102 -0
- package/dist/geometry/vec3.js +256 -0
- package/dist/geometry/vec4.d.ts +71 -0
- package/dist/geometry/vec4.js +238 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/math/clamp.d.ts +1 -0
- package/dist/math/clamp.js +5 -0
- package/dist/math/fract.d.ts +1 -0
- package/dist/math/fract.js +3 -0
- package/dist/math/index.d.ts +8 -0
- package/dist/math/index.js +8 -0
- package/dist/math/mix.d.ts +1 -0
- package/dist/math/mix.js +3 -0
- package/dist/math/mod.d.ts +1 -0
- package/dist/math/mod.js +5 -0
- package/dist/math/signed.d.ts +1 -0
- package/dist/math/signed.js +3 -0
- package/dist/math/smoothstep.d.ts +3 -0
- package/dist/math/smoothstep.js +24 -0
- package/dist/math/split-into-int-and-fract.d.ts +1 -0
- package/dist/math/split-into-int-and-fract.js +5 -0
- package/dist/math/units.d.ts +2 -0
- package/dist/math/units.js +6 -0
- package/dist/noise/idw.d.ts +11 -0
- package/dist/noise/idw.js +10 -0
- package/dist/noise/index.d.ts +6 -0
- package/dist/noise/index.js +6 -0
- package/dist/noise/perlin.d.ts +5 -0
- package/dist/noise/perlin.js +29 -0
- package/dist/noise/random.d.ts +13 -0
- package/dist/noise/random.js +39 -0
- package/dist/noise/value.d.ts +4 -0
- package/dist/noise/value.js +20 -0
- package/dist/noise/voronoise.d.ts +10 -0
- package/dist/noise/voronoise.js +26 -0
- package/dist/noise/worley.d.ts +4 -0
- package/dist/noise/worley.js +39 -0
- package/dist/shader-modules/index.d.ts +2 -0
- package/dist/shader-modules/index.js +2 -0
- package/dist/shader-modules/library.d.ts +2 -0
- package/dist/shader-modules/library.js +36 -0
- package/dist/shader-modules/modules/camera.d.ts +1 -0
- package/dist/shader-modules/modules/camera.js +43 -0
- package/dist/shader-modules/modules/color/blend.d.ts +1 -0
- package/dist/shader-modules/modules/color/blend.js +108 -0
- package/dist/shader-modules/modules/color/index.d.ts +1 -0
- package/dist/shader-modules/modules/color/index.js +135 -0
- package/dist/shader-modules/modules/constants.d.ts +1 -0
- package/dist/shader-modules/modules/constants.js +14 -0
- package/dist/shader-modules/modules/geometry.d.ts +1 -0
- package/dist/shader-modules/modules/geometry.js +110 -0
- package/dist/shader-modules/modules/math.d.ts +1 -0
- package/dist/shader-modules/modules/math.js +19 -0
- package/dist/shader-modules/modules/noise.d.ts +1 -0
- package/dist/shader-modules/modules/noise.js +410 -0
- package/dist/shader-modules/modules/random.d.ts +1 -0
- package/dist/shader-modules/modules/random.js +147 -0
- package/dist/shader-modules/modules/ray-marching.d.ts +1 -0
- package/dist/shader-modules/modules/ray-marching.js +54 -0
- package/dist/shader-modules/modules/sdf/index.d.ts +1 -0
- package/dist/shader-modules/modules/sdf/index.js +183 -0
- package/dist/shader-modules/modules/sdf/operations.d.ts +1 -0
- package/dist/shader-modules/modules/sdf/operations.js +77 -0
- package/dist/shader-modules/modules/utils.d.ts +1 -0
- package/dist/shader-modules/modules/utils.js +115 -0
- package/dist/shader-modules/registry.d.ts +2 -0
- package/dist/shader-modules/registry.js +7 -0
- package/dist/shader-modules/shaders.d.ts +1 -0
- package/dist/shader-modules/shaders.js +109 -0
- package/dist/shader-renderer/helpers.d.ts +10 -0
- package/dist/shader-renderer/helpers.js +19 -0
- package/dist/shader-renderer/index.d.ts +2 -0
- package/dist/shader-renderer/index.js +2 -0
- package/dist/shader-renderer/pixel-shader.d.ts +32 -0
- package/dist/shader-renderer/pixel-shader.js +172 -0
- package/dist/shader-renderer/pixel-vertex-shader.d.ts +1 -0
- package/dist/shader-renderer/pixel-vertex-shader.js +14 -0
- package/dist/shader-renderer/texture.d.ts +28 -0
- package/dist/shader-renderer/texture.js +97 -0
- package/dist/shader-renderer/types.d.ts +17 -0
- package/dist/shader-renderer/types.js +4 -0
- package/dist/shader-renderer/uniform.d.ts +16 -0
- package/dist/shader-renderer/uniform.js +134 -0
- package/dist/tilings/delaunay.d.ts +11 -0
- package/dist/tilings/delaunay.js +28 -0
- package/dist/tilings/grid.d.ts +5 -0
- package/dist/tilings/grid.js +29 -0
- package/dist/tilings/hexagononal.d.ts +4 -0
- package/dist/tilings/hexagononal.js +40 -0
- package/dist/tilings/index.d.ts +12 -0
- package/dist/tilings/index.js +12 -0
- package/dist/tilings/line.d.ts +5 -0
- package/dist/tilings/line.js +19 -0
- package/dist/tilings/mediterranean.d.ts +3 -0
- package/dist/tilings/mediterranean.js +49 -0
- package/dist/tilings/pythagorean.d.ts +3 -0
- package/dist/tilings/pythagorean.js +40 -0
- package/dist/tilings/random-cuts.d.ts +6 -0
- package/dist/tilings/random-cuts.js +16 -0
- package/dist/tilings/recursive-cuts.d.ts +5 -0
- package/dist/tilings/recursive-cuts.js +11 -0
- package/dist/tilings/rhombille.d.ts +3 -0
- package/dist/tilings/rhombille.js +16 -0
- package/dist/tilings/rightangled-triangle.d.ts +3 -0
- package/dist/tilings/rightangled-triangle.js +29 -0
- package/dist/tilings/triangle.d.ts +3 -0
- package/dist/tilings/triangle.js +17 -0
- package/dist/tilings/voronoi.d.ts +11 -0
- package/dist/tilings/voronoi.js +29 -0
- package/dist/tilings/weaving.d.ts +1 -0
- package/dist/tilings/weaving.js +8 -0
- package/dist/transforms/camera/camera.d.ts +32 -0
- package/dist/transforms/camera/camera.js +60 -0
- package/dist/transforms/camera/index.d.ts +3 -0
- package/dist/transforms/camera/index.js +3 -0
- package/dist/transforms/camera/orthographic.d.ts +15 -0
- package/dist/transforms/camera/orthographic.js +31 -0
- package/dist/transforms/camera/perspective.d.ts +16 -0
- package/dist/transforms/camera/perspective.js +40 -0
- package/dist/transforms/index.d.ts +2 -0
- package/dist/transforms/index.js +2 -0
- package/dist/transforms/normalize-transform.d.ts +12 -0
- package/dist/transforms/normalize-transform.js +20 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/dist/utilities/create-frame.d.ts +2 -0
- package/dist/utilities/create-frame.js +6 -0
- package/dist/utilities/ensure-array.d.ts +1 -0
- package/dist/utilities/ensure-array.js +3 -0
- package/dist/utilities/idw-interpolator.d.ts +9 -0
- package/dist/utilities/idw-interpolator.js +18 -0
- package/dist/utilities/index.d.ts +8 -0
- package/dist/utilities/index.js +8 -0
- package/dist/utilities/marching-squares.d.ts +51 -0
- package/dist/utilities/marching-squares.js +177 -0
- package/dist/utilities/point-sampler.d.ts +12 -0
- package/dist/utilities/point-sampler.js +24 -0
- package/dist/utilities/poisson/grid.d.ts +21 -0
- package/dist/utilities/poisson/grid.js +51 -0
- package/dist/utilities/poisson/poissonnier.d.ts +50 -0
- package/dist/utilities/poisson/poissonnier.js +118 -0
- package/dist/utilities/resolution.d.ts +10 -0
- package/dist/utilities/resolution.js +14 -0
- package/dist/utilities/rng.d.ts +13 -0
- package/dist/utilities/rng.js +80 -0
- package/package.json +28 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Resolution } from "../utilities/resolution.js";
|
|
2
|
+
import { parseShaderUniforms } from "./helpers.js";
|
|
3
|
+
import { pixelVertexShader } from "./pixel-vertex-shader.js";
|
|
4
|
+
import { ShaderTexture } from "./texture.js";
|
|
5
|
+
import { UniformHandler } from "./uniform.js";
|
|
6
|
+
export class PixelShader {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.textures = {};
|
|
9
|
+
this.textureUnitCounter = 0;
|
|
10
|
+
let { fragmentShader, resolution = new Resolution(100, 100), attributes, gl: customGL } = options;
|
|
11
|
+
let canvas;
|
|
12
|
+
let gl;
|
|
13
|
+
let programData;
|
|
14
|
+
if (customGL) {
|
|
15
|
+
canvas = customGL.canvas;
|
|
16
|
+
resolution = new Resolution(canvas.width, canvas.height);
|
|
17
|
+
({ gl, programData } = setupWebGL(customGL, pixelVertexShader, fragmentShader, attributes));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
canvas = new OffscreenCanvas(resolution.width, resolution.height);
|
|
21
|
+
({ gl, programData } = setupWebGL(canvas, pixelVertexShader, fragmentShader, attributes));
|
|
22
|
+
}
|
|
23
|
+
this.resolution = resolution;
|
|
24
|
+
this.canvas = canvas;
|
|
25
|
+
this.gl = gl;
|
|
26
|
+
this.programData = programData;
|
|
27
|
+
this.uniforms = new UniformHandler(gl, programData.uniforms);
|
|
28
|
+
}
|
|
29
|
+
setUniform(...args) {
|
|
30
|
+
this.ensureCurrentProgram();
|
|
31
|
+
let data;
|
|
32
|
+
if (args.length === 2) {
|
|
33
|
+
data = { [args[0]]: args[1] };
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
data = args[0];
|
|
37
|
+
}
|
|
38
|
+
for (const name in data) {
|
|
39
|
+
this.uniforms.setUniform(name, data[name]);
|
|
40
|
+
}
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
createTexture(name, spec) {
|
|
44
|
+
const tex = new ShaderTexture(this.gl, spec);
|
|
45
|
+
this.textures[name] = tex;
|
|
46
|
+
return tex;
|
|
47
|
+
}
|
|
48
|
+
setTextureData(name, width, height, data) {
|
|
49
|
+
const tex = this.textures[name];
|
|
50
|
+
if (!tex)
|
|
51
|
+
throw new Error(`Texture ${name} not found`);
|
|
52
|
+
tex.setData(width, height, data);
|
|
53
|
+
}
|
|
54
|
+
updateTextureSpec(name, partial) {
|
|
55
|
+
const tex = this.textures[name];
|
|
56
|
+
if (!tex)
|
|
57
|
+
throw new Error(`Texture ${name} not found`);
|
|
58
|
+
tex.updateSpec(partial);
|
|
59
|
+
}
|
|
60
|
+
ensureCurrentProgram() {
|
|
61
|
+
const { program, vao } = this.programData;
|
|
62
|
+
this.gl.useProgram(program);
|
|
63
|
+
this.gl.bindVertexArray(vao);
|
|
64
|
+
}
|
|
65
|
+
render(target) {
|
|
66
|
+
const { gl, programData, textures, resolution: { width, height } } = this;
|
|
67
|
+
gl.viewport(0, 0, width, height);
|
|
68
|
+
gl.clearColor(0, 0, 0, 0);
|
|
69
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
70
|
+
this.ensureCurrentProgram();
|
|
71
|
+
let unit = 0;
|
|
72
|
+
for (const name in textures) {
|
|
73
|
+
const tex = textures[name];
|
|
74
|
+
const uniform = programData.uniforms[name];
|
|
75
|
+
if (!uniform)
|
|
76
|
+
continue;
|
|
77
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
78
|
+
gl.bindTexture(gl.TEXTURE_2D, tex.texture);
|
|
79
|
+
gl.uniform1i(uniform.location, unit);
|
|
80
|
+
unit++;
|
|
81
|
+
}
|
|
82
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
83
|
+
let ctx = null;
|
|
84
|
+
if (target instanceof HTMLCanvasElement || target instanceof OffscreenCanvas) {
|
|
85
|
+
ctx = target.getContext("2d");
|
|
86
|
+
}
|
|
87
|
+
else if (target instanceof CanvasRenderingContext2D || target instanceof OffscreenCanvasRenderingContext2D) {
|
|
88
|
+
ctx = target;
|
|
89
|
+
}
|
|
90
|
+
if (ctx) {
|
|
91
|
+
const transform = ctx.getTransform();
|
|
92
|
+
ctx.resetTransform();
|
|
93
|
+
ctx.drawImage(this.canvas, 0, 0);
|
|
94
|
+
ctx.setTransform(transform);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
resize(resolution) {
|
|
98
|
+
const { resolution: currentResolution } = this;
|
|
99
|
+
if (currentResolution.width === resolution.width && currentResolution.height === resolution.height)
|
|
100
|
+
return;
|
|
101
|
+
this.resolution = resolution;
|
|
102
|
+
this.canvas.width = resolution.width;
|
|
103
|
+
this.canvas.height = resolution.height;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export function setupWebGL(target, vertexShaderDefinition, fragmentShaderDefinition, attributes) {
|
|
107
|
+
let gl;
|
|
108
|
+
if (target instanceof WebGL2RenderingContext) {
|
|
109
|
+
gl = target;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
gl = target.getContext("webgl2", attributes);
|
|
113
|
+
}
|
|
114
|
+
if (!gl)
|
|
115
|
+
throw new Error("Unable to create WebGL2 context");
|
|
116
|
+
const program = createProgram(gl, vertexShaderDefinition, fragmentShaderDefinition);
|
|
117
|
+
const positionAttribLocation = gl.getAttribLocation(program, "p");
|
|
118
|
+
const positionBuffer = gl.createBuffer();
|
|
119
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
120
|
+
const positionData = new Float32Array([
|
|
121
|
+
0, 0,
|
|
122
|
+
1, 0,
|
|
123
|
+
1, 1,
|
|
124
|
+
0, 0,
|
|
125
|
+
1, 1,
|
|
126
|
+
0, 1
|
|
127
|
+
]);
|
|
128
|
+
gl.bufferData(gl.ARRAY_BUFFER, positionData, gl.STATIC_DRAW);
|
|
129
|
+
const vao = gl.createVertexArray();
|
|
130
|
+
gl.bindVertexArray(vao);
|
|
131
|
+
gl.enableVertexAttribArray(positionAttribLocation);
|
|
132
|
+
gl.vertexAttribPointer(positionAttribLocation, 2, gl.FLOAT, false, 0, 0);
|
|
133
|
+
gl.useProgram(program);
|
|
134
|
+
const parsedUniforms = parseShaderUniforms(fragmentShaderDefinition);
|
|
135
|
+
const uniformMap = {};
|
|
136
|
+
for (const u of parsedUniforms) {
|
|
137
|
+
const location = gl.getUniformLocation(program, u.name);
|
|
138
|
+
uniformMap[u.name] = { ...u, location };
|
|
139
|
+
}
|
|
140
|
+
const programData = {
|
|
141
|
+
program,
|
|
142
|
+
vao,
|
|
143
|
+
uniforms: uniformMap
|
|
144
|
+
};
|
|
145
|
+
return { gl, programData };
|
|
146
|
+
}
|
|
147
|
+
function createProgram(gl, vertexShaderDefinition, fragmentShaderDefinition) {
|
|
148
|
+
const program = gl.createProgram();
|
|
149
|
+
const vertexShader = createShader(gl, "VERTEX", vertexShaderDefinition);
|
|
150
|
+
const fragmentShader = createShader(gl, "FRAGMENT", fragmentShaderDefinition);
|
|
151
|
+
gl.attachShader(program, vertexShader);
|
|
152
|
+
gl.attachShader(program, fragmentShader);
|
|
153
|
+
gl.linkProgram(program);
|
|
154
|
+
const linkStatus = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
155
|
+
if (!linkStatus) {
|
|
156
|
+
gl.deleteProgram(program);
|
|
157
|
+
throw new Error(`Error linking program: ${gl.getProgramParameter(program, gl.LINK_STATUS)}`);
|
|
158
|
+
}
|
|
159
|
+
return program;
|
|
160
|
+
}
|
|
161
|
+
function createShader(gl, type, definition) {
|
|
162
|
+
const shader = gl.createShader(gl[`${type}_SHADER`]);
|
|
163
|
+
gl.shaderSource(shader, definition);
|
|
164
|
+
gl.compileShader(shader);
|
|
165
|
+
const compileStatus = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
166
|
+
if (!compileStatus) {
|
|
167
|
+
const error = gl.getShaderInfoLog(shader);
|
|
168
|
+
gl.deleteShader(shader);
|
|
169
|
+
throw new Error(`Unable to compile ${type}: ${error}`);
|
|
170
|
+
}
|
|
171
|
+
return shader;
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const pixelVertexShader: string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Vec2 } from "../geometry/vec2.js";
|
|
2
|
+
import { Vec3 } from "../geometry/vec3.js";
|
|
3
|
+
import { Vec4 } from "../geometry/vec4.js";
|
|
4
|
+
export type TextureDimension = 1 | 2 | 3 | 4;
|
|
5
|
+
export type TextureDataItem = number | Vec2 | Vec3 | Vec4;
|
|
6
|
+
export type TextureData = Array<TextureDataItem> | TexImageSource;
|
|
7
|
+
export type TextureSpec = {
|
|
8
|
+
dimension: TextureDimension;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
filter?: GLenum;
|
|
12
|
+
wrap?: GLenum;
|
|
13
|
+
};
|
|
14
|
+
export declare class ShaderTexture {
|
|
15
|
+
gl: WebGL2RenderingContext;
|
|
16
|
+
texture: WebGLTexture;
|
|
17
|
+
spec: TextureSpec;
|
|
18
|
+
constructor(gl: WebGL2RenderingContext, spec: TextureSpec);
|
|
19
|
+
private createGLTexture;
|
|
20
|
+
/** Update only filtering, wrapping, etc. */
|
|
21
|
+
updateSpec(partial: Partial<TextureSpec>): void;
|
|
22
|
+
/** Uploads data – reallocates if width/height changed */
|
|
23
|
+
setData(width: number, height: number, data: TextureData): void;
|
|
24
|
+
/** Converts Vec2/Vec3/Vec4/number to Float32Array */
|
|
25
|
+
private convertToFlatArray;
|
|
26
|
+
private internalFormat;
|
|
27
|
+
private format;
|
|
28
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export class ShaderTexture {
|
|
2
|
+
constructor(gl, spec) {
|
|
3
|
+
this.gl = gl;
|
|
4
|
+
this.spec = {
|
|
5
|
+
filter: gl.NEAREST,
|
|
6
|
+
wrap: gl.CLAMP_TO_EDGE,
|
|
7
|
+
width: 20,
|
|
8
|
+
height: 1,
|
|
9
|
+
...spec,
|
|
10
|
+
};
|
|
11
|
+
this.texture = this.createGLTexture();
|
|
12
|
+
}
|
|
13
|
+
createGLTexture() {
|
|
14
|
+
const { gl } = this;
|
|
15
|
+
const tex = gl.createTexture();
|
|
16
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
17
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.spec.filter);
|
|
18
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.spec.filter);
|
|
19
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.spec.wrap);
|
|
20
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.spec.wrap);
|
|
21
|
+
// allocate empty storage initially
|
|
22
|
+
const { width, height } = this.spec;
|
|
23
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, this.internalFormat(), width, height, 0, this.format(), gl.FLOAT, null);
|
|
24
|
+
return tex;
|
|
25
|
+
}
|
|
26
|
+
/** Update only filtering, wrapping, etc. */
|
|
27
|
+
updateSpec(partial) {
|
|
28
|
+
Object.assign(this.spec, partial);
|
|
29
|
+
const { gl, texture } = this;
|
|
30
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
31
|
+
if (partial.filter !== undefined) {
|
|
32
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.spec.filter);
|
|
33
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.spec.filter);
|
|
34
|
+
}
|
|
35
|
+
if (partial.wrap !== undefined) {
|
|
36
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, this.spec.wrap);
|
|
37
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.spec.wrap);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Uploads data – reallocates if width/height changed */
|
|
41
|
+
setData(width, height, data) {
|
|
42
|
+
const { gl } = this;
|
|
43
|
+
let texData;
|
|
44
|
+
if (Array.isArray(data)) {
|
|
45
|
+
const expectedItems = width * height;
|
|
46
|
+
if (data.length !== expectedItems) {
|
|
47
|
+
throw new Error(`Texture data mismatch: expected ${expectedItems}, got ${data.length}`);
|
|
48
|
+
}
|
|
49
|
+
texData = this.convertToFlatArray(data);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
texData = data;
|
|
53
|
+
}
|
|
54
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
55
|
+
const dimensionChanged = width !== this.spec.width || height !== this.spec.height;
|
|
56
|
+
if (dimensionChanged) {
|
|
57
|
+
this.spec.width = width;
|
|
58
|
+
this.spec.height = height;
|
|
59
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, this.internalFormat(), width, height, 0, this.format(), gl.FLOAT, texData);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Faster path when dimension stays constant
|
|
63
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, this.format(), gl.FLOAT, texData);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Converts Vec2/Vec3/Vec4/number to Float32Array */
|
|
67
|
+
convertToFlatArray(data) {
|
|
68
|
+
const { dimension } = this.spec;
|
|
69
|
+
let flatData = [];
|
|
70
|
+
if (dimension === 1) {
|
|
71
|
+
flatData = data;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
for (const item of data)
|
|
75
|
+
flatData.push(...item.toArray());
|
|
76
|
+
}
|
|
77
|
+
return new Float32Array(flatData);
|
|
78
|
+
}
|
|
79
|
+
internalFormat() {
|
|
80
|
+
const gl = this.gl;
|
|
81
|
+
return {
|
|
82
|
+
1: gl.R32F,
|
|
83
|
+
2: gl.RG32F,
|
|
84
|
+
3: gl.RGB32F,
|
|
85
|
+
4: gl.RGBA32F,
|
|
86
|
+
}[this.spec.dimension];
|
|
87
|
+
}
|
|
88
|
+
format() {
|
|
89
|
+
const gl = this.gl;
|
|
90
|
+
return {
|
|
91
|
+
1: gl.RED,
|
|
92
|
+
2: gl.RG,
|
|
93
|
+
3: gl.RGB,
|
|
94
|
+
4: gl.RGBA,
|
|
95
|
+
}[this.spec.dimension];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Vec2 } from "../geometry/vec2.js";
|
|
2
|
+
import { Vec3 } from "../geometry/vec3.js";
|
|
3
|
+
import { Vec4 } from "../geometry/vec4.js";
|
|
4
|
+
import { ParsedUniform } from "./helpers.js";
|
|
5
|
+
export type Uniform = ParsedUniform & {
|
|
6
|
+
location: WebGLUniformLocation;
|
|
7
|
+
};
|
|
8
|
+
export type UniformMap = Record<string, Uniform>;
|
|
9
|
+
export type ProgramData = {
|
|
10
|
+
program: WebGLProgram;
|
|
11
|
+
vao: WebGLVertexArrayObject;
|
|
12
|
+
uniforms: UniformMap;
|
|
13
|
+
};
|
|
14
|
+
export type UniformPrimitive = number | boolean | Vec2 | Vec3 | Vec4 | Array<boolean>;
|
|
15
|
+
export type UniformValue = UniformPrimitive | Array<UniformPrimitive>;
|
|
16
|
+
export type UniformData = Record<string, UniformValue>;
|
|
17
|
+
export declare const vecClasses: readonly [typeof Vec2, typeof Vec3, typeof Vec4];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UniformMap } from "./types.js";
|
|
2
|
+
export declare class UniformHandler {
|
|
3
|
+
gl: WebGL2RenderingContext;
|
|
4
|
+
uniforms: UniformMap;
|
|
5
|
+
constructor(gl: WebGL2RenderingContext, uniforms: UniformMap);
|
|
6
|
+
setUniform(name: string, value: any): void;
|
|
7
|
+
private throwError;
|
|
8
|
+
private setFloatUniform;
|
|
9
|
+
private setIntUniform;
|
|
10
|
+
private setBoolUniform;
|
|
11
|
+
private setVecUniform;
|
|
12
|
+
private setIVecUniform;
|
|
13
|
+
private setBVecUniform;
|
|
14
|
+
private setMatUniform;
|
|
15
|
+
private processUniformValue;
|
|
16
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { vecClasses } from "./types.js";
|
|
2
|
+
export class UniformHandler {
|
|
3
|
+
constructor(gl, uniforms) {
|
|
4
|
+
this.gl = gl;
|
|
5
|
+
this.uniforms = uniforms;
|
|
6
|
+
}
|
|
7
|
+
setUniform(name, value) {
|
|
8
|
+
const uniform = this.uniforms[name];
|
|
9
|
+
if (!uniform)
|
|
10
|
+
throw new Error(`Uniform with name ${name} not found`);
|
|
11
|
+
const processedValue = this.processUniformValue(uniform, value);
|
|
12
|
+
const { type } = uniform;
|
|
13
|
+
if (type === "float") {
|
|
14
|
+
this.setFloatUniform(uniform, processedValue);
|
|
15
|
+
}
|
|
16
|
+
else if (type === "int") {
|
|
17
|
+
this.setIntUniform(uniform, processedValue);
|
|
18
|
+
}
|
|
19
|
+
else if (type === "bool") {
|
|
20
|
+
this.setBoolUniform(uniform, processedValue);
|
|
21
|
+
}
|
|
22
|
+
else if (type.startsWith("vec")) {
|
|
23
|
+
this.setVecUniform(uniform, processedValue);
|
|
24
|
+
}
|
|
25
|
+
else if (type.startsWith("ivec")) {
|
|
26
|
+
this.setIVecUniform(uniform, processedValue);
|
|
27
|
+
}
|
|
28
|
+
else if (type.startsWith("bvec")) {
|
|
29
|
+
this.setBVecUniform(uniform, processedValue);
|
|
30
|
+
}
|
|
31
|
+
else if (type.startsWith("mat")) {
|
|
32
|
+
this.setMatUniform(uniform, processedValue);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
throw new Error(`Invalid uniform type: ${type}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throwError(uniform, error, value) {
|
|
39
|
+
const { name } = uniform;
|
|
40
|
+
throw new Error(`Error when setting ${name}: ${error} value: ${JSON.stringify(value)}`);
|
|
41
|
+
}
|
|
42
|
+
setFloatUniform(uniform, value) {
|
|
43
|
+
if (value.some((v) => typeof v !== "number"))
|
|
44
|
+
this.throwError(uniform, `not all values are numbers.`, value);
|
|
45
|
+
this.gl.uniform1fv(uniform.location, value);
|
|
46
|
+
}
|
|
47
|
+
setIntUniform(uniform, value) {
|
|
48
|
+
if (!allAreInt(value))
|
|
49
|
+
this.throwError(uniform, `not all values are integers.`, value);
|
|
50
|
+
this.gl.uniform1iv(uniform.location, value);
|
|
51
|
+
}
|
|
52
|
+
setBoolUniform(uniform, value) {
|
|
53
|
+
if (!allAreBool(value))
|
|
54
|
+
this.throwError(uniform, `not all values are booleans.`, value);
|
|
55
|
+
this.gl.uniform1iv(uniform.location, value.map((v) => v ? 1 : 0));
|
|
56
|
+
}
|
|
57
|
+
setVecUniform(uniform, value) {
|
|
58
|
+
if (!allAreVec(value))
|
|
59
|
+
this.throwError(uniform, `not all values are VecN.`, value);
|
|
60
|
+
const array = value
|
|
61
|
+
.map((v) => v.toArray())
|
|
62
|
+
.flat();
|
|
63
|
+
switch (uniform.type) {
|
|
64
|
+
case "vec4": return this.gl.uniform4fv(uniform.location, array);
|
|
65
|
+
case "vec3": return this.gl.uniform3fv(uniform.location, array);
|
|
66
|
+
case "vec2": return this.gl.uniform2fv(uniform.location, array);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
setIVecUniform(uniform, value) {
|
|
70
|
+
if (!allAreVec(value))
|
|
71
|
+
this.throwError(uniform, `not all values are Vec.`, value);
|
|
72
|
+
const array = value.map((v) => v.toArray()).flat();
|
|
73
|
+
if (!allAreInt(array))
|
|
74
|
+
this.throwError(uniform, `not all entries are integers.`, array);
|
|
75
|
+
switch (uniform.type) {
|
|
76
|
+
case "ivec4": return this.gl.uniform4iv(uniform.location, array);
|
|
77
|
+
case "ivec3": return this.gl.uniform3iv(uniform.location, array);
|
|
78
|
+
case "ivec2": return this.gl.uniform2iv(uniform.location, array);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
setBVecUniform(uniform, value) {
|
|
82
|
+
if (!value.every(allAreBool))
|
|
83
|
+
this.throwError(uniform, `not all values are arrays of booleans.`, value);
|
|
84
|
+
const array = value
|
|
85
|
+
.flat()
|
|
86
|
+
.map((v) => v ? 1 : 0);
|
|
87
|
+
switch (uniform.type) {
|
|
88
|
+
case "bvec4": return this.gl.uniform4iv(uniform.location, array);
|
|
89
|
+
case "bvec3": return this.gl.uniform3iv(uniform.location, array);
|
|
90
|
+
case "bvec2": return this.gl.uniform2iv(uniform.location, array);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
setMatUniform(_uniform, _value) {
|
|
94
|
+
throw new Error("Not implemented yet");
|
|
95
|
+
}
|
|
96
|
+
processUniformValue(uniform, value) {
|
|
97
|
+
const { type, isArray } = uniform;
|
|
98
|
+
let wrappedValue = [];
|
|
99
|
+
// Start with types that are arrays of arrays
|
|
100
|
+
if (type.startsWith("bvec") || type.startsWith("mat")) {
|
|
101
|
+
if (isArray) {
|
|
102
|
+
if (!Array.isArray(value) || !Array.isArray(value[0]))
|
|
103
|
+
this.throwError(uniform, `Expected array of arrays.`, value);
|
|
104
|
+
wrappedValue = value;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
if (Array.isArray(value[0]))
|
|
108
|
+
this.throwError(uniform, `Expected array of values.`, value);
|
|
109
|
+
wrappedValue = [value];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
if (!isArray && Array.isArray(value))
|
|
114
|
+
this.throwError(uniform, `Expected non-array.`, value);
|
|
115
|
+
wrappedValue = Array.isArray(value) ? value : [value];
|
|
116
|
+
}
|
|
117
|
+
if (isArray) {
|
|
118
|
+
if (!Array.isArray(value))
|
|
119
|
+
this.throwError(uniform, `Array value expected.`, value);
|
|
120
|
+
if (value.length !== uniform.arraySize)
|
|
121
|
+
this.throwError(uniform, `Invalid array length, expected size ${uniform.arraySize}, received ${value.length}`);
|
|
122
|
+
}
|
|
123
|
+
return wrappedValue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function allAreVec(value) {
|
|
127
|
+
return value.every((v) => vecClasses.some((C) => v instanceof C));
|
|
128
|
+
}
|
|
129
|
+
function allAreInt(value) {
|
|
130
|
+
return value.every((v) => typeof v === "number" && Math.floor(v) === v);
|
|
131
|
+
}
|
|
132
|
+
function allAreBool(value) {
|
|
133
|
+
return value.every((v) => typeof v === "boolean");
|
|
134
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Polygon } from "../geometry/polygon.js";
|
|
2
|
+
import { RNG } from "../utilities/rng.js";
|
|
3
|
+
import { PointSamplerStrategy } from "../utilities/point-sampler.js";
|
|
4
|
+
import { Hollowgon } from "../geometry/hollowgon.js";
|
|
5
|
+
export type DelaunaySubdivisionOptions = {
|
|
6
|
+
shape: Polygon | Hollowgon;
|
|
7
|
+
numPoints: number;
|
|
8
|
+
rng: RNG;
|
|
9
|
+
strategy?: PointSamplerStrategy;
|
|
10
|
+
};
|
|
11
|
+
export declare function delaunaySubdivision(options: DelaunaySubdivisionOptions): Polygon[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Delaunay } from "d3-delaunay";
|
|
2
|
+
import { Polygon } from "../geometry/polygon.js";
|
|
3
|
+
import { vec2 } from "../geometry/vec2.js";
|
|
4
|
+
import { pointSampler } from "../utilities/point-sampler.js";
|
|
5
|
+
import { clip } from "../geometry/polygon-operations.js";
|
|
6
|
+
import { Hollowgon } from "../geometry/hollowgon.js";
|
|
7
|
+
export function delaunaySubdivision(options) {
|
|
8
|
+
const { shape, numPoints, rng, strategy = "poisson" } = options;
|
|
9
|
+
const pointBuffer = [];
|
|
10
|
+
for (const { x, y } of pointSampler({ shape, size: numPoints, strategy, rng })) {
|
|
11
|
+
pointBuffer.push(x, y);
|
|
12
|
+
}
|
|
13
|
+
// TODO figure out if there is a way to triangulate directly on polygon with holes
|
|
14
|
+
const vertices = shape instanceof Hollowgon ? shape.boundary.vertices : shape.vertices;
|
|
15
|
+
for (const { x, y } of vertices)
|
|
16
|
+
pointBuffer.push(x, y);
|
|
17
|
+
const delaunay = new Delaunay(pointBuffer);
|
|
18
|
+
let triangles = delaunay.trianglePolygons();
|
|
19
|
+
const polygons = [];
|
|
20
|
+
for (const triangle of triangles) {
|
|
21
|
+
const vertices = [];
|
|
22
|
+
for (const vertex of triangle) {
|
|
23
|
+
vertices.push(vec2(vertex[0], vertex[1]));
|
|
24
|
+
}
|
|
25
|
+
polygons.push(new Polygon(vertices));
|
|
26
|
+
}
|
|
27
|
+
return clip(polygons, shape);
|
|
28
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Hollowgon } from "../geometry/hollowgon.js";
|
|
2
|
+
import { OperationShape } from "../geometry/polygon-operations.js";
|
|
3
|
+
import { Polygon } from "../geometry/polygon.js";
|
|
4
|
+
export declare function gridSubdivision(shape: Polygon, rotation: number, resolution: number): Array<Polygon>;
|
|
5
|
+
export declare function gridSubdivision(shape: Hollowgon, rotation: number, resolution: number): Array<OperationShape>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Line } from "../geometry/line.js";
|
|
2
|
+
import { subdivide } from "../geometry/polygon-operations.js";
|
|
3
|
+
import { Vec2, vec2 } from "../geometry/vec2.js";
|
|
4
|
+
export function gridSubdivision(shape, rotation, resolution) {
|
|
5
|
+
const centroid = shape.centroid();
|
|
6
|
+
const { xMin, yMin, width, height, aspectRatio } = shape
|
|
7
|
+
.clone()
|
|
8
|
+
.rotate(-rotation, centroid)
|
|
9
|
+
.aabb();
|
|
10
|
+
const [columns, rows] = aspectRatio > 1 ?
|
|
11
|
+
[Math.round(aspectRatio * resolution), resolution] :
|
|
12
|
+
[resolution, Math.round(resolution / aspectRatio)];
|
|
13
|
+
const columnSpacing = width / columns;
|
|
14
|
+
let polygons = [shape];
|
|
15
|
+
let cutDirection = Vec2.fromPolar(1, Math.PI / 2 + rotation);
|
|
16
|
+
for (let i = 1; i < columns; i++) {
|
|
17
|
+
const point = vec2(xMin + columnSpacing * i, yMin - 1).rotate(rotation, centroid);
|
|
18
|
+
const cutLine = new Line(point, cutDirection);
|
|
19
|
+
polygons = subdivide(polygons, cutLine);
|
|
20
|
+
}
|
|
21
|
+
const rowSpacing = height / rows;
|
|
22
|
+
cutDirection = Vec2.fromPolar(1, rotation);
|
|
23
|
+
for (let i = 1; i < rows; i++) {
|
|
24
|
+
const point = vec2(xMin - 1, yMin + rowSpacing * i).rotate(rotation, centroid);
|
|
25
|
+
const cutLine = new Line(point, cutDirection);
|
|
26
|
+
polygons = subdivide(polygons, cutLine);
|
|
27
|
+
}
|
|
28
|
+
return polygons;
|
|
29
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Hollowgon } from "../geometry/hollowgon.js";
|
|
2
|
+
import { Polygon } from "../geometry/polygon.js";
|
|
3
|
+
export declare function hexagonalTiling(shape: Polygon | Hollowgon, rotation: number, resolution: number): Polygon[];
|
|
4
|
+
export declare function hexagonalTileHelper(shape: Polygon | Hollowgon, rotation: number, resolution: number): Polygon[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { clip } from "../geometry/polygon-operations.js";
|
|
2
|
+
import { Polygon } from "../geometry/polygon.js";
|
|
3
|
+
import { vec2 } from "../geometry/vec2.js";
|
|
4
|
+
const SQRT_3 = Math.sqrt(3);
|
|
5
|
+
export function hexagonalTiling(shape, rotation, resolution) {
|
|
6
|
+
const polygons = hexagonalTileHelper(shape, rotation, resolution);
|
|
7
|
+
for (const polygon of polygons)
|
|
8
|
+
polygon.rotate(rotation, shape.centroid());
|
|
9
|
+
return clip(polygons, shape);
|
|
10
|
+
}
|
|
11
|
+
export function hexagonalTileHelper(shape, rotation, resolution) {
|
|
12
|
+
const { xMin, xMax, yMin, width, height, aspectRatio } = shape
|
|
13
|
+
.clone()
|
|
14
|
+
.rotate(-rotation, shape.centroid())
|
|
15
|
+
.aabb();
|
|
16
|
+
const size = (aspectRatio > 1 ? 1.5 * height : Math.sqrt(3) * width) / resolution;
|
|
17
|
+
const hexagonWidth = Math.sqrt(3) * size;
|
|
18
|
+
const hexagonHeight = 1.5 * size;
|
|
19
|
+
const columns = Math.ceil(width / hexagonWidth);
|
|
20
|
+
// Compensate for hexagon height portion that is tapered
|
|
21
|
+
const rows = Math.ceil((height + 0.5 * size) / hexagonHeight);
|
|
22
|
+
let polygons = [];
|
|
23
|
+
for (let row = 0; row < rows; row++) {
|
|
24
|
+
const y0 = yMin + row * hexagonHeight;
|
|
25
|
+
for (let column = row % 2 === 0 ? 0 : -0.5; column < columns; column++) {
|
|
26
|
+
const x0 = xMin + column * hexagonWidth;
|
|
27
|
+
if (x0 > xMax)
|
|
28
|
+
break;
|
|
29
|
+
polygons.push(new Polygon([
|
|
30
|
+
vec2(x0, y0),
|
|
31
|
+
vec2(x0 + 0.5 * SQRT_3 * size, y0 - 0.5 * size),
|
|
32
|
+
vec2(x0 + SQRT_3 * size, y0),
|
|
33
|
+
vec2(x0 + SQRT_3 * size, y0 + size),
|
|
34
|
+
vec2(x0 + 0.5 * SQRT_3 * size, y0 + 1.5 * size),
|
|
35
|
+
vec2(x0, y0 + size),
|
|
36
|
+
]));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return polygons;
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./delaunay.js";
|
|
2
|
+
export * from "./grid.js";
|
|
3
|
+
export * from "./hexagononal.js";
|
|
4
|
+
export * from "./line.js";
|
|
5
|
+
export * from "./pythagorean.js";
|
|
6
|
+
export * from "./random-cuts.js";
|
|
7
|
+
export * from "./recursive-cuts.js";
|
|
8
|
+
export * from "./rhombille.js";
|
|
9
|
+
export * from "./rightangled-triangle.js";
|
|
10
|
+
export * from "./triangle.js";
|
|
11
|
+
export * from "./voronoi.js";
|
|
12
|
+
export * from "./mediterranean.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./delaunay.js";
|
|
2
|
+
export * from "./grid.js";
|
|
3
|
+
export * from "./hexagononal.js";
|
|
4
|
+
export * from "./line.js";
|
|
5
|
+
export * from "./pythagorean.js";
|
|
6
|
+
export * from "./random-cuts.js";
|
|
7
|
+
export * from "./recursive-cuts.js";
|
|
8
|
+
export * from "./rhombille.js";
|
|
9
|
+
export * from "./rightangled-triangle.js";
|
|
10
|
+
export * from "./triangle.js";
|
|
11
|
+
export * from "./voronoi.js";
|
|
12
|
+
export * from "./mediterranean.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Hollowgon } from "../geometry/hollowgon.js";
|
|
2
|
+
import { OperationShape } from "../geometry/polygon-operations.js";
|
|
3
|
+
import { Polygon } from "../geometry/polygon.js";
|
|
4
|
+
export declare function lineSubdivision(shape: Polygon, rotation: number, columns: number): Array<Polygon>;
|
|
5
|
+
export declare function lineSubdivision(shape: Hollowgon, rotation: number, columns: number): Array<OperationShape>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Line } from "../geometry/line.js";
|
|
2
|
+
import { subdivide } from "../geometry/polygon-operations.js";
|
|
3
|
+
import { Vec2, vec2 } from "../geometry/vec2.js";
|
|
4
|
+
export function lineSubdivision(shape, rotation, columns) {
|
|
5
|
+
const centroid = shape.centroid();
|
|
6
|
+
const { xMin, yMin, width } = shape
|
|
7
|
+
.clone()
|
|
8
|
+
.rotate(-rotation, centroid)
|
|
9
|
+
.aabb();
|
|
10
|
+
const spacing = width / columns;
|
|
11
|
+
let polygons = [shape];
|
|
12
|
+
const cutDirection = Vec2.fromPolar(1, Math.PI / 2 + rotation);
|
|
13
|
+
for (let i = 1; i < columns; i++) {
|
|
14
|
+
const point = vec2(xMin + spacing * i, yMin - 1).rotate(rotation, centroid);
|
|
15
|
+
const cutLine = new Line(point, cutDirection);
|
|
16
|
+
polygons = subdivide(polygons, cutLine);
|
|
17
|
+
}
|
|
18
|
+
return polygons;
|
|
19
|
+
}
|