webgl2-sdf 0.0.6 → 0.0.8

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.
@@ -1,17 +1,36 @@
1
1
  // import { getWebGLContext } from './webgl-utils/get-gl-context.js';
2
2
  import { vertex } from './shaders/vertex.js';
3
- import { getFragment } from './shaders/fragment.js';
3
+ import { getFragment, GLSL_DEFAULT } from './shaders/fragment.js';
4
4
  import { initProgram } from './webgl-utils/use-program.js';
5
5
  import { mainProgram } from './main-program.js';
6
6
  import { ROW_COUNT } from './row-count.js';
7
7
  import { getPathsFromStr } from './svg/get-paths-from-str.js';
8
8
  import { MAX_ASPECT_RATIO_BEFORE_STRETCH } from './max-aspect-ratio-before-stretch.js';
9
9
  import { GlContext } from './types/gl-context.js';
10
- // import { debugShaders } from './debug-shaders.js';
11
10
 
12
11
  const { ceil, min, max } = Math;
13
12
 
14
13
 
14
+ interface SdfOptions {
15
+ /** the position where to draw, x-coordinate */
16
+ readonly x?: number | undefined;
17
+ readonly y?: number | undefined;
18
+ readonly testInteriorExterior?: boolean | undefined;
19
+ readonly calcSdfForInside?: boolean | undefined;
20
+ readonly calcSdfForOutside?: boolean | undefined;
21
+ readonly customData?: [number,number,number,number] | undefined;
22
+ readonly glslRgbaCalcStr?: string | undefined;
23
+ }
24
+
25
+
26
+ const defaultSdfOptions: SdfOptions = {
27
+ x: 0, y: 0,
28
+ testInteriorExterior: true,
29
+ calcSdfForInside: true, calcSdfForOutside: true,
30
+ customData: [1,0,0,0]
31
+ }
32
+
33
+
15
34
  /**
16
35
  * Generates an sdf (signed distance field) from the given bezier curves,
17
36
  * viewbox, etc. and renders the result
@@ -22,36 +41,91 @@ const { ceil, min, max } = Math;
22
41
  * thereof) given by given by their ordered control points,
23
42
  * e.g. `[ [[0,0],[1,1],[2,1],[2,0]], [[2,0],[7,2],[1,5],[8,6]], ... ]` **OR**
24
43
  * * an SVG string, e.g. "M26.53 478.83 C028.89 481.61 031.33 484.32 ..."
44
+ * @param viewbox the viewbox given as `[x1,x2,y1,y2]` (**not as** `[x,y,widht,height]`)
25
45
  * @param width the width of the drawing rectangle
26
46
  * @param height the height of the drawing rectangle
27
- * @param viewbox the viewbox
28
47
  * @param maxDistance maximum sdf distance
29
- * @param sdfExponent TODO
30
- * @param inclInside if `true` the sdf will be calculate for the inside of the shape
31
- * @param inclOutside if `true` the sdf will be calculate for the outside of the shape
32
- * @param x the position where to draw, x-coordinate
33
- * @param y the position where to draw, y-coordinate
34
- * @param channel TODO
48
+ * @param options additional options (see below)
49
+ *
50
+ * **The following are properties of the `options` parameters**
51
+ * @param x defaults to `0`; the position where to draw on the canvas, x-coordinate
52
+ * @param y defaults to `0`; the position where to draw on the canvas, y-coordinate
53
+ * @param testInteriorExterior defaults to `true`;
54
+ * if `false` winds will always be `0.0` and only an un-signed sdf can be calculated since all
55
+ * fragments are considered outside
56
+ * @param calcSdfForInside defaults to `true`;
57
+ * if `false` the sdf will not be calculate for the inside of the shape, in the shader, `res` will always be `1.0`
58
+ * @param calcSdfForOutside defaults to `true`;
59
+ * if `false` the sdf will not be calculate for the outside of the shape, in the shader, `res` will always be `1.0`
60
+ * @param customData optional custom data (must be an array of 4 numbers) to send to
61
+ * the fragment shader as a uniform, e.g. exponent, scale, a timer, or whatever
62
+ * @param glslRgbaCalcStr a glsl string (#version 300 es) inserted at the end of
63
+ * the fragment shader to modify the output frag color in any way (you can also discard the fragment);
64
+ * see below for available variables that can be used;
65
+ *
66
+ * defaults to (designed to match webgl-sdf-generator)
67
+ * ```glsl
68
+ * float exponent = uCustom.x;
69
+ * res = (pow(1.0 - res, exponent) * 0.5) * (inside ? -1.0 : 1.0);
70
+ * float red = res;
71
+ * float green = res;
72
+ * float blue = res;
73
+ * float alpha = res;
74
+ *
75
+ * ```
76
+ * You must define and assign \`red\`, \`green\`, \`blue\` and \`alpha\`.
77
+ *
78
+ * Usable variables:
79
+ * ```glsl
80
+ * // the result of the distance calculation for this fragment, a value from 0.0 to 1.0
81
+ * // with 1.0 occuring when the fragment is >= maxDistance away and 0.0 when the
82
+ * // fragment is exactly on a curve
83
+ * float res
84
+ *
85
+ * // the number of anti-clockwise winds around the fragment, a value != 0
86
+ * // means the fragment is inside the shape
87
+ * float winds
88
+ *
89
+ * // 4 custom values set via an options parameter of `generateSdf`; defaults to `[1,0,0,0]`;
90
+ * // the first value is used as the exponent within the default `glslRgbaCalcStr`
91
+ * vec4 uCustom;
92
+ *
93
+ * // the max distance value supplied via `generateSdf`
94
+ * float uMaxDistance
95
+ *
96
+ * // bit 0 -> calc sdf when fragment is inside; defaults to 1,
97
+ * // bit 1 -> calc sdf when fragment is outside; defaults to 1
98
+ * // bit 1 -> calc `winds` (required for signing the distance); defaults to 1
99
+ * // note: when the distance calculation is not done (via options from `generateSdf`),
100
+ * `res` will be set to 1.0 (max distance away)
101
+ * int uTestInOut
102
+ *
103
+ * // the original x,y coordinates of the fragment in the original space provided
104
+ * // via the `viewbox` in `generateSdf`, e.g. the very bottom-left fragment
105
+ * // will have vXY == (viebox[0], viebox[1]) and the very top right will have
106
+ * // coordinates vXY == (viebox[2], viebox[3])
107
+ * vec2 vXY
108
+ *
109
+ * // whether the point is inside or outside the shape, often used to sign `res`
110
+ * // it is identical to `winds != 0`
111
+ * bool inside
112
+ *
113
+ * // pretty much useless unless you want to create a checkerboard pattern for no good reason
114
+ * int instanceId
35
115
  */
