├── .gitignore
├── CNAME
├── README.md
├── package.json
├── index.html
├── rollup.config.js
├── utils.js
├── main.js
├── main-multi.js
└── dist
├── bundle.js
└── bundle-multi.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | assembly.generated.space
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Apparatus Assembly
2 |
3 | Utilizing the Apparatus Generator in an animated example.
4 |
5 | Demo: https://kgolid.github.io/apparatus-assembly/
6 |
7 | (Press 's' to toggle symmetric assembly on/off)
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apparatus-assembly",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dist/bundle.js",
6 | "mainMulti": "dist/bundle-multi.js",
7 | "keywords": [],
8 | "author": "Kjetil Golid",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/kgolid/apparatus-assemply.git"
13 | },
14 | "dependencies": {
15 | "apparatus-generator": "^1.3.1",
16 | "chromotome": "^1.0.4"
17 | },
18 | "devDependencies": {
19 | "rollup": "^0.64.1",
20 | "rollup-plugin-commonjs": "^9.2.0",
21 | "rollup-plugin-node-resolve": "^3.4.0"
22 | },
23 | "scripts": {
24 | "build": "rollup -c",
25 | "dev": "rollup -c -w"
26 | },
27 | "files": [
28 | "dist"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 | Apparatus Assembly
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import pkg from './package.json';
4 |
5 | export default [
6 | // browser-friendly UMD build
7 | {
8 | input: 'main.js',
9 | output: {
10 | name: 'apparatus-assembly',
11 | file: pkg.main,
12 | format: 'umd'
13 | },
14 | plugins: [
15 | resolve(), // so Rollup can find `ms`
16 | commonjs() // so Rollup can convert `ms` to an ES module
17 | ]
18 | },
19 | {
20 | input: 'main-multi.js',
21 | output: {
22 | name: 'multi-apparatus-assembly',
23 | file: pkg.mainMulti,
24 | format: 'umd'
25 | },
26 | plugins: [
27 | resolve(), // so Rollup can find `ms`
28 | commonjs() // so Rollup can convert `ms` to an ES module
29 | ]
30 | }
31 | ];
32 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | // List utils
2 |
3 | function contains(xs, y, eq) {
4 | return xs.some(x => eq(x, y));
5 | }
6 |
7 | function union(xs, ys, eq) {
8 | let zs = [];
9 | xs.forEach(x => zs.push(x));
10 | ys.forEach(y => {
11 | if (!contains(xs, y, eq)) zs.push(y);
12 | });
13 | return zs;
14 | }
15 |
16 | function flatten(xs, eq) {
17 | return xs.reduce((acc, val) => union(acc, val, eq), []);
18 | }
19 |
20 | function get_random_from(list) {
21 | return list[Math.floor(Math.random() * list.length)]
22 | }
23 |
24 | // ---- Direction utils ----
25 |
26 | // Get random direction among n choices.
27 | function random_dir(n) {
28 | if (n === 2) return Math.random() > 0.5 ? 0 : 2; // Up or down (50/50)
29 | if (n === 3) return Math.random() > 0.5 ? 1 : Math.random() > 0.5 ? 2 : 0; // Up, right or down (25/50/25)
30 | return Math.floor(Math.random() * n); // Up, right, down or left (25/25/25/25)
31 | }
32 |
33 | // Get horizontally mirrored direction.
34 | function mirror(dir) {
35 | if (is_vertical(dir)) return dir;
36 | if (dir == 1) return 3;
37 | if (dir == 3) return 1;
38 | }
39 |
40 | // Check wether direction is vertical.
41 | function is_vertical(dir) {
42 | return dir == 0 || dir == 2;
43 | }
44 |
45 | export { contains, union, flatten, get_random_from, random_dir, mirror, is_vertical };
46 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | import ApparatusGenerator from 'apparatus-generator';
2 | import * as tome from 'chromotome';
3 | import * as ut from './utils';
4 |
5 | const sketch = p => {
6 | let app_gen, apparatus;
7 | let scale = 6;
8 | let shuffle = 220;
9 | let tick = 0;
10 | let final_frame_duration = 25;
11 | let symmetric_assembly = true;
12 | let movement_length = 0.82;
13 |
14 | p.setup = () => {
15 | p.createCanvas(800, 800);
16 | p.background('#eee8e2');
17 | p.fill(0);
18 | p.frameRate(30);
19 | p.strokeWeight(3);
20 | p.stroke('#5c3936');
21 | app_gen = new ApparatusGenerator(26, 36, {
22 | solidness: 0.5,
23 | initiate_chance: 0.9,
24 | extension_chance: 0.86,
25 | vertical_chance: 0.5,
26 | roundness: 0,
27 | group_size: 0.82,
28 | colors: tome.get('retro-washedout').colors
29 | });
30 |
31 | setup_apparatus();
32 | };
33 |
34 | function setup_apparatus() {
35 | symmetric_assembly = true;
36 | apparatus = app_gen.generate();
37 | populate_apparatus(apparatus);
38 |
39 | let chosen, origin, direction;
40 | let start_from_new_part = true;
41 | for (let i = final_frame_duration; i < shuffle; i++) {
42 | if (i === shuffle / 2) symmetric_assembly = false;
43 |
44 | apparatus.forEach(part => {
45 | part.path.push({ x: part.x1, y: part.y1 });
46 | });
47 | if (start_from_new_part) {
48 | chosen = ut.get_random_from(apparatus);
49 | origin = symmetric_assembly
50 | ? get_with_id(apparatus, chosen.id)
51 | : [chosen];
52 | direction =
53 | symmetric_assembly && origin.length === 1
54 | ? ut.random_dir(2)
55 | : ut.random_dir(symmetric_assembly ? 3 : 4);
56 | }
57 | start_from_new_part = p.random() > movement_length;
58 |
59 | if (ut.is_vertical(direction) || !symmetric_assembly) {
60 | let neighborhood = get_neighborhood(origin, apparatus, direction);
61 | shift_all(neighborhood, direction, i);
62 | } else {
63 | let neighborhood_left = get_neighborhood(
64 | [origin[0]],
65 | apparatus,
66 | ut.mirror(direction)
67 | );
68 | let neighborhood_right = get_neighborhood(
69 | [origin[1]],
70 | apparatus,
71 | direction
72 | );
73 | shift_all(neighborhood_left, ut.mirror(direction), i);
74 | shift_all(neighborhood_right, direction, i);
75 | }
76 | }
77 | }
78 |
79 | function populate_apparatus(app) {
80 | app.forEach(part => {
81 | part.x2 = part.x1 + part.w;
82 | part.y2 = part.y1 + part.h;
83 | part.path = [];
84 | for (let i = 0; i < final_frame_duration; i++) {
85 | part.path.push({ x: part.x1, y: part.y1 });
86 | }
87 | });
88 | }
89 |
90 | p.draw = () => {
91 | p.background('#eee8e2');
92 | p.translate(
93 | (p.width - (app_gen.xdim + 2) * scale) / 2,
94 | (p.height - (app_gen.ydim + 2) * scale) / 2
95 | );
96 |
97 | if (tick >= shuffle) {
98 | setup_apparatus();
99 | tick = 0;
100 | }
101 | apparatus.forEach(part => {
102 | display_rect(part, scale, shuffle - tick - 1);
103 | });
104 | tick++;
105 | };
106 |
107 | function get_neighborhood(ps, rs, dir) {
108 | let ns = ps;
109 | let ms = ut.union(
110 | ns,
111 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
112 | equal_rect
113 | );
114 |
115 | while (ms.length > ns.length) {
116 | ns = ms;
117 | ms = ut.union(
118 | ns,
119 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
120 | equal_rect
121 | );
122 | }
123 | return ms;
124 | }
125 |
126 | function get_neighbors(r1, rs, dir) {
127 | return rs.filter(r => is_neighbor(r1, r, dir));
128 | }
129 |
130 | function is_neighbor(r1, r2, dir) {
131 | if (equal_rect(r1, r2)) return false; // Identical
132 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North
133 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East
134 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South
135 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West
136 | return false; // Error
137 | }
138 |
139 | function equal_rect(r1, r2) {
140 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2;
141 | }
142 |
143 | function shift_all(rs, dir, time) {
144 | rs.forEach(r => shift(r, dir, time));
145 | }
146 |
147 | function shift(r, dir, time) {
148 | let sx = ut.is_vertical(dir) ? 0 : dir == 1 ? 1 : -1;
149 | let sy = !ut.is_vertical(dir) ? 0 : dir == 2 ? 1 : -1;
150 |
151 | r.x1 += sx;
152 | r.y1 += sy;
153 | r.x2 += sx;
154 | r.y2 += sy;
155 | r.path[time] = { x: r.x1, y: r.y1 };
156 | }
157 |
158 | function get_with_id(rs, id) {
159 | return rs.filter(r => r.id === id);
160 | }
161 |
162 | function display_rect(r, scale, time) {
163 | p.fill(r.col ? r.col : '#fff');
164 | p.rect(
165 | r.path[time].x * scale,
166 | r.path[time].y * scale,
167 | r.w * scale,
168 | r.h * scale
169 | );
170 | }
171 |
172 | p.keyPressed = () => {
173 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly;
174 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png');
175 | };
176 | };
177 |
178 | new p5(sketch);
179 |
--------------------------------------------------------------------------------
/main-multi.js:
--------------------------------------------------------------------------------
1 | import ApparatusGenerator from 'apparatus-generator';
2 | import * as tome from 'chromotome';
3 | import * as ut from './utils';
4 |
5 | const sketch = p => {
6 | let app_gen, apparata;
7 | const number_of_apparata = 9;
8 | const scale = 2.5;
9 | const shuffle = 300;
10 | let tick = 0;
11 | const final_frame_duration = 120;
12 | let symmetric_assembly = true;
13 | const movement_length = 0.85;
14 | const row_length = 3;
15 |
16 | p.setup = () => {
17 | p.createCanvas(1200, 1000);
18 | p.background('#eee8e2');
19 | p.fill(0);
20 | p.frameRate(40);
21 | p.strokeWeight(1.5);
22 | p.stroke('#5c3936');
23 |
24 | apparata = [];
25 | app_gen = new ApparatusGenerator(25, 35, {
26 | solidness: 0.5,
27 | initiate_chance: 0.9,
28 | extension_chance: 0.88,
29 | vertical_chance: 0.6,
30 | roundness: 0,
31 | group_size: 0.82,
32 | colors: tome.get('retro-washedout').colors
33 | });
34 |
35 | for (let i = 0; i < number_of_apparata / row_length; i++) {
36 | setup_apparatus(row_length);
37 | }
38 | };
39 |
40 | function setup_apparatus(n) {
41 | let apparatus = app_gen.generate();
42 |
43 | for (let i = 0; i < n; i++) {
44 | let extra_shuffle = p.floor(p.random(100));
45 | let animatable_app = populate_apparatus(apparatus, extra_shuffle);
46 | animate_apparatus(animatable_app, extra_shuffle);
47 | apparata.push(animatable_app);
48 | }
49 | }
50 |
51 | function populate_apparatus(app, extra_shuffle) {
52 | let new_app = [];
53 | app.forEach(part => {
54 | let new_part = {
55 | ...part,
56 | x2: part.x1 + part.w,
57 | y2: part.y1 + part.h,
58 | path: []
59 | };
60 | for (let i = 0; i < final_frame_duration - extra_shuffle; i++) {
61 | new_part.path.push({ x: new_part.x1, y: new_part.y1 });
62 | }
63 | new_app.push(new_part);
64 | });
65 | return new_app;
66 | }
67 |
68 | function animate_apparatus(apparatus, extra_shuffle) {
69 | symmetric_assembly = true;
70 | let chosen, origin, direction;
71 | let start_from_new_part = true;
72 | let actual_ff_duration = final_frame_duration - extra_shuffle;
73 | for (let i = actual_ff_duration; i < shuffle; i++) {
74 | if (i - actual_ff_duration >= (shuffle - actual_ff_duration) / 2) {
75 | symmetric_assembly = false;
76 | }
77 |
78 | apparatus.forEach(part => {
79 | part.path.push({ x: part.x1, y: part.y1 });
80 | });
81 | if (start_from_new_part) {
82 | chosen = ut.get_random_from(apparatus);
83 | origin = symmetric_assembly
84 | ? get_with_id(apparatus, chosen.id)
85 | : [chosen];
86 | direction =
87 | symmetric_assembly && origin.length === 1
88 | ? ut.random_dir(2)
89 | : ut.random_dir(symmetric_assembly ? 3 : 4);
90 | }
91 | start_from_new_part = p.random() > movement_length;
92 |
93 | if (ut.is_vertical(direction) || !symmetric_assembly) {
94 | let neighborhood = get_neighborhood(origin, apparatus, direction);
95 | shift_all(neighborhood, direction, i);
96 | } else {
97 | let neighborhood_left = get_neighborhood(
98 | [origin[0]],
99 | apparatus,
100 | ut.mirror(direction)
101 | );
102 | let neighborhood_right = get_neighborhood(
103 | [origin[1]],
104 | apparatus,
105 | direction
106 | );
107 | shift_all(neighborhood_left, ut.mirror(direction), i);
108 | shift_all(neighborhood_right, direction, i);
109 | }
110 | }
111 | }
112 |
113 | p.draw = () => {
114 | p.background('#eee8e2');
115 | p.translate(
116 | (p.width - (app_gen.xdim + 2) * scale) / (row_length + 1),
117 | (p.height - (app_gen.ydim + 2) * scale) / 6
118 | );
119 |
120 | if (tick >= shuffle) {
121 | apparata = [];
122 | for (let i = 0; i < number_of_apparata / row_length; i++) {
123 | setup_apparatus(row_length);
124 | }
125 | tick = 0;
126 | }
127 |
128 | for (let i = 0; i < apparata.length; i++) {
129 | display_apparatus(apparata[i]);
130 | p.translate((p.width - (app_gen.xdim + 2) * scale) / (row_length + 1), 0);
131 | if (i % row_length === row_length - 1)
132 | p.translate(
133 | (-row_length * (p.width - (app_gen.xdim + 2) * scale)) /
134 | (row_length + 1),
135 | (p.height - (app_gen.ydim + 2) * scale) / 3
136 | );
137 | }
138 |
139 | tick++;
140 | };
141 |
142 | function display_apparatus(appar) {
143 | appar.forEach(part => {
144 | display_rect(part, scale, shuffle - tick - 1);
145 | });
146 | }
147 |
148 | function get_neighborhood(ps, rs, dir) {
149 | let ns = ps;
150 | let ms = ut.union(
151 | ns,
152 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
153 | equal_rect
154 | );
155 |
156 | while (ms.length > ns.length) {
157 | ns = ms;
158 | ms = ut.union(
159 | ns,
160 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
161 | equal_rect
162 | );
163 | }
164 | return ms;
165 | }
166 |
167 | function get_neighbors(r1, rs, dir) {
168 | return rs.filter(r => is_neighbor(r1, r, dir));
169 | }
170 |
171 | function is_neighbor(r1, r2, dir) {
172 | if (equal_rect(r1, r2)) return false; // Identical
173 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North
174 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East
175 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South
176 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West
177 | return false; // Error
178 | }
179 |
180 | function equal_rect(r1, r2) {
181 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2;
182 | }
183 |
184 | function shift_all(rs, dir, time) {
185 | rs.forEach(r => shift(r, dir, time));
186 | }
187 |
188 | function shift(r, dir, time) {
189 | let sx = ut.is_vertical(dir) ? 0 : dir == 1 ? 1 : -1;
190 | let sy = !ut.is_vertical(dir) ? 0 : dir == 2 ? 1 : -1;
191 |
192 | r.x1 += sx;
193 | r.y1 += sy;
194 | r.x2 += sx;
195 | r.y2 += sy;
196 | r.path[time] = { x: r.x1, y: r.y1 };
197 | }
198 |
199 | function get_with_id(rs, id) {
200 | return rs.filter(r => r.id === id);
201 | }
202 |
203 | function display_rect(r, scale, time) {
204 | p.fill(r.col ? r.col : '#fff');
205 | p.rect(
206 | r.path[time].x * scale,
207 | r.path[time].y * scale,
208 | r.w * scale,
209 | r.h * scale
210 | );
211 | }
212 |
213 | p.keyPressed = () => {
214 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly;
215 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png');
216 | };
217 | };
218 |
219 | new p5(sketch);
220 |
--------------------------------------------------------------------------------
/dist/bundle.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (factory());
5 | }(this, (function () { 'use strict';
6 |
7 | class index {
8 | constructor(
9 | width,
10 | height,
11 | {
12 | initiate_chance = 0.8,
13 | extension_chance = 0.8,
14 | vertical_chance = 0.8,
15 | horizontal_symmetry = true,
16 | vertical_symmetry = false,
17 | roundness = 0.1,
18 | solidness = 0.5,
19 | colors = [],
20 | color_mode = 'group',
21 | group_size = 0.8,
22 | simple = false,
23 | } = {}
24 | ) {
25 | this.xdim = Math.round(width * 2 + 11, 0);
26 | this.ydim = Math.round(height * 2 + 11, 0);
27 | this.radius_x = width;
28 | this.radius_y = height;
29 | this.chance_new = initiate_chance;
30 | this.chance_extend = extension_chance;
31 | this.chance_vertical = vertical_chance;
32 | this.colors = colors;
33 | this.color_mode = color_mode;
34 | this.group_size = group_size;
35 | this.h_symmetric = horizontal_symmetry;
36 | this.v_symmetric = vertical_symmetry;
37 | this.roundness = roundness;
38 | this.solidness = solidness;
39 | this.simple = simple;
40 | }
41 |
42 | generate() {
43 | this.main_color = get_random(this.colors);
44 | this.id_counter = 0;
45 |
46 | let grid = new Array(this.ydim + 1);
47 | for (var i = 0; i < grid.length; i++) {
48 | grid[i] = new Array(this.xdim + 1);
49 | for (var j = 0; j < grid[i].length; j++) {
50 | if (i == 0 || j == 0) {
51 | grid[i][j] = { h: false, v: false, in: false, col: null };
52 | } else if (this.h_symmetric && j > grid[i].length / 2) {
53 | grid[i][j] = deep_copy(grid[i][grid[i].length - j]);
54 | grid[i][j].v = grid[i][grid[i].length - j + 1].v;
55 | } else if (this.v_symmetric && i > grid.length / 2) {
56 | grid[i][j] = deep_copy(grid[grid.length - i][j]);
57 | grid[i][j].h = grid[grid.length - i + 1][j].h;
58 | } else {
59 | grid[i][j] = this.next_block(j, i, grid[i][j - 1], grid[i - 1][j]);
60 | }
61 | }
62 | }
63 | let rects = convert_linegrid_to_rectangles(grid);
64 | return rects;
65 | }
66 |
67 | next_block(x, y, left, top) {
68 | const context = this;
69 |
70 | if (!left.in && !top.in) {
71 | return block_set_1(x, y);
72 | }
73 |
74 | if (left.in && !top.in) {
75 | if (left.h) return block_set_3(x, y);
76 | return block_set_2(x, y);
77 | }
78 |
79 | if (!left.in && top.in) {
80 | if (top.v) return block_set_5(x, y);
81 | return block_set_4(x, y);
82 | }
83 |
84 | if (left.in && top.in) {
85 | if (!left.h && !top.v) return block_set_6();
86 | if (left.h && !top.v) return block_set_7(x, y);
87 | if (!left.h && top.v) return block_set_8(x, y);
88 | return block_set_9(x, y);
89 | }
90 |
91 | // --- Block sets ----
92 |
93 | function block_set_1(x, y) {
94 | if (start_new_from_blank(x, y)) return new_block();
95 | return { v: false, h: false, in: false, col: null, id: null };
96 | }
97 |
98 | function block_set_2(x, y) {
99 | if (start_new_from_blank(x, y)) return new_block();
100 | return { v: true, h: false, in: false, col: null, id: null };
101 | }
102 |
103 | function block_set_3(x, y) {
104 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id };
105 | return block_set_2(x, y);
106 | }
107 |
108 | function block_set_4(x, y) {
109 | if (start_new_from_blank(x, y)) return new_block();
110 | return { v: false, h: true, in: false, col: null, id: null };
111 | }
112 |
113 | function block_set_5(x, y) {
114 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id };
115 | return block_set_4(x, y);
116 | }
117 |
118 | function block_set_6() {
119 | return { v: false, h: false, in: true, col: left.col, id: left.id };
120 | }
121 |
122 | function block_set_7(x, y) {
123 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id };
124 | if (start_new(x, y)) return new_block();
125 | return { v: true, h: true, in: false, col: null, id: null };
126 | }
127 |
128 | function block_set_8(x, y) {
129 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id };
130 | if (start_new(x, y)) return new_block();
131 | return { v: true, h: true, in: false, col: null, id: null };
132 | }
133 |
134 | function block_set_9(x, y) {
135 | if (vertical_dir()) return { v: true, h: false, in: true, col: top.col, id: top.id };
136 | return { v: false, h: true, in: true, col: left.col, id: left.id };
137 | }
138 |
139 | // ---- Blocks ----
140 |
141 | function new_block() {
142 | let col;
143 | if (context.color_mode === 'random') {
144 | col = get_random(context.colors);
145 | } else if (context.color_mode === 'main') {
146 | col = Math.random() > 0.75 ? get_random(context.colors) : context.main_color;
147 | } else if (context.color_mode === 'group') {
148 | let keep = Math.random() > 0.5 ? left.col : top.col;
149 | context.main_color =
150 | Math.random() > context.group_size ? get_random(context.colors) : keep || context.main_color;
151 | col = context.main_color;
152 | } else {
153 | col = context.main_color;
154 | }
155 |
156 | return { v: true, h: true, in: true, col: col, id: context.id_counter++ };
157 | }
158 |
159 | // ---- Decisions ----
160 |
161 | function start_new_from_blank(x, y) {
162 | if (context.simple) return true;
163 | if (!active_position(x, y, -1 * (1 - context.roundness))) return false;
164 | return Math.random() <= context.solidness;
165 | }
166 |
167 | function start_new(x, y) {
168 | if (context.simple) return true;
169 | if (!active_position(x, y, 0)) return false;
170 | return Math.random() <= context.chance_new;
171 | }
172 |
173 | function extend(x, y) {
174 | if (!active_position(x, y, 1 - context.roundness) && !context.simple) return false;
175 | return Math.random() <= context.chance_extend;
176 | }
177 |
178 | function vertical_dir() {
179 | return Math.random() <= context.chance_vertical;
180 | }
181 |
182 | function active_position(x, y, fuzzy) {
183 | let fuzziness = 1 + Math.random() * fuzzy;
184 | let xa = Math.pow(x - context.xdim / 2, 2) / Math.pow(context.radius_x * fuzziness, 2);
185 | let ya = Math.pow(y - context.ydim / 2, 2) / Math.pow(context.radius_y * fuzziness, 2);
186 | return xa + ya < 1;
187 | }
188 | }
189 | }
190 |
191 | function get_random(array) {
192 | return array[Math.floor(Math.random() * array.length)];
193 | }
194 |
195 | function deep_copy(obj) {
196 | let nobj = [];
197 | for (var key in obj) {
198 | if (obj.hasOwnProperty(key)) {
199 | nobj[key] = obj[key];
200 | }
201 | }
202 | return nobj;
203 | }
204 |
205 | // --- Conversion ---
206 | function convert_linegrid_to_rectangles(grid) {
207 | let nw_corners = get_nw_corners(grid);
208 | extend_corners_to_rectangles(nw_corners, grid);
209 | return nw_corners;
210 | }
211 |
212 | function get_nw_corners(grid) {
213 | let nw_corners = [];
214 | for (let i = 0; i < grid.length; i++) {
215 | for (let j = 0; j < grid[i].length; j++) {
216 | let cell = grid[i][j];
217 | if (cell.h && cell.v && cell.in) nw_corners.push({ x1: j, y1: i, col: cell.col, id: cell.id });
218 | }
219 | }
220 | return nw_corners;
221 | }
222 |
223 | function extend_corners_to_rectangles(corners, grid) {
224 | corners.map(c => {
225 | let accx = 1;
226 | while (c.x1 + accx < grid[c.y1].length && !grid[c.y1][c.x1 + accx].v) {
227 | accx++;
228 | }
229 | let accy = 1;
230 | while (c.y1 + accy < grid.length && !grid[c.y1 + accy][c.x1].h) {
231 | accy++;
232 | }
233 | c.w = accx;
234 | c.h = accy;
235 | return c;
236 | });
237 | }
238 |
239 | const palettes = [
240 | {
241 | name: 'tundra1',
242 | colors: ['#40708c', '#8e998c', '#5d3f37', '#ed6954', '#f2e9e2']
243 | },
244 | {
245 | name: 'tundra2',
246 | colors: ['#5f9e93', '#3d3638', '#733632', '#b66239', '#b0a1a4', '#e3dad2']
247 | },
248 | {
249 | name: 'tundra3',
250 | colors: [
251 | '#87c3ca',
252 | '#7b7377',
253 | '#b2475d',
254 | '#7d3e3e',
255 | '#eb7f64',
256 | '#d9c67a',
257 | '#f3f2f2'
258 | ]
259 | },
260 | {
261 | name: 'tundra4',
262 | colors: [
263 | '#d53939',
264 | '#b6754d',
265 | '#a88d5f',
266 | '#524643',
267 | '#3c5a53',
268 | '#7d8c7c',
269 | '#dad6cd'
270 | ]
271 | },
272 | {
273 | name: 'retro',
274 | colors: [
275 | '#69766f',
276 | '#9ed6cb',
277 | '#f7e5cc',
278 | '#9d8f7f',
279 | '#936454',
280 | '#bf5c32',
281 | '#efad57'
282 | ]
283 | },
284 | {
285 | name: 'retro-washedout',
286 | colors: [
287 | '#878a87',
288 | '#cbdbc8',
289 | '#e8e0d4',
290 | '#b29e91',
291 | '#9f736c',
292 | '#b76254',
293 | '#dfa372'
294 | ]
295 | },
296 | {
297 | name: 'roygbiv-warm',
298 | colors: [
299 | '#705f84',
300 | '#687d99',
301 | '#6c843e',
302 | '#fc9a1a',
303 | '#dc383a',
304 | '#aa3a33',
305 | '#9c4257'
306 | ]
307 | },
308 | {
309 | name: 'roygbiv-toned',
310 | colors: [
311 | '#817c77',
312 | '#396c68',
313 | '#89e3b7',
314 | '#f59647',
315 | '#d63644',
316 | '#893f49',
317 | '#4d3240'
318 | ]
319 | },
320 | {
321 | name: 'present-correct',
322 | colors: [
323 | '#fd3741',
324 | '#fe4f11',
325 | '#ff6800',
326 | '#ffa61a',
327 | '#ffc219',
328 | '#ffd114',
329 | '#fcd82e',
330 | '#f4d730',
331 | '#ced562',
332 | '#8ac38f',
333 | '#79b7a0',
334 | '#72b5b1',
335 | '#5b9bae',
336 | '#6ba1b7',
337 | '#49619d',
338 | '#604791',
339 | '#721e7f',
340 | '#9b2b77',
341 | '#ab2562',
342 | '#ca2847'
343 | ]
344 | }
345 | ];
346 |
347 | var palettes$1 = palettes.map(p => {
348 | p.size = p.colors.length;
349 | return p;
350 | });
351 |
352 | function get(name) {
353 | return palettes$1.find(pal => pal.name == name);
354 | }
355 |
356 | // List utils
357 |
358 | function contains(xs, y, eq) {
359 | return xs.some(x => eq(x, y));
360 | }
361 |
362 | function union(xs, ys, eq) {
363 | let zs = [];
364 | xs.forEach(x => zs.push(x));
365 | ys.forEach(y => {
366 | if (!contains(xs, y, eq)) zs.push(y);
367 | });
368 | return zs;
369 | }
370 |
371 | function flatten(xs, eq) {
372 | return xs.reduce((acc, val) => union(acc, val, eq), []);
373 | }
374 |
375 | function get_random_from(list) {
376 | return list[Math.floor(Math.random() * list.length)]
377 | }
378 |
379 | // ---- Direction utils ----
380 |
381 | // Get random direction among n choices.
382 | function random_dir(n) {
383 | if (n === 2) return Math.random() > 0.5 ? 0 : 2; // Up or down (50/50)
384 | if (n === 3) return Math.random() > 0.5 ? 1 : Math.random() > 0.5 ? 2 : 0; // Up, right or down (25/50/25)
385 | return Math.floor(Math.random() * n); // Up, right, down or left (25/25/25/25)
386 | }
387 |
388 | // Get horizontally mirrored direction.
389 | function mirror(dir) {
390 | if (is_vertical(dir)) return dir;
391 | if (dir == 1) return 3;
392 | if (dir == 3) return 1;
393 | }
394 |
395 | // Check wether direction is vertical.
396 | function is_vertical(dir) {
397 | return dir == 0 || dir == 2;
398 | }
399 |
400 | const sketch = p => {
401 | let app_gen, apparatus;
402 | let scale = 6;
403 | let shuffle = 220;
404 | let tick = 0;
405 | let final_frame_duration = 25;
406 | let symmetric_assembly = true;
407 | let movement_length = 0.82;
408 |
409 | p.setup = () => {
410 | p.createCanvas(800, 800);
411 | p.background('#eee8e2');
412 | p.fill(0);
413 | p.frameRate(30);
414 | p.strokeWeight(3);
415 | p.stroke('#5c3936');
416 | app_gen = new index(26, 36, {
417 | solidness: 0.5,
418 | initiate_chance: 0.9,
419 | extension_chance: 0.86,
420 | vertical_chance: 0.5,
421 | roundness: 0,
422 | group_size: 0.82,
423 | colors: get('retro-washedout').colors
424 | });
425 |
426 | setup_apparatus();
427 | };
428 |
429 | function setup_apparatus() {
430 | symmetric_assembly = true;
431 | apparatus = app_gen.generate();
432 | populate_apparatus(apparatus);
433 |
434 | let chosen, origin, direction;
435 | let start_from_new_part = true;
436 | for (let i = final_frame_duration; i < shuffle; i++) {
437 | if (i === shuffle / 2) symmetric_assembly = false;
438 |
439 | apparatus.forEach(part => {
440 | part.path.push({ x: part.x1, y: part.y1 });
441 | });
442 | if (start_from_new_part) {
443 | chosen = get_random_from(apparatus);
444 | origin = symmetric_assembly
445 | ? get_with_id(apparatus, chosen.id)
446 | : [chosen];
447 | direction =
448 | symmetric_assembly && origin.length === 1
449 | ? random_dir(2)
450 | : random_dir(symmetric_assembly ? 3 : 4);
451 | }
452 | start_from_new_part = p.random() > movement_length;
453 |
454 | if (is_vertical(direction) || !symmetric_assembly) {
455 | let neighborhood = get_neighborhood(origin, apparatus, direction);
456 | shift_all(neighborhood, direction, i);
457 | } else {
458 | let neighborhood_left = get_neighborhood(
459 | [origin[0]],
460 | apparatus,
461 | mirror(direction)
462 | );
463 | let neighborhood_right = get_neighborhood(
464 | [origin[1]],
465 | apparatus,
466 | direction
467 | );
468 | shift_all(neighborhood_left, mirror(direction), i);
469 | shift_all(neighborhood_right, direction, i);
470 | }
471 | }
472 | }
473 |
474 | function populate_apparatus(app) {
475 | app.forEach(part => {
476 | part.x2 = part.x1 + part.w;
477 | part.y2 = part.y1 + part.h;
478 | part.path = [];
479 | for (let i = 0; i < final_frame_duration; i++) {
480 | part.path.push({ x: part.x1, y: part.y1 });
481 | }
482 | });
483 | }
484 |
485 | p.draw = () => {
486 | p.background('#eee8e2');
487 | p.translate(
488 | (p.width - (app_gen.xdim + 2) * scale) / 2,
489 | (p.height - (app_gen.ydim + 2) * scale) / 2
490 | );
491 |
492 | if (tick >= shuffle) {
493 | setup_apparatus();
494 | tick = 0;
495 | }
496 | apparatus.forEach(part => {
497 | display_rect(part, scale, shuffle - tick - 1);
498 | });
499 | tick++;
500 | };
501 |
502 | function get_neighborhood(ps, rs, dir) {
503 | let ns = ps;
504 | let ms = union(
505 | ns,
506 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
507 | equal_rect
508 | );
509 |
510 | while (ms.length > ns.length) {
511 | ns = ms;
512 | ms = union(
513 | ns,
514 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
515 | equal_rect
516 | );
517 | }
518 | return ms;
519 | }
520 |
521 | function get_neighbors(r1, rs, dir) {
522 | return rs.filter(r => is_neighbor(r1, r, dir));
523 | }
524 |
525 | function is_neighbor(r1, r2, dir) {
526 | if (equal_rect(r1, r2)) return false; // Identical
527 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North
528 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East
529 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South
530 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West
531 | return false; // Error
532 | }
533 |
534 | function equal_rect(r1, r2) {
535 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2;
536 | }
537 |
538 | function shift_all(rs, dir, time) {
539 | rs.forEach(r => shift(r, dir, time));
540 | }
541 |
542 | function shift(r, dir, time) {
543 | let sx = is_vertical(dir) ? 0 : dir == 1 ? 1 : -1;
544 | let sy = !is_vertical(dir) ? 0 : dir == 2 ? 1 : -1;
545 |
546 | r.x1 += sx;
547 | r.y1 += sy;
548 | r.x2 += sx;
549 | r.y2 += sy;
550 | r.path[time] = { x: r.x1, y: r.y1 };
551 | }
552 |
553 | function get_with_id(rs, id) {
554 | return rs.filter(r => r.id === id);
555 | }
556 |
557 | function display_rect(r, scale, time) {
558 | p.fill(r.col ? r.col : '#fff');
559 | p.rect(
560 | r.path[time].x * scale,
561 | r.path[time].y * scale,
562 | r.w * scale,
563 | r.h * scale
564 | );
565 | }
566 |
567 | p.keyPressed = () => {
568 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly;
569 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png');
570 | };
571 | };
572 |
573 | new p5(sketch);
574 |
575 | })));
576 |
--------------------------------------------------------------------------------
/dist/bundle-multi.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
3 | typeof define === 'function' && define.amd ? define(factory) :
4 | (factory());
5 | }(this, (function () { 'use strict';
6 |
7 | class index {
8 | constructor(
9 | width,
10 | height,
11 | {
12 | initiate_chance = 0.8,
13 | extension_chance = 0.8,
14 | vertical_chance = 0.8,
15 | horizontal_symmetry = true,
16 | vertical_symmetry = false,
17 | roundness = 0.1,
18 | solidness = 0.5,
19 | colors = [],
20 | color_mode = 'group',
21 | group_size = 0.8,
22 | simple = false,
23 | } = {}
24 | ) {
25 | this.xdim = Math.round(width * 2 + 11, 0);
26 | this.ydim = Math.round(height * 2 + 11, 0);
27 | this.radius_x = width;
28 | this.radius_y = height;
29 | this.chance_new = initiate_chance;
30 | this.chance_extend = extension_chance;
31 | this.chance_vertical = vertical_chance;
32 | this.colors = colors;
33 | this.color_mode = color_mode;
34 | this.group_size = group_size;
35 | this.h_symmetric = horizontal_symmetry;
36 | this.v_symmetric = vertical_symmetry;
37 | this.roundness = roundness;
38 | this.solidness = solidness;
39 | this.simple = simple;
40 | }
41 |
42 | generate() {
43 | this.main_color = get_random(this.colors);
44 | this.id_counter = 0;
45 |
46 | let grid = new Array(this.ydim + 1);
47 | for (var i = 0; i < grid.length; i++) {
48 | grid[i] = new Array(this.xdim + 1);
49 | for (var j = 0; j < grid[i].length; j++) {
50 | if (i == 0 || j == 0) {
51 | grid[i][j] = { h: false, v: false, in: false, col: null };
52 | } else if (this.h_symmetric && j > grid[i].length / 2) {
53 | grid[i][j] = deep_copy(grid[i][grid[i].length - j]);
54 | grid[i][j].v = grid[i][grid[i].length - j + 1].v;
55 | } else if (this.v_symmetric && i > grid.length / 2) {
56 | grid[i][j] = deep_copy(grid[grid.length - i][j]);
57 | grid[i][j].h = grid[grid.length - i + 1][j].h;
58 | } else {
59 | grid[i][j] = this.next_block(j, i, grid[i][j - 1], grid[i - 1][j]);
60 | }
61 | }
62 | }
63 | let rects = convert_linegrid_to_rectangles(grid);
64 | return rects;
65 | }
66 |
67 | next_block(x, y, left, top) {
68 | const context = this;
69 |
70 | if (!left.in && !top.in) {
71 | return block_set_1(x, y);
72 | }
73 |
74 | if (left.in && !top.in) {
75 | if (left.h) return block_set_3(x, y);
76 | return block_set_2(x, y);
77 | }
78 |
79 | if (!left.in && top.in) {
80 | if (top.v) return block_set_5(x, y);
81 | return block_set_4(x, y);
82 | }
83 |
84 | if (left.in && top.in) {
85 | if (!left.h && !top.v) return block_set_6();
86 | if (left.h && !top.v) return block_set_7(x, y);
87 | if (!left.h && top.v) return block_set_8(x, y);
88 | return block_set_9(x, y);
89 | }
90 |
91 | // --- Block sets ----
92 |
93 | function block_set_1(x, y) {
94 | if (start_new_from_blank(x, y)) return new_block();
95 | return { v: false, h: false, in: false, col: null, id: null };
96 | }
97 |
98 | function block_set_2(x, y) {
99 | if (start_new_from_blank(x, y)) return new_block();
100 | return { v: true, h: false, in: false, col: null, id: null };
101 | }
102 |
103 | function block_set_3(x, y) {
104 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id };
105 | return block_set_2(x, y);
106 | }
107 |
108 | function block_set_4(x, y) {
109 | if (start_new_from_blank(x, y)) return new_block();
110 | return { v: false, h: true, in: false, col: null, id: null };
111 | }
112 |
113 | function block_set_5(x, y) {
114 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id };
115 | return block_set_4(x, y);
116 | }
117 |
118 | function block_set_6() {
119 | return { v: false, h: false, in: true, col: left.col, id: left.id };
120 | }
121 |
122 | function block_set_7(x, y) {
123 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id };
124 | if (start_new(x, y)) return new_block();
125 | return { v: true, h: true, in: false, col: null, id: null };
126 | }
127 |
128 | function block_set_8(x, y) {
129 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id };
130 | if (start_new(x, y)) return new_block();
131 | return { v: true, h: true, in: false, col: null, id: null };
132 | }
133 |
134 | function block_set_9(x, y) {
135 | if (vertical_dir()) return { v: true, h: false, in: true, col: top.col, id: top.id };
136 | return { v: false, h: true, in: true, col: left.col, id: left.id };
137 | }
138 |
139 | // ---- Blocks ----
140 |
141 | function new_block() {
142 | let col;
143 | if (context.color_mode === 'random') {
144 | col = get_random(context.colors);
145 | } else if (context.color_mode === 'main') {
146 | col = Math.random() > 0.75 ? get_random(context.colors) : context.main_color;
147 | } else if (context.color_mode === 'group') {
148 | let keep = Math.random() > 0.5 ? left.col : top.col;
149 | context.main_color =
150 | Math.random() > context.group_size ? get_random(context.colors) : keep || context.main_color;
151 | col = context.main_color;
152 | } else {
153 | col = context.main_color;
154 | }
155 |
156 | return { v: true, h: true, in: true, col: col, id: context.id_counter++ };
157 | }
158 |
159 | // ---- Decisions ----
160 |
161 | function start_new_from_blank(x, y) {
162 | if (context.simple) return true;
163 | if (!active_position(x, y, -1 * (1 - context.roundness))) return false;
164 | return Math.random() <= context.solidness;
165 | }
166 |
167 | function start_new(x, y) {
168 | if (context.simple) return true;
169 | if (!active_position(x, y, 0)) return false;
170 | return Math.random() <= context.chance_new;
171 | }
172 |
173 | function extend(x, y) {
174 | if (!active_position(x, y, 1 - context.roundness) && !context.simple) return false;
175 | return Math.random() <= context.chance_extend;
176 | }
177 |
178 | function vertical_dir() {
179 | return Math.random() <= context.chance_vertical;
180 | }
181 |
182 | function active_position(x, y, fuzzy) {
183 | let fuzziness = 1 + Math.random() * fuzzy;
184 | let xa = Math.pow(x - context.xdim / 2, 2) / Math.pow(context.radius_x * fuzziness, 2);
185 | let ya = Math.pow(y - context.ydim / 2, 2) / Math.pow(context.radius_y * fuzziness, 2);
186 | return xa + ya < 1;
187 | }
188 | }
189 | }
190 |
191 | function get_random(array) {
192 | return array[Math.floor(Math.random() * array.length)];
193 | }
194 |
195 | function deep_copy(obj) {
196 | let nobj = [];
197 | for (var key in obj) {
198 | if (obj.hasOwnProperty(key)) {
199 | nobj[key] = obj[key];
200 | }
201 | }
202 | return nobj;
203 | }
204 |
205 | // --- Conversion ---
206 | function convert_linegrid_to_rectangles(grid) {
207 | let nw_corners = get_nw_corners(grid);
208 | extend_corners_to_rectangles(nw_corners, grid);
209 | return nw_corners;
210 | }
211 |
212 | function get_nw_corners(grid) {
213 | let nw_corners = [];
214 | for (let i = 0; i < grid.length; i++) {
215 | for (let j = 0; j < grid[i].length; j++) {
216 | let cell = grid[i][j];
217 | if (cell.h && cell.v && cell.in) nw_corners.push({ x1: j, y1: i, col: cell.col, id: cell.id });
218 | }
219 | }
220 | return nw_corners;
221 | }
222 |
223 | function extend_corners_to_rectangles(corners, grid) {
224 | corners.map(c => {
225 | let accx = 1;
226 | while (c.x1 + accx < grid[c.y1].length && !grid[c.y1][c.x1 + accx].v) {
227 | accx++;
228 | }
229 | let accy = 1;
230 | while (c.y1 + accy < grid.length && !grid[c.y1 + accy][c.x1].h) {
231 | accy++;
232 | }
233 | c.w = accx;
234 | c.h = accy;
235 | return c;
236 | });
237 | }
238 |
239 | const palettes = [
240 | {
241 | name: 'tundra1',
242 | colors: ['#40708c', '#8e998c', '#5d3f37', '#ed6954', '#f2e9e2']
243 | },
244 | {
245 | name: 'tundra2',
246 | colors: ['#5f9e93', '#3d3638', '#733632', '#b66239', '#b0a1a4', '#e3dad2']
247 | },
248 | {
249 | name: 'tundra3',
250 | colors: [
251 | '#87c3ca',
252 | '#7b7377',
253 | '#b2475d',
254 | '#7d3e3e',
255 | '#eb7f64',
256 | '#d9c67a',
257 | '#f3f2f2'
258 | ]
259 | },
260 | {
261 | name: 'tundra4',
262 | colors: [
263 | '#d53939',
264 | '#b6754d',
265 | '#a88d5f',
266 | '#524643',
267 | '#3c5a53',
268 | '#7d8c7c',
269 | '#dad6cd'
270 | ]
271 | },
272 | {
273 | name: 'retro',
274 | colors: [
275 | '#69766f',
276 | '#9ed6cb',
277 | '#f7e5cc',
278 | '#9d8f7f',
279 | '#936454',
280 | '#bf5c32',
281 | '#efad57'
282 | ]
283 | },
284 | {
285 | name: 'retro-washedout',
286 | colors: [
287 | '#878a87',
288 | '#cbdbc8',
289 | '#e8e0d4',
290 | '#b29e91',
291 | '#9f736c',
292 | '#b76254',
293 | '#dfa372'
294 | ]
295 | },
296 | {
297 | name: 'roygbiv-warm',
298 | colors: [
299 | '#705f84',
300 | '#687d99',
301 | '#6c843e',
302 | '#fc9a1a',
303 | '#dc383a',
304 | '#aa3a33',
305 | '#9c4257'
306 | ]
307 | },
308 | {
309 | name: 'roygbiv-toned',
310 | colors: [
311 | '#817c77',
312 | '#396c68',
313 | '#89e3b7',
314 | '#f59647',
315 | '#d63644',
316 | '#893f49',
317 | '#4d3240'
318 | ]
319 | },
320 | {
321 | name: 'present-correct',
322 | colors: [
323 | '#fd3741',
324 | '#fe4f11',
325 | '#ff6800',
326 | '#ffa61a',
327 | '#ffc219',
328 | '#ffd114',
329 | '#fcd82e',
330 | '#f4d730',
331 | '#ced562',
332 | '#8ac38f',
333 | '#79b7a0',
334 | '#72b5b1',
335 | '#5b9bae',
336 | '#6ba1b7',
337 | '#49619d',
338 | '#604791',
339 | '#721e7f',
340 | '#9b2b77',
341 | '#ab2562',
342 | '#ca2847'
343 | ]
344 | }
345 | ];
346 |
347 | var palettes$1 = palettes.map(p => {
348 | p.size = p.colors.length;
349 | return p;
350 | });
351 |
352 | function get(name) {
353 | return palettes$1.find(pal => pal.name == name);
354 | }
355 |
356 | // List utils
357 |
358 | function contains(xs, y, eq) {
359 | return xs.some(x => eq(x, y));
360 | }
361 |
362 | function union(xs, ys, eq) {
363 | let zs = [];
364 | xs.forEach(x => zs.push(x));
365 | ys.forEach(y => {
366 | if (!contains(xs, y, eq)) zs.push(y);
367 | });
368 | return zs;
369 | }
370 |
371 | function flatten(xs, eq) {
372 | return xs.reduce((acc, val) => union(acc, val, eq), []);
373 | }
374 |
375 | function get_random_from(list) {
376 | return list[Math.floor(Math.random() * list.length)]
377 | }
378 |
379 | // ---- Direction utils ----
380 |
381 | // Get random direction among n choices.
382 | function random_dir(n) {
383 | if (n === 2) return Math.random() > 0.5 ? 0 : 2; // Up or down (50/50)
384 | if (n === 3) return Math.random() > 0.5 ? 1 : Math.random() > 0.5 ? 2 : 0; // Up, right or down (25/50/25)
385 | return Math.floor(Math.random() * n); // Up, right, down or left (25/25/25/25)
386 | }
387 |
388 | // Get horizontally mirrored direction.
389 | function mirror(dir) {
390 | if (is_vertical(dir)) return dir;
391 | if (dir == 1) return 3;
392 | if (dir == 3) return 1;
393 | }
394 |
395 | // Check wether direction is vertical.
396 | function is_vertical(dir) {
397 | return dir == 0 || dir == 2;
398 | }
399 |
400 | const sketch = p => {
401 | let app_gen, apparata;
402 | const number_of_apparata = 9;
403 | const scale = 2.5;
404 | const shuffle = 300;
405 | let tick = 0;
406 | const final_frame_duration = 120;
407 | let symmetric_assembly = true;
408 | const movement_length = 0.85;
409 | const row_length = 3;
410 |
411 | p.setup = () => {
412 | p.createCanvas(1200, 1000);
413 | p.background('#eee8e2');
414 | p.fill(0);
415 | p.frameRate(40);
416 | p.strokeWeight(1.5);
417 | p.stroke('#5c3936');
418 |
419 | apparata = [];
420 | app_gen = new index(25, 35, {
421 | solidness: 0.5,
422 | initiate_chance: 0.9,
423 | extension_chance: 0.88,
424 | vertical_chance: 0.6,
425 | roundness: 0,
426 | group_size: 0.82,
427 | colors: get('retro-washedout').colors
428 | });
429 |
430 | for (let i = 0; i < number_of_apparata / row_length; i++) {
431 | setup_apparatus(row_length);
432 | }
433 | };
434 |
435 | function setup_apparatus(n) {
436 | let apparatus = app_gen.generate();
437 |
438 | for (let i = 0; i < n; i++) {
439 | let extra_shuffle = p.floor(p.random(100));
440 | let animatable_app = populate_apparatus(apparatus, extra_shuffle);
441 | animate_apparatus(animatable_app, extra_shuffle);
442 | apparata.push(animatable_app);
443 | }
444 | }
445 |
446 | function populate_apparatus(app, extra_shuffle) {
447 | let new_app = [];
448 | app.forEach(part => {
449 | let new_part = {
450 | ...part,
451 | x2: part.x1 + part.w,
452 | y2: part.y1 + part.h,
453 | path: []
454 | };
455 | for (let i = 0; i < final_frame_duration - extra_shuffle; i++) {
456 | new_part.path.push({ x: new_part.x1, y: new_part.y1 });
457 | }
458 | new_app.push(new_part);
459 | });
460 | return new_app;
461 | }
462 |
463 | function animate_apparatus(apparatus, extra_shuffle) {
464 | symmetric_assembly = true;
465 | let chosen, origin, direction;
466 | let start_from_new_part = true;
467 | let actual_ff_duration = final_frame_duration - extra_shuffle;
468 | for (let i = actual_ff_duration; i < shuffle; i++) {
469 | if (i - actual_ff_duration >= (shuffle - actual_ff_duration) / 2) {
470 | symmetric_assembly = false;
471 | }
472 |
473 | apparatus.forEach(part => {
474 | part.path.push({ x: part.x1, y: part.y1 });
475 | });
476 | if (start_from_new_part) {
477 | chosen = get_random_from(apparatus);
478 | origin = symmetric_assembly
479 | ? get_with_id(apparatus, chosen.id)
480 | : [chosen];
481 | direction =
482 | symmetric_assembly && origin.length === 1
483 | ? random_dir(2)
484 | : random_dir(symmetric_assembly ? 3 : 4);
485 | }
486 | start_from_new_part = p.random() > movement_length;
487 |
488 | if (is_vertical(direction) || !symmetric_assembly) {
489 | let neighborhood = get_neighborhood(origin, apparatus, direction);
490 | shift_all(neighborhood, direction, i);
491 | } else {
492 | let neighborhood_left = get_neighborhood(
493 | [origin[0]],
494 | apparatus,
495 | mirror(direction)
496 | );
497 | let neighborhood_right = get_neighborhood(
498 | [origin[1]],
499 | apparatus,
500 | direction
501 | );
502 | shift_all(neighborhood_left, mirror(direction), i);
503 | shift_all(neighborhood_right, direction, i);
504 | }
505 | }
506 | }
507 |
508 | p.draw = () => {
509 | p.background('#eee8e2');
510 | p.translate(
511 | (p.width - (app_gen.xdim + 2) * scale) / (row_length + 1),
512 | (p.height - (app_gen.ydim + 2) * scale) / 6
513 | );
514 |
515 | if (tick >= shuffle) {
516 | apparata = [];
517 | for (let i = 0; i < number_of_apparata / row_length; i++) {
518 | setup_apparatus(row_length);
519 | }
520 | tick = 0;
521 | }
522 |
523 | for (let i = 0; i < apparata.length; i++) {
524 | display_apparatus(apparata[i]);
525 | p.translate((p.width - (app_gen.xdim + 2) * scale) / (row_length + 1), 0);
526 | if (i % row_length === row_length - 1)
527 | p.translate(
528 | (-row_length * (p.width - (app_gen.xdim + 2) * scale)) /
529 | (row_length + 1),
530 | (p.height - (app_gen.ydim + 2) * scale) / 3
531 | );
532 | }
533 |
534 | tick++;
535 | };
536 |
537 | function display_apparatus(appar) {
538 | appar.forEach(part => {
539 | display_rect(part, scale, shuffle - tick - 1);
540 | });
541 | }
542 |
543 | function get_neighborhood(ps, rs, dir) {
544 | let ns = ps;
545 | let ms = union(
546 | ns,
547 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
548 | equal_rect
549 | );
550 |
551 | while (ms.length > ns.length) {
552 | ns = ms;
553 | ms = union(
554 | ns,
555 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect),
556 | equal_rect
557 | );
558 | }
559 | return ms;
560 | }
561 |
562 | function get_neighbors(r1, rs, dir) {
563 | return rs.filter(r => is_neighbor(r1, r, dir));
564 | }
565 |
566 | function is_neighbor(r1, r2, dir) {
567 | if (equal_rect(r1, r2)) return false; // Identical
568 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North
569 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East
570 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South
571 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West
572 | return false; // Error
573 | }
574 |
575 | function equal_rect(r1, r2) {
576 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2;
577 | }
578 |
579 | function shift_all(rs, dir, time) {
580 | rs.forEach(r => shift(r, dir, time));
581 | }
582 |
583 | function shift(r, dir, time) {
584 | let sx = is_vertical(dir) ? 0 : dir == 1 ? 1 : -1;
585 | let sy = !is_vertical(dir) ? 0 : dir == 2 ? 1 : -1;
586 |
587 | r.x1 += sx;
588 | r.y1 += sy;
589 | r.x2 += sx;
590 | r.y2 += sy;
591 | r.path[time] = { x: r.x1, y: r.y1 };
592 | }
593 |
594 | function get_with_id(rs, id) {
595 | return rs.filter(r => r.id === id);
596 | }
597 |
598 | function display_rect(r, scale, time) {
599 | p.fill(r.col ? r.col : '#fff');
600 | p.rect(
601 | r.path[time].x * scale,
602 | r.path[time].y * scale,
603 | r.w * scale,
604 | r.h * scale
605 | );
606 | }
607 |
608 | p.keyPressed = () => {
609 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly;
610 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png');
611 | };
612 | };
613 |
614 | new p5(sketch);
615 |
616 | })));
617 |
--------------------------------------------------------------------------------