├── screenshots ├── minmax.png ├── hexstruct.png └── mondrian.png ├── .gitignore ├── .travis.yml ├── examples ├── OctopodII.es ├── Grinder.es ├── frameinframe.es ├── testminmaxsize.es ├── menger.es ├── city.es ├── mondrian.es ├── PanelStruct.es ├── Konstrukt.es ├── HexStruct.es ├── Schmiegel.es └── Noct.es ├── pack-examples.sh ├── package.json ├── structure.ts ├── README.md ├── mega-structure.html ├── gulpfile.js ├── progress.ts ├── synthesizer-webworker.ts ├── mega-structure.ts ├── codemirror-eisen-script-mode.js ├── eisen-script.peg └── synthesizer.ts /screenshots/minmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/mega-structure/master/screenshots/minmax.png -------------------------------------------------------------------------------- /screenshots/hexstruct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/mega-structure/master/screenshots/hexstruct.png -------------------------------------------------------------------------------- /screenshots/mondrian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/mega-structure/master/screenshots/mondrian.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bundle.js 2 | eisen-script.js 3 | synthesizer-webworker.js 4 | examples-generated.ts 5 | id_rsa 6 | id_rsa.pub 7 | node_modules 8 | typings 9 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '7' 4 | before_script: 5 | - npm install -g gulp 6 | script: 7 | - gulp 8 | - gulp install 9 | deploy: 10 | provider: pages 11 | skip_cleanup: true 12 | github_token: $GITHUB_TOKEN 13 | local_dir: _site 14 | on: 15 | branch: master 16 | 17 | -------------------------------------------------------------------------------- /examples/OctopodII.es: -------------------------------------------------------------------------------- 1 | set maxdepth 100 2 | 3 | 10 * { ry 36 sat 0.9 } 30 * { ry 10 } 1 * { h 30 b 0.8 sat 0.8 a 0.3 } r1 4 | 5 | rule r1 w 20 { 6 | { s 0.9 rz 5 h 5 rx 5 x 1 } r1 7 | { s 1 0.2 0.5 } box 8 | } 9 | 10 | rule r1 w 20 { 11 | { s 0.99 rz -5 h 5 rx -5 x 1 } r1 12 | { s 1 0.2 0.5 } box 13 | } 14 | 15 | rule r1 { 16 | } -------------------------------------------------------------------------------- /pack-examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "// Code auto-generated by $(basename $0)." 5 | echo "// Do not edit." 6 | echo "var fs = require('fs');" 7 | echo "var EisenScripts : { [s: string]: string; } = {};" 8 | for i in $(ls examples/*.es); do 9 | base=$(basename $i) 10 | echo "EisenScripts[\"${base%%.es}\"]=fs.readFileSync('$i', 'ascii');"; 11 | done 12 | echo "export = EisenScripts;" 13 | -------------------------------------------------------------------------------- /examples/Grinder.es: -------------------------------------------------------------------------------- 1 | // Grinder.es 2 | 3 | set maxobjects 117600 4 | grinder 5 | 6 | rule grinder { 7 | 36 * { ry 10 z 1.2 b 0.99 } 1 * { ry 34 rx 34 } arm 8 | } 9 | 10 | rule arm { 11 | 12 | { rz 2 rx 3 z 0.22 x 0.3 ry 10 s 0.999 x 0.5 sat 0.9995 hue 0.05 } arm 13 | xbox 14 | } 15 | 16 | rule arm { 17 | 18 | { rz 2 rx 3 z 0.22 x 0.3 ry 10 s 0.99 x 0.5 sat 0.9995 hue 0.15 } arm 19 | xbox 20 | } 21 | 22 | rule xbox { 23 | { b 0.7 } box 24 | } 25 | -------------------------------------------------------------------------------- /examples/frameinframe.es: -------------------------------------------------------------------------------- 1 | set background white 2 | 3 | { hue 20 sat 0.8 } r2 4 | 5 | rule r2 maxdepth 20 { 6 | { s 0.75 rz 10 b 0.9 } r2 7 | frame 8 | } 9 | 10 | rule frame { 11 | { s 0.1 1.1 0.1 x 5 z 5 } box 12 | { s 0.1 1.1 0.1 x 5 z -5 } box 13 | { s 0.1 1.1 0.1 x -5 z 5 } box 14 | { s 0.1 1.1 0.1 x -5 z -5 } box 15 | 16 | { s 1 0.1 0.1 y 5 z 5 } box 17 | { s 1 0.1 0.1 y 5 z -5 } box 18 | { s 1 0.1 0.1 y -5 z 5 } box 19 | { s 1 0.1 0.1 y -5 z -5 } box 20 | 21 | { s 0.1 0.1 1 y 5 x 5 } box 22 | { s 0.1 0.1 1 y 5 x -5 } box 23 | { s 0.1 0.1 1 y -5 x 5 } box 24 | { s 0.1 0.1 1 y -5 x -5 } box 25 | } 26 | -------------------------------------------------------------------------------- /examples/testminmaxsize.es: -------------------------------------------------------------------------------- 1 | set minsize 0.8 // or 0.4, or 0.2 2 | 3 | set maxdepth 600 4 | set background #333 5 | { h 30 sat 0.2 h -67 b 0.8 } spiral 6 | 7 | rule spiral w 100 { 8 | box2 9 | { y 0.4 rx 90 hue 1 s 0.995 b 0.999 } spiral 10 | } 11 | 12 | rule spiral w 100 { 13 | box2 14 | { y 0.4 rx 90 hue -1 rz -90 s 0.995 b 0.999 } spiral 15 | } 16 | 17 | rule spiral w 100 { 18 | box2 19 | { y 0.4 rx 90 hue 0 rz 90 s 0.995 b 0.995 } spiral 20 | } 21 | 22 | rule spiral w 3 { 23 | { rz 5 s 1 1 1 } spiral 24 | { ry 4 h 3 s 1 1 1 } spiral 25 | } 26 | 27 | rule box2 { 28 | { s 1 5 1 } box 29 | } 30 | 31 | rule box2 { 32 | { s 5 1 1 } box 33 | } 34 | 35 | rule box2 { 36 | } -------------------------------------------------------------------------------- /examples/menger.es: -------------------------------------------------------------------------------- 1 | R1 2 | 3 | rule R1 maxdepth 3 > c2 { 4 | { s 1/3 x -1 y -1 } R1 5 | { s 1/3 x -1 y -1 z -1 } R1 6 | { s 1/3 x -1 y -1 z +1 } R1 7 | { s 1/3 x 1 y -1 } R1 8 | { s 1/3 x 1 y -1 z -1 } R1 9 | { s 1/3 x 1 y -1 z +1 } R1 10 | { s 1/3 y -1 z -1 } R1 11 | { s 1/3 y -1 z +1 } R1 12 | { s 1/3 x -1 y 1 } R1 13 | { s 1/3 x -1 y 1 z -1 } R1 14 | { s 1/3 x -1 y 1 z +1 } R1 15 | { s 1/3 x 1 y 1 } R1 16 | { s 1/3 x 1 y 1 z -1 } R1 17 | { s 1/3 x 1 y 1 z +1 } R1 18 | { s 1/3 y 1 z -1 } R1 19 | { s 1/3 y 1 z +1 } R1 20 | { s 1/3 x -1 z -1 } R1 21 | { s 1/3 x -1 z +1 } R1 22 | { s 1/3 x 1 z -1 } R1 23 | { s 1/3 x 1 z +1 } R1 24 | } 25 | 26 | rule c2 { 27 | box 28 | } 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/city.es: -------------------------------------------------------------------------------- 1 | {color white} r1 2 | {x 0 y 0 z -0.05 s 5 5 0.1 color grey}box 3 | //rule 1, fills the square 4 | 5 | rule r1 md 4{ 6 | { x -0.25 y 0.25 s 0.5 0.5 0.75 rz 180} r1 7 | { x 0.25 y -0.25 s 0.5 0.5 1 rz 0} r1 8 | { x -0.25 y -0.25 s 0.5 0.5 1 rz 90} r1 9 | tower} 10 | 11 | rule r1 md 5 { 12 | { x -0.25 y 0.25 s 0.5 0.5 1 rz 0} r1 13 | { x 0.25 y -0.25 s 0.5 0.5 0.75 rz 90} r1 14 | { x -0.25 y -0.25 s 0.5 0.5 1 rz 90} r1 15 | tower} 16 | 17 | rule tower w 1{ 18 | {z 0.01 rz 3 s 0.8}tower 19 | base} 20 | rule tower w 1{ 21 | {z 0.01 rz 3 s 1.01}tower 22 | base} 23 | rule tower w 1{ 24 | {z 0.01 rz 3 s 1.02}tower 25 | base} 26 | rule tower w 1{ 27 | {z 0.01 rz 3 s 0.95}tower 28 | base} 29 | rule tower w 0.5{} 30 | 31 | rule base { 32 | {x 0.25 y 0.25 s 0.45 0.45 0.01 sat 0.3}box} -------------------------------------------------------------------------------- /examples/mondrian.es: -------------------------------------------------------------------------------- 1 | // Mondrian Cube. 2 | 3 | mondrian 4 | 5 | rule mondrian { 6 | // The six faces of the cube 7 | a2 8 | { x -0.5 z -0.5 ry 90 } a2 9 | { x +0.5 z -0.5 ry 90 } a2 10 | { z -1 } a2 11 | { y +0.5 z -0.5 rx 90 } a2 12 | { y -0.5 z -0.5 rx 90 } a2 13 | } 14 | 15 | rule a2 w 2 maxdepth 2 > d { 16 | // Split into two halves in x direction 17 | { s 0.333 1 1 x -1 } a2 18 | { s 0.666 1 1 x 0.26 } a2 19 | } 20 | 21 | rule a2 w 2 maxdepth 2 > d { 22 | // Split into two halves in y direction 23 | { s 1 0.333 1 y -1 } a2 24 | { s 1 0.666 1 y 0.26 } a2 25 | } 26 | 27 | 28 | rule a2 { d } 29 | 30 | // This rule chooses a random color. 31 | rule d { { s 1 1 0.02 color #F00 } square } 32 | rule d { { s 1 1 0.02 color #0404A2 } square } 33 | rule d { { s 1 1 0.02 color #FFFE33 } square } 34 | rule d w 2 { { s 1 1 0.02 color #FFF } square } 35 | 36 | // Draws a framed square 37 | rule square { 38 | { s 0.95 0.95 1 } box 39 | { s 0.05 1 1 b 0 x -10 } box 40 | { s 0.05 1 1 b 0 x 10 } box 41 | { s 1.05 0.05 1 b 0 y -10 } box 42 | { s 1.05 0.05 1 b 0 y 10 } box 43 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mega-structure", 3 | "version": "1.0.0", 4 | "description": "A structure synth clone", 5 | "main": "mega-structure.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ssrb/mega-structure.git" 12 | }, 13 | "keywords": [ 14 | "generative", 15 | "art" 16 | ], 17 | "author": "ssrb", 18 | "license": "BSD-2-Clause", 19 | "bugs": { 20 | "url": "https://github.com/ssrb/mega-structure/issues" 21 | }, 22 | "homepage": "https://github.com/ssrb/mega-structure#readme", 23 | "dependencies": { 24 | "@types/browserify": "^12.0.33", 25 | "@types/codemirror": "0.0.56", 26 | "@types/gl-matrix": "^2.4.0", 27 | "@types/jquery": "^3.3.1", 28 | "@types/node": "^8.10.1", 29 | "@types/pegjs": "^0.10.0", 30 | "@types/seedrandom": "^2.4.27", 31 | "@types/three": "^0.93.0", 32 | "@types/tinycolor2": "^1.4.0", 33 | "bootstrap": "^4.1.0", 34 | "brfs": "^1.4.3", 35 | "browserify": "^16.1.1", 36 | "codemirror": "^5.36.0", 37 | "gl-matrix": "^2.4.0", 38 | "gulp": "^3.9.1", 39 | "gulp-peg": "^0.2.0", 40 | "gulp-util": "^3.0.7", 41 | "gulp-file": "^0.4.0", 42 | "jquery": "^3.3.1", 43 | "npm": "^5.8.0", 44 | "run-sequence": "^1.1.5", 45 | "seedrandom": "^2.4.3", 46 | "three": "^0.93.0", 47 | "three-orbit-controls": "^82.1.0", 48 | "tinycolor2": "^1.4.1", 49 | "tsify": "^3.0.4", 50 | "typescript": "^2.8.1", 51 | "typescript-collections": "^1.1.2", 52 | "uglifyify": "^3.0.1", 53 | "vinyl-source-stream": "^1.1.0", 54 | "watchify": "^3.7.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /structure.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Sebastien Sydney Robert Bigot 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | // The views and conclusions contained in the software and documentation are those 25 | // of the authors and should not be interpreted as representing official policies, 26 | // either expressed or implied, of the FreeBSD Project. 27 | import * as tinycolor from 'tinycolor2'; 28 | 29 | export interface ShapeInstance { 30 | shape: string; 31 | geospace: Float32Array; 32 | colorspace: tinycolor.ColorFormats.HSVA; 33 | } -------------------------------------------------------------------------------- /examples/PanelStruct.es: -------------------------------------------------------------------------------- 1 | set background #444 2 | 3 | set raytracer::light [0,5000,5000] 4 | set raytracer::samples 6 5 | set raytracer::ambient-occlusion-samples 2 6 | set raytracer::phong [0.6,0.6,0.9] 7 | 8 | { color white z 4.5 } Core 9 | 10 | rule Core 11 | { 12 | { y 4.5 } PanelStruct 13 | } 14 | 15 | // Panelstruct 16 | 17 | rule PanelStruct w 1 md 4 18 | { 19 | { ry 90 } Panel 20 | { y 2.0 } PanelStruct 21 | } 22 | 23 | rule PanelStruct2 w 0 24 | { 25 | {} Panel 26 | } 27 | 28 | rule Panel md 5 29 | { 30 | {}PanelPart 31 | { ry -60 x 0.325 z 0.6 } Panel 32 | } 33 | 34 | rule Panel2 md 5 w 50 35 | { 36 | {}PanelPart 37 | { ry 60 x 0.325 z 0.6 } Panel 38 | } 39 | 40 | rule Panel2 md 1 w 0 41 | { 42 | } 43 | 44 | rule PanelPart w 4 45 | { 46 | { rz 90 s 0.2 0.05 0.1 } beamAssembly 47 | { s 0.05 2.0 0.8 } box 48 | } 49 | 50 | rule PanelPart w 2 51 | { 52 | { b 0.5 rz 90 s 0.2 0.05 0.1 } beamAssembly 53 | { b 0.5 s 0.05 2.0 0.8 } box 54 | } 55 | 56 | // Beam 57 | 58 | rule beamAssembly w 4 59 | { 60 | { z -5 } beam 61 | { z 5 } beam 62 | 63 | { s 1 0.2 11 y 12 } box 64 | { s 1 0.2 11 y -12 } box 65 | 66 | { z 0.4 } vertPanel 67 | { z 5 } vertPanel 68 | { z -5 } vertPanel 69 | } 70 | 71 | rule beamAssembly w 2 72 | { 73 | } 74 | 75 | rule vertPanel 76 | { 77 | {s 1 4 0.2} box 78 | {s 1 1 0.5} box 79 | } 80 | 81 | rule vertPanel md 15 > end 82 | { 83 | widePane 84 | { y 1 } vertPanel 85 | } 86 | 87 | rule end 88 | { 89 | } 90 | 91 | rule vertPanel 92 | { 93 | {s 1 1 0.5 } box 94 | {s 0.2 4 0.2 x 2 } box 95 | {s 0.2 4 0.2 x -2 } box 96 | {s 1 0.2 0.2 y 10 } box 97 | {s 1 0.2 0.2 y -10 } box 98 | } 99 | 100 | rule beam 101 | { 102 | { s 0.2 5 0.2 } box 103 | } 104 | 105 | rule widePane 106 | { 107 | thinBeam1 108 | { y 5 } thinBeam1 109 | { x 4.9 y 2.5 } thinBeamVert 110 | { x -4.9 y 2.5 } thinBeamVert 111 | pane 112 | } 113 | 114 | rule pane 115 | { 116 | {s 10 5 0.05 y 0.5} box 117 | } 118 | 119 | rule pane 120 | { 121 | { s 10 2.5 0.05 y 0.5 } box 122 | } 123 | 124 | rule thinBeam1 125 | { 126 | { s 10 0.2 0.2 } box 127 | } 128 | 129 | rule thinBeamVert 130 | { 131 | { s 0.2 5 0.2 } box 132 | } -------------------------------------------------------------------------------- /examples/Konstrukt.es: -------------------------------------------------------------------------------- 1 | set background #000 2 | #define _md 34 3 | #define _rz 0 4 | #define _zoom 1 5 | 6 | set maxdepth _md 7 | 8 | set background #222 9 | 10 | set raytracer::light [0,5000,5000] 11 | set raytracer::samples 8 12 | set raytracer::ambient-occlusion-samples 2 13 | set raytracer::phong [0.5,0.5,0.125] 14 | 15 | { rz _rz s _zoom } r0 16 | 17 | rule r0 { 18 | 3 * { rz 120 } R1 19 | 3 * { rz 120 } R2 20 | } 21 | 22 | rule R1 { 23 | { x -0.8 rz 6 ry 12 s 0.97 } R1 24 | { color white ry 90 s 1 } Panel 25 | } 26 | 27 | rule R2 { 28 | set seed initial 29 | { x -1.0 rz 6 ry 12 s 0.98 } R2 30 | { color white ry 90 s 0.75 } Panel 31 | } 32 | 33 | rule Panel md 2 > Panel2 34 | { 35 | {}PanelPart 36 | { y 1.0 rz -60 y 1.0 } Panel 37 | } 38 | 39 | rule Panel2 md 4 w 50 40 | { 41 | PanelPart 42 | { y 1.0 rz -60 y 1.0 } Panel2 43 | } 44 | 45 | rule Panel2 w 2 46 | { 47 | } 48 | 49 | rule PanelPart w 4 50 | { 51 | { rz 90 s 0.2 0.05 0.1 } beamAssembly 52 | { s 0.05 2.0 0.8 } box 53 | } 54 | 55 | rule PanelPart w 2 56 | { 57 | { b 0.5 rz 90 s 0.2 0.05 0.1 } beamAssembly 58 | { b 0.5 s 0.05 2.0 0.8 } box 59 | } 60 | 61 | // Beam 62 | 63 | rule beamAssembly w 4 64 | { 65 | { z -5 } beam 66 | { z 5 } beam 67 | 68 | { s 1 0.2 11 y 12 } box 69 | { s 1 0.2 11 y -12 } box 70 | 71 | { z 0.4 } vertPanel 72 | { z 5 } vertPanel 73 | { z -5 } vertPanel 74 | } 75 | 76 | rule beamAssembly w 2 77 | { 78 | } 79 | 80 | rule vertPanel 81 | { 82 | {s 1 4 0.2} box 83 | {s 1 1 0.5} box 84 | } 85 | 86 | rule vertPanel md 15 > end 87 | { 88 | widePane 89 | { y 1 } vertPanel 90 | } 91 | 92 | rule end 93 | { 94 | } 95 | 96 | rule vertPanel 97 | { 98 | {s 1 1 0.5 } box 99 | {s 0.2 4 0.2 x 2 } box 100 | {s 0.2 4 0.2 x -2 } box 101 | {s 1 0.2 0.2 y 10 } box 102 | {s 1 0.2 0.2 y -10 } box 103 | } 104 | 105 | rule beam 106 | { 107 | { s 0.2 5 0.2 } box 108 | } 109 | 110 | rule widePane 111 | { 112 | thinBeam1 113 | { y 5 } thinBeam1 114 | { x 4.9 y 2.5 } thinBeamVert 115 | { x -4.9 y 2.5 } thinBeamVert 116 | pane 117 | } 118 | 119 | rule pane 120 | { 121 | {s 10 5 0.05 y 0.5} box 122 | } 123 | 124 | rule pane 125 | { 126 | { s 10 2.5 0.05 y 0.5 } box 127 | } 128 | 129 | rule thinBeam1 130 | { 131 | { s 10 0.2 0.2 } box 132 | } 133 | 134 | rule thinBeamVert 135 | { 136 | { s 0.2 5 0.2 } box 137 | } -------------------------------------------------------------------------------- /examples/HexStruct.es: -------------------------------------------------------------------------------- 1 | set background #444 2 | 3 | set raytracer::light [0,5000,5000] 4 | set raytracer::samples 8 5 | set raytracer::ambient-occlusion-samples 2 6 | set raytracer::phong [0.6,0.6,0.9] 7 | 8 | { color white } Core 9 | 10 | rule Core 11 | { 12 | SphereStuct 13 | } 14 | 15 | rule SphereStuct w 40 md 15 16 | { 17 | ubox 18 | dbox 19 | { x 2 y 3.25 ry 12 } SphereStuct 20 | } 21 | 22 | rule SphereStuct w 14 { r2 } 23 | 24 | rule r2 w 10 25 | { 26 | {} r2 27 | } 28 | 29 | rule r2 { SphereStuct } 30 | 31 | rule dbox w 8 maxdepth 3 32 | { 33 | { x 0 y -6.5 rx 3.6 } dbox 34 | { ry 6 rx -1.8 } Panel 35 | } 36 | rule dbox w 8 maxdepth 2 37 | { 38 | { x 0 y -6.5 rx 3.6 } dbox 39 | { b 0.5 ry 6 rx -1.8 } Panel 40 | } 41 | rule dbox { } 42 | 43 | rule ubox w 8 maxdepth 3 44 | { 45 | { x 0 y 6.5 rx -3.6 } ubox 46 | { ry 6 rx 1.8 } Panel 47 | } 48 | rule ubox w 8 maxdepth 2 49 | { 50 | { x 0 y 6.5 rx -3.6 } ubox 51 | { b 0.5 ry 6 rx 1.8 } Panel 52 | } 53 | rule ubox { } 54 | 55 | rule Panel md 1 w 1 56 | { 57 | { y 1.0 rz -60 y 1.0 } Panel 58 | } 59 | 60 | rule Panel md 6 w 16 61 | { 62 | PanelPart 63 | { y 1.0 rz -60 y 1.0 } Panel 64 | } 65 | rule PanelPart 66 | { 67 | { rz 90 s 0.2 0.2 0.1 } beamAssembly 68 | { s 0.1 1.75 1 } box 69 | } 70 | 71 | // Beam 72 | 73 | rule beamAssembly w 1 74 | { 75 | { z -5 } beam 76 | { z 5 } beam 77 | 78 | { s 1 0.2 11 y 12 } box 79 | { s 1 0.2 11 y -12 } box 80 | 81 | { z 0.4 } vertPanel 82 | { z 5 } vertPanel 83 | { z -5 } vertPanel 84 | } 85 | 86 | rule beamAssembly w 6 87 | { 88 | } 89 | 90 | rule vertPanel 91 | { 92 | {s 1 4 0.2} box 93 | {s 1 1 0.5} box 94 | } 95 | 96 | rule vertPanel md 15 > end 97 | { 98 | widePane 99 | { y 1 } vertPanel 100 | } 101 | 102 | rule end 103 | { 104 | } 105 | 106 | rule vertPanel 107 | { 108 | {s 1 1 0.5 } box 109 | {s 0.2 4 0.2 x 2 } box 110 | {s 0.2 4 0.2 x -2 } box 111 | {s 1 0.2 0.2 y 10 } box 112 | {s 1 0.2 0.2 y -10 } box 113 | } 114 | 115 | rule beam 116 | { 117 | { s 0.2 5 0.2 } box 118 | } 119 | 120 | rule widePane 121 | { 122 | thinBeam1 123 | { y 5 } thinBeam1 124 | { x 4.9 y 2.5 } thinBeamVert 125 | { x -4.9 y 2.5 } thinBeamVert 126 | pane 127 | } 128 | 129 | rule pane 130 | { 131 | {s 10 5 0.05 y 0.5} box 132 | } 133 | 134 | rule pane 135 | { 136 | { s 10 2.5 0.05 y 0.5 } box 137 | } 138 | 139 | rule thinBeam1 140 | { 141 | { s 10 0.2 0.2 } box 142 | } 143 | 144 | rule thinBeamVert 145 | { 146 | { s 0.2 5 0.2 } box 147 | } -------------------------------------------------------------------------------- /examples/Schmiegel.es: -------------------------------------------------------------------------------- 1 | set background white 2 | //{ s 50 0.1 50 y -6 color white } box 3 | 4 | { b 0.99 sat 0.1 hue 0} r1 5 | 6 | 7 | rule r1 maxdepth 4 > void { 8 | { s 1/3 x -0.95 y -0.95 } r2 9 | { s 1/3 x -0.95 y -0.95 z -0.95 } r2 10 | { s 1/3 x -0.95 y -0.95 z +0.95 } r2 11 | { s 1/3 x 0.95 y -0.95 } r2 12 | { s 1/3 x 0.95 y -0.95 z -0.95 } r2 13 | { s 1/3 x 0.95 y -0.95 z +0.95 } r2 14 | { s 1/3 y -0.95 z -0.95 } r2 15 | { s 1/3 y -0.95 z +0.95 } r2 16 | { s 1/3 x -0.95 y 0.95 } r2 17 | { s 1/3 x -0.95 y 0.95 z -0.95 } r2 18 | { s 1/3 x -0.95 y 0.95 z +0.95 } r2 19 | { s 1/3 x 0.95 y 0.95 } r2 20 | { s 1/3 x 0.95 y 0.95 z -0.95 } r2 21 | { s 1/3 x 0.95 y 0.95 z +0.95 } r2 22 | { s 1/3 y 0.95 z -0.95 } r2 23 | { s 1/3 y 0.95 z +0.95 } r2 24 | { s 1/3 x -0.95 z -0.95 } r2 25 | { s 1/3 x -0.95 z +0.95 } r2 26 | { s 1/3 x 0.95 z -0.95 } r2 27 | { s 1/3 x 0.95 z +0.95 } r2 28 | } 29 | 30 | rule void { 31 | } 32 | 33 | rule r2 { 34 | { hue 37 s 0.9 } frameR 35 | } 36 | rule r2 { 37 | { hue 8 s 0.9 } frameR 38 | } 39 | 40 | 41 | 42 | 43 | //Make Whacky 44 | rule frameR { 45 | { rx 1 ry 2 rz 3} frame 46 | } 47 | rule frameR { 48 | { rx 2 ry 3 rz 1} frame 49 | } 50 | rule frameR { 51 | { rx 3 ry 1 rz 2} frame 52 | } 53 | 54 | rule frameR { 55 | { rx -1 ry -2 rz -3} frame 56 | } 57 | rule frameR { 58 | { rx -2 ry -3 rz -1} frame 59 | } 60 | rule frameR { 61 | { rx -3 ry -1 rz -2} frame 62 | } 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | rule r2 { 71 | { hue 19 } r1 72 | } 73 | 74 | 75 | rule r2 { 76 | { hue 20 } r1 77 | } 78 | 79 | 80 | rule frame { 81 | { s 0.05 1.05 0.05 x 10 z 10 } box 82 | { s 0.05 1.05 0.05 x 10 z -10 } box 83 | { s 0.05 1.05 0.05 x -10 z 10 } box 84 | { s 0.05 1.05 0.05 x -10 z -10 } box 85 | 86 | { s 1 0.05 0.05 y 10 z 10 } box 87 | { s 1 0.05 0.05 y 10 z -10 } box 88 | { s 1 0.05 0.05 y -10 z 10 } box 89 | { s 1 0.05 0.05 y -10 z -10 } box 90 | 91 | { s 0.05 0.05 1 y 10 x 10 } box 92 | { s 0.05 0.05 1 y 10 x -10 } box 93 | { s 0.05 0.05 1 y -10 x 10 } box 94 | { s 0.05 0.05 1 y -10 x -10 } box 95 | 96 | { s 1.04 h 180 b 0.9} sphere 97 | } 98 | 99 | rule frame { 100 | { s 0.05 1.05 0.05 x 10 z 10 } box 101 | { s 0.05 1.05 0.05 x 10 z -10 } box 102 | { s 0.05 1.05 0.05 x -10 z 10 } box 103 | { s 0.05 1.05 0.05 x -10 z -10 } box 104 | 105 | { s 1 0.05 0.05 y 10 z 10 } box 106 | { s 1 0.05 0.05 y 10 z -10 } box 107 | { s 1 0.05 0.05 y -10 z 10 } box 108 | { s 1 0.05 0.05 y -10 z -10 } box 109 | 110 | { s 0.05 0.05 1 y 10 x 10 } box 111 | { s 0.05 0.05 1 y 10 x -10 } box 112 | { s 0.05 0.05 1 y -10 x 10 } box 113 | { s 0.05 0.05 1 y -10 x -10 } box 114 | 115 | { s 1.04 h 240 b 0.9} sphere 116 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mega-structure 2 | 3 | [![Build Status](https://travis-ci.org/ssrb/mega-structure.png)](https://travis-ci.org/ssrb/mega-structure) 4 | 5 | mega-structure is a structure synthesizer for the web based on [Mikael Hvidtfeldt](http://hvidtfeldts.net)'s eisenscript language. 6 | 7 | ![Screenshot 1](https://raw.githubusercontent.com/ssrb/mega-structure/master/screenshots/hexstruct.png) 8 | 9 | ![Screenshot 2](https://raw.githubusercontent.com/ssrb/mega-structure/master/screenshots/minmax.png) 10 | 11 | ![Screenshot 3](https://raw.githubusercontent.com/ssrb/mega-structure/master/screenshots/mondrian.png) 12 | 13 | The [Structure Synth](http://structuresynth.sourceforge.net) application provides the reference implementation for the language. 14 | 15 | A similar project is called [Eisenscript](https://github.com/after12am/eisenscript). 16 | 17 | ### Eisenscript syntax 18 | 19 | #### Termination criteria 20 | 21 | * **set maxdepth [integer]**: break a generation path as soon as a it's [integer] long; 22 | * **set maxobjects [integer]**: stop as soon as [integer] objects have been created; 23 | * **set minsize/maxsize [float]**: break a generation path when the local coordinate frame diagonal is below/above [float] units; 24 | * **set seed [integer]**: seed the PRNG used to choose between rules; 25 | * **set background [color]**: self-explanatory 26 | 27 | #### Rule modifiers 28 | 29 | * **md / maxdepth [integer]**: break a generation path if it includes [integer] call to that rule; 30 | * **md / maxdepth [integer] > [rulename]**: same as above, failover rule [rulename] once the limit is reached; 31 | * **w / weight [float]**: how likely the rule is going to be selected in case multiple rules have the same name. Default weight is 1. 32 | 33 | #### Transformations 34 | 35 | ##### Geometric transformations 36 | 37 | * **x [float]**: [float] units translation along the X axis; 38 | * **y [float]**: as above; 39 | * **z [float]**: as above; 40 | * **rx [float]**: [float] degrees rotation about an axis colinear to X going through local coordinate (0, 0.5, 0.5); 41 | * **ry [float]**: as above; 42 | * **rz [float]**: as above; 43 | * **s [float]**: uniformely scale by [float] along the 3 axis; 44 | * **s [f1] [f2] [f3]**: scale along the X axis by [f1], along Y by [f2], along Z by [f3]; 45 | * **m [f1] ... [f9]**: 3x3 generic matrix transformation; 46 | * **fx**: X axis mirror (flip sign of the frame x coordinates); 47 | * **fy**: as above; 48 | * **fz**: as above; 49 | 50 | ##### Color space transformations 51 | 52 | * **h / hue [float]**: add [float] degrees to the current color hue. Result is [0-360] normalized; 53 | * **sat [float]**: multiply the current color saturation by [float]. Result is [0-1] clamped; 54 | * **b / brightness [float]**: multiply the current color brightness (HSV value) by [float]. Result is [0-1] clamped; 55 | * **a / alpha [float]**: multiply the current color alpha by [float]. Result is [0-1] clamped; 56 | * **color [color]**: set the current color to [color] 57 | 58 | -------------------------------------------------------------------------------- /mega-structure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | mega-structure 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 73 | 74 |
75 |
76 |
77 |
78 | 79 |
80 | 81 | 82 | Fork me on GitHub 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var source = require('vinyl-source-stream'); 3 | var browserify = require('browserify'); 4 | var watchify = require('watchify'); 5 | var tsify = require('tsify'); 6 | var uglify = require('uglifyify'); 7 | var runSequence = require('run-sequence'); 8 | var peg = require('gulp-peg'); 9 | var gutil = require('gulp-util'); 10 | var newfile = require('gulp-file'); 11 | 12 | gulp.task('.npm.clean', function (cb) { 13 | var del = require('del'); 14 | del(['node_modules/'], cb); 15 | }); 16 | 17 | gulp.task('watch', function () { 18 | var bundler = watchify(browserify({ debug: true }) 19 | .add('synthesizer.ts') 20 | .plugin(tsify) 21 | .transform(browserifyShader)); 22 | 23 | bundler.on('update', rebundle) 24 | 25 | function rebundle() { 26 | return bundler.bundle() 27 | .pipe(source('bundle.js')) 28 | .pipe(gulp.dest('.')) 29 | } 30 | 31 | return rebundle(); 32 | }); 33 | 34 | gulp.task('.peg', function () { 35 | return gulp.src("eisen-script.peg") 36 | .pipe(peg().on("error", gutil.log)) 37 | .pipe(gulp.dest('.')) 38 | }); 39 | 40 | gulp.task('.examples', function (cb) { 41 | var exec = require('child_process').exec; 42 | exec('bash ./pack-examples.sh > examples-generated.ts', function (err, stdout, stderr) { 43 | console.log(stdout); 44 | console.log(stderr); 45 | cb(err); 46 | }); 47 | }); 48 | 49 | gulp.task('.ui', function () { 50 | var bundler = browserify({ debug: true }) 51 | .add('./mega-structure.ts') 52 | .plugin(tsify, { target: 'es5' }) 53 | .transform('brfs') 54 | 55 | return bundler.bundle() 56 | .pipe(source('bundle.js')) 57 | .pipe(gulp.dest('.')); 58 | }); 59 | 60 | gulp.task('.synth', function () { 61 | var bundler = browserify({ debug: true }) 62 | .add('./synthesizer-webworker.ts') 63 | .plugin(tsify, { target: 'es5' }) 64 | return bundler.bundle() 65 | .pipe(source('synthesizer-webworker.js')) 66 | .pipe(gulp.dest('.')); 67 | }); 68 | 69 | gulp.task('.ui.release', function () { 70 | var bundler = browserify() 71 | .add('./mega-structure.ts') 72 | .plugin(tsify, { target: 'es5' }) 73 | .transform('brfs') 74 | .transform(uglify); 75 | 76 | return bundler.bundle() 77 | .pipe(source('bundle.js')) 78 | .pipe(gulp.dest('.')); 79 | }); 80 | 81 | gulp.task('.synth.release', function () { 82 | var bundler = browserify() 83 | .add('./synthesizer-webworker.ts') 84 | .plugin(tsify, { target: 'es5' }) 85 | .transform(uglify); 86 | 87 | return bundler.bundle() 88 | .pipe(source('synthesizer-webworker.js')) 89 | .pipe(gulp.dest('.')); 90 | }); 91 | 92 | gulp.task('install', function () { 93 | gulp.src([ 94 | "mega-structure.html", 95 | "bundle.js", 96 | "synthesizer-webworker.js", 97 | "codemirror-eisen-script-mode.js", 98 | "node_modules/jquery/dist/**", 99 | "node_modules/bootstrap/dist/**", 100 | "node_modules/codemirror/**" 101 | ], {base: "."}).pipe(gulp.dest('_site')); 102 | 103 | newfile('.nojekyll', '').pipe(gulp.dest('_site')); 104 | }); 105 | 106 | gulp.task('default', function (callback) { 107 | runSequence('.peg', 108 | '.examples', 109 | '.ui.release', 110 | '.synth.release', 111 | callback); 112 | }); 113 | -------------------------------------------------------------------------------- /examples/Noct.es: -------------------------------------------------------------------------------- 1 | set background #222 2 | 3 | set raytracer::light [0,5000,5000] 4 | set raytracer::samples 8 5 | set raytracer::ambient-occlusion-samples 2 6 | set raytracer::phong [0.5,0.5,0.125] 7 | 8 | { color white } Core 9 | 10 | rule Core 11 | { 12 | { y -1.5 } OuterRingStruct 13 | //{ s 1.1 0.5 1.1 x -12 y -1.25 } Stair 14 | } 15 | 16 | // Stairs 17 | 18 | rule Stair 19 | { 20 | 700 * { ry 0.95 y 0.1 z 0.2 } Step 21 | } 22 | 23 | rule Step 24 | { 25 | { s 1 0.025 0.1 } box 26 | { b 0.25 s 1.5 0.01 0.01 } box 27 | { b 0.25 ry 0.95 rx -29.4 x 0.75 s 0.1 0.025 0.25 } box 28 | { b 0.25 ry 0.95 rx -23.4 x -0.75 s 0.1 0.025 0.25 } box 29 | } 30 | 31 | // OuterRing 32 | 33 | rule OuterRingStruct md 4 34 | { 35 | { x 1 z 10 } OuterRingSup1 36 | { ry 11.25 y 2 } OuterRingStruct 37 | } 38 | 39 | rule OuterRingSup1 md 10 > OuterRingSup2 40 | { 41 | { ry 5.625 }OuterRingPart 42 | { ry 11.25 x 2 } OuterRingSup1 43 | } 44 | 45 | rule OuterRingSup2 w 15 md 22 46 | { 47 | { ry 5.625 }OuterRingPart 48 | { ry 11.25 x 2 } OuterRingSup2 49 | } 50 | 51 | rule OuterRingSup2 w 5 52 | { 53 | } 54 | 55 | rule OuterRingPart 56 | { 57 | { b 0.25 y -1.5 rx -90 } Panel 58 | { b 0.4 a 0.5 y 1.0 s 2.0 1 0.1 } box 59 | { b 0.4 a 0.5 z 2 y 1.0 s 2.4 1 0.1 } box 60 | { } Ringsub 61 | } 62 | 63 | // Ring 64 | 65 | rule Ringsub w 1 66 | { 67 | //{ ry 90 x -0.25 s 2 1.25 2.5 } thinPanelSquare1 68 | //{ ry 90 x 0.05 s 2 1.25 2.5 } thinPanelSquare1 69 | //{ b 0.25 ry 180 rz 90 s 0.5 0.5 0.4 } beamAssembly 70 | // { b 0.25 rz 90 s 1 2 0.75 z 0.5 y -0.5 }RodBox 71 | { s 1.25 } rodrow 72 | } 73 | 74 | rule Ringsub w 300 md 5 75 | { 76 | //{ ry 90 x -0.25 s 2 1.25 2.5 } thinPanelSquare1 77 | //{ ry 90 x 0.05 s 2 1.25 2.5 } thinPanelSquare1 78 | { s 1.25 } rodrow 79 | { z 0.5 } Ringsub 80 | } 81 | 82 | rule Ringsub w 10 83 | { 84 | { s 1.25 } rodrow 85 | } 86 | 87 | rule Panel 88 | { 89 | 2 * { z 2 } PanelSub 90 | //{ b 0.5 a 0.5 z 3 s 0.5 0.1 1.75 } box 91 | { y -1.9 z 3 s 0.05 0.05 1.925 } box 92 | } 93 | 94 | rule PanelSub 95 | { 96 | { s 2.2 0.25 0.125 } box 97 | { y -1 s 0.05 1.75 0.05 } box 98 | { y -1.925 s 2.4 0.1 0.05 } box 99 | 100 | //{ a 0.5 b 0.25 y 1.75 s 0.75 3 0.05 } box 101 | } 102 | 103 | // Boxform 104 | 105 | rule boxForm1 w 15 106 | { 107 | boxForm2 108 | } 109 | 110 | rule boxForm1 w 1 111 | { 112 | } 113 | 114 | rule boxForm2 115 | { 116 | {} RodBox 117 | { x 0.5521 z -0.5 s 1.2 }thinPanelSquare1 118 | { x -0.5524 z -0.5 s 1.2 }thinPanelSquare1 119 | { ry 90 s 1.2 }thinPanelSquare1 120 | { ry 90 x 1 s 1.2 }thinPanelSquare1 121 | { rz 90 z -0.5 s 1.2 }thinPanelSquare1 122 | { rz 90 z -0.5 x 1 s 1.2 }thinPanelSquare1 123 | } 124 | 125 | rule thinPanelSquare1 w 6 126 | { 127 | thinPanelSquare2 128 | } 129 | 130 | rule thinPanelSquare1 w 4 131 | { 132 | } 133 | 134 | rule thinPanelSquare2 135 | { 136 | //{ ry 90 rz 90 y -0.75 x 0.5 } rodrow 137 | { y 0.5 s 0.075 5 0.75 } box 138 | } 139 | 140 | rule thinPanelSquare2 w 75 141 | { 142 | } 143 | 144 | rule rodrow 145 | { 146 | { s 2.1 0.25 0.25 y 2 } box 147 | } 148 | 149 | rule RodBox 150 | { 151 | RodBoxP1 152 | { z -1 rx 90 } RodBoxP1 153 | } 154 | 155 | rule RodBoxP1 156 | { 157 | RodBoxSide 158 | { x 0.5 z -0.5 ry 90 } RodBoxSide 159 | { x -0.5 z -0.5 ry -90 } RodBoxSide 160 | { z -1 ry 180 }RodBoxSide 161 | } 162 | 163 | rule RodBoxSide 164 | { 165 | {} Rod 166 | { y 1 } Rod 167 | } 168 | 169 | rule Rod w 4 170 | { 171 | { s 0.965 0.2054 0.116 } box 172 | Rod2 173 | } 174 | 175 | rule Rod w 1 176 | { 177 | } 178 | 179 | rule Rod2 w 1 180 | { 181 | { s 1.435 0.02 0.02 } box 182 | } 183 | 184 | rule Rod2 w 1 185 | { 186 | { s 1.763 0.02 0.02 } box 187 | } 188 | 189 | // Beam 190 | 191 | rule beamAssembly 192 | { 193 | { z -5 } beam 194 | { z 5 } beam 195 | 196 | { s 1 0.2 11 y 12 } box 197 | { s 1 0.2 11 y -12 } box 198 | 199 | { z 5 } vertPanel 200 | { z -5 } vertPanel 201 | } 202 | 203 | rule vertPanel 204 | { 205 | { s 1 4 0.2 } box 206 | { s 1 1 0.5 } box 207 | } 208 | 209 | rule vertPanel md 1 w 7 210 | { 211 | widePane 212 | { y 1 } vertPanel 213 | } 214 | 215 | rule vertPanel w 1 216 | { 217 | {s 1 1 0.5 } box 218 | {s 0.2 4 0.2 x 2 } box 219 | {s 0.2 4 0.2 x -2 } box 220 | {s 1 0.2 0.2 y 10 } box 221 | {s 1 0.2 0.2 y -10 } box 222 | } 223 | 224 | rule beam 225 | { 226 | { s 0.2 5 0.2 } box 227 | } 228 | 229 | rule widePane 230 | { 231 | thinBeam1 232 | { y 5 } thinBeam1 233 | { x 4.9 y 2.5 } thinBeamVert 234 | { x -4.9 y 2.5 } thinBeamVert 235 | pane 236 | } 237 | 238 | rule pane 239 | { 240 | {s 10 5 0.05 y 0.5} box 241 | } 242 | 243 | rule pane 244 | { 245 | { s 10 2.5 0.05 y 0.5 } box 246 | } 247 | 248 | rule thinBeam1 249 | { 250 | { s 10 0.2 0.2 } box 251 | } 252 | 253 | rule thinBeamVert 254 | { 255 | { s 0.2 5 0.2 } box 256 | } -------------------------------------------------------------------------------- /progress.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Sebastien Sydney Robert Bigot 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | // The views and conclusions contained in the software and documentation are those 25 | // of the authors and should not be interpreted as representing official policies, 26 | // either expressed or implied, of the FreeBSD Project. 27 | 28 | import * as THREE from 'three'; 29 | 30 | declare function postMessage(message: any) 31 | 32 | export class Progress extends THREE.Mesh { 33 | 34 | public constructor() { 35 | 36 | var canvas = document.createElement('canvas'); 37 | canvas.width = canvas.height = 1024; 38 | var texture = new THREE.Texture(canvas); 39 | 40 | super(new THREE.PlaneBufferGeometry(1, 1), new THREE.MeshBasicMaterial({ 41 | map: texture, 42 | depthWrite: false, 43 | depthTest: false, 44 | transparent: true 45 | })); 46 | 47 | this.texture = texture; 48 | this.texture.needsUpdate = true; 49 | 50 | this.canvas = canvas; 51 | 52 | this.rotateX(Math.PI); 53 | this.visible = false; 54 | 55 | this.nshapes = 0; 56 | this.nprocessed = 0; 57 | 58 | this.completeTick = -1; 59 | } 60 | 61 | public init() { 62 | this.visible = true; 63 | this.nshapes = 0; 64 | this.nprocessed = 0; 65 | this.completeTick = -1; 66 | } 67 | 68 | public update(msg: any) { 69 | this.nshapes = msg.nshapes; 70 | this.nprocessed = msg.nprocessed; 71 | } 72 | 73 | public setPixelSize(size: number) { 74 | this.canvas.width = this.canvas.height = Math.pow(2, Math.ceil(Math.log(size) / Math.log(2))); 75 | } 76 | 77 | public animate(tick: number) { 78 | 79 | var context = this.canvas.getContext('2d'); 80 | context.globalAlpha = 1; 81 | 82 | var fadeoutStart = 500; 83 | var fadeoutDuration = 1000; 84 | 85 | if (this.nshapes > 0 && this.nprocessed == this.nshapes) { 86 | if (this.completeTick == -1) { 87 | this.completeTick = tick; 88 | } else if (tick - this.completeTick < fadeoutStart) { 89 | } else if (tick - this.completeTick < fadeoutStart + fadeoutDuration) { 90 | context.globalAlpha = 1 - (tick - this.completeTick - fadeoutStart) / fadeoutDuration; 91 | } else { 92 | this.visible = false; 93 | } 94 | } 95 | 96 | context.clearRect(0, 0, this.canvas.width, this.canvas.height); 97 | 98 | var progressX = this.canvas.width / 2; 99 | var progressY = this.canvas.height / 2; 100 | var halfProgressWidth = this.canvas.width / 8; 101 | var halfProgressHeight = halfProgressWidth / 10; 102 | 103 | var textX = progressX; 104 | var textY = progressY - 3 * halfProgressHeight; 105 | var fontSize = 4 * halfProgressHeight + "px serif"; 106 | 107 | context.font = fontSize; 108 | context.textAlign = "center"; 109 | context.fillStyle = 'white'; 110 | context.strokeStyle = 'black'; 111 | context.fillText(this.nshapes + " shapes", textX, textY); 112 | context.strokeText(this.nshapes + " shapes", textX, textY); 113 | 114 | context.fillStyle = 'white'; 115 | context.strokeStyle = 'white'; 116 | 117 | context.beginPath(); 118 | context.moveTo(progressX - halfProgressWidth, progressY - halfProgressHeight); 119 | context.lineTo(progressX - halfProgressWidth, progressY + halfProgressHeight); 120 | context.lineTo(progressX + halfProgressWidth, progressY + halfProgressHeight); 121 | context.lineTo(progressX + halfProgressWidth, progressY - halfProgressHeight); 122 | context.lineTo(progressX - halfProgressWidth, progressY - halfProgressHeight); 123 | context.closePath(); 124 | 125 | context.stroke(); 126 | 127 | var proccessedRatio = this.nprocessed / this.nshapes; 128 | 129 | context.beginPath(); 130 | context.moveTo(progressX - halfProgressWidth, progressY - halfProgressHeight); 131 | context.lineTo(progressX - halfProgressWidth, progressY + halfProgressHeight); 132 | context.lineTo(progressX - halfProgressWidth * (1 - 2 * proccessedRatio), progressY + halfProgressHeight); 133 | context.lineTo(progressX - halfProgressWidth * (1 - 2 * proccessedRatio), progressY - halfProgressHeight); 134 | context.lineTo(progressX - halfProgressWidth, progressY - halfProgressHeight); 135 | context.closePath(); 136 | 137 | context.fill(); 138 | 139 | this.texture.needsUpdate = true; 140 | } 141 | 142 | canvas: HTMLCanvasElement; 143 | texture: THREE.Texture; 144 | nshapes: number; 145 | nprocessed: number; 146 | completeTick: number; 147 | }; 148 | -------------------------------------------------------------------------------- /synthesizer-webworker.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Sebastien Sydney Robert Bigot 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | // The views and conclusions contained in the software and documentation are those 25 | // of the authors and should not be interpreted as representing official policies, 26 | // either expressed or implied, of the FreeBSD Project. 27 | 28 | var glmat = require('gl-matrix'); 29 | import * as tinycolor from 'tinycolor2'; 30 | import * as collections from 'typescript-collections'; 31 | import { Synthesizer } from './synthesizer'; 32 | import { ShapeInstance } from './structure'; 33 | 34 | // A cube 35 | var cubetriangles = [0, 1, 2, 1, 2, 3, 36 | 4, 5, 6, 5, 6, 7, 37 | 0, 1, 4, 1, 4, 5, 38 | 2, 3, 6, 3, 6, 7, 39 | 0, 2, 4, 2, 4, 6, 40 | 1, 3, 5, 3, 5, 7]; 41 | 42 | var cubevertices = [[0, 0, 0, 1], 43 | [0, 0, 1, 1], 44 | [0, 1, 0, 1], 45 | [0, 1, 1, 1], 46 | [1, 0, 0, 1], 47 | [1, 0, 1, 1], 48 | [1, 1, 0, 1], 49 | [1, 1, 1, 1]]; 50 | 51 | // An ico-sphere 52 | var spheretriangles = [0, 4, 2, 2, 4, 1, 53 | 1, 4, 3, 3, 4, 0, 54 | 0, 2, 5, 2, 1, 5, 55 | 1, 3, 5, 3, 0, 5]; 56 | 57 | var spherevertices = [[1, 0, 0, 1], 58 | [-1, 0, 0, 1], 59 | [0, 1, 0, 1], 60 | [0, -1, 0, 1], 61 | [0, 0, 1, 1], 62 | [0, 0, -1, 1]]; 63 | 64 | for (var vi = 0; vi < cubevertices.length; ++vi) { 65 | cubevertices[vi][0] -= 0.5; 66 | cubevertices[vi][1] -= 0.5; 67 | cubevertices[vi][2] -= 0.5; 68 | } 69 | 70 | var cubetransformed = new Array(cubevertices.length); 71 | for (var i = 0; i < cubetransformed.length; ++i) { 72 | cubetransformed[i] = [0, 0, 0, 1]; 73 | } 74 | 75 | for (var i = 0; i < 3; ++i) { 76 | spheretriangles = Subdivide(spheretriangles, spherevertices); 77 | } 78 | 79 | for (var vi = 0; vi < spherevertices.length; ++vi) { 80 | spherevertices[vi][0] /= 2; 81 | spherevertices[vi][1] /= 2; 82 | spherevertices[vi][2] /= 2; 83 | } 84 | 85 | var sphereretransformed = new Array(spherevertices.length); 86 | for (var i = 0; i < sphereretransformed.length; ++i) { 87 | sphereretransformed[i] = [0, 0, 0, 1]; 88 | } 89 | 90 | 91 | interface GeoGenProgressFunc { 92 | (ngenerated: number, done: boolean): void; 93 | } 94 | 95 | function AllocBuffers(structure: ShapeInstance[], progress: GeoGenProgressFunc): [Float32Array, Float32Array] { 96 | var nverts = 0; 97 | for (var si = 0; si < structure.length; ++si) { 98 | switch (structure[si].shape) { 99 | case "box": 100 | nverts += 36; 101 | break; 102 | case "sphere": 103 | nverts += 4 * 4 * 4 * 24; 104 | break; 105 | }; 106 | } 107 | return [new Float32Array(3 * nverts), new Float32Array(3 * nverts)]; 108 | } 109 | 110 | function GenerateShapeGeometry( 111 | triangles: number[], 112 | reference: number[][], 113 | transformed: number[][], 114 | transform: Float32Array, 115 | rgb: any, 116 | position: Float32Array, 117 | color: Float32Array, 118 | pi: number, 119 | ci: number 120 | ): [number, number] { 121 | for (var vi = 0; vi < reference.length; ++vi) { 122 | glmat.vec4.transformMat4(transformed[vi], reference[vi], transform); 123 | } 124 | for (var ti = 0; ti < triangles.length; ++ti) { 125 | var vert = transformed[triangles[ti]]; 126 | position[pi++] = vert[0]; 127 | position[pi++] = vert[1]; 128 | position[pi++] = vert[2]; 129 | color[ci++] = rgb.r / 255; 130 | color[ci++] = rgb.g / 255; 131 | color[ci++] = rgb.b / 255; 132 | // Alpha not supported yet 133 | } 134 | return [pi, ci]; 135 | } 136 | 137 | function VertexForEdge( 138 | edges: collections.Dictionary<[number, number], number>, 139 | vertices: number[][], 140 | v0: number, 141 | v1: number 142 | ) { 143 | if (v0 > v1) { 144 | var tmp = v1; 145 | v1 = v0; 146 | v0 = tmp; 147 | } 148 | 149 | var vmid = edges.getValue([v0, v1]); 150 | 151 | if (undefined == vmid) { 152 | var vmid = vertices.length; 153 | edges.setValue([v0, v1], vmid); 154 | 155 | var v = [0, 0, 0, 1]; 156 | glmat.vec3.add(v, vertices[v0], vertices[v1]) 157 | glmat.vec3.normalize(v, v); 158 | vertices.push(v); 159 | } 160 | 161 | return vmid; 162 | } 163 | 164 | function Subdivide( 165 | triangles: number[], 166 | vertices: number[][] 167 | ): number[] { 168 | var edges = new collections.Dictionary<[number, number], number>(); 169 | var result = []; 170 | 171 | for (var ti = 0; ti < triangles.length; ti += 3) { 172 | var vmid = [0, 0, 0]; 173 | for (var edgei = 0; edgei < 3; ++edgei) { 174 | vmid[edgei] = VertexForEdge(edges, vertices, triangles[ti + edgei], triangles[ti + (edgei + 1) % 3]); 175 | } 176 | 177 | result.push(triangles[ti]); 178 | result.push(vmid[0]); 179 | result.push(vmid[2]); 180 | 181 | result.push(triangles[ti + 1]); 182 | result.push(vmid[1]); 183 | result.push(vmid[0]); 184 | 185 | result.push(triangles[ti + 2]); 186 | result.push(vmid[2]); 187 | result.push(vmid[1]); 188 | 189 | result.push(vmid[0]); 190 | result.push(vmid[1]); 191 | result.push(vmid[2]); 192 | } 193 | 194 | return result; 195 | } 196 | 197 | function GenerateGeometry(structure: ShapeInstance[], progress: GeoGenProgressFunc): [Float32Array, Float32Array] { 198 | 199 | var [position, color] = AllocBuffers(structure, progress); 200 | 201 | for (var si = 0, pi = 0, ci = 0; si < structure.length; ++si) { 202 | 203 | progress(si, false); 204 | 205 | var rgb = tinycolor(structure[si].colorspace).toRgb(); 206 | switch (structure[si].shape) { 207 | case "box": 208 | [pi, ci] = GenerateShapeGeometry(cubetriangles, cubevertices, cubetransformed, structure[si].geospace, rgb, position, color, pi, ci); 209 | break; 210 | case "sphere": 211 | [pi, ci] = GenerateShapeGeometry(spheretriangles, spherevertices, sphereretransformed, structure[si].geospace, rgb, position, color, pi, ci); 212 | break 213 | }; 214 | } 215 | 216 | progress(structure.length, true); 217 | 218 | return [position, color]; 219 | } 220 | 221 | onmessage = function (e) { 222 | 223 | var worker = this; 224 | 225 | var synth = new Synthesizer(e.data, (() => { 226 | var nshapesLast = 0; 227 | return (shapes: ShapeInstance[], done: boolean) => { 228 | if (shapes.length - nshapesLast >= 100 || done) { 229 | (this).postMessage({ type: 'progress', nshapes: shapes.length, nprocessed: 0 }); 230 | nshapesLast = shapes.length; 231 | } 232 | }; 233 | })() 234 | ); 235 | 236 | console.log('Synthesizing !'); 237 | var tstamp = new Date().getTime(); 238 | var structure = synth.synthesize(); 239 | console.log('Synthesized in ' + (new Date().getTime() - tstamp) + 'ms'); 240 | 241 | console.log('Detailing geometry !'); 242 | tstamp = new Date().getTime(); 243 | var [position, color] = GenerateGeometry(structure, (() => { 244 | var nshapesLast = 0; 245 | return (nshapes: number, done: boolean) => { 246 | if (nshapes - nshapesLast >= 100 || done) { 247 | (this).postMessage({ type: 'progress', nshapes: structure.length, nprocessed: nshapes }); 248 | nshapesLast = nshapes; 249 | } 250 | }; 251 | })() 252 | ); 253 | 254 | console.log('Detailed in ' + (new Date().getTime() - tstamp) + 'ms'); 255 | 256 | console.log('Posting structure !'); 257 | tstamp = new Date().getTime(); 258 | (worker).postMessage({ 259 | type: 'done', 260 | background: synth.background, 261 | position: position.buffer, 262 | color: color.buffer 263 | }, [position.buffer, color.buffer]); 264 | console.log('Posted in ' + (new Date().getTime() - tstamp) + 'ms'); 265 | } 266 | 267 | -------------------------------------------------------------------------------- /mega-structure.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Sebastien Sydney Robert Bigot 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | // The views and conclusions contained in the software and documentation are those 25 | // of the authors and should not be interpreted as representing official policies, 26 | // either expressed or implied, of the FreeBSD Project. 27 | import * as THREE from 'three'; 28 | import * as CodeMirror from 'codemirror'; 29 | import * as EisenScripts from './examples-generated'; 30 | import { ShapeInstance } from './structure'; 31 | import { Progress } from './progress'; 32 | 33 | var renderer: THREE.WebGLRenderer; 34 | 35 | function toggleFullScreen() { 36 | var doc = document; 37 | if (!doc.mozFullScreenElement && !doc.webkitFullscreenElement) { 38 | var canvas = renderer.domElement; 39 | if (canvas.mozRequestFullScreen) { 40 | canvas.mozRequestFullScreen(); 41 | } else { 42 | canvas.webkitRequestFullscreen((Element).ALLOW_KEYBOARD_INPUT); 43 | } 44 | } else { 45 | if (doc.mozCancelFullScreen) { 46 | doc.mozCancelFullScreen(); 47 | } else { 48 | doc.webkitExitFullscreen(); 49 | } 50 | } 51 | } 52 | 53 | window.addEventListener('load', () => { 54 | 55 | document.addEventListener("keydown", e => { 56 | if (e.keyCode == 122) { 57 | e.preventDefault(); 58 | toggleFullScreen(); 59 | } 60 | }, false); 61 | 62 | renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); 63 | renderer.autoClear = false; 64 | renderer.setPixelRatio(window.devicePixelRatio); 65 | renderer.domElement.addEventListener("dblclick", e => { 66 | toggleFullScreen(); 67 | }, false); 68 | 69 | var camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); 70 | camera.position.z = 1.5; 71 | 72 | var view = document.getElementById("structure-view"); 73 | view.appendChild(renderer.domElement); 74 | 75 | var scene = new THREE.Scene(); 76 | scene.add(camera); 77 | 78 | var progress = new Progress(); 79 | var olaycam = new THREE.OrthographicCamera(-0.5 * camera.aspect, 0.5 * camera.aspect, -0.5, 0.5, 0.1, 10000); 80 | olaycam.position.z = 100; 81 | var olayscene = new THREE.Scene(); 82 | olayscene.add(olaycam); 83 | olayscene.add(progress); 84 | 85 | var material = 86 | new THREE.MeshPhongMaterial({ 87 | vertexColors: THREE.FaceColors, 88 | side: THREE.DoubleSide, 89 | flatShading: true 90 | }); 91 | 92 | var mesh = new THREE.Mesh( 93 | new THREE.BoxGeometry(0, 0, 0), 94 | material 95 | ); 96 | 97 | scene.add(mesh); 98 | 99 | var ambientLight = new THREE.AmbientLight(0x000000); 100 | scene.add(ambientLight); 101 | 102 | var lights = []; 103 | lights[0] = new THREE.PointLight(0xffffff, 1, 0); 104 | lights[1] = new THREE.PointLight(0xffffff, 1, 0); 105 | lights[2] = new THREE.PointLight(0xffffff, 1, 0); 106 | 107 | lights[0].position.set(0, 200, 0); 108 | lights[1].position.set(100, 200, 100); 109 | lights[2].position.set(-100, -200, -100); 110 | 111 | scene.add(lights[0]); 112 | scene.add(lights[1]); 113 | scene.add(lights[2]); 114 | 115 | var OrbitControls = require('three-orbit-controls')(THREE) 116 | var controls = new OrbitControls(camera, renderer.domElement); 117 | controls.enableKeys = false; 118 | controls.target.set(0, 0, 0); 119 | 120 | var turntable = true; 121 | 122 | var lastTime = new Date().getTime(); 123 | function animate() { 124 | 125 | var timeNow = new Date().getTime(); 126 | 127 | if (turntable) { 128 | var dt = (timeNow - lastTime) / (60 * 1000); 129 | var dtheta = 2 * Math.PI * 1 * dt 130 | mesh.rotation.x += dtheta; 131 | mesh.rotation.y += dtheta; 132 | } 133 | 134 | renderer.clear(true, true, true); 135 | renderer.render(scene, camera); 136 | 137 | if (progress.visible) { 138 | var s = renderer.getSize(); 139 | progress.animate(timeNow); 140 | renderer.clear(false, true, true); 141 | renderer.render(olayscene, olaycam); 142 | } 143 | 144 | requestAnimationFrame(animate); 145 | 146 | lastTime = timeNow; 147 | } 148 | 149 | function doResize() { 150 | var doc = document; 151 | if (!doc.mozFullScreenElement && !doc.webkitFullscreenElement) { 152 | var w = view.offsetWidth, h = window.innerHeight - document.getElementById("header").offsetHeight; 153 | w *= 0.95; 154 | h *= 0.95; 155 | renderer.setSize(w, h); 156 | $(".CodeMirror").height(h + "px"); 157 | } else { 158 | renderer.setSize(window.innerWidth, window.innerHeight); 159 | } 160 | 161 | var s = renderer.getSize(); 162 | camera.aspect = s.width / s.height; 163 | camera.updateProjectionMatrix(); 164 | 165 | olaycam.left = -0.5 * camera.aspect; 166 | olaycam.right = 0.5 * camera.aspect; 167 | olaycam.updateProjectionMatrix(); 168 | 169 | progress.setPixelSize(s.height); 170 | }; 171 | window.addEventListener('resize', doResize); 172 | 173 | require("./codemirror-eisen-script-mode.js"); 174 | require('./node_modules/codemirror/addon/edit/matchbrackets.js'); 175 | 176 | var opts = { 177 | lineNumbers: true, 178 | matchBrackets: true, 179 | mode: 'eisen-script', 180 | theme: 'twilight' 181 | }; 182 | var codeMirror = CodeMirror(document.getElementById("structure-code"), opts); 183 | 184 | var tstamp = 0; 185 | 186 | function synthetize() { 187 | console.log('Synth request !'); 188 | progress.init(); 189 | tstamp = new Date().getTime(); 190 | myWorker.postMessage(codeMirror.getValue()); 191 | } 192 | 193 | function resetViewport() { 194 | var bbox = mesh.geometry.boundingBox; 195 | var diag = new THREE.Vector3(); 196 | diag.subVectors(bbox.max, bbox.min); 197 | 198 | camera.position.x = 0; 199 | camera.position.y = 0; 200 | camera.position.z = Math.max(diag.x, diag.y) / Math.tan(0.5 * camera.fov * Math.PI / 180); 201 | 202 | camera.rotation.x = 0; 203 | camera.rotation.y = 0; 204 | camera.rotation.z = 0; 205 | 206 | controls.target.set(0, 0, 0); 207 | controls.update(); 208 | 209 | mesh.rotation.x = 0; 210 | mesh.rotation.y = 0; 211 | } 212 | 213 | function toggleTurntable() { 214 | turntable = !turntable; 215 | } 216 | 217 | var exampleSelector = document.getElementById("examples"); 218 | 219 | Object.keys(EisenScripts).forEach(e => { 220 | var opt = document.createElement('option'); 221 | opt.value = opt.innerHTML = e; 222 | exampleSelector.appendChild(opt); 223 | }); 224 | 225 | function exampleChanged() { 226 | codeMirror.setValue(EisenScripts[exampleSelector.value]); 227 | } 228 | 229 | exampleSelector.onchange = exampleChanged; 230 | 231 | document.getElementById("synthBtn").onclick = synthetize; 232 | document.getElementById("resetViewportBtn").onclick = resetViewport; 233 | document.getElementById("toggleTurntableBtn").onclick = toggleTurntable; 234 | 235 | var myWorker = new Worker("synthesizer-webworker.js"); 236 | myWorker.onmessage = function (e) { 237 | var msg = e.data; 238 | switch (msg.type) { 239 | case 'progress': 240 | progress.update(msg); 241 | break; 242 | case 'done': 243 | renderer.setClearColor(new THREE.Color(msg.background)); 244 | var geometry = new THREE.BufferGeometry(); 245 | geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(msg.position), 3)); 246 | geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(msg.color), 3)); 247 | geometry.center(); 248 | mesh.geometry = geometry; 249 | resetViewport(); 250 | console.log('Synth request processed in ' + (new Date().getTime() - tstamp) + 'ms'); 251 | break; 252 | } 253 | } 254 | 255 | exampleSelector.value = 'frameinframe'; 256 | exampleChanged(); 257 | synthetize(); 258 | requestAnimationFrame(animate); 259 | doResize(); 260 | }); 261 | -------------------------------------------------------------------------------- /codemirror-eisen-script-mode.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Sebastien Sydney Robert Bigot 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | // The views and conclusions contained in the software and documentation are those 25 | // of the authors and should not be interpreted as representing official policies, 26 | // either expressed or implied, of the FreeBSD Project. 27 | 28 | (function (mod) { 29 | if (typeof exports == "object" && typeof module == "object") // CommonJS 30 | mod(require("./node_modules/codemirror/lib/codemirror")); 31 | else if (typeof define == "function" && define.amd) // AMD 32 | define(["./node_modules/codemirror/lib/codemirror"], mod); 33 | else // Plain browser env 34 | mod(CodeMirror); 35 | })(function (CodeMirror) { 36 | "use strict"; 37 | 38 | CodeMirror.defineMode("eisen-script", function (config, parserConfig) { 39 | var indentUnit = config.indentUnit, 40 | statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, 41 | dontAlignCalls = parserConfig.dontAlignCalls, 42 | 43 | indentStatements = parserConfig.indentStatements !== false, 44 | 45 | defKeywords = words("rule"), 46 | 47 | keywords = words("rule set md maxdepth w weight maxobjects minsize maxsize seed background h hue sat b brightness a alpha color blend initial x y z rx ry rz s fx fy fz m raytracer"), 48 | 49 | builtin = words("box grid sphere line point triangle mesh cylinder tube white black red green blue grey"), 50 | 51 | isPunctuationChar = /[\[\]{}\(\),;\:\.]/, 52 | 53 | numberStart = /[\d\.]/, 54 | number = /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i, 55 | 56 | colorStart = /[\#]/, 57 | color = /^#[\da-f]{3}(?:[\da-f]{3})?/i, 58 | 59 | isOperatorChar = /[+\-/*]/, 60 | 61 | namespaceSeparator = "::"; 62 | 63 | var curPunc, isDefKeyword; 64 | 65 | function Context(indented, column, type, align, prev) { 66 | this.indented = indented; 67 | this.column = column; 68 | this.type = type; 69 | this.align = align; 70 | this.prev = prev; 71 | } 72 | 73 | 74 | function isStatement(type) { 75 | return type == "statement"; 76 | } 77 | 78 | function pushContext(state, col, type) { 79 | var indent = state.indented; 80 | if (state.context && isStatement(state.context.type) && !isStatement(type)) 81 | indent = state.context.indented; 82 | return state.context = new Context(indent, col, type, null, state.context); 83 | } 84 | 85 | function popContext(state) { 86 | var t = state.context.type; 87 | if (t == ")" || t == "]" || t == "}") 88 | state.indented = state.context.indented; 89 | return state.context = state.context.prev; 90 | } 91 | 92 | function typeBefore(stream, state) { 93 | if (state.prevToken == "variable" || state.prevToken == "variable-3") return true; 94 | if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, stream.start))) return true; 95 | } 96 | 97 | function isTopScope(context) { 98 | for (; ;) { 99 | if (!context || context.type == "top") return true; 100 | if (context.type == "}") return false; 101 | context = context.prev; 102 | } 103 | } 104 | 105 | 106 | 107 | function words(str) { 108 | var obj = {}, words = str.split(" "); 109 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 110 | return obj; 111 | } 112 | 113 | function contains(words, word) { 114 | if (typeof words === "function") { 115 | return words(word); 116 | } else { 117 | return words.propertyIsEnumerable(word); 118 | } 119 | } 120 | 121 | function tokenComment(stream, state) { 122 | var maybeEnd = false, ch; 123 | while (ch = stream.next()) { 124 | if (ch == "/" && maybeEnd) { 125 | state.tokenize = null; 126 | break; 127 | } 128 | maybeEnd = (ch == "*"); 129 | } 130 | return "comment"; 131 | } 132 | 133 | function tokenBase(stream, state) { 134 | var ch = stream.next(); 135 | 136 | if (isPunctuationChar.test(ch)) { 137 | curPunc = ch; 138 | return null; 139 | } 140 | 141 | if (colorStart.test(ch)) { 142 | stream.backUp(1) 143 | if (stream.match("#define")) { 144 | isDefKeyword = true; 145 | return "keyword"; 146 | } 147 | if (stream.match(color)) return "number" 148 | stream.next() 149 | } 150 | 151 | if (numberStart.test(ch)) { 152 | stream.backUp(1) 153 | if (stream.match(number)) return "number" 154 | stream.next() 155 | } 156 | if (ch == "/") { 157 | if (stream.eat("*")) { 158 | state.tokenize = tokenComment; 159 | return tokenComment(stream, state); 160 | } 161 | if (stream.eat("/")) { 162 | stream.skipToEnd(); 163 | return "comment"; 164 | } 165 | } 166 | 167 | if (isOperatorChar.test(ch)) { 168 | stream.eatWhile(isOperatorChar); 169 | return "operator"; 170 | } 171 | 172 | stream.eatWhile(/[\w\$_\xa1-\uffff]/); 173 | // set raytracer:: 174 | //if (namespaceSeparator) while (stream.match(namespaceSeparator)) 175 | // stream.eatWhile(/[\w\$_\xa1-\uffff]/); 176 | 177 | var cur = stream.current(); 178 | 179 | if (contains(keywords, cur)) { 180 | if (contains(defKeywords, cur)) isDefKeyword = true; 181 | return "keyword"; 182 | } 183 | 184 | if (contains(builtin, cur)) { 185 | return "builtin"; 186 | } 187 | 188 | return "variable"; 189 | } 190 | 191 | 192 | 193 | return { 194 | startState: function (basecolumn) { 195 | return { 196 | tokenize: null, 197 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), 198 | indented: 0, 199 | startOfLine: true, 200 | prevToken: null 201 | }; 202 | }, 203 | 204 | token: function (stream, state) { 205 | var ctx = state.context; 206 | if (stream.sol()) { 207 | if (ctx.align == null) ctx.align = false; 208 | state.indented = stream.indentation(); 209 | state.startOfLine = true; 210 | } 211 | if (stream.eatSpace()) return null; 212 | curPunc = isDefKeyword = null; 213 | var style = (state.tokenize || tokenBase)(stream, state); 214 | if (style == "comment" || style == "meta") return style; 215 | if (ctx.align == null) ctx.align = true; 216 | 217 | if (curPunc == "{") pushContext(state, stream.column(), "}"); 218 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 219 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 220 | else if (curPunc == "}") { 221 | while (isStatement(ctx.type)) ctx = popContext(state); 222 | if (ctx.type == "}") ctx = popContext(state); 223 | while (isStatement(ctx.type)) ctx = popContext(state); 224 | } 225 | else if (curPunc == ctx.type) popContext(state); 226 | else if (indentStatements && 227 | (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") || 228 | (isStatement(ctx.type) && curPunc == "newstatement"))) { 229 | var type = "statement"; 230 | pushContext(state, stream.column(), type); 231 | } 232 | 233 | if (style == "variable" && 234 | ((state.prevToken == "def" || 235 | (parserConfig.typeFirstDefinitions && typeBefore(stream, state) && 236 | isTopScope(state.context) && stream.match(/^\s*\(/, false))))) 237 | style = "def"; 238 | 239 | if (style == "def" && parserConfig.styleDefs === false) style = "variable"; 240 | 241 | state.startOfLine = false; 242 | state.prevToken = isDefKeyword ? "def" : style || curPunc; 243 | return style; 244 | }, 245 | 246 | indent: function (state, textAfter) { 247 | if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; 248 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); 249 | if (isStatement(ctx.type) && firstChar == "}") ctx = ctx.prev; 250 | 251 | var closing = firstChar == ctx.type; 252 | 253 | if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) { 254 | while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev 255 | return ctx.indented 256 | } 257 | if (isStatement(ctx.type)) 258 | return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); 259 | if (ctx.align && (!dontAlignCalls || ctx.type != ")")) 260 | return ctx.column + (closing ? 0 : 1); 261 | if (ctx.type == ")" && !closing) 262 | return ctx.indented + statementIndentUnit; 263 | 264 | return ctx.indented + (closing ? 0 : indentUnit); 265 | }, 266 | 267 | electricInput: /^\s*[{}]$/, 268 | blockCommentStart: "/*", 269 | blockCommentEnd: "*/", 270 | lineComment: "//", 271 | fold: "brace" 272 | }; 273 | }); 274 | 275 | }); 276 | -------------------------------------------------------------------------------- /eisen-script.peg: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Sebastien Sydney Robert Bigot 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | // The views and conclusions contained in the software and documentation are those 25 | // of the authors and should not be interpreted as representing official policies, 26 | // either expressed or implied, of the FreeBSD Project. 27 | { 28 | function extractOptional(optional, index) { 29 | return optional ? optional[index] : null; 30 | } 31 | 32 | function optionalList(value) { 33 | return value !== null ? value : []; 34 | } 35 | 36 | function buildList(head, tail, index) { 37 | return [head].concat(extractList(tail, index)); 38 | } 39 | 40 | function extractList(list, index) { 41 | var result = new Array(list.length), i; 42 | 43 | for (i = 0; i < list.length; i++) { 44 | result[i] = list[i][index]; 45 | } 46 | 47 | return result; 48 | } 49 | 50 | var clauseid = 0; 51 | } 52 | 53 | Start = StatementSeq? 54 | 55 | StatementSeq 56 | = _? head:Statement tail:(_ Statement)* _? { 57 | return buildList(head, tail, 1); 58 | } 59 | 60 | Statement "statement" 61 | = SetStatement / RuleInvoc / RuleDef 62 | 63 | RuleDef "definition" 64 | = RuleToken _ rule:RuleName attributes:(_ RuleAttributes)* _? "{" _? production:RuleInvocSeq? _? "}" { 65 | 66 | var weight = 1; 67 | var failover = null; 68 | var maxdepth = -1; 69 | 70 | var attributes = extractList(attributes, 1); 71 | for (var i = 0; i < attributes.length; ++i) { 72 | var attribute = attributes[i]; 73 | switch (attribute.type) { 74 | case "md": 75 | maxdepth = attribute.maxdepth; 76 | failover = attribute.failover; 77 | break; 78 | case "weight": 79 | weight = attribute.weight; 80 | break; 81 | } 82 | } 83 | 84 | return {type: "def", rule: rule, weight: weight, failover: failover, maxdepth: maxdepth, production: optionalList(production), id: clauseid++} 85 | } 86 | 87 | RuleInvocSeq "production" 88 | = head:RuleInvoc tail:(_ RuleInvoc)* { 89 | return buildList(head, tail, 1); 90 | } 91 | 92 | RuleAttributes "attributes" 93 | = MaxDepthAttribute / WeightAttribute 94 | 95 | MaxDepthAttribute "maxdepth" 96 | = MaxDepthToken _ maxdepth:UnsignedIntegerLitteral failover:(_? Failover)? { 97 | return {type: "md", maxdepth: maxdepth, failover: extractOptional(failover, 1)}; 98 | } 99 | 100 | Failover "failover" 101 | = ">" _? rule:RuleName { 102 | return rule; 103 | } 104 | 105 | WeightAttribute "weight modifier" 106 | = WeightToken _ weight:Float { 107 | return {type: "weight" , weight: weight}; 108 | } 109 | 110 | RuleName "name" 111 | = !Reserved identifier:Identifier { 112 | return identifier 113 | } 114 | 115 | RuleInvoc "invocation" 116 | = transformations:Transformation* _? next:Next { 117 | return {type: "invoc", transformations: transformations, next: next} 118 | } 119 | 120 | Next = name:RuleName { 121 | return {type: "call", name: name} 122 | } 123 | / name:Shape { 124 | return {type: "shape", name: name} 125 | } 126 | 127 | Transformation "transformation" 128 | = _? multiplier:Multiplier? "{" _? sequence:ElementaryTransformationSeq? _? "}" { 129 | return {multiplier: multiplier ? multiplier : 1, sequence: optionalList(sequence)}; 130 | } 131 | 132 | Multiplier "multiplier" 133 | = multiplier:UnsignedIntegerLitteral _? "*" _? { 134 | return multiplier; 135 | } 136 | 137 | ElementaryTransformationSeq 138 | = head:ElementaryTransformation tail:(_ ElementaryTransformation)* { 139 | return buildList(head, tail, 1); 140 | } 141 | 142 | ElementaryTransformation 143 | = Tx / Ty / Tz / Rx / Ry / Rz / Scale / Fx / Fy / Fz / Matrix / Hue / Sat / Bright / Alpha / SetColor / Blend 144 | 145 | // 3D-space transforms 146 | 147 | Tx "translate x" 148 | = TxToken _ dx:ArithExpression { 149 | return {type: "trans", t: [dx, 0, 0]} 150 | } 151 | 152 | Ty "translate y" 153 | = TyToken _ dy:ArithExpression { 154 | return {type: "trans", t: [0, dy, 0]} 155 | } 156 | 157 | Tz "translate z" 158 | = TzToken _ dz:ArithExpression { 159 | return {type: "trans", t: [0, 0, dz]} 160 | } 161 | 162 | Rx "rotate x" 163 | = RxToken _ theta:ArithExpression { 164 | return {type: "rot", axis: 0, theta: theta} 165 | } 166 | 167 | Ry "rotate y" 168 | = RyToken _ theta:ArithExpression { 169 | return {type: "rot", axis: 1, theta: theta} 170 | } 171 | 172 | Rz "rotate z" 173 | = RzToken _ theta:ArithExpression { 174 | return {type: "rot", axis: 2, theta: theta} 175 | } 176 | 177 | Scale "scale" 178 | = ScaleToken _ x:ArithExpression yz:(_ ArithExpression _ ArithExpression)? { 179 | return {type: "scale", s: yz ? [x, yz[1], yz[3]] : [x, x, x]}; 180 | } 181 | 182 | Fx "flip x" 183 | = FxToken { 184 | return {type: "scale", s: [-1, 1, 1]}; 185 | } 186 | 187 | Fy "flip y" 188 | = FyToken { 189 | return {type: "scale", s: [1, -1, 1]}; 190 | } 191 | 192 | Fz "flip z" 193 | = FzToken { 194 | return {type: "scale", s: [1, 1, -1]}; 195 | } 196 | 197 | Matrix "matrix" 198 | = MatrixToken m:(_ ArithExpression)[9] { 199 | return {type: "matrix", m: extractList(m, 1)}; 200 | } 201 | 202 | // // TODO Colorspace transforms 203 | Hue "hue" 204 | = HueToken _ hue:ArithExpression { 205 | return {type: "hue", h: hue}; 206 | } 207 | 208 | Sat "saturation" 209 | = SatToken _ sat:ArithExpression { 210 | return {type: "sat", s: sat}; 211 | } 212 | 213 | Bright "brightness" 214 | = BrightnessToken _ brightness:ArithExpression { 215 | return {type: "brightness", v: brightness}; 216 | } 217 | 218 | Alpha "alpha" 219 | = AlphaToken _ alpha:ArithExpression { 220 | return {type: "alpha", a: alpha}; 221 | } 222 | 223 | SetColor "set color" 224 | = ColorToken _ color:Color { 225 | return {type: "color", color: color}; 226 | } 227 | 228 | Blend "blend" 229 | = BlendToken _ a:ArithExpression _ b:ArithExpression { 230 | return {blend: [a, b]} 231 | } 232 | 233 | // TODO 234 | // set color random 235 | // set colorpool [scheme] 236 | 237 | Shape "shape" 238 | = "box" / "grid" / "sphere" / "line" / "point" / "triangle" / "mesh" / "cylinder" / "tube" 239 | 240 | SetStatement "set statement" 241 | = SetToken _ what:(Maxdepth / MaxObjects / Minsize / Naxsize / Seed / Background / Raytracer) { 242 | return what 243 | } 244 | 245 | Maxdepth "maxdepth" 246 | = MaxDepthToken _? max:UnsignedIntegerLitteral { 247 | return {type: "maxdepth", max: max}; 248 | } 249 | 250 | MaxObjects "maxobjects" 251 | = MaxObjectsToken _? max:UnsignedIntegerLitteral { 252 | return {type: "maxobjects", max: max}; 253 | } 254 | 255 | Minsize "minsize" 256 | = MinSizeToken _? min:Float { 257 | return {type: "minsize", min: min}; 258 | } 259 | 260 | Naxsize "maxsize" 261 | = MaxSizeToken _? max:Float { 262 | return {type: "maxsize", max: max}; 263 | } 264 | 265 | Seed "seed" 266 | = SeedToken _? seed:Integer { 267 | return {type: "seed", seed: seed}; 268 | } 269 | / SeedToken _? "initial" // TODO 270 | 271 | Background "background" 272 | = BackgroundToken _? color:Color { 273 | return {type: "background", color: color}; 274 | } 275 | 276 | // For structure-synth compat 277 | Raytracer "raytracer" 278 | = RaytracerToken (!LineTerminator SourceCharacter)* 279 | 280 | DecimalDigit 281 | = [0-9] 282 | 283 | NonZeroDigit 284 | = [1-9] 285 | 286 | HexDigit "hex digit" 287 | = d:[0-9a-f]i 288 | 289 | Character "character" 290 | = [a-z]i 291 | 292 | UnsignedIntegerLitteral 293 | = UnsignedInteger { 294 | return parseInt(text()); 295 | } 296 | 297 | UnsignedInteger "integer" 298 | = "0" / NonZeroDigit DecimalDigit* 299 | 300 | Integer 301 | = [+-]? UnsignedInteger 302 | 303 | ExponentPart 304 | = ExponentIndicator Integer 305 | 306 | ExponentIndicator 307 | = "e"i 308 | 309 | // Litterals 310 | Float "float" 311 | = (Integer "." DecimalDigit* ExponentPart? / "." DecimalDigit+ ExponentPart? / Integer ExponentPart?) { 312 | return parseFloat(text()); 313 | } 314 | 315 | 316 | Color "color" 317 | = "#" HexDigit HexDigit HexDigit (HexDigit HexDigit HexDigit)? { 318 | return text() 319 | } 320 | / ColorName { 321 | return text() 322 | } 323 | 324 | ColorName = "white" / "black" / "red" / "green" / "blue" / "grey" 325 | 326 | // Simple arithmetic 327 | ArithExpression "arithmetic expression" 328 | = head:ArithTerm tail:(_? ("+" / "-") _? ArithTerm)* { 329 | var result = head, i; 330 | 331 | for (i = 0; i < tail.length; i++) { 332 | if (tail[i][1] === "+") { result += tail[i][3]; } 333 | if (tail[i][1] === "-") { result -= tail[i][3]; } 334 | } 335 | 336 | return result; 337 | } 338 | 339 | ArithTerm 340 | = head:ArithFactor tail:(_? ("*" / "/") _? ArithFactor)* { 341 | var result = head, i; 342 | 343 | for (i = 0; i < tail.length; i++) { 344 | if (tail[i][1] === "*") { result *= tail[i][3]; } 345 | if (tail[i][1] === "/") { result /= tail[i][3]; } 346 | } 347 | 348 | return result; 349 | } 350 | 351 | ArithFactor 352 | = "(" _? expr:ArithExpression _? ")" { return expr; } 353 | / Float 354 | 355 | Identifier "identifier" 356 | = head:IdentifierStart tail:IdentifierPart* { return head + tail.join("") } 357 | 358 | IdentifierStart 359 | = "_" / Character 360 | 361 | IdentifierPart 362 | = IdentifierStart / DecimalDigit 363 | 364 | SourceCharacter = . 365 | 366 | WhiteSpace "whitespace" = "\t" / "\v" / "\f" / " " 367 | 368 | LineTerminator = [\n\r] 369 | 370 | LineTerminatorSequence "end of line" = "\n" / "\r\n" / "\r" 371 | 372 | Comment "comment" 373 | = MultiLineComment 374 | / SingleLineComment 375 | 376 | MultiLineComment 377 | = "/*" (!"*/" SourceCharacter)* "*/" 378 | 379 | SingleLineComment 380 | = "//" (!LineTerminator SourceCharacter)* 381 | _ 382 | = (WhiteSpace / LineTerminatorSequence / Comment)+ 383 | 384 | SetToken = "set" !IdentifierPart 385 | RuleToken = "rule" !IdentifierPart 386 | MaxDepthToken = "md" !IdentifierPart / "maxdepth" !IdentifierPart 387 | WeightToken = "w" !IdentifierPart / "weight" !IdentifierPart 388 | MaxObjectsToken = "maxobjects" !IdentifierPart 389 | MinSizeToken = "minsize" !IdentifierPart 390 | MaxSizeToken = "maxsize" !IdentifierPart 391 | SeedToken = "seed" !IdentifierPart 392 | BackgroundToken = "background" !IdentifierPart 393 | HueToken = "h" !IdentifierPart / "hue" !IdentifierPart 394 | SatToken = "sat" !IdentifierPart 395 | BrightnessToken = "b" !IdentifierPart / "brightness" !IdentifierPart 396 | AlphaToken = "a" !IdentifierPart / "alpha" !IdentifierPart 397 | ColorToken = "color" !IdentifierPart 398 | BlendToken = "blend" !IdentifierPart 399 | InitialToken = "initial" !IdentifierPart 400 | TxToken = "x" !IdentifierPart 401 | TyToken = "y" !IdentifierPart 402 | TzToken = "z" !IdentifierPart 403 | RxToken = "rx" !IdentifierPart 404 | RyToken = "ry" !IdentifierPart 405 | RzToken = "rz" !IdentifierPart 406 | ScaleToken = "s" !IdentifierPart 407 | FxToken = "fx" !IdentifierPart 408 | FyToken = "fy" !IdentifierPart 409 | FzToken = "fz" !IdentifierPart 410 | MatrixToken = "m" !IdentifierPart 411 | RaytracerToken = "raytracer" !IdentifierPart 412 | 413 | Reserved "reserved" 414 | = SetToken 415 | / RuleToken 416 | / MaxDepthToken 417 | / WeightToken 418 | / MaxObjectsToken 419 | / MinSizeToken 420 | / MaxSizeToken 421 | / SeedToken 422 | / BackgroundToken 423 | / HueToken 424 | / SatToken 425 | / BrightnessToken 426 | / AlphaToken 427 | / ColorToken 428 | / BlendToken 429 | / InitialToken 430 | / TxToken 431 | / TyToken 432 | / TzToken 433 | / RxToken 434 | / RyToken 435 | / RzToken 436 | / ScaleToken 437 | / FxToken 438 | / FyToken 439 | / FzToken 440 | / MatrixToken 441 | / RaytracerToken 442 | / Shape !IdentifierPart 443 | / ColorName !IdentifierPart 444 | -------------------------------------------------------------------------------- /synthesizer.ts: -------------------------------------------------------------------------------- 1 | 1// Copyright (c) 2016, Sebastien Sydney Robert Bigot 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // 1. Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | // The views and conclusions contained in the software and documentation are those 25 | // of the authors and should not be interpreted as representing official policies, 26 | // either expressed or implied, of the FreeBSD Project. 27 | 28 | var eisenscript = require('./eisen-script'); 29 | var glmat = require('gl-matrix'); 30 | 31 | import { ShapeInstance } from './structure'; 32 | import * as tinycolor from 'tinycolor2'; 33 | import * as collections from 'typescript-collections'; 34 | import * as seedrandom from 'seedrandom'; 35 | 36 | enum Axis { X, Y, Z }; 37 | 38 | interface ASTNode { 39 | type: string; 40 | } 41 | 42 | interface DefStatement extends ASTNode { 43 | rule: string; 44 | weight: number; 45 | maxdepth: number; 46 | failover: string; 47 | production: InvocStatement[]; 48 | id: number; 49 | } 50 | 51 | interface InvocStatement extends ASTNode { 52 | transformations: Transformation[]; 53 | next: NextNode; 54 | } 55 | 56 | interface SetStatement extends ASTNode { 57 | // TODO 58 | } 59 | 60 | interface NextNode extends ASTNode { 61 | name: string; 62 | } 63 | 64 | interface Transformation { 65 | multiplier: number; 66 | sequence: ASTNode[]; 67 | } 68 | 69 | interface TransNode extends ASTNode { 70 | t: number[]; 71 | } 72 | 73 | interface RotNode extends ASTNode { 74 | axis: Axis; 75 | theta: number; 76 | } 77 | 78 | interface ScaleNode extends ASTNode { 79 | s: number[]; 80 | } 81 | 82 | interface MatrixNode extends ASTNode { 83 | m: number[]; 84 | } 85 | 86 | interface ColorNode extends ASTNode { 87 | color: string; 88 | } 89 | 90 | interface HueNode extends ASTNode { 91 | h: number; 92 | } 93 | 94 | interface SatNode extends ASTNode { 95 | s: number; 96 | } 97 | 98 | interface BrightnessNode extends ASTNode { 99 | v: number; 100 | } 101 | 102 | interface AlphaNode extends ASTNode { 103 | a: number; 104 | } 105 | 106 | interface MaxNode extends ASTNode { 107 | max: number; 108 | } 109 | 110 | interface MinNode extends ASTNode { 111 | min: number; 112 | } 113 | 114 | interface SeedNode extends ASTNode { 115 | seed: number; 116 | } 117 | 118 | 119 | interface SynthFrame { 120 | rule: string; 121 | globalDepth: number; 122 | clauseDepthMap: collections.Dictionary; 123 | geospace: Float32Array; 124 | colorspace: tinycolor.ColorFormats.HSVA; 125 | } 126 | 127 | function clamp(value: number, min: number, max: number): number { 128 | return Math.min(Math.max(value, min), max); 129 | }; 130 | 131 | function normalizeAngle(angle: number, lower: number) { 132 | var upper = lower + 360; 133 | while (angle < lower) { 134 | angle += 360; 135 | } 136 | while (angle > upper) { 137 | angle -= 360; 138 | } 139 | return angle; 140 | } 141 | 142 | interface ProgressFunc { 143 | (nhapes: ShapeInstance[], done: boolean): void; 144 | } 145 | 146 | export class Synthesizer { 147 | 148 | public constructor(script: string, progress: ProgressFunc) { 149 | this.ast = eisenscript.parse(script); 150 | this.index = Synthesizer.indexRules(this.ast); 151 | 152 | this.background = tinycolor("black").toHexString(); 153 | 154 | // Let them crash their browser if they're keen. 155 | this.maxObjects = Infinity; 156 | this.maxDepth = Infinity; 157 | this.maxSize = Infinity; 158 | this.minSize = 0; 159 | 160 | this.prng = seedrandom(); 161 | this.progress = progress; 162 | } 163 | 164 | private static indexRules(ast: ASTNode[]): collections.Dictionary { 165 | var index = new collections.Dictionary(); 166 | for (var si = 0; si < ast.length; ++si) { 167 | if (ast[si].type == "def") { 168 | var def = ast[si]; 169 | var wclauses = index.getValue(def.rule); 170 | if (!wclauses) { 171 | wclauses = [0, []]; 172 | index.setValue(def.rule, wclauses); 173 | } 174 | wclauses[0] += def.weight; 175 | wclauses[1].push(def); 176 | } 177 | } 178 | index.forEach(function (rule: string, wclauses: [number, DefStatement[]]): void { 179 | wclauses[1].sort(function (left: DefStatement, right: DefStatement): number { 180 | return left.weight - right.weight; 181 | }); 182 | }); 183 | return index; 184 | } 185 | 186 | private pickClause(rule: string): DefStatement { 187 | var wclauses = this.index.getValue(rule); 188 | var guess = wclauses[0] * this.prng(); 189 | for (var ci = 0; ci < wclauses[1].length; ++ci) { 190 | var clause = wclauses[1][ci]; 191 | guess -= clause.weight; 192 | if (guess < 0) { 193 | return clause; 194 | } 195 | } 196 | } 197 | 198 | public synthesize(): ShapeInstance[] { 199 | var shapes = new Array(); 200 | 201 | for (var si = 0; si < this.ast.length; ++si) { 202 | switch (this.ast[si].type) { 203 | case "invoc": 204 | this.synthesizeOne(this.ast[si], shapes); 205 | break; 206 | case "maxobjects": 207 | this.maxObjects = (this.ast[si]).max; 208 | break; 209 | case "maxdepth": 210 | this.maxDepth = (this.ast[si]).max; 211 | break; 212 | case "background": 213 | this.background = tinycolor((this.ast[si]).color).toHexString(); 214 | break; 215 | case "minsize": 216 | this.minSize = (this.ast[si]).min; 217 | break; 218 | case "masize": 219 | this.maxSize = (this.ast[si]).max; 220 | break; 221 | case "seed": 222 | this.prng = seedrandom((this.ast[si]).seed.toString()); 223 | break; 224 | } 225 | } 226 | 227 | this.progress(shapes, true); 228 | 229 | return shapes; 230 | } 231 | 232 | private synthesizeOne(prod: InvocStatement, shapes: ShapeInstance[]): void { 233 | 234 | var queue = new collections.Queue(); 235 | 236 | this.synthProduction(prod, 0, new collections.Dictionary(), glmat.mat4.create(), tinycolor("RED").toHsv(), queue, shapes); 237 | 238 | while (!queue.isEmpty()) { 239 | 240 | var { rule, globalDepth, clauseDepthMap, geospace, colorspace } = queue.dequeue(); 241 | 242 | if (globalDepth >= this.maxDepth) { 243 | continue; 244 | } 245 | ++globalDepth; 246 | 247 | var clause = this.pickClause(rule); 248 | 249 | var clauseDepthMapCopy = new collections.Dictionary(); 250 | clauseDepthMap.forEach(function (key: number, value: number) { 251 | clauseDepthMapCopy.setValue(key, value); 252 | }); 253 | 254 | if (clause.maxdepth >= 0) { 255 | 256 | var thisClauseDepth = clauseDepthMapCopy.getValue(clause.id); 257 | 258 | if (undefined == thisClauseDepth) { 259 | thisClauseDepth = 0; 260 | } 261 | 262 | if (thisClauseDepth >= clause.maxdepth) { 263 | if (clause.failover) { 264 | queue.enqueue({ 265 | rule: clause.failover, 266 | globalDepth: globalDepth + 1, 267 | clauseDepthMap: clauseDepthMapCopy, 268 | geospace, colorspace 269 | }); 270 | } 271 | continue; 272 | } else { 273 | clauseDepthMapCopy.setValue(clause.id, thisClauseDepth + 1); 274 | } 275 | } 276 | 277 | for (var pi = 0; pi < clause.production.length; ++pi) { 278 | this.synthProduction(clause.production[pi], globalDepth, clauseDepthMapCopy, geospace, colorspace, queue, shapes); 279 | 280 | if (shapes.length >= this.maxObjects) { 281 | return; 282 | } 283 | 284 | } 285 | } 286 | } 287 | 288 | private synthProduction(prod: InvocStatement, 289 | globalDepth: number, 290 | clauseDepthMap: collections.Dictionary, 291 | geospace: Float32Array, 292 | colorspace: tinycolor.ColorFormats.HSVA, 293 | queue: collections.Queue, 294 | shapes: ShapeInstance[]): void { 295 | 296 | var [childGeospaces, childColorspaces] = this.transform(prod.transformations, geospace, colorspace); 297 | 298 | console.assert(childGeospaces.length == childColorspaces.length); 299 | 300 | switch (prod.next.type) { 301 | case "shape": 302 | for (var mi = 0; mi < childGeospaces.length; ++mi) { 303 | var diag = [1, 1, 1, 0]; 304 | glmat.vec4.transformMat4(diag, diag, childGeospaces[mi]); 305 | var size = glmat.vec4.length(diag); 306 | if (this.minSize <= size && size <= this.maxSize) { 307 | shapes.push({ shape: prod.next.name, geospace: childGeospaces[mi], colorspace: childColorspaces[mi] }); 308 | this.progress(shapes, false); 309 | if (shapes.length >= this.maxObjects) { 310 | console.log("max objects reached"); 311 | return; 312 | } 313 | } 314 | } 315 | break; 316 | case "call": 317 | for (var mi = 0; mi < childGeospaces.length; ++mi) { 318 | var diag = [1, 1, 1, 0]; 319 | glmat.vec4.transformMat4(diag, diag, childGeospaces[mi]); 320 | var size = glmat.vec4.length(diag); 321 | if (this.minSize <= size && size <= this.maxSize) { 322 | queue.enqueue({ rule: prod.next.name, globalDepth, clauseDepthMap, geospace: childGeospaces[mi], colorspace: childColorspaces[mi] }); 323 | } 324 | } 325 | break; 326 | } 327 | } 328 | 329 | private transform(transforms: Transformation[], geospace: Float32Array, colorspace: tinycolor.ColorFormats.HSVA): [Float32Array[], tinycolor.ColorFormats.HSVA[]] { 330 | 331 | var childGeospaces = new Array(); 332 | var childColorspaces = new Array(); 333 | 334 | var stack = new collections.Stack<[number, Float32Array, tinycolor.ColorFormats.HSVA]>(); 335 | stack.push([0, geospace, colorspace]); 336 | while (!stack.isEmpty()) { 337 | var [ti, childGeospace, childColorSpace] = stack.pop(); 338 | if (ti < transforms.length) { 339 | var trans = transforms[ti]; 340 | for (var repeat = 0; repeat < trans.multiplier; ++repeat) { 341 | var [childGeospace, childColorSpace] = this.transformOne(transforms[ti].sequence, childGeospace, childColorSpace); 342 | stack.push([ti + 1, childGeospace, childColorSpace]); 343 | } 344 | } else { 345 | childGeospaces.push(childGeospace); 346 | childColorspaces.push(childColorSpace); 347 | } 348 | }; 349 | 350 | return [childGeospaces, childColorspaces]; 351 | } 352 | 353 | private transformOne(sequence: ASTNode[], geospace: Float32Array, colorspace: tinycolor.ColorFormats.HSVA): [Float32Array, tinycolor.ColorFormats.HSVA] { 354 | 355 | var childGeospace = new Float32Array(geospace); 356 | var childColorspace = { h: colorspace.h, s: colorspace.s, v: colorspace.v, a: colorspace.a }; 357 | 358 | for (var si = 0; si < sequence.length; ++si) { 359 | switch (sequence[si].type) { 360 | case "trans": 361 | var trans = sequence[si]; 362 | glmat.mat4.translate(childGeospace, childGeospace, trans.t); 363 | break; 364 | case "rot": 365 | var rot = sequence[si]; 366 | var thetaRad = rot.theta * Math.PI / 180 367 | switch (rot.axis) { 368 | case Axis.X: 369 | glmat.mat4.rotateX(childGeospace, childGeospace, thetaRad); 370 | break; 371 | case Axis.Y: 372 | glmat.mat4.rotateY(childGeospace, childGeospace, thetaRad); 373 | break; 374 | case Axis.Z: 375 | glmat.mat4.rotateZ(childGeospace, childGeospace, thetaRad); 376 | break; 377 | } 378 | break; 379 | case "scale": 380 | var scale = sequence[si]; 381 | glmat.mat4.scale(childGeospace, childGeospace, scale.s); 382 | break; 383 | case "matrix": 384 | var matrix = sequence[si]; 385 | 386 | var m = [matrix.m[0], matrix.m[1], matrix.m[2], 0, 387 | matrix.m[3], matrix.m[4], matrix.m[5], 0, 388 | matrix.m[6], matrix.m[7], matrix.m[8], 0, 389 | 0, 0, 0, 1]; 390 | 391 | glmat.mat4.multiply(childGeospace, childGeospace, m); 392 | break; 393 | case "color": 394 | var color = sequence[si]; 395 | childColorspace = tinycolor(color.color).toHsv(); 396 | break; 397 | case "sat": 398 | var sat = sequence[si]; 399 | childColorspace.s = clamp(childColorspace.s * sat.s, 0, 1); 400 | break; 401 | case "hue": 402 | var hue = sequence[si]; 403 | childColorspace.h = normalizeAngle(childColorspace.h + hue.h, 0); 404 | break; 405 | case "brightness": 406 | var brightness = sequence[si]; 407 | childColorspace.v = clamp(childColorspace.v * brightness.v, 0, 1); 408 | break; 409 | case "alpha": 410 | var alpha = sequence[si]; 411 | childColorspace.a = clamp(childColorspace.a * alpha.a, 0, 1); 412 | break; 413 | } 414 | } 415 | 416 | return [childGeospace, childColorspace]; 417 | } 418 | 419 | private ast: ASTNode[]; 420 | private maxObjects: number; 421 | private maxDepth: number; 422 | private maxSize: number; 423 | private minSize: number; 424 | private index: collections.Dictionary; 425 | private prng: seedrandom.prng; 426 | private progress: ProgressFunc; 427 | public background: string; 428 | 429 | } 430 | --------------------------------------------------------------------------------