36
116
  function generateSdf(
37
117
  glContext: GlContext,
38
118
  bezierCurves_or_svgStr: (number[][])[][] | string,
119
+ viewbox: [number,number,number,number],
39
120
  width: number,
40
121
  height: number,
41
- viewbox: [number,number,number,number],
42
122
  maxDistance: number,
43
- sdfExponent = 1,
44
- inclInside = true,
45
- inclOutside = true,
46
- x = 0, y = 0,
47
- channel = 0) {
123
+ options: SdfOptions = defaultSdfOptions) {
48
124
 
49
125
  const psss = typeof bezierCurves_or_svgStr === 'string'
50
126
  ? getPathsFromStr(bezierCurves_or_svgStr)
51
127
  : bezierCurves_or_svgStr;
52
128
 
53
- // const glContext = getWebGLContext(gl);
54
-
55
129
  let stretch = 1;
56
130
  const aspectRatio = width/height;
57
131
  if (aspectRatio > MAX_ASPECT_RATIO_BEFORE_STRETCH) {
@@ -67,30 +141,41 @@ function generateSdf(
67
141
 
68
142
  const padCount = 2*ceil(min(maxDistance, maxDim)/cellSize/2);
69
143
 
144
+ const { glslRgbaCalcStr } = options;
145
+ const hash = calcStrHash(glslRgbaCalcStr || '');
146
+
70
147
  const programMain = initProgram(
71
- glContext, `main${colCount}-${padCount}`,
72
- vertex, getFragment(colCount, padCount)
148
+ glContext, `main${colCount}-${padCount}-${hash}`,
149
+ vertex, getFragment(colCount, padCount, glslRgbaCalcStr || GLSL_DEFAULT, hash)
73
150
  );
74
151
 
75
152
  const { gl } = glContext;
76
153
 
77
- // debugShaders(gl); // comment for production
78
-
79
154
  gl.useProgram(programMain.program);
80
155
  mainProgram(
81
- glContext, programMain, psss,
82
- viewbox, maxDistance, sdfExponent, x, y, width, height, colCount,
83
- cellSize, inclInside, inclOutside, padCount, stretch
156
+ glContext, programMain,
157
+ psss, viewbox, maxDistance,
158
+ width, height,
159
+ options,
160
+ colCount, cellSize, padCount, stretch,
161
+
84
162
  );
163
+ }
164
+
85
165
 
86
- // testing!!
87
- if (Math.random() > 0.995) {
88
- const loseContextExt = gl.getExtension('WEBGL_lose_context');
89
- if (loseContextExt) {
90
- loseContextExt.loseContext();
91
- }
166
+ /**
167
+ * Calculates and returns a hash of the given string
168
+ */
169
+ function calcStrHash(str: string) {
170
+ if (!str) return 0;
171
+
172
+ let hash = 5381;
173
+ for (let i=0; i<str.length; i++) {
174
+ hash = ((hash << 5) + hash) + str.charCodeAt(i);
92
175
  }
176
+
177
+ return hash >>> 0; // Convert to unsigned 32-bit integer
93
178
  }
94
179
 
95
180
 
96
- export { generateSdf }
181
+ export { generateSdf, SdfOptions }
@@ -7,6 +7,7 @@ import { GlContext } from './types/gl-context.js';
7
7
  import { prepareBuffers } from './prepare-buffers.js';
8
8
  import { TEX_WIDTH } from './tex-width.js';
9
9
  import { ROW_COUNT } from './row-count.js';
10
+ import { SdfOptions } from './generate-sdf.js';
10
11
 
11
12
 
12
13
  const SEG_TEX_INDEX = 0;
@@ -17,23 +18,29 @@ const CROSS_TEX_INDEX = 2;
17
18
  function mainProgram(
18
19
  glContext: GlContext,
19
20
  programMain: Program,
21
+
20
22
  psss: number[][][][],
21
23
  viewbox: [number,number,number,number],
22
24
  maxDistance: number,
23
- sdfExponent = 1,
24
- x: number,
25
- y: number,
26
25
  width: number,
27
26
  height: number,
27
+
28
+ options: SdfOptions,
29
+
28
30
  colCount: number,
29
31
  cellSize: number,
30
- inclInside: boolean,
31
- inclOutside: boolean,
32
32
  padCount: number,
33
33
  stretch: number) {
34
34
 
35
35
  const { gl } = glContext;
36
36
 
37
+ const {
38
+ x = 0, y = 0, testInteriorExterior = true,
39
+ calcSdfForInside = true, calcSdfForOutside = true,
40
+ customData = [1,0,0,0],
41
+ glslRgbaCalcStr
42
+ } = options;
43
+
37
44
  const vertices: number[] = [];
38
45
  const x0 = 0;
39
46
  const y0 = 0;
@@ -80,8 +87,12 @@ function mainProgram(
80
87
  // Init/update uniforms
81
88
  setUniform_('2f', 'uWidthHeight', width, height);
82
89
  setUniform_('1f', 'uMaxDistance', maxDistance);
83
- setUniform_('1f', 'uExponent', sdfExponent); // TODO
84
- setUniform_('1i', 'uIncl', (inclInside ? 1 : 0) + (inclOutside ? 2 : 0));
90
+ setUniform_('1i', 'uTestInOut',
91
+ (calcSdfForInside ? 1 : 0) +
92
+ (calcSdfForOutside ? 2 : 0) +
93
+ (testInteriorExterior ? 4 : 0)
94
+ );
95
+ setUniform_('4f', 'uCustom', ...customData);
85
96
 
86
97
  setUniformBlock(programMain)('SegIdxRangePerCellBlock', 0, segIdxs_PerCell_Range_Arr);
87
98
  setUniformBlock(programMain)('SegIdxRangePerStripBlock', 1, segIdxs_PerStrip_Range_Arr);
@@ -89,15 +100,19 @@ function mainProgram(
89
100
  ///////////////////////////////////////
90
101
  // Create buffer for line segment data
91
102
  useTexture(glContext, SEG_TEX_INDEX, 'segs');
92
- gl.texImage2D( // really 1d
103
+ gl.texImage2D(
93
104
  gl.TEXTURE_2D,
94
- 0, // level - irrelevant
95
- gl.RGBA32F, // internalFormat - we're using 4 floats for the 2 line segment endpoints
96
- lineSegPtCoords_Arr.length/4, // width === number of lines
97
- 1, // height - linear data texture so we only need height of 1
98
- 0, // border - whatever
99
- gl.RGBA, // format
100
- gl.FLOAT, // it holds floats
105
+ 0, // level - irrelevant
106
+ gl.RGBA32F, // internalFormat - we're using 4 floats for the 2 line segment endpoints
107
+
108
+ TEX_WIDTH, // fixed width
109
+ lineSegPtCoords_Arr.length / 4 / TEX_WIDTH, // height === number of point coordinates
110
+ // lineSegPtCoords_Arr.length/4,
111
+ // 1,
112
+
113
+ 0, // border - whatever
114
+ gl.RGBA, // format
115
+ gl.FLOAT, // it holds floats
101
116
  lineSegPtCoords_Arr // texture data
102
117
  );
103
118
  const segTexLoc = gl.getUniformLocation(programMain.program, "uSegs");
@@ -107,12 +122,12 @@ function mainProgram(
107
122
  ///////////////////////////////////////////////
108
123
  // Create buffer for close cell indexes per cell
109
124
  useTexture(glContext, CELL_TEX_INDEX, 'closeCellIdxsPerCell');
110
- gl.texImage2D( // really 1d
125
+ gl.texImage2D(
111
126
  gl.TEXTURE_2D,
112
127
  0, // level - irrelevant
113
128
  gl.R32I, // internalFormat - we're using 1 signed 32-bit int for indexes
114
- TEX_WIDTH, // width === number of indexes
115
- closeCellIdxs_PerCell_Arr.length / TEX_WIDTH,
129
+ TEX_WIDTH, // fixed width
130
+ closeCellIdxs_PerCell_Arr.length / TEX_WIDTH, // height === number of indexes
116
131
  0, // border - whatever
117
132
  gl.RED_INTEGER, // format
118
133
  gl.INT, // it holds ints
@@ -126,12 +141,12 @@ function mainProgram(
126
141
  ///////////////////////////////////////////////
127
142
  // Create buffer for crossing cell indexes per cell
128
143
  useTexture(glContext, CROSS_TEX_INDEX, 'crossCellIdxsPerCell');
129
- gl.texImage2D( // really 1d
144
+ gl.texImage2D(
130
145
  gl.TEXTURE_2D,
131
146
  0, // level - irrelevant
132
147
  gl.R32I, // internalFormat - we're using 1 signed 32-bit int for indexes
133
- TEX_WIDTH, // width === number of indexes
134
- crossCellIdxs_PerCell_Arr.length / TEX_WIDTH, // height - linear data texture so we only need height of 1
148
+ TEX_WIDTH, // fixed width
149
+ crossCellIdxs_PerCell_Arr.length / TEX_WIDTH, // height === number of indexes
135
150
  0, // border - whatever
136
151
  gl.RED_INTEGER, // format
137
152
  gl.INT, // it holds ints
@@ -148,6 +163,8 @@ function mainProgram(
148
163
 
149
164
  gl.viewport(x, y, width, height);
150
165
 
166
+ gl.colorMask(true, true, true, true);
167
+
151
168
  // draw a square colCount * ROW_COUNT times - 6 vertics
152
169
  gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, colCount*ROW_COUNT);
153
170
 
@@ -26,7 +26,7 @@ function prepareBuffers(
26
26
 
27
27
  const lineSegs = bezierCurvesToLineSegs(psss_);
28
28
  const grid = createEmptyGrid(colCount, padCount);
29
- const strips = new Array(ROW_COUNT).fill(undefined).map(v => []);
29
+ const strips = new Array(ROW_COUNT).fill(undefined).map(v => [] as number[][][]);
30
30
 
31
31
  for (let i=0; i<lineSegs.length; i++) {
32
32
  const seg = lineSegs[i];
@@ -39,16 +39,16 @@ function prepareBuffers(
39
39
  findCrossingCells(grid, colCount, padCount); // add crossing cells
40
40
  ////////////////////////////////////////////////////////////////////////////
41
41
 
42
- const allSegs: number[][][] = [];
43
- const segIdxs_PerCell_Range: [number,number][] = [];
42
+ const allSegs: number[] = [];
43
+ const segIdxs_PerCell_Range: number[] = [];
44
44
 
45
45
  // close cells
46
46
  const closeCellIdxs_PerCell: number[] = [];
47
- const closeCellIdxs_PerCell_Range: [number,number][] = [];
47
+ const closeCellIdxs_PerCell_Range: number[] = [];
48
48
 
49
49
  // crossing cells
50
50
  const crossCellIdxs_PerCell: number[] = [];
51
- const crossCellIdxs_PerCell_Range: [number,number][] = [];
51
+ const crossCellIdxs_PerCell_Range: number[] = [];
52
52
 
53
53
  // const closeCellIdxsPerCell_: number[][] = []; // testing
54
54
 
@@ -66,13 +66,15 @@ function prepareBuffers(
66
66
  const { closeCells, crossingCells } = cell;
67
67
 
68
68
  const L1 = crossingCells.length;
69
- crossCellIdxs_PerCell.push(...crossingCells);
70
- crossCellIdxs_PerCell_Range.push([S1, L1]);
71
- S1 += L1;
72
-
73
69
  const L2 = closeCells.length;
70
+
71
+ crossCellIdxs_PerCell.push(...crossingCells);
74
72
  closeCellIdxs_PerCell.push(...closeCells);
75
- closeCellIdxs_PerCell_Range.push([S2, L2]);
73
+
74
+ crossCellIdxs_PerCell_Range.push(S1, L1);
75
+ closeCellIdxs_PerCell_Range.push(S2, L2);
76
+
77
+ S1 += L1;
76
78
  S2 += L2;
77
79
 
78
80
  // closeCellIdxsPerCell_.push(closeCells); // testing
@@ -82,16 +84,12 @@ function prepareBuffers(
82
84
  const { lineSegs } = cell;
83
85
 
84
86
  const L3 = lineSegs.length;
85
- segIdxs_PerCell_Range.push([S3, L3]);
87
+ segIdxs_PerCell_Range.push(S3, L3);
86
88
  S3 += L3;
87
- allSegs.push(...lineSegs);
89
+ allSegs.push(...lineSegs.flat(2));
88
90
  }
89
91
  }
90
92
 
91
- // It is a requirement to fill in multiples of `TEX_WIDTH`
92
- while (closeCellIdxs_PerCell.length % TEX_WIDTH !== 0) { closeCellIdxs_PerCell.push(0); }
93
- while (crossCellIdxs_PerCell.length % TEX_WIDTH !== 0) { crossCellIdxs_PerCell.push(0); }
94
-
95
93
  // Add line segs from strips
96
94
  const segIdxs_PerStrip_Range: [number,number][] = [];
97
95
  for (let i=0; i<ROW_COUNT; i++) {
@@ -101,21 +99,26 @@ function prepareBuffers(
101
99
  const L = lineSegs.length;
102
100
  segIdxs_PerStrip_Range.push([S3, L]);
103
101
  S3 += L;
104
- allSegs.push(...lineSegs);
102
+ allSegs.push(...lineSegs.flat(2));
105
103
  }
106
104
 
105
+ // It is a requirement to fill in multiples of `TEX_WIDTH`
106
+ while (closeCellIdxs_PerCell.length % TEX_WIDTH !== 0) { closeCellIdxs_PerCell.push(0); }
107
+ while (crossCellIdxs_PerCell.length % TEX_WIDTH !== 0) { crossCellIdxs_PerCell.push(0); }
108
+ while (allSegs.length % (4*TEX_WIDTH) !== 0) { allSegs.push(0); }
109
+
107
110
 
108
111
  // all line segments, with their ranges per cell and per strip
109
- const lineSegPtCoords_Arr = new Float32Array(allSegs.flat(2));
110
- const segIdxs_PerCell_Range_Arr = new Int32Array(segIdxs_PerCell_Range.flat());
112
+ const lineSegPtCoords_Arr = new Float32Array(allSegs);
113
+ const segIdxs_PerCell_Range_Arr = new Int32Array(segIdxs_PerCell_Range);
111
114
 
112
115
  // close cell idxs and range
113
- const closeCellIdxs_PerCell_Range_Arr = new Int32Array(closeCellIdxs_PerCell_Range.flat());
116
+ const closeCellIdxs_PerCell_Range_Arr = new Int32Array(closeCellIdxs_PerCell_Range);
114
117
  const closeCellIdxs_PerCell_Arr = new Int32Array(closeCellIdxs_PerCell);
115
118
 
116
119
  // cross cell idxs and range
117
120
  const crossCellIdxs_PerCell_Arr = new Int32Array(crossCellIdxs_PerCell);
118
- const crossCellIdxs_perCell_Range_Arr = new Int32Array(crossCellIdxs_PerCell_Range.flat());
121
+ const crossCellIdxs_perCell_Range_Arr = new Int32Array(crossCellIdxs_PerCell_Range);
119
122
 
120
123
  // segment index ranges per strip
121
124
  const segIdxs_PerStrip_Range_Arr = new Int32Array(segIdxs_PerStrip_Range.flat());