p5.env 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.
Files changed (3) hide show
  1. package/README.md +71 -0
  2. package/p5.env.js +440 -0
  3. package/package.json +14 -0
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Generative lighting
2
+
3
+ This is an experiment around replacing image lighting in p5 with something that is lighter computationally and easier to code by hand for non-photoreal workflows.
4
+
5
+ A representation for this has to be both expressive, and also cheaply blurrable for different levels of roughness. Since this is intended for generative art, is is not essential that it perfectly sum the energy coming in from different angles; it just has to have that general look. The Phong lighting model in p5.js can cheaply be evaluated at different roughness levels, but isn't quite expressive enough to create scenes with. Image lighting is more expressive -- you can create an image and draw to it with whatever you want -- but all the convolutions required to make the different roughness levels are prohibitively expensive to animate environment lighting.
6
+
7
+ The solution I'm working with here is based on angular signed distance functions, creating 2D shapes that are mapped onto an infinitely large sphere around a subject.
8
+
9
+ ## Angular SDFs
10
+
11
+ The angular SDFs here take in a surface normal and return two things:
12
+ - **distance**: the distance in radians to the edge of a shape
13
+ - **thickness**: the radius of the largest circle that can be inscribed within the shape
14
+
15
+ ## Builder API
16
+
17
+ ```js
18
+ myShader = buildEnvLightShader(() => {
19
+ envColor.begin()
20
+ const l = envLight(baseColor, envColor.dir, envColor.blur)
21
+ l.mix(l.envCircle(...), lightColor)
22
+ envColor.set(l.get())
23
+ envColor.end()
24
+ })
25
+ ```
26
+
27
+ `envLight(baseColor, dir, blur)` creates a builder. `dir` and `blur` are captured once and used by all subsequent method calls.
28
+
29
+ **Shape methods** (return an SDF result to pass to `mix`):
30
+ - `l.envCircle(center, radius)` - spherical cap; `center` is a unit vec3, `radius` in radians
31
+ - `l.envCapsule(a, b, radius)` - capsule between two unit vec3 endpoints, `radius` in radians
32
+ - `l.envStar(center, n, innerRadius, outerRadius, rotation?)` - n-pointed star; radii in radians, `n` is a plain JS integer
33
+ - `l.envRect(center, size, rotation?)` - rectangle; `size` is `[halfWidth, halfHeight]` as chord lengths (sine of angle, not radians), `rotation` in radians
34
+ - `l.envWindow(center, size, panes, barWidth)` - rectangle subdivided into panes; `panes` is `[nx, ny]`; `size` and `barWidth` are chord lengths (sine of angle, not radians — for small shapes the difference is negligible)
35
+
36
+ **Color methods** (return a scalar/vec3 usable in expressions or as a base color):
37
+ - `l.envGradient(center, ...stops)` - radial gradient; each stop is `{ t, color }` where `t` is angle from `center` in radians and `color` is a vec3; spread stops as individual arguments
38
+ - `l.envNoise(size)` - blur-aware fractal noise value; `size` is the angular scale of the largest octave
39
+ - `l.envNoisePlane(planeNormal, h, size, { rotation?, offset? })` - projects a planar noise field onto the sphere; `h` is the plane's height, `size` is the noise scale; `offset` is a vec2 that shifts the noise coordinate (use for animation)
40
+
41
+ **Builder methods**:
42
+ - `l.mix(shape, color)` - blends `color` into the accumulated result using the shape's SDF; returns `l` for chaining
43
+ - `l.get()` - returns the final accumulated color
44
+
45
+ ## Panorama
46
+
47
+ The same `envColor` hook can also be used to create a version of `panorama()` but using your generative environment, `panoramaEnv`.
48
+
49
+ ```js
50
+ function envHooks() {
51
+ envColor.begin()
52
+ // Draw something here!
53
+ envColor.end()
54
+ }
55
+
56
+ let envShader, panoramaShader
57
+
58
+ function setup() {
59
+ envShader = buildEnvLightShader(envHooks)
60
+ panoramaShader = buildEnvLightPanorama(envHooks)
61
+ }
62
+
63
+ function draw() {
64
+ clear()
65
+ panoramaEnv(panoramaShader)
66
+ shader(envShader)
67
+ sphere(150)
68
+ }
69
+ ```
70
+
71
+ The API is `panoramaEnv(shader, blur = 0)`. Pass a nonzero `blur` (in radians) to draw a blurry background instead of a default clear one.
package/p5.env.js ADDED
@@ -0,0 +1,440 @@
1
+ function envLight(p5, fn) {
2
+ fn.baseEnvLightShader = function() {
3
+ if (!this._baseEnvLightShader) {
4
+ this._baseEnvLightShader = new p5.Shader(
5
+ this.baseMaterialShader()._renderer,
6
+ this.baseMaterialShader()._vertSrc,
7
+ this.baseMaterialShader()._fragSrc,
8
+ {
9
+ declarations: 'vec3 n; vec3 r;',
10
+ vertex: {
11
+ ...this.baseMaterialShader().hooks.vertex,
12
+ },
13
+ fragment: {
14
+ 'vec3 envColor': `(vec3 dir, float blur) { return vec3(0.); }`,
15
+ ...this.baseMaterialShader().hooks.fragment,
16
+ 'Inputs getPixelInputs': `(Inputs inputs) {
17
+ n = inputs.normal * uCameraNormalMatrix;
18
+ vec3 lightDir = normalize(vViewPosition);
19
+ r = reflect(lightDir, inputs.normal) * uCameraNormalMatrix;
20
+ return inputs;
21
+ }`,
22
+ 'vec4 combineColors': `(ColorComponents components) {
23
+ components.diffuse = HOOK_envColor(n, ${PI/2});
24
+ components.specular = HOOK_envColor(r, ${PI/2}/(1. + 0.25 * uShininess));
25
+ vec4 color = vec4(0.);
26
+ color.rgb += components.diffuse * components.baseColor;
27
+ color.rgb += components.ambient * components.ambientColor;
28
+ color.rgb += components.specular * components.specularColor;
29
+ color.rgb += components.emissive;
30
+ color.a = components.opacity;
31
+ return color;
32
+ }`,
33
+ },
34
+ }
35
+ )
36
+ }
37
+ return this._baseEnvLightShader
38
+ }
39
+
40
+ fn.buildEnvLightShader = function(...args) {
41
+ return this.baseEnvLightShader().modify(...args)
42
+ }
43
+
44
+ fn.baseEnvLightPanoramaShader = function() {
45
+ if (!this._baseEnvLightPanoramaShader) {
46
+ this._baseEnvLightPanoramaShader = new p5.Shader(
47
+ this.baseFilterShader()._renderer,
48
+ this.baseFilterShader()._vertSrc,
49
+ this.baseFilterShader()._fragSrc,
50
+ {
51
+ declarations: `
52
+ uniform float uFovY;
53
+ uniform float uAspect;
54
+ uniform mat3 uCameraRotation;
55
+ uniform float uBlur;
56
+ `,
57
+ fragment: {
58
+ 'vec3 envColor': '(vec3 dir, float blur) { return vec3(0.); }',
59
+ ...this.baseFilterShader().hooks.fragment,
60
+ 'vec4 getColor': `(FilterInputs inputs, sampler2D tex0) {
61
+ float fovX = uFovY * uAspect;
62
+ float angleY = mix(uFovY/2.0, -uFovY/2.0, inputs.texCoord.y);
63
+ float angleX = mix(fovX/2.0, -fovX/2.0, inputs.texCoord.x);
64
+ vec3 dir = uCameraRotation * normalize(vec3(angleX, angleY, 1.0));
65
+ return vec4(HOOK_envColor(-dir, uBlur), 1.0);
66
+ }`,
67
+ },
68
+ }
69
+ )
70
+ }
71
+ return this._baseEnvLightPanoramaShader
72
+ }
73
+
74
+ fn.buildEnvLightPanorama = function(...args) {
75
+ return this.baseEnvLightPanoramaShader().modify(...args)
76
+ }
77
+
78
+ fn.panoramaEnv = function(panoramaShader, blur = 0) {
79
+ const renderer = this._renderer
80
+ renderer.scratchMat3.inverseTranspose4x4(renderer.states.uViewMatrix)
81
+ renderer.scratchMat3.invert(renderer.scratchMat3)
82
+ panoramaShader.setUniform('uFovY', renderer.states.curCamera.cameraFOV)
83
+ panoramaShader.setUniform('uAspect', renderer.states.curCamera.aspectRatio)
84
+ panoramaShader.setUniform('uCameraRotation', renderer.scratchMat3.mat3)
85
+ panoramaShader.setUniform('uBlur', Math.min(blur, Math.PI / 2))
86
+ this.filter(panoramaShader)
87
+ }
88
+
89
+ // Color helpers
90
+
91
+ fn.envGradient = function(dir, center, blur, ...stops) {
92
+ dir = p5.strandsNode(dir)
93
+ center = p5.strandsNode(center)
94
+ blur = p5.strandsNode(blur)
95
+
96
+ for (const stop of stops) {
97
+ stop.t = p5.strandsNode(stop.t)
98
+ stop.color = p5.strandsNode(stop.color)
99
+ }
100
+
101
+ let r = this.acos(this.clamp(this.dot(dir, center), -1, 1))
102
+
103
+ // Clamp window to [0, PI] and ensure nonzero width so blur=0 stays safe
104
+ let safeBlur = this.max(blur, 0.0001)
105
+ let a = this.max(0, r.sub(safeBlur))
106
+ let b = this.min(Math.PI, r.add(safeBlur))
107
+
108
+ let totalWeight = p5.strandsNode(0)
109
+ let weightedColor = this.vec3(0, 0, 0)
110
+
111
+ // Region before first stop: extend with its color
112
+ let beforeOverlap = this.max(0, this.min(b, stops[0].t).sub(a))
113
+ weightedColor = weightedColor.add(p5.strandsNode(stops[0].color).mult(beforeOverlap))
114
+ totalWeight = totalWeight.add(beforeOverlap)
115
+
116
+ // Each linear segment: integrate over the blur window overlap
117
+ for (let i = 0; i < stops.length - 1; i++) {
118
+ const t0 = stops[i].t
119
+ const t1 = stops[i + 1].t
120
+ let segLo = this.max(a, t0)
121
+ let segHi = this.min(b, t1)
122
+ let segOverlap = this.max(0, segHi.sub(segLo))
123
+ // Average lerp factor across the overlap is (f_lo + f_hi) / 2
124
+ let segF0 = this.clamp(segLo.sub(t0).div(t1.sub(t0)), 0, 1)
125
+ let segF1 = this.clamp(segHi.sub(t0).div(t1.sub(t0)), 0, 1)
126
+ let segColor = this.mix(
127
+ stops[i].color,
128
+ stops[i + 1].color,
129
+ segF0.add(segF1).div(2)
130
+ )
131
+ weightedColor = weightedColor.add(segColor.mult(segOverlap))
132
+ totalWeight = totalWeight.add(segOverlap)
133
+ }
134
+
135
+ // Region after last stop: extend with its color
136
+ let afterOverlap = this.max(0, b.sub(this.max(a, stops[stops.length - 1].t)))
137
+ weightedColor = weightedColor.add(p5.strandsNode(stops[stops.length - 1].color).mult(afterOverlap))
138
+ totalWeight = totalWeight.add(afterOverlap)
139
+
140
+ return weightedColor.div(totalWeight)
141
+ }
142
+
143
+ // Shape helpers
144
+
145
+ fn.envCircle = function(dir, center, radius) {
146
+ dir = p5.strandsNode(dir)
147
+ center = p5.strandsNode(center)
148
+ radius = p5.strandsNode(radius)
149
+
150
+ return {
151
+ distance: this.acos(this.dot(dir, center)).sub(radius),
152
+ thickness: radius,
153
+ }
154
+ }
155
+
156
+ fn.envCapsule = function(dir, a, b, radius) {
157
+ dir = p5.strandsNode(dir)
158
+ a = p5.strandsNode(a)
159
+ b = p5.strandsNode(b)
160
+ radius = p5.strandsNode(radius)
161
+
162
+ let ba = b.sub(a)
163
+ let h = this.clamp(this.dot(dir.sub(a), ba).div(this.dot(ba, ba)), 0, 1)
164
+ let closest = this.normalize(a.add(ba.mult(h)))
165
+ return {
166
+ distance: this.acos(this.dot(dir, closest)).sub(radius),
167
+ thickness: radius,
168
+ }
169
+ }
170
+
171
+ fn.envNoise = function(dir, size, blur, offset = [0, 0]) {
172
+ // Adjust if p5's noise output is not centered exactly here
173
+ const noiseMean = 0.5
174
+
175
+ dir = p5.strandsNode(dir)
176
+ size = p5.strandsNode(size)
177
+ blur = p5.strandsNode(blur).mult(2)
178
+ offset = p5.strandsNode(offset)
179
+
180
+ // Single-octave raw noise so we can stack octaves ourselves
181
+ this.noiseDetail(1, 0.5)
182
+
183
+ // Adding offset before dividing by size means all octaves drift at the
184
+ // same world-space rate (higher-frequency octaves get a proportionally
185
+ // larger offset in their normalized coordinate space).
186
+ let p = dir.add(offset).div(size)
187
+ let blurRatio = blur.div(size)
188
+
189
+ // Each octave fades out when blur exceeds that octave's angular size
190
+ let f1 = this.max(0, p5.strandsNode(1).sub(blurRatio))
191
+ let f2 = this.max(0, p5.strandsNode(1).sub(blurRatio.mult(2)))
192
+ let f3 = this.max(0, p5.strandsNode(1).sub(blurRatio.mult(4)))
193
+ let f4 = this.max(0, p5.strandsNode(1).sub(blurRatio.mult(8)))
194
+
195
+ // Subtract the mean before weighting so blur only attenuates variation,
196
+ // not the average. Add the mean back once at the end.
197
+ return p5.strandsNode(noiseMean)
198
+ .add(f1.mult(this.noise(p).sub(noiseMean)))
199
+ .add(f2.mult(this.noise(p.mult(2)).sub(noiseMean)).mult(0.5))
200
+ .add(f3.mult(this.noise(p.mult(4)).sub(noiseMean)).mult(0.25))
201
+ .add(f4.mult(this.noise(p.mult(8)).sub(noiseMean)).mult(0.125))
202
+ }
203
+
204
+ fn.envNoisePlane = function(dir, planeNormal, h, size, blur, { rotation = 0, offset = [0, 0] } = {}) {
205
+ dir = p5.strandsNode(dir)
206
+ planeNormal = p5.strandsNode(planeNormal)
207
+ rotation = p5.strandsNode(rotation)
208
+ h = p5.strandsNode(h)
209
+
210
+ let up = p5.strandsTernary(this.abs(planeNormal.y).lt(0.99), this.vec3(0, 1, 0), this.vec3(1, 0, 0))
211
+ let xLocal = this.normalize(this.cross(up, planeNormal))
212
+ let yLocal = this.cross(planeNormal, xLocal)
213
+
214
+ let pn = this.dot(dir, planeNormal)
215
+ let px = this.dot(dir, xLocal)
216
+ let py = this.dot(dir, yLocal)
217
+
218
+ // Rotate in chord space, then perspective-project onto the plane (x/n).
219
+ let cosR = this.cos(rotation)
220
+ let sinR = this.sin(rotation)
221
+ let rpx = cosR.mult(px).add(sinR.mult(py))
222
+ let rpy = cosR.mult(py).sub(sinR.mult(px))
223
+ let invPn = h.div(pn.add(0.001))
224
+ let coord = this.vec2(rpx.mult(invPn), rpy.mult(invPn))
225
+
226
+ // let blurScale = h.div(this.pow(pn, 2).add(0.001))
227
+ // let blurScale = h.div(this.abs(pn).add(0.001))
228
+ let blurScale = h.div(this.pow(this.abs(pn), 1.5).add(0.001))
229
+ return this.mix(
230
+ 0.5,
231
+ this.envNoise(coord.add(1000), size, blur.mult(blurScale), offset),
232
+ this.abs(pn) // Hack for smoother transition
233
+ )
234
+ }
235
+
236
+ fn.envStar = function(dir, center, n, innerRadius, outerRadius, rotation = 0) {
237
+ dir = p5.strandsNode(dir)
238
+ center = p5.strandsNode(center)
239
+ innerRadius = p5.strandsNode(innerRadius)
240
+ outerRadius = p5.strandsNode(outerRadius)
241
+ rotation = p5.strandsNode(rotation)
242
+
243
+ let up = p5.strandsTernary(this.abs(center.y).lt(0.99), this.vec3(0, 1, 0), this.vec3(1, 0, 0))
244
+ let xLocal = this.normalize(this.cross(up, center))
245
+ let yLocal = this.cross(center, xLocal)
246
+
247
+ const an = Math.PI / n
248
+
249
+ // True angular distance from center -- same units as innerRadius/outerRadius
250
+ let r = this.acos(this.clamp(this.dot(dir, center), -1, 1))
251
+
252
+ // Azimuthal angle via a single atan on raw dot products.
253
+ // The atan discontinuity at +-PI is harmless: mod(PI, 2an) == mod(-PI, 2an)
254
+ let a = this.mod(
255
+ this.atan(this.dot(dir, yLocal), this.dot(dir, xLocal)).sub(rotation),
256
+ 2 * an
257
+ )
258
+ let aFolded = this.min(a, p5.strandsNode(2 * an).sub(a))
259
+ let q = r.mult(this.vec2(this.cos(aFolded), this.sin(aFolded)))
260
+
261
+ // Edge from outer tip to inner valley
262
+ let tip = this.vec2(outerRadius, 0)
263
+ let valley = this.vec2(innerRadius.mult(Math.cos(an)), innerRadius.mult(Math.sin(an)))
264
+ let edge = valley.sub(tip)
265
+
266
+ // Signed distance: negative inside the star
267
+ let h = this.clamp(this.dot(q.sub(tip), edge).div(this.dot(edge, edge)), 0, 1)
268
+ let d = this.length(q.sub(tip.add(edge.mult(h))))
269
+ let cross2D = edge.x.mult(q.y).sub(edge.y.mult(q.x.sub(outerRadius)))
270
+ d = d.mult(this.sign(cross2D.mult(-1)))
271
+
272
+ return {
273
+ distance: d,
274
+ thickness: innerRadius,
275
+ }
276
+ }
277
+
278
+ fn.rotate2D = function(p, angle) {
279
+ p = p5.strandsNode(p)
280
+ angle = p5.strandsNode(angle)
281
+
282
+ let c = this.cos(angle)
283
+ let s = this.sin(angle)
284
+
285
+ return this.vec2(
286
+ c.mult(p.x).sub(s.mult(p.y)),
287
+ s.mult(p.x).add(c.mult(p.y))
288
+ )
289
+ }
290
+
291
+ fn.envRect = function(dir, center, size, rotation = 0) {
292
+ dir = p5.strandsNode(dir)
293
+ center = p5.strandsNode(center)
294
+ size = p5.strandsNode(size)
295
+ rotation = p5.strandsNode(rotation)
296
+
297
+ let up = p5.strandsTernary(this.abs(center.y).lessThan(0.99), this.vec3(0, 1, 0), this.vec3(1, 0, 0))
298
+ let xLocal = this.normalize(this.cross(up, center))
299
+ let yLocal = this.cross(center, xLocal)
300
+
301
+ // Raw dot-product coordinates in chord space (sin of angle, not radians).
302
+ let px = this.dot(dir, xLocal)
303
+ let py = this.dot(dir, yLocal)
304
+
305
+ let cosR = this.cos(rotation)
306
+ let sinR = this.sin(rotation)
307
+ let rpx = cosR.mult(px).add(sinR.mult(py))
308
+ let rpy = cosR.mult(py).sub(sinR.mult(px))
309
+
310
+ let half = size.mult(0.5)
311
+ let q = this.abs(this.vec2(rpx, rpy)).sub(half)
312
+ let d = this.length(this.max(q, 0)).add(this.min(this.max(q.x, q.y), 0))
313
+
314
+ // Clip to front hemisphere to prevent a false rect at the antipodal point.
315
+ let hemiD = this.acos(this.clamp(this.dot(dir, center), -1, 1)).sub(Math.PI / 2)
316
+ d = this.max(d, hemiD)
317
+
318
+ return {
319
+ distance: d,
320
+ thickness: this.min(half.x, half.y),
321
+ }
322
+ }
323
+
324
+ fn.envWindow = function(dir, center, size, panes, barWidth) {
325
+ dir = p5.strandsNode(dir)
326
+ center = p5.strandsNode(center)
327
+ size = p5.strandsNode(size)
328
+ panes = p5.strandsNode(panes)
329
+ barWidth = p5.strandsNode(barWidth)
330
+
331
+ let up = p5.strandsTernary(this.abs(center.y).lessThan(0.99), this.vec3(0, 1, 0), this.vec3(1, 0, 0))
332
+ let xLocal = this.normalize(this.cross(up, center))
333
+ let yLocal = this.cross(center, xLocal)
334
+
335
+ // Raw dot-product coordinates in chord space (sin of angle, not radians).
336
+ let p = this.vec2(this.dot(dir, xLocal), this.dot(dir, yLocal))
337
+
338
+ let half = size.mult(0.5)
339
+
340
+ let q = this.abs(p).sub(half)
341
+ let outerD = this.length(this.max(q, 0)).add(this.min(this.max(q.x, q.y), 0))
342
+
343
+ let cell = this.vec2(
344
+ size.x.div(panes.x.sub(1)),
345
+ size.y.div(panes.y.sub(1))
346
+ )
347
+
348
+ // position in grid space
349
+ let g = p.add(half)
350
+
351
+ // local position within a cell
352
+ let cellP = this.mod(g, cell).sub(cell.mult(0.5))
353
+
354
+ // distance to nearest vertical/horizontal bar centerlines
355
+ let barLocal = this.min(
356
+ this.abs(cellP.x),
357
+ this.abs(cellP.y)
358
+ ).sub(barWidth.mult(0.5))
359
+
360
+ // final SDF: window clipped, then subtract bars
361
+ let d = this.max(outerD, barLocal.mult(-1))
362
+
363
+ // Clip to front hemisphere. Back-hemisphere directions also produce small
364
+ // chord coordinates and would create a false window at the antipodal point
365
+ // without this
366
+ let hemiD = this.acos(this.clamp(this.dot(dir, center), -1, 1)).sub(Math.PI / 2)
367
+ d = this.max(d, hemiD)
368
+
369
+ // thickness = size of a single pane (not full window!)
370
+ let paneSize = this.vec2(
371
+ cell.x.sub(barWidth),
372
+ cell.y.sub(barWidth)
373
+ )
374
+
375
+ return {
376
+ distance: d,
377
+ thickness: this.min(paneSize.x, paneSize.y).mult(0.5),
378
+ }
379
+ }
380
+
381
+ fn.mixEnv = function(result, materialColor, c, blur) {
382
+ c = p5.strandsNode(c)
383
+ materialColor = p5.strandsNode(materialColor)
384
+ blur = p5.strandsNode(blur)
385
+
386
+ let d = result.distance
387
+ let thickness = result.thickness
388
+ let mixAmt = p5.strandsTernary(
389
+ blur.equalTo(0),
390
+ this.step(0, d.mult(-1)),
391
+ this.map(d.sub(blur.div(2)), blur.mult(-1), blur, 1, 0, true)
392
+ )
393
+ let fade = this.min(1, thickness.div(blur))
394
+ mixAmt = mixAmt.mult(fade)
395
+ return this.mix(c, materialColor, mixAmt)
396
+ }
397
+
398
+ fn.envLight = function(baseColor, dir, blur) {
399
+ const sketch = this
400
+ let c = p5.strandsNode(baseColor)
401
+
402
+ return {
403
+ envCircle(center, radius) {
404
+ return sketch.envCircle(dir, center, radius)
405
+ },
406
+ envCapsule(a, b, radius) {
407
+ return sketch.envCapsule(dir, a, b, radius)
408
+ },
409
+ envStar(center, n, innerRadius, outerRadius, rotation) {
410
+ return sketch.envStar(dir, center, n, innerRadius, outerRadius, rotation)
411
+ },
412
+ envRect(center, size, rotation) {
413
+ return sketch.envRect(dir, center, size, rotation)
414
+ },
415
+ envWindow(center, size, panes, barWidth) {
416
+ return sketch.envWindow(dir, center, size, panes, barWidth)
417
+ },
418
+ envNoise(size) {
419
+ return sketch.envNoise(dir, size, blur)
420
+ },
421
+ envGradient(center, ...stops) {
422
+ return sketch.envGradient(dir, center, blur, ...stops)
423
+ },
424
+ envNoisePlane(planeNormal, h, size, { rotation = 0, offset = [0, 0] } = {}) {
425
+ return sketch.envNoisePlane(dir, planeNormal, h, size, blur, { rotation, offset })
426
+ },
427
+ mix(shape, materialColor) {
428
+ c = sketch.mixEnv(shape, materialColor, c, blur)
429
+ return this
430
+ },
431
+ get() {
432
+ return c
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ if (typeof p5 !== 'undefined') {
439
+ p5.registerAddon(envLight)
440
+ }
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "p5.env",
3
+ "version": "0.0.1",
4
+ "description": "Generative environment light for p5.strands",
5
+ "main": "p5.env.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "Dave Pagurek",
10
+ "license": "MIT",
11
+ "files": [
12
+ "p5.env.js"
13
+ ]
14
+ }