├── .gitignore ├── README.md ├── .prettierrc ├── rollup.config.js ├── package.json ├── index-old.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apparatus Generator 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "trailingComma": "es5", 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | 5 | export default [ 6 | { 7 | input: 'index.js', 8 | output: { 9 | name: 'apparatus', 10 | file: pkg.browser, 11 | format: 'umd', 12 | }, 13 | plugins: [resolve(), commonjs()], 14 | }, 15 | { 16 | input: 'index.js', 17 | output: [ 18 | { file: pkg.main, format: 'cjs' }, 19 | { file: pkg.module, format: 'es' }, 20 | ], 21 | plugins: [resolve(), commonjs()], 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apparatus-generator", 3 | "version": "1.6.5", 4 | "description": "A library for generating apparatus shapes.", 5 | "homepage": "http://apparatus.generated.space", 6 | "author": "Kjetil Golid", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/kgolid/apparatus-generator.git" 11 | }, 12 | "main": "dist/index.cjs.js", 13 | "module": "dist/index.esm.js", 14 | "browser": "dist/index.umd.js", 15 | "files": [ 16 | "dist" 17 | ], 18 | "scripts": { 19 | "build": "rollup -c", 20 | "dev": "rollup -c -w" 21 | }, 22 | "dependencies": { 23 | "seed-random": "^2.2.0" 24 | }, 25 | "devDependencies": { 26 | "rollup": "^1.32.0", 27 | "@rollup/plugin-commonjs": "^11.0.2", 28 | "@rollup/plugin-node-resolve": "^7.1.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /index-old.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | constructor( 3 | width, 4 | height, 5 | { 6 | initiate_chance = 0.8, 7 | extension_chance = 0.8, 8 | vertical_chance = 0.8, 9 | horizontal_symmetry = true, 10 | vertical_symmetry = false, 11 | roundness = 0.1, 12 | solidness = 0.5, 13 | colors = [], 14 | color_mode = 'group', 15 | group_size = 0.8, 16 | simple = false, 17 | } = {} 18 | ) { 19 | this.xdim = Math.round(width * 2 + 11, 0); 20 | this.ydim = Math.round(height * 2 + 11, 0); 21 | this.radius_x = width; 22 | this.radius_y = height; 23 | this.chance_new = initiate_chance; 24 | this.chance_extend = extension_chance; 25 | this.chance_vertical = vertical_chance; 26 | this.colors = colors; 27 | this.color_mode = color_mode; 28 | this.group_size = group_size; 29 | this.h_symmetric = horizontal_symmetry; 30 | this.v_symmetric = vertical_symmetry; 31 | this.roundness = roundness; 32 | this.solidness = solidness; 33 | this.simple = simple; 34 | } 35 | 36 | generate(initial_top = null, initial_left = null, verbose = false) { 37 | this.main_color = get_random(this.colors); 38 | this.id_counter = 0; 39 | 40 | let grid = new Array(this.ydim + 1); 41 | for (var i = 0; i < grid.length; i++) { 42 | grid[i] = new Array(this.xdim + 1); 43 | for (var j = 0; j < grid[i].length; j++) { 44 | if (i == 0 || j == 0) grid[i][j] = { h: false, v: false, in: false, col: null }; 45 | else if (i == 1 && initial_top != null) grid[i][j] = { ...initial_top[j], h: true }; 46 | else if (j == 1 && initial_left != null) grid[i][j] = { ...initial_left[i], v: true }; 47 | else if (this.h_symmetric && j > grid[i].length / 2) { 48 | grid[i][j] = deep_copy(grid[i][grid[i].length - j]); 49 | grid[i][j].v = grid[i][grid[i].length - j + 1].v; 50 | } else if (this.v_symmetric && i > grid.length / 2) { 51 | grid[i][j] = deep_copy(grid[grid.length - i][j]); 52 | grid[i][j].h = grid[grid.length - i + 1][j].h; 53 | } else { 54 | grid[i][j] = this.next_block(j, i, grid[i][j - 1], grid[i - 1][j]); 55 | } 56 | } 57 | } 58 | let rects = convert_linegrid_to_rectangles(grid); 59 | return verbose ? [rects, grid] : rects; 60 | } 61 | 62 | next_block(x, y, left, top) { 63 | const context = this; 64 | 65 | if (!left.in && !top.in) { 66 | return block_set_1(x, y); 67 | } 68 | 69 | if (left.in && !top.in) { 70 | if (left.h) return block_set_3(x, y); 71 | return block_set_2(x, y); 72 | } 73 | 74 | if (!left.in && top.in) { 75 | if (top.v) return block_set_5(x, y); 76 | return block_set_4(x, y); 77 | } 78 | 79 | if (left.in && top.in) { 80 | if (!left.h && !top.v) return block_set_6(); 81 | if (left.h && !top.v) return block_set_7(x, y); 82 | if (!left.h && top.v) return block_set_8(x, y); 83 | return block_set_9(x, y); 84 | } 85 | 86 | // --- Block sets ---- 87 | 88 | function block_set_1(x, y) { 89 | if (start_new_from_blank(x, y)) return new_block(); 90 | return { v: false, h: false, in: false, col: null, id: null }; 91 | } 92 | 93 | function block_set_2(x, y) { 94 | if (start_new_from_blank(x, y)) return new_block(); 95 | return { v: true, h: false, in: false, col: null, id: null }; 96 | } 97 | 98 | function block_set_3(x, y) { 99 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 100 | return block_set_2(x, y); 101 | } 102 | 103 | function block_set_4(x, y) { 104 | if (start_new_from_blank(x, y)) return new_block(); 105 | return { v: false, h: true, in: false, col: null, id: null }; 106 | } 107 | 108 | function block_set_5(x, y) { 109 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 110 | return block_set_4(x, y); 111 | } 112 | 113 | function block_set_6() { 114 | return { v: false, h: false, in: true, col: left.col, id: left.id }; 115 | } 116 | 117 | function block_set_7(x, y) { 118 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 119 | if (start_new(x, y)) return new_block(); 120 | return { v: true, h: true, in: false, col: null, id: null }; 121 | } 122 | 123 | function block_set_8(x, y) { 124 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 125 | if (start_new(x, y)) return new_block(); 126 | return { v: true, h: true, in: false, col: null, id: null }; 127 | } 128 | 129 | function block_set_9(x, y) { 130 | if (vertical_dir()) return { v: true, h: false, in: true, col: top.col, id: top.id }; 131 | return { v: false, h: true, in: true, col: left.col, id: left.id }; 132 | } 133 | 134 | // ---- Blocks ---- 135 | 136 | function new_block() { 137 | let col; 138 | if (context.color_mode === 'random') { 139 | col = get_random(context.colors); 140 | } else if (context.color_mode === 'main') { 141 | col = Math.random() > 0.75 ? get_random(context.colors) : context.main_color; 142 | } else if (context.color_mode === 'group') { 143 | let keep = Math.random() > 0.5 ? left.col : top.col; 144 | context.main_color = 145 | Math.random() > context.group_size ? get_random(context.colors) : keep || context.main_color; 146 | col = context.main_color; 147 | } else { 148 | col = context.main_color; 149 | } 150 | 151 | return { v: true, h: true, in: true, col: col, id: context.id_counter++ }; 152 | } 153 | 154 | // ---- Decisions ---- 155 | 156 | function start_new_from_blank(x, y) { 157 | if (context.simple) return true; 158 | if (!active_position(x, y, -1 * (1 - context.roundness))) return false; 159 | return Math.random() <= context.solidness; 160 | } 161 | 162 | function start_new(x, y) { 163 | if (context.simple) return true; 164 | if (!active_position(x, y, 0)) return false; 165 | return Math.random() <= context.chance_new; 166 | } 167 | 168 | function extend(x, y) { 169 | if (!active_position(x, y, 1 - context.roundness) && !context.simple) return false; 170 | return Math.random() <= context.chance_extend; 171 | } 172 | 173 | function vertical_dir() { 174 | return Math.random() <= context.chance_vertical; 175 | } 176 | 177 | function active_position(x, y, fuzzy) { 178 | let fuzziness = 1 + Math.random() * fuzzy; 179 | let xa = Math.pow(x - context.xdim / 2, 2) / Math.pow(context.radius_x * fuzziness, 2); 180 | let ya = Math.pow(y - context.ydim / 2, 2) / Math.pow(context.radius_y * fuzziness, 2); 181 | return xa + ya < 1; 182 | } 183 | } 184 | } 185 | 186 | function get_random(array) { 187 | return array[Math.floor(Math.random() * array.length)]; 188 | } 189 | 190 | function deep_copy(obj) { 191 | let nobj = []; 192 | for (var key in obj) { 193 | if (obj.hasOwnProperty(key)) { 194 | nobj[key] = obj[key]; 195 | } 196 | } 197 | return nobj; 198 | } 199 | 200 | // --- Conversion --- 201 | function convert_linegrid_to_rectangles(grid) { 202 | let nw_corners = get_nw_corners(grid); 203 | extend_corners_to_rectangles(nw_corners, grid); 204 | return nw_corners; 205 | } 206 | 207 | function get_nw_corners(grid) { 208 | let nw_corners = []; 209 | for (let i = 0; i < grid.length; i++) { 210 | for (let j = 0; j < grid[i].length; j++) { 211 | let cell = grid[i][j]; 212 | if (cell.h && cell.v && cell.in) nw_corners.push({ x1: j, y1: i, col: cell.col, id: cell.id }); 213 | } 214 | } 215 | return nw_corners; 216 | } 217 | 218 | function extend_corners_to_rectangles(corners, grid) { 219 | corners.map(c => { 220 | let accx = 1; 221 | while (c.x1 + accx < grid[c.y1].length && !grid[c.y1][c.x1 + accx].v) { 222 | accx++; 223 | } 224 | let accy = 1; 225 | while (c.y1 + accy < grid.length && !grid[c.y1 + accy][c.x1].h) { 226 | accy++; 227 | } 228 | c.w = accx; 229 | c.h = accy; 230 | return c; 231 | }); 232 | } 233 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import seedrandom from 'seed-random'; 2 | 3 | export default class { 4 | constructor( 5 | width, 6 | height, 7 | { 8 | initiate_chance = 0.8, 9 | extension_chance = 0.8, 10 | vertical_chance = 0.8, 11 | horizontal_symmetry = true, 12 | vertical_symmetry = false, 13 | roundness = 0.1, 14 | solidness = 0.5, 15 | colors = [], 16 | color_mode = 'group', 17 | group_size = 0.8, 18 | simple = false, 19 | simplex = null, 20 | rate_of_change = 0.01, 21 | } = {} 22 | ) { 23 | this.xdim = Math.round(width * 2 + 11, 0); 24 | this.ydim = Math.round(height * 2 + 11, 0); 25 | this.radius_x = width; 26 | this.radius_y = height; 27 | this.chance_new = initiate_chance; 28 | this.chance_extend = extension_chance; 29 | this.chance_vertical = vertical_chance; 30 | this.colors = colors; 31 | this.color_mode = color_mode; 32 | this.group_size = group_size; 33 | this.h_symmetric = horizontal_symmetry; 34 | this.v_symmetric = vertical_symmetry; 35 | this.roundness = roundness; 36 | this.solidness = solidness; 37 | this.simple = simple; 38 | this.simplex = simplex; 39 | this.rate_of_change = rate_of_change; 40 | this.global_seed = Math.random(); 41 | } 42 | 43 | generate(initial_top = null, initial_left = null, verbose = false, idx = 0, idy = 0) { 44 | this.idx = idx; 45 | this.idy = idy; 46 | 47 | this.main_color = this.get_random(this.colors, 1, 1); 48 | this.id_counter = 0; 49 | 50 | let grid = new Array(this.ydim + 1); 51 | for (var i = 0; i < grid.length; i++) { 52 | grid[i] = new Array(this.xdim + 1); 53 | for (var j = 0; j < grid[i].length; j++) { 54 | if (i == 0 || j == 0) grid[i][j] = { h: false, v: false, in: false, col: null }; 55 | else if (i == 1 && initial_top != null) grid[i][j] = { ...initial_top[j], h: true }; 56 | else if (j == 1 && initial_left != null) grid[i][j] = { ...initial_left[i], v: true }; 57 | else if (this.h_symmetric && j > grid[i].length / 2) { 58 | grid[i][j] = deep_copy(grid[i][grid[i].length - j]); 59 | grid[i][j].v = grid[i][grid[i].length - j + 1].v; 60 | } else if (this.v_symmetric && i > grid.length / 2) { 61 | grid[i][j] = deep_copy(grid[grid.length - i][j]); 62 | grid[i][j].h = grid[grid.length - i + 1][j].h; 63 | } else { 64 | grid[i][j] = this.next_block(j, i, grid[i][j - 1], grid[i - 1][j]); 65 | } 66 | } 67 | } 68 | let rects = convert_linegrid_to_rectangles(grid); 69 | return verbose ? [rects, grid] : rects; 70 | } 71 | 72 | next_block(x, y, left, top) { 73 | const context = this; 74 | 75 | if (!left.in && !top.in) { 76 | return block_set_1(x, y); 77 | } 78 | 79 | if (left.in && !top.in) { 80 | if (left.h) return block_set_3(x, y); 81 | return block_set_2(x, y); 82 | } 83 | 84 | if (!left.in && top.in) { 85 | if (top.v) return block_set_5(x, y); 86 | return block_set_4(x, y); 87 | } 88 | 89 | if (left.in && top.in) { 90 | if (!left.h && !top.v) return block_set_6(); 91 | if (left.h && !top.v) return block_set_7(x, y); 92 | if (!left.h && top.v) return block_set_8(x, y); 93 | return block_set_9(x, y); 94 | } 95 | 96 | // --- Block sets ---- 97 | 98 | function block_set_1(x, y) { 99 | if (start_new_from_blank(x, y)) return new_block(x, y); 100 | return { v: false, h: false, in: false, col: null, id: null }; 101 | } 102 | 103 | function block_set_2(x, y) { 104 | if (start_new_from_blank(x, y)) return new_block(x, y); 105 | return { v: true, h: false, in: false, col: null, id: null }; 106 | } 107 | 108 | function block_set_3(x, y) { 109 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 110 | return block_set_2(x, y); 111 | } 112 | 113 | function block_set_4(x, y) { 114 | if (start_new_from_blank(x, y)) return new_block(x, y); 115 | return { v: false, h: true, in: false, col: null, id: null }; 116 | } 117 | 118 | function block_set_5(x, y) { 119 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 120 | return block_set_4(x, y); 121 | } 122 | 123 | function block_set_6() { 124 | return { v: false, h: false, in: true, col: left.col, id: left.id }; 125 | } 126 | 127 | function block_set_7(x, y) { 128 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 129 | if (start_new(x, y)) return new_block(x, y); 130 | return { v: true, h: true, in: false, col: null, id: null }; 131 | } 132 | 133 | function block_set_8(x, y) { 134 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 135 | if (start_new(x, y)) return new_block(x, y); 136 | return { v: true, h: true, in: false, col: null, id: null }; 137 | } 138 | 139 | function block_set_9(x, y) { 140 | if (vertical_dir(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 141 | return { v: false, h: true, in: true, col: left.col, id: left.id }; 142 | } 143 | 144 | // ---- Blocks ---- 145 | 146 | function new_block(nx, ny) { 147 | let col; 148 | if (context.color_mode === 'random') { 149 | col = context.get_random(context.colors, nx, ny); 150 | } else if (context.color_mode === 'main') { 151 | col = context.noise(x, y, '_main') > 0.75 ? context.get_random(context.colors, x, y) : context.main_color; 152 | } else if (context.color_mode === 'group') { 153 | let keep = context.noise(x, y, '_keep') > 0.5 ? left.col : top.col; 154 | context.main_color = 155 | context.noise(x, y, '_group') > context.group_size 156 | ? context.get_random(context.colors, x, y) 157 | : keep || context.main_color; 158 | col = context.main_color; 159 | } else { 160 | col = context.main_color; 161 | } 162 | 163 | return { v: true, h: true, in: true, col: col, id: context.id_counter++ }; 164 | } 165 | 166 | // ---- Decisions ---- 167 | 168 | function start_new_from_blank(x, y) { 169 | if (context.simple) return true; 170 | if (!active_position(x, y, -1 * (1 - context.roundness))) return false; 171 | return context.noise(x, y, '_blank') <= context.solidness; 172 | } 173 | 174 | function start_new(x, y) { 175 | if (context.simple) return true; 176 | if (!active_position(x, y, 0)) return false; 177 | return context.noise(x, y, '_new') <= context.chance_new; 178 | } 179 | 180 | function extend(x, y) { 181 | if (!active_position(x, y, 1 - context.roundness) && !context.simple) return false; 182 | return context.noise(x, y, '_extend') <= context.chance_extend; 183 | } 184 | 185 | function vertical_dir(x, y) { 186 | return context.noise(x, y, '_vert') <= context.chance_vertical; 187 | } 188 | 189 | function active_position(x, y, fuzzy) { 190 | let fuzziness = 1 + context.noise(x, y, '_active') * fuzzy; 191 | let xa = Math.pow(x - context.xdim / 2, 2) / Math.pow(context.radius_x * fuzziness, 2); 192 | let ya = Math.pow(y - context.ydim / 2, 2) / Math.pow(context.radius_y * fuzziness, 2); 193 | return xa + ya < 1; 194 | } 195 | } 196 | 197 | noise(nx, ny, nz = '') { 198 | if (!this.simplex) return Math.random(); 199 | const rng = seedrandom('' + this.global_seed + nx + ny + nz); 200 | const n = this.simplex.noise3D(this.idx * this.rate_of_change, this.idy * this.rate_of_change, rng() * 23.4567); 201 | return (n + 1) / 2; 202 | } 203 | 204 | get_random(array, nx, ny) { 205 | return array[Math.floor(this.noise(nx, ny, '_array') * array.length)]; 206 | } 207 | } 208 | 209 | function deep_copy(obj) { 210 | let nobj = []; 211 | for (var key in obj) { 212 | if (obj.hasOwnProperty(key)) { 213 | nobj[key] = obj[key]; 214 | } 215 | } 216 | return nobj; 217 | } 218 | 219 | // --- Conversion --- 220 | function convert_linegrid_to_rectangles(grid) { 221 | let nw_corners = get_nw_corners(grid); 222 | extend_corners_to_rectangles(nw_corners, grid); 223 | return nw_corners; 224 | } 225 | 226 | function get_nw_corners(grid) { 227 | let nw_corners = []; 228 | for (let i = 0; i < grid.length; i++) { 229 | for (let j = 0; j < grid[i].length; j++) { 230 | let cell = grid[i][j]; 231 | if (cell.h && cell.v && cell.in) nw_corners.push({ x1: j, y1: i, col: cell.col, id: cell.id }); 232 | } 233 | } 234 | return nw_corners; 235 | } 236 | 237 | function extend_corners_to_rectangles(corners, grid) { 238 | corners.map(c => { 239 | let accx = 1; 240 | while (c.x1 + accx < grid[c.y1].length && !grid[c.y1][c.x1 + accx].v) { 241 | accx++; 242 | } 243 | let accy = 1; 244 | while (c.y1 + accy < grid.length && !grid[c.y1 + accy][c.x1].h) { 245 | accy++; 246 | } 247 | c.w = accx; 248 | c.h = accy; 249 | return c; 250 | }); 251 | } 252 | --------------------------------------------------------------------------------