Skip to content

Commit c449faa

Browse files
committed
Add more type tests, fix docs issue
1 parent c88e621 commit c449faa

4 files changed

Lines changed: 360 additions & 10 deletions

File tree

docs/parameterData.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3583,10 +3583,10 @@
35833583
"updatePixels": {
35843584
"overloads": [
35853585
[
3586-
"Integer",
3587-
"Integer",
3588-
"Integer",
3589-
"Integer"
3586+
"Integer?",
3587+
"Integer?",
3588+
"Integer?",
3589+
"Integer?"
35903590
]
35913591
]
35923592
},

src/image/p5.Image.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,12 @@ class Image {
209209
* If the image was loaded from a GIF, then calling `img.updatePixels()`
210210
* will update the pixels in current frame.
211211
*
212-
* @param {Integer} x x-coordinate of the upper-left corner
213-
* of the subsection to update.
214-
* @param {Integer} y y-coordinate of the upper-left corner
215-
* of the subsection to update.
216-
* @param {Integer} w width of the subsection to update.
217-
* @param {Integer} h height of the subsection to update.
212+
* @param {Integer} [x] x-coordinate of the upper-left corner
213+
* of the subsection to update.
214+
* @param {Integer} [y] y-coordinate of the upper-left corner
215+
* of the subsection to update.
216+
* @param {Integer} [w] width of the subsection to update.
217+
* @param {Integer} [h] height of the subsection to update.
218218
*
219219
* @example
220220
* <div>

test/types/basic.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Modified from https://openprocessing.org/sketch/2500100
2+
13
import '../../types/global'
24

35
p5.disableFriendlyErrors = true

test/types/webgl-pixels.ts

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
// From https://openprocessing.org/sketch/2308573
2+
3+
import '../../types/global'
4+
// Fake matter.js import
5+
declare const Matter: any
6+
7+
let engine: any
8+
let blobs: Blob[] = []
9+
let metaballShader: p5.Shader
10+
let spheremap: p5.Image
11+
let renderer: p5.RendererGL
12+
13+
async function setup() {
14+
spheremap = await loadImage('https://deckard.openprocessing.org/user67809/visual2181338/h987a85d77bacbc3b232fb87ce6fe440a/dusseldorf_bridge.jpg')
15+
renderer = createCanvas(600, 600, WEBGL)
16+
metaballShader = createShader(vert, frag)
17+
setupScene()
18+
blobs.push(new Blob(random(-1,1)*100, 50, 100, '#f3e17e'))
19+
blobs.push(new Blob(random(-1,1)*100, -150, 100, '#dd483c'))
20+
blobs.push(new Blob(random(-1,1)*100, -350, 50, '#4b8a5f'))
21+
blobs.push(new Blob(random(-1,1)*100, -550, 50, '#0d150b'))
22+
}
23+
24+
function setupScene() {
25+
engine = Matter.Engine.create()
26+
27+
const ground = Matter.Bodies.rectangle(0, height / 2 + 30, width, 60, {
28+
isStatic: true,
29+
})
30+
const wallLeft = Matter.Bodies.rectangle(-width/2 - 30, 0, 60, 3 * height, {
31+
isStatic: true,
32+
})
33+
const wallRight = Matter.Bodies.rectangle(width/2 + 30, 0, 60, 3 * height, {
34+
isStatic: true,
35+
})
36+
Matter.World.add(engine.world, [ground, wallLeft, wallRight])
37+
}
38+
39+
function draw() {
40+
background('#faf8e2')
41+
// translate(width/2, height/2)
42+
43+
for (const blob of blobs) {
44+
blob.update()
45+
}
46+
Matter.Engine.update(engine, 1000 / 60)
47+
48+
for (const blob of blobs) {
49+
blob.drawBlob()
50+
}
51+
}
52+
53+
const BLOB_NODE_SIZE = 20
54+
const BLOB_NODE_R = 15
55+
const BLOB_NODE_AREA = Math.PI * BLOB_NODE_SIZE * BLOB_NODE_SIZE
56+
57+
class Blob {
58+
c: p5.Color
59+
nodes: any[]
60+
springs: any[]
61+
tex: p5.Image
62+
63+
constructor(x, y, r, c) {
64+
this.nodes = []
65+
this.springs = []
66+
this.c = color(c)
67+
this.tex = createImage(20, 20)
68+
this.tex.loadPixels()
69+
for (let i = 0; i < this.tex.pixels.length; i++) {
70+
this.tex.pixels[i] = 255
71+
}
72+
// @ts-ignore
73+
renderer.getTexture(this.tex).setInterpolation(NEAREST, NEAREST)
74+
75+
const a = PI * r * r
76+
const numBlobs = ceil(a / BLOB_NODE_AREA)
77+
78+
while (this.nodes.length < numBlobs) {
79+
const rx = random(-r, r)
80+
const ry = random(-r, r)
81+
if (Math.hypot(rx, ry) > r) continue
82+
83+
const vert = Matter.Bodies.circle(x + rx, y + ry, BLOB_NODE_R, { inertia: Infinity, friction: 0.015 })
84+
this.nodes.push(vert)
85+
}
86+
87+
Matter.World.add(engine.world, this.nodes)
88+
}
89+
90+
bin(x: number, y: number) {
91+
return [round(x/80), round(y/80)]
92+
}
93+
94+
nodeBin(node) {
95+
return this.bin(node.position.x, node.position.y)
96+
}
97+
98+
adjacentBins(node) {
99+
const [x, y] = this.nodeBin(node)
100+
const bins: [number, number][] = []
101+
for (const dx of [-1, 0, 1]) {
102+
for (const dy of [-1, 0, 1]) {
103+
bins.push([x + dx, y + dy])
104+
}
105+
}
106+
return bins
107+
}
108+
109+
binKey(bin) {
110+
return bin.join(',')
111+
}
112+
113+
binnedNodes() {
114+
const bins = {}
115+
for (const node of this.nodes) {
116+
const binKey = this.binKey(this.nodeBin(node))
117+
if (!bins[binKey]) {
118+
bins[binKey] = []
119+
}
120+
bins[binKey].push(node)
121+
}
122+
return bins
123+
}
124+
125+
update() {
126+
Matter.World.remove(engine.world, this.springs)
127+
this.springs = []
128+
const bins = this.binnedNodes()
129+
for (const node of this.nodes) {
130+
const binsToCheck = this.adjacentBins(node)
131+
for (const bin of binsToCheck) {
132+
const key = this.binKey(bin)
133+
if (!bins[key]) continue
134+
for (const other of bins[key]) {
135+
if (other === node) continue
136+
this.springs.push(Matter.Constraint.create({
137+
bodyA: node,
138+
pointA: { x: 0, y: 0 },
139+
bodyB: other,
140+
pointB: { x: 0, y: 0 },
141+
stiffness: map(
142+
Math.hypot(node.position.x - other.position.x, node.position.y - other.position.y),
143+
0, 12*BLOB_NODE_SIZE,
144+
0.02, 0.03,
145+
true
146+
),
147+
damping: 0.001,
148+
// length: 0,
149+
length: max(
150+
2 * BLOB_NODE_SIZE,
151+
Math.hypot(node.position.x - other.position.x, node.position.y - other.position.y) * 0.975
152+
),
153+
}))
154+
}
155+
}
156+
}
157+
Matter.World.add(engine.world, this.springs)
158+
}
159+
160+
drawBlob() {
161+
const minX = Math.min(...this.nodes.map((n) => n.position.x)) - 4 * BLOB_NODE_SIZE
162+
const maxX = Math.max(...this.nodes.map((n) => n.position.x)) + 4 * BLOB_NODE_SIZE
163+
const minY = Math.min(...this.nodes.map((n) => n.position.y)) - 4 * BLOB_NODE_SIZE
164+
const maxY = Math.max(...this.nodes.map((n) => n.position.y)) + 4 * BLOB_NODE_SIZE
165+
const x = (maxX + minX)/2
166+
const y = (maxY + minY)/2
167+
const w = maxX - minX
168+
const h = maxY - minY
169+
170+
this.nodes.forEach((node, i) => {
171+
this.tex.pixels[i * 4 + 0] = map(node.position.x, minX, maxX, 0, 255, true)
172+
this.tex.pixels[i * 4 + 1] = map(node.position.y, minY, maxY, 0, 255, true)
173+
})
174+
this.tex.updatePixels()
175+
176+
push()
177+
translate(x, y)
178+
noStroke()
179+
shader(metaballShader)
180+
metaballShader.setUniform('bbox', [minX, minY, maxX, maxY])
181+
metaballShader.setUniform('k', BLOB_NODE_SIZE * 3)
182+
metaballShader.setUniform('numNodes', this.nodes.length)
183+
metaballShader.setUniform('data', this.tex)
184+
metaballShader.setUniform('r', BLOB_NODE_R)
185+
// TODO: make this a public API
186+
// @ts-ignore
187+
metaballShader.setUniform('c', this.c.array())
188+
metaballShader.setUniform('spheremap', spheremap)
189+
plane(w, h)
190+
pop()
191+
}
192+
193+
draw2D() {
194+
fill(this.c)
195+
stroke(this.c)
196+
strokeWeight(2 * BLOB_NODE_R)
197+
strokeJoin(ROUND)
198+
const hull = convexHull(this.nodes.map(n => n.position))
199+
beginShape()
200+
for (const { x, y } of hull) vertex(x, y)
201+
endShape(CLOSE)
202+
203+
noStroke()
204+
fill(0)
205+
for (const node of this.nodes) {
206+
circle(node.position.x, node.position.y, BLOB_NODE_R * 2)
207+
}
208+
}
209+
}
210+
211+
let vert = `#version 300 es
212+
precision highp float;
213+
214+
in vec3 aPosition;
215+
in vec2 aTexCoord;
216+
217+
uniform mat4 uModelViewMatrix;
218+
uniform mat4 uProjectionMatrix;
219+
220+
out vec2 vTexCoord;
221+
222+
void main() {
223+
// Apply the camera transform
224+
vec4 viewModelPosition =
225+
uModelViewMatrix *
226+
vec4(aPosition, 1.0);
227+
228+
// Tell WebGL where the vertex goes
229+
gl_Position =
230+
uProjectionMatrix *
231+
viewModelPosition;
232+
233+
// Pass along data to the fragment shader
234+
vTexCoord = aTexCoord;
235+
}`
236+
237+
let frag = `#version 300 es
238+
precision highp float;
239+
240+
in vec2 vTexCoord;
241+
out vec4 fragColor;
242+
243+
uniform sampler2D data;
244+
uniform sampler2D spheremap;
245+
uniform vec4 bbox;
246+
uniform int numNodes;
247+
uniform float k;
248+
uniform float r;
249+
uniform vec4 c;
250+
251+
float opSmoothUnion( float d1, float d2, float k )
252+
{
253+
float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
254+
return mix( d2, d1, h ) - k*h*(1.0-h);
255+
}
256+
257+
vec2 nodeCoord(int i) {
258+
float x = fract(float(i)/20.) + 1./40.;
259+
float y = floor(float(i)/20.)/20. + 1./40.;
260+
vec2 pos = texture(data, vec2(x, y)).xy;
261+
return mix(bbox.xy, bbox.zw, pos);
262+
}
263+
264+
void main() {
265+
vec2 coord = mix(bbox.xy, bbox.zw, vTexCoord);
266+
267+
float dist = 100000.;
268+
float avg = 0.;
269+
float total = 0.;
270+
for (int i = 0; i < 400; i++) {
271+
if (i >= numNodes) break;
272+
273+
float dist2 = length(coord - nodeCoord(i)) - r;
274+
avg += pow(dist2, 2.);
275+
total += 1.;
276+
dist = opSmoothUnion(
277+
dist,
278+
dist2,
279+
k
280+
);
281+
// dist = min(dist, dist2);
282+
}
283+
avg /= total;
284+
// vec3 pos = vec3(coord, avg * 0.05);
285+
vec3 pos = vec3(coord, -40.*smoothstep(0., 2., pow(-dist, .1)));
286+
287+
vec3 normal = -normalize(cross(dFdx(pos), dFdy(pos)));
288+
289+
vec3 fromCam = normalize(pos - vec3(0., 0., -800.0));
290+
vec3 n = reflect(
291+
fromCam,
292+
normal
293+
);
294+
float phi = acos( n.y );
295+
float theta = 0.0;
296+
theta = acos(n.x / sin(phi));
297+
float sinTheta = n.z / sin(phi);
298+
if (sinTheta < 0.0) {
299+
// Turn it into -theta, but in the 0-2PI range
300+
theta = 2.0 * 3.14159 - theta;
301+
}
302+
theta = theta / (2.0 * 3.14159);
303+
phi = phi / 3.14159 ;
304+
vec2 angles = vec2( fract(theta + 0.25), 1.0 - phi );
305+
306+
vec3 lightDir = normalize(vec3(-0.3, 0.9, 0.));
307+
float l = 0.15 * max(0., dot(lightDir, normal)) + 0.85;
308+
vec3 outColor = c.xyz * l + pow(texture(spheremap, angles).xyz, vec3(4.));
309+
// outColor = vec3(smoothstep(0., 2., pow(-dist, .15)));
310+
// outColor = normal;
311+
312+
fragColor = vec4(outColor, 1.) * (1. - smoothstep(0., 0.01, dist));
313+
}`
314+
315+
const comparison = (a: p5.Vector, b: p5.Vector) => {
316+
return a.x == b.x ? a.y - b.y : a.x - b.x
317+
}
318+
319+
const cross = (a: p5.Vector, b: p5.Vector, o: p5.Vector) => {
320+
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)
321+
}
322+
323+
function convexHull(points: p5.Vector[]) {
324+
points.sort(comparison)
325+
const L: p5.Vector[] = []
326+
for (let i = 0; i < points.length; i++) {
327+
while (
328+
L.length >= 2 &&
329+
cross(L[L.length - 2], L[L.length - 1], points[i]) <= 0
330+
) {
331+
L.pop()
332+
}
333+
L.push(points[i])
334+
}
335+
const U: p5.Vector[] = []
336+
for (let i = points.length - 1; i >= 0; i--) {
337+
while (
338+
U.length >= 2 &&
339+
cross(U[U.length - 2], U[U.length - 1], points[i]) <= 0
340+
) {
341+
U.pop()
342+
}
343+
U.push(points[i])
344+
}
345+
L.pop()
346+
U.pop()
347+
return L.concat(U)
348+
}

0 commit comments

Comments
 (0)