├── .babelrc ├── .editorconfig ├── .electron-vue ├── build-runner.js ├── build.sh ├── dev-client.js ├── dev-runner.js ├── webpack.embed.config.js ├── webpack.main.config.js └── webpack.renderer.config.js ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── build └── icons │ └── microbium.icns ├── dist ├── electron │ └── .gitkeep └── embed │ └── .gitkeep ├── package.json ├── screen_shots └── screen_shot_04.jpg ├── src ├── embed.ejs ├── index.ejs ├── main │ ├── exporters │ │ └── html.js │ ├── index.dev.js │ ├── index.js │ ├── io │ │ └── socket.js │ ├── ui │ │ ├── menu.js │ │ └── touchbar.js │ └── utils │ │ ├── platform.js │ │ └── window.js └── renderer │ ├── MicrobiumApp.vue │ ├── MicrobiumEmbed.vue │ ├── assets │ ├── .gitkeep │ ├── backgrounds │ │ └── range-fill.png │ ├── fonts │ │ ├── fira-mono │ │ │ ├── index.scss │ │ │ └── ttf │ │ │ │ ├── FiraMono-Bold.ttf │ │ │ │ ├── FiraMono-Medium.ttf │ │ │ │ └── FiraMono-Regular.ttf │ │ └── fira-sans │ │ │ ├── index.scss │ │ │ └── ttf │ │ │ ├── FiraSans-Bold.ttf │ │ │ ├── FiraSans-ExtraBold.ttf │ │ │ ├── FiraSans-ExtraLight.ttf │ │ │ ├── FiraSans-Heavy.ttf │ │ │ ├── FiraSans-Light.ttf │ │ │ ├── FiraSans-Medium.ttf │ │ │ └── FiraSans-Regular.ttf │ ├── icons │ │ ├── app-mono.png │ │ ├── app.png │ │ ├── constraints.svg │ │ ├── draw.svg │ │ ├── effects.svg │ │ ├── forces.svg │ │ ├── geometry.svg │ │ ├── minus.svg │ │ ├── plus.svg │ │ ├── select.svg │ │ ├── styles.svg │ │ ├── tool.svg │ │ └── viewport.svg │ └── images │ │ ├── background.png │ │ └── textures │ │ ├── alpha-hairy.jpg │ │ ├── alpha-hatchy.jpg │ │ ├── ground-mud.jpg │ │ └── watercolor.jpg │ ├── components │ ├── Editor.vue │ ├── Palette.vue │ ├── Viewer.vue │ ├── display │ │ └── Icon.vue │ ├── editor │ │ ├── Compositor.vue │ │ ├── Cursor.vue │ │ └── QuickTool.vue │ ├── input │ │ ├── Button.vue │ │ ├── Checkbox.vue │ │ ├── Color.vue │ │ ├── File.vue │ │ ├── FileRefresh.vue │ │ ├── Range.vue │ │ ├── Select.vue │ │ └── Text.vue │ └── palette │ │ ├── Camera.vue │ │ ├── Constraint.vue │ │ ├── ConstraintList.vue │ │ ├── ConstraintPreview.vue │ │ ├── Effects.vue │ │ ├── Force.vue │ │ ├── ForceList.vue │ │ ├── Group.vue │ │ ├── ItemController.vue │ │ ├── Modes.vue │ │ ├── Section.vue │ │ ├── Simulation.vue │ │ ├── Style.vue │ │ ├── StyleList.vue │ │ ├── StylePreview.vue │ │ ├── Tool.vue │ │ └── Viewport.vue │ ├── constants │ ├── color-palettes.js │ ├── line-styles.js │ ├── scene-format.js │ └── types.js │ ├── core │ ├── cameras.js │ ├── geometry.js │ ├── index.js │ ├── interaction.js │ ├── io.js │ ├── renderer.js │ ├── scenes.js │ ├── simulation.js │ └── viewport.js │ ├── draw │ ├── commands │ │ └── screen-space.js │ └── routines │ │ ├── geometry.js │ │ ├── origin.js │ │ ├── primitive.js │ │ └── simulation.js │ ├── embed.js │ ├── main.js │ ├── mixins │ └── EditableListMixin.js │ ├── physics │ ├── constraints │ │ └── ViscousDistanceConstraint.js │ ├── forces │ │ ├── RepulsorForce.js │ │ └── RotatorForce.js │ └── systems │ │ └── ParticleSystem.js │ ├── router │ └── main.js │ ├── shaders │ ├── alpha │ │ ├── basic-dash.glsl │ │ ├── bulging-dash.glsl │ │ ├── concentric-dash.glsl │ │ ├── lateral-dash.glsl │ │ ├── radial-dash.glsl │ │ └── wavy-dash.glsl │ ├── band-gradient.glsl │ ├── basic.frag │ ├── basic.vert │ ├── box-blur.glsl │ ├── color │ │ ├── brightness-contrast.glsl │ │ ├── hsv2rgb.glsl │ │ └── rgb2hsv.glsl │ ├── edge-frei-chen.glsl │ ├── fills-entities.frag │ ├── fills-entities.vert │ ├── line-antialias-alpha.glsl │ ├── lines-entities.frag │ ├── lines-entities.vert │ ├── lines-ui.frag │ ├── position │ │ ├── entities-mapz.glsl │ │ └── entities-transform.glsl │ ├── post-fx-banding.frag │ ├── post-fx-box-blur.frag │ ├── post-fx-copy.frag │ ├── post-fx-copy.vert │ ├── post-fx-edges.frag │ ├── post-fx-feedback.frag │ ├── post-fx-feedback.vert │ ├── post-fx-gaussian-blur.frag │ ├── post-fx-hash-blur.frag │ ├── post-fx-mirror.frag │ ├── post-fx.frag │ ├── post-fx.vert │ └── vignette.glsl │ ├── store │ ├── hubs │ │ └── PaletteControllers.js │ ├── index.js │ └── modules │ │ ├── Editor.js │ │ ├── Palette.js │ │ └── index.js │ └── utils │ ├── array.js │ ├── color.js │ ├── ctor.js │ ├── draw.js │ ├── fbo.js │ ├── function.js │ ├── gl-logger.js │ ├── logger.js │ ├── loop.js │ ├── math.js │ ├── number.js │ ├── pool.js │ ├── screen.js │ ├── svg.js │ ├── task.js │ ├── texture.js │ ├── timer.js │ ├── tween.js │ └── word.js ├── static ├── .gitkeep ├── exporter-templates │ └── html.ejs └── icons │ └── touchbar │ ├── constraints-alt.png │ ├── constraints-alt@2x.png │ ├── constraints.png │ ├── constraints@2x.png │ ├── effects-alt.png │ ├── effects-alt@2x.png │ ├── effects.png │ ├── effects@2x.png │ ├── forces-alt.png │ ├── forces-alt@2x.png │ ├── forces.png │ ├── forces@2x.png │ ├── geometry-alt.png │ ├── geometry-alt@2x.png │ ├── geometry.png │ ├── geometry@2x.png │ ├── simulation-pause-active.png │ ├── simulation-pause-active@2x.png │ ├── simulation-pause-inactive.png │ ├── simulation-pause-inactive@2x.png │ ├── simulation-play.png │ ├── simulation-play@2x.png │ ├── simulation-stop.png │ ├── simulation-stop@2x.png │ ├── styles-alt.png │ ├── styles-alt@2x.png │ ├── styles.png │ ├── styles@2x.png │ ├── tool-alt.png │ ├── tool-alt@2x.png │ ├── tool.png │ ├── tool@2x.png │ ├── viewport-alt.png │ ├── viewport-alt@2x.png │ ├── viewport.png │ └── viewport@2x.png ├── test └── fixtures │ ├── scene-001.json │ └── scene-001.mcrbm └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "main": { 5 | "presets": [ 6 | [ 7 | "@babel/preset-env", 8 | { 9 | "targets": { 10 | "node": 7 11 | } 12 | } 13 | ] 14 | ] 15 | }, 16 | "renderer": { 17 | "presets": [ 18 | [ 19 | "@babel/preset-env", 20 | { 21 | "modules": false 22 | } 23 | ] 24 | ] 25 | }, 26 | "embed": { 27 | "presets": [ 28 | [ 29 | "@babel/preset-env", 30 | { 31 | "modules": false 32 | } 33 | ] 34 | ] 35 | } 36 | }, 37 | "plugins": [ 38 | "@babel/plugin-transform-runtime", 39 | "@babel/plugin-syntax-dynamic-import", 40 | "@babel/plugin-proposal-class-properties" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | indent_style = tab 14 | indent_size = 4 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.electron-vue/build-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const { say } = require('cfonts') 6 | const chalk = require('chalk') 7 | const del = require('del') 8 | const webpack = require('webpack') 9 | const Multispinner = require('multispinner') 10 | 11 | const mainConfig = require('./webpack.main.config') 12 | const rendererConfig = require('./webpack.renderer.config') 13 | const embedConfig = require('./webpack.embed.config') 14 | const pkg = require('../package.json') 15 | 16 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 17 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 18 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 19 | const isCI = process.env.CI || false 20 | 21 | ;(function init () { 22 | switch (process.env.BUILD_TARGET) { 23 | case 'clean': 24 | clean() 25 | break 26 | case 'embed': 27 | embed() 28 | break 29 | default: 30 | build() 31 | break 32 | } 33 | })() 34 | 35 | function clean () { 36 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*']) 37 | console.log(`\n${doneLog}\n`) 38 | process.exit() 39 | } 40 | 41 | function build () { 42 | greeting() 43 | 44 | del.sync(['dist/electron/*', '!.gitkeep']) 45 | 46 | const tasks = ['main', 'renderer'] 47 | const m = new Multispinner(tasks, { 48 | preText: 'building', 49 | postText: 'process' 50 | }) 51 | 52 | let results = '' 53 | 54 | m.on('success', () => { 55 | process.stdout.write('\x1B[2J\x1B[0f') 56 | console.log(`\n\n${results}`) 57 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 58 | process.exit() 59 | }) 60 | 61 | pack(mainConfig).then(result => { 62 | results += result + '\n\n' 63 | m.success('main') 64 | }).catch(err => { 65 | m.error('main') 66 | console.log(`\n ${errorLog}failed to build main process`) 67 | console.error(`\n${err}\n`) 68 | process.exit(1) 69 | }) 70 | 71 | pack(rendererConfig).then(result => { 72 | results += result + '\n\n' 73 | m.success('renderer') 74 | }).catch(err => { 75 | m.error('renderer') 76 | console.log(`\n ${errorLog}failed to build renderer process`) 77 | console.error(`\n${err}\n`) 78 | process.exit(1) 79 | }) 80 | } 81 | 82 | function pack (config) { 83 | return new Promise((resolve, reject) => { 84 | webpack(config, (err, stats) => { 85 | if (err) reject(err.stack || err) 86 | else if (stats.hasErrors()) { 87 | let err = '' 88 | 89 | stats 90 | .toString({ 91 | chunks: false, 92 | colors: true 93 | }) 94 | .split(/\r?\n/) 95 | .forEach(line => { 96 | err += ` ${line}\n` 97 | }) 98 | 99 | reject(err) 100 | } else { 101 | resolve(stats.toString({ 102 | chunks: false, 103 | colors: true 104 | })) 105 | } 106 | }) 107 | }) 108 | } 109 | 110 | function embed () { 111 | del.sync(['dist/embed/*', '!.gitkeep']) 112 | webpack(embedConfig, (err, stats) => { 113 | if (err || stats.hasErrors()) console.log(err) 114 | 115 | console.log(stats.toString({ 116 | chunks: false, 117 | colors: true 118 | })) 119 | 120 | // TODO: Investigate why this was needed; was blocking analyzer plugin from working 121 | // process.exit() 122 | }) 123 | } 124 | 125 | function greeting () { 126 | const cols = process.stdout.columns 127 | let text = '' 128 | 129 | if (cols > 60) text = 'Microbium' 130 | else text = false 131 | 132 | if (text && !isCI) { 133 | say(text, { 134 | colors: ['yellow'], 135 | font: 'simple', 136 | space: false 137 | }) 138 | } else console.log(chalk.yellow.bold('\n Microbium')) 139 | console.log('\n' + chalk.blue(' v' + pkg.version) + '\n') 140 | console.log() 141 | } 142 | -------------------------------------------------------------------------------- /.electron-vue/build.sh: -------------------------------------------------------------------------------- 1 | run_build () { 2 | export BUILD_NUMBER="$(git rev-parse --short HEAD)" 3 | node ./.electron-vue/build-runner.js 4 | electron-builder --publish=never 5 | terminal-notifier -title 'Microbium' -message 'Build Complete' 6 | } 7 | 8 | if [[ -n $(git status --porcelain) ]] 9 | then 10 | echo "$(tput setaf 1)Working tree is dirty, please resolve changes before building.$(tput sgr0)" 11 | else 12 | run_build 13 | fi 14 | -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(event => { 4 | /** 5 | * Reload browser when HTMLWebpackPlugin emits a new index.html 6 | */ 7 | if (event.action === 'reload') { 8 | window.location.reload() 9 | } 10 | 11 | /** 12 | * Notify `mainWindow` when `main` process is compiling, 13 | * giving notice for an expected reload of the `electron` process 14 | */ 15 | if (event.action === 'compiling') { 16 | document.body.innerHTML += ` 17 | 30 | 31 |
32 | Compiling Main Process... 33 |
34 | ` 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /.electron-vue/webpack.embed.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'embed' 4 | 5 | const fs = require('fs') 6 | const path = require('path') 7 | const webpack = require('webpack') 8 | 9 | const CopyWebpackPlugin = require('copy-webpack-plugin') 10 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 11 | const HtmlWebpackPlugin = require('html-webpack-plugin') 12 | const { VueLoaderPlugin } = require('vue-loader') 13 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') 14 | 15 | const { version } = require('../package.json') 16 | const whiteListedModules = ['vue', 'events'] 17 | 18 | const embedConfig = { 19 | devtool: '#cheap-module-eval-source-map', 20 | stats: 'verbose', 21 | entry: { 22 | embed: path.join(__dirname, '../src/renderer/embed.js') 23 | }, 24 | module: { 25 | rules: [ 26 | /*{ 27 | test: /\.(js|vue)$/, 28 | enforce: 'pre', 29 | exclude: /node_modules/, 30 | use: { 31 | loader: 'eslint-loader', 32 | options: { 33 | formatter: require('eslint-friendly-formatter') 34 | } 35 | } 36 | },*/ 37 | { 38 | test: /\.vue$/, 39 | use: { 40 | loader: 'vue-loader', 41 | options: { 42 | extractCSS: process.env.NODE_ENV === 'production' 43 | } 44 | } 45 | }, 46 | { 47 | test: /\.js$/, 48 | use: 'babel-loader', 49 | exclude: /node_modules/ 50 | }, 51 | { 52 | test: /\.node$/, 53 | use: 'node-loader' 54 | }, 55 | { 56 | test: /\.scss$/, 57 | use: [ 58 | process.env.NODE_ENV !== 'production' 59 | ? 'vue-style-loader' 60 | : MiniCssExtractPlugin.loader, 61 | 'css-loader', 62 | 'sass-loader' 63 | ] 64 | }, 65 | { 66 | test: /\.css$/, 67 | use: [ 68 | process.env.NODE_ENV !== 'production' 69 | ? 'vue-style-loader' 70 | : MiniCssExtractPlugin.loader, 71 | 'css-loader' 72 | ] 73 | }, 74 | { 75 | test: /\.html$/, 76 | use: 'vue-html-loader' 77 | }, 78 | { 79 | test: /\.svg$/, 80 | loader: 'vue-svg-loader', 81 | options: { 82 | // optional [svgo](https://github.com/svg/svgo) options 83 | svgo: { 84 | plugins: [ 85 | {removeDoctype: true}, 86 | {removeComments: true} 87 | ] 88 | } 89 | } 90 | }, 91 | { 92 | test: /\.(png|jpe?g|gif)(\?.*)?$/, 93 | use: { 94 | loader: 'url-loader', 95 | query: { 96 | limit: 10000, 97 | name: 'imgs/[name]--[folder].[ext]' 98 | } 99 | } 100 | }, 101 | { 102 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 103 | loader: 'url-loader', 104 | options: { 105 | limit: 10000, 106 | name: 'media/[name]--[folder].[ext]' 107 | } 108 | }, 109 | { 110 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 111 | use: { 112 | loader: 'url-loader', 113 | query: { 114 | limit: 10000, 115 | name: 'fonts/[name]--[folder].[ext]' 116 | } 117 | } 118 | }, 119 | { 120 | test: /\.(glsl|frag|vert)$/, 121 | use: 'raw-loader' 122 | }, 123 | { 124 | test: /\.(glsl|frag|vert)$/, 125 | use: 'glslify-loader' 126 | } 127 | ] 128 | }, 129 | plugins: [ 130 | new VueLoaderPlugin(), 131 | new MiniCssExtractPlugin({ filename: 'styles.css' }), 132 | new webpack.DefinePlugin({ 133 | 'process.env.IS_WEB': 'true' 134 | }), 135 | new webpack.HotModuleReplacementPlugin(), 136 | new webpack.NoEmitOnErrorsPlugin() 137 | ], 138 | output: { 139 | filename: `[name]-${version}.js`, 140 | path: path.join(__dirname, '../dist/embed'), 141 | library: 'MicrobiumEmbed', 142 | libraryTarget: 'var' 143 | }, 144 | resolve: { 145 | alias: { 146 | '@root': path.join(__dirname, '../'), 147 | '@main': path.join(__dirname, '../src/main'), 148 | '@renderer': path.join(__dirname, '../src/renderer'), 149 | 'vue$': 'vue/dist/vue.esm.js', 150 | 'regl': '@microbium/regl' 151 | }, 152 | extensions: ['.js', '.vue', '.json', '.css'] 153 | }, 154 | target: 'web' 155 | } 156 | 157 | // Development (embed) 158 | if (process.env.NODE_ENV !== 'production' && 159 | process.env.BUILD_TARGET === 'embed') { 160 | const fixtureName = process.env.SCENE_FIXTURE 161 | 162 | embedConfig.plugins.push( 163 | new HtmlWebpackPlugin({ 164 | filename: 'index.html', 165 | template: path.resolve(__dirname, '../src/embed.ejs'), 166 | minify: { 167 | collapseWhitespace: true, 168 | removeAttributeQuotes: true, 169 | removeComments: true 170 | }, 171 | sceneData: fs.readFileSync(path.resolve(__dirname, 172 | `../test/fixtures/scene-${fixtureName}.json`)) 173 | })) 174 | } 175 | 176 | // Production 177 | if (process.env.NODE_ENV === 'production') { 178 | embedConfig.devtool = '' 179 | 180 | embedConfig.plugins.push( 181 | new BundleAnalyzerPlugin({ 182 | analyzerMode: 'static', 183 | generateStatsFile: true, 184 | reportFilename: 'stats-report.html', 185 | statsFilename: 'stats.json' 186 | }) 187 | ) 188 | } 189 | 190 | module.exports = embedConfig 191 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | let mainConfig = { 10 | mode: process.env.NODE_ENV || 'development', 11 | entry: { 12 | main: path.join(__dirname, '../src/main/index.js') 13 | }, 14 | externals: [ 15 | ...Object.keys(dependencies || {}) 16 | ], 17 | module: { 18 | rules: [ 19 | /*{ 20 | test: /\.(js)$/, 21 | enforce: 'pre', 22 | exclude: /node_modules/, 23 | use: { 24 | loader: 'eslint-loader', 25 | options: { 26 | formatter: require('eslint-friendly-formatter') 27 | } 28 | } 29 | },*/ 30 | { 31 | test: /\.js$/, 32 | use: 'babel-loader', 33 | exclude: /node_modules/ 34 | }, 35 | { 36 | test: /\.node$/, 37 | use: 'node-loader' 38 | } 39 | ] 40 | }, 41 | node: { 42 | __dirname: process.env.NODE_ENV !== 'production', 43 | __filename: process.env.NODE_ENV !== 'production' 44 | }, 45 | output: { 46 | filename: '[name].js', 47 | libraryTarget: 'commonjs2', 48 | path: path.join(__dirname, '../dist/electron') 49 | }, 50 | plugins: [ 51 | new webpack.NoEmitOnErrorsPlugin() 52 | ], 53 | resolve: { 54 | alias: { 55 | '@root': path.join(__dirname, '../'), 56 | '@main': path.join(__dirname, '../src/main'), 57 | '@renderer': path.join(__dirname, '../src/renderer') 58 | }, 59 | extensions: ['.js', '.json', '.node'] 60 | }, 61 | target: 'electron-main' 62 | } 63 | 64 | /** 65 | * Adjust mainConfig for development settings 66 | */ 67 | if (process.env.NODE_ENV !== 'production') { 68 | mainConfig.plugins.push( 69 | new webpack.DefinePlugin({ 70 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 71 | }) 72 | ) 73 | } 74 | 75 | /** 76 | * Adjust mainConfig for production settings 77 | */ 78 | if (process.env.NODE_ENV === 'production') { 79 | } 80 | 81 | module.exports = mainConfig 82 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/unit/coverage/** 2 | test/unit/*.js 3 | test/e2e/*.js 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true 10 | }, 11 | extends: [ 12 | 'standard', 13 | 'plugin:vue/base' 14 | ], 15 | globals: { 16 | __static: true 17 | }, 18 | plugins: [ 19 | 'html' 20 | ], 21 | 'rules': { 22 | // allow paren-less arrow functions 23 | 'arrow-parens': 0, 24 | // allow async-await 25 | 'generator-star-spacing': 0, 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 28 | // make unused vars generate a warning for development 29 | 'no-unused-vars': [ 30 | process.env.NODE_ENV === 'production' ? 'error' : 'warn', 31 | { 'vars': 'all', 'args': 'none', 'ignoreRestSiblings': true } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/embed/* 4 | build/* 5 | !build/icons 6 | build/icons/.DS_Store 7 | coverage 8 | node_modules/ 9 | npm-debug.log 10 | npm-debug.log.* 11 | thumbs.db 12 | !.gitkeep 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microbium 2 | 3 | > Organic creature drawing and simulating toy for macOS. 4 | 5 | ![Microbium](./screen_shots/screen_shot_04.jpg) 6 | 7 | 8 | ## Videos 9 | 10 | [**Ambient Microbium worlds** - GitHub Universe 2020](https://www.youtube.com/watch?v=XRV0yRLiC1g) – Audio + video performance 11 | 12 | [**Create kinetic simulations with Microbium** - GitHub Universe 2020](https://www.youtube.com/watch?v=RJtifEJ7mpE) – Short technical overview and tutorial 13 | 14 | 15 | ## App Downloads 16 | 17 | **[v1.0.0-beta-2 for macOS](https://microbiumapp.com/download/v1.0.0-beta-2)** – Released Spring 2020 18 | 19 | **[v1.0.0-beta-1 for macOS](https://microbiumapp.com/download/v1.0.0-beta-1)** – Released Fall 2019 20 | 21 | 22 | ## License 23 | 24 | Microbium (c) 2017-2025 by Ash Weeks 25 | 26 | Microbium is licensed under a 27 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 28 | 29 | You should have received a copy of the license along with this 30 | work. If not, see https://creativecommons.org/licenses/by-nc-sa/4.0/. 31 | 32 | 33 | ## Developing 34 | 35 | ### Build Setup 36 | 37 | **Install dependencies** 38 | `yarn install` 39 | 40 | **Run app in development mode** 41 | `yarn dev` 42 | 43 | **Build packaged app** 44 | `yarn build` 45 | 46 | **Run code style linter** 47 | `yarn lint` 48 | 49 | -------------------------------------------------------------------------------- /build/icons/microbium.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/build/icons/microbium.icns -------------------------------------------------------------------------------- /dist/electron/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/dist/electron/.gitkeep -------------------------------------------------------------------------------- /dist/embed/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/dist/embed/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Microbium", 3 | "version": "1.0.0-beta-3", 4 | "author": "Jay Weeks ", 5 | "description": "Microbiome drawing and simulating toy.", 6 | "private": true, 7 | "license": "CC-BY-NC-SA-4.0", 8 | "main": "./dist/electron/main.js", 9 | "scripts": { 10 | "dev": "node .electron-vue/dev-runner.js", 11 | "dev:embed": "cross-env BUILD_TARGET=embed SCENE_FIXTURE=001 node .electron-vue/dev-runner.js", 12 | "build": "sh ./.electron-vue/build.sh", 13 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 14 | "build:clean": "cross-env BUILD_TARGET=clean node ./.electron-vue/build-runner.js", 15 | "build:embed": "cross-env BUILD_TARGET=embed node ./.electron-vue/build-runner.js", 16 | "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src", 17 | "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src", 18 | "pack": "npm run pack:main && npm run pack:renderer", 19 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 20 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 21 | "test": "" 22 | }, 23 | "build": { 24 | "productName": "Microbium", 25 | "copyright": "Copyright © 2017-2020 Jay Weeks", 26 | "appId": "com.jayweeks.microbium", 27 | "fileAssociations": [ 28 | { 29 | "ext": "mcrbm", 30 | "name": "Microbium Scene", 31 | "role": "Editor", 32 | "isPackage": false 33 | } 34 | ], 35 | "directories": { 36 | "output": "build" 37 | }, 38 | "files": [ 39 | "dist/electron/**/*" 40 | ], 41 | "dmg": { 42 | "contents": [ 43 | { 44 | "x": 410, 45 | "y": 150, 46 | "type": "link", 47 | "path": "/Applications" 48 | }, 49 | { 50 | "x": 130, 51 | "y": 150, 52 | "type": "file" 53 | } 54 | ] 55 | }, 56 | "mac": { 57 | "icon": "build/icons/microbium.icns", 58 | "extendInfo": { 59 | "NSRequiresAquaSystemAppearance": false, 60 | "NSSupportsAutomaticGraphicsSwitching": false 61 | } 62 | } 63 | }, 64 | "dependencies": { 65 | "@babel/runtime": "^7.8.4", 66 | "@microbium/electron-recorder": "^3.3.0", 67 | "@microbium/regl": "^1.3.13-1", 68 | "camera-picking-ray": "^1.0.1", 69 | "cardinal-spline": "^0.0.1", 70 | "colr": "^1.2.2", 71 | "electron-log": "^4.0.6", 72 | "electron-store": "^5.1.0", 73 | "events": "^3.1.0", 74 | "fs-extra": "^8.1.0", 75 | "gl-matrix": "^3.1.0", 76 | "glsl-blend": "^1.0.3", 77 | "glsl-fast-gaussian-blur": "^1.0.2", 78 | "glsl-hash-blur": "^1.0.3", 79 | "glsl-inject-defines": "^1.0.3", 80 | "glsl-lut": "^1.1.1", 81 | "glsl-noise": "^0.0.0", 82 | "glsl-random": "^0.0.5", 83 | "leapjs": "^0.6.4", 84 | "lodash": "^4.17.15", 85 | "particulate": "^0.3.3", 86 | "pngjs": "^3.4.0", 87 | "ray-plane-intersection": "^1.0.0", 88 | "recursive-iterator": "^3.3.0", 89 | "regl-line-builder": "^0.8.2", 90 | "vue": "^2.6.11", 91 | "vue-electron": "^1.0.6", 92 | "vue-range-slider": "^0.6.0", 93 | "vue-router": "^3.1.5", 94 | "vuex": "^3.1.2", 95 | "webmidi": "^2.5.1" 96 | }, 97 | "devDependencies": { 98 | "@babel/core": "^7.8.4", 99 | "@babel/plugin-proposal-class-properties": "^7.8.3", 100 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 101 | "@babel/plugin-transform-runtime": "^7.8.3", 102 | "@babel/preset-env": "^7.8.4", 103 | "babel-eslint": "^10.0.3", 104 | "babel-loader": "^8.0.6", 105 | "cfonts": "^2.5.2", 106 | "chalk": "^3.0.0", 107 | "copy-webpack-plugin": "^5.1.1", 108 | "cross-env": "^7.0.0", 109 | "css-loader": "^3.4.2", 110 | "del": "^5.1.0", 111 | "devtron": "^1.4.0", 112 | "electron": "^8.0.0", 113 | "electron-builder": "^22.3.2", 114 | "electron-debug": "^3.0.1", 115 | "electron-devtools-installer": "^2.2.4", 116 | "eslint": "^6.8.0", 117 | "eslint-config-standard": "^14.1.0", 118 | "eslint-friendly-formatter": "^4.0.1", 119 | "eslint-loader": "^3.0.3", 120 | "eslint-plugin-html": "^6.0.0", 121 | "eslint-plugin-import": "^2.20.1", 122 | "eslint-plugin-node": "^11.0.0", 123 | "eslint-plugin-promise": "^4.2.1", 124 | "eslint-plugin-standard": "^4.0.1", 125 | "eslint-plugin-vue": "^6.1.2", 126 | "file-loader": "^5.0.2", 127 | "glslify-loader": "^2.0.0", 128 | "html-webpack-plugin": "^3.2.0", 129 | "inject-loader": "^4.0.1", 130 | "mini-css-extract-plugin": "^0.9.0", 131 | "multispinner": "^0.2.1", 132 | "node-loader": "^0.6.0", 133 | "node-sass": "^4.13.1", 134 | "raw-loader": "^4.0.0", 135 | "sass-loader": "^8.0.2", 136 | "style-loader": "^1.1.3", 137 | "url-loader": "^3.0.0", 138 | "vue-html-loader": "^1.2.4", 139 | "vue-loader": "^15.8.3", 140 | "vue-style-loader": "^4.1.2", 141 | "vue-svg-loader": "^0.15.0", 142 | "vue-template-compiler": "^2.6.11", 143 | "webpack": "^4.41.5", 144 | "webpack-bundle-analyzer": "^3.6.0", 145 | "webpack-dev-server": "^3.10.2", 146 | "webpack-hot-middleware": "^2.25.0" 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /screen_shots/screen_shot_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/screen_shots/screen_shot_04.jpg -------------------------------------------------------------------------------- /src/embed.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Microbium 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Microbium 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 13 | 14 |
15 | 16 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/exporters/html.js: -------------------------------------------------------------------------------- 1 | import { 2 | copyFile, 3 | readFile, 4 | writeFile 5 | } from 'fs-extra' 6 | import { 7 | basename, 8 | dirname, 9 | join as pathJoin 10 | } from 'path' 11 | import log from 'electron-log' 12 | import { template as compileTemplate } from 'lodash' 13 | 14 | import { version } from '@root/package.json' 15 | 16 | const TEMPLATE_SRC = 'exporter-templates/html.ejs' 17 | const API_VERSION = version 18 | const PEP_VERSION = '0.4.3' 19 | 20 | const cachedTemplates = {} 21 | function getTemplate (srcPath) { 22 | if (cachedTemplates[srcPath]) return cachedTemplates[srcPath] 23 | 24 | const fullPath = pathJoin(global.__static, srcPath) 25 | return (cachedTemplates[srcPath] = readFile(fullPath) 26 | .then((str) => compileTemplate(str))) 27 | } 28 | 29 | function toTitleCase (str) { 30 | return str.replace(/\w\S*/g, 31 | (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()) 32 | } 33 | 34 | function resolveDataAssets (destPath, sceneData) { 35 | const { controls } = sceneData 36 | const textureAssetFiles = [] 37 | const exportedAssets = {} 38 | 39 | textureAssetFiles.push(controls.postEffects.lut.textureFile) 40 | controls.styles.forEach((style) => { 41 | textureAssetFiles.push(style.lineAlphaMapFile, style.fillAlphaMapFile) 42 | }) 43 | 44 | const assetResolutions = textureAssetFiles.map((textureFile) => { 45 | if (!textureFile) return null 46 | 47 | const srcName = textureFile.name 48 | const srcPath = textureFile.path 49 | 50 | const exportPath = `${srcName}` 51 | const assetDestPath = pathJoin(dirname(destPath), exportPath) 52 | textureFile.path = exportPath 53 | 54 | if (exportedAssets[srcPath] != null) return 55 | exportedAssets[srcPath] = 1 56 | 57 | return copyFile(srcPath, assetDestPath) 58 | }).filter(Boolean) 59 | 60 | return Promise.all(assetResolutions).then(() => { 61 | return sceneData 62 | }) 63 | } 64 | 65 | export function exportSceneHTML (destPath, sceneData) { 66 | return Promise.all([ 67 | resolveDataAssets(destPath, sceneData), 68 | getTemplate(TEMPLATE_SRC) 69 | ]).then(([resolvedSceneData, template]) => { 70 | const subTitle = toTitleCase( 71 | basename(destPath, '.html').replace(/[-_]+/g, ' ')) 72 | const rawSceneData = JSON.stringify(resolvedSceneData) 73 | const backgroundColor = '#222222' 74 | 75 | return template({ 76 | API_VERSION, 77 | PEP_VERSION, 78 | subTitle, 79 | backgroundColor, 80 | rawSceneData 81 | }) 82 | }).then((htmlOut) => writeFile(destPath, htmlOut)) 83 | .catch((err) => { 84 | log.error(err) 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /src/main/index.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Set environment for development 11 | // process.env.NODE_ENV = 'development' 12 | 13 | // Install `electron-debug` with `devtron` 14 | const { app } = require('electron') 15 | const electronDebug = require('electron-debug') 16 | const installExtension = require('electron-devtools-installer') 17 | 18 | electronDebug({ showDevTools: true }) 19 | app.on('ready', () => { 20 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 21 | .then((name) => console.log(`Added Extension: ${name}`)) 22 | .catch(err => { 23 | console.log(err) 24 | }) 25 | }) 26 | 27 | // Require `main` process to boot app 28 | require('./index') 29 | -------------------------------------------------------------------------------- /src/main/io/socket.js: -------------------------------------------------------------------------------- 1 | import { createSocket } from 'dgram' 2 | 3 | export function createMessageSocket (port, address) { 4 | const socket = createSocket('udp4') 5 | 6 | function send (message) { 7 | socket.send(serializeMessage(message), port, address) 8 | } 9 | 10 | return { 11 | send 12 | } 13 | } 14 | 15 | function serializeMessage (message) { 16 | switch (typeof message) { 17 | case 'string': 18 | return message 19 | case 'object': 20 | return JSON.stringify(message) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/utils/platform.js: -------------------------------------------------------------------------------- 1 | import { release } from 'os' 2 | 3 | export function isOSX () { 4 | return process.platform === 'darwin' 5 | } 6 | 7 | export function isHighSierra () { 8 | return process.platform === 'darwin' && 9 | Number(release().split('.')[0]) >= 17 10 | } 11 | -------------------------------------------------------------------------------- /src/main/utils/window.js: -------------------------------------------------------------------------------- 1 | const { round } = Math 2 | 3 | export function fitRect (rect, { padding, aspect, alignX, alignY }) { 4 | const displayAspect = rect.width / rect.height 5 | const transform = {} 6 | 7 | if (displayAspect > aspect) { 8 | transform.height = rect.height - padding * 2 9 | transform.width = round(transform.height * aspect) 10 | transform.x = round((rect.width - transform.width) * alignX) 11 | transform.y = padding 12 | } else { 13 | transform.width = rect.width - padding * 2 14 | transform.height = round(transform.width / aspect) 15 | transform.x = padding 16 | transform.y = round((rect.height - transform.height) * alignY) 17 | } 18 | 19 | return transform 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/MicrobiumApp.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /src/renderer/MicrobiumEmbed.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 38 | 39 | 54 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/backgrounds/range-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/backgrounds/range-fill.png -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-mono/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fira Mono'; 3 | src: local("Fira Mono Regular"), 4 | url("./ttf/FiraMono-Regular.ttf") format("truetype"); 5 | font-weight: 400; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'Fira Mono'; 11 | src: local("Fira Mono Medium"), 12 | url("./ttf/FiraMono-Medium.ttf") format("truetype"); 13 | font-weight: 500; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'Fira Mono'; 19 | src: local("Fira Mono Bold"), 20 | url("./ttf/FiraMono-Bold.ttf") format("truetype"); 21 | font-weight: 600; 22 | font-style: normal; 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-mono/ttf/FiraMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-mono/ttf/FiraMono-Bold.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-mono/ttf/FiraMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-mono/ttf/FiraMono-Medium.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-mono/ttf/FiraMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-mono/ttf/FiraMono-Regular.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fira Sans'; 3 | src: local("Fira Sans ExtraLight"), 4 | url("./ttf/FiraSans-ExtraLight.ttf") format("truetype"); 5 | font-weight: 200; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'Fira Sans'; 11 | src: local("Fira Sans Light"), 12 | url("./ttf/FiraSans-Light.ttf") format("truetype"); 13 | font-weight: 300; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'Fira Sans'; 19 | src: local("Fira Sans Regular"), 20 | url("./ttf/FiraSans-Regular.ttf") format("truetype"); 21 | font-weight: 400; 22 | font-style: normal; 23 | } 24 | 25 | @font-face { 26 | font-family: 'Fira Sans'; 27 | src: local("Fira Sans Medium"), 28 | url("./ttf/FiraSans-Medium.ttf") format("truetype"); 29 | font-weight: 500; 30 | font-style: normal; 31 | } 32 | 33 | @font-face { 34 | font-family: 'Fira Sans'; 35 | src: local("Fira Sans Bold"), 36 | url("./ttf/FiraSans-Bold.ttf") format("truetype"); 37 | font-weight: 600; 38 | font-style: normal; 39 | } 40 | 41 | @font-face { 42 | font-family: 'Fira Sans'; 43 | src: local("Fira Sans ExtraBold"), 44 | url("./ttf/FiraSans-ExtraBold.ttf") format("truetype"); 45 | font-weight: 700; 46 | font-style: normal; 47 | } 48 | 49 | @font-face { 50 | font-family: 'Fira Sans'; 51 | src: local("Fira Sans Heavy"), 52 | url("./ttf/FiraSans-Heavy.ttf") format("truetype"); 53 | font-weight: 800; 54 | font-style: normal; 55 | } 56 | -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Bold.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/ttf/FiraSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-sans/ttf/FiraSans-ExtraBold.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/ttf/FiraSans-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-sans/ttf/FiraSans-ExtraLight.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Heavy.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Light.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Medium.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/fonts/fira-sans/ttf/FiraSans-Regular.ttf -------------------------------------------------------------------------------- /src/renderer/assets/icons/app-mono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/icons/app-mono.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/icons/app.png -------------------------------------------------------------------------------- /src/renderer/assets/icons/constraints.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/draw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/effects.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/forces.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/geometry.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/select.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/styles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/renderer/assets/icons/viewport.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/renderer/assets/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/images/background.png -------------------------------------------------------------------------------- /src/renderer/assets/images/textures/alpha-hairy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/images/textures/alpha-hairy.jpg -------------------------------------------------------------------------------- /src/renderer/assets/images/textures/alpha-hatchy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/images/textures/alpha-hatchy.jpg -------------------------------------------------------------------------------- /src/renderer/assets/images/textures/ground-mud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/images/textures/ground-mud.jpg -------------------------------------------------------------------------------- /src/renderer/assets/images/textures/watercolor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/src/renderer/assets/images/textures/watercolor.jpg -------------------------------------------------------------------------------- /src/renderer/components/Viewer.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 64 | 65 | 147 | -------------------------------------------------------------------------------- /src/renderer/components/display/Icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 64 | -------------------------------------------------------------------------------- /src/renderer/components/editor/Cursor.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 48 | 49 | 111 | -------------------------------------------------------------------------------- /src/renderer/components/editor/QuickTool.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 34 | 35 | 64 | -------------------------------------------------------------------------------- /src/renderer/components/input/Button.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | 52 | -------------------------------------------------------------------------------- /src/renderer/components/input/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 66 | 67 | 108 | -------------------------------------------------------------------------------- /src/renderer/components/input/Color.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 48 | -------------------------------------------------------------------------------- /src/renderer/components/input/File.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 63 | -------------------------------------------------------------------------------- /src/renderer/components/input/FileRefresh.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 56 | 57 | 96 | -------------------------------------------------------------------------------- /src/renderer/components/input/Range.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 81 | -------------------------------------------------------------------------------- /src/renderer/components/input/Select.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 55 | -------------------------------------------------------------------------------- /src/renderer/components/input/Text.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 49 | 50 | 104 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Camera.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 81 | 82 | 137 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Constraint.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 61 | 62 | 119 | -------------------------------------------------------------------------------- /src/renderer/components/palette/ConstraintList.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 41 | 42 | 71 | -------------------------------------------------------------------------------- /src/renderer/components/palette/ConstraintPreview.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 48 | 49 | 119 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Force.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 83 | 84 | 151 | -------------------------------------------------------------------------------- /src/renderer/components/palette/ForceList.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 42 | 43 | 72 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Group.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 166 | 167 | 231 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Modes.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 42 | 43 | 65 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Section.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | 44 | 62 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Simulation.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 28 | 29 | 65 | -------------------------------------------------------------------------------- /src/renderer/components/palette/StyleList.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 43 | 44 | 75 | -------------------------------------------------------------------------------- /src/renderer/components/palette/StylePreview.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 55 | 56 | 181 | -------------------------------------------------------------------------------- /src/renderer/components/palette/Viewport.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 66 | 67 | 112 | -------------------------------------------------------------------------------- /src/renderer/constants/color-palettes.js: -------------------------------------------------------------------------------- 1 | export const UI_PALETTE = { 2 | BACK_PRIMARY: '#92D9E7', 3 | BACK_SECONDARY: '#A7DBD8', 4 | BACK_TERTIARY: '#FAFAFA', 5 | HI_PRIMARY: '#41EDC1', 6 | HI_SECONDARY: '#556270' 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/constants/line-styles.js: -------------------------------------------------------------------------------- 1 | export const LINE_WIDTH = { 2 | ULTRA_THIN: 0.5, 3 | THIN: 1, 4 | REGULAR: 2, 5 | THICK: 4, 6 | FAT: 8 7 | } 8 | 9 | export const LINE_WIDTH_KEYS = [ 10 | 'ULTRA_THIN', 11 | 'THIN', 12 | 'REGULAR', 13 | 'THICK', 14 | 'FAT' 15 | ] 16 | -------------------------------------------------------------------------------- /src/renderer/constants/scene-format.js: -------------------------------------------------------------------------------- 1 | export const SERIALIZE_KEYS_MAP = { 2 | am: 'activeMode', 3 | ap: 'activePalettes', 4 | af: 'alphaFactor', 5 | ai: 'lineAlphaFuncIndex', 6 | au: 'alphaFunctions', 7 | at: 'alphaTextureIndex', 8 | bn: 'banding', 9 | bs: 'bandStep', 10 | bl: 'bloom', 11 | bp: 'blurPasses', 12 | be: 'blurStep', 13 | bu: 'bufferScale', 14 | cl: 'clear', 15 | ch: 'colorHex', 16 | cs: 'colorShift', 17 | ci: 'connectedIndices', 18 | cg: 'constraintGroups', 19 | cn: 'constraintIndex', 20 | ct: 'controls', 21 | cu: 'curve', 22 | df: 'dashFunction', 23 | ed: 'edges', 24 | ec: 'engineCadence', 25 | ef: 'engineFlex', 26 | fc: 'forces', 27 | go: 'geometry', 28 | ii: 'index', 29 | is: 'indices', 30 | im: 'inputModTypeIndex', 31 | iy: 'intensity', 32 | if: 'intensityFactor', 33 | it: 'intensityTypeIndex', 34 | ic: 'isClosed', 35 | io: 'isComplete', 36 | ll: 'lineLengths', 37 | lt: 'lineTool', 38 | la: 'linkSizeAvg', 39 | md: 'modifiers', 40 | na: 'name', 41 | no: 'noise', 42 | of: 'offset', 43 | pa: 'polarAngle', 44 | pi: 'polarIterations', 45 | po: 'polarOffset', 46 | py: 'positionTypeIndex', 47 | pf: 'postEffects', 48 | rd: 'radius', 49 | sc: 'scale', 50 | sl: 'segMaxLength', 51 | sg: 'segments', 52 | sn: 'segMinLength', 53 | st: 'slipTolerance', 54 | sa: 'strokeAlpha', 55 | sk: 'strokeColor', 56 | sw: 'strokeWidth', 57 | sm: 'strokeWidthMod', 58 | su: 'strokeWidthModulations', 59 | si: 'styleIndex', 60 | ss: 'styles', 61 | sd: 'subDivisions', 62 | ti: 'textureIndex', 63 | tk: 'thickness', 64 | ta: 'tintAlpha', 65 | th: 'tintHex', 66 | te: 'typeIndex', 67 | us: 'useScreenTintFunc', 68 | vt: 'vertices', 69 | vp: 'viewport' 70 | } 71 | -------------------------------------------------------------------------------- /src/renderer/constants/types.js: -------------------------------------------------------------------------------- 1 | export const MODE_TYPES = [ 2 | { 3 | id: 'draw', 4 | name: 'Draw' 5 | }, 6 | { 7 | id: 'select', 8 | name: 'Select' 9 | } 10 | ] 11 | 12 | export const PALETTE_TYPES = [ 13 | { 14 | id: 'tool', 15 | name: 'Tools / Controllers' 16 | }, 17 | { 18 | id: 'viewport', 19 | name: 'World / Simulation' 20 | }, 21 | { 22 | id: 'forces', 23 | name: 'Simulation Forces' 24 | }, 25 | { 26 | id: 'constraints', 27 | name: 'Constraint Groups' 28 | }, 29 | { 30 | id: 'styles', 31 | name: 'Style Layers' 32 | }, 33 | { 34 | id: 'effects', 35 | name: 'Visual Effects' 36 | } 37 | ] 38 | 39 | export const PALETTE_LAYOUTS = [ 40 | { 41 | id: 'narrow', 42 | name: 'Narrow' 43 | }, 44 | { 45 | id: 'wide', 46 | name: 'Wide' 47 | } 48 | ] 49 | 50 | export const INPUT_MOD_TYPES = [ 51 | { 52 | index: 0, 53 | name: 'No' 54 | }, 55 | { 56 | index: 1, 57 | name: 'Velocity' 58 | }, 59 | { 60 | index: 2, 61 | name: 'Pen Pressure' 62 | }, 63 | { 64 | index: 3, 65 | name: 'Scroll Wheel' 66 | } 67 | ] 68 | 69 | export const FORCE_TYPES = [ 70 | { 71 | index: 0, 72 | name: 'Attractor / Repulsor' 73 | }, 74 | { 75 | index: 1, 76 | name: 'Rotator' 77 | } 78 | ] 79 | 80 | export const FORCE_POSITION_TYPES = [ 81 | { 82 | index: 0, 83 | name: 'Static' 84 | }, 85 | { 86 | index: 1, 87 | name: 'Cursor' 88 | }, 89 | { 90 | index: 2, 91 | name: 'Hand' 92 | } 93 | ] 94 | 95 | export const FORCE_INTENSITY_TYPES = [ 96 | { 97 | index: 0, 98 | name: 'Static' 99 | }, 100 | { 101 | index: 1, 102 | name: 'Ebb and Flow' 103 | }, 104 | { 105 | index: 2, 106 | name: 'Velocity' 107 | } 108 | // { 109 | // index: 3, 110 | // name: 'Hand Proximity' 111 | // } 112 | ] 113 | 114 | export const CONSTRAINT_TYPES = [ 115 | { 116 | index: 0, 117 | name: 'Pin' 118 | }, 119 | { 120 | index: 1, 121 | name: 'Stick' 122 | }, 123 | { 124 | index: 2, 125 | name: 'Engine' 126 | } 127 | ] 128 | -------------------------------------------------------------------------------- /src/renderer/core/cameras.js: -------------------------------------------------------------------------------- 1 | import { vec2, vec3, mat4 } from 'gl-matrix' 2 | import { radialPosition } from '@renderer/utils/math' 3 | 4 | export function createCameras (tasks, state, renderer) { 5 | const { regl } = renderer 6 | 7 | const scratchVec3A = vec3.create() 8 | const scratchVec3B = vec3.create() 9 | 10 | const baseUniforms = { 11 | viewResolution: regl.prop('viewResolution'), 12 | viewOffset: regl.prop('viewOffset'), 13 | viewScale: regl.prop('viewScale') 14 | } 15 | 16 | const sceneOrtho = (() => { 17 | const view = mat4.create() 18 | const projection = mat4.create() 19 | 20 | const setup = regl({ 21 | uniforms: { 22 | ...baseUniforms, 23 | view: (context, props) => { 24 | const { viewOffset, viewScale } = props 25 | const offset3 = vec3.set(scratchVec3A, viewOffset[0], viewOffset[1], 0) 26 | const scale3 = vec3.set(scratchVec3B, viewScale, viewScale, viewScale) 27 | mat4.fromTranslation(view, offset3) 28 | mat4.scale(view, view, scale3) 29 | return view 30 | }, 31 | projection: () => projection, 32 | projectionMode: 0 33 | } 34 | }) 35 | 36 | const update = () => { 37 | } 38 | 39 | const updateProjection = (size) => { 40 | const w = size[0] / 4 41 | const h = size[1] / 4 42 | mat4.ortho(projection, -w, w, h, -h, 0, 2000) 43 | } 44 | 45 | return { 46 | view, 47 | projection, 48 | lineScaleFactor: 1, 49 | shouldAdjustThickness: true, 50 | setup, 51 | update, 52 | updateProjection 53 | } 54 | })() 55 | 56 | const scenePerspective = (() => { 57 | const view = mat4.create() 58 | const projection = mat4.create() 59 | const colorMasks = { 60 | none: [true, true, true, true], 61 | left: [false, true, true, true], 62 | right: [true, false, false, true] 63 | } 64 | 65 | // FIXME: Inverted up vector 66 | const polarPosition = vec2.create() 67 | const eye = vec3.create() 68 | const eyeTarget = vec3.create() 69 | const eyeOffset = vec3.create() 70 | const eyeStereoOffset = vec3.create() 71 | const center = vec3.create() 72 | const up = vec3.set(vec3.create(), 0, -1, 0) 73 | 74 | const setup = regl({ 75 | uniforms: { 76 | ...baseUniforms, 77 | view: (context, props) => { 78 | updateEyeStereo(props) 79 | vec3.set(center, 0, 0, state.controls.camera.depthOffset + 100) 80 | vec3.add(eyeOffset, eye, eyeStereoOffset) 81 | mat4.lookAt(view, eyeOffset, center, up) 82 | return view 83 | }, 84 | projection: () => projection, 85 | projectionMode: 1 86 | }, 87 | colorMask: (context, props) => { 88 | return props.eyeMask 89 | ? colorMasks[props.eyeMask] 90 | : colorMasks.none 91 | } 92 | }) 93 | 94 | // TODO: Check stereo offset calculation 95 | const updateEyeStereo = (props) => { 96 | const { stereoDistance } = props 97 | const isStereo = props.eyeMask && props.eyeMask !== 'none' 98 | 99 | if (isStereo) { 100 | vec3.cross(eyeStereoOffset, eye, up) 101 | vec3.normalize(eyeStereoOffset, eyeStereoOffset) 102 | } else { 103 | vec3.set(eyeStereoOffset, 0, 0, 0) 104 | } 105 | 106 | switch (props.eyeMask) { 107 | case 'left': 108 | vec3.scale(eyeStereoOffset, eyeStereoOffset, stereoDistance) 109 | break 110 | case 'right': 111 | vec3.scale(eyeStereoOffset, eyeStereoOffset, -stereoDistance) 112 | break 113 | } 114 | } 115 | 116 | const updateEye = () => { 117 | const cameraState = state.controls.camera 118 | const { polarOffset, polarAngle, depthOffset, tweenFactor } = cameraState 119 | 120 | polarPosition[1] = polarOffset * polarOffset 121 | radialPosition(eyeTarget, polarPosition, 122 | polarAngle / 180 * Math.PI) 123 | eyeTarget[2] = -(depthOffset * depthOffset) 124 | 125 | eye[0] += (eyeTarget[0] - eye[0]) * tweenFactor 126 | eye[1] += (eyeTarget[1] - eye[1]) * tweenFactor 127 | eye[2] += (eyeTarget[2] - eye[2]) * tweenFactor 128 | } 129 | 130 | const update = () => { 131 | updateEye() 132 | } 133 | 134 | // TODO: Add tween to fov change 135 | let currentAspect = null 136 | let currentFov = null 137 | const updateProjection = (size, fov) => { 138 | const aspect = size[0] / size[1] 139 | const fovRads = fov / 180 * Math.PI 140 | if (currentAspect === aspect && currentFov === fov) return 141 | 142 | currentAspect = aspect 143 | currentFov = fov 144 | mat4.perspective(projection, fovRads, aspect, 0.01, 10000) 145 | } 146 | 147 | return { 148 | view, 149 | projection, 150 | lineScaleFactor: 0, 151 | shouldAdjustThickness: false, 152 | setup, 153 | update, 154 | updateProjection 155 | } 156 | })() 157 | 158 | // TODO: Improve determining active camera 159 | const getActiveCamera = () => { 160 | const { isRunning } = state.simulation 161 | const { enabled } = state.controls.camera 162 | return (isRunning && enabled) ? scenePerspective : sceneOrtho 163 | } 164 | 165 | tasks.add((event) => { 166 | const { size } = state.viewport 167 | const { fov } = state.controls.camera 168 | sceneOrtho.updateProjection(size) 169 | scenePerspective.updateProjection(size, fov) 170 | }, 'resize') 171 | 172 | tasks.registerResponder('cameras.updateProjection', null, () => { 173 | const { size } = state.viewport 174 | const { fov } = state.controls.camera 175 | scenePerspective.updateProjection(size, fov) 176 | }) 177 | 178 | tasks.registerResponder('cameras.scene', null, () => { 179 | return getActiveCamera() 180 | }) 181 | 182 | return { 183 | get scene () { 184 | return getActiveCamera() 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/renderer/core/renderer.js: -------------------------------------------------------------------------------- 1 | import createREGL from 'regl' 2 | import { vec2 } from 'gl-matrix' 3 | 4 | import { createTextureManager } from '@renderer/utils/texture' 5 | import { createPostBuffers } from '@renderer/utils/fbo' 6 | import { 7 | createGlLogger, 8 | getGpuInfo 9 | } from '@renderer/utils/gl-logger' 10 | 11 | import { 12 | createDrawBanding, 13 | createDrawEdges, 14 | createDrawFeedback, 15 | createDrawGaussBlur, 16 | createDrawRect, 17 | createDrawScreen, 18 | createDrawTexture 19 | } from '@renderer/draw/commands/screen-space' 20 | 21 | const DEBUG_LOG_GPU = false 22 | const DEBUG_TRACK_GL = false 23 | 24 | export function createRenderer (tasks, state) { 25 | const canvas = document.createElement('canvas') 26 | const regl = createREGL({ 27 | canvas, 28 | extensions: [ 29 | // 'ANGLE_instanced_arrays', 30 | 'OES_standard_derivatives', 31 | 'OES_element_index_uint' 32 | ], 33 | attributes: { 34 | antialias: false, 35 | preserveDrawingBuffer: false, 36 | premultipliedAlpha: false, 37 | alpha: false, 38 | powerPreference: 'high-performance' 39 | } 40 | }) 41 | 42 | const { resolutionMax } = state.viewport 43 | const { maxRenderbufferSize } = regl.limits 44 | vec2.set(resolutionMax, 45 | maxRenderbufferSize, maxRenderbufferSize) 46 | 47 | const textures = createTextureManager(regl) 48 | const postBuffers = createPostBuffers(regl, 49 | 'full', 'fullExport', 50 | 'banding', 'edges', 51 | 'bloom', 'blurA', 'blurB') 52 | 53 | const commands = { 54 | drawBanding: createDrawBanding(regl, postBuffers), 55 | drawEdges: createDrawEdges(regl, postBuffers), 56 | drawFeedback: createDrawFeedback(regl, postBuffers, textures), 57 | drawScreen: createDrawScreen(regl, postBuffers), 58 | drawGaussBlur: createDrawGaussBlur(regl, postBuffers), 59 | drawRect: createDrawRect(regl, postBuffers), 60 | drawTexture: createDrawTexture(regl, postBuffers) 61 | } 62 | const logger = DEBUG_TRACK_GL 63 | ? createGlLogger(regl._gl) 64 | : null 65 | 66 | const info = getGpuInfo(regl._gl) 67 | state.renderer.info = info 68 | 69 | if (DEBUG_LOG_GPU) { 70 | console.log('gpu', info) 71 | } 72 | 73 | tasks.defer((containers) => { 74 | containers.scene.appendChild(canvas) 75 | return Promise.resolve() 76 | }, 'inject') 77 | 78 | tasks.add((event) => { 79 | const { resolution } = state.viewport 80 | canvas.width = resolution[0] 81 | canvas.height = resolution[1] 82 | }, 'resize') 83 | 84 | return { 85 | regl, 86 | info, 87 | canvas, 88 | textures, 89 | postBuffers, 90 | commands, 91 | logger 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/renderer/draw/routines/geometry.js: -------------------------------------------------------------------------------- 1 | import { vec2 } from 'gl-matrix' 2 | import { UI_PALETTE } from '@renderer/constants/color-palettes' 3 | import { map, flatten2 } from '@renderer/utils/array' 4 | import { clamp, mapLinear } from '@renderer/utils/math' 5 | import { arc } from './primitive' 6 | 7 | export function drawGeometry (state, contexts, segmentStart, segmentCount) { 8 | drawSegments(state, contexts, segmentStart, segmentCount) 9 | } 10 | 11 | export function drawSegments (state, contexts, segmentStart_, segmentCount_) { 12 | const { segments, vertices } = state.geometry 13 | const { styles } = state.controls 14 | const segmentStart = segmentStart_ || 0 15 | const segmentCount = segmentCount_ || segments.length 16 | if (!segments.length) return 17 | 18 | for (let s = segmentStart; s < segmentCount; s++) { 19 | const segment = segments[s] 20 | const { 21 | depths, indices, isClosed, styleIndex, 22 | strokeWidth, strokeWidthModulations, strokeColor, strokeAlpha, 23 | fillColor, fillAlpha, 24 | linkSizeAvg 25 | } = segment 26 | 27 | const count = isClosed ? indices.length - 1 : indices.length 28 | if (count < 2) continue 29 | 30 | const { ctx } = contexts[styleIndex] 31 | const { 32 | strokeWidthMod, 33 | curveSubDivisions, 34 | curveSegMinLength, 35 | curveSegMaxLength 36 | } = styles[styleIndex] 37 | const curvePrecision = computeCurvePrecision( 38 | curveSubDivisions, curveSegMinLength, curveSegMaxLength, linkSizeAvg) 39 | 40 | const points = map(indices, (i) => vertices[i]) 41 | if (isClosed) points.pop() 42 | 43 | const pointsFlat = flatten2(points) 44 | 45 | ctx.globalAlpha = strokeAlpha 46 | ctx.strokeStyle = strokeColor 47 | 48 | ctx.beginPath() 49 | if (curvePrecision <= 1) { 50 | ctx.polyline(pointsFlat, depths, 51 | strokeWidth, strokeWidthMod, strokeWidthModulations, 52 | isClosed) 53 | } else { 54 | ctx.curve(pointsFlat, depths, 55 | strokeWidth, strokeWidthMod, strokeWidthModulations, 56 | 0.5, curvePrecision, isClosed) 57 | } 58 | 59 | if (isClosed) { 60 | ctx.closePath() 61 | ctx.globalAlpha = fillAlpha 62 | ctx.fillStyle = fillColor 63 | ctx.fill() 64 | } 65 | 66 | ctx.globalAlpha = strokeAlpha 67 | ctx.stroke() 68 | } 69 | } 70 | 71 | // TODO: Improve curve precision mapping 72 | function computeCurvePrecision (subDivisions, segMinLength, segMaxLength, linkSizeAvg) { 73 | return Math.round(subDivisions * clamp(0, 1, 74 | mapLinear(segMinLength, segMaxLength * 10, 0, 1, linkSizeAvg))) 75 | } 76 | 77 | // OPTIM: Minimize state stack changes 78 | export function drawFocus (state, ctx, index) { 79 | const { vertices } = state.geometry 80 | const { scale } = state.viewport 81 | const point = vertices[index] 82 | if (!point) return 83 | 84 | const scaleInv = 1 / scale 85 | const pointRad = vec2.length(point) 86 | const innerRad = 6 * scaleInv 87 | 88 | ctx.save() 89 | ctx.globalAlpha = 0.9 90 | ctx.lineWidth = 1.2 * scaleInv 91 | ctx.strokeStyle = UI_PALETTE.HI_PRIMARY 92 | 93 | ctx.beginPath() 94 | arc(ctx, 95 | point[0], point[1], innerRad, 96 | 0, Math.PI * 2 * 5 / 6, 97 | false, Math.PI * 0.3) 98 | ctx.closePath() 99 | ctx.stroke() 100 | 101 | ctx.rotate(Math.atan2(point[1], point[0]), 'z') 102 | ctx.globalAlpha = 0.35 103 | ctx.lineWidth = 1 * scaleInv 104 | ctx.strokeStyle = UI_PALETTE.BACK_TERTIARY 105 | 106 | ctx.beginPath() 107 | ctx.moveTo(pointRad - 120, 0) 108 | ctx.lineTo(pointRad + 120, 0) 109 | ctx.stroke() 110 | 111 | ctx.beginPath() 112 | ctx.moveTo(pointRad, -40) 113 | ctx.lineTo(pointRad, +40) 114 | ctx.stroke() 115 | 116 | ctx.restore() 117 | } 118 | 119 | // OPTIM: Maybe use GL points to render proximate points 120 | export function drawFocusProximate (state, ctx, indices, ignoreIndex) { 121 | const { vertices } = state.geometry 122 | const { scale } = state.viewport 123 | // TODO: Maybe sort by distance factor 124 | const count = Math.min(20, indices.length) 125 | 126 | for (let i = 0; i < count; i++) { 127 | const { index, factor } = indices[i] 128 | const point = vertices[index] 129 | if (index === ignoreIndex || !point) continue 130 | 131 | const scaleInv = 1 / scale 132 | const innerRad = (3 * (1 - factor) + 3) * scaleInv 133 | 134 | ctx.globalAlpha = 0.6 * (1 - factor) + 0.2 135 | ctx.lineWidth = 0.75 * scaleInv 136 | ctx.strokeStyle = UI_PALETTE.BACK_TERTIARY 137 | 138 | ctx.beginPath() 139 | arc(ctx, 140 | point[0], point[1], innerRad, 141 | 0, Math.PI * 2 * 0.75, 142 | false, Math.PI * 0.45) 143 | ctx.closePath() 144 | ctx.stroke() 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/renderer/draw/routines/origin.js: -------------------------------------------------------------------------------- 1 | import { UI_PALETTE } from '@renderer/constants/color-palettes' 2 | import { arc } from './primitive' 3 | 4 | const { PI } = Math 5 | 6 | export function drawOrigin (state, ctx) { 7 | const size = 6 8 | 9 | ctx.globalAlpha = 0.95 10 | ctx.strokeStyle = UI_PALETTE.BACK_TERTIARY 11 | ctx.lineWidth = 1.5 12 | 13 | ctx.beginPath() 14 | arc(ctx, 15 | 0, 0, size, 16 | 0, PI * 2 - Math.PI * 0.2, 17 | false, Math.PI * 0.2) 18 | ctx.closePath() 19 | ctx.stroke() 20 | } 21 | 22 | export function drawOriginTick (state, ctx) { 23 | const { tick } = state.simulation 24 | const size = 10 25 | 26 | ctx.save() 27 | ctx.globalAlpha = 0.95 28 | ctx.strokeStyle = UI_PALETTE.BACK_TERTIARY 29 | ctx.lineWidth = 1 30 | ctx.rotate(tick * 0.02, 'z') 31 | 32 | ctx.beginPath() 33 | ctx.arc(0, 0, size, 0, PI * 0.5) 34 | ctx.stroke() 35 | ctx.beginPath() 36 | ctx.arc(0, 0, size, PI, PI * 1.5) 37 | ctx.stroke() 38 | 39 | ctx.restore() 40 | } 41 | 42 | export function drawPolarGrid (state, ctx) { 43 | const radialOffsets = [100, 400, 900] 44 | const polarIterations = [13, 41, 91] 45 | const tickSizes = [2, 3, 4] 46 | 47 | ctx.save() 48 | 49 | for (let i = 0; i < radialOffsets.length; i++) { 50 | const radius = radialOffsets[i] 51 | const polarSteps = polarIterations[i] 52 | const tickSize = tickSizes[i] 53 | const angleStep = PI * 0.5 / polarSteps 54 | 55 | for (let j = 0; j < polarSteps; j++) { 56 | const isEven = j % 2 === 0 57 | const length = isEven ? tickSize : tickSize * 3 58 | 59 | ctx.globalAlpha = isEven ? 1 : 0.5 60 | ctx.lineWidth = 0.5 61 | ctx.strokeStyle = UI_PALETTE.BACK_TERTIARY 62 | 63 | ctx.beginPath() 64 | ctx.moveTo(0, radius - length, 0) 65 | ctx.lineTo(0, radius + length, 0) 66 | ctx.stroke() 67 | ctx.rotate(angleStep, 'z') 68 | } 69 | } 70 | 71 | ctx.restore() 72 | } 73 | -------------------------------------------------------------------------------- /src/renderer/draw/routines/primitive.js: -------------------------------------------------------------------------------- 1 | export function arc (ctx, x, y, radius, startAngle, endAngle, anticlockwise, precision_) { 2 | const delta = Math.abs(endAngle - startAngle) 3 | const dir = anticlockwise === true ? -1 : 1 4 | const precision = precision_ || Math.PI * 0.1 5 | const count = Math.ceil(delta / precision) 6 | 7 | for (let i = 0; i < count; i++) { 8 | const t = i / (count - 1) 9 | const angle = startAngle + t * delta * dir 10 | const ax = x + Math.cos(angle) * radius 11 | const ay = y + Math.sin(angle) * radius 12 | 13 | if (i === 0) ctx.moveTo(ax, ay, 0) 14 | else ctx.lineTo(ax, ay, 0) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/draw/routines/simulation.js: -------------------------------------------------------------------------------- 1 | import { vec2 } from 'gl-matrix' 2 | import { radialPosition } from '@renderer/utils/math' 3 | import { arc } from './primitive' 4 | 5 | const { PI } = Math 6 | const scratchVec2A = vec2.create() 7 | const scratchVec2B = vec2.create() 8 | 9 | export function drawSimulatorUI (state, ctx) { 10 | if (!state.simulation.isRunning) return 11 | const { tick, isRunning } = state.simulation 12 | const { center, size } = state.viewport 13 | const { overlay } = state.controls.viewport 14 | 15 | const offsetA = 6 + Math.sin(tick * 0.02) * 2 16 | const offsetB = 10 + Math.sin(tick * 0.02) * 2 17 | 18 | ctx.save() 19 | ctx.translate(-center[0], -center[1], 0) 20 | 21 | ctx.globalAlpha = (isRunning ? overlay.alphaFactor : 1) * 0.8 22 | ctx.strokeStyle = overlay.colorHighlightHex 23 | 24 | ctx.lineWidth = 3 25 | ctx.strokeRect(offsetA, offsetA, 26 | size[0] - offsetA * 2, size[1] - offsetA * 2) 27 | 28 | ctx.lineWidth = 0.5 29 | ctx.strokeRect(offsetB, offsetB, 30 | size[0] - offsetB * 2, size[1] - offsetB * 2) 31 | 32 | ctx.restore() 33 | } 34 | 35 | export function drawSimulatorForces ( 36 | state, ctx, baseRadius, alpha, 37 | positionType = 0, renderTicker = false 38 | ) { 39 | const { tick, isRunning } = state.simulation 40 | const { forces } = state.controls 41 | const { scale } = state.viewport 42 | const { overlay } = state.controls.viewport 43 | const simulationPoints = state.simulationForces && state.simulationForces.points 44 | const scaleInv = 1 / scale 45 | 46 | forces.forEach(({ positionTypeIndex, polarAngle, polarOffset, intensity, radius }, i) => { 47 | if (positionTypeIndex !== positionType) return 48 | 49 | let position 50 | if (isRunning) { 51 | const point = simulationPoints[i] 52 | position = point.position 53 | intensity = point.force.intensity 54 | } else { 55 | const offset = vec2.set(scratchVec2A, polarOffset * polarOffset, 0) 56 | position = radialPosition(scratchVec2B, offset, polarAngle / 180 * PI) 57 | } 58 | 59 | ctx.save() 60 | ctx.translate(position[0], position[1], 0) 61 | 62 | ctx.globalAlpha = (isRunning ? overlay.alphaFactor : 1) * alpha 63 | ctx.strokeStyle = '#ffffff' 64 | 65 | ctx.lineWidth = 1.5 * scaleInv 66 | ctx.beginPath() 67 | arc(ctx, 68 | 0, 0, baseRadius + intensity * 1.5, 69 | 0, Math.PI * 2 - Math.PI * 0.2, 70 | false, Math.PI * 0.2) 71 | ctx.closePath() 72 | ctx.stroke() 73 | 74 | ctx.lineWidth = 1 * scaleInv 75 | ctx.beginPath() 76 | arc(ctx, 77 | 0, 0, baseRadius + intensity * 1.5 + 4, 78 | 0, Math.PI * 2 - Math.PI * 0.2, 79 | false, Math.PI * 0.2) 80 | ctx.closePath() 81 | ctx.stroke() 82 | 83 | if (renderTicker) { 84 | ctx.lineWidth = 1 * scaleInv 85 | ctx.beginPath() 86 | arc(ctx, 87 | 0, 0, 88 | baseRadius + 2 + Math.sin(tick * 0.05) * 6, 89 | 0, Math.PI * 2 - Math.PI * 0.2, 90 | false, Math.PI * 0.2) 91 | ctx.closePath() 92 | ctx.stroke() 93 | } 94 | 95 | ctx.restore() 96 | }) 97 | } 98 | 99 | export function drawSimulatorForcesTick (state, ctx, baseRadius, alpha) { 100 | drawSimulatorForces(state, ctx, baseRadius, alpha, 0, true) 101 | } 102 | 103 | export function drawSimulatorPointerForces (state, ctx, baseRadius, alpha) { 104 | drawSimulatorForces(state, ctx, baseRadius, alpha, 1, false) 105 | drawSimulatorForces(state, ctx, baseRadius, alpha, 2, false) 106 | } 107 | -------------------------------------------------------------------------------- /src/renderer/embed.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import MicrobiumEmbed from './MicrobiumEmbed' 3 | 4 | Vue.config.productionTip = false 5 | 6 | // OPTIM: Would be nice to not depend on Vue for embed 7 | // It's not really needed and is ~20% of bundle size 8 | // Might not be worth redesigning the component structure though ... 9 | let viewer = null 10 | export function mountViewer (rawSceneData, el_) { 11 | if (viewer) { 12 | viewer.updateSceneData(rawSceneData) 13 | return 14 | } 15 | 16 | let el = el_ 17 | if (!el) { 18 | el = document.createElement('div') 19 | document.body.appendChild(el) 20 | } 21 | 22 | viewer = new Vue({ 23 | el, 24 | components: { MicrobiumEmbed }, 25 | template: '', 26 | 27 | data () { 28 | return { 29 | rawSceneData 30 | } 31 | }, 32 | 33 | methods: { 34 | updateSceneData (rawSceneData) { 35 | this.rawSceneData = rawSceneData 36 | } 37 | } 38 | }) 39 | 40 | return { 41 | updateSceneData (rawSceneData) { 42 | viewer.updateSceneData(rawSceneData) 43 | } 44 | } 45 | } 46 | 47 | ;(function init () { 48 | if (window.microbiumAsyncInit) { 49 | setTimeout(window.microbiumAsyncInit, 1) 50 | } 51 | })() 52 | -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueElectron from 'vue-electron' 3 | 4 | import MicrobiumApp from './MicrobiumApp' 5 | import router from './router/main' 6 | import store from './store' 7 | 8 | Vue.use(VueElectron) 9 | Vue.config.productionTip = false 10 | 11 | /* eslint-disable no-new */ 12 | new Vue({ 13 | components: { MicrobiumApp }, 14 | router, 15 | store, 16 | template: '' 17 | }).$mount('#app') 18 | -------------------------------------------------------------------------------- /src/renderer/mixins/EditableListMixin.js: -------------------------------------------------------------------------------- 1 | const noop = () => {} 2 | 3 | export default { 4 | methods: { 5 | getItemIndex (item) { 6 | return this.list.indexOf(item) 7 | }, 8 | 9 | createDuplicateItem (item, index) { 10 | const { name } = item 11 | const nameCount = this.list.reduce((accum, c) => { 12 | return accum + (c.name.indexOf(name) === 0 ? 1 : 0) 13 | }, 0) 14 | 15 | return Object.assign({}, item, { 16 | index, 17 | name: `${name} ${1 + nameCount}` 18 | }) 19 | }, 20 | 21 | // TODO: Enable removing item anywhere in list 22 | // TODO: Ensure removed item doesn't have dependent data (geometry segments) 23 | removeListItem (item) { 24 | const { list } = this 25 | const index = this.getItemIndex(item) 26 | if (index !== list.length - 1) return 27 | 28 | this.willRemoveListItem(item, index) 29 | list.splice(index, 1) 30 | this.didRemoveListItem(item, index) 31 | }, 32 | 33 | willRemoveListItem: noop, 34 | didRemoveListItem: noop, 35 | 36 | duplicateListItem (item) { 37 | const { list } = this 38 | const index = list.length 39 | const duplicate = this.createDuplicateItem(item, index) 40 | 41 | this.willDuplicateListItem(duplicate, index) 42 | list.push(duplicate) 43 | this.didDuplicateListItem(duplicate, index) 44 | }, 45 | 46 | willDuplicateListItem: noop, 47 | didDuplicateListItem: noop 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/renderer/physics/constraints/ViscousDistanceConstraint.js: -------------------------------------------------------------------------------- 1 | import { DistanceConstraint } from 'particulate' 2 | import { inherit } from '@renderer/utils/ctor' 3 | 4 | export { ViscousDistanceConstraint } 5 | 6 | /** 7 | Modified Distance Constraint (2D) 8 | 9 | Applies viscous liquid oppositional force normal to member 10 | */ 11 | 12 | function ViscousDistanceConstraint (distance, a, b) { 13 | DistanceConstraint.call(this, distance, a, b) 14 | this.fluidFrictionFactor = 1.0 / (50 * 1000) 15 | } 16 | 17 | inherit(DistanceConstraint, ViscousDistanceConstraint, { 18 | applyConstraint (index, p0, p1) { 19 | const { indices, fluidFrictionFactor } = this 20 | const min2 = this._min2 21 | const max2 = this._max2 22 | 23 | const ai = indices[index]; const bi = indices[index + 1] 24 | const ax = ai * 3; const ay = ax + 1 25 | const bx = bi * 3; const by = bx + 1 26 | 27 | let dx = p0[bx] - p0[ax] 28 | let dy = p0[by] - p0[ay] 29 | 30 | if (!(dx || dy)) { 31 | dx = dy = 0.1 32 | } 33 | 34 | // OPTIM: Use non-sqrt approximations 35 | const dist2 = dx * dx + dy * dy 36 | const dist = Math.sqrt(dist2) 37 | const distInv = (1 / dist) || 0 38 | 39 | // Constrain particles to distance range 40 | if (dist2 > max2 || dist2 < min2) { 41 | const target2 = dist2 < min2 ? min2 : max2 42 | const diff = target2 / (dist2 + target2) 43 | const aDiff = diff - 0.5 44 | const bDiff = diff - 0.5 45 | 46 | p0[ax] -= dx * aDiff 47 | p0[ay] -= dy * aDiff 48 | p0[bx] += dx * bDiff 49 | p0[by] += dy * bDiff 50 | } 51 | 52 | // Segment unit length vector 53 | const udx = dx * distInv 54 | const udy = dy * distInv 55 | 56 | // Segment normal vector (2D) 57 | const ndx = udy 58 | const ndy = -udx 59 | 60 | // Particle movement vectors 61 | const velocityAX = p1[ax] - p0[ax] 62 | const velocityAY = p1[ay] - p0[ay] 63 | const velocityBX = p1[bx] - p0[bx] 64 | const velocityBY = p1[by] - p0[by] 65 | 66 | // TODO: Experiment with non-linear velocity factor (velocity^2 ?) 67 | // TODO: Investigate effect of variable particle weights on friction 68 | 69 | // Project particle movement onto normal as friction vector 70 | const velocityAT = ndx * velocityAX + ndy * velocityAY 71 | const velocityBT = ndx * velocityBX + ndy * velocityBY 72 | 73 | const factor = fluidFrictionFactor * dist 74 | const factorA = Math.min(5, Math.abs(Math.pow(velocityAT, 2))) * factor 75 | const factorB = Math.min(5, Math.abs(Math.pow(velocityBT, 2))) * factor 76 | const frictionAT = velocityAT * factorA 77 | const frictionBT = velocityBT * factorB 78 | 79 | const frictionAX = ndx * frictionAT 80 | const frictionAY = ndy * frictionAT 81 | const frictionBX = ndx * frictionBT 82 | const frictionBY = ndy * frictionBT 83 | 84 | p1[ax] -= frictionAX 85 | p1[ay] -= frictionAY 86 | p1[bx] -= frictionBX 87 | p1[by] -= frictionBY 88 | } 89 | }) 90 | -------------------------------------------------------------------------------- /src/renderer/physics/forces/RepulsorForce.js: -------------------------------------------------------------------------------- 1 | import { Force } from 'particulate' 2 | import { inherit } from '@renderer/utils/ctor' 3 | 4 | export { RepulsorForce } 5 | 6 | function RepulsorForce (position, opts) { 7 | opts = opts || {} 8 | Force.apply(this, arguments) 9 | this.intensity = opts.intensity || 0.05 10 | this.setRadius(opts.radius || 0) 11 | } 12 | 13 | inherit(Force, RepulsorForce, { 14 | setRadius (r) { 15 | this.radius = r 16 | this.radiusSq = r * r 17 | }, 18 | 19 | applyForce (ix, f0, p0, p1) { 20 | const v0 = this.vector 21 | const iy = ix + 1 22 | const iz = ix + 2 23 | 24 | const dx = p0[ix] - v0[0] 25 | const dy = p0[iy] - v0[1] 26 | const dz = p0[iz] - v0[2] 27 | 28 | const radiusSq = this.radiusSq 29 | const distSqXY = dx * dx + dy * dy 30 | const diffSqXY = distSqXY - radiusSq 31 | const isActive = distSqXY > 0 && diffSqXY < 1 32 | if (!isActive) return 33 | 34 | const dist = Math.sqrt(distSqXY + dz * dz) 35 | const intensity = this.intensity 36 | 37 | const distInv = 1 / dist 38 | const nx = dx * distInv 39 | const ny = dy * distInv 40 | const nz = dz * distInv 41 | 42 | f0[ix] += nx * intensity 43 | f0[iy] += ny * intensity 44 | f0[iz] += nz * intensity * 0.2 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /src/renderer/physics/forces/RotatorForce.js: -------------------------------------------------------------------------------- 1 | import { Force } from 'particulate' 2 | import { inherit } from '@renderer/utils/ctor' 3 | 4 | export { RotatorForce } 5 | 6 | function RotatorForce (position, opts) { 7 | opts = opts || {} 8 | Force.apply(this, arguments) 9 | this.intensity = opts.intensity || 0.05 10 | this.setRadius(opts.radius || 0) 11 | } 12 | 13 | inherit(Force, RotatorForce, { 14 | setRadius (r) { 15 | this.radius = r 16 | }, 17 | 18 | applyForce (ix, f0, p0, p1) { 19 | const v0 = this.vector 20 | const iy = ix + 1 21 | const iz = ix + 2 22 | 23 | const radius = this.radius 24 | const intensity = this.intensity 25 | 26 | const dx = p0[ix] - v0[0] 27 | const dy = p0[iy] - v0[1] 28 | // const dz = p0[iz] - v0[2] 29 | 30 | const dist = Math.sqrt(dx * dx + dy * dy) 31 | const diff = dist - radius 32 | const isActive = dist > 0 && diff < 1 33 | if (!isActive) return 34 | 35 | const distInv = 1 / dist 36 | const nx = dx * distInv 37 | const ny = dy * distInv 38 | // const nz = dz * distInv 39 | 40 | const scale = Math.min(1, dist / radius) * intensity 41 | 42 | f0[ix] -= ny * scale 43 | f0[iy] += nx * scale 44 | f0[iz] += ny * scale 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /src/renderer/physics/systems/ParticleSystem.js: -------------------------------------------------------------------------------- 1 | import { ParticleSystem as BaseParticleSystem } from 'particulate' 2 | import { inherit } from '@renderer/utils/ctor' 3 | 4 | export { ParticleSystem } 5 | 6 | function ParticleSystem (particles, iterations) { 7 | BaseParticleSystem.call(this, particles, iterations) 8 | this._engineConstraints = [] 9 | } 10 | 11 | inherit(BaseParticleSystem, ParticleSystem, { 12 | addEngineConstraint (constraint) { 13 | this._engineConstraints.push(constraint) 14 | }, 15 | 16 | satisfyConstraints () { 17 | const iterations = this._iterations 18 | const globals = this._globalConstraints 19 | const locals = this._localConstraints 20 | const pins = this._pinConstraints 21 | const engines = this._engineConstraints 22 | const globalCount = this._count 23 | const globalItemSize = 3 24 | 25 | for (let i = 0; i < iterations; i++) { 26 | this.satisfyConstraintGroup(globals, globalCount, globalItemSize) 27 | this.satisfyConstraintGroup(locals) 28 | if (engines.length) this.satisfyConstraintGroup(engines) 29 | if (pins.length) this.satisfyConstraintGroup(pins) 30 | } 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /src/renderer/router/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import Editor from '@renderer/components/Editor' 5 | import Palette from '@renderer/components/Palette' 6 | 7 | Vue.use(Router) 8 | 9 | const router = new Router({ 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'editor', 14 | component: Editor, 15 | meta: { 16 | title: 'Editor' 17 | } 18 | }, 19 | { 20 | path: '/palette', 21 | name: 'palette', 22 | component: Palette, 23 | meta: { 24 | title: 'Palette' 25 | } 26 | } 27 | ] 28 | }) 29 | router.beforeEach((to, from, next) => { 30 | document.title = to.meta.title 31 | next() 32 | }) 33 | 34 | export default router 35 | -------------------------------------------------------------------------------- /src/renderer/shaders/alpha/basic-dash.glsl: -------------------------------------------------------------------------------- 1 | float basicDash (vec3 udo, float scale, float thickness) { 2 | float rcoord = udo.y * scale; 3 | float line = abs(fract(rcoord - 0.5) - 0.5) / fwidth(rcoord); 4 | return clamp( 5 | thickness - min(line, thickness), 6 | 0.0, 1.0); 7 | } 8 | 9 | #pragma glslify: export(basicDash) 10 | -------------------------------------------------------------------------------- /src/renderer/shaders/alpha/bulging-dash.glsl: -------------------------------------------------------------------------------- 1 | float bulgingDash ( 2 | vec3 udo, 3 | float repeat, float offset, 4 | float shapeStart, float shapeEnd 5 | ) { 6 | vec2 coord = vec2( 7 | (udo.x + 1.0) * 0.5, 8 | sin((udo.y - offset) * 0.2) * 0.5 + 0.5); 9 | 10 | float lineWidth = max(0.0, sin(coord.x * PI)) * 11 | (1.0 - sin(coord.x * PI) * 0.25); 12 | float lineStep = sin((coord.y * PI) * repeat) * 13 | (shapeEnd - shapeStart) + shapeStart; 14 | float line = pow(smoothstep(1.0 - lineWidth, 1.0, lineStep), 0.5); 15 | 16 | return smoothstep(0.0, 1.0, line); 17 | } 18 | 19 | #pragma glslify: export(bulgingDash) 20 | -------------------------------------------------------------------------------- /src/renderer/shaders/alpha/concentric-dash.glsl: -------------------------------------------------------------------------------- 1 | float concentricDash (vec2 coord, float scale, float thickness, vec2 origin, float radius) { 2 | float lcoord = length(coord - origin); 3 | float rcoord = lcoord * scale; 4 | float line = abs(fract(rcoord - 0.5) - 0.5) / fwidth(rcoord); 5 | 6 | float lineFactor = clamp(thickness - min(line, thickness), 0.0, 1.0); 7 | float radiusFactor = radius == -1.0 ? 1.0 : (1.0 - smoothstep(0.8, 1.1, lcoord / radius)); 8 | return lineFactor * radiusFactor; 9 | } 10 | 11 | float concentricDash (vec2 coord, float scale, float thickness) { 12 | return concentricDash(coord, scale, thickness, vec2(0.0), -1.0); 13 | } 14 | 15 | #pragma glslify: export(concentricDash) 16 | -------------------------------------------------------------------------------- /src/renderer/shaders/alpha/lateral-dash.glsl: -------------------------------------------------------------------------------- 1 | float lateralDash (vec3 udo, float scale, float thickness) { 2 | float PI_2 = PI / 2.0; 3 | vec2 coord = vec2( 4 | (udo.x + 1.0) * 0.5, 5 | udo.y); 6 | 7 | float rcoord = coord.x * scale; 8 | float line = abs(fract(rcoord - 0.5) - 0.5) / fwidth(rcoord); 9 | return clamp( 10 | thickness - min(line, thickness), 11 | 0.0, 1.0); 12 | } 13 | 14 | #pragma glslify: export(lateralDash) 15 | -------------------------------------------------------------------------------- /src/renderer/shaders/alpha/radial-dash.glsl: -------------------------------------------------------------------------------- 1 | float radialDash (vec2 coord, float steps, float scale, float thickness) { 2 | float rcoord = atan(coord.x, coord.y) * steps / PI * scale; 3 | float line = abs(fract(rcoord - 0.5) - 0.5) / fwidth(rcoord); 4 | float rthickness = thickness * length(coord) / steps; 5 | return clamp( 6 | rthickness - min(line, rthickness), 7 | 0.0, 1.0); 8 | } 9 | 10 | #pragma glslify: export(radialDash) 11 | -------------------------------------------------------------------------------- /src/renderer/shaders/alpha/wavy-dash.glsl: -------------------------------------------------------------------------------- 1 | float wavyDash ( 2 | vec3 udo, 3 | float repeat, float offset, 4 | float shapeStart, float shapeEnd 5 | ) { 6 | float PI_2 = PI / 2.0; 7 | vec2 coord = vec2( 8 | (udo.x + 1.0) * 0.5, 9 | udo.y); 10 | 11 | float lineWidth = max(0.0, sin(coord.y * 0.2) * 0.4 + 0.5); 12 | float offsetX = sin(coord.y * 0.2) * 13 | (sin(offset * 0.025) * 0.1 + 0.2); 14 | float lineStep = sin(((coord.x + offsetX) * PI + PI_2 * 1.2) * repeat) * 15 | (shapeEnd - shapeStart) + shapeStart; 16 | float line = pow(smoothstep(1.0 - lineWidth, 1.0, lineStep), 0.5); 17 | 18 | return smoothstep(0.0, 1.0, line); 19 | } 20 | 21 | #pragma glslify: export(wavyDash) 22 | -------------------------------------------------------------------------------- /src/renderer/shaders/band-gradient.glsl: -------------------------------------------------------------------------------- 1 | // TODO: Smooth out band aliasing 2 | float bandGradient(float value, float step) { 3 | float scaled = value * step; 4 | float diff = fract(scaled); 5 | return (scaled - diff) / step; 6 | } 7 | 8 | #pragma glslify: export(bandGradient) 9 | -------------------------------------------------------------------------------- /src/renderer/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform vec4 color; 3 | void main() { 4 | gl_FragColor = color; 5 | } 6 | -------------------------------------------------------------------------------- /src/renderer/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 projection; 2 | uniform mat4 model; 3 | uniform mat4 view; 4 | attribute vec3 position; 5 | 6 | void main() { 7 | gl_Position = projection * view * model * vec4(position, 1.0); 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/shaders/box-blur.glsl: -------------------------------------------------------------------------------- 1 | vec3 boxBlur(sampler2D color, vec2 uv, vec2 sizeReciprocol) { 2 | float W = float((1 + 2 * R) * (1 + 2 * R)); 3 | vec3 avg = vec3(0.0); 4 | for (int x = -R; x <= +R; x++) { 5 | for (int y = -R; y <= +R; y++) { 6 | vec2 uvOffset = vec2(float(x) * sizeReciprocol.x, float(y) * sizeReciprocol.y); 7 | avg += (1.0 / W) * texture2D(color, uv + uvOffset).rgb; 8 | } 9 | } 10 | return avg; 11 | } 12 | 13 | #pragma glslify: export(boxBlur) 14 | -------------------------------------------------------------------------------- /src/renderer/shaders/color/brightness-contrast.glsl: -------------------------------------------------------------------------------- 1 | vec3 brightnessContrast(vec3 color, float brightness, float contrast) { 2 | return (color - 0.5) * contrast + 0.5 + brightness; 3 | } 4 | 5 | #pragma glslify: export(brightnessContrast) 6 | -------------------------------------------------------------------------------- /src/renderer/shaders/color/hsv2rgb.glsl: -------------------------------------------------------------------------------- 1 | vec3 hsv2rgb(vec3 c) { 2 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 3 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 4 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 5 | } 6 | 7 | #pragma glslify: export(hsv2rgb) 8 | -------------------------------------------------------------------------------- /src/renderer/shaders/color/rgb2hsv.glsl: -------------------------------------------------------------------------------- 1 | vec3 rgb2hsv(vec3 c) { 2 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 3 | vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); 4 | vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); 5 | 6 | float d = q.x - min(q.w, q.y); 7 | float e = 1.0e-10; 8 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); 9 | } 10 | 11 | #pragma glslify: export(rgb2hsv) 12 | -------------------------------------------------------------------------------- /src/renderer/shaders/edge-frei-chen.glsl: -------------------------------------------------------------------------------- 1 | // Edge Detection Shader using Frei-Chen filter 2 | // Based on http://rastergrid.com/blog/2011/01/frei-chen-edge-detector 3 | // @author zz85 / https://github.com/zz85 | https://www.lab4games.net/zz85/blog 4 | 5 | // Hard coded matrix values as suggested in 6 | // https://github.com/neilmendoza/ofxPostProcessing/blob/master/src/EdgePass.cpp#L45 7 | mat3 G[9]; 8 | const float gN0 = 0.3535533845424652; 9 | const float gN1 = 0.1666666716337204; 10 | const float gN2 = 0.3333333432674408; 11 | const float gN3 = 0.6666666865348816; 12 | const mat3 g0 = mat3(gN0, 0, -gN0, 0.5, 0, -0.5, gN0, 0, -gN0); 13 | const mat3 g1 = mat3(gN0, 0.5, gN0, 0, 0, 0, -gN0, -0.5, -gN0); 14 | const mat3 g2 = mat3(0, gN0, -0.5, -gN0, 0, gN0, 0.5, -gN0, 0); 15 | const mat3 g3 = mat3(0.5, -gN0, 0, -gN0, 0, gN0, 0, gN0, -0.5); 16 | const mat3 g4 = mat3(0, -0.5, 0, 0.5, 0, 0.5, 0, -0.5, 0); 17 | const mat3 g5 = mat3(-0.5, 0, 0.5, 0, 0, 0, 0.5, 0, -0.5); 18 | const mat3 g6 = mat3(gN1, -gN2, gN1, -gN2, gN3, -gN2, gN1, -gN2, gN1); 19 | const mat3 g7 = mat3(-gN2, gN1, -gN2, gN1, gN3, gN1, -gN2, gN1, -gN2); 20 | const mat3 g8 = mat3(gN2, gN2, gN2, gN2, gN2, gN2, gN2, gN2, gN2); 21 | 22 | float edge(sampler2D color, vec2 uv, vec2 resolution, float scale) { 23 | G[0] = g0, 24 | G[1] = g1, 25 | G[2] = g2, 26 | G[3] = g3, 27 | G[4] = g4, 28 | G[5] = g5, 29 | G[6] = g6, 30 | G[7] = g7, 31 | G[8] = g8; 32 | 33 | vec2 texel = vec2(scale / resolution.x, scale / resolution.y); 34 | mat3 I; 35 | float cnv[9]; 36 | vec3 sample; 37 | 38 | // Fetch the 3x3 neighbourhood and use the RGB vector's length as intensity value 39 | for (float i = 0.0; i < 3.0; i++) { 40 | for (float j = 0.0; j < 3.0; j++) { 41 | sample = (texture2D(color, uv + texel * vec2(i - 1.0, j - 1.0))).rgb; 42 | I[int(i)][int(j)] = length(sample); 43 | } 44 | } 45 | 46 | // Calculate the convolution values for all the masks 47 | for (int i = 0; i < 9; i++) { 48 | float dp3 = dot(G[i][0], I[0]) + dot(G[i][1], I[1]) + dot(G[i][2], I[2]); 49 | cnv[i] = dp3 * dp3; 50 | } 51 | 52 | float M = (cnv[0] + cnv[1]) + (cnv[2] + cnv[3]); 53 | float S = (cnv[4] + cnv[5]) + (cnv[6] + cnv[7]) + (cnv[8] + M); 54 | 55 | return sqrt(M / S); 56 | } 57 | 58 | #pragma glslify: export(edge) 59 | -------------------------------------------------------------------------------- /src/renderer/shaders/fills-entities.frag: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | 3 | precision highp float; 4 | 5 | #define PI 3.141592653589793 6 | 7 | uniform vec3 viewResolution; // [x, y, pxRatio] 8 | uniform vec2 viewOffset; 9 | uniform float viewScale; 10 | uniform float tick; 11 | 12 | uniform int useAlphaMap; 13 | uniform float alphaMapRepeat; 14 | uniform sampler2D alphaMap; 15 | 16 | uniform int dashFunction; 17 | 18 | varying vec4 vColor; 19 | varying float vId; 20 | // varying vec3 vUDO; // [u, distance, offset] 21 | 22 | #pragma glslify: radialDash = require(./alpha/radial-dash, fwidth=fwidth, PI=PI) 23 | #pragma glslify: concentricDash = require(./alpha/concentric-dash, fwidth=fwidth, PI=PI) 24 | 25 | float sampleAlphaMap (vec2 fragPosition, float repeat, float offset, sampler2D map) { 26 | vec2 c0 = (fragPosition + offset) / repeat; 27 | vec2 m0 = mod(floor(c0), 2.0); 28 | float cx = fract(mix(c0.x, 1.0 - c0.x, m0.x)); 29 | float cy = fract(mix(c0.y, 1.0 - c0.y, m0.y)); 30 | return texture2D(map, vec2(cx, cy)).r; 31 | } 32 | 33 | void main() { 34 | vec2 fragCoord = gl_FragCoord.xy / viewResolution.z; 35 | vec2 fragCenter = fragCoord - viewResolution.xy / viewResolution.z * 0.5; 36 | vec2 fragPosition = fragCenter - vec2(viewOffset.x, -viewOffset.y); 37 | vec2 coord = fragCoord / viewResolution.xy; 38 | // vec3 udo = vUDO; 39 | 40 | vec3 outColor = vColor.rgb; 41 | float outAlpha = vColor.a; 42 | 43 | // TODO: Parameterize tick offset animation 44 | if (useAlphaMap == 1) { 45 | outAlpha *= sampleAlphaMap(fragPosition, 46 | alphaMapRepeat * viewScale, 47 | vId * alphaMapRepeat * viewScale, 48 | alphaMap); 49 | } 50 | 51 | if (dashFunction == 1) { 52 | outAlpha *= radialDash(fragPosition, 800.0, 0.1, 10.0); 53 | } else if (dashFunction == 2) { 54 | outAlpha *= concentricDash(fragPosition, 0.1 / viewScale, 3.0); 55 | } 56 | 57 | gl_FragColor = vec4(outColor, outAlpha); 58 | } 59 | -------------------------------------------------------------------------------- /src/renderer/shaders/fills-entities.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | #pragma glslify: transformPosition = require(./position/entities-transform) 4 | #pragma glslify: mapZ = require(./position/entities-mapz) 5 | 6 | uniform mat4 projection; 7 | uniform int projectionMode; 8 | uniform mat4 model; 9 | uniform mat4 view; 10 | 11 | uniform vec4 tint; 12 | uniform vec3 mirror; // [x, y, alpha] 13 | 14 | uniform vec3 depth; // [offset, scale, polarOffset] 15 | uniform int useDepthMap; 16 | uniform sampler2D depthMap; 17 | uniform vec2 depthMapParams; // [repeat, displacement] 18 | 19 | uniform float angle; 20 | uniform float angleAlpha; 21 | 22 | attribute vec3 position; 23 | attribute vec4 color; 24 | attribute float id; 25 | 26 | varying vec4 vColor; 27 | varying float vId; 28 | 29 | void main() { 30 | mat4 projViewModel = projection * view * model; 31 | vec4 posProjected = projViewModel * 32 | vec4(transformPosition(position.xy, mirror.xy, angle), 33 | mapZ(position, depth, useDepthMap, depthMap, depthMapParams, id), 1.0); 34 | 35 | vColor = vec4(tint.rgb * color.rgb, tint.a * color.a * mirror.z * angleAlpha); 36 | vId = id; 37 | 38 | if (projectionMode == 0) { 39 | posProjected *= vec4(0.5, 0.5, 0.5, 1.0); 40 | } 41 | 42 | gl_Position = posProjected; 43 | } 44 | -------------------------------------------------------------------------------- /src/renderer/shaders/line-antialias-alpha.glsl: -------------------------------------------------------------------------------- 1 | // TODO: Apply antialiasing to line ends 2 | float lineAntialiasAlpha (float coord) { 3 | float acoord = (coord + 1.0) / 2.0; 4 | return clamp( 5 | abs(fract(acoord - 0.5) - 0.5) / fwidth(acoord), 6 | 0.0, 1.0); 7 | } 8 | 9 | #pragma glslify: export(lineAntialiasAlpha) 10 | -------------------------------------------------------------------------------- /src/renderer/shaders/lines-entities.frag: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | 3 | precision highp float; 4 | 5 | #define PI 3.141592653589793 6 | 7 | uniform vec3 viewResolution; // [x, y, pxRatio] 8 | uniform vec2 viewOffset; 9 | uniform float viewScale; 10 | uniform float tick; 11 | 12 | uniform int dashFunction; 13 | uniform int useAlphaMap; 14 | uniform float alphaMapRepeat; 15 | uniform sampler2D alphaMap; 16 | 17 | // uniform int useDiffuseMap; 18 | // uniform sampler2D diffuseMap; 19 | // uniform int useScreenTintFunc; 20 | 21 | varying vec4 vColor; 22 | varying vec3 vUDO; // [u, distance, offset] 23 | 24 | #pragma glslify: lineAntialiasAlpha = require(./line-antialias-alpha, fwidth=fwidth) 25 | #pragma glslify: basicDash = require(./alpha/basic-dash, fwidth=fwidth, PI=PI) 26 | #pragma glslify: radialDash = require(./alpha/radial-dash, fwidth=fwidth, PI=PI) 27 | #pragma glslify: concentricDash = require(./alpha/concentric-dash, fwidth=fwidth, PI=PI) 28 | #pragma glslify: bulgingDash = require(./alpha/bulging-dash, fwidth=fwidth, PI=PI) 29 | #pragma glslify: lateralDash = require(./alpha/lateral-dash, fwidth=fwidth, PI=PI) 30 | #pragma glslify: wavyDash = require(./alpha/wavy-dash, fwidth=fwidth, PI=PI) 31 | 32 | float sampleAlphaMap (vec3 udo, float repeat, float offset, sampler2D map) { 33 | vec2 coords = vec2( 34 | (udo.x + 1.0) * 0.5, 35 | fract((udo.y + offset) / repeat) * 0.8 + 0.1); 36 | return texture2D(map, coords).r; // * smoothstep(0.0, 2.0, 1.0 - coords.x); 37 | } 38 | 39 | void main() { 40 | vec2 fragCoord = gl_FragCoord.xy / viewResolution.z; 41 | vec2 fragCenter = fragCoord - viewResolution.xy / viewResolution.z * 0.5; 42 | vec2 fragPosition = fragCenter - vec2(viewOffset.x, -viewOffset.y); 43 | vec2 coord = fragCoord / viewResolution.xy; 44 | vec3 udo = vUDO; 45 | 46 | vec3 outColor = vColor.rgb; 47 | float outAlpha = vColor.a * lineAntialiasAlpha(udo.x); 48 | 49 | // if (useDiffuseMap == 1) { 50 | // outColor *= texture2D(diffuseMap, coord).rgb; 51 | // } 52 | 53 | // if (useScreenTintFunc == 1) { 54 | // outColor *= vec3(coord.x, 0.6 - distance(coord, vec2(0.5)), coord.y); 55 | // } 56 | 57 | if (useAlphaMap == 1) { 58 | // TODO: Parameterize tick offset animation 59 | outAlpha *= sampleAlphaMap(udo, alphaMapRepeat, tick * 0.5, alphaMap); 60 | } 61 | 62 | if (dashFunction == 1) { 63 | outAlpha *= radialDash(fragPosition, 800.0, 0.1, 10.0); 64 | } else if (dashFunction == 2) { 65 | outAlpha *= concentricDash(fragPosition, 0.1 / viewScale, 3.0); 66 | } else if (dashFunction == 3) { 67 | outAlpha *= basicDash(udo, 0.1, 3.0); 68 | } else if (dashFunction == 4) { 69 | outAlpha *= bulgingDash(udo, 1.5, -tick * 0.5, 0.35, 0.9); 70 | } else if (dashFunction == 5) { 71 | outAlpha *= wavyDash(udo, 2.0, -tick * 0.5, 0.05, 0.95); 72 | } else if (dashFunction == 6) { 73 | outAlpha *= lateralDash(udo, 1.0, 2.0); 74 | } 75 | 76 | gl_FragColor = vec4(outColor, outAlpha); 77 | } 78 | -------------------------------------------------------------------------------- /src/renderer/shaders/lines-entities.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | #pragma glslify: computeMiterOffset = require(regl-line-builder/src/shaders/compute-miter-offset) 4 | #pragma glslify: transformPosition = require(./position/entities-transform) 5 | #pragma glslify: mapZ = require(./position/entities-mapz) 6 | 7 | uniform mat4 projection; 8 | uniform mat4 model; 9 | uniform mat4 view; 10 | uniform float aspect; 11 | uniform int adjustProjectedThickness; 12 | 13 | uniform vec4 tint; 14 | uniform float thickness; 15 | uniform float miterLimit; 16 | uniform vec3 mirror; // [x, y, alpha] 17 | 18 | uniform vec3 depth; // [offset, scale, polarOffset] 19 | uniform int useDepthMap; 20 | uniform sampler2D depthMap; 21 | uniform vec2 depthMapParams; // [repeat, displacement] 22 | 23 | uniform float angle; 24 | uniform float angleAlpha; 25 | 26 | attribute vec3 prevPosition; 27 | attribute vec3 currPosition; 28 | attribute vec3 nextPosition; 29 | 30 | attribute float prevId; 31 | attribute float currId; 32 | attribute float nextId; 33 | 34 | attribute float offset; 35 | attribute vec4 color; 36 | attribute vec2 ud; 37 | 38 | varying vec4 vColor; 39 | varying vec3 vUDO; 40 | 41 | void main() { 42 | mat4 projViewModel = projection * view * model; 43 | 44 | vec4 prevProjected = projViewModel * 45 | vec4(transformPosition(prevPosition.xy, mirror.xy, angle), 46 | mapZ(prevPosition, depth, useDepthMap, depthMap, depthMapParams, prevId), 1.0); 47 | vec4 currProjected = projViewModel * 48 | vec4(transformPosition(currPosition.xy, mirror.xy, angle), 49 | mapZ(currPosition, depth, useDepthMap, depthMap, depthMapParams, currId), 1.0); 50 | vec4 nextProjected = projViewModel * 51 | vec4(transformPosition(nextPosition.xy, mirror.xy, angle), 52 | mapZ(nextPosition, depth, useDepthMap, depthMap, depthMapParams, nextId), 1.0); 53 | 54 | vec2 miterOffset = computeMiterOffset( 55 | projection, adjustProjectedThickness, 56 | aspect, thickness, miterLimit, 57 | prevId, currId, nextId, 58 | prevProjected, currProjected, nextProjected); 59 | vec2 positionOffset = miterOffset * offset; 60 | vec4 position = currProjected + vec4(positionOffset, 0.0, 1.0); 61 | 62 | vColor = vec4(tint.rgb * color.rgb, tint.a * color.a * mirror.z * angleAlpha); 63 | vUDO = vec3(ud, length(positionOffset)); 64 | 65 | gl_Position = position; 66 | } 67 | -------------------------------------------------------------------------------- /src/renderer/shaders/lines-ui.frag: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | 3 | precision highp float; 4 | 5 | uniform vec3 viewResolution; // [x, y, pxRatio] 6 | uniform vec2 viewOffset; 7 | uniform vec4 tint; 8 | 9 | varying vec4 vColor; 10 | varying vec2 vUD; 11 | 12 | #pragma glslify: lineAntialiasAlpha = require(./line-antialias-alpha, fwidth=fwidth) 13 | 14 | void main() { 15 | vec2 ud = vUD; 16 | vec3 outColor = vColor.rgb; 17 | float outAlpha = vColor.a * lineAntialiasAlpha(ud.x); 18 | 19 | gl_FragColor = vec4(outColor, outAlpha) * tint; 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/shaders/position/entities-mapz.glsl: -------------------------------------------------------------------------------- 1 | float mapZ ( 2 | vec3 pos, 3 | vec3 depth, 4 | int useDepthMap, 5 | sampler2D depthMap, 6 | vec2 depthMapParams, 7 | float id 8 | ) { 9 | float offset = depth.x; 10 | float scale = depth.y; 11 | float polarOffset = depth.z; 12 | float mapDepth = 0.0; 13 | 14 | if (useDepthMap == 1) { 15 | vec2 depthCoord = pos.xy / depthMapParams.x; 16 | float depthDisplacement = depthMapParams.y; 17 | mapDepth = texture2D(depthMap, depthCoord).r * depthDisplacement; 18 | } 19 | 20 | return offset + polarOffset + mapDepth + pos.z * scale; 21 | } 22 | 23 | #pragma glslify: export(mapZ) 24 | -------------------------------------------------------------------------------- /src/renderer/shaders/position/entities-transform.glsl: -------------------------------------------------------------------------------- 1 | vec2 transformPosition (vec2 position, vec2 mirror, float angle) { 2 | return vec2( 3 | (+cos(angle) * position.x + position.y * sin(angle)) * mirror.x, 4 | (-sin(angle) * position.x + position.y * cos(angle)) * mirror.y); 5 | } 6 | 7 | #pragma glslify: export(transformPosition) 8 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-banding.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D color; 4 | uniform float bandingStep; 5 | uniform float tick; 6 | 7 | varying vec2 uv; 8 | 9 | #pragma glslify: bandGradient = require(./band-gradient) 10 | #pragma glslify: rgb2hsv = require(./color/rgb2hsv) 11 | #pragma glslify: hsv2rgb = require(./color/hsv2rgb) 12 | 13 | void main() { 14 | // Base Color 15 | vec3 baseColor = texture2D(color, uv).rgb; 16 | vec3 baseColorHSV = rgb2hsv(baseColor); 17 | 18 | // Banded Gradients 19 | float bandingSample = bandGradient(baseColorHSV.b, bandingStep); 20 | vec3 bandingColor = hsv2rgb(vec3(baseColorHSV.r, baseColorHSV.g, bandingSample)); 21 | 22 | gl_FragColor = vec4(bandingColor, bandingSample); 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-box-blur.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | uniform sampler2D color; 3 | uniform vec3 viewResolution; // [x, y, pxRatio] 4 | varying vec2 uv; 5 | 6 | #pragma glslify: boxBlur = require(./box-blur, R = BLUR_RADIUS) 7 | 8 | void main() { 9 | gl_FragColor = vec4( 10 | boxBlur(color, uv, 1.0 / viewResolution.xy), 11 | 1.0); 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-copy.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform sampler2D color; 3 | varying vec2 uv; 4 | void main() { 5 | gl_FragColor = texture2D(color, uv); 6 | } 7 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-copy.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | uniform float scale; 3 | uniform vec2 offset; 4 | attribute vec2 position; 5 | varying vec2 uv; 6 | 7 | void main() { 8 | uv = (0.5 * (position + 1.0) + offset) * scale + (1.0 - scale) * 0.5; 9 | gl_Position = vec4(position, 0.0, 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-edges.frag: -------------------------------------------------------------------------------- 1 | #extension GL_OES_standard_derivatives : enable 2 | 3 | precision highp float; 4 | 5 | uniform sampler2D color; 6 | uniform float thickness; 7 | uniform float repeat; 8 | uniform float tick; 9 | uniform vec3 viewResolution; // [x, y, pxRatio] 10 | 11 | varying vec2 uv; 12 | 13 | #pragma glslify: rgb2hsv = require(./color/rgb2hsv) 14 | #pragma glslify: hsv2rgb = require(./color/hsv2rgb) 15 | 16 | float edgeDetect (float f, float width, float feather) { 17 | float w1 = width - feather * 0.5; 18 | float d = length(vec2(dFdx(f), dFdy(f))); 19 | 20 | f = 0.5 - abs(mod(f, 1.0) - 0.5); 21 | return smoothstep(d * w1, d * (w1 + feather), f); 22 | } 23 | 24 | void main() { 25 | // Base Color 26 | vec3 baseColor = texture2D(color, uv).rgb; 27 | vec3 baseColorHSV = rgb2hsv(baseColor); 28 | 29 | // Edge Detection 30 | float edgesSample = edgeDetect(baseColorHSV.b * repeat, thickness, thickness * 1.5); 31 | vec3 edgesColor = hsv2rgb(vec3(baseColorHSV.r, baseColorHSV.g, edgesSample)); 32 | 33 | gl_FragColor = vec4(edgesColor, edgesSample); 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-feedback.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D color; 4 | uniform sampler2D displace; 5 | uniform int useDisplace; 6 | uniform float displaceOffset; 7 | 8 | uniform vec2 offset; 9 | uniform float scale; 10 | 11 | varying vec2 uv; 12 | 13 | void main() { 14 | vec2 fuv = uv; 15 | 16 | if (useDisplace == 1) { 17 | float dStrength = texture2D(displace, uv).r; 18 | vec2 dOffset = vec2(0.5 - uv) * dStrength * displaceOffset * 0.1; 19 | fuv = uv + dOffset; 20 | } 21 | 22 | vec4 outColor = texture2D(color, fuv); 23 | 24 | gl_FragColor = outColor; 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-feedback.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | uniform float scale; 3 | uniform vec2 offset; 4 | attribute vec2 position; 5 | varying vec2 uv; 6 | 7 | void main() { 8 | uv = (0.5 * (position + 1.0) + offset) * scale + (1.0 - scale) * 0.5; 9 | gl_Position = vec4(position, 0.0, 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-gaussian-blur.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | uniform sampler2D color; 3 | uniform vec3 viewResolution; // [x, y, pxRatio] 4 | uniform vec2 blurDirection; 5 | varying vec2 uv; 6 | 7 | #pragma glslify: gaussBlur = require(glsl-fast-gaussian-blur/5) 8 | 9 | void main() { 10 | gl_FragColor = gaussBlur(color, uv, viewResolution.xy, blurDirection); 11 | } 12 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-hash-blur.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | uniform sampler2D color; 3 | uniform float radius; 4 | uniform float offset; 5 | uniform vec2 resolution; 6 | varying vec2 uv; 7 | 8 | #define ITERATIONS 13 9 | 10 | vec3 sampleColor(vec2 uv); 11 | #pragma glslify: hashBlur = require(glsl-hash-blur, sample = sampleColor, iterations = ITERATIONS) 12 | 13 | vec3 sampleColor(vec2 uv) { 14 | return texture2D(color, uv).rgb; 15 | } 16 | 17 | void main() { 18 | float aspect = resolution.x / resolution.y; 19 | float sampleRadius = radius / resolution.x; 20 | 21 | vec3 blurColor = hashBlur(uv, sampleRadius, aspect, offset); 22 | gl_FragColor = vec4(blurColor, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx-mirror.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D color; 4 | uniform vec3 viewResolution; // [x, y, pxRatio] 5 | 6 | varying vec2 uv; 7 | 8 | void main() { 9 | vec2 muv = vec2(1.0 - uv.x, uv.y); 10 | 11 | gl_FragColor = mix( 12 | texture2D(color, uv), 13 | texture2D(color, muv), 0.15); 14 | } 15 | -------------------------------------------------------------------------------- /src/renderer/shaders/post-fx.vert: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | attribute vec2 position; 3 | varying vec2 uv; 4 | 5 | void main() { 6 | uv = 0.5 * (position + 1.0); 7 | gl_Position = vec4(position, 0.0, 1.0); 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/shaders/vignette.glsl: -------------------------------------------------------------------------------- 1 | float vignette(vec2 coord, float radius, float smoothness) { 2 | float diff = radius - distance(coord, vec2(0.5, 0.5)); 3 | return smoothstep(-smoothness, smoothness, diff); 4 | } 5 | 6 | #pragma glslify: export(vignette) 7 | -------------------------------------------------------------------------------- /src/renderer/store/hubs/PaletteControllers.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export const PaletteControllers = new Vue({ 4 | methods: { 5 | emit (...args) { 6 | this.$emit(...args) 7 | }, 8 | 9 | on (...args) { 10 | this.$on(...args) 11 | }, 12 | 13 | off (...args) { 14 | this.$off(...args) 15 | } 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /src/renderer/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import modules from './modules' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | modules, 10 | strict: process.env.NODE_ENV !== 'production' 11 | }) 12 | -------------------------------------------------------------------------------- /src/renderer/store/modules/Editor.js: -------------------------------------------------------------------------------- 1 | import { vec2, vec3, vec4 } from 'gl-matrix' 2 | import { pixelRatio } from '@renderer/utils/screen' 3 | import { createControlsState } from './Palette' 4 | 5 | export function createCompositorState () { 6 | const seek = { 7 | timePrev: 0, 8 | velocity: 0, 9 | velocitySmoothed: 0, 10 | screen: vec2.create(), 11 | move: vec2.create(), 12 | movePrev: vec2.create(), 13 | wheelOffset: 0, 14 | hand: vec3.create(), 15 | index: null, 16 | maxDistance: 14, 17 | proximateDistance: 60, 18 | proximateIndices: [] 19 | } 20 | 21 | const drag = { 22 | shouldNavigate: false, 23 | shouldZoom: false, 24 | 25 | isDown: false, 26 | isDragging: false, 27 | isDrawing: false, 28 | isPanning: false, 29 | isZooming: false, 30 | 31 | panDown: vec2.create(), 32 | panOffset: vec2.create(), 33 | zoomDown: 1, 34 | zoomOffset: 0, 35 | pressure: 0.5, 36 | 37 | down: vec2.create(), 38 | move: vec2.create(), 39 | movePrev: vec2.create(), 40 | up: vec2.create(), 41 | upPrev: vec2.create(), 42 | upTimeLast: 0 43 | } 44 | 45 | const viewport = { 46 | showStats: false, 47 | didResize: false, 48 | pixelRatioNative: pixelRatio(), 49 | pixelRatioClamped: 0, 50 | size: vec2.create(), 51 | bounds: vec4.create(), 52 | resolution: vec2.create(), 53 | resolutionMax: vec2.create(), 54 | center: vec2.create(), 55 | offset: vec2.create(), 56 | scale: 1 57 | } 58 | 59 | const input = { 60 | alt: false, 61 | control: false, 62 | shift: false 63 | } 64 | 65 | const geometry = { 66 | activeSegment: null, 67 | activeDepth: null, 68 | prevPoint: null, 69 | candidatePoint: null, 70 | linkSizeMin: 12, 71 | linkSizeMinStrokeFactor: 2, 72 | shouldAppend: false, 73 | shouldAppendOnce: false, 74 | segments: [], 75 | vertices: [] 76 | } 77 | 78 | const simulation = { 79 | wasRunning: false, 80 | isRunning: false, 81 | isPaused: false, 82 | tick: 0, 83 | forcesCount: null, 84 | pinConstraintCount: null, 85 | localConstraintCount: null 86 | } 87 | 88 | const recording = { 89 | isActive: false, 90 | tick: 0 91 | } 92 | 93 | const renderer = { 94 | info: null, 95 | lastRenderHash: null, 96 | needsUpdate: false, // Force render escape hatch 97 | updateOverlapTick: 0, 98 | drawCalls: 0, 99 | fullScreenPasses: 0, 100 | lineQuads: 0, 101 | verticesCount: 0, 102 | segmentsCount: 0 103 | } 104 | 105 | const controls = createControlsState() 106 | 107 | return { 108 | seek, 109 | drag, 110 | viewport, 111 | input, 112 | geometry, 113 | simulation, 114 | recording, 115 | renderer, 116 | controls 117 | } 118 | } 119 | 120 | export function hashRenderState (state) { 121 | return '' + 122 | state.seek.index + 123 | state.seek.wheelOffset.toFixed(6) + 124 | proximateIndicesStr(state.seek.proximateIndices) + 125 | boolStr(state.drag.isDown) + 126 | boolStr(state.drag.isDragging) + 127 | vec2Str(state.drag.move) + 128 | vec2Str(state.drag.panOffset) + 129 | state.drag.zoomOffset + 130 | vec2Str(state.viewport.size) + 131 | vec2Str(state.viewport.offset) + 132 | state.viewport.scale + 133 | state.geometry.segments.length + 134 | state.geometry.vertices.length + 135 | boolStr(state.simulation.isRunning) + 136 | state.simulation.tick 137 | } 138 | 139 | function boolStr (bool) { 140 | return bool ? '1' : '0' 141 | } 142 | function vec2Str (v) { 143 | return v[0] + ',' + v[1] 144 | } 145 | function proximateIndicesStr (proximateIndices) { 146 | if (!proximateIndices.length) return '' 147 | const { index, factor } = proximateIndices[0] 148 | return index + ',' + factor 149 | } 150 | 151 | // TODO: Maybe implement some compositor state in observable store 152 | const state = {} 153 | const mutations = {} 154 | const actions = {} 155 | 156 | export default { 157 | state, 158 | mutations, 159 | actions 160 | } 161 | -------------------------------------------------------------------------------- /src/renderer/store/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The file enables `@src/store/index.js` to import all vuex modules 3 | * in a one-shot manner. There should not be any reason to edit this file. 4 | */ 5 | 6 | const files = require.context('.', false, /\.js$/) 7 | const modules = {} 8 | 9 | files.keys().forEach(key => { 10 | if (key === './index.js') return 11 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default 12 | }) 13 | 14 | export default modules 15 | -------------------------------------------------------------------------------- /src/renderer/utils/array.js: -------------------------------------------------------------------------------- 1 | const { sqrt } = Math 2 | const _map = Array.prototype.map 3 | 4 | export function createArrayCursor (array) { 5 | return new ArrayCursor(array) 6 | } 7 | 8 | function ArrayCursor (array) { 9 | this.array = array 10 | this.cursor = 0 11 | } 12 | 13 | Object.assign(ArrayCursor.prototype, { 14 | push () { 15 | const { array } = this 16 | let { cursor } = this 17 | for (let i = 0; i < arguments.length; i++) { 18 | array[cursor++] = arguments[i] 19 | } 20 | this.cursor = cursor 21 | } 22 | }) 23 | 24 | export function range (count) { 25 | const out = [] 26 | for (let i = 0; i < count; i++) { 27 | out.push(i) 28 | } 29 | return out 30 | } 31 | 32 | export function map (arr, predicate) { 33 | return _map.call(arr, predicate) 34 | } 35 | 36 | export function flatten2 (arr, Ctor = Array) { 37 | const count = arr.length 38 | const out = new Ctor(count * 2) 39 | for (let i = 0; i < count; i++) { 40 | out[i * 2] = arr[i][0] 41 | out[i * 2 + 1] = arr[i][1] 42 | } 43 | return out 44 | } 45 | 46 | export function expand2 (arr, Ctor = Array) { 47 | const count = Math.round(arr.length / 2) 48 | const out = new Array(count) 49 | for (let i = 0; i < count; i++) { 50 | const v = new Ctor(2) 51 | v[0] = arr[i * 2] 52 | v[1] = arr[i * 2 + 1] 53 | out[i] = v 54 | } 55 | return out 56 | } 57 | 58 | export function distance2 (aBuff, bBuff, ai, bi) { 59 | const aix = ai * 3 60 | const bix = bi * 3 61 | 62 | const dx = aBuff[aix] - bBuff[bix] 63 | const dy = aBuff[aix + 1] - bBuff[bix + 1] 64 | 65 | return sqrt(dx * dx + dy * dy) 66 | } 67 | -------------------------------------------------------------------------------- /src/renderer/utils/color.js: -------------------------------------------------------------------------------- 1 | const RGB_FACTOR = 1 / 255 2 | 3 | export function toVec4 (out, colr, hex, alpha) { 4 | const rgb = colr.fromHex(hex).toRgbArray() 5 | out[0] = rgb[0] * RGB_FACTOR 6 | out[1] = rgb[1] * RGB_FACTOR 7 | out[2] = rgb[2] * RGB_FACTOR 8 | out[3] = alpha 9 | return out 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/utils/ctor.js: -------------------------------------------------------------------------------- 1 | export function ctor (Ctor) { 2 | return function () { 3 | const instance = Object.create(Ctor.prototype) 4 | Ctor.apply(instance, arguments) 5 | return instance 6 | } 7 | } 8 | 9 | export function inherit (ParentCtor, Ctor, ...mixins) { 10 | Ctor.create = ctor(Ctor) 11 | if (ParentCtor) Ctor.prototype = Object.create(ParentCtor.prototype) 12 | Ctor.prototype.constructor = Ctor 13 | mixins.forEach((mixin) => { 14 | Object.assign(Ctor.prototype, mixin) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/utils/draw.js: -------------------------------------------------------------------------------- 1 | import cardinalSplineCurve from 'cardinal-spline' 2 | import { lerp } from '@renderer/utils/math' 3 | 4 | const { max } = Math 5 | 6 | export function curve (points, depths, 7 | strokeWidth, strokeWidthMod, strokeWidths, 8 | tension, segmentsCount, isClosed 9 | ) { 10 | const spline = cardinalSplineCurve(points, tension, segmentsCount, isClosed) 11 | const count = (isClosed ? spline.length - 2 : spline.length) / 2 12 | 13 | for (let i = 0; i < count; i++) { 14 | const ix = i * 2 15 | const iy = ix + 1 16 | const iwt = i / segmentsCount 17 | const iw = Math.floor(iwt * 0.99) 18 | 19 | const depth = lerp(depths[iw], depths[iw + 1], iwt - iw) 20 | const lineWidthSeg = lerp(strokeWidths[iw], strokeWidths[iw + 1], iwt - iw) 21 | const lineWidth = strokeWidth + strokeWidth * strokeWidthMod * lineWidthSeg 22 | 23 | this.lineWidth = max(0, lineWidth) 24 | 25 | if (i === 0) this.moveTo(spline[ix], spline[iy], depth) 26 | else this.lineTo(spline[ix], spline[iy], depth) 27 | } 28 | 29 | return spline 30 | } 31 | 32 | export function polyline (points, depths, 33 | strokeWidth, strokeWidthMod, strokeWidths, 34 | isClosed 35 | ) { 36 | const count = (isClosed ? points.length - 1 : points.length) / 2 37 | 38 | for (let i = 0; i < count; i++) { 39 | const ix = i * 2 40 | const iy = ix + 1 41 | 42 | const depth = depths[i] 43 | const lineWidthSeg = strokeWidths[i] 44 | const lineWidth = strokeWidth + strokeWidth * strokeWidthMod * lineWidthSeg 45 | 46 | this.lineWidth = max(0, lineWidth) 47 | 48 | if (i === 0) this.moveTo(points[ix], points[iy], depth) 49 | else this.lineTo(points[ix], points[iy], depth) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/renderer/utils/fbo.js: -------------------------------------------------------------------------------- 1 | export function createPostBuffers (regl, ...names) { 2 | const createBuffer = () => regl.framebuffer({ 3 | color: regl.texture({ 4 | mag: 'linear', 5 | min: 'linear', 6 | wrap: 'clamp', 7 | format: 'rgba' 8 | }), 9 | depth: true, 10 | stencil: false 11 | }) 12 | 13 | const buffers = { 14 | blank: createBuffer() 15 | } 16 | names.forEach((name) => { 17 | buffers[name] = createBuffer() 18 | }) 19 | 20 | return { 21 | get (name, size) { 22 | const buffer = buffers[name] 23 | if (size) buffer.resize(size[0], size[1]) 24 | return buffer 25 | }, 26 | 27 | use (name, scope) { 28 | const buffer = buffers[name] 29 | return buffer.use(scope) 30 | }, 31 | 32 | swap (nameA, nameB) { 33 | const bufferA = buffers[nameA] 34 | const bufferB = buffers[nameB] 35 | buffers[nameA] = bufferB 36 | buffers[nameB] = bufferA 37 | }, 38 | 39 | resize (name, size, factor = 1) { 40 | buffers[name].resize( 41 | Math.round(size[0] * factor), 42 | Math.round(size[1] * factor)) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/renderer/utils/function.js: -------------------------------------------------------------------------------- 1 | export function bindAll (context, ...names) { 2 | names.forEach((name) => { 3 | context[name] = context[name].bind(context) 4 | }) 5 | } 6 | 7 | export function delayResolution (delay) { 8 | return new Promise((resolve) => { 9 | delayFrame(resolve, delay) 10 | }) 11 | } 12 | 13 | export function delayFrame (fn, delay) { 14 | setTimeout(() => { 15 | window.requestAnimationFrame(fn) 16 | }, delay) 17 | } 18 | 19 | export function memoize (fn) { 20 | const cache = {} 21 | return (...args) => { 22 | const hash = args.map((v) => '' + v).join('') || '_' 23 | const value = cache[hash] 24 | if (value !== undefined) return value 25 | return (cache[hash] = fn.apply(null, args)) 26 | } 27 | } 28 | 29 | export function memoizeAll (object) { 30 | const nextObject = {} 31 | Object.keys(object).forEach((key) => { 32 | nextObject[key] = memoize(object[key]) 33 | }) 34 | return nextObject 35 | } 36 | 37 | // Throttle and debounce funcs based on: 38 | // jQuery throttle / debounce - v1.1 - 3/7/2010 39 | // http://benalman.com/projects/jquery-throttle-debounce-plugin/ 40 | // 41 | // Copyright (c) 2010 "Cowboy" Ben Alman 42 | // Dual licensed under the MIT and GPL licenses. 43 | // http://benalman.com/about/license/ 44 | 45 | export function throttle (delay, noTrailing, fn, debounceMode) { 46 | const state = { 47 | id: null, 48 | lastTime: 0 49 | } 50 | 51 | if (typeof noTrailing !== 'boolean') { 52 | debounceMode = fn 53 | fn = noTrailing 54 | noTrailing = undefined 55 | } 56 | 57 | return (...args) => { 58 | const elapsed = Date.now() - state.lastTime 59 | const exec = () => { 60 | state.lastTime = Date.now() 61 | fn.apply(this, args) 62 | } 63 | const clear = () => { 64 | state.id = null 65 | } 66 | 67 | if (debounceMode && !state.id) exec() 68 | if (state.id) clearTimeout(state.id) 69 | 70 | if (debounceMode == null && elapsed > delay) { 71 | exec() 72 | } else if (noTrailing !== true) { 73 | const nextAction = debounceMode ? clear : exec 74 | const nextDelay = debounceMode ? delay - elapsed : delay 75 | state.id = setTimeout(nextAction, nextDelay) 76 | } 77 | } 78 | } 79 | 80 | export function debounce (delay, immediate, fn) { 81 | return fn === undefined 82 | ? throttle(delay, immediate, false) 83 | : throttle(delay, fn, immediate !== false) 84 | } 85 | -------------------------------------------------------------------------------- /src/renderer/utils/gl-logger.js: -------------------------------------------------------------------------------- 1 | export function createGlLogger (gl) { 2 | const state = { 3 | log: [], 4 | enabled: false 5 | } 6 | 7 | const funcs = [] 8 | for (const key in gl) { 9 | const val = gl[key] 10 | if (typeof val === 'function') { 11 | funcs.push(key) 12 | } 13 | } 14 | 15 | funcs.forEach((key) => { 16 | const fn = gl[key] 17 | gl[key] = (...args) => { 18 | if (state.enabled) { 19 | state.log.push({ 20 | fn: key, 21 | args 22 | }) 23 | } 24 | return fn.apply(gl, args) 25 | } 26 | }) 27 | 28 | const enable = () => { 29 | state.log = [] 30 | state.enabled = true 31 | } 32 | 33 | const disable = () => { 34 | state.enabled = false 35 | } 36 | 37 | const getLog = () => { 38 | return state.log 39 | } 40 | 41 | return { 42 | enable, 43 | disable, 44 | getLog 45 | } 46 | } 47 | 48 | export function getGpuInfo (gl) { 49 | const debugInfo = gl.getExtension('WEBGL_debug_renderer_info') 50 | const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) 51 | const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) 52 | 53 | return { 54 | vendor, 55 | renderer 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/renderer/utils/logger.js: -------------------------------------------------------------------------------- 1 | const METHODS = [ 2 | 'log', 3 | 'info', 4 | 'time', 5 | 'timeEnd' 6 | ] 7 | 8 | function noop () {} 9 | 10 | function ensureConsole () { 11 | if (window.console) return window.console 12 | const noopConsole = {} 13 | METHODS.forEach((name) => { 14 | noopConsole[name] = noop 15 | }) 16 | return noopConsole 17 | } 18 | 19 | function createLogger () { 20 | const _console = ensureConsole() 21 | const logger = {} 22 | METHODS.forEach((name) => { 23 | logger[name] = (...args) => _console[name](...args) 24 | }) 25 | logger.logHash = (name, hash) => { 26 | Object.keys(hash).forEach((key) => { 27 | _console.log(`| ${key}: ${hash[key]}`) 28 | }) 29 | _console.log(name) 30 | } 31 | return logger 32 | } 33 | 34 | const logger = createLogger() 35 | export { logger } 36 | -------------------------------------------------------------------------------- /src/renderer/utils/loop.js: -------------------------------------------------------------------------------- 1 | export function createLoop (context_, sync_, update_, render_, delta) { 2 | const instance = {} 3 | const sync = context_ ? context_[sync_] : sync_ 4 | const update = context_ ? context_[update_] : update_ 5 | const render = context_ ? context_[render_] : render_ 6 | const context = context_ || instance 7 | 8 | const targetDelta = delta || (1 / 60 * 1000) 9 | const maxDelta = targetDelta 10 | let stepTime = 0 11 | 12 | let lastTick = 0 13 | let tick = 0 14 | 15 | let isLooping = false 16 | let lastTime 17 | 18 | function animateStep (time, delta) { 19 | stepTime += delta 20 | let steps = Math.floor(stepTime / targetDelta) 21 | 22 | if (steps > 0) { 23 | stepTime -= steps * targetDelta 24 | instance.didUpdate = true 25 | } 26 | 27 | while (steps > 0) { 28 | update.call(context, targetDelta) 29 | steps-- 30 | } 31 | } 32 | 33 | function renderStep (delta) { 34 | const stepProgress = stepTime / targetDelta 35 | render.call(context, targetDelta, stepProgress) 36 | } 37 | 38 | function animate () { 39 | if (!isLooping) { return } 40 | const time = Date.now() 41 | const delta = Math.min(maxDelta, time - lastTime) 42 | 43 | tick = sync(time, delta) 44 | if (tick !== lastTick) { 45 | instance.didUpdate = false 46 | animateStep(time, delta) 47 | } 48 | renderStep(delta) 49 | 50 | window.requestAnimationFrame(animate) 51 | lastTime = time 52 | lastTick = tick 53 | } 54 | 55 | instance.stop = () => { 56 | isLooping = false 57 | } 58 | 59 | instance.start = () => { 60 | lastTime = Date.now() 61 | isLooping = true 62 | animate() 63 | } 64 | 65 | instance.toggle = () => { 66 | if (isLooping) instance.stop() 67 | else instance.start() 68 | } 69 | 70 | return instance 71 | } 72 | -------------------------------------------------------------------------------- /src/renderer/utils/math.js: -------------------------------------------------------------------------------- 1 | // Linear mapping from range to range 2 | export function mapLinear (a1, a2, b1, b2, x) { 3 | return b1 + (x - a1) * (b2 - b1) / (a2 - a1) 4 | } 5 | 6 | export function clamp (min, max, x) { 7 | return Math.min(max, Math.max(min, x)) 8 | } 9 | 10 | export function lerp (a, b, t) { 11 | return (1 - t) * a + t * b 12 | } 13 | 14 | export function radialPosition (out, position, angle) { 15 | const { cos, sin } = Math 16 | out[0] = +cos(angle) * position[0] + position[1] * sin(angle) 17 | out[1] = -sin(angle) * position[0] + position[1] * cos(angle) 18 | return out 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/utils/number.js: -------------------------------------------------------------------------------- 1 | const { floor, round } = Math 2 | 3 | const ones = [ 4 | '', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'] 5 | const tens = [ 6 | '', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'] 7 | const teens = [ 8 | 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 9 | 'seventeen', 'eighteen', 'nineteen'] 10 | 11 | function convertHundreds (num) { 12 | if (num > 99) return `${ones[floor(num / 100)]} hundred ${convertTens(num % 100)}` 13 | else return convertTens(num) 14 | } 15 | 16 | function convertTens (num) { 17 | if (num < 10) return ones[num] 18 | else if (num >= 10 && num < 20) return teens[num - 10] 19 | else return `${tens[floor(num / 10)]} ${ones[num % 10]}` 20 | } 21 | 22 | export function numberToWords (num) { 23 | if (num === 0) return 'zero' 24 | else return convertHundreds(num) 25 | } 26 | 27 | export function roundToPlaces (num, places) { 28 | const factor = 10 ** places 29 | return round(num * factor) / factor 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/utils/pool.js: -------------------------------------------------------------------------------- 1 | export function createGroupPool ({ createItem }) { 2 | const pool = [] 3 | const groups = {} 4 | 5 | function getGroup (count) { 6 | if (groups[count]) { 7 | return groups[count] 8 | } 9 | 10 | const nextGroup = new Array(count) 11 | for (let i = 0; i < count; i++) { 12 | if (pool.length > i) { 13 | nextGroup[i] = pool[i] 14 | } else { 15 | const nextItem = createItem() 16 | nextGroup[i] = nextItem 17 | pool.push(nextItem) 18 | } 19 | } 20 | 21 | groups[count] = nextGroup 22 | return nextGroup 23 | } 24 | 25 | return { 26 | groups, 27 | getGroup 28 | } 29 | } 30 | 31 | export function createKeyedPool ({ createItem }) { 32 | const pool = [] 33 | const cache = {} 34 | 35 | function get (name) { 36 | if (cache[name]) { 37 | return cache[name] 38 | } 39 | 40 | const nextItem = createItem() 41 | cache[name] = nextItem 42 | pool.push(nextItem) 43 | return nextItem 44 | } 45 | 46 | return { 47 | cache, 48 | get 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/renderer/utils/screen.js: -------------------------------------------------------------------------------- 1 | import { clamp } from './math' 2 | 3 | export function isFullscreen () { 4 | return window.innerWidth === window.screen.width && 5 | window.innerHeight === window.screen.height 6 | } 7 | 8 | export function pixelRatio () { 9 | if (typeof window === 'undefined') return 1 10 | return window.devicePixelRatio || 1 11 | } 12 | 13 | export function pixelRatioClamped () { 14 | return clamp(1.5, 2, pixelRatio()) 15 | } 16 | 17 | export function clampPixelRatio (size, pixelRatio, maxDimension) { 18 | const biggestDim = Math.max(size[0], size[1]) * pixelRatio 19 | return pixelRatio / Math.max(1, biggestDim / maxDimension) 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/utils/svg.js: -------------------------------------------------------------------------------- 1 | export function pointsAttr (points) { 2 | return points.map((v) => v.join(',')).join(' ') 3 | } 4 | 5 | export function pointsCircle (precision, x, y, radius) { 6 | return pointsArc(precision, x, y, radius, 0, Math.PI * 2) 7 | } 8 | 9 | export function pointsArc (precision, x, y, radius, angleStart, angleEnd) { 10 | const angleDelta = angleEnd - angleStart 11 | return pointsAttr((new Array(precision)) 12 | .fill(0) 13 | .map((n, i) => { 14 | const angle = angleStart + i / precision * angleDelta 15 | const px = Math.cos(angle) * radius 16 | const py = Math.sin(angle) * radius 17 | return [x + px, y + py] 18 | })) 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/utils/task.js: -------------------------------------------------------------------------------- 1 | export function createTaskManager (...queueNames) { 2 | const tasks = {} 3 | const responders = {} 4 | 5 | queueNames.forEach((name) => { 6 | tasks[name] = [] 7 | }) 8 | 9 | // Sync 10 | 11 | tasks.add = (context, queueName, name_) => { 12 | const name = name_ || queueName 13 | const queue = tasks[queueName] 14 | const fn = context[name] || context 15 | queue.push({ 16 | context, fn 17 | }) 18 | } 19 | 20 | tasks.run = (queueName, ...args) => { 21 | tasks[queueName].forEach((task) => { 22 | task.fn.apply(task.context, args) 23 | }) 24 | } 25 | 26 | // Async 27 | 28 | tasks.defer = (context, queueName, name_) => { 29 | const name = name_ || queueName 30 | const queue = tasks[queueName] 31 | const fn = context[name] || context 32 | return new Promise((resolve, reject) => { 33 | queue.push({ 34 | context, fn, resolve, reject 35 | }) 36 | }) 37 | } 38 | 39 | tasks.flush = (queueName, ...args) => { 40 | return Promise.all(tasks[queueName].map((task) => { 41 | return task.fn.apply(task.context, args).then((res) => ( 42 | task.resolve(res) 43 | )) 44 | })).then(() => { 45 | tasks[queueName].length = 0 46 | }) 47 | } 48 | 49 | // Request / Response 50 | 51 | tasks.registerResponder = (responderName, context, fn) => { 52 | responders[responderName] = { 53 | context, 54 | fn 55 | } 56 | } 57 | 58 | tasks.registerResponders = (responderNames, context, namespace) => { 59 | responderNames.forEach((name) => { 60 | tasks.registerResponder(`${namespace}.${name}`, context, context[name]) 61 | }) 62 | } 63 | 64 | tasks.requestSync = (responderName, ...args) => { 65 | const responder = responders[responderName] 66 | if (!responder) throw new Error(`No responder for ${responderName}`) 67 | return responder.fn.apply(responder.context, args) 68 | } 69 | 70 | return tasks 71 | } 72 | -------------------------------------------------------------------------------- /src/renderer/utils/texture.js: -------------------------------------------------------------------------------- 1 | const SRC_BASE = process.env.IS_WEB ? '' : 'file://' 2 | 3 | const RGB_LINEAR = { 4 | channels: 3, 5 | min: 'linear', 6 | mag: 'linear', 7 | wrap: 'clamp' 8 | } 9 | 10 | // TODO: Handle image load errors 11 | // TODO: Convert npot textures with canvas 12 | export function createTextureManager (regl) { 13 | const cache = {} 14 | 15 | function addToCache (key, resource) { 16 | cache[key] = resource 17 | } 18 | 19 | function getTexture (key, src, opts = RGB_LINEAR) { 20 | if (key == null) return null 21 | 22 | const cached = cache[key] 23 | if (cached && cached.src === src) return cached.texture 24 | 25 | const texture = (cached && cached.texture) || regl.texture(opts) 26 | if (src == null) { 27 | if (!cached) addToCache(key, { src, texture }) 28 | return texture 29 | } 30 | 31 | const image = document.createElement('img') 32 | 33 | image.crossOrigin = 'Anonymous' 34 | image.onload = () => { 35 | const { naturalWidth, naturalHeight } = image 36 | texture({ 37 | width: naturalWidth, 38 | height: naturalHeight, 39 | data: image, 40 | ...opts 41 | }) 42 | } 43 | 44 | image.src = `${SRC_BASE}${src}` 45 | addToCache(key, { src, image, texture }) 46 | 47 | return texture 48 | } 49 | 50 | return { 51 | get: getTexture 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer/utils/timer.js: -------------------------------------------------------------------------------- 1 | function createTimer () { 2 | const timeBegin = {} 3 | const stats = {} 4 | let isEnabled = true 5 | 6 | return { 7 | enable (shouldEnable) { 8 | isEnabled = shouldEnable 9 | }, 10 | 11 | reset () { 12 | if (!isEnabled) return 13 | Object.keys(stats).forEach((key) => { 14 | stats[key] = 0 15 | }) 16 | }, 17 | 18 | begin (name) { 19 | if (!isEnabled) return 20 | timeBegin[name] = performance.now() 21 | }, 22 | 23 | end (name) { 24 | if (!isEnabled) return 25 | const curr = stats[name] || 0 26 | const next = performance.now() - timeBegin[name] 27 | return (stats[name] = curr + (next - curr) * 0.1) 28 | }, 29 | 30 | get (name, precision) { 31 | const val = stats[name] 32 | return (val || 0).toFixed(precision) 33 | } 34 | } 35 | } 36 | 37 | const timer = createTimer() 38 | export { timer } 39 | -------------------------------------------------------------------------------- /src/renderer/utils/tween.js: -------------------------------------------------------------------------------- 1 | // Tween to target by difference factor 2 | export function factorTween (name, context, target, factor) { 3 | const nextState = target[name] 4 | let state = context[name] 5 | if (state == null) state = context[name] = nextState 6 | context[name] += (nextState - state) * factor 7 | return context[name] 8 | } 9 | 10 | export const TWEEN_KEYS = { 11 | vec3: [0, 1, 2] 12 | } 13 | 14 | export function factorTweenAll (keys, name, context, target, factor) { 15 | const itemContext = context[name] 16 | const itemTarget = target[name] 17 | for (let i = 0; i < keys.length; i++) { 18 | const key = keys[i] 19 | factorTween(key, itemContext, itemTarget, factor) 20 | } 21 | return itemContext 22 | } 23 | 24 | // Tween to target by fixed step 25 | export function stepTween (name, context, target, step) { 26 | const nextState = target[name] 27 | let state = context[name] 28 | if (state == null) state = context[name] = nextState 29 | if (state === nextState) return state 30 | const dir = state < nextState ? 1 : -1 31 | 32 | if ((nextState - state) * dir < step) { 33 | context[name] = nextState 34 | return state 35 | } 36 | 37 | context[name] += step * dir 38 | return context[name] 39 | } 40 | 41 | // Easing funcs from https://github.com/tweenjs/tween.js 42 | // The MIT License 43 | // Copyright (c) 2010-2012 Tween.js authors. 44 | 45 | export function easeQuadraticIn (k) { 46 | return k * k 47 | } 48 | 49 | export function easeQuadraticOut (k) { 50 | return k * (2 - k) 51 | } 52 | 53 | export function easeQuadraticInOut (k) { 54 | if ((k *= 2) < 1) return 0.5 * k * k 55 | return -0.5 * (--k * (k - 2) - 1) 56 | } 57 | 58 | export function easeCubicIn (k) { 59 | return k * k * k 60 | } 61 | 62 | export function easeCubicOut (k) { 63 | return --k * k * k + 1 64 | } 65 | 66 | export function easeCubicInOut (k) { 67 | if ((k *= 2) < 1) return 0.5 * k * k 68 | return -0.5 * (--k * (k - 2) - 1) 69 | } 70 | 71 | export function continuousSawTooth (k) { 72 | return 1 - Math.abs(k % 1 - 1) 73 | } 74 | -------------------------------------------------------------------------------- /src/renderer/utils/word.js: -------------------------------------------------------------------------------- 1 | export function pluralize (count, singular, plural) { 2 | return count === 1 ? singular : plural 3 | } 4 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/.gitkeep -------------------------------------------------------------------------------- /static/exporter-templates/html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Microbium – <%= subTitle %> 6 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/icons/touchbar/constraints-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/constraints-alt.png -------------------------------------------------------------------------------- /static/icons/touchbar/constraints-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/constraints-alt@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/constraints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/constraints.png -------------------------------------------------------------------------------- /static/icons/touchbar/constraints@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/constraints@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/effects-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/effects-alt.png -------------------------------------------------------------------------------- /static/icons/touchbar/effects-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/effects-alt@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/effects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/effects.png -------------------------------------------------------------------------------- /static/icons/touchbar/effects@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/effects@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/forces-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/forces-alt.png -------------------------------------------------------------------------------- /static/icons/touchbar/forces-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/forces-alt@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/forces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/forces.png -------------------------------------------------------------------------------- /static/icons/touchbar/forces@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/forces@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/geometry-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/geometry-alt.png -------------------------------------------------------------------------------- /static/icons/touchbar/geometry-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/geometry-alt@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/geometry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/geometry.png -------------------------------------------------------------------------------- /static/icons/touchbar/geometry@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/geometry@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-pause-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-pause-active.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-pause-active@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-pause-active@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-pause-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-pause-inactive.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-pause-inactive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-pause-inactive@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-play.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-play@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-stop.png -------------------------------------------------------------------------------- /static/icons/touchbar/simulation-stop@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/simulation-stop@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/styles-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/styles-alt.png -------------------------------------------------------------------------------- /static/icons/touchbar/styles-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/styles-alt@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/styles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/styles.png -------------------------------------------------------------------------------- /static/icons/touchbar/styles@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/styles@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/tool-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/tool-alt.png -------------------------------------------------------------------------------- /static/icons/touchbar/tool-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/tool-alt@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/tool.png -------------------------------------------------------------------------------- /static/icons/touchbar/tool@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/tool@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/viewport-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/viewport-alt.png -------------------------------------------------------------------------------- /static/icons/touchbar/viewport-alt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/viewport-alt@2x.png -------------------------------------------------------------------------------- /static/icons/touchbar/viewport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/viewport.png -------------------------------------------------------------------------------- /static/icons/touchbar/viewport@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/static/icons/touchbar/viewport@2x.png -------------------------------------------------------------------------------- /test/fixtures/scene-001.mcrbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microbium/microbium-app/2642703ab412c510c3a987cee68d6d49e2a55cd1/test/fixtures/scene-001.mcrbm --------------------------------------------------------------------------------