Skip to content

Commit d3c29c8

Browse files
committed
Implement workers, transparency, priority image, lil-gui
1 parent e483e9f commit d3c29c8

5 files changed

Lines changed: 583 additions & 445 deletions

File tree

genetic_art/factory.js

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
function random(min, max) {
2+
return min + Math.random() * (max - min)
3+
}
4+
5+
function randomChoice(list) {
6+
return list[Math.floor(Math.random() * list.length)]
7+
}
8+
9+
function clamp(x, min, max) {
10+
[min, max] = [Math.min(min, max), Math.max(min, max)]
11+
return Math.min(max, Math.max(min, x))
12+
}
13+
14+
function rgb2hex(red, green, blue) {
15+
const rgb = blue | (green << 8) | (red << 16)
16+
return '#' + (0x1000000 + rgb).toString(16).slice(1)
17+
}
18+
19+
export class PolygonFactory {
20+
constructor(width, height, options) {
21+
this.width = width
22+
this.height = height
23+
this.options = options
24+
}
25+
26+
generatePoint() {
27+
if (this.options.restriction.enable) {
28+
const sides = this.options.restriction.sides
29+
const angle = this.options.restriction.angle
30+
const radius = Math.min(this.width, this.height) / 2
31+
32+
const i1 = Math.floor(Math.random() * sides)
33+
const i2 = (i1 + 1) % sides
34+
35+
const x1 = Math.cos(i1 / sides * 2 * Math.PI + angle) * radius + this.width / 2
36+
const y1 = Math.sin(i1 / sides * 2 * Math.PI + angle) * radius + this.height / 2
37+
38+
const x2 = Math.cos(i2 / sides * 2 * Math.PI + angle) * radius + this.width / 2
39+
const y2 = Math.sin(i2 / sides * 2 * Math.PI + angle) * radius + this.height / 2
40+
41+
const t = Math.random()
42+
const x = (1 - t) * x1 + t * x2
43+
const y = (1 - t) * y1 + t * y2
44+
45+
return [x, y]
46+
} else {
47+
return [random(0, this.width), random(0, this.height)]
48+
}
49+
}
50+
51+
tweakPoint(point) {
52+
if (this.options.restriction.enable) {
53+
return this.generatePoint()
54+
} else {
55+
return [clamp(point[0] + random(-this.width / 10, this.width / 10), 0, this.width),
56+
clamp(point[1] + random(-this.height / 10, this.height / 10), 0, this.height)]
57+
}
58+
}
59+
60+
generate() {
61+
const points = []
62+
const sides = Math.floor(random(this.options.minSides, this.options.maxSides))
63+
64+
for (let i = 0; i < sides; i++) {
65+
points.push(this.generatePoint())
66+
}
67+
68+
return {
69+
r: Math.floor(Math.random() * 256),
70+
g: Math.floor(Math.random() * 256),
71+
b: Math.floor(Math.random() * 256),
72+
a: this.options.transparent ? Math.random() : 1,
73+
points
74+
}
75+
}
76+
77+
tweak(polygon) {
78+
const newPolygon = { ...polygon, points: polygon.points.slice() }
79+
const r = random(0, polygon.points.length + 4)
80+
if (r < 3) {
81+
const color = randomChoice(['r', 'g', 'b'])
82+
newPolygon[color] = clamp(polygon[color] + random(-25, 25), 0, 255)
83+
} else if (r < 4 && this.options.transparent) {
84+
newPolygon.a = clamp(polygon.a + random(-0.1, 0.1), 0, 1)
85+
} else {
86+
const i = Math.floor(Math.random() * polygon.points.length)
87+
if (polygon.points.length > this.options.minSides && Math.random() < 0.1) {
88+
newPolygon.points.splice(i, 1)
89+
} else if (polygon.points.length < this.options.maxSides && Math.random() < 0.1) {
90+
newPolygon.points.splice(i, 0, this.generatePoint())
91+
} else if (Math.random() < 0.5) {
92+
const j = Math.floor(Math.random() * polygon.points.length)
93+
newPolygon.points[i] = polygon.points[j]
94+
newPolygon.points[j] = polygon.points[i]
95+
} else {
96+
newPolygon.points[i] = this.tweakPoint(polygon.points[i])
97+
}
98+
}
99+
100+
return newPolygon
101+
}
102+
103+
draw(ctx, polygon) {
104+
const style = `rgba(${polygon.r}, ${polygon.g}, ${polygon.b}, ${polygon.a})`
105+
ctx.strokeStyle = ctx.fillStyle = style
106+
107+
ctx.beginPath()
108+
ctx.moveTo(polygon.points[0][0], polygon.points[0][1])
109+
for (let i = 0; i < polygon.points.length; i++) {
110+
ctx.lineTo(polygon.points[i][0], polygon.points[i][1])
111+
}
112+
113+
if (polygon.points.length === 2) {
114+
ctx.stroke()
115+
} else {
116+
ctx.fill()
117+
}
118+
}
119+
120+
svg(polygon) {
121+
const style = rgb2hex(polygon.r, polygon.g, polygon.b)
122+
const points = polygon.points.map(([x, y]) => `${x},${y}`)
123+
return `<polygon points="${points.join(' ')}" fill="${style}" opacity="${polygon.a}"/>`
124+
}
125+
}
126+
127+
export class RegularPolygonFactory extends PolygonFactory {
128+
generate() {
129+
return {
130+
r: Math.floor(Math.random() * 256),
131+
g: Math.floor(Math.random() * 256),
132+
b: Math.floor(Math.random() * 256),
133+
a: this.options.transparent ? Math.random() : 1,
134+
sides: Math.floor(random(this.options.minSides, this.options.maxSides)),
135+
x: random(0, this.width), y: random(0, this.height),
136+
radius: random(this.options.minRadius, this.options.maxRadius),
137+
angle: random(0, 2 * Math.PI)
138+
}
139+
}
140+
141+
tweak(polygon) {
142+
const newPolygon = { ...polygon }
143+
const r = random(0, 10)
144+
if (r < 4 && (polygon.sides > this.options.minSides || polygon.sides < this.options.maxSides)) {
145+
if (polygon.sides > this.options.minSides) {
146+
newPolygon.sides--
147+
} else if (polygon.sides < this.options.maxSides) {
148+
newPolygon.sides++
149+
}
150+
} else if (r < 6) {
151+
const color = randomChoice(['r', 'g', 'b'])
152+
newPolygon[color] = clamp(polygon[color] + random(-25, 25), 0, 255)
153+
} else if (r < 7 && this.options.transparent) {
154+
newPolygon.a = clamp(polygon.a + random(-0.1, 0.1), 0, 1)
155+
} else {
156+
newPolygon.angle = (polygon.angle + random(-0.1, 0.1)) % (2 * Math.PI)
157+
}
158+
159+
return newPolygon
160+
}
161+
162+
getPoint(polygon, i) {
163+
const rot = i / polygon.sides * 2 * Math.PI + polygon.angle
164+
return [
165+
Math.cos(rot) * polygon.radius + polygon.x,
166+
Math.sin(rot) * polygon.radius + polygon.y
167+
]
168+
}
169+
170+
draw(ctx, polygon) {
171+
const style = `rgba(${polygon.r}, ${polygon.g}, ${polygon.b}, ${polygon.a})`
172+
ctx.strokeStyle = ctx.fillStyle = style
173+
174+
ctx.beginPath()
175+
if (polygon.sides === 50) {
176+
ctx.arc(polygon.x, polygon.y, polygon.radius, 0, 2 * Math.PI)
177+
} else {
178+
for (let i = 0; i < polygon.sides; i++) {
179+
const [x, y] = this.getPoint(polygon, i)
180+
if (i === 0) {
181+
ctx.moveTo(x, y)
182+
} else {
183+
ctx.lineTo(x, y)
184+
}
185+
}
186+
}
187+
188+
if (polygon.sides === 2) {
189+
ctx.stroke()
190+
} else {
191+
ctx.fill()
192+
}
193+
}
194+
195+
svg(polygon) {
196+
const style = rgb2hex(polygon.r, polygon.g, polygon.b)
197+
198+
if (polygon.sides === 50) {
199+
return `<circle cx="${polygon.x}" cy="${polygon.y}" r="${polygon.radius}" fill="${style}" />`
200+
} else {
201+
const points = []
202+
for (let i = 0; i < polygon.sides; i++) {
203+
const [x, y] = this.getPoint(polygon, i)
204+
points.push(`${x},${y}`)
205+
}
206+
return `<polygon points="${points.join(' ')}" fill="${style}" opacity="${polygon.a}"/>`
207+
}
208+
}
209+
}
210+
211+
export class TextureFactory {
212+
constructor(width, height, options, textureCanvas) {
213+
this.width = width
214+
this.height = height
215+
this.options = options
216+
this.textureCanvas = textureCanvas
217+
}
218+
219+
generate() {
220+
return {
221+
a: this.options.transparent ? Math.random() : 1,
222+
x: random(0, this.width),
223+
y: random(0, this.height),
224+
radius: random(this.options.minRadius, this.options.maxRadius),
225+
angle: random(0, 2 * Math.PI)
226+
}
227+
}
228+
229+
tweak(texture) {
230+
const newTexture = { ...texture }
231+
232+
const r = random(0, 5)
233+
if (r < 1 && this.options.transparent) {
234+
newTexture.a = clamp(texture.a + random(-0.1, 0.1), 0, 1)
235+
} else if (r < 2) {
236+
newTexture.x = clamp(texture.x + random(-0.1, 0.1) * this.width, -texture.radius, this.width + texture.radius)
237+
} else if (r < 3) {
238+
newTexture.y = clamp(texture.y + random(-0.1, 0.1) * this.height, -texture.radius, this.height + texture.radius)
239+
} else if (r < 4) {
240+
newTexture.radius = clamp(texture.radius + random(this.options.minRadius, this.options.maxRadius) / 10, this.options.minRadius, this.options.maxRadius)
241+
} else {
242+
newTexture.angle = (texture.angle + random(-0.1, 0.1)) % (2 * Math.PI)
243+
}
244+
245+
return newTexture
246+
}
247+
248+
draw(ctx, texture) {
249+
ctx.save()
250+
ctx.globalAlpha = texture.a
251+
ctx.translate(texture.x, texture.y)
252+
ctx.rotate(texture.angle)
253+
254+
let width = texture.radius
255+
let height = texture.radius
256+
257+
if (texture.width > texture.height) {
258+
height *= this.textureCanvas.height / this.textureCanvas.width
259+
} else {
260+
width *= this.textureCanvas.width / this.textureCanvas.height
261+
}
262+
263+
ctx.drawImage(this.textureCanvas, -width / 2, -height / 2, width, height)
264+
ctx.restore()
265+
}
266+
}

0 commit comments

Comments
 (0)