├── .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 | 
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 |
2 |
3 |
4 |
5 |
6 |
7 |
43 |
44 |
49 |
--------------------------------------------------------------------------------
/src/renderer/MicrobiumEmbed.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
10 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/draw.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/effects.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/forces.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/geometry.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/minus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/select.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/styles.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/tool.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/src/renderer/assets/icons/viewport.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
15 |
16 |
17 |
64 |
65 |
147 |
--------------------------------------------------------------------------------
/src/renderer/components/display/Icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
64 |
--------------------------------------------------------------------------------
/src/renderer/components/editor/Cursor.vue:
--------------------------------------------------------------------------------
1 |
2 |
4 |
13 |
14 |
15 |
16 |
48 |
49 |
111 |
--------------------------------------------------------------------------------
/src/renderer/components/editor/QuickTool.vue:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
34 |
35 |
64 |
--------------------------------------------------------------------------------
/src/renderer/components/input/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
33 |
34 |
52 |
--------------------------------------------------------------------------------
/src/renderer/components/input/Checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
66 |
67 |
108 |
--------------------------------------------------------------------------------
/src/renderer/components/input/Color.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
17 |
18 |
48 |
--------------------------------------------------------------------------------
/src/renderer/components/input/File.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
25 |
26 |
63 |
--------------------------------------------------------------------------------
/src/renderer/components/input/FileRefresh.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
56 |
57 |
96 |
--------------------------------------------------------------------------------
/src/renderer/components/input/Range.vue:
--------------------------------------------------------------------------------
1 |
74 |
75 |
81 |
--------------------------------------------------------------------------------
/src/renderer/components/input/Select.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
25 |
26 |
55 |
--------------------------------------------------------------------------------
/src/renderer/components/input/Text.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
49 |
50 |
104 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Camera.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Camera
5 |
6 |
7 |
8 | Lens
9 |
10 |
11 |
12 |
13 |
{{ fovName }} field of view
14 |
16 |
17 |
18 |
19 |
20 |
21 | Position
22 |
23 |
24 |
25 |
26 |
{{ polarAngleName }} polar angle
27 |
29 |
30 |
31 |
32 |
33 |
34 |
{{ polarOffsetName }} polar offset
35 |
37 |
38 |
39 |
40 |
41 |
42 |
{{ depthOffsetName }} depth offset
43 |
45 |
46 |
47 |
48 |
49 |
50 |
{{ tweenFactorName }} tween factor
51 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Stereo Camera
61 |
62 |
63 |
64 | Eye
65 |
66 |
67 |
68 |
69 |
{{ stereoDistanceName }} distance
70 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
81 |
82 |
137 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Constraint.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ typeName }}
7 |
8 |
11 |
12 | constraint
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
{{ slipToleranceName }} slip tolerance
22 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
{{ engineFlexName }} distance flex
34 |
36 |
37 |
38 |
39 |
40 |
41 |
{{ engineCadenceName }} flex cadence
42 |
44 |
45 |
46 |
47 |
48 |
49 |
{{ engineCadenceDelayName }} instance flex delay
50 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
61 |
62 |
119 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/ConstraintList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 | {{ constraint.name }}
9 |
10 |
11 |
12 |
14 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
30 |
41 |
42 |
71 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/ConstraintPreview.vue:
--------------------------------------------------------------------------------
1 |
2 |
40 |
41 |
42 |
48 |
49 |
119 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Force.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ typeName }}
7 |
8 |
11 |
12 | force
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ positionTypeName }}
21 |
22 |
25 |
26 | position mapping
27 |
28 |
29 |
30 |
31 |
32 |
33 |
{{ polarAngleName }} polar angle
34 |
36 |
37 |
38 |
39 |
40 |
41 |
{{ polarOffsetName }} polar offset
42 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{ intensityTypeName }}
53 |
54 |
57 |
58 | intensity mapping
59 |
60 |
61 |
62 |
63 |
64 |
{{ intensityName }} intensity
65 |
67 |
68 |
69 |
70 |
71 |
72 |
{{ radiusName }} radius
73 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
83 |
84 |
151 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/ForceList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 | {{ force.name }}
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
42 |
43 |
72 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Group.vue:
--------------------------------------------------------------------------------
1 |
2 |
30 |
31 |
32 |
166 |
167 |
231 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Modes.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
42 |
43 |
65 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Section.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
43 |
44 |
62 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Simulation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Simulation
5 |
6 |
7 |
8 |
9 | {{ simulationIterationsName }} iterations
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{{ simulationSpeedName }} speed
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
65 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/StyleList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 | {{ style.name }}
9 |
10 |
11 |
12 |
14 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
43 |
44 |
75 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/StylePreview.vue:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
49 |
55 |
56 |
181 |
--------------------------------------------------------------------------------
/src/renderer/components/palette/Viewport.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Viewport
5 |
6 |
7 | Background
8 |
9 |
10 |
11 | {{ model.background.colorHex.toUpperCase() }}
12 |
13 | color
14 |
15 |
16 |
17 |
18 |
19 |
{{ backgroundAlphaName }} fade out
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Overlay
29 |
30 |
31 |
32 | {{ model.overlay.colorHighlightHex.toUpperCase() }}
33 |
34 | highlight color
35 |
36 |
37 |
38 |
39 |
40 |
{{ overlayAlphaName }} opacity
41 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Resolution
50 |
51 |
52 |
53 |
54 |
55 | {{ pixelRatioName }} pixel density
56 |
57 |
58 |
59 |
60 |
61 |
62 |
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
--------------------------------------------------------------------------------