├── playground
├── github.png
├── fullscreen.css
├── README.md
├── demos
│ ├── julia.js
│ ├── mandelbulb.js
│ ├── kifs.js
│ ├── twist.js
│ ├── let_it_shine.js
│ ├── pipe.js
│ ├── alien_portal.js
│ ├── union.js
│ ├── texture_transforms.js
│ ├── voxels_and_edges.js
│ ├── superformula.js
│ ├── noise.js
│ ├── intro.js
│ ├── snare.js
│ ├── textures.js
│ ├── geometries.js
│ ├── postprocessing.js
│ ├── constructors.js
│ ├── portal2.js
│ ├── csg.js
│ ├── procedural_textures.js
│ ├── hydra.js
│ ├── gifs_and_links.js
│ ├── p5.js
│ ├── textures_tutorial.js
│ ├── audio.js
│ ├── livecoding.js
│ ├── tutorial_1.js
│ └── lighting.js
├── index.htm
├── main.css
└── codemirror.css
├── js
├── index.js
├── int.js
├── float.js
├── objects.js
├── utils.js
├── background.js
├── osc.js
├── fog.js
├── vignette.js
├── textureWrap.js
├── noise.js
├── audio.js
├── alterations.js
├── sceneNode.js
├── var.js
├── transform.js
├── mergepass.js
├── vec.js
├── camera.js
├── renderFragmentShader.js
├── scene.js
├── material.js
├── distanceDeformations.js
└── distanceOperations.js
├── .gitignore
├── demo.html
├── gulpfile.js
├── README.markdown
├── LICENSE
├── package.json
├── CREDITS.markdown
└── docs
└── index.html
/playground/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/charlieroberts/marching/HEAD/playground/github.png
--------------------------------------------------------------------------------
/playground/fullscreen.css:
--------------------------------------------------------------------------------
1 | .CodeMirror-fullscreen {
2 | position: fixed;
3 | top: 0; left: 0; right: 0; bottom: 0;
4 | height: auto;
5 | z-index: 9;
6 | }
7 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Marching = require( './main.js' )
4 |
5 | Marching.__export = Marching.export
6 | Marching.export = obj => {
7 | obj.march = Marching.createScene.bind( Marching )
8 | Marching.__export( obj )
9 | }
10 |
11 | window.Marching = Marching
12 |
13 | module.exports = Marching
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Numerous always-ignore extensions
2 | *.diff
3 | *.err
4 | *.orig
5 | *.log
6 | *.rej
7 | *.swo
8 | *.swp
9 | *.vi
10 | *~
11 | *.sass-cache
12 |
13 | # OS or Editor folders
14 | .DS_Store
15 | Thumbs.db
16 | .cache
17 | .project
18 | .settings
19 | .tmproj
20 | *.esproj
21 |
22 | node_modules
23 |
24 | nbproject
25 | *.sublime-project
26 | *.sublime-workspace
27 |
--------------------------------------------------------------------------------
/js/int.js:
--------------------------------------------------------------------------------
1 | const emit_int = function( a ) {
2 | if( a % 1 !== 0 )
3 | return Math.round( a )
4 | else
5 | return a
6 | }
7 |
8 | const IntPrototype = {
9 | type: 'int',
10 | emit() { return emit_int( this.x ) },
11 | emit_decl() { return "" }
12 | }
13 |
14 |
15 | const Int = function( x=0 ) {
16 | const f = Object.create( IntPrototype )
17 | f.x = x
18 | return f
19 | }
20 |
21 | module.exports = Int
22 |
--------------------------------------------------------------------------------
/playground/README.md:
--------------------------------------------------------------------------------
1 | To build the marching.js playground, run the following command from the top level of this repo (*not* from within the playground directory):
2 |
3 | `npx browserify playground/environment.js -o playground/playground.bundle.js`
4 |
5 | You can then start a server in the top level:
6 |
7 | `npx http-server . -p 10000`
8 |
9 | ... and access the page at `http://localhost:10000/playground/index.htm`
10 |
--------------------------------------------------------------------------------
/playground/demos/julia.js:
--------------------------------------------------------------------------------
1 | module.exports=`mat1 = Material( 'phong', Vec3(.0),Vec3(.5),Vec3(1), 32, Vec3(0,.25,1) )
2 | tex = Texture( 'cellular', { strength:.15, scale:20 })
3 |
4 | march(
5 | Julia(1.5)
6 | .material( mat1 )
7 | .texture( tex )
8 | .bump( tex, .05 )
9 | )
10 | .light(
11 | Light( Vec3(5,5,8), Vec3(1), .025 )
12 | )
13 | .fog( 1, Vec3(0) )
14 | .render()
15 | .camera(0,0,1.75)
16 | `
17 |
--------------------------------------------------------------------------------
/js/float.js:
--------------------------------------------------------------------------------
1 | const emit_float = function( a ) {
2 | if (a % 1 === 0)
3 | return a.toFixed( 1 )
4 | else
5 | return a
6 | }
7 |
8 | const FloatPrototype = {
9 | type: 'float',
10 | emit() { return emit_float( this.x ) },
11 | emit_decl() { return "" }
12 | }
13 |
14 |
15 | const Float = function( x=0 ) {
16 | const f = Object.create( FloatPrototype )
17 | f.x = x
18 | return f
19 | }
20 |
21 | module.exports = Float
22 |
--------------------------------------------------------------------------------
/playground/demos/mandelbulb.js:
--------------------------------------------------------------------------------
1 | module.exports = `mat1 = Material( 'phong', Vec3(.0),Vec3(.5),Vec3(1), 32, Vec3(0) )
2 |
3 | march(
4 | Mandelbulb( 6.5 )
5 | .rotate( 270, 1,0,0 )
6 | .translate( 0,0,.5 )
7 | .material( mat1 ),
8 |
9 | Plane( Vec3(0,0,1), .5 ).material( 'white glow')
10 | )
11 | .light(
12 | Light( Vec3(-3,2,4), Vec3(1), .25 ),
13 | Light( Vec3(0,0,4), Vec3(1), .95 )
14 | )
15 | .render()
16 | .camera( 0,0,3 )`
17 |
--------------------------------------------------------------------------------
/playground/demos/kifs.js:
--------------------------------------------------------------------------------
1 | module.exports =`// inspired by http://roy.red/folding-the-koch-snowflake-.html
2 | // this is still under development
3 | march(
4 | r = Rotation(
5 | // number of iterations, fold amount, radius, threshold, scale, center, material
6 | k = KIFS( 6,.25,.01,.01, 2, null, Material.white ),
7 | Vec3( 1 )
8 | )
9 | )
10 | .render( 3,true )
11 | .camera( 0,0,3 )
12 |
13 | onframe = t=> {
14 | k.fold = -.1 + sin( t ) * .5
15 | r.angle = t/4
16 | }`
17 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
24 |
25 |
--------------------------------------------------------------------------------
/playground/demos/twist.js:
--------------------------------------------------------------------------------
1 | module.exports =`Material.default = Material.grey
2 |
3 | m = march(
4 | StairsUnion(
5 | Repeat(
6 | t = Twist(
7 | PolarRepeat(
8 | Cylinder( Vec2(.025,2.75) ),
9 | 10,
10 | .25
11 | ).rotate( 270, 1,0,0 ),
12 | Vec3(0)
13 | ),
14 | Vec3(2.5,0,0)
15 | ),
16 | Plane(),
17 | .35
18 | )
19 | )
20 | .light( Light( Vec3(.4), Vec3(.5) ) )
21 | .background( Vec3(.5,.6,.7) )
22 | .render(3, true )
23 | .camera( 2, 0, 6 )
24 |
25 | onframe = time => {
26 | t.amount = Math.sin(time/4)*5
27 | }`
28 |
--------------------------------------------------------------------------------
/playground/demos/let_it_shine.js:
--------------------------------------------------------------------------------
1 | module.exports = `march(
2 | int = Intersection(
3 | Sphere( 2 ).material(),
4 | box = Mandelbox( .3,2.5,7 ).material( 'red' )
5 | )
6 | )
7 | .background(
8 | Vec3(.125,0,.125)
9 | )
10 | .light(
11 | Light( Vec3(5), Vec3(1,0,1), .1)
12 | )
13 | .post(
14 | Focus( .575 ),
15 | rays = Godrays( 1.01,.01,1,.65 )
16 | )
17 | .fog(
18 | .2, Vec3(.125,0,.125)
19 | )
20 | .render( 'fractal.low' )
21 | .camera( 0,0,3.5 )
22 |
23 | onframe = t => {
24 | box.size = 2.4 + sin(t) * .15
25 | box.fold = .3 + cos(t/2) * .1
26 | int.rotate(t*10,.5,.5,sin(t/5))
27 | }
28 |
29 | rays.color = [1,.5,1]`
30 |
--------------------------------------------------------------------------------
/playground/demos/pipe.js:
--------------------------------------------------------------------------------
1 | module.exports = `Marching.lighting.mode = 'orenn'
2 |
3 | march(
4 | SmoothUnion(
5 | rot = Rotation(
6 | pipe = Pipe(
7 | Box(),
8 | sphere = Sphere(2.5, null, Material.blue),
9 | .5
10 | ),
11 | Vec3(1,.5,.5)
12 | ),
13 | Plane( Vec3(0,1,.75), 2.25 ),
14 | .125
15 | )
16 | )
17 | .light(
18 | Light( Vec3(0,5,0), Vec3(0,.25,1), .05 )
19 | )
20 | .background( Vec3(.1) )
21 | .shadow( 8 )
22 | .render(3, true)
23 | .camera( 0,0,7 )
24 |
25 | callbacks.push( time => {
26 | pipe.c = .8 + Math.abs(Math.sin(time/2)) / 4
27 | rot.angle = time
28 | sphere.radius = 2 + Math.abs( Math.sin(time/4))
29 | })`
30 |
--------------------------------------------------------------------------------
/playground/demos/alien_portal.js:
--------------------------------------------------------------------------------
1 | module.exports = `mat = Material( 'phong', Vec3(.05), Vec3(1), Vec3(3), 64, Vec3( 0,6,4) )
2 |
3 | m = march(
4 | StairsUnion(
5 | Plane( Vec3(0,.5,0) ).material( mat ),
6 | PolarRepeat(
7 | PolarRepeat(
8 | Torus82().material( mat ),
9 | 20,
10 | 2.75
11 | ).rotate(90, 1,0,0 ),
12 | 25,
13 | 2
14 | ),
15 | .25
16 | ),
17 | Plane( Vec3(0,.5,0) )
18 | .bump( Texture('noise', { strength:1.5, scale:13 }) )
19 | )
20 | .fog( .15, Vec3(0) )
21 | .light( Light( Vec3(0,.25,0), Vec3(1,.5,.25), .125 ) )
22 | .post( Antialias(2), Bloom(.35,1.25,4,4), Focus(.05, .005 ))
23 | .resolution(1)
24 | .render()
25 | .camera( 0, 0, 10 )`
26 |
--------------------------------------------------------------------------------
/js/objects.js:
--------------------------------------------------------------------------------
1 | const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen } = require( './var.js' )
2 | const { Vec2, Vec3, Vec4 } = require( './vec.js' )
3 | const SceneNode = require( './sceneNode.js' )
4 | const { param_wrap, MaterialIDAlloc } = require( './utils.js' )
5 |
6 |
7 | const { Union, SmoothUnion, Intersection, Substraction } = require( './distanceOperations.js' )
8 | const Subtraction = Substraction
9 | const { Scale, Repeat } = require( './domainOperations' )
10 |
11 |
12 | const __out = {
13 | Vec2,
14 | Vec3,
15 | Vec4,
16 | Var,
17 | SceneNode,
18 | Union,
19 | Intersection,
20 | Substraction,
21 | Subtraction,
22 | Scale,
23 | Repeat,
24 | SmoothUnion
25 | }
26 |
27 | Object.assign( __out, require( './primitives.js' ) )
28 |
29 | module.exports = __out
30 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp')
2 | var browserify = require('browserify')
3 | var source = require('vinyl-source-stream')
4 | var watchify = require( 'watchify' )
5 | var gutil = require( 'gulp-util' )
6 | const tsify = require('tsify')
7 |
8 | gulp.task('build', function () {
9 |
10 | return browserify({
11 | debug:true,
12 | entries: './js/index.js'
13 | })
14 | .plugin( tsify )
15 | .bundle()
16 | .pipe( source('index.js') )
17 | .pipe( gulp.dest( './dist/' ) )
18 |
19 | })
20 |
21 | watchify.args.entries = './js/index.js'
22 | watchify.args.debug = true
23 |
24 | var b = watchify( browserify( watchify.args ) )
25 | b.on( 'update', bundle )
26 | b.on( 'log', gutil.log )
27 |
28 | gulp.task('default', bundle)
29 |
30 | function bundle() {
31 | return b.bundle().pipe( source('index.js') ).pipe( gulp.dest( './dist/' ) )
32 | }
33 |
--------------------------------------------------------------------------------
/playground/demos/union.js:
--------------------------------------------------------------------------------
1 | module.exports = `// unions and smooth unions can be used to
2 | // combine two geometries together. a Union
3 | // creates boundaries with reasonably well-defined
4 | // edges.
5 |
6 | scene([
7 | Union(
8 | Sphere(),
9 | TriPrism( null, Vec2(2) )
10 | )
11 | ], 90 )
12 |
13 |
14 | // smooth union blends these edges together,
15 | // at an adjustable amount
16 | scene([
17 | s = SmoothUnion(
18 | Sphere(),
19 | TriPrism( null, Vec2(2) ),
20 | .5
21 | )
22 | ], 90 )
23 |
24 | callbacks.push( time => s.blend = (time/4) % 1 )
25 |
26 |
27 | // add some noise for fun...
28 | scene([
29 | r = Rotation(
30 | SmoothUnion(
31 | Sphere( Noise(.25, 1.5 ) ),
32 | TriPrism( null, Vec2(3) )
33 | ),
34 | Vec3(1,0,0),
35 | 1.5
36 | )
37 | ])
38 |
39 | callbacks.push( time => r.angle = time )`
40 |
--------------------------------------------------------------------------------
/playground/demos/texture_transforms.js:
--------------------------------------------------------------------------------
1 | module.exports =`// In this demo the important point
2 | // to note is that transformations can be applied to
3 | // most functions, not just geometries. Here, a
4 | // rotation is applied to a Union while translation
5 | // is applied to the domain created by Repeat().
6 |
7 | march(
8 | rpt = Repeat(
9 | union = Union2(
10 | cyl = Cylinder(Vec2(1,1.5))
11 | .texture('dots', {scale:2}),
12 | cyl2 = Cylinder(Vec2(.95,1.5))
13 | .rotate(90,0,0,1)
14 | .texture('stripes', {scale:1}),
15 | cyl3 = Cylinder(Vec2(.95,1.5))
16 | .rotate(90,1,0,0)
17 | .texture('checkers', {scale:5})
18 | )
19 | .scale(.15),
20 | .75
21 | )
22 | )
23 | .fog( .5, Vec3( 0 ) )
24 | .render( 'repeat.low' )
25 |
26 | onframe = time => {
27 | union.rotate( time*65,1,.5,.5 )
28 | rpt.translate( 0,0,time/3 )
29 | }`
30 |
--------------------------------------------------------------------------------
/playground/demos/voxels_and_edges.js:
--------------------------------------------------------------------------------
1 | module.exports = `// this uses a voxelized scene with
2 | // a procedural texture generated by hydra
3 | // alongside of various post-processing effects,
4 | // inclulding edge detection and depth-of-field.
5 |
6 | // hit alt+c to hide the code editor
7 | // and take control of the camera using the WASD
8 | // and arrow keys. if your graphics card can handle
9 | // it, try changing the quality variable to 'med' or
10 | // 'high' and then re-executing all the code below.
11 |
12 | quality = 'low'
13 |
14 | use('hydra').then( ()=> {
15 |
16 | hydra = Hydra()
17 | hydra.osc(10,.1,10).out()
18 |
19 | march(
20 | Repeat(
21 | sphere = Sphere(.75).texture( hydra.texture() ),
22 | 2
23 | )
24 | )
25 | .voxel(.05)
26 | .fog( .5, Vec3(0) )
27 | .post( Edge(), Invert(1), f = Focus(.15) )
28 | .render( 'voxel.'+quality )
29 |
30 | sphere.tex.scale = 8
31 |
32 | onframe = time => sphere.radius = .65 + sin( time/5 ) * .15
33 |
34 | })`
35 |
--------------------------------------------------------------------------------
/js/utils.js:
--------------------------------------------------------------------------------
1 | const Var = require('./var.js').Var
2 | const { Vec2, Vec3, Vec4 } = require( './vec.js' )
3 |
4 | // Wrapper
5 | function param_wrap( v, __default, name=null ) {
6 | if( v === undefined || v === null ) return __default()
7 | if( v.__isVar === true ) return v
8 |
9 | return Var( v, name )
10 | }
11 |
12 | const MaterialID = {
13 | current: 0,
14 | alloc() {
15 | return MaterialID.current++
16 | },
17 | clear() {
18 | MaterialID.current = 0
19 | }
20 | }
21 |
22 | const processVec2 = function( val ) {
23 | if( typeof val === 'number' )
24 | val = Vec2( color )
25 | else if( Array.isArray( val ) )
26 | val = Vec2( val[0], val[1] )
27 |
28 | return val
29 | }
30 |
31 | const processVec3 = function( val ) {
32 | if( typeof val === 'number' )
33 | val = Vec3( val )
34 | else if( Array.isArray( val ) )
35 | val = Vec3( val[0], val[1] || val[0], val[2] || val[0] )
36 |
37 | return val
38 | }
39 |
40 | module.exports = { param_wrap, MaterialID, processVec2, processVec3 }
41 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # marching.js
2 |
3 | [Online Playground (Chrome/Firefox)](https://charlieroberts.github.io/marching/playground/)
4 | [Atom plugin](https://atom.io/packages/atom-marching)
5 | [Reference](https://charlieroberts.github.io/marching/docs/index.html)
6 |
7 | [](https://postimg.cc/vcPgC5NB)
8 | Marching.js is a JavaScript shader compiler specifically focused on ray marching via signed distance functions. The goals of this project are:
9 |
10 | - Expose beginning programmers to constructive solid geometry (CSG) concepts
11 | - Enable JS programmers to explore CSG without having to learn GLSL
12 | - Provide a terse API suitable for live coding performance
13 |
14 | Marching.js builds on the [work of many other people](https://github.com/charlieroberts/marching/blob/master/CREDITS.markdown).
15 |
16 | ## Development
17 | The library is compiled using gulp. Run `npm install` to install all necessary dependencies, and then `npm run build` to build the library.
18 |
--------------------------------------------------------------------------------
/playground/demos/superformula.js:
--------------------------------------------------------------------------------
1 | module.exports = `// a 3D superformula essentially two 2D supershapes,
2 | // first six coefficients govern one, second
3 | // six coefficients govern the other.
4 |
5 | mat1 = Material( 'phong', Vec3(.0),Vec3(0),Vec3(1), 16, Vec3(0,.25,4) )
6 |
7 | m = march(
8 | s = SuperFormula(
9 | 1, 1, 16, 1, 1, 1,
10 | 1, 1, 16, 1, 1, 1
11 | )
12 | .translate( 0, .5, .85 )
13 | .material( mat1 )
14 | .texture( 'truchet', { color:.125, scale:30 } ),
15 |
16 | Plane( Vec3(0,1,0), 1 ).material( Material('phong', Vec3(.15), Vec3(1) ) )
17 | )
18 | .light(
19 | Light( Vec3(0,5,0), Vec3(.25,.25,.5), .5 ),
20 | Light( Vec3(3,3,0), Vec3(.5), .5 )
21 | )
22 | .fog( .25, Vec3(0) )
23 | .render( 2, true )
24 | .camera( 0,0,5 )
25 |
26 | onframe = time => {
27 | t = 12
28 | s.n1_1 = Math.PI + Math.sin( time )
29 | s.n1_2 = Math.PI + Math.cos( time )
30 | s.m_1 = Math.sin( time / 2 ) * t
31 | s.m_2 = Math.cos( time / 2 ) * t
32 | s.rotate( time * 10, 0,1,0 )
33 | }
34 |
35 | // thanks to https://github.com/Softwave/glsl-superformula`
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Charlie Roberts
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/playground/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | marching.js playground
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/playground/demos/noise.js:
--------------------------------------------------------------------------------
1 | module.exports = `// Noise() can be used in place of any float or Vector member.
2 |
3 | march(
4 | // noisy radius
5 | Sphere( Noise(), Vec3(-2,0,0) ),
6 |
7 | // noisy y-size
8 | Box( Vec3(2,0,0), Vec3( 1, Noise(.25,1), 0 ) )
9 | ).render()
10 |
11 | // Noise( (float)strength=.25, (float)bias=1, (float)timeMod=1 )
12 |
13 | march( Sphere( Noise() ) ).render()
14 | march( Sphere( Noise(.5) ) ).render()
15 | march( Sphere( Noise(.25,2,.5) ) ).render()
16 |
17 | // Displace() works a bit differently, in that it
18 | // takes the output of a distance function and alters
19 | // it, rather than providing an input to a distance
20 | // function (like Noise()).
21 |
22 | // displace the rays colliding with our box
23 | d = Displace( Box( Vec3(0), Vec3(2,4,2)), Vec3(1) )
24 |
25 | // rotate the displacement on the x and z axes
26 | r = Rotation( d, Vec3(1,0,1), 0 )
27 |
28 | // compile the shader
29 | march( r ).render( ) 3, true )
30 |
31 | callbacks.push(
32 | // change rotattion
33 | time => r.angle = time,
34 | // change displacement
35 | time => d.displacement.x = 1 + Math.sin(time/4) % 1
36 | )
37 |
38 | // move the camera back to see the whole scene
39 | camera.pos.z = 10`
40 |
--------------------------------------------------------------------------------
/playground/demos/intro.js:
--------------------------------------------------------------------------------
1 | module.exports = `march(
2 | Repeat(
3 | StairsDifference(
4 | Sphere(2),
5 | Repeat( Sphere( .1 ), .25 ),
6 | .25,
7 | 10
8 | ),
9 | 6
10 | ).translate( 0,-1.5 ),
11 | Plane().texture( 'cellular', { strength:-.5, scale:10 })
12 | )
13 | .fog( .1, Vec3(0,0,.25) )
14 | .background( Vec3(0,0,.25) )
15 | .render()
16 |
17 | /* __--__--__--__--__--__--__--____
18 |
19 | select code and hit ctrl+enter to
20 | execute. alt+enter (option+enter on
21 | a mac) executes a block of code.
22 | ctrl+shift+g toggles hiding
23 | the gui/code. try the other demos
24 | using the menu in the upper right
25 | corner. when you're ready to start
26 | coding go through the tutorials
27 | found in the same menu. Click on
28 | the help link for a reference.
29 |
30 | For a nice intro on ray marching and
31 | signed distance functions,which are
32 | the techniques used by marching.js,
33 | see:
34 |
35 | https://bit.ly/2qRMrpe
36 |
37 | Finally, if you'd prefer to work
38 | outside of the browser, try the
39 | Atom plugin for marching.js:
40 |
41 | https://atom.io/packages/atom-marching
42 |
43 | ** __--__--__--__--__--__--__--__*/`
44 |
--------------------------------------------------------------------------------
/playground/demos/snare.js:
--------------------------------------------------------------------------------
1 | module.exports = `// because, like, marching.js, snare drums, marching...
2 |
3 | const grey = Material( 'phong', Vec3(0), Vec3(3), Vec3(2), 32, Vec3(0,0,2) ),
4 | white = Material( 'phong', Vec3(0), Vec3(50), Vec3(1), 8, Vec3(0,50,2) ),
5 | a = .45, b = .25 // for side bands on drum
6 |
7 | stick = ()=> {
8 | return Union(
9 | Groove(
10 | Cylinder( Vec2(.2,2.5), Vec3(.5,4.5,-.5) )
11 | .move( .5,4.5,-.5 )
12 | .material( grey ),
13 | Capsule( Vec3(.5, 2,-.5), Vec3(.5,2.25,-.5), 1 ).material( grey ),
14 | .08
15 | ),
16 | Capsule( Vec3(.5, 2,-.5), Vec3(.5,2.25,-.5), .175 ).material( grey )
17 | )
18 | }
19 |
20 | stickL = stick()
21 | .rotate( 300, 0,0,1 )
22 | .move( -1.5,.25, 0 )
23 |
24 | stickR = stick()
25 | .rotate( 300, 0,.75,.5 )
26 | .move( -2.05,.0, -.5 )
27 |
28 | drum = RoundUnion(
29 | Union2(
30 | ChamferDifference(
31 | Cylinder( Vec2(2.25, .45) ).material( grey ),
32 | Cylinder( Vec2(2,.4) ).material( grey ),
33 | .1
34 | ),
35 | Cylinder( Vec2(2,.3) ).material( white )
36 | ),
37 | PolarRepeat(
38 | Quad( Vec3(0,-a,-b), Vec3(0,-a,0), Vec3(0,a,b), Vec3(0,a,0) ).material( grey ),
39 | 15,
40 | 2.25
41 | ),
42 | .05
43 | )
44 |
45 | march(
46 | drum.rotate( 60, 1,1,0 ),
47 | stickL,
48 | stickR
49 | )
50 | .background(Vec3(0))
51 | .shadow(.75)
52 | .light( Light( Vec3(2,5,8), Vec3(1), .125 ) )
53 | .render()
54 | .camera( 0,0,7 )`
55 |
--------------------------------------------------------------------------------
/playground/demos/textures.js:
--------------------------------------------------------------------------------
1 | module.exports =`zigzag = Box(.5)
2 | .move( -2,1.25 )
3 | .texture(
4 | 'zigzag',
5 | { scale:5 }
6 | )
7 |
8 | dots = Box(.5)
9 | .move( -0,1.25 )
10 | .texture(
11 | 'dots',
12 | {
13 | scale:10,
14 | color:[1,0,0]
15 | }
16 | )
17 |
18 | noise = Box(.5)
19 | .move( 2,1.25 )
20 | .texture(
21 | 'noise',
22 | {
23 | wrap:true,
24 | scale:20,
25 | color:[1,0,0]
26 | }
27 | )
28 |
29 | truchet = Box(.5)
30 | .move( -2,-.15 )
31 | .texture(
32 | 'truchet',
33 | {
34 | scale:20,
35 | color:[0,1,1]
36 | }
37 | )
38 |
39 | stripes = Box(.5)
40 | .move( -0,-.15)
41 | .texture(
42 | 'stripes',
43 | {
44 | scale:10,
45 | color:[1,0,0]
46 | }
47 | )
48 |
49 | checkers = Box(.5)
50 | .move( 2, -.15 )
51 | .texture(
52 | 'checkers',
53 | {
54 | scale:20,
55 | color1:[0,1,1],
56 | color2:[1,0,0]
57 | }
58 | )
59 |
60 | cellular = Box(.5)
61 | .move( -2, -1.55 )
62 | .texture(
63 | 'cellular',
64 | {
65 | scale:10,
66 | strength:1
67 | }
68 | )
69 |
70 | rainbows = Box(.5)
71 | .move( -0,-1.55)
72 | .texture(
73 | 'rainbow',
74 | {
75 | scale:4
76 | }
77 | )
78 |
79 | voronoi = Box(.5)
80 | .move( 2,-1.55 )
81 | .texture(
82 | 'voronoi',
83 | {
84 | wrap:true,
85 | scale:10,
86 | mode:2
87 | }
88 | )
89 |
90 | bg = Plane( Vec3(0,0,1), .5 ).material('white glow')
91 |
92 | march(
93 | zigzag, dots, noise,
94 | truchet, stripes, cellular,
95 | checkers, rainbows, voronoi, bg
96 | )
97 | .render()`
98 |
--------------------------------------------------------------------------------
/js/background.js:
--------------------------------------------------------------------------------
1 | const SceneNode = require( './sceneNode.js' ),
2 | { param_wrap, MaterialID } = require( './utils.js' ),
3 | { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen } = require( './var.js' )
4 |
5 | const { Vec2, Vec3, Vec4 } = require( './vec.js' )
6 |
7 | const BG = function( Scene, SDF ) {
8 |
9 | const Background = function( color ) {
10 | if( SDF.memo.background === undefined ) {
11 | const bg = Object.create( Background.prototype )
12 |
13 | if( color !== undefined && color.type === 'vec3' ) color = Vec4( color.x, color.y, color.z, 1 )
14 | const __color = param_wrap( Vec4( color ), vec4_var_gen( 0,0,0,1, 'bg' ), 'bg' )
15 |
16 | Object.defineProperty( bg, 'color', {
17 | get() { return __color },
18 | set( v ) {
19 | __color.var.set( v )
20 | }
21 | })
22 |
23 | // this refers to the current scene via implicit binding in scene.js
24 | //this.postprocessing.push( bg )
25 | bg.__backgroundColor = color
26 | this.__background = bg
27 |
28 | SDF.memo.background = true
29 | }
30 | return this
31 | }
32 |
33 | Background.prototype = SceneNode()
34 |
35 | Object.assign( Background.prototype, {
36 | emit() {
37 | return ''// this.color.emit()
38 | },
39 |
40 | emit_decl() {
41 | //let str = this.color.emit_decl()
42 | //SDF.memo.background = true
43 |
44 | const out = this.__backgroundColor === undefined
45 | ? 'vec4 bg = vec4(0.,0.,0.,1.);'
46 | : `vec4 bg = vec4(${ this.__backgroundColor.x }, ${this.__backgroundColor.y}, ${this.__backgroundColor.z}, 1.);`
47 |
48 | return out
49 | },
50 |
51 | update_location( gl, program ) {
52 | this.color.update_location( gl, program )
53 | },
54 |
55 | upload_data( gl ) {
56 | this.color.upload_data( gl )
57 | }
58 | })
59 |
60 | return Background
61 | }
62 |
63 | module.exports = BG
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marching",
3 | "version": "1.2.0",
4 | "description": "Marching.js is a JavaScript library that compiles GLSL ray marchers.",
5 | "main": "js/index.js",
6 | "devDependencies": {
7 | "@bandaloo/merge-pass": "^0.6.4",
8 | "browserify": "^14.5.0",
9 | "codemirror": "^5.5.0",
10 | "get-pixels": "^3.3.2",
11 | "gl-mat4": "^1.2.0",
12 | "gl-texture2d": "^2.1.0",
13 | "gl-vec3": "^1.1.3",
14 | "glsl-diffuse-oren-nayar": "^1.0.2",
15 | "glsl-noise": "0.0.0",
16 | "glsl-sdf-primitives": "0.0.1",
17 | "glsl-specular-gaussian": "^1.0.0",
18 | "glsl-superformula": "^1.0.3",
19 | "glsl-voronoi-noise": "^1.2.2",
20 | "glsl-worley": "^1.0.2",
21 | "glslify": "^6.4.1",
22 | "gulp": "^4.0.2",
23 | "gulp-util": "^3.0.8",
24 | "mousetrap": "^1.6.1",
25 | "osc": "^2.4.4",
26 | "toastr": "^2.1.4",
27 | "tsify": "^4.0.2",
28 | "tweakpane": "^1.5.3",
29 | "typescript": "^3.9.6",
30 | "vinyl-source-stream": "^2.0.0",
31 | "vinyl-transform": "^1.0.0",
32 | "watchify": "^3.10.0"
33 | },
34 | "scripts": {
35 | "test": "echo \"Error: no test specified\" && exit 1",
36 | "build": "npx gulp build",
37 | "watch": "npx gulp",
38 | "playground": "npx browserify playground/environment.js -o playground/playground.bundle.js"
39 | },
40 | "author": "Charlie Roberts",
41 | "license": "MIT",
42 | "repository": {
43 | "type": "git",
44 | "url": "git+https://github.com/charlieroberts/marching.git"
45 | },
46 | "keywords": [
47 | "glsl",
48 | "ray",
49 | "marching",
50 | "graphics",
51 | "csg",
52 | "constructive",
53 | "solid",
54 | "geometry",
55 | "javascript",
56 | "webgl"
57 | ],
58 | "bugs": {
59 | "url": "https://github.com/charlieroberts/marching/issues"
60 | },
61 | "browserify": {
62 | "transform": [
63 | "glslify"
64 | ]
65 | },
66 | "homepage": "https://github.com/charlieroberts/marching#readme"
67 | }
68 |
--------------------------------------------------------------------------------
/js/osc.js:
--------------------------------------------------------------------------------
1 | const osc = require( 'osc/dist/osc-browser' ),
2 | { Vec2, Vec3, Vec4 } = require( './vec.js' )
3 |
4 | function unpackArg( arg ) {
5 | if( Array.isArray( arg ) )
6 | return Vec4( arg.map( a => a.value ) )
7 |
8 | return arg.value
9 | }
10 |
11 | const OSC = {
12 | client: null,
13 |
14 | start( url ) {
15 | console.log("STARTING", url)
16 | OSC.client = new osc.WebSocketPort({ url, metadata: true, unpackSingleArgs: true })
17 | OSC.client.on( 'message', OSC.messageCallback )
18 | OSC.client.open()
19 | console.log("STARTING", OSC.client)
20 | },
21 |
22 | stop() {
23 | OSC.client.close()
24 | },
25 |
26 | messageCallback(message) {
27 | const parts = message.address.split('/')
28 |
29 | let object = window
30 | for( const part of parts.slice( 1, -1 ) ) {
31 | object = object[part]
32 |
33 | if( !object ) {
34 | throw new Error( `can't handle OSC message ${message.address}: no such key '${part}'` )
35 | }
36 | }
37 |
38 | const key = parts[parts.length - 1]
39 | switch( key ) {
40 | /* methods */
41 | case 'translate':
42 | case 'rotate':
43 | case 'scale':
44 | case 'fog':
45 | case 'material':
46 | object[key].apply( object, message.args.map( unpackArg ) )
47 | break
48 |
49 | /* variables */
50 | default: {
51 | const old = object[key]
52 |
53 | if( !old ) {
54 | throw new Error( `can't handle OSC message ${message.address}: no such key ${key}` )
55 | }
56 |
57 | switch( old.type ) {
58 | case 'int':
59 | case 'float':
60 | object[key] = message.args.value
61 | break
62 |
63 | case 'vec2':
64 | case 'vec3':
65 | case 'vec4':
66 | object[key] = Vec4( message.args.map( a => a.value ) )
67 | break
68 |
69 | default:
70 | throw new Error( `can't handle OSC message ${message.address}: type ${old.type}` )
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 | module.exports = OSC
78 |
--------------------------------------------------------------------------------
/playground/demos/geometries.js:
--------------------------------------------------------------------------------
1 | module.exports = `v3 = Vec3, v2 = Vec2
2 |
3 | mat1 = Material( 'phong', .05, 1, .5 )
4 |
5 | // Torus: Vec2 radius(outer,inner)
6 | torus = Torus( v2(.5,.1) )
7 | .move( -2.25,1.5 )
8 | .rotate( 90, 1,0,0 )
9 | .material( mat1 )
10 |
11 | // Torus82: Vec2 radius
12 | torus82 = Torus82()
13 | .move( -.75,1.5,0 )
14 | .rotate( 90, 1,0,0 )
15 | .material( mat1 )
16 |
17 | // Torus88: Vec2 radius
18 | torus88 = Torus88()
19 | .move( .6,1.5,0 )
20 | .rotate( 90, 1,0,0 )
21 | .material( mat1 )
22 |
23 | // Sphere: float radius
24 | sphere = Sphere(.65)
25 | .move( 2.25,1.5,0 )
26 | .material( mat1 )
27 |
28 | // Box: Vec3 size
29 | box = Box( .5 )
30 | .move( -2,0,0 )
31 | .material( mat1 )
32 |
33 | // Cylinder: Vec2( radius, height )
34 | cylinder = Cylinder( v2(.35,.5) )
35 | .move( -.75,0,0 )
36 | .material( mat1 )
37 |
38 | // Cone: Vec3 dimensions
39 | cone = Cone( v3(.1, .075, .925) )
40 | .move( .6,.45,0 )
41 | .material( mat1 )
42 |
43 | // Octahedron: float size
44 | octahedron = Octahedron( .65 )
45 | .move(2.25,0,0)
46 | .material( mat1 )
47 |
48 | // HexPrism: Vec2 size(radius, depth)
49 | hexPrism = HexPrism( v2(.6,.45) )
50 | .move( -2,-1.5,0 )
51 | .material( mat1 )
52 |
53 | // TriPrism: Vec2 size(radius, depth)
54 | triPrism = TriPrism( v2(.85,.3) )
55 | .move( -.5,-1.75,0 )
56 | .material( mat1 )
57 |
58 | // RoundBox: Vec3 size, roundness
59 | roundBox = RoundBox( v3(.45), .15 )
60 | .move( 1.15,-1.5,0 )
61 | .material( mat1 )
62 |
63 | // Capsule: Vec3 start, Vec3 end, float radius
64 | capsule = Capsule( v3( 0, -.55, 0), v3(0,.4,0), .25 )
65 | .move( 2.5,-1.5, 0 )
66 | .material( mat1 )
67 |
68 | mat = Material( 'phong', v3(0), v3(.1), v3(.25) )
69 | // Plane: Vec3 normal, float distance
70 | plane = Plane( v3(0,0,1), 1).material( mat )
71 |
72 | march(
73 | torus, torus82, torus88, sphere,
74 | box, cylinder, cone, capsule,
75 | octahedron, hexPrism, triPrism, roundBox,
76 | plane
77 | )
78 | .light(
79 | Light( Vec3(2,0,5), Vec3(1), .2 )
80 | )
81 | .render()
82 | .camera( 0,0, 6 )`
83 |
--------------------------------------------------------------------------------
/CREDITS.markdown:
--------------------------------------------------------------------------------
1 | # Credits
2 |
3 | [Chi Shen](http://shenchi.github.io) performed [initial development work on this project](https://github.com/shenchi/glsllab).
4 |
5 | The project uses code from [Stack.gl ecosystem](http://stack.gl/), specifically:
6 | - [glsl-raytrace](https://github.com/glslify/glsl-raytrace) by [Hugh Kennedy](https://github.com/hughsk).
7 | - [glsl-sdf-normal](https://github.com/glslify/glsl-sdf-normal) by [Hugh Kennedy](https://github.com/hughsk).
8 | - [glsl-camera-ray](https://github.com/glslify/glsl-camera-ray) by [Hugh Kennedy](https://github.com/hughsk).
9 | - many geometric primitives from [glsl-sdf-primitives](https://github.com/marklundin/glsl-sdf-primitives) by [Mark Lundin](https://github.com/marklundin).
10 | - Ambient occlusion and soft shadows are taken from the [glsl-sdf-ops package](https://github.com/marklundin/glsl-sdf-ops) by [Mark Lundin](https://github.com/marklundin).
11 |
12 | The project takes a variety of operators from the exceptional [hg_sdf library](http://mercury.sexy/hg_sdf/) by the [Mercury demogroup](http://mercury.sexy/).
13 |
14 | The defaut lighting scheme is adapted from [this demo](https://www.shadertoy.com/view/Xds3zN) by [Inigo Quilez](http://www.iquilezles.org). Many operators are also taken from his [excellent tutorial on SDFs](http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm).
15 |
16 | The Mandelbulb primitive is taken from [this demo](https://www.shadertoy.com/view/ltfSWn) by [Inigo Quilez](http://www.iquilezles.org).
17 |
18 | The Mandalay fractal is taken from [this demo](https://www.shadertoy.com/view/MdV3Wz) by Dave Hoskins.
19 |
20 | The Julia fractal primitive is taken from [this demo](https://www.shadertoy.com/view/MsfGRr) by [Inigo Quilez](http://www.iquilezles.org).
21 |
22 | The SuperFormula primitive is taken from [a repo](https://github.com/Softwave/glsl-superformula) by [JC Leyba](http://s0ftwave.com).
23 |
24 | A [matrix data structure by Evan Wallace](https://github.com/evanw/lightgl.js/).
25 |
26 | The Worley noise texture is taken from [a repo](https://github.com/Erkaman/glsl-worley) by [Eric Arnebäck](https://erkaman.github.io/).
27 |
--------------------------------------------------------------------------------
/js/fog.js:
--------------------------------------------------------------------------------
1 | const SceneNode = require( './sceneNode.js' ),
2 | { param_wrap, MaterialID } = require( './utils.js' ),
3 | { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' )
4 |
5 | const { Vec2, Vec3, Vec4 } = require( './vec.js' )
6 |
7 | const Fogger = function( Scene, SDF ) {
8 |
9 | const Fog = function( amount=0.055, color ) {
10 | const fog = Object.create( Fog.prototype )
11 | const __amount = param_wrap( amount, float_var_gen( amount ) )
12 |
13 | Object.defineProperty( fog, 'amount', {
14 | get() { return __amount },
15 | set( v ) {
16 | __amount.set( v )
17 | }
18 | })
19 |
20 | const __color = param_wrap( Vec3(color), vec3_var_gen( 0,0,0 ) )
21 |
22 | Object.defineProperty( fog, 'color', {
23 | get() { return __color },
24 | set( v ) {
25 | __color.var.set( v )
26 | }
27 | })
28 |
29 | // this refers to the current scene via implicit binding in scene.js
30 | this.postprocessing.push( fog )
31 |
32 | return this
33 | }
34 |
35 | Fog.prototype = SceneNode()
36 |
37 | Object.assign( Fog.prototype, {
38 | emit() {
39 | return ` color.rgb = applyFog( color.rgb, t.x, ${this.amount.emit()} );`
40 | },
41 |
42 | emit_decl() {
43 | let str = this.amount.emit_decl() + this.color.emit_decl()
44 | const preface = ` vec3 applyFog( in vec3 rgb, in float distance, in float amount ) {
45 | float fogAmount = 1. - exp( -distance * amount );
46 | vec3 fogColor = ${this.color.emit()};
47 | return mix( rgb, fogColor, fogAmount );
48 | }
49 | `
50 | if( SDF.memo.fog === undefined ) {
51 | str = str + preface
52 | SDF.memo.fog = true
53 | }else{
54 | str = ''
55 | }
56 |
57 | return str
58 | },
59 |
60 | update_location( gl, program ) {
61 | this.amount.update_location( gl, program )
62 | this.color.update_location( gl, program )
63 | },
64 |
65 | upload_data( gl ) {
66 | this.amount.upload_data( gl )
67 | this.color.upload_data( gl )
68 | }
69 | })
70 |
71 | return Fog
72 | }
73 |
74 | module.exports = Fogger
75 |
--------------------------------------------------------------------------------
/js/vignette.js:
--------------------------------------------------------------------------------
1 | const SceneNode = require( './sceneNode.js' ),
2 | { param_wrap, MaterialID } = require( './utils.js' ),
3 | { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' )
4 |
5 | const Vignette = function( Scene, SDF ) {
6 |
7 | const Vgn = function( radius=0.1, smoothness=16 ) {
8 | const vgn = Object.create( Vgn.prototype )
9 | const __radius = param_wrap( radius, float_var_gen( radius ) )
10 |
11 | Object.defineProperty( vgn, 'radius', {
12 | get() { return __radius },
13 | set( v ) {
14 | __radius.set( v )
15 | }
16 | })
17 |
18 | const __smoothness = param_wrap( smoothness, float_var_gen( smoothness ) )
19 |
20 | Object.defineProperty( vgn, 'smoothness', {
21 | get() { return __smoothness },
22 | set( v ) {
23 | __smoothness.set( v )
24 | }
25 | })
26 |
27 | // this refers to the current scene via implicit binding in scene.js
28 | this.postprocessing.push( vgn )
29 |
30 | return this
31 | }
32 |
33 | Vgn.prototype = SceneNode()
34 |
35 | Object.assign( Vgn.prototype, {
36 | emit() {
37 | return ` color *= vignette( uv, ${this.radius.emit()}, ${this.smoothness.emit()} );`
38 | },
39 |
40 | emit_decl() {
41 | let str = this.radius.emit_decl() + this.smoothness.emit_decl()
42 | // taken from https://gist.github.com/r-lyeh-archived/170b53fcdc0e17afcf15
43 | // originally iq
44 | const preface = ` float vignette(vec2 uv, float radius, float smoothness) {
45 | return radius + 0.5*smoothness*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y);
46 | }
47 | `
48 | if( SDF.memo.vgn === undefined ) {
49 | str = str + preface
50 | SDF.memo.vgn = true
51 | }else{
52 | str = ''
53 | }
54 |
55 | return str
56 | },
57 |
58 | update_location( gl, program ) {
59 | this.radius.update_location( gl, program )
60 | this.smoothness.update_location( gl, program )
61 | },
62 |
63 | upload_data( gl ) {
64 | this.radius.upload_data( gl )
65 | this.smoothness.upload_data( gl )
66 | }
67 | })
68 |
69 | return Vgn
70 | }
71 |
72 | module.exports = Vignette
73 |
--------------------------------------------------------------------------------
/playground/demos/postprocessing.js:
--------------------------------------------------------------------------------
1 | module.exports = `/* __--__--__--__--__--__--__--____
2 |
3 | Marching.js wraps the mergepass lib
4 | for performing post-processing:
5 |
6 | https://tinyurl.com/y4zxayey
7 |
8 | There's a bunch of presets that make
9 | fun effects. These effects are called
10 | "post" processing because they are
11 | applied to the marching.js scene after
12 | it has been rendered... think of them
13 | as filters in Photoshop.
14 |
15 | All post-proccessing effects are
16 | applied through the .post() method.
17 |
18 | ** __--__--__--__--__--__--__--__*/
19 |
20 | // simple but fun---godrays
21 |
22 | march(
23 | i = Intersection(
24 | Box(1.5),
25 | Repeat( Sphere(.125), .5 )
26 | )
27 | )
28 | .post( rays = Godrays() )
29 | .render('med')
30 |
31 | onframe = t => i.rotate( t*10, 1,.5,.25 )
32 |
33 | // try below one line at a time
34 | rays.color = [1,0,1]
35 | rays.decay = 1.01
36 | // change z position of rays to put
37 | // them in the box
38 | rays.threshold = .25
39 |
40 |
41 | // __--__--__--__--__--__--__--____
42 | // ok, let's try and example based
43 | // on simulating a depth-of-field effect.
44 | // this will blur areas out of focus.
45 |
46 | march(
47 | Repeat(
48 | Box().texture('dots').scale(.25),
49 | 1.5
50 | )
51 | )
52 | .post( f = Focus() )
53 | .fog( .15, Vec3(0) )
54 | .render('med')
55 |
56 | onframe = t => camera.pos.z = t/3
57 |
58 | // change depth target
59 | f.depth = .15
60 | // change width of focus
61 | f.radius = .05
62 |
63 |
64 | // __--__--__--__--__--__--__--____
65 | // last but not least, let's combine
66 | // a bunch of options. look in the reference
67 | // for more post-processing effects to layer!
68 |
69 | march(
70 | ri = RoundIntersection(
71 | s = Sphere(2).material('green').texture('dots', { scale:75 }),
72 | r = Repeat(
73 | Box().scale(.1).material('green').texture('dots', { scale:10 }),
74 | .5
75 | )
76 | )
77 | )
78 | .post(
79 | bloom = Bloom(.25,8),
80 | g = Godrays(),
81 | Edge()
82 | )
83 | .render('med')
84 |
85 | z = 0
86 | onframe = t => {
87 | z += abs( sin(t/2) * .02 )
88 | r.translate( 0,0,z )
89 | ri.rotate( t*15,1,.5,.25 )
90 | }
91 |
92 | g.decay = 1.01`
93 |
--------------------------------------------------------------------------------
/js/textureWrap.js:
--------------------------------------------------------------------------------
1 | module.exports = `
2 | // p = point on surface, p0 = object center
3 | vec2 getUVCubic(vec3 p ){
4 | vec3 absp = abs(p);
5 |
6 | // First conditional: If the point is in one of the sextants to the
7 | // left or right of the x-axis, the uv cordinate will be (0.5*p.zy)/(p.x).
8 | // If you trace a line out to a zy plane that is 0.5 units from the zero origin,
9 | // (0.5*p.xyz)/(p.x) will be the result, and
10 | // the yz components will be our uv coordinates, hence (0.5*p.zy)/(p.x).
11 |
12 | vec2 uv = ((absp.x>=absp.y)&&(absp.x>=absp.z))
13 | ? (0.5*p.zy)/(p.x)
14 | : ((absp.y>=absp.z)&&(absp.y>=absp.x)) ? (0.5*p.xz)/(p.y) : (-0.5*p.xy)/(p.z);
15 |
16 | //We still need to determine which side our uv cordinates are on so
17 | //that the texture orients the right way. Note that there's some
18 | // redundancy there, which I'll fix at some stage. For now, it works, so I'm not touching it. :)
19 | if( ((p.x<0.)&&(absp.x>=absp.y)&&(absp.x>=absp.z))
20 | || ((p.y<0.)&&(absp.y>=absp.z)&&(absp.y>=absp.x))
21 | || ((p.z>0.)&&(absp.z>=absp.x)&&(absp.z>=absp.y)) ) uv.y*=-1.;
22 |
23 | // Mapping the uv range from [-0.5, 0.5] to [0.0, 1.0].
24 | return (uv+0.5);
25 | }
26 | vec4 triplanar(vec3 n, vec4 texx, vec4 texy, vec4 texz, bool adjust3d, bool rescale) {
27 | //if (doflipz) n.z = -n.z;
28 | if (rescale) {
29 | texx = 2.0*texx - 1.0;
30 | texy = 2.0*texy - 1.0;
31 | texz = 2.0*texz - 1.0;
32 | }
33 | if (adjust3d) {
34 | texx.x *= sign(n.x);
35 | texy.y *= sign(n.y);
36 | texz.z *= sign(n.z);
37 | }
38 | //if (justtexy) return texy;
39 | vec3 weights = abs(n);
40 | //if (doweightcorrection) weights /= dot(weights,vec3(1)); // Keep spherical!
41 | return mat4(texx,texy,texz,vec4(0))*vec4(weights,0);
42 | }
43 | `
44 | /*
45 | module.exports = `vec3 t3(sampler2D tex, vec3 p, vec3 n)
46 | {
47 | mat3 R = mat3(vec3(cos(T),sin(T),0),vec3(-sin(T),cos(T),0),vec3(0,0,-1));
48 | p *= R/8.0;
49 | n *= R;
50 | #ifdef Smooth
51 | return (texture(tex,p.xy).rgb*n.z*n.z
52 | +texture(tex,p.zy).rgb*n.x*n.x
53 | +texture(tex,p.xz).rgb*n.y*n.y);
54 | #else
55 | return (texture(tex,p.xy).rgb
56 | +texture(tex,p.zy).rgb
57 | +texture(tex,p.xz).rgb)/3.0;
58 | #endif
59 | }`
60 | */
61 |
--------------------------------------------------------------------------------
/js/noise.js:
--------------------------------------------------------------------------------
1 | const glsl = require( 'glslify' )
2 | const SceneNode = require( './sceneNode.js' )
3 | const { param_wrap, MaterialID } = require( './utils.js' )
4 | const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen } = require( './var.js' )
5 |
6 | const getNoise = function( SDF ) {
7 | Noise = function( strength=.25, bias=1, timeMod=1 ) {
8 | const op = Object.create( Noise.prototype )
9 | op.type = 'string'
10 | op.isGen = true
11 |
12 | const defaultValues = [.5,.5,.5]
13 |
14 | op.matId = MaterialID.alloc()
15 |
16 | const __strength = param_wrap( strength, float_var_gen( strength ) )
17 | const __timeMod = param_wrap( timeMod, float_var_gen( timeMod ) )
18 |
19 | Object.defineProperty( op, 'strength', {
20 | get() { return __strength },
21 | set(v) {
22 | __strength.var.set( v )
23 | }
24 | })
25 | Object.defineProperty( op, 'timeMod', {
26 | get() { return __timeMod },
27 | set(v) {
28 | __timeMod.var.set( v )
29 | }
30 | })
31 | const __bias = param_wrap( bias, float_var_gen( bias ) )
32 |
33 | Object.defineProperty( op, 'bias', {
34 | get() { return __bias},
35 | set(v) {
36 | __bias.var.set( v )
37 | }
38 | })
39 | return op
40 | }
41 |
42 | Noise.prototype = SceneNode()
43 |
44 | Noise.prototype.emit = function ( __name ) {
45 | let name = __name === undefined ? 'p' : __name
46 |
47 | const out = `(${this.bias.emit()} + snoise( vec4( p.xyz, time * ${this.timeMod.emit()} )) * ${this.strength.emit()})`
48 |
49 | const output = {
50 | out,
51 | preface:''
52 | }
53 |
54 | return output
55 | }
56 | Noise.prototype.glsl = glsl` #pragma glslify: noise = require('glsl-noise/simplex/4d')`
57 |
58 | Noise.prototype.emit_decl = function () {
59 | let str = this.strength.emit_decl() + this.timeMod.emit_decl() + this.bias.emit_decl()
60 |
61 | if( SDF.memo.noise === undefined ) {
62 | str = Noise.prototype.glsl + str
63 | SDF.memo.noise = true
64 | }
65 |
66 | return str
67 | };
68 |
69 | Noise.prototype.update_location = function(gl, program) {
70 | this.strength.update_location( gl, program )
71 | this.timeMod.update_location( gl, program )
72 | this.bias.update_location( gl, program )
73 | }
74 |
75 | Noise.prototype.upload_data = function(gl) {
76 | this.strength.upload_data( gl )
77 | this.timeMod.upload_data( gl )
78 | this.bias.upload_data( gl )
79 | }
80 |
81 | return Noise
82 |
83 | }
84 |
85 | module.exports = getNoise
86 |
--------------------------------------------------------------------------------
/playground/demos/constructors.js:
--------------------------------------------------------------------------------
1 | module.exports=`/* __--__--__--__--__--__--__--____
2 |
3 | Note: this tutorial is only for people
4 | who know GLSL and want to incorporate
5 | their own shader code into marching.js
6 |
7 | If you know some GLSL, it's not too
8 | hard to add your own forms to
9 | marching.js. The process consists of:
10 |
11 | 1. Defining all the interactive
12 | properties of your form.
13 |
14 | 2. Defining a shader function that
15 | will create your form when the proper
16 | arguments are passed. This function
17 | will only be added to the shader once.
18 |
19 | 3. Defining a call to your shader
20 | function that passes the correct
21 | arguments; this function call will
22 | be inserted into the shader wherever
23 | an instance of your form is placed
24 | in the scene graph.
25 |
26 | ** __--__--__--__--__--__--__--__*/
27 |
28 | spongeDesc = {
29 | // 'parameters' define our points of interaction.
30 | // types are float, int, vec2, vec3, and vec4
31 | parameters:[
32 | { name:'frequency', type:'float', default:5 }
33 | ],
34 |
35 | // this is the primary signed distance function
36 | // used by your form. The first argument should
37 | // always be a point you're testing, subsequent
38 | // arguments are your parameters.
39 | glslify:
40 | \`float sineSponge( vec3 p, float frequency ) {
41 | p *= frequency;
42 | return (sin(p.x) + sin(p.y) + sin( p.z )) / 3. / frequency;
43 | }\`,
44 |
45 | // this is a function that will insert code calling
46 | // to your distance function wherever your form is
47 | // placed in a graph. It is passed the name of
48 | // the point (of type vec3) that is being sampled
49 | // by the ray marcher. Following passing
50 | // the point, you will pass each of the input parameters
51 | // to your signed distance function (in this case, only frequency)
52 | primitiveString( pName ) {
53 | return \`sineSponge( \${pName}, \${this.frequency.emit()} )\`
54 | }
55 | }
56 |
57 | // create and store resulting constructor in a global variable
58 | Sponge = Marching.primitives.create( 'Sponge', spongeDesc )
59 |
60 | march(
61 | obj = RoundDifference(
62 | Sphere(2).material('glue'),
63 | s = Sponge( 5 ).material('glue'),
64 | .125
65 | )
66 | )
67 | .light(
68 | Light( Vec3(0,5,5), Vec3(1), .5 )
69 | )
70 | .background( Vec3(.125) )
71 | .render(3,true).camera(0,0,5)
72 |
73 | onframe = t => {
74 | obj.rotate( t*10 )
75 | s.frequency = 10 + sin(t/2) * 5
76 | }`
77 |
--------------------------------------------------------------------------------
/playground/demos/portal2.js:
--------------------------------------------------------------------------------
1 | module.exports = `mat1 = Material( 'phong', Vec3(.5), Vec3(1), Vec3(3), 64, Vec3( 0,1,4 ) )
2 | mat2 = Material( 'phong', Vec3(.05), Vec3(1), Vec3(3), 64, Vec3( 2,4,8 ) )
3 | mat3 = Material( 'phong', Vec3(.5), Vec3(1), Vec3(0), 16, Vec3( 0,1,1 ) )
4 | mat4 = Material( 'phong', Vec3(.05), Vec3(1,0,0), Vec3(1), 16, Vec3( 4,2,5 ) )
5 | mat5 = Material( 'phong', Vec3(.05), Vec3(.5,.5,.5), Vec3(.5,.5,.5), 32, Vec3( 0,2,.25 ) )
6 | mat6 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 64, Vec3( 0,4,8 ) )
7 | mat7 = Material( 'orenn', Vec3(.05), Vec3(1), Vec3(1), 12, Vec3( 0,0,1 ) )
8 |
9 | lights = [
10 | Light( Vec3(0,2,0), Vec3(1,.35,.35), 1 ),
11 | Light( Vec3(4,0,-12), Vec3(1), .25 ),
12 | Light( Vec3(-4,0,-12), Vec3(1), .25 ),
13 | Light( Vec3(0,-2,0), Vec3(1,.35,.35), 2 )
14 | ]
15 |
16 | twopi = Math.PI * 2
17 | count = 25
18 | radius = 6
19 | for( let i = 0; i < count; i++ ) {
20 | const percent = i / count
21 | lights.push( Light(
22 | Vec3( Math.sin( percent * twopi ) * radius, 2, Math.cos( percent * twopi ) * radius ),
23 | Vec3(1,1,.75),
24 | 2.
25 | ))
26 | }
27 |
28 | lightSpheres = PolarRepeat( Sphere(.2 ).material( mat5 ), 25, radius ).translate( 4,0,0 )
29 |
30 | column = Difference(
31 | Cylinder( Vec2(.1,2.5) ).material( mat1 ),
32 | Repeat(
33 | Box( Vec3(.1) ).material( mat6 ),
34 | Vec3(0,1,0)
35 | ),
36 | .1
37 | )
38 | columns = PolarRepeat( column, 12, .85 )
39 |
40 | candelabra = Union2(
41 | StairsIntersection(
42 | PolarRepeat(
43 | PolarRepeat(
44 | t = Torus88( Vec2(.75,.1) ).material( mat1 ),
45 | 15,
46 | 3.75
47 | ).rotate( 180,1,0,0 ),
48 | count,
49 | 2
50 | ),
51 | Plane( Vec3(0,-.25,0) ).material( mat1 ),
52 | .1
53 | ),
54 | lightSpheres,
55 | Sphere( .25, Vec3(0,3.5,0) ).material( mat5 ),
56 | Sphere( .25, Vec3(0,-1,0) ).material( mat5 )
57 | ).translate( 0,-1,0)
58 |
59 | roomRadius = 8.5
60 | m = march(
61 | Union2(
62 | RoundUnion(
63 | candelabra,
64 | Plane( Vec3( 0,-1,0 ), 2.5 ).material( mat3 ),
65 | .5
66 | ),
67 | columns,
68 |
69 | Difference(
70 | Onion(
71 | Cylinder( Vec2( roomRadius, 2.5 ), Vec3(0,.45,0) ).material( mat7 ),
72 | .175
73 | ),
74 | PolarRepeat( Box( Vec3(.7,1.5,.125) ).material( mat5 ), 40, roomRadius+.5 )
75 | )
76 | )
77 | )
78 | .fog( .125, Vec3(0) )
79 | .light( ...lights )
80 | .render()
81 | .camera( 0, 0, roomRadius-.5 )`
82 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Marching.js
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
43 |
44 |
45 |
46 |
47 |
61 |
62 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/playground/demos/csg.js:
--------------------------------------------------------------------------------
1 | module.exports = `/* __--__--__--__--__--__--__--__--
2 |
3 | "constructive solid geometry (CSG)"
4 | is the name given to techniques for
5 | combining various geometries in
6 | different ways. in this tutorial,
7 | we'll re-create the example shown
8 | on the wikipedia page for CSG:
9 |
10 | https://bit.ly/2Fs2GV6
11 |
12 | our first step will be to create
13 | a rounded box, by taking the
14 | intersection of a box and a sphere.
15 | we'll go ahead and render it to see
16 | what it looks like.
17 |
18 | ** __--__--__--__--__--__--__--__*/
19 |
20 | roundedSphere = Intersection(
21 | Box( .775 ).material( 'red' ),
22 | Sphere( 1 ).material( 'blue' )
23 | )
24 |
25 | march( roundedSphere ).render()
26 |
27 | /* __--__--__--__--__--__--__--__--
28 |
29 | it's a little tricky to get a feel
30 | for it viewing it straight on, so
31 | let's rotate it along two axes.
32 |
33 | ** __--__--__--__--__--__--__--__*/
34 |
35 | roundedSphere = Intersection(
36 | Box( .775 ).material( 'red' ),
37 | Sphere( 1 ).material( 'blue' )
38 | )
39 |
40 | // rotate() takes an angle followed by
41 | // x,y, and z axis for rotation
42 | roundedSphere.rotate( 45, 1,1,0 )
43 |
44 | march( roundedSphere ).render()
45 |
46 | /* __--__--__--__--__--__--__--__--
47 |
48 | great. next we want to make a cross
49 | that we'll subtract from our rounded
50 | sphere. We can do this by combining
51 | three cylinders. We'll rotate one
52 | on the z-axis and one on the x-axis.
53 | The Union2 operator is a shortcut
54 | to combine as many objects as we
55 | want (regular Union only lets us
56 | combine two).
57 |
58 | ** __--__--__--__--__--__--__--__*/
59 |
60 | crossRadius = .5
61 | crossHeight = 1
62 | dimensions = Vec2(crossRadius, crossHeight )
63 |
64 | cross = Union2(
65 | Cylinder( dimensions ).material( 'green' ),
66 | Cylinder( dimensions )
67 | .material( 'green' )
68 | .rotate( 270, 0,0,1 ),
69 | Cylinder( dimensions )
70 | .material( 'green' )
71 | .rotate( 270, 1,0,0 )
72 | )
73 |
74 | march( cross ).render()
75 |
76 | /* __--__--__--__--__--__--__--__--
77 |
78 | OK, now we put it all together by
79 | subtracting the cross from our
80 | rounded sphere. we will animate the
81 | rotation of ourfinal geometry to
82 | get a good view from a bunch of
83 | angles.
84 |
85 | ** __--__--__--__--__--__--__--__*/
86 |
87 | roundedSphere = Intersection(
88 | Box( .775 ).material( 'red' ),
89 | Sphere( 1 ).material( 'blue' )
90 | )
91 |
92 | crossRadius = .5
93 | crossHeight = 1
94 | dimensions = Vec2(crossRadius, crossHeight )
95 |
96 | cross = Union2(
97 | Cylinder( dimensions ).material( 'green' ),
98 | Cylinder( dimensions )
99 | .material( 'green' )
100 | .rotate( 270, 0,0,1 ),
101 | Cylinder( dimensions )
102 | .material( 'green' )
103 | .rotate( 270, 1,0,0 )
104 | )
105 |
106 | march(
107 | obj = Difference(
108 | roundedSphere,
109 | cross
110 | ).rotate( 45, 1,.5,0 )
111 | )
112 | .render( 3, true )
113 |
114 | callbacks.push( t => obj.rotate( t * 25 ) )`
115 |
--------------------------------------------------------------------------------
/playground/demos/procedural_textures.js:
--------------------------------------------------------------------------------
1 | module.exports = `/* Marching.js lets you define your own
2 | procedural textures; this is a very
3 | similar process to defining your own
4 | GLSL geometries, covered in another
5 | tutorial. You give your texture a name,
6 | define a set of parameters you would
7 | like to expose for control, and then
8 | write a snippet of GLSL that generates
9 | a color based on the current pixel position
10 | and the values of the various parameters
11 | you defined.
12 |
13 | Below is the 'dots' texture included
14 | in Marching.js in use; we'll be
15 | extending it to enable some different
16 | control parameters to get a feel for the
17 | process of defining a texture.*/
18 |
19 | march( s = Sphere(1.5).texture('dots') )
20 | .fog( .15, Vec3(0))
21 | .render( 3, true )
22 |
23 | onframe = t => s.rotate(t*10, 1,0,0 )
24 |
25 | /* Below is the code for 'dots', renamed
26 | to 'dots2', and put into action. One
27 | important aspect to notice is that the
28 | 'name' property of the definition must
29 | match the name of the GLSL function that
30 | you define.*/
31 |
32 | def = {
33 | // define the name for our texture
34 | name:'dots2',
35 | // define parameters for interaction
36 | parameters: [
37 | { name:'scale', type:'float', default:5 },
38 | { name:'color', type:'vec3', default:[1,1,1] }
39 | ],
40 | // define our GLSL function, which must output a
41 | // RGB color in a vec3, and must be named the same
42 | // as our definition's 'name' property.
43 | glsl:\`
44 | vec3 dots2( vec3 pos, float count, vec3 color ) {
45 | vec3 tex;
46 | tex = vec3( color - smoothstep(0.3, 0.32, length(fract(pos*(round(count/2.)+.5)) -.5 )) );
47 | return tex;
48 | }\`
49 | }
50 |
51 | // To create a function, call Texture.create
52 | // and pass a defintion.
53 |
54 | Texture.create( def )
55 |
56 | // use it
57 | march( s = Sphere(1.5).texture('dots2') )
58 | .fog( .15, Vec3(0))
59 | .render( 3, true )
60 |
61 | // That should look the same as the original
62 | // texture in this tutorial. Let's add a couple
63 | // new parameters: the first will control the
64 | // base radius of our circles, while the second
65 | // will control the softness of the circle edges.
66 | // Larger values for softness will generate
67 | // larger circles with soft edges. Both these
68 | // parameters will be used in the call to
69 | // smoothstep in our GLSL code.
70 |
71 | def = {
72 | name:'dots2',
73 | parameters: [
74 | { name:'scale', type:'float', default:10 },
75 | { name:'radius', type:'float', default:.35 },
76 | { name:'spread', type:'float', default:.02 },
77 | { name:'color', type:'vec3', default:[1,1,1] }
78 | ],
79 | glsl:\`vec3 dots2( vec3 pos, float scale, float radius, float spread, vec3 color ) {
80 | vec3 tex;
81 | tex = vec3( color - smoothstep(radius, radius+spread, length(fract(pos*(round(scale/2.)+.5)) -.5 )) );
82 | return tex;
83 | }\` ,
84 | }
85 |
86 | Texture.create( def )
87 |
88 | march( s = Sphere(1.5).texture('dots2', { radius:.05 }) ).render(5, true)
89 |
90 | // animate our parameters
91 | onframe = t => {
92 | s.rotate(t*10,1,0,0)
93 | s.texture.color.x = sin(t)
94 | s.texture.color.y = cos(t/3)
95 | s.texture.spread = t % .5
96 | s.texture.scale = t % 10
97 | }`
98 |
--------------------------------------------------------------------------------
/playground/demos/hydra.js:
--------------------------------------------------------------------------------
1 | module.exports = `/* __--__--__--__--__--__--__--__--
2 |
3 | It's **highly** recommended that you
4 | complete the "texturing" tutorial
5 | before beginning this one.
6 |
7 | Marching.js can use Hydra as a
8 | texture generator for its 3D forms.
9 | Once you've created a texture using
10 | Hydra, you can live code with it
11 | in a similar fashion to any other
12 | Hydra sketch.
13 |
14 | If you've never tried Hydra before,
15 | go here for more info + tons of fun:
16 | http://hydra.ojack.xyz
17 |
18 | ** __--__--__--__--__--__--__--__*/
19 |
20 | // the first step is to load Hydra,
21 | // which isn't included in marching.js
22 | // by default. The use() function lets
23 | // us do this. Run the code below by
24 | // hitting ctrl+enter
25 | use('hydra')
26 |
27 | // you should see a notification appear
28 | // at the bottom of your screen when
29 | // hydra is ready to go. Use the Hydra()
30 | // constructor to make a new hydra object.
31 | // When you make calls to hydra functions
32 | // they will draw to a canvas stored in
33 | // this hydra object.
34 | hydra = Hydra()
35 | osc(50, .05, 10).modulate( noise(15) ).out()
36 |
37 | // ok, next we need to texture a marching.js
38 | // object with the hydra object we created.
39 |
40 | march(
41 | box = Box(1.5).texture( hydra.texture() )
42 | ).render('med')
43 |
44 | onframe = t => box.rotate(t*5,1,1,1)
45 |
46 | // note that you can easily live code
47 | // the texture by simply re-running the
48 | // osc().modulate().out() line after
49 | // making changes. or here's a different
50 | // idea from Hydra creator @_ojack_ (slightly modded).
51 | // you can run the whole block by placing
52 | // your cursor in it and hitting alt+enter
53 |
54 | pattern = () => osc(150, 0).kaleid(200).scale(1, 0.4)
55 | pattern()
56 | .scrollX(0.5, 0.05)
57 | .mult(pattern())
58 | .out()
59 |
60 | // ok, let's use this in a more complex scene. Again,
61 | // you can execute the
62 | hydra = Hydra()
63 | pattern = () => osc(50, 0).kaleid(200).scale(1, 0.4)
64 | pattern()
65 | .scrollX(0.5, 0.05)
66 | .mult(pattern())
67 | .out()
68 |
69 | march(
70 | rpt = Repeat(
71 | box = Box(.35).texture( hydra.texture() ),
72 | 2
73 | )
74 | )
75 | .fog(.1,Vec3(0))
76 | .render('med')
77 |
78 | onframe = t => {
79 | box.rotate(t*5,1,1,1)
80 | rpt.distance = 1.5+ sin(t)*.35
81 | }
82 |
83 | // from the texture tutorial, we know
84 | // we can scale the texture
85 |
86 | box.tex.scale = 3
87 |
88 | // or...
89 |
90 | box.tex.scale = .5
91 |
92 | // and we can also change the strength
93 | // of the texture, which determines
94 | // how much it overrides the default
95 | // lighting of the material
96 | box.tex.strength = .25
97 |
98 | /* finally, fractals + hydra === _ _
99 | ( v )
100 | \\ /
101 | v
102 | */
103 |
104 | hydra = Hydra()
105 | osc(50, .05, .5).modulate( noise(15) ).out()
106 |
107 | march(
108 | m = Mandelbox(1.25,1.1,3).texture( hydra.texture() ).scale(.5)
109 | ).render('fractal.med')
110 |
111 | m.tex.strength = .5
112 | m.tex.wrap = Texture.mirror
113 | m.tex.scale = 4
114 |
115 | onframe = t => {
116 | m.rotate(t*15,1,1,1)
117 | m.size = 1 + sin(t/2) * .125
118 | }`
--------------------------------------------------------------------------------
/playground/demos/gifs_and_links.js:
--------------------------------------------------------------------------------
1 | module.exports=`/* __--__--__--__--__--__--__--____
2 |
3 | Marching.js lets you both download
4 | animated gifs and generate links.
5 | Together, you can post a short clip
6 | on social media alongside a link
7 | that enables people to easily
8 | experiment with your code.
9 |
10 | Links can be generated at any time
11 | but creating gifs requires a tiny
12 | bit of extra wok. Both are described
13 | below.
14 |
15 | If you remember, please use
16 | #marchingjs when you post online!
17 |
18 | ** __--__--__--__--__--__--__--__*/
19 |
20 | // First let's generate a link to this code.
21 | // delete the long block comment above, then
22 | // run the line below by highlighting it and
23 | // hitting control+enter
24 | getlink('text for link blah')
25 |
26 | // You'll notice there's a short link, for
27 | // copying and pasting into an email, pdf
28 | // or other document that suppots rich text.
29 | // Most browsers will also let you copy the
30 | // generated URL if you control-click on the
31 | // link. As shown above, you can specify the
32 | // text you'd like to use for your link. Also,
33 | // if your call to getlink() is on the last
34 | // line of your sketch, it won't appear in the
35 | // geneated link. So, you should probably always
36 | // run it at the bottom of your sketch. Finally,
37 | // the code in the link will automatically run
38 | // in the playground.
39 |
40 | // IMPORTANT: There's a maximum length in most
41 | // browsers for URLs. Make sure your link works
42 | // the way you expect it to before posting it!
43 |
44 | // OK, now let's make an animated gif. First,
45 | // we have to load an additional library. Run
46 | // the line of code below with control+enter:
47 |
48 | use('gif')
49 |
50 | // You'll see a notification when the library
51 | // has loaded. Now we can add a call to .gif,
52 | // and pass in:
53 | // width - default 600
54 | // height - default 335
55 | // length (in frames) - default 60
56 | // quality (1-10) - default 5
57 | // interframe delay (in ms) - default 17 (for 60 fps)
58 | // filename - default 'marching.gif'
59 |
60 | // Here's an example of what should be a "perfect"
61 | // 400x400 loop for one second (make sure you
62 | // ran use('gif') before trying this):
63 |
64 | march(
65 | j = Julia().scale(1.5)
66 | )
67 | .gif( 400,400,59 )
68 | .render( 'fractal.high' )
69 |
70 | let fc = 0
71 | onframe = time => {
72 | j.fold = 3.5 + sin(fc)
73 | fc += (Math.PI*2)/60
74 | }
75 |
76 | // last but not least, when you make a call
77 | // to .use(), it will take a bit to download the
78 | // requested libraries. You can just wait for
79 | // a notification to appear that the library has
80 | // been loaded before running additional code,
81 | // but if you'd like to publish a link that runs
82 | // everyhing automatically this becomes problematic.
83 | // use() returns a promise that will resolve when
84 | // the library is loaded, so you can delay execution
85 | // as follows:
86 |
87 | use('hydra').then( ()=> {
88 | h = Hydra()
89 | osc( 50,.15,.25 ).modulate( noise(15) ).out()
90 |
91 | march(
92 | m = Box().texture( h.texture() )
93 | )
94 | .render('med')
95 | .camera(0,0,4)
96 |
97 | onframe = t => m.rotate(t*5, .5,1,.5 )
98 |
99 | h.texture.strength = .5
100 | })`
--------------------------------------------------------------------------------
/playground/demos/p5.js:
--------------------------------------------------------------------------------
1 | module.exports=`/* __--__--__--__--__--__--__--__--
2 |
3 | It's **highly** recommended that you
4 | complete the "texturing" tutorial
5 | before beginning this one.
6 |
7 | Marching.js can use p5.js as a
8 | texture generator for its 3D forms.
9 | Once you've created a texture using
10 | p5, you can use live code what is
11 | drawn to it by changing the onframe()
12 | method, which is equivalent to draw()
13 | in p5.js.
14 |
15 | Never played with p5.js before?
16 | https://p5js.org/
17 |
18 | ** __--__--__--__--__--__--__--__*/
19 |
20 | // the first step is to load p5,
21 | // which isn't included in marching.js
22 | // by default. The use() function lets
23 | // us do this. Run the code below by
24 | // hitting ctrl+enter
25 | use('p5')
26 |
27 | // you should see a notification appear
28 | // at the bottom of your screen when
29 | // p5 is ready to go. Use the P5()
30 | // constructor to make a new p5 object.
31 | // All the p5 drawing functions will then
32 | // be found in ths object, we'll use them
33 | // to create a red/white texture;
34 | pfive = P5()
35 | pfive.background(255,255,255)
36 | pfive.fill(255,0,0)
37 | pfive.rect(0,0,pfive.width/2,pfive.height)
38 |
39 | // ok, next we need to texture a marching.js
40 | // object with the p5 object we created.
41 |
42 | march(
43 | box = Box(1.5).texture( pfive.texture() )
44 | ).render('med')
45 |
46 | onframe = t => box.rotate(t*5,1,1,1)
47 |
48 | // we can make this more fun by animating
49 | // inside of our onframe() method:
50 |
51 | onframe = t => {
52 | pfive.background(255,255,255)
53 | pfive.rect(0,0,(.5+sin(t)*.5)*pfive.width,pfive.height)
54 | box.rotate(t*5,1,1,1)
55 | }
56 |
57 | // scale the texture
58 | box.tex.scale = 4
59 |
60 | // we can load external p5 libraries from
61 | // content delivery networks like jsdelivr. Let's
62 | // load p5.Polar by Liz Peng (https://github.com/liz-peng/p5.Polar)
63 | // which is a fun library for creating circular patterns
64 | // using polar coordinates.
65 |
66 | use( 'https://cdn.jsdelivr.net/gh/liz-peng/p5.Polar/p5.Polar.js' )
67 |
68 | // you should see a notification appear at the bottom
69 | // once the library has loaded. We can now run some drawing
70 | // code... doing it outside of onframe() would be roughly
71 | // the same as doing it in setup() in a normal p5 sketch:
72 | pfive = P5(600,600)
73 | pfive.setCenter( pfive.width/2, pfive.height/2)
74 | pfive.stroke( 255,255,255,64 )
75 | pfive.colorMode( pfive.HSB, 255 )
76 |
77 | // we'll create a more complex scene below. The use of
78 | // the 'blackhole' material causes the spheres to ignore
79 | // much of the lighting information in the scene in favor
80 | // of the p5 texture.
81 | march(
82 | rpt = Repeat(
83 | box = Sphere(1).material('blackhole').texture( pfive.texture() ),
84 | 3
85 | )
86 | )
87 | .fog(.1,Vec3(0))
88 | .render('repeat.med')
89 |
90 | onframe = t => {
91 | box.rotate(t*5,1,1,1)
92 | pfive.background(0)
93 | for( let i = 0; i < 10; i++ ) {
94 | pfive.fill( pfive.color( i * 25, i * 25, 255, 32 ) )
95 | pfive.polarEllipses(
96 | i*6, 80 - i*6.5, 80-i*6.5, 30 * i * (.85 +(sin(t) * .15)),
97 | (...args) => {
98 | args[1] = (t*2)/(i+1)
99 | return args
100 | }
101 | )
102 | }
103 | }
104 |
105 | // we can also change the strength
106 | // of the texture, which determines
107 | // how much it overrides the default
108 | // lighting of the material
109 | box.tex.strength = .5`
--------------------------------------------------------------------------------
/js/audio.js:
--------------------------------------------------------------------------------
1 | const Audio = {
2 | __hasInput: false,
3 | ctx: null,
4 | bins:null,
5 |
6 | start( bins=null ) {
7 | if( Audio.__hasInput === false ) {
8 | Audio.ctx = new AudioContext()
9 | Audio.createInput().then( input => {
10 | if( bins !== null ) Audio.bins = bins
11 | Audio.createFFT()
12 | input.connect( Audio.FFT )
13 |
14 | Audio.interval = setInterval( Audio.fftCallback, 1000/60 )
15 | //window.FFT = Audio.FFT
16 | })
17 | Audio.__hasInput = true
18 | }else{
19 | if( bins !== null ) Audio.bins = bins
20 | }
21 | },
22 |
23 | createInput() {
24 | console.log( 'connecting audio input...' )
25 |
26 | const p = new Promise( resolve => {
27 | navigator.mediaDevices.getUserMedia({ audio:true, video:false })
28 | .then( stream => {
29 | console.log( 'audio input connected' )
30 | Audio.input = Audio.ctx.createMediaStreamSource( stream )
31 | //Audio.mediaStreamSource.connect( Gibberish.node )
32 | Audio.__hasInput = true
33 | resolve( Audio.input )
34 | })
35 | .catch( err => {
36 | console.log( 'error opening audio input:', err )
37 | })
38 | })
39 | return p
40 | },
41 |
42 | createFFT() {
43 | Audio.FFT = Audio.ctx.createAnalyser()
44 |
45 | let __windowSize = 512
46 | Object.defineProperty( Audio, 'windowSize', {
47 | configurable:true,
48 | get() { return __windowSize },
49 | set(v){
50 | __windowSize = v
51 | Audio.FFT.fftSize = v
52 | Audio.FFT.values = new Uint8Array( Audio.FFT.frequencyBinCount )
53 | }
54 | })
55 |
56 | Audio.windowSize = 512
57 | },
58 |
59 | fftCallback() {
60 | Audio.FFT.getByteFrequencyData( Audio.FFT.values )
61 |
62 | let lowSum, midSum, highSum, lowCount, midCount, highCount
63 | lowSum = midSum = highSum = lowCount = midCount = highCount = 0
64 |
65 | let frequencyCounter = 0
66 |
67 | // does this start at 0Hz? ack... can't remember... does it include DC offset?
68 | const hzPerBin = (Audio.ctx.sampleRate / 2) / Audio.FFT.frequencyBinCount
69 | const lowRange = 180, midRange = 1400, highRange = Audio.ctx.sampleRate / 2
70 |
71 | if( Audio.bins === null ) {
72 | for( let i = 1; i < Audio.FFT.frequencyBinCount; i++ ) {
73 | if( frequencyCounter < lowRange ) {
74 | lowSum += Audio.FFT.values[ i ]
75 | lowCount++
76 | }else if( frequencyCounter < midRange ) {
77 | midSum += Audio.FFT.values[ i ]
78 | midCount++
79 | }else{
80 | highSum += Audio.FFT.values[ i ]
81 | highCount++
82 | }
83 |
84 | frequencyCounter += hzPerBin
85 | }
86 |
87 | Audio.low = (lowSum / lowCount) / 255
88 | Audio.mid = (midSum / midCount) / 255 || 0
89 | Audio.high = (highSum / highCount) / 255
90 | }else{
91 | const sums = {}
92 | for( let bin = 0; bin < Audio.bins.length; bin++ ) {
93 | const frequency = Audio.bins[ bin ]
94 |
95 | sums[ bin ] = { count: 0, value: 0 }
96 |
97 | for( let i = 1; i < Audio.FFT.frequencyBinCount; i++ ) {
98 | if( frequencyCounter < frequency ) {
99 | sums[ bin ].value += Audio.FFT.values[i]
100 | sums[ bin ].count++
101 | frequencyCounter += hzPerBin
102 | }else{
103 | break
104 | }
105 | }
106 | Audio[ bin ] = (sums[ bin ].value / sums[ bin ].count) / 255
107 | }
108 | }
109 | }
110 | }
111 |
112 | module.exports = Audio
113 |
--------------------------------------------------------------------------------
/playground/demos/textures_tutorial.js:
--------------------------------------------------------------------------------
1 | module.exports=`/* __--__--__--__--__--__--__--__--
2 |
3 | Marching.js provides a variety of
4 | ways to create procedural textures.
5 | In order of increasing complexity,
6 | they are:
7 |
8 | 1. Using texture presets
9 | 2. Using HTML objects
10 | 3. Using Hydra, another live coding
11 | system for 2D visual chaos
12 | 4. Writing your own shaders
13 |
14 | #4 is covered in the "defining
15 | procedural textures" tutorial, we'll
16 | cover thhe rest here.
17 |
18 | ** __--__--__--__--__--__--__--__*/
19 |
20 | // marching.js comes with a number
21 | // of texture presets that share
22 | // some properties; you can see most
23 | // of them in theh "textures catalog"
24 | // demo. Let's look at "truchet" to get
25 | // a sense for how these presets work.
26 |
27 | tex = Texture( 'truchet' )
28 | march( Box().texture( tex ) ).render()
29 |
30 | // calling Texture() makes a new texture
31 | // object that can be passed to the
32 | // .texture() method of any geometry or
33 | // combinator. If you call .texture()
34 | // on a combinator, the texture will be
35 | // applied to all it's members.
36 |
37 | tex = Texture( 'checkers' )
38 | march(
39 | StairsUnion(
40 | Box(),
41 | Box( Vec3(.5,2,.5) )
42 | )
43 | .texture( tex )
44 | .rotate(45,1,1,1)
45 | ).render()
46 |
47 | // while some properties are unique
48 | // to specific textures, others can
49 | // be found on all of them: scale,
50 | // strength, and uv. Examples of each
51 | // are shown below. Here we'll call
52 | // the .texture() method and pass in
53 | // names of presets instead of passing
54 | // in a texture object. When you do this,
55 | // you can also pass a dictionary of property
56 | // setters.
57 |
58 | // the scale property scales texture coordinates
59 | march(
60 | Sphere().translate(1,0,0).texture( 'checkers', { scale:5 }),
61 | Sphere().translate(-1,0,0).texture( 'checkers', { scale:15 })
62 | ).render()
63 |
64 | // you can also provide coordinate offset using
65 | // uv property. When you want to refer to the texture
66 | // of an object, you can do so with the following form:
67 | //
68 | // objvariable.texture.propertyname = value
69 |
70 | march(
71 | s1 = Sphere().translate(1,0,0).texture( 'checkers', { scale:5 }),
72 | s2= Sphere().translate(-1,0,0).texture( 'checkers', { scale:15 })
73 | ).render( 'med' )
74 |
75 | onframe = t => {
76 | s1.texture.uv.x = t/5
77 | s2.texture.uv.y = t/5
78 | }
79 |
80 | // last but not least, many textures (but possiibly not all)
81 | // have a "strength" property that blends the texture with
82 | // the non-textured lighting.
83 |
84 | march(
85 | Box(.5).translate(-1.1,0,0).texture( 'rainbow', { strength:0, scale:10 }),
86 | Box(.5).translate(0,0,0).texture( 'rainbow', { strength:.25, scale:10 }),
87 | Box(.5).translate(1.1,0,0).texture( 'rainbow', { strength:1, scale:10 })
88 | ).render()
89 |
90 | // there's one additional preset, 'feedback', that is a special case.
91 | // it creates a texture of the entire scene, that you can then
92 | // use to texture individual objects. Try running this one twice if you
93 | // don't get full feedback... there's a bug I need to fix in there.
94 |
95 | t = Texture( 'feedback' )
96 |
97 | march(
98 | RoundUnion(
99 | b = Box(1).texture( t ),
100 | c = Sphere().texture('dots').translate(2,0,0)
101 | ),
102 | Plane( Vec3(0,0,1) ).texture('stripes')
103 | )
104 | .render('low')
105 | .camera(0,0,3.5)
106 |
107 | onframe = time => {
108 | t.update()
109 | b.rotate(time*5, 1,.5,.25 )
110 | c.rotate(time*4, .5,1,.25 )
111 | }`
112 |
--------------------------------------------------------------------------------
/playground/demos/audio.js:
--------------------------------------------------------------------------------
1 | module.exports = `
2 | /* __--__--__--__--__--__--__--____
3 | Audio-Reactive Visuals
4 |
5 | marching.js will perform an FFT analysis
6 | of any sound/music fed to the browser. When
7 | you first start the FFT, you'll be asked to
8 | choose an audio device to listen to. You can
9 | later change this in Chrome by clicking on
10 | the camera icon in the browser window's location
11 | bar.
12 |
13 | By using software like SoundFlower or JACK you
14 | can virtually route audio from your favorite music
15 | software into marching.js... or you can simply use a
16 | microphone / standard audio input.
17 | __--__--__--__--__--__--__--____ */
18 |
19 | // create a scene to play with
20 | march(
21 | si = StairsIntersection(
22 | Sphere(2).material( 'white' ),
23 | repeat = Repeat(
24 | sphere = Sphere(.125),
25 | Vec3(.5)
26 | ),
27 | .125
28 | )
29 | ).render( 4, true )
30 |
31 | // start our FFT
32 | FFT.start()
33 |
34 | // animate
35 | onframe = time => {
36 | si.rotate( time * 15 )
37 |
38 | // our FFT object has low,mid, and high
39 | // properties that we can assign to elements
40 | // of our ray marching scene
41 | repeat.distance.x = FFT.low
42 | repeat.distance.y = FFT.mid
43 | repeat.distance.z = FFT.high
44 | sphere.radius = FFT.mid * FFT.high
45 | }
46 |
47 | si.d = 4
48 | si.c = .5
49 |
50 | /* __--__--__--__--__--__--__--____
51 | increasing the window size (how many samples
52 | of audio the FFT looks at) will result in
53 | less hectic animations. The window size must
54 | be a power of 2; doubling and halving it is
55 | an easy way to experiment with different sizes.
56 | __--__--__--__--__--__--__--____ */
57 |
58 | // run multiple times for greater effect
59 | FFT.windowSize *= 2
60 |
61 | /* __--__--__--__--__--__--__--____
62 | One fun combinator use with the FFT is
63 | Switch, which enables you to alternate
64 | between two geometries depending on whether
65 | or not an input exceeds a certain threshold.
66 | __--__--__--__--__--__--__--____ */
67 |
68 | // super-simple Switch example
69 | march(
70 | s = Switch(
71 | Sphere(),
72 | Box()
73 | )
74 | ).render( 3, true )
75 |
76 | // the threshold property is unhelpfully
77 | // named 'c' for now...
78 | onframe = t => s.c = t/2 % 1
79 |
80 |
81 | // extending our first example with Switch...
82 | march(
83 | si = StairsIntersection(
84 | swt = Switch(
85 | s = Sphere(2).material('red'),
86 | b = Box(1.75).material('white')
87 | ),
88 | repeat = Repeat(
89 | sphere = Sphere(.125),
90 | Vec3(.25)
91 | ),
92 | .125/2
93 | ),
94 | Plane( Vec3(0,1,0), 1.35 )
95 | )
96 | .fog(.25, Vec3(0) )
97 | .render( 4, true )
98 |
99 | onframe = t => {
100 | si.rotate( t * 15 )
101 | // try scaling the FFT results
102 | // by different values to control
103 | // the switch effect
104 | swt.c = FFT.low * 1
105 |
106 | repeat.distance.x = FFT.mid * FFT.low
107 | fft = (FFT.low + FFT.mid + FFT.high)
108 |
109 | // scale both our sphere and our box on every
110 | // frame, since we don't know which will be active
111 | s.radius = fft
112 | b.size = fft * .75
113 |
114 | sphere.radius = FFT.high / 2
115 | }
116 |
117 | /* __--__--__--__--__--__--__--____
118 | By default, the FFT assigns frequences
119 | < 150 Hz o FFT.low, 150-1400Hz to
120 | FFT.mid, and all remaining frequencies
121 | to FFT.high. However, you can also
122 | define your own definitions by passing
123 | an array to FFT.start(); if you do this
124 | the averages will then be in FFT[0],
125 | FFT[1], FFT[2] etc. Below is the first
126 | example from this tutorial, modified
127 | to use more bins.
128 | __--__--__--__--__--__--__--____ */
129 |
130 | march(
131 | si = StairsIntersection(
132 | Sphere(2).material( 'white' ),
133 | repeat = Repeat(
134 | sphere = Sphere(.125),
135 | Vec3(.5)
136 | ),
137 | .125
138 | )
139 | ).render( 4, true )
140 |
141 | // start our FFT
142 | FFT.start([100,200,400,800,1600,3200,6400,12800,22050])
143 |
144 | onframe = time => {
145 | si.rotate( time * 15 )
146 |
147 | repeat.distance.x = FFT[0]
148 | repeat.distance.y = FFT[1] + FFT[2]
149 | repeat.distance.z = FFT[3]
150 | sphere.radius = FFT[4] * FFT[5]
151 | }
152 | `
153 |
--------------------------------------------------------------------------------
/playground/demos/livecoding.js:
--------------------------------------------------------------------------------
1 | module.exports = `/* __--__--__--__--__--__--__--____
2 | Live Coding
3 |
4 | There are a couple of techniques
5 | that can make live coding a bit
6 | easier in marching.js. First, if
7 | you're doing any audio-reactive
8 | visuals, be sure to check out the
9 | audio / FFT tutorial.
10 |
11 | You'll notice that many demos
12 | and tutorials use a pattern where
13 | we name objects in our graph for
14 | subsequent manipulations. This is
15 | an important step to get used to
16 | when you're setting up your graph.
17 | __--__--__--__--__--__--__--____ */
18 |
19 | march(
20 | rpt = Repeat(
21 | Sphere(.125),
22 | Vec3(.5)
23 | )
24 | ).render( 3, true )
25 |
26 | // now we can change the distance later...
27 | rpt.distance.x = 1
28 |
29 | /* __--__--__--__--__--__--__--____
30 | Easy enough. However, a problem occurs
31 | if you then re-execute your calls
32 | to march / render... the repeat distance
33 | is reset to the the value in its
34 | constructor. During live coding
35 | performances you often want to
36 | maintain state (as much as possible)
37 | while making changes to the graph, so
38 | that abrupt changes don't occur.
39 |
40 | If you run the line:
41 |
42 | Marching.useProxies = true
43 |
44 | ...a new behavior is enabled. When
45 | you create an object in
46 | the global namespace, a 'proxy' is
47 | created. IF you reassign a new
48 | object to this proxy, the proxy
49 | will copy all the properties of
50 | the previous object to the new one.
51 | This enables you to re-execute
52 | the graph while maintaining state,
53 | and makes it much easier to acheive
54 | continuity when live coding. Try running
55 | the code below:
56 | __--__--__--__--__--__--__--____ */
57 |
58 | // run this line by itself before
59 | // executing other code
60 | Marching.useProxies = true
61 |
62 | march(
63 | rpt2 = Repeat(
64 | Sphere(.125),
65 | Vec3(.5)
66 | )
67 | ).render( 3, true )
68 |
69 | rpt2.distance.x = 1
70 |
71 | // if you re-execute the graph
72 | // now, you'll notice the distance.x
73 | // value is maintained. This only
74 | // works because we enabled proxies
75 | // and assigned our Repeat object
76 | // to the same global variable.
77 |
78 | /* __--__--__--__--__--__--__--____
79 | Another useful technique, that relies
80 | on assigning names to objects, is
81 | to "fade", or transition gradually,
82 | to a new state in objects.
83 | __--__--__--__--__--__--__--____ */
84 |
85 | march( s = Sphere(0) ).render( 3, true )
86 | fade( 's','radius', 2, 10 )
87 |
88 | /* __--__--__--__--__--__--__--____
89 | In the example above, we give the
90 | name of the object we want to manipulate,
91 | the name of the property, the value
92 | we want to transition to, and the
93 | length in seconds of the transition.
94 | There is quadratic easing on the fade.
95 |
96 | By using dot notation in the property
97 | string, we can fade individual vector
98 | members.
99 | __--__--__--__--__--__--__--____ */
100 |
101 |
102 | march( b = Box() ).render( 3, true )
103 | fade( 'b','size.x', 2, 10 )
104 |
105 | /* __--__--__--__--__--__--__--____
106 | it's also worth noting we can
107 | fade a whole vector by simply
108 | leaving out the dot notation. The
109 | example below fades the size on
110 | the x,y,and z axis in one line of
111 | code. Note we have to use a new name
112 | to avoid the proxy effect!
113 | __--__--__--__--__--__--__--____ */
114 |
115 | march( b2 = Box() ).render( 3, true )
116 | fade( 'b2','size', 1.5, 10 )
117 |
118 | /* __--__--__--__--__--__--__--____
119 | We can use the same vector shortcut when
120 | manipulate animation at the frame level.
121 | __--__--__--__--__--__--__--____ */
122 |
123 | march(
124 | rpt2 = Repeat(
125 | b3 = Box(),
126 | Vec3(1)
127 | )
128 | )
129 | .fog( .25, Vec3(0) )
130 | .render( 3, true )
131 |
132 | onframe = t => {
133 | // manipulate one vector member
134 | rpt2.distance.x = .5 + Math.sin( t/3 ) * .125
135 |
136 | // manipulate entire vector at once
137 | b3.size = .1 + Math.cos( t/2 ) * .075
138 | }
139 |
140 | /* __--__--__--__--__--__--__--____
141 | Hopefully this is enough to get you
142 | started live coding. Between the use of
143 | onframe, fade, the fft, and proxies, there's
144 | a number of tools to get started.
145 | __--__--__--__--__--__--__--____ */`
146 |
--------------------------------------------------------------------------------
/js/alterations.js:
--------------------------------------------------------------------------------
1 | const SceneNode = require( './sceneNode.js' )
2 | const { param_wrap, MaterialID } = require( './utils.js' )
3 | const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen } = require( './var.js' )
4 |
5 | const ops = {
6 | Onion: {
7 | func( sdf,thickness ) { return `vec2( opOnion( ${sdf}.x, ${thickness} ), ${sdf}.y )` },
8 | variables:[['thickness', 'float', .03]],
9 | def:`
10 | float opOnion( in float sdf, in float thickness ){
11 | return abs(sdf)-thickness;
12 | }
13 | vec2 opOnion( vec2 sdf, float thickness ) {
14 | float x = 0.;
15 |
16 | sdf.x = opOnion( sdf.x, thickness );
17 |
18 | return sdf;
19 | }
20 | `
21 | },
22 | Halve: {
23 | func( sdf, direction ) { return `vec2( opHalve( ${sdf}.x, p, ${direction} ), ${sdf}.y )` },
24 | variables:[['direction','int',0]],
25 | def:`
26 | float opHalve( in float sdf, vec4 p, in int dir ){
27 | float _out = 0.;
28 | switch( dir ) {
29 | case 0:
30 | _out = max( sdf, p.y );
31 | break;
32 | case 1:
33 | _out = max( sdf, -p.y );
34 | break;
35 | case 2:
36 | _out = max( sdf, p.x );
37 | break;
38 | case 3:
39 | _out = max( sdf, -p.x );
40 | break;
41 | }
42 |
43 | return _out;
44 | }
45 |
46 | vec2 opHalve( vec2 sdf, vec4 p, int dir ) {
47 | float x = 0.;
48 |
49 | x = opHalve( sdf.x, p, dir );
50 |
51 | sdf.x = x;
52 |
53 | return sdf;
54 | }
55 | `
56 | },
57 | Round: {
58 | func( sdf, amount ) { return `vec2( ${sdf}.x - ${amount}, ${sdf}.y )` },
59 | variables:[['amount','float',.1]],
60 | def:`
61 |
62 | `
63 | }
64 | }
65 |
66 |
67 |
68 | //vec4 opElongate( in vec3 p, in vec3 h ) {
69 | // //return vec4( p-clamp(p,-h,h), 0.0 ); // faster, but produces zero in the interior elongated box
70 |
71 | // vec3 q = abs(p)-h;
72 | // return vec4( max(q,0.0), min(max(q.x,max(q.y,q.z)),0.0) );
73 | //}
74 | const pushString = function( name ) {
75 | const glslobj = ops[ name ].def
76 |
77 | // some definitions are a single string, and not split into
78 | // separate float and opOut functions
79 | if( typeof glslobj === 'string' ) {
80 | if( Alterations.__glsl.indexOf( glslobj ) === -1 ) {
81 | Alterations.__glsl.push( glslobj )
82 | }
83 | }
84 | }
85 |
86 | const Alterations = {
87 | __glsl:[],
88 | __getGLSL() {
89 | return this.__glsl.join('\n')
90 | },
91 | __clear() { this.__glsl.length = 0 }
92 | }
93 |
94 | for( let name in ops ) {
95 |
96 | // get codegen function
97 | let op = ops[ name ]
98 |
99 | // create constructor
100 | Alterations[ name ] = function( sdf, ...args ) {
101 | const __op = Object.create( Alterations[ name ].prototype )
102 | __op.sdf = sdf
103 | __op.variables = []
104 | __op.__desc = { parameters:[] }
105 |
106 | for( let i = 0; i < op.variables.length; i++ ) {
107 | const propArray = op.variables[ i ]
108 | const propName = propArray[ 0 ]
109 | const propType = propArray[ 1 ]
110 | const propValue = args[ i ] === undefined ? propArray[ 2 ] : args[ i ]
111 |
112 | __op.__desc.parameters.push({ name:propName, value:propValue })
113 | let param
114 |
115 | switch( propType ) {
116 | case 'int':
117 | param = int_var_gen( propValue )()
118 | break;
119 | default:
120 | param = float_var_gen( propValue )()
121 | break;
122 | }
123 |
124 | Object.defineProperty( __op, propName, {
125 | get() { return param },
126 | set(v) { param.set( v ) }
127 | })
128 |
129 | __op.variables.push( param )
130 | }
131 |
132 | __op.matId = MaterialID.alloc()
133 |
134 | return __op
135 | }
136 |
137 | Alterations[ name ].prototype = SceneNode()
138 |
139 | Alterations[ name ].prototype.emit = function ( __name ) {
140 | const emitterA = this.sdf.emit( __name )
141 | pushString( name )
142 | //const emitterB = this.b.emit()
143 |
144 | const output = {
145 | out: op.func( emitterA.out, ...this.variables.map( v => v.emit() ) ),
146 | preface: (emitterA.preface || '')
147 | }
148 |
149 | return output
150 | }
151 |
152 | Alterations[name].prototype.emit_decl = function () {
153 | let str = this.sdf.emit_decl()
154 | for( let v of this.variables ) {
155 | str += v.emit_decl()
156 | }
157 |
158 | return str
159 | };
160 |
161 | Alterations[name].prototype.update_location = function(gl, program) {
162 | this.sdf.update_location( gl, program )
163 | for( let v of this.variables ) v.update_location( gl, program )
164 | }
165 |
166 | Alterations[name].prototype.upload_data = function(gl) {
167 | this.sdf.upload_data( gl )
168 | for( let v of this.variables ) v.upload_data( gl )
169 |
170 | }
171 | }
172 |
173 | Alterations.Halve.UP = 0
174 | Alterations.Halve.DOWN = 1
175 | Alterations.Halve.LEFT = 3
176 | Alterations.Halve.RIGHT = 2
177 |
178 | module.exports = Alterations
179 |
--------------------------------------------------------------------------------
/js/sceneNode.js:
--------------------------------------------------------------------------------
1 | const SceneNode = ()=> Object.create( SceneNode.prototype )
2 | const Matrix = require( './external/matrix.js' )
3 |
4 | SceneNode.prototype = {
5 | active: 1,
6 |
7 | // register functions passed as property values
8 | // to callbacks, and assign initial value by
9 | // running the function
10 | __processFunction( obj, value, name, shouldAdd=false ) {
11 | if( typeof value === 'function' ) {
12 | const __value = value
13 | let fnc = null
14 |
15 | if( typeof obj[ name ] === 'function' ) {
16 | fnc = t => obj[ name ]( __value( t ) )
17 | }else{
18 | fnc = shouldAdd
19 | ? t => obj[ name ] += __value( t )
20 | : t => obj[ name ] =__value( t )
21 | }
22 |
23 | Marching.postrendercallbacks.push( fnc )
24 |
25 | value = value( 0 )
26 | }
27 |
28 | return value
29 | },
30 |
31 | emit() { return "#NotImplemented#"; },
32 |
33 | emit_decl() { return ""; },
34 |
35 | update_location(gl, program) {},
36 |
37 | upload_data(gl) {},
38 |
39 | __dirty() {
40 | const params = this.params || this.parameters
41 | params.forEach( v => {
42 | if( this[ v.name ] !== undefined ) {
43 | this[ v.name ].dirty = true
44 | }
45 | })
46 |
47 | if( this.transform !== undefined ) {
48 | this.transform.dirty = true
49 | }
50 | },
51 |
52 | getID() {
53 | let id = this.id
54 |
55 | if( id === undefined && this.sdf !== undefined ) {
56 | id = this.sdf.getID()
57 | }
58 |
59 | return id
60 | },
61 |
62 | getCenter() {
63 | let center = this.center
64 |
65 | if( center === undefined && this.sdf !== undefined ) {
66 | if( this.sdf.getCenter === undefined ) {
67 | center = this.sdf.__wrapped.getCenter()
68 | }else{
69 | center = this.sdf.getCenter()
70 | }
71 | }
72 |
73 | return center
74 | },
75 |
76 | move( ...args ) {
77 | return this.translate( ...args )
78 | },
79 |
80 | moveBy( x,y,z ) {
81 | x = this.__processFunction( this.transform.translation, x, 'x', true )
82 | y = this.__processFunction( this.transform.translation, y, 'y', true )
83 | z = this.__processFunction( this.transform.translation, z, 'z', true )
84 |
85 | if( x !== undefined && x !== null ) this.transform.translation.x += x
86 | if( y !== undefined && y !== null ) this.transform.translation.y += y
87 | if( z !== undefined && z !== null ) this.transform.translation.z += z
88 |
89 | return this
90 | },
91 |
92 | rotate( angle, x,y,z ) {
93 | angle = this.__processFunction( this.transform.rotation, angle, 'angle' )
94 | this.transform.rotation.angle = angle
95 |
96 | x = this.__processFunction( this.transform.rotation.axis, x, 'x' )
97 | y = this.__processFunction( this.transform.rotation.axis, y, 'y' )
98 | z = this.__processFunction( this.transform.rotation.axis, z, 'z' )
99 | if( x !== undefined ) this.transform.rotation.axis.x = x
100 | if( y !== undefined ) this.transform.rotation.axis.y = y
101 | if( z !== undefined ) this.transform.rotation.axis.z = z
102 |
103 | return this
104 | },
105 |
106 | rotateX( angle ) {
107 | angle = this.__processFunction( this.transform.rotation, angle, 'angle' )
108 | this.transform.rotation.angle = angle
109 |
110 |
111 | },
112 |
113 | rotateBy( angle,x,y,z ) {
114 | this.transform.__rotations.push( Matrix.rotate( angle,x,y,z ) )
115 | return this
116 | },
117 |
118 | translate( x,y,z ) {
119 | x = this.__processFunction( this.transform.translation, x, 'x' )
120 | y = this.__processFunction( this.transform.translation, y, 'y' )
121 | z = this.__processFunction( this.transform.translation, z, 'z' )
122 | if( x !== undefined && x !== null ) this.transform.translation.x = x
123 | if( y !== undefined && y !== null ) this.transform.translation.y = y
124 | if( z !== undefined && z !== null ) this.transform.translation.z = z
125 |
126 | return this
127 | },
128 |
129 | scale( amount ) {
130 | amount = this.__processFunction( this, amount, 'scale' )
131 | if( amount !== undefined ) this.transform.scale = amount
132 | return this
133 | },
134 |
135 | scaleBy( amount ) {
136 | amount = this.__processFunction( this, amount, 'scaleBy' )
137 | if( amount !== undefined ) this.transform.scale += amount
138 | return this
139 | },
140 |
141 | material( mat ) {
142 | this.__setMaterial( mat )
143 | return this
144 | },
145 |
146 | texture( tex,props ) {
147 | this.__setTexture( tex,props )
148 | return this
149 | },
150 |
151 | bump( tex,strength ) {
152 | this.__setBump( tex,strength )
153 | return this
154 | }
155 | }
156 |
157 | const ops = [ 'repeat', 'polarRepeat', 'elongation' ]
158 |
159 | ops.forEach( op => {
160 | const constructorName = op[0].toUpperCase() + op.slice(1)
161 | SceneNode.prototype[ op ] = function( ...args ) {
162 | this[ op ] = this[ op ].bind( this )
163 | Object.assign( this[ op ], SceneNode.prototype )
164 | this.__target = this[ op ]
165 | this[ '__'+op ] = Marching[ constructorName ]( this, ...args, this[ op ] )
166 | this[ op ].transform = this[ '__'+op ].transform
167 | return this
168 | }
169 | })
170 |
171 | module.exports = SceneNode
172 |
--------------------------------------------------------------------------------
/playground/demos/tutorial_1.js:
--------------------------------------------------------------------------------
1 | module.exports = `/* __--__--__--__--__--__--__--__--
2 |
3 | let's start by making a simple
4 | scene with one sphere. highlight
5 | the lines below and hit ctrl+enter
6 | to run them. make sure not to high-
7 | light these instructions. you can
8 | also hit alt+enter (option in macOS)
9 | to run an entire block at once. use
10 | ctrl+. (period) to clear graphics.
11 |
12 | ** __--__--__--__--__--__--__--__*/
13 |
14 | sphere1 = Sphere()
15 |
16 | march( sphere1 )
17 | .render( 2, true )
18 |
19 | /* __--__--__--__--__--__--__--__--
20 |
21 | the march() method accepts an array
22 | of objects that can be geometric
23 | primitives or transformations. we
24 | can now change the radius of our
25 | sphere:
26 |
27 | ** __--__--__--__--__--__--__--__*/
28 |
29 | sphere1.radius = 1.25
30 |
31 | /* __--__--__--__--__--__--__--__--
32 |
33 | or change its position...
34 |
35 | ** __--__--__--__--__--__--__--__*/
36 |
37 | sphere1.move( .5, -.5 )
38 |
39 | /* __--__--__--__--__--__--__--__--
40 |
41 | note that we can only make these
42 | changes after the initial render if
43 | a value of true is passed to our
44 | render function as its second
45 | parameter. Depending on the
46 | computer you use, you probably will
47 | want to turn the resolution down
48 | (the first arg) when animating the
49 | scene. If no value or a value of
50 | false is passed as the second
51 | argument, marching.js will create a
52 | static image at maximum quality.
53 |
54 | we can also register a onframe function
55 | to change properties over time. this
56 | runs once per video frame, and is
57 | passed the current time. here we'll
58 | use the time to change the sphere's
59 | position.
60 |
61 | ** __--__--__--__--__--__--__--__*/
62 |
63 | onframe = time => {
64 | sphere1.move(
65 | Math.sin( time/2 ) * 2,
66 | null,
67 | Math.sin( time ) * 4
68 | )
69 | }
70 |
71 | /* __--__--__--__--__--__--__--__--
72 |
73 | this library uses signed distance
74 | functions (SDFs) to render geometry
75 | and perform transformations. You
76 | can do fun stuff with SDFs. Below
77 | we'll render a box, but subtract a
78 | sphere from its center.
79 |
80 | ** __--__--__--__--__--__--__--__*/
81 |
82 | march(
83 | Difference(
84 | Box(), Sphere( 1.35 )
85 | )
86 | )
87 | .render()
88 |
89 | /* __--__--__--__--__--__--__--__--
90 |
91 | we can animate this as well. we'll
92 | turn down the quality first...
93 | try turning it back up and see if
94 | your computer can handle it.
95 |
96 | ** __--__--__--__--__--__--__--__*/
97 |
98 | sphere2 = Sphere( 1.35 )
99 | box1 = Box()
100 |
101 | march(
102 | Difference( box1, sphere2 )
103 | )
104 | .render( 3, true )
105 |
106 | onframe = time => sphere2.radius = 1.25 + Math.sin( time ) * .1
107 |
108 |
109 | /* __--__--__--__--__--__--__--__--
110 |
111 | One fun transform we can do is to
112 | repeat a shape throughout our scene,
113 | We can define how coarse/fine these
114 | repetitions are.
115 |
116 | ** __--__--__--__--__--__--__--__*/
117 |
118 | march(
119 | Repeat(
120 | Sphere( .25 ),
121 | 1
122 | )
123 | )
124 | .render()
125 |
126 | /* __--__--__--__--__--__--__--__--
127 |
128 | The vector we pass as the second
129 | argument to repeat determines the
130 | spacing; higher numbers yield fewer
131 | repeats. What if we want to take two
132 | shapes and repeat them? In order to
133 | do that we need to create a union.
134 |
135 | ** __--__--__--__--__--__--__--__*/
136 |
137 | sphere3 = Sphere( .35 )
138 | box3 = Box( Vec3( .35 ) )
139 | sphereBox = RoundUnion( sphere3, box3, .9 )
140 |
141 | march(
142 | Repeat( sphereBox, 2 )
143 | )
144 | .render( 3, true )
145 |
146 | onframe = time => sphere3.radius = Math.sin( time ) * .75
147 |
148 | /* __--__--__--__--__--__--__--__--
149 |
150 | Hopefully your computer can handle
151 | that, but if not, you can always
152 | lower the resolution further or
153 | shrink your browser window. Lowering
154 | your monitor resolution while doing
155 | realtime experiments or performances
156 | will also help (especially with
157 | hidpi or "retina" displays). in
158 | addition to improving efficiency,
159 | we can also change the performance
160 | of our raymarcher to get fun glitch
161 | effects.
162 |
163 | ** __--__--__--__--__--__--__--__*/`
164 |
165 |
166 | /*
167 | march(
168 | Sphere( Noise() )
169 | )
170 | // halve the resolution
171 | .resolution(.5)
172 | // only take one sample per ray
173 | .steps(1)
174 | // how far do our rays go?
175 | .farPlane(10)
176 | // ignore quality parameter in favor
177 | // of the other settings we've defined
178 | // and animate
179 | .render(null, true)*/
180 |
--------------------------------------------------------------------------------
/js/var.js:
--------------------------------------------------------------------------------
1 | const { Vec2, Vec3, Vec4 } = require( './vec.js' )
2 | const float = require( './float.js' )
3 | const int = require( './int.js' )
4 |
5 | // Var
6 | const VarAlloc = {
7 | current: 0,
8 | clear() {
9 | VarAlloc.current = 0
10 | },
11 | alloc() {
12 | return VarAlloc.current++
13 | }
14 | }
15 |
16 | const Var = function( value, fixedName = null, __type ) {
17 | const v = Object.create( Var.prototype )
18 | v.varName = fixedName !== null ? fixedName : 'var' + VarAlloc.alloc()
19 | v.value = value
20 | v.type = v.value.type
21 | if( v.type === undefined ) v.type = __type || 'float'
22 |
23 | value.var = v
24 |
25 | if( v.type !== 'float' && v.type !== 'int' ) {
26 | Object.defineProperties( v, {
27 | x: {
28 | get() { return this.value.x },
29 | set(v){ this.value.x = v; this.dirty = true }
30 | },
31 | y: {
32 | get() { return this.value.y },
33 | set(v){ this.value.y = v; this.dirty = true }
34 | },
35 | z: {
36 | get() { return this.value.z },
37 | set(v){ this.value.z = v; this.dirty = true }
38 | },
39 | w: {
40 | get() { return this.value.w },
41 | set(v){ this.value.w = v; this.dirty = true }
42 | },
43 | r: {
44 | get() { return this.value.x },
45 | set(v){ this.value.x = v; this.dirty = true }
46 | },
47 | g: {
48 | get() { return this.value.y },
49 | set(v){ this.value.y = v; this.dirty = true }
50 | },
51 | b: {
52 | get() { return this.value.z },
53 | set(v){ this.value.z = v; this.dirty = true }
54 | },
55 | })
56 | }
57 |
58 | return v
59 | }
60 |
61 | Var.hardcode = false
62 | const emit_float = function( a ) {
63 | if (a % 1 === 0)
64 | return a.toFixed( 1 )
65 | else
66 | return a
67 | }
68 |
69 | Var.prototype = {
70 | dirty: true,
71 |
72 | loc: -1,
73 |
74 | emit() {
75 | let out
76 | if( this.value.isGen ) {
77 | const vecOut = this.value.emit()
78 | out = vecOut.preface + vecOut.out
79 |
80 | }else{
81 | out = this.varName
82 | }
83 |
84 | return out
85 | },
86 |
87 | emit_decl() {
88 | let out = ''
89 | if( this.value.isGen ) {
90 | out = this.value.emit_decl()
91 | }else{
92 | if( Var.hardcode === true ) {
93 |
94 | if( typeof this.value.emit !== 'function' ) {
95 | if( this.type === 'float' ) {
96 | out = `${this.type} ${this.varName} = ${emit_float(this.value)};\n`
97 | }else{
98 | out = `${this.type} ${this.varName} = ${this.value};\n`
99 | }
100 | }else{
101 | let val = this.value.emit()
102 | if( typeof val !== 'string' ) val = val.out
103 | out = val !== undefined ? `${this.type} ${this.varName} = ${val};\n` : ''
104 | }
105 | }else{
106 | out = `uniform ${this.type} ${this.varName};\n`
107 | }
108 | }
109 | return out
110 | },
111 |
112 | set(v) { this.value = v; this.dirty = true; },
113 |
114 | update_location(gl, program) {
115 | if( this.value.isGen ) {
116 | this.value.update_location( gl, program )
117 | return
118 | }
119 |
120 | //if( this.loc === -1 )
121 | this.loc = gl.getUniformLocation(program, this.varName)
122 | },
123 |
124 | upload_data(gl) {
125 | if( !this.dirty ) return
126 |
127 | if( this.value.isGen ) {
128 | this.value.upload_data( gl )
129 | this.dirty = false
130 | return
131 | }
132 | let v = this.value
133 | if (typeof v === 'number' ) {
134 | gl.uniform1f( this.loc, v )
135 | }else if ( v instanceof Vec2 ) {
136 | gl.uniform2f(this.loc, v.x, v.y )
137 | } else if( v instanceof Vec3 ) {
138 | gl.uniform3f(this.loc, v.x, v.y, v.z )
139 | } else if( v instanceof Vec4 ) {
140 | gl.uniform4f(this.loc, v.x, v.y, v.z, v.w )
141 | } else {
142 | // for color variables
143 | if( this.type === 'float' ) {
144 | gl.uniform1f( this.loc, v.x )
145 | }else{
146 | gl.uniform1i( this.loc, v.x )
147 | }
148 | }
149 |
150 | this.dirty = false
151 | }
152 | }
153 |
154 |
155 | function int_var_gen(x,name=null) {
156 | let output = ()=> {
157 | let out = Var( int(x), name, 'int' )
158 | return out
159 | }
160 |
161 | return output
162 | }
163 | function float_var_gen(x,name=null) { return ()=> { return Var( float(x), name, 'float' ) } }
164 |
165 | function vec2_var_gen(x, y,name=null) {
166 | return ()=> Var( Vec2(x, y), name )
167 | }
168 |
169 | function vec3_var_gen(x=0, y, z,name=null) {
170 | return ()=> Var( Vec3(x, y, z), name )
171 | }
172 |
173 | function vec4_var_gen( x, y, z, w, name=null ) {
174 | return Var( Vec4( x, y, z, w ), name )
175 | }
176 | //function float_var_gen(x,name=null) { return ()=> { return Var( float(x), name, 'float' ) } }
177 |
178 | //function vec2_var_gen(x, y,name=null) { return ()=> Var( Vec2(x, y), name ) }
179 |
180 | //function vec3_var_gen(x, y, z,name=null) { return ()=> Var( Vec3(x, y, z), name ) }
181 |
182 | //function vec4_var_gen( x, y, z, w, name=null ) { return Var( Vec4( x, y, z, w ), name ) }
183 |
184 | module.exports = { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc }
185 |
186 | /*function float_var_gen(x,name=null) { return ()=> { return Var( float(x), name, 'float' ) } }
187 |
188 | function vec2_var_gen(x, y,name=null) {
189 | if( y === undefined ) y = x
190 | return ()=> Var( Vec2(x, y), name )
191 | }
192 |
193 | function vec3_var_gen(x, y, z,name=null) {
194 | if( y === undefined ) y = x
195 | if( z === undefined ) z = x
196 | return ()=> Var( Vec3(x, y, z), name )
197 | }
198 |
199 | function vec4_var_gen( x, y, z, w, name=null ) {
200 | if( y === undefined ) y = x
201 | if( z === undefined ) z = x
202 | if( w === undefined ) w = x
203 | return Var( Vec4( x, y, z, w ), name )
204 | }
205 | */
206 |
--------------------------------------------------------------------------------
/js/transform.js:
--------------------------------------------------------------------------------
1 | const { param_wrap, MaterialID } = require( './utils.js' )
2 | const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' )
3 | const Matrix = require( './external/matrix.js' )
4 | window.Matrix = Matrix
5 | const MatrixWrap = function ( shouldInvert = false ) {
6 | const m = Object.create( MatrixWrap.prototype )
7 | m.dirty = true
8 | m.translation = {}
9 | m.scale = {}
10 | m.shouldInvert = shouldInvert
11 | m.shouldRotate = true
12 | m.rotation = {
13 | axis: {}
14 | }
15 | m.parent = null
16 |
17 | let tx = 0, ty = 0, tz = 0
18 | Object.defineProperties( m.translation, {
19 | x: {
20 | get() { return tx },
21 | set(v){
22 | tx = v
23 | //m.__data = m.__data.multiply( Matrix.translate( tx, ty, tz ) )
24 | m.dirty = true
25 | }
26 | },
27 | y: {
28 | get() { return ty },
29 | set(v){
30 | ty = v
31 | //m.__data = m.__data.multiply( Matrix.translate( tx, ty, tz ) )
32 | m.dirty = true
33 | }
34 | },
35 | z: {
36 | get() { return tz },
37 | set(v){
38 | tz = v
39 | //m.__data = m.__data.multiply( Matrix.translate( tx, ty, tz ) )
40 | m.dirty = true
41 | }
42 | },
43 | })
44 |
45 | // scaling must be sent as separate uniform to avoid sdf over estimation
46 | let scale = 1
47 | Object.defineProperty( m,'scale', {
48 | get() { return scale },
49 | set(v){
50 | scale = v
51 | //m.__data = m.__data.multiply( Matrix.rotate( angle, rx, ry, rz ) )
52 | m.dirty = true
53 | }
54 | })
55 |
56 | /* FOR NON-UNIFORM SCALING:
57 | *
58 | * 1. comment out scale property above
59 | * 2. uncomment scale property below
60 | * 3. change emit_decl to use a vec3 for scale
61 | * 4. change upload_data to upload a 3f
62 | * 5. In "primitives.js", replace line 155 (part of emit) to use compensated scaling
63 | */
64 |
65 | //let sx = 1, sy = 1, sz = 1
66 | //Object.defineProperties( m.scale, {
67 | // x: {
68 | // get() { return sx },
69 | // set(v){
70 | // sx = v
71 | // //m.__data = m.__data.multiply( Matrix.scale( sx, sy, sz ) )
72 | // m.dirty = true
73 | // }
74 | // },
75 | // y: {
76 | // get() { return sy },
77 | // set(v){
78 | // sy = v
79 | // //m.__data = m.__data.multiply( Matrix.scale( sx, sy, sz ) )
80 | // m.dirty = true
81 | // }
82 | // },
83 | // z: {
84 | // get() { return sz },
85 | // set(v){
86 | // sz = v
87 | // //m.__data = m.__data.multiply( Matrix.scale( sx, sy, sz ) )
88 | // m.dirty = true
89 | // }
90 | // },
91 | //})
92 |
93 | let angle = 0
94 | Object.defineProperty( m.rotation, 'angle', {
95 | get() { return angle },
96 | set(v){
97 | angle = v
98 | //m.__data = m.__data.multiply( Matrix.rotate( angle, rx, ry, rz ) )
99 | m.dirty = true
100 | }
101 | })
102 |
103 | let rx = 0, ry = 0, rz = 0
104 | Object.defineProperties( m.rotation.axis, {
105 | x: {
106 | get() { return rx },
107 | set(v){
108 | rx = v
109 | //m.__data = m.__data = Matrix.rotate( angle, rx, ry, rz, m.__data )
110 | m.dirty = true
111 | }
112 | },
113 | y: {
114 | get() { return ry },
115 | set(v){
116 | ry = v
117 | //m.__data = m.__data = Matrix.rotate( angle, rx, ry, rz, m.__data )
118 | m.dirty = true
119 | }
120 | },
121 | z: {
122 | get() { return rz },
123 | set(v){
124 | rz = v
125 | //m.__data = m.__data = Matrix.rotate( angle, rx, ry, rz, m.__data )
126 | m.dirty = true
127 | }
128 | },
129 | })
130 |
131 | m.__rotations = []
132 | m.__id = VarAlloc.alloc()
133 | m.__dirty = function() {}
134 | m.__data = Matrix.identity()
135 | m.__Matrix = Matrix
136 | m.varName = 'transform' + m.__id
137 |
138 | return m
139 | }
140 |
141 | MatrixWrap.prototype = {
142 | type: 'matrix',
143 |
144 | emit() { return this.varName },
145 |
146 | emit_scale() { return this.varName + '_scale' },
147 |
148 | emit_decl() {
149 | const decl = ` uniform mat4 ${this.varName};
150 | uniform float ${this.varName}_scale;
151 | `
152 |
153 | return decl
154 | },
155 |
156 | update_location(gl, program) {
157 | this.loc = gl.getUniformLocation( program, this.varName )
158 | this.loc_scale = gl.getUniformLocation( program, this.varName+'_scale' )
159 | },
160 |
161 | upload_data(gl) {
162 | if( !this.dirty ) return
163 |
164 | this.internal()
165 |
166 | if( this.shouldInvert === true ) {
167 | const inverse = Matrix.inverse( this.__data )
168 | gl.uniformMatrix4fv( this.loc, false, inverse.m )
169 | }else{
170 | gl.uniformMatrix4fv( this.loc, false, this.__data.m )
171 | }
172 | //gl.uniform3f(this.loc_scale, this.scale.x, this.scale.y, this.scale.z )
173 |
174 | // scaling must be sent as separate uniform to avoid sdf over-estimation
175 | gl.uniform1f(this.loc_scale, this.scale )
176 |
177 | this.dirty = false
178 | },
179 |
180 |
181 | internal() {
182 | this.__data = Matrix.identity()
183 | if( this.parent != null ) this.__data = this.parent.__data
184 |
185 | this.__data = this.__data.multiply( Matrix.translate( this.translation.x, this.translation.y, this.translation.z ) )
186 |
187 | // handle cumulative rotations via .rotateBy() method
188 | this.__rotations.forEach( r => {
189 | this.__data = this.__data.multiply( r )
190 | })
191 |
192 | // handle absolute rotations via .rotate() method... should this be aliased to rotateTo() ?
193 | if( this.shouldRotate ) {
194 | this.__data = this.__data.multiply( Matrix.rotate( this.rotation.angle, this.rotation.axis.x, this.rotation.axis.y, this.rotation.axis.z ) )
195 | }
196 |
197 | this.__data = this.__data.multiply( Matrix.scale( this.scale, this.scale, this.scale ) )
198 | },
199 |
200 | invert( shouldInvert = true) {
201 | this.shouldInvert = shouldInvert
202 | this.dirty = true
203 | },
204 |
205 | apply( transform = null, shouldInvert = false ) {
206 | this.parent = transform
207 | this.dirty = true
208 | }
209 |
210 | }
211 |
212 | module.exports = MatrixWrap
213 |
--------------------------------------------------------------------------------
/playground/demos/lighting.js:
--------------------------------------------------------------------------------
1 | module.exports =`/* __--__--__--__--__--__--__--__--
2 |
3 | By default, marching.js uses a
4 | lighting system consisting of a
5 | skydome(fill), a single front light,
6 | and a back light. This lighting
7 | system includes ambient occlusion
8 | and basic shadows, as taken from
9 | this demo by Inigo Quilez:
10 |
11 | https://www.shadertoy.com/view/Xds3zN
12 |
13 | However, each Material in marching.js
14 | can uses its own lighting algorithm.
15 | In the example below, the left sphere and
16 | the ground plane use the default lighting
17 | algorithm while the right sphere uses
18 | the normal for each pixel to determine
19 | its color.
20 |
21 | ** __--__--__--__--__--__--__--__*/
22 |
23 | march(
24 | Sphere( 1 ).translate(-1.25),
25 | Sphere( 1 )
26 | .translate(1,0,0)
27 | .material( 'normal' ),
28 | Plane()
29 | )
30 | .render()
31 |
32 | /* __--__--__--__--__--__--__--__--
33 |
34 | For most shapes, the third values
35 | passed to constructors will determine
36 | the material used by the shape. In
37 | the last example we saw that there is
38 | a "preset" to use normals for lighting;
39 | there are also presets for common
40 | colors.
41 |
42 | ** __--__--__--__--__--__--__--__*/
43 |
44 | march(
45 | Sphere( 1 )
46 | .translate( -1.25 )
47 | .material( 'green' ),
48 | Sphere( 1 )
49 | .translate(1)
50 | .material( 'red' ),
51 | Plane().material( 'yellow' )
52 | )
53 | .render()
54 |
55 | /* __--__--__--__--__--__--__--__--
56 |
57 | Note that each geomety's color is
58 | determined by both properties of its
59 | material and the lights in the scene.
60 | For example, we could change the scene
61 | above to use magenta light, our green
62 | sphere will essentially show up as
63 | black (magenta light contains no green).
64 |
65 | ** __--__--__--__--__--__--__--__*/
66 |
67 | march(
68 | Sphere( 1 )
69 | .translate( -1.25 )
70 | .material( 'green' ),
71 | Sphere( 1 )
72 | .translate(1)
73 | .material( 'red' ),
74 | Plane().material( 'yellow' )
75 | )
76 | .light( Light( Vec3(2,2,3), Vec3(1,0,1) ) )
77 | .render()
78 |
79 | /* __--__--__--__--__--__--__--__--
80 |
81 | To really get full control over our
82 | lighting, we'll want to avoid the
83 | default lighting scheme, which has
84 | some properties baked in, and
85 | customize everything by hand. The
86 | example below uses the Phong
87 | lighting model, with an additioanl
88 | Fresnel effect added. We'll also
89 | use a simple white light positioned
90 | to the upper left of the scene's
91 | center.
92 |
93 | ** __--__--__--__--__--__--__--__*/
94 |
95 | mat1 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 8 )
96 | march(
97 | Sphere( 1 ).material( mat1 ),
98 | Plane().material( mat1 )
99 | )
100 | .light( Light( Vec3(2,2,3), Vec3(1) ) )
101 | .render()
102 |
103 | /* __--__--__--__--__--__--__--__--
104 |
105 | In the example above, our material
106 | uses the 'phong' lighting model, has
107 | a ambient RGB coefficient of .05,
108 | diffuse RGB coefficient of .5,
109 | specular RGB coefficients of 1. The
110 | last number determines the diffuseness
111 | of the specular highlights, with lower
112 | numbers yielding more diffuse highlights.
113 |
114 | We can add a final Vec3 to control
115 | the Fresnel effect on the material,
116 | which can create a halo effect around
117 | a geometry. The three parameters for
118 | the Fresnel effect are bias, scale,
119 | and power.
120 |
121 | ** __--__--__--__--__--__--__--__*/
122 |
123 | mat1 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 8, Vec3(1,50,5) )
124 | mat2 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 8 )
125 | march(
126 | Sphere( 1 ).material( mat1 ),
127 | Plane().material( mat2 )
128 | )
129 | .light( Light( Vec3(2,2,3), Vec3(1) ) )
130 | .render()
131 |
132 | /* __--__--__--__--__--__--__--__--
133 |
134 | We can have lots of lights! However,
135 | this will increase rendering time,
136 | so tread carefully if you're doing
137 | realtime work.
138 |
139 | ** __--__--__--__--__--__--__--__*/
140 |
141 | mat1 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 8, Vec3(1,50,5) )
142 | mat2 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 8 )
143 | march(
144 | Sphere( 1 ).material( mat1 ),
145 | Plane().material( mat2 )
146 | )
147 | .light(
148 | Light( Vec3(2,2,3), Vec3(1) ),
149 | Light( Vec3(-2,2,3), Vec3(1,0,0) ),
150 | Light( Vec3(0,0,-3), Vec3(0,0,1) ),
151 | )
152 | .render()
153 |
154 | /* __--__--__--__--__--__--__--__--
155 |
156 | Two other elements that affect
157 | lighting in marching.js are fog and
158 | shadows. The fog() method accepts
159 | two arguments, a color for the fog
160 | and an intensity coefficient. For
161 | typical fog effects, set the fog
162 | color to be the same as the background
163 | color for the scene.
164 |
165 | ** __--__--__--__--__--__--__--__*/
166 |
167 | march(
168 | Sphere()
169 | .translate(-1.25,0,0)
170 | .material( 'green' ),
171 | Sphere()
172 | .translate(1)
173 | .material( 'red' ),
174 | Plane().material( 'yellow' )
175 | )
176 | .background( Vec3(0,0,.5) )
177 | .fog( .125, Vec3(0,0,.5) )
178 | .render()
179 |
180 | /* __--__--__--__--__--__--__--__--
181 |
182 | The effect is especially easy to see
183 | on fields of repeated geoemtries. Run
184 | the code below, and then uncomment
185 | the fog and run it again.
186 |
187 | ** __--__--__--__--__--__--__--__*/
188 |
189 | mat1 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 8, Vec3(1,4,1) )
190 | march(
191 | Repeat(
192 | Sphere( .25 ).material( mat1 ),
193 | Vec3( .75)
194 | )
195 | )
196 | .light(
197 | Light( Vec3(2,2,3), Vec3(1) ),
198 | Light( Vec3(-2,2,3), Vec3(1,0,0) ),
199 | )
200 | .background( Vec3(0) )
201 | //.fog( .5, Vec3(0) )
202 | .render()
203 |
204 | /* __--__--__--__--__--__--__--__--
205 |
206 | Last but not least, we can change
207 | the softness of shadows in our scene
208 | by adjusting a shadow coefficient.
209 | Lower values (such as 2) yield soft,
210 | diffuse shadows while high values
211 | (like 16 or 32) yield shadows with
212 | hard edges. You can also pass a value
213 | of 0 to remove shadows from a scene.
214 | Experiment with passing different
215 | values to the shadow method below.
216 |
217 | ** __--__--__--__--__--__--__--__*/
218 |
219 | mat1 = Material( 'phong', Vec3(.05), Vec3(.5), Vec3(1), 8, Vec3(0,1,2) )
220 | march(
221 | Box( .75 )
222 | .translate( 0,.25,1 )
223 | .material( mat1 ),
224 | Plane().material( mat1 )
225 | )
226 | .light( Light( Vec3(-1,2,2), Vec3(1) ) )
227 | .shadow(2)
228 | .render()`
229 |
--------------------------------------------------------------------------------
/js/mergepass.js:
--------------------------------------------------------------------------------
1 | const MP = require( '@bandaloo/merge-pass' )
2 |
3 | const FX = {
4 | merger:null,
5 | chain: [],
6 | MP,
7 | clear() {
8 | this.deleteMerger()
9 | this.chain.length = 0
10 | },
11 |
12 | deleteMerger() {
13 | if( this.merger !== null ) {
14 | this.merger.delete()
15 | this.merger = null
16 | }
17 | },
18 |
19 | init( colorTexture, depthTexture, gl ) {
20 | this.merger = new MP.Merger(
21 | this.chain,
22 | colorTexture,
23 | gl,
24 | // pass null to create second scratch channel
25 | // this is the samplerNum for arguments
26 | { channels: [ depthTexture, null, null ] }
27 | )
28 | },
29 |
30 | run( time ) {
31 | merger.draw( time )
32 | },
33 |
34 | post( ...fx ) {
35 | //FX.chain = fx.map( v => v.__wrapped__ )
36 | FX.chain.length = 0
37 |
38 | fx.forEach( v => {
39 | if( Array.isArray( v.__wrapped__ ) ) {
40 | v.__wrapped__.forEach( w => FX.chain.push( w ) )
41 | }else{
42 | FX.chain.push( v.__wrapped__ )
43 | }
44 | })
45 | },
46 |
47 | export( obj ) {
48 | obj.Antialias = FX.Antialias
49 | obj.Blur = FX.Blur
50 | obj.Bloom = FX.Bloom
51 | obj.BloomOld = FX.BloomOld
52 | obj.Brightness = FX.Brightness
53 | obj.Contrast = FX.Contrast
54 | obj.Edge = FX.Edge
55 | obj.Focus = FX.Focus
56 | obj.Glow = FX.Glow
57 | obj.Godrays = FX.Godrays
58 | obj.Hue = FX.Hue
59 | obj.Invert = FX.Invert
60 | obj.MotionBlur = FX.MotionBlur
61 | },
62 |
63 | wrapProperty( obj, name, __value, transform=null ) {
64 | __value = transform === null ? __value : transform( __value )
65 | const primitive = MP.float( MP.mut( __value ) )
66 |
67 | let value = __value
68 | Object.defineProperty( obj, name, {
69 | get() { return value },
70 | set(v) {
71 | value = transform === null ? v : transform( v )
72 | primitive.setVal( value )
73 | }
74 | })
75 |
76 | return primitive
77 | },
78 |
79 | Bloom( __threshold=0, __boost = .5, __horizontal=1, __vertical=1, taps = 9, reps = 3, num=1 ) {
80 | const fx = {},
81 | threshold = FX.wrapProperty( fx, 'threshold', __threshold ),
82 | boost = FX.wrapProperty( fx, 'amount', __boost ),
83 | horizontal = FX.wrapProperty( fx, 'vertical', __vertical ),
84 | vertical = FX.wrapProperty( fx, 'horizontal', __horizontal )
85 |
86 | fx.__wrapped__ = MP.bloom( threshold, horizontal, vertical, boost, num, taps, reps )
87 |
88 | return fx
89 | },
90 |
91 | BloomOld( __threshold=0, __boost=.5 ) {
92 | const fx = {},
93 | threshold = FX.wrapProperty( fx, 'threshold', __threshold ),
94 | boost = FX.wrapProperty( fx, 'amount', __boost )
95 |
96 | fx.__wrapped__ = MP.bloom( threshold, boost )
97 |
98 | return fx
99 | },
100 |
101 | Blur( amount=3, reps=2, taps=5 ) {
102 | const fx = {}
103 |
104 | const __amount = FX.wrapProperty( fx, 'amount', amount )
105 | fx.__wrapped__ = MP.blur2d( __amount, __amount, reps, taps )
106 |
107 | return fx
108 | },
109 |
110 | Brightness( __amount=.25 ) {
111 | const fx = {},
112 | amount = FX.wrapProperty( fx, 'amount', __amount )
113 |
114 | fx.__wrapped__ = MP.brightness( amount )
115 |
116 | return fx
117 | },
118 |
119 | Contrast( __amount=.5 ) {
120 | const fx = {},
121 | amount = FX.wrapProperty( fx, 'amount', __amount )
122 |
123 | fx.__wrapped__ = MP.contrast( amount )
124 |
125 | return fx
126 | },
127 |
128 | Edge( mode=0, color=1 ) {
129 | const fx = {}
130 |
131 | switch( mode ) {
132 | case 0: fx.__wrapped__ = MP.sobel(); break;
133 | case 1: fx.__wrapped__ = MP.edge( color,0 ); break;
134 | case 2: fx.__wrapped__ = MP.edgecolor( MP.vec4(...color) ); break;
135 | }
136 |
137 | return fx
138 | },
139 |
140 | Focus( __depth=0, __radius=.01 ) {
141 | const fx = {},
142 | depth = FX.wrapProperty( fx, 'depth', __depth, v => 1 - v ),
143 | radius = FX.wrapProperty( fx, 'radius', __radius )
144 |
145 | fx.__wrapped__ = MP.dof( depth, radius )
146 |
147 | return fx
148 | },
149 |
150 | Glow( __contrast=1.2, __brightness = .15, __blur=1, __adjust=-.5, loops=5 ) {
151 | const fx = {},
152 | contrast = FX.wrapProperty( fx, 'contrast', __contrast ),
153 | brightness = FX.wrapProperty( fx, 'brightness', __brightness ),
154 | blur = FX.wrapProperty( fx, 'blur', __blur ),
155 | adjust = FX.wrapProperty( fx, 'adjust', __adjust )
156 |
157 | fx.__wrapped__ = [
158 | MP.loop([
159 | MP.gauss(MP.vec2(blur, 0)),
160 | MP.gauss(MP.vec2(0, blur)),
161 | MP.brightness( brightness ),
162 | MP.contrast( contrast),
163 | ], loops ),
164 | MP.brightness( adjust ),
165 | MP.op( MP.fcolor(), "+", MP.input() )
166 | ]
167 |
168 | return fx
169 | },
170 |
171 |
172 | Godrays( __decay=1, __weight=.01, __density=1, __threshold=.9, __newColor=[.5,.15,0,1] ) {
173 | const fx = {},
174 | decay = FX.wrapProperty( fx, 'decay', __decay ),
175 | weight = FX.wrapProperty( fx, 'weight', __weight ),
176 | density = FX.wrapProperty( fx, 'density', __density ),
177 | threshold = FX.wrapProperty( fx, 'threshold', __threshold, v => 1 - v )
178 |
179 | const newColor = MP.mut( MP.pvec4( ...__newColor ) )
180 |
181 | let value = __newColor
182 | Object.defineProperty( fx, 'color', {
183 | get() { return value },
184 | set(v) {
185 | value = Array.isArray(v) ? v : [v,v,v,v]
186 | fx.__wrapped__.setNewColor( MP.pvec4( ...value ) )
187 | }
188 | })
189 |
190 |
191 | fx.__wrapped__ = MP.godrays({
192 | decay, weight, density,
193 |
194 | convertDepth: {
195 | threshold,
196 | newColor
197 | }
198 | })
199 |
200 | return fx
201 | },
202 |
203 | Hue( __shift=.5, __threshold = .99 ) {
204 | const fx = {},
205 | frag = MP.fcolor(),
206 | depth = MP.channel(0),
207 | shift = FX.wrapProperty( fx, 'shift', __shift ),
208 | threshold = FX.wrapProperty( fx, 'threshold', __threshold, v => 1 - v )
209 |
210 | let control
211 | fx.__wrapped__ = MP.hsv2rgb(
212 | MP.changecomp(
213 | MP.rgb2hsv( MP.fcolor() ),
214 | MP.cfloat( MP.tag `length(${depth}.rgb) >= ${threshold} ? ${shift} : 0.` ),
215 | "r",
216 | "+"
217 | )
218 | )
219 |
220 | return fx
221 | },
222 |
223 | Invert( __threshold = .99 ) {
224 | const fx = {},
225 | frag = MP.fcolor(),
226 | depth = MP.channel(0),
227 | threshold = FX.wrapProperty( fx, 'threshold', __threshold, v => 1 - v )
228 |
229 | fx.__wrapped__ = MP.cvec4( MP.tag `length(${depth}.rgb) >= ${threshold} ? (1. - vec4(${frag}.rgb, 0.)) : ${frag}` )
230 |
231 |
232 | return fx
233 | },
234 |
235 | MotionBlur( __amount = .7) {
236 | const fx = {},
237 | amount = FX.wrapProperty( fx, 'amount', __amount, v => 1-v )
238 |
239 | // 1 is the sampler for blur, and 2 is for motionbblur
240 | fx.__wrapped__ = MP.motionblur( 2, amount )
241 |
242 | return fx
243 | },
244 |
245 | Antialias( mult=1 ) {
246 | return { __wrapped__: MP.loop([ MP.fxaa() ], mult ) }
247 | },
248 |
249 | }
250 |
251 | module.exports = FX
252 |
--------------------------------------------------------------------------------
/js/vec.js:
--------------------------------------------------------------------------------
1 | // add functions to animation callback if needed
2 | const process = function( vec, dim, arg ) {
3 |
4 | if( typeof arg === 'function' ) {
5 | const fnc = arg
6 | Marching.postrendercallbacks.push( t => {
7 | vec[ dim ] = fnc( t )
8 | vec.dirty = true
9 | vec.var.dirty = true
10 | })
11 |
12 | // set initial value with t=0
13 | arg = fnc( 0 )
14 | }
15 |
16 | return arg
17 | }
18 |
19 | const Vec2 = function (x=0, y=0) {
20 | if( x.type === 'vec2' ) return x
21 | const v = Object.create( Vec2.prototype )
22 | if( Array.isArray( x ) ) {
23 | v.x = process(v, 'x', x[0]); v.y = process(v, 'y', x[1]);
24 | } else if( y === undefined ) {
25 | v.x = process(v, 'x', x)
26 | v.y = process(v, 'y', x)
27 | }else{
28 | v.x = process(v,'x',x); v.y = process(v,'y',y);
29 | }
30 |
31 | return v
32 | }
33 |
34 | Vec2.prototype = {
35 | type: 'vec2',
36 | emit() { return "vec2(" + this.x + "," + this.y + ")" },
37 | emit_decl() { return ""; },
38 | copy() {
39 | return Vec2( this.x, this.y )
40 | }
41 | }
42 |
43 | const Vec3 = function (x=0, y, z) {
44 | if( x.type === 'vec3' ) return x
45 | const v = Object.create( Vec3.prototype )
46 | let vx =0,vy=0,vz=0
47 | Object.defineProperties( v, {
48 | x: {
49 | get() { return vx },
50 | set(v) { vx = v; this.dirty = true; }
51 | },
52 |
53 | y: {
54 | get() { return vy },
55 | set(v) { vy = v; this.dirty = true; }
56 | },
57 |
58 | z: {
59 | get() { return vz },
60 | set(v) { vz = v; this.dirty = true; }
61 | },
62 | r: {
63 | get() { return vx },
64 | set(v) { vx = v; this.dirty = true; }
65 | },
66 |
67 | g: {
68 | get() { return vy },
69 | set(v) { vy = v; this.dirty = true; }
70 | },
71 |
72 | b: {
73 | get() { return vz },
74 | set(v) { vz = v; this.dirty = true; }
75 | },
76 |
77 | })
78 |
79 | if( Array.isArray( x ) ) {
80 | v.x = process(v,'x',x[0]); v.y = process(v,'y',x[1]); v.z = process(v,'z',x[2]);
81 | } else if( y === undefined ) {
82 | v.x = process(v, 'x', x)
83 | v.y = process(v, 'y', x)
84 | v.z = process(v, 'z', x)
85 | }else{
86 | v.x = process(v,'x',x); v.y = process(v,'y',y); v.z=process(v,'z',z)
87 | }
88 |
89 | v.isGen = v.x.type === 'string' || v.y.type === 'string' || v.z.type === 'string'
90 | return v
91 | };
92 |
93 | Vec3.prototype = {
94 | type: 'vec3',
95 | emit() {
96 | let out = `vec3(`
97 | let preface = ''
98 |
99 | if( this.x.type === 'string' ) {
100 | const xout = this.x.emit()
101 | out += xout.out + ','
102 | }else{
103 | out += this.x + ','
104 | }
105 |
106 | if( this.y.type === 'string' ) {
107 | const yout = this.y.emit()
108 | out += yout.out + ','
109 | }else{
110 | out += this.y + ','
111 | }
112 | if( this.z.type === 'string' ) {
113 | const zout = this.z.emit()
114 | out += zout.out
115 | }else{
116 | out += this.z
117 | }
118 |
119 | out += ')'
120 |
121 | return { out, preface }
122 | },
123 | emit_decl() {
124 | let out = ''
125 | if( this.x.type === 'string' ) {
126 | out += this.x.emit_decl()
127 | }
128 | if( this.y.type === 'string' && this.x !== this.y ) {
129 | out += this.y.emit_decl()
130 | }
131 | if( this.z.type === 'string' && this.z !== this.y && this.z !== this.x ) {
132 | out += this.z.emit_decl()
133 | }
134 | return out
135 | },
136 |
137 | update_location(gl, program) {
138 | if( this.isGen ) {
139 | if( this.x.type === 'string' ) {
140 | this.x.update_location(gl,program)
141 | }
142 | if( this.y.type === 'string' && this.x !== this.y ) {
143 | this.y.update_location(gl,program)
144 | }
145 | if( this.z.type === 'string' && this.z !== this.y && this.z !== this.x ) {
146 | this.z.update_location(gl,program)
147 | }
148 | }
149 | },
150 |
151 | upload_data(gl) {
152 | if( this.isGen ) {
153 | if( this.x.type === 'string' ) {
154 | this.x.upload_data(gl)
155 | }
156 | if( this.y.type === 'string' && this.x !== this.y ) {
157 | this.y.upload_data(gl)
158 | }
159 | if( this.z.type === 'string' && this.z !== this.y && this.z !== this.x ) {
160 | this.z.upload_data(gl)
161 | }
162 | }
163 | },
164 |
165 | copy() {
166 | return Vec3( this.x, this.y, this.z )
167 | }
168 |
169 | }
170 |
171 | const Vec4 = function (x=0, y, z, w) {
172 | if( x.type === 'vec4' ) return x
173 | const v = Object.create( Vec4.prototype )
174 |
175 | if( Array.isArray( x ) ) {
176 | v.x = x[0]; v.y = x[1]; v.z = x[2]; v.w = x[3]
177 | } else if( y === undefined && z === undefined) {
178 | v.x = v.y = v.z = v.w = x
179 | }else{
180 | v.x = x; v.y = y; v.z = z; v.w = w;;
181 | }
182 |
183 | v.isGen = v.x.type === 'string' || v.y.type === 'string' || v.z.type === 'string'
184 |
185 | return v
186 | };
187 |
188 | Vec4.prototype = {
189 | type: 'vec4',
190 | emit() {
191 | let out = `vec4(`
192 | let preface = ''
193 |
194 | if( this.x.type === 'string' ) {
195 | const xout = this.x.emit()
196 | out += xout.out + ','
197 | }else{
198 | out += this.x + ','
199 | }
200 |
201 | if( this.y.type === 'string' ) {
202 | const yout = this.y.emit()
203 | out += yout.out + ','
204 | }else{
205 | out += this.y + ','
206 | }
207 |
208 | if( this.z.type === 'string' ) {
209 | const zout = this.z.emit()
210 | out += zout.out
211 | }else{
212 | out += this.z
213 | }
214 |
215 | if( this.w.type === 'string' ) {
216 | const wout = this.w.emit()
217 | out += wout.out
218 | }else{
219 | out += this.w
220 | }
221 |
222 | out += ')'
223 |
224 | return { out, preface }
225 | },
226 | emit_decl() {
227 | let out = ''
228 | if( this.x.type === 'string' ) {
229 | out += this.x.emit_decl()
230 | }
231 | if( this.y.type === 'string' && this.x !== this.y ) {
232 | out += this.y.emit_decl()
233 | }
234 | if( this.z.type === 'string' && this.z !== this.y && this.z !== this.x ) {
235 | out += this.z.emit_decl()
236 | }
237 | if( this.w.type === 'string' && this.w !== this.y && this.w !== this.x && this.w !== this.z ) {
238 | out += this.w.emit_decl()
239 | }
240 | return out
241 | },
242 |
243 | update_location(gl, program) {
244 | if( this.isGen ) {
245 | if( this.x.type === 'string' ) {
246 | this.x.update_location(gl,program)
247 | }
248 | if( this.y.type === 'string' && this.x !== this.y ) {
249 | this.y.update_location(gl,program)
250 | }
251 | if( this.z.type === 'string' && this.z !== this.y && this.z !== this.x ) {
252 | this.z.update_location(gl,program)
253 | }
254 | if( this.w.type === 'string' && this.w !== this.y && this.w !== this.x && this.w !== this.z ) {
255 | this.w.update_location(gl,program)
256 | }
257 | }
258 | },
259 |
260 | upload_data(gl) {
261 | if( this.isGen ) {
262 | if( this.x.type === 'string' ) {
263 | this.x.upload_data(gl)
264 | }
265 | if( this.y.type === 'string' && this.x !== this.y ) {
266 | this.y.upload_data(gl)
267 | }
268 | if( this.z.type === 'string' && this.z !== this.y && this.z !== this.x ) {
269 | this.z.upload_data(gl)
270 | }
271 | if( this.w.type === 'string' && this.w !== this.y && this.w !== this.x && this.w !== this.z ) {
272 | this.w.upload_data(gl)
273 | }
274 | }
275 | },
276 |
277 | copy() {
278 | return Vec4( this.x, this.y, this.z, this.w )
279 | }
280 | }
281 | // Vec4
282 |
283 | //let Vec4 = function (x, y, z, w) {
284 | // const v = Object.create( Vec4.prototype )
285 | // v.x = x; v.y = y; v.z = z; v.w = w
286 |
287 | // return v
288 | //};
289 |
290 | //Vec4.prototype = {
291 | // type: 'vec4',
292 | // emit() { return "vec4(" + this.x + "," + this.y + "," + this.z + "," + this.w + ")"; },
293 | // emit_decl() { return ""; }
294 | //}
295 |
296 |
297 |
298 |
299 |
300 | module.exports = { Vec2, Vec3, Vec4 }
301 |
--------------------------------------------------------------------------------
/js/camera.js:
--------------------------------------------------------------------------------
1 | const vec3 = require('gl-vec3')
2 | const mat4 = require('gl-mat4')
3 |
4 | // camera adapted from https://github.com/shama/first-person-camera
5 | function FirstPersonCamera(opts) {
6 | if (!(this instanceof FirstPersonCamera)) return new FirstPersonCamera(opts)
7 | opts = opts || {}
8 | this.position = opts.position || vec3.create()
9 | this.rotation = opts.rotation || vec3.create()
10 | this.positionSpeed = opts.positionSpeed || -.5
11 | this.rotationSpeed = opts.rotationSpeed || .01
12 | }
13 | module.exports = FirstPersonCamera
14 |
15 | FirstPersonCamera.prototype.view = function(out) {
16 | if (!out) out = mat4.create()
17 | // altered x/y ordering from original
18 | mat4.rotateY(out, out, this.rotation[1])
19 | mat4.rotateX(out, out, this.rotation[0])
20 | mat4.rotateZ(out, out, this.rotation[2] - Math.PI)
21 | mat4.translate(out, out, [-this.position[0], -this.position[1], -this.position[2]])
22 |
23 | return out
24 | }
25 |
26 | FirstPersonCamera.prototype.control = function(dt, move, mouse, prevMouse) {
27 | var speed = (this.positionSpeed / 1000) * dt
28 | var dir = [0,0,0]
29 | if (move[0]) dir[2] -= speed * (Marching.keys.Alt ? 4 : 1 )
30 | else if (move[1]) dir[2] += speed * (Marching.keys.Alt ? 4 : 1 )
31 | if (move[2]) dir[0] += speed * (Marching.keys.Alt ? 4 : 1 )
32 | else if (move[3]) dir[0] -= speed * (Marching.keys.Alt ? 4 : 1 )
33 | if (move[4]) dir[1] -= speed * (Marching.keys.Alt ? 4 : 1 )
34 | else if (move[5]) dir[1] += speed * (Marching.keys.Alt ? 4 : 1 )
35 | this.move(dir)
36 | // just use arrow keys instead of mouse
37 | // this.pointer(mouse, prevMouse)
38 | }
39 |
40 | FirstPersonCamera.prototype.move = function(dir) {
41 | if (dir[0] !== 0 || dir[1] !== 0 || dir[2] !== 0) {
42 | var cam = mat4.create()
43 | mat4.rotateY(cam, cam, this.rotation[1])
44 | mat4.rotateX(cam, cam, this.rotation[0])
45 | vec3.transformMat4(dir, dir, cam)
46 | vec3.add(this.position, this.position, dir)
47 | this.parent.pos.dirty = true
48 |
49 | }
50 | }
51 |
52 | //FirstPersonCamera.prototype.pointer = function(da, db) {
53 | // var dt = [da[0] - db[0], da[1]- db[1]]
54 | // var rot = this.rotation
55 | // rot[1] -= dt[0] * this.rotationSpeed
56 | // if (rot[1] < 0) rot[1] += Math.PI * 2
57 | // if (rot[1] >= Math.PI * 2) rot[1] -= Math.PI * 2
58 | // rot[0] -= dt[1] * this.rotationSpeed
59 | // if (rot[0] < -Math.PI * .5) rot[0] = -Math.PI*0.5
60 | // if (rot[0] > Math.PI * .5) rot[0] = Math.PI*0.5
61 | //}
62 |
63 | const Camera = {
64 | init( gl, program, handler ) {
65 |
66 | const camera = FirstPersonCamera({
67 | fov: 190,
68 | near:.01,
69 | far:10,
70 | direction:[0,0,1],
71 | viewport:[1,1,1,-1]
72 | })
73 | camera.rotation = [0,Math.PI,Math.PI]
74 | Camera.__camera = camera
75 | camera.parent = this
76 |
77 | const camera_pos = gl.getUniformLocation( program, 'camera_pos' )
78 | const camera_normal = gl.getUniformLocation( program, 'camera_normal' )
79 | const camera_rot = gl.getUniformLocation( program, 'camera_rot' )
80 | const ucamera = gl.getUniformLocation( program, 'camera' )
81 |
82 | this.pos = { dirty:false }
83 | this.dir = { dirty:true }
84 | this.__rot = { dirty:true, value:0 }
85 |
86 | Object.defineProperty( this, 'rotation', {
87 | configurable:true,
88 | get() { return this.__rot.value },
89 | set(v) {
90 | this.__rot.value = v
91 | this.__rot.dirty = true
92 | }
93 | })
94 |
95 | let px = 0, py =0, pz = 5, nx = 0, ny = 0, nz = 0
96 | Object.defineProperties( this.pos, {
97 | x: {
98 | get() { return px },
99 | set(v) { px = camera.position[0] = v;this.dirty = true; }
100 | },
101 |
102 | y: {
103 | get() { return py },
104 | set(v) { py = camera.position[1] = v; this.dirty = true; }
105 | },
106 |
107 | z: {
108 | get() { return pz },
109 | set(v) { pz = camera.position[2] = v; this.dirty = true; }
110 | },
111 | })
112 |
113 | Object.defineProperties( this.dir, {
114 | x: {
115 | get() { return nx },
116 | set(v) { nx = camera.rotation[0] = v; this.dirty = true; }
117 | },
118 |
119 | y: {
120 | get() { return ny },
121 | set(v) { ny = camera.rotation[1] = v; this.dirty = true; }
122 | },
123 |
124 | z: {
125 | get() { return nz },
126 | set(v) { nz = camera.rotation[2] = v; this.dirty = true; }
127 | },
128 | })
129 |
130 | let init = false
131 | gl.uniform3f( camera_normal, this.dir.x, this.dir.y, this.dir.z )
132 | camera.position = [this.pos.x, this.pos.y, this.pos.z ]
133 | //camera.update()
134 | gl.uniform3f( camera_pos, this.pos.x, this.pos.y, this.pos.z )
135 | gl.uniformMatrix4fv( ucamera, false, camera.view() )
136 | gl.uniform1f( camera_rot, this.rot )
137 |
138 | Camera.move = (x,y,z) => {
139 | // XXX does this need to update property values?
140 | camera.move([x,y,z])
141 | Camera.update()
142 | }
143 | Camera.moveTo = (x,y,z) => {
144 | Camera.pos.x = x
145 | Camera.pos.y = y
146 | Camera.pos.z = z
147 | }
148 | Camera.update = ()=> {
149 | const pos = camera.position
150 | gl.uniform3f( camera_pos, pos[0], pos[1], pos[2] )
151 | gl.uniformMatrix4fv( ucamera, false, camera.view() )
152 | }
153 |
154 | // determine an offset from the current camera position based
155 | // on the current camera rotation e.g. to always position a light
156 | // behind the camera.
157 | Camera.offset = (amt=[0,0,3]) => {
158 | const cam = mat4.create()
159 | mat4.rotateY(cam, cam, camera.rotation[1])
160 | mat4.rotateX(cam, cam, camera.rotation[0])
161 | vec3.transformMat4(amt, amt, cam)
162 | return amt
163 | }
164 |
165 | let prvx = 0, prvy = 0, x = 0, y = 0
166 | Camera.__mousemovefnc = e => {
167 | prvx = x
168 | prvy = y
169 | x = e.pageX
170 | y = e.pageY
171 | }
172 |
173 | let prevTime = 0
174 | let k = Marching.keys
175 | Camera.__framefnc = t => {
176 | if( k.ArrowLeft ) camera.rotation[1] += camera.rotationSpeed
177 | if( k.ArrowRight ) camera.rotation[1] -= camera.rotationSpeed
178 | if( k.ArrowUp && !k.Shift ) camera.rotation[0] -= camera.rotationSpeed
179 | if( k.ArrowDown && !k.Shift) camera.rotation[0] += camera.rotationSpeed
180 |
181 | if( Marching.cameraEnabled ) {
182 | camera.control(
183 | t*1000 - prevTime,
184 | [k.w,k.s,k.d,k.a,k.ArrowUp && k.Shift, k.ArrowDown && k.Shift],
185 | [x,y], [prvx,prvy]
186 | )
187 | Camera.update()
188 | prvx = x
189 | prvy = y
190 | prevTime = t*1000
191 | }
192 | }
193 |
194 | Camera.__mousemove = null
195 | Camera.on = ()=> {
196 | if( Camera.__mousemove === null ) {
197 | window.addEventListener( 'mousemove', Camera.__mousemovefnc )
198 | Camera.__mousemove = true
199 | }
200 | if( Marching.callbacks.indexOf( Camera.__framefnc ) === -1 ) {
201 | Marching.callbacks.push( Camera.__framefnc )
202 | }
203 | }
204 |
205 | handler( ()=> {
206 | if( this.pos.dirty === true ) {
207 |
208 | //camera.position = [this.pos.x, this.pos.y, this.pos.z ]
209 |
210 | //camera.position = [this.pos.x, this.pos.y, this.pos.z ]
211 | //camera.update()
212 | const pos = camera.position
213 | gl.uniform3f( camera_pos, pos[0], pos[1], pos[2] )
214 | gl.uniformMatrix4fv( ucamera, false, camera.view() )
215 | this.pos.dirty = false
216 |
217 |
218 | }
219 |
220 | // XXX this is broken and needs to be fixed
221 | if( this.dir.dirty === true ) {
222 | gl.uniform3f( camera_normal, this.dir.x, this.dir.y, this.dir.z )
223 | gl.uniformMatrix4fv( ucamera, false, camera.view() )
224 | this.dir.dirty = false
225 | }
226 | if( this.__rot.dirty === true ) {
227 | gl.uniform1f( camera_rot, this.__rot.value )
228 | this.__rot.dirty = false
229 | }
230 | if( typeof this.onmove === 'function' ) {
231 | this.onmove( this )
232 | }
233 | })
234 |
235 | }
236 | }
237 |
238 | module.exports = Camera
239 |
--------------------------------------------------------------------------------
/js/renderFragmentShader.js:
--------------------------------------------------------------------------------
1 | const getMainContinuous = function( steps, minDistance, maxDistance, postprocessing, bg='vec4(0.,0.,0.,1.)' ) {
2 | const out = `
3 | // adapted from https://www.shadertoy.com/view/ldfSWs
4 | vec3 calcNormal(vec3 pos, float eps) {
5 | const vec3 v1 = vec3( 1.0,-1.0,-1.0);
6 | const vec3 v2 = vec3(-1.0,-1.0, 1.0);
7 | const vec3 v3 = vec3(-1.0, 1.0,-1.0);
8 | const vec3 v4 = vec3( 1.0, 1.0, 1.0);
9 |
10 | return normalize( v1 * scene ( pos + v1*eps ).x+
11 | v2 * scene ( pos + v2*eps ).x+
12 | v3 * scene ( pos + v3*eps ).x+
13 | v4 * scene ( pos + v4*eps ).x);
14 | }
15 |
16 | vec3 calcNormal(vec3 pos) {
17 | return calcNormal(pos, 0.002);
18 | }
19 |
20 | // Adapted from from https://www.shadertoy.com/view/ldfSWs
21 | vec2 calcRayIntersection( vec3 rayOrigin, vec3 rayDir, float maxd, float precis ) {
22 | float latest = precis * 2.0;
23 | float dist = +0.0;
24 | float type = -1.0;
25 | vec2 result;
26 | vec2 res = vec2(-50000., -1.);;
27 |
28 | for (int i = 0; i < ${steps} ; i++) {
29 | if (latest < precis || dist > maxd) break;
30 |
31 | result = scene(rayOrigin + rayDir * dist);
32 |
33 | latest = result.x;
34 | dist += latest;
35 | }
36 |
37 | if( dist < maxd ) {
38 | result.x = dist;
39 | res = result;
40 | }
41 |
42 | return res;
43 | }
44 |
45 | layout(location = 0) out vec4 col;
46 | layout(location = 1) out vec4 depth;
47 | void main() {
48 | vec2 uv = gl_FragCoord.xy / resolution;
49 | vec2 pos = uv * 2.0 - 1.0;
50 |
51 | // not sure why I need the -y axis but without it
52 | // everything is flipped using perspective-camera
53 | pos.x *= ( resolution.x / -resolution.y );
54 |
55 | vec4 color = bg;
56 | vec3 ro = camera_pos;
57 | vec3 rd = normalize( mat3(camera) * vec3( pos, 2. ) );
58 |
59 | vec2 t = calcRayIntersection( ro, rd, ${maxDistance}, ${minDistance} );
60 |
61 | vec3 samplePos = vec3(100.f);
62 | if( t.x > -0.5 ) {
63 | samplePos = ro + rd * t.x;
64 | vec3 nor = calcNormal( samplePos );
65 |
66 | color = vec4( lighting( samplePos, nor, ro, rd, t.y, true ), 1. );
67 | ${postprocessing}
68 | }
69 |
70 |
71 | col = clamp( vec4( color ), 0., 1. );
72 |
73 | float normalizedDepth = t.x / ${maxDistance};
74 | depth = abs(samplePos.z - ro.z ) < ${maxDistance} ? vec4( vec3( 1.-normalizedDepth ), 1. ) : vec4(0.);
75 | }`
76 |
77 | return out
78 | }
79 |
80 | const getMainVoxels = function( steps, postprocessing, voxelSize = .05 ) {
81 | const out = `
82 | struct VoxelDistance {
83 | bvec3 mask;
84 | vec3 distance;
85 | float fogCoeff;
86 | int id;
87 | };
88 |
89 | VoxelDistance calcRayIntersection( vec3 rayOrigin, vec3 rayDir ) {
90 | vec2 result;
91 |
92 | float m = ${voxelSize.toFixed(2)};
93 | rayOrigin *= 1./m;
94 | vec3 mapPos = vec3(floor(rayOrigin));
95 | vec3 diff = mapPos - rayOrigin;
96 |
97 | vec3 deltaDist = abs(vec3(length(rayDir)) / rayDir);
98 | vec3 rayStep = vec3(sign(rayDir));
99 | vec3 sideDist = (sign(rayDir) * diff + (sign(rayDir) * 0.5) + 0.5) * deltaDist;
100 |
101 | bvec3 mask;
102 | vec3 d = vec3(-100000.);
103 | float fogCoeff = 0.;
104 |
105 | for (int i = 0; i < ${Math.round(steps*1/voxelSize)} ; i++) {
106 | result = scene(mapPos*m);
107 | if( result.x <= 0. ) {
108 | d = mapPos*m+result.x;
109 | break;
110 | }
111 |
112 | mask = bvec3( lessThanEqual(sideDist.xyz, min(sideDist.yzx, sideDist.zxy)) );
113 | sideDist += vec3( mask ) * deltaDist;
114 | mapPos += vec3(mask) * rayStep;
115 | fogCoeff += result.x * m;
116 | }
117 |
118 | VoxelDistance vd = VoxelDistance( mask, d, fogCoeff, int(result.y) );
119 | return vd;
120 | }
121 |
122 | layout(location = 0) out vec4 col;
123 | layout(location = 1) out vec4 depth;
124 | void main() {
125 | vec2 uv = gl_FragCoord.xy / resolution;
126 | vec2 pos = uv * 2.0 - 1.0;
127 |
128 | // not sure why I need the -y axis but without it
129 | // everything is flipped using perspective-camera
130 | pos.x *= ( resolution.x / -resolution.y );
131 |
132 | vec4 color = bg;
133 | vec3 ro = camera_pos;
134 | vec3 rd = normalize( mat3(camera) * vec3( pos, 2. ) );
135 |
136 | VoxelDistance vd = calcRayIntersection( ro, rd );
137 | bvec3 mask = vd.mask;
138 |
139 | vec3 nor;
140 | if (mask.x) {
141 | color = vec4(vec3(0.5), 1.);
142 | nor = vec3(1.,0.,0.);
143 | }
144 | if (mask.y) {
145 | color = vec4( vec3(1.0), 1. );
146 | nor = vec3(0.,1.,0.);
147 | }
148 | if (mask.z) {
149 | color = vec4( vec3(0.75), 1. );
150 | nor = vec3(0.,0.,1.);
151 | }
152 | if( vd.distance.x == -100000. ) {
153 | color = bg;
154 | }
155 |
156 | float modAmount = ${(1./voxelSize).toFixed(2)};
157 | bool hit = false;
158 | vec3 t = vec3( length(vd.distance-ro) );
159 |
160 | if( color != bg ) {
161 | vec3 pos = vd.distance;
162 | color.xyz *= lighting( pos * modAmount, nor, ro, rd, float(vd.id), false );
163 | hit = true;
164 | ${postprocessing};
165 | }
166 |
167 | col = color;
168 |
169 | float normalizedDepth = length( (vd.distance-ro) * ${voxelSize.toFixed(2)} );
170 | depth = hit == true ? vec4( vec3(1.-normalizedDepth), 1. ) : vec4(0.);
171 | }`
172 |
173 | return out
174 | }
175 |
176 | module.exports = function( variables, scene, preface, geometries, lighting, postprocessing, steps=90, minDistance=.001, maxDistance=20, ops, useVoxels=false, voxelSize=0 ) {
177 |
178 | const main = useVoxels === false
179 | ? getMainContinuous( steps, minDistance, maxDistance, postprocessing )
180 | : getMainVoxels( steps, postprocessing, voxelSize )
181 |
182 | const fs_source = ` #version 300 es
183 | precision highp float;
184 |
185 | float PI = 3.141592653589793;
186 |
187 | struct Light {
188 | vec3 position;
189 | vec3 color;
190 | float attenuation;
191 | };
192 |
193 | int rotationCount = 1;
194 |
195 | mat4 rotations[4] = mat4[4](
196 | mat4(0.), mat4(0.), mat4(0.), mat4(0.)
197 | );
198 |
199 | struct Material {
200 | int mode;
201 | vec3 ambient;
202 | vec3 diffuse;
203 | vec3 specular;
204 | float shininess;
205 | vec3 fresnel;
206 | int textureID;
207 | };
208 |
209 | struct SDF {
210 | int materialID;
211 | mat4 transform;
212 | int textureID;
213 | vec3 repeat;
214 | mat4 repeatTransform;
215 | };
216 |
217 | uniform vec3 camera_pos;
218 | uniform vec3 camera_normal;
219 | uniform float camera_rot;
220 | uniform mat4 camera;
221 |
222 | vec2 resolution = vec2( ${ Marching.__scene.width }., ${Marching.__scene.height}. );
223 |
224 | ${variables}
225 |
226 | // must be before geometries!
227 | float length8( vec2 p ) {
228 | return float( pow( pow(p.x,8.)+pow(p.y,8.), 1./8. ) );
229 | }
230 |
231 | vec4 opElongate( in vec3 p, in vec3 h ) {
232 | //return vec4( p-clamp(p,-h,h), 0.0 ); // faster, but produces zero in the interior elongated box
233 |
234 | vec3 q = abs(p)-h;
235 | return vec4( max(q,0.0), min(max(q.x,max(q.y,q.z)),0.0) );
236 | }
237 | ${ops}
238 |
239 | /* GEOMETRIES */
240 | ${geometries}
241 |
242 | vec2 scene(vec3 p);
243 |
244 | // XXX todo put this in domainOperations.js
245 | vec3 polarRepeat(vec3 p, float repetitions) {
246 | float angle = 2.*PI/repetitions;
247 | float a = atan(p.z, p.x) + angle/2.;
248 | float r = length(p.xz);
249 | float c = floor(a/angle);
250 | a = mod(a,angle) - angle/2.;
251 | vec3 _p = vec3( cos(a) * r, p.y, sin(a) * r );
252 | // For an odd number of repetitions, fix cell index of the cell in -x direction
253 | // (cell index would be e.g. -5 and 5 in the two halves of the cell):
254 | if (abs(c) >= (repetitions/2.)) c = abs(c);
255 | return _p;
256 | }
257 |
258 | // XXX this shouldn't be here...
259 | float opHalve( in float sdf, vec4 p, in int dir ){
260 | float _out = 0.;
261 | switch( dir ) {
262 | case 0:
263 | _out = max( sdf, p.y );
264 | break;
265 | case 1:
266 | _out = max( sdf, -p.y );
267 | break;
268 | case 2:
269 | _out = max( sdf, p.x );
270 | break;
271 | case 3:
272 | _out = max( sdf, -p.x );
273 | break;
274 | }
275 |
276 | return _out;
277 | }
278 |
279 | // taken from https://iquilezles.org/articles/rmshadows/
280 | float softshadow( in vec3 ro, in vec3 rd, float mint, float maxt, float w )
281 | {
282 | float res = 1.0;
283 | float t = mint;
284 | for( int i=0; i<256 && tmaxt ) break;
290 | }
291 | res = max(res,-1.0);
292 | return 0.25*(1.0+res)*(1.0+res)*(2.0-res);
293 | }
294 |
295 | ${lighting}
296 |
297 | vec2 scene(vec3 _p ) {
298 | vec4 p = vec4( _p, 1. );
299 | ${preface}
300 | return ${scene};
301 | }
302 |
303 | ${main}
304 | `
305 |
306 | return fs_source
307 | }
308 |
--------------------------------------------------------------------------------
/js/scene.js:
--------------------------------------------------------------------------------
1 | const getFog = require( './fog.js' )
2 | const vignette = require( './vignette.js' )
3 | const { param_wrap, MaterialID } = require( './utils.js' )
4 | const __lighting = require( './lighting.js' )
5 | const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require('./var.js')
6 |
7 | const getScene = function( SDF ) {
8 |
9 | Scene = function( objs, canvas, steps=100, minDistance=.001, maxDistance=40, size=2, shouldAnimate=false ) {
10 | const scene = Object.create( Scene.prototype )
11 |
12 | MaterialID.clear()
13 |
14 | SDF.lighting.lights = []
15 |
16 | scene.__prerender = objs
17 | if( objs.length > 1 ) {
18 | // reduce objects to nested Unions
19 | scene.__prerender = objs.reduce( ( current, next ) => SDF.Union( current, next ) )
20 | }
21 |
22 | Object.assign( scene, {
23 | objs,
24 | canvas,
25 | postprocessing:[],
26 | __shadow:.2,
27 | __followLight:null,
28 | __postprocessingFlag:false,
29 | __steps:null,
30 | __thresold:null,
31 | __farPlane:null,
32 | __resolution:null,
33 | __voxelSize:.1
34 | })
35 |
36 | scene.useQuality = true
37 | scene.useVoxels = false
38 |
39 | SDF.__scene = scene
40 |
41 | return scene
42 | }
43 |
44 | Scene.prototype = {
45 | animate( v ) { this.__animate = v; return this },
46 | setdim( w, h ) {
47 | this.width = w
48 | this.height = h
49 |
50 | this.donotuseresolution = true
51 | return this
52 | },
53 | resolution( v ) {
54 | this.width = Math.floor( this.canvas.width = window.innerWidth * v )
55 | this.height = Math.floor( this.canvas.height = window.innerHeight * v )
56 |
57 | this.__resolution = v;
58 | return this
59 | },
60 | voxel( v = .1 ) {
61 | this.useVoxels = true
62 | this.__voxelSize = v
63 | return this
64 | },
65 | threshold( v ) { this.__threshold = v; return this },
66 | steps( v ) { this.__steps = v; return this },
67 | farPlane( v ) { this.__farPlane = v; return this },
68 | camera( x=0, y=0, z=5, speed=1 ) {
69 | SDF.camera.__camera.position[0] = x
70 | SDF.camera.__camera.position[1] = y
71 | SDF.camera.__camera.position[2] = z
72 | SDF.camera.__camera.rotationSpeed = speed * .01
73 | SDF.camera.__camera.positionSpeed = speed * -.25
74 | SDF.camera.update()
75 | return this
76 | },
77 | shadow( k=0 ) {
78 | this.__shadow = k;
79 | return this;
80 | },
81 | quality( quality=10 ) {
82 | if( this.__thresold === null ) this.threshold( .1 / (quality * quality * quality ) )
83 | if( this.__steps === null ) this.steps( quality * 20 )
84 | if( this.__farPlane === null ) this.farPlane( quality * 5 )
85 | if( this.donotuseresolution === undefined && this.__resolultion === null ) this.resolution( Math.min( .2 * quality, 2 ) )
86 |
87 | return this
88 | },
89 | follow( light, distance=3 ) {
90 | this.__followLight = light
91 | SDF.camera.onmove = function( camera ) {
92 | const offset = SDF.camera.offset()
93 | light.pos.x = SDF.camera.__camera.position[0] - offset[0]
94 | light.pos.y = SDF.camera.__camera.position[1] - offset[1]
95 | light.pos.z = SDF.camera.__camera.position[2] - offset[2]
96 | light.dirty = true
97 | }
98 | SDF.lighting.lights = [light]
99 | return this
100 | },
101 | light( ...lights ) {
102 | SDF.lighting.lights = SDF.lighting.lights.concat( lights )
103 | if( this.__followLight !== null ) SDF.lighting.lights.push( this.__followLight )
104 | return this
105 | },
106 | fog: getFog( Scene, SDF ),
107 | vignette: vignette( Scene, SDF ),
108 | background: require( './background.js' )( Scene, SDF ),
109 |
110 | applyPreset( presetName ) {
111 | const preset = this.presets[ presetName ]
112 | if( preset === undefined ) {
113 | throw ReferenceError(`The render preset ${presetName} doesn't exist.`)
114 | }
115 | if( preset.farPlane !== undefined ) {
116 | this.farPlane( this.__farPlane || preset.farPlane )
117 | }else{
118 | this.__farPlane = 0
119 | }
120 | this.steps( this.__steps || preset.steps )
121 | if( this.donotuseresolution === undefined ) this.resolution( this.__resolution || preset.resolution )
122 | this.threshold( this.__threshold || preset.threshold || .001 )
123 | this.useVoxels = presetName.indexOf( 'voxel' ) !== -1
124 |
125 | return preset.animated
126 | },
127 |
128 | post( ...fx ) {
129 | this.__postprocessingFlag = true
130 | SDF.fx.clear()
131 | SDF.fx.post( ...fx )
132 | return this
133 | },
134 |
135 | render( quality=10, animate=false, useQuality=true, shouldResetTime=false ) {
136 | // adds default if none has been specified
137 | this.background()
138 | if( this.__postprocessingFlag === false ) { SDF.fx.clear() }
139 |
140 | if( typeof quality === 'string' ) {
141 | animate = this.applyPreset( quality )
142 | }else if( this.useQuality === true ) {
143 | this.quality( quality )
144 | }
145 |
146 | this.animate( animate )
147 |
148 | //SDF.distanceOps.__clear()
149 | SDF.alterations.__clear()
150 | SDF.textures.clear()
151 |
152 | // MATERIALS MUST BE GENERATED BEFORE GEOMETRIES,
153 | // SO THAT GEOMETRIES CAN PROPERLY REFERENCE THEM
154 | SDF.materials.generate()
155 | const geometries = SDF.primitives.emit_geometries()
156 |
157 | let [ variablesDeclaration, sceneRendering, postprocessing ] = SDF.generateSDF( this )
158 | //SDF.materials.generate( Marching.scene )
159 |
160 | const lighting = SDF.lighting.gen( this.__shadow, geometries )
161 | variablesDeclaration += SDF.materials.emit_decl()
162 | variablesDeclaration += SDF.textures.emit_decl()
163 | variablesDeclaration += SDF.lighting.emit_decl()
164 | variablesDeclaration += this.__background.emit_decl()
165 |
166 | if( this.width === undefined ) this.width = window.innerWidth
167 | if( this.height === undefined ) this.height = window.innerHeight
168 |
169 | this.fs = SDF.renderFragmentShader(
170 | variablesDeclaration,
171 | sceneRendering.out,
172 | sceneRendering.preface,
173 | SDF.requiredGeometries.join('\n') + SDF.requiredOps.join('\n'),
174 | lighting,
175 | postprocessing,
176 | this.__steps, this.__threshold, this.__farPlane.toFixed(1),
177 | SDF.distanceOps.__getGLSL() + SDF.alterations.__getGLSL(),
178 | this.useVoxels,
179 | this.__voxelSize
180 | )
181 |
182 |
183 | const time = SDF.render !== null && !shouldResetTime ? SDF.render.time : 0
184 | SDF.start( this.fs, this.width, this.height, this.__animate, time )
185 |
186 | //SDF.materials.materials.length = 0
187 |
188 | this.useQuality = true
189 |
190 | this.__postprocessingFlag = false
191 | this.__threshold = null
192 | this.__farPlane = null
193 | this.__steps = null
194 | this.__resolution = null
195 |
196 | return this
197 | },
198 | presets: {
199 | 'fractal.close': {
200 | farPlane:1,
201 | resolution:1,
202 | steps:150,
203 | animated:true,
204 | threshold:.000125
205 | },
206 | 'fractal.kindaclose': {
207 | farPlane:2,
208 | resolution:1,
209 | steps:250,
210 | animated:true,
211 | threshold:.000125/2
212 | },
213 | 'fractal.med': {
214 | farPlane:5,
215 | resolution:.75,
216 | steps:80,
217 | animated:true,
218 | threshold:.001,
219 | },
220 | 'fractal.low': {
221 | farPlane:3.0,
222 | resolution:.5,
223 | animated:true,
224 | steps:50,
225 | threshold:.005,
226 | },
227 | 'fractal.high': {
228 | farPlane:10,
229 | resolution:1,
230 | animated:true,
231 | steps:100,
232 | threshold:.001,
233 | },
234 | 'repeat.low': {
235 | farPlane:25,
236 | resolution:.5,
237 | animated:true,
238 | steps:50
239 | },
240 | 'repeat.med': {
241 | farPlane:35,
242 | resolution:1,
243 | animated:true,
244 | steps:75
245 | },
246 | 'repeat.high': {
247 | farPlane:40,
248 | resolution:1,
249 | animated:true,
250 | steps:100
251 | },
252 | 'voxel.high': {
253 | resolution:1,
254 | animated:true,
255 | steps:30
256 | },
257 | 'voxel.med': {
258 | resolution:1,
259 | animated:true,
260 | steps:20
261 | },
262 | 'voxel.low': {
263 | resolution:.5,
264 | animated:true,
265 | steps:10
266 | },
267 | low: {
268 | threshold:.05,
269 | steps:45,
270 | farPlane:12,
271 | resolution:.4,
272 | animated:true
273 | },
274 | medium: {
275 | threshold:.01,
276 | steps:80,
277 | farPlane:18,
278 | resolution:.5,
279 | animated:true
280 | },
281 | med: {
282 | threshold:.01,
283 | steps:80,
284 | farPlane:18,
285 | resolution:.5,
286 | animated:true
287 | },
288 | high: {
289 | threshold:.005,
290 | steps:90,
291 | farPlane:20,
292 | resolution:1,
293 | animated:true
294 | }
295 | },
296 |
297 | }
298 |
299 | return Scene
300 |
301 | }
302 |
303 | module.exports = getScene
304 |
--------------------------------------------------------------------------------
/js/material.js:
--------------------------------------------------------------------------------
1 | const SceneNode = require( './sceneNode.js' ),
2 | { param_wrap, MaterialID } = require( './utils.js' ),
3 | { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen } = require( './var.js' ),
4 | { Vec2, Vec3, Vec4 } = require( './vec.js' )
5 |
6 |
7 | const glsl = require( 'glslify' )
8 |
9 | const __Materials = function( SDF ) {
10 |
11 | const Materials = {
12 | materials:[],
13 | __materials:[],
14 | __clearOnEmit: true,
15 | modeConstants : [
16 | 'global',
17 | 'normal',
18 | 'phong',
19 | 'orenn',
20 | 'noise'
21 | ],
22 |
23 | default: 'global',
24 |
25 | //defaultMaterials:`
26 | // Material materials[2] = Material[2](
27 | // Material( 0, vec3( 1. ), vec3(0.,0.,0.), vec3(1.), 8., Fresnel( 0., 1., 2.) ),
28 | // Material( 0, vec3( 1. ), vec3(1.,0.,0.), vec3(1.), 8., Fresnel( 0., 1., 2.) )
29 | // );
30 | //`,
31 |
32 | addMaterial( mat ) {
33 | if( mat === undefined ) mat = Materials.material.default
34 |
35 | if( Materials.materials.indexOf( mat ) === -1 ) {
36 | mat.id = MaterialID.alloc()
37 |
38 | // we have to dirty the material so that its data
39 | // will be uploaded to new shaders, otherwise the
40 | // material will only work the first time it's used, when
41 | // it's dirty on initialization.
42 | Materials.dirty( mat )
43 |
44 | Materials.materials.push( mat )
45 | }else{
46 | mat.id = Materials.materials.indexOf( mat )
47 | }
48 |
49 | return mat
50 | },
51 |
52 | material( mode='global', __ambient, __diffuse, __specular, __shininess, __fresnel, __texture=null ){
53 | let modeIdx = Materials.modeConstants.indexOf( mode )
54 | if( modeIdx === -1 ) {
55 | console.warn( `There is no material type named ${mode}. Using the default material, ${Materials.default}, instead.` )
56 | mode = Materials.default
57 | modeIdx = Materials.modeConstants.indexOf( mode )
58 | }
59 |
60 | if( typeof __ambient === 'number' ) __ambient = Vec3( __ambient )
61 | const ambient = param_wrap( __ambient, vec3_var_gen(.1,.1,.1) )
62 | if( typeof __diffuse=== 'number' ) __diffuse= Vec3( __diffuse )
63 | const diffuse = param_wrap( __diffuse, vec3_var_gen(0,0,1) )
64 | if( typeof __specular === 'number' ) __specular = Vec3( __specular )
65 | const specular = param_wrap( __specular, vec3_var_gen(1,1,1) )
66 | const shininess = param_wrap( __shininess, float_var_gen(8) )
67 | if( typeof __fresnel === 'number' ) __fresnel = Vec3( __fresnel )
68 | const fresnel = param_wrap( __fresnel, vec3_var_gen(0,1,2) )
69 |
70 | const mat = { shininess, mode, texture:__texture, type:'material' }
71 |
72 | Object.defineProperty( mat, 'ambient', {
73 | get() { return ambient },
74 | set(v) {
75 | if( typeof v === 'object' ) {
76 | ambient.set( v )
77 | }else{
78 | ambient.value.x = v
79 | ambient.value.y = v
80 | ambient.value.z = v
81 | ambient.dirty = true
82 | }
83 | }
84 | })
85 | Object.defineProperty( mat, 'diffuse', {
86 | get() { return diffuse },
87 | set(v) {
88 | if( typeof v === 'object' ) {
89 | diffuse.set( v )
90 | }else{
91 | diffuse.value.x = v
92 | diffuse.value.y = v
93 | diffuse.value.z = v
94 | diffuse.dirty = true
95 | }
96 | }
97 | })
98 | Object.defineProperty( mat, 'specular', {
99 | get() { return specular },
100 | set(v) {
101 | if( typeof v === 'object' ) {
102 | specular.set( v )
103 | }else{
104 | specular.value.x = v
105 | specular.value.y = v
106 | specular.value.z = v
107 | specular.dirty = true
108 | }
109 | }
110 | })
111 | Object.defineProperty( mat, 'fresnel', {
112 | get() { return fresnel },
113 | set(v) {
114 | if( typeof v === 'object' ) {
115 | fresnel.set( v )
116 | }else{
117 | fresnel.value.x = v
118 | fresnel.value.y = v
119 | fresnel.value.z = v
120 | fresnel.dirty = true
121 | }
122 | }
123 | })
124 | //Object.defineProperty( mat, 'shininess', {
125 | // get() { return mat.shininess.value },
126 | // set(v){
127 | // mat.shininess.value = v
128 | // mat.shininess.dirty = true
129 | // }
130 | //}) //
131 |
132 | return mat
133 | },
134 |
135 | dirty( mat ) {
136 | mat.ambient.dirty = true
137 | mat.diffuse.dirty = true
138 | mat.specular.dirty = true
139 | mat.shininess.dirty = true
140 | mat.fresnel.dirty = true
141 | if( mat.texture !== null ) mat.texture.dirty = true
142 | },
143 |
144 | walk( branch ) {
145 | if( branch === null ) return
146 | if( branch.__material ) {
147 | branch.mat = branch.__material = this.addMaterial( branch.__material )
148 | }
149 |
150 | if( 'a' in branch ) {
151 | // combinators
152 | this.walk( branch.a )
153 | this.walk( branch.b )
154 | }else if( 'sdf' in branch ) {
155 | // repeat etc.
156 | this.walk( branch.sdf )
157 | }
158 | },
159 |
160 | generate() {
161 | this.materials = []
162 | MaterialID.clear()
163 |
164 | const head = Array.isArray( SDF.__scene.__prerender )
165 | ? SDF.__scene.__prerender[0]
166 | : SDF.__scene.__prerender
167 |
168 | this.walk( head )
169 | },
170 |
171 | emit_materials() {
172 | let str = `Material materials[${this.materials.length}] = Material[${this.materials.length}](`
173 |
174 | this.materials.sort( (a,b) => a.id > b.id ? 1 : -1 )
175 |
176 | for( let mat of this.materials ) {
177 | Materials.dirty( mat )
178 | str += `\n Material( ${this.modeConstants.indexOf( mat.mode )}, ${mat.ambient.emit()}, ${mat.diffuse.emit()}, ${mat.specular.emit()}, ${mat.shininess.emit()}, ${mat.fresnel.emit()}, 0 ),`
179 | }
180 |
181 | str = str.slice(0,-1) // remove trailing comma
182 |
183 | str += '\n );'
184 |
185 | this.__materials = this.materials.slice( 0 )
186 | this.__str = str
187 | if( this.__clearOnEmit ) {
188 | //this.materials.length = 0
189 | }
190 |
191 | return str
192 | },
193 |
194 | emit_decl() {
195 | let str = ''
196 | for( let mat of this.__materials ) {
197 | str += mat.ambient.emit_decl()
198 | str += mat.diffuse.emit_decl()
199 | str += mat.specular.emit_decl()
200 | str += mat.shininess.emit_decl()
201 | str += mat.fresnel.emit_decl()
202 | }
203 |
204 | return str
205 | },
206 |
207 | update_location( gl, program ) {
208 | for( let mat of this.__materials ) {
209 | if( mat.ambient.dirty === true ) mat.ambient.update_location( gl, program )
210 | if( mat.diffuse.dirty === true ) mat.diffuse.update_location( gl, program )
211 | if( mat.specular.dirty === true ) mat.specular.update_location( gl, program )
212 | if( mat.shininess.dirty === true ) mat.shininess.update_location( gl, program )
213 | if( mat.fresnel.dirty === true ) mat.fresnel.update_location( gl, program )
214 | }
215 | },
216 |
217 | upload_data( gl, program='' ) {
218 | for( let mat of this.__materials ) {
219 | if( mat.ambient.dirty === true ) mat.ambient.upload_data( gl, program )
220 | if( mat.diffuse.dirty === true ) mat.diffuse.upload_data( gl, program )
221 | if( mat.specular.dirty === true ) mat.specular.upload_data( gl, program )
222 | if( mat.shininess.dirty === true ) mat.shininess.upload_data( gl, program )
223 | if( mat.fresnel.dirty === true ) mat.fresnel.upload_data( gl, program )
224 | }
225 | }
226 | }
227 |
228 | const f = value => value % 1 === 0 ? value.toFixed(1) : value
229 |
230 | Object.assign( Materials.material, {
231 | default : Materials.material( 'global', Vec3( .15 ), Vec3(0), Vec3(1), 8, Vec3( 0 ) ),
232 | red : Materials.material( 'global', Vec3(.25,0,0), Vec3(1,0,0), Vec3(0), 2, Vec3(0) ),
233 | green : Materials.material( 'global', Vec3(0,.25,0), Vec3(0,1,0), Vec3(0), 2, Vec3(0) ),
234 | blue : Materials.material( 'global', Vec3(0,0,.25), Vec3(0,0,1), Vec3(0), 2, Vec3(0) ),
235 | cyan : Materials.material( 'global', Vec3(0,.25,.25), Vec3(0,1,1), Vec3(0), 2, Vec3(0) ),
236 | magenta : Materials.material( 'global', Vec3(.25,0,.25), Vec3(1,0,1), Vec3(0), 2, Vec3(0) ),
237 | yellow : Materials.material( 'global', Vec3(.25,.25,.0), Vec3(1,1,0), Vec3(0), 2, Vec3(0) ),
238 | black : Materials.material( 'global', Vec3(0, 0, 0), Vec3(0,0,0), Vec3(0), 2, Vec3(0) ),
239 | white : Materials.material( 'global', Vec3(.25), Vec3(1), Vec3(1), 2, Vec3(0) ),
240 | grey : Materials.material( 'global', Vec3(.25), Vec3(.33), Vec3(1), 2, Vec3(0) ),
241 |
242 | 'white glow' : Materials.material( 'phong', Vec3(.015), Vec3(1), Vec3(1), 16, Vec3(0,200,5) ),
243 | glue : Materials.material( 'phong', Vec3(.015), Vec3(1), Vec3(1), 16, Vec3(0,15,-.1) ),
244 | inverse : Materials.material( 'phong', 1, .5, 1, 16, Vec3(1,.5,-2) ),
245 | blackhole : Materials.material( 'phong', Vec3(0), Vec3(0), Vec3(0), 32 ),
246 | redp : Materials.material( 'phong', Vec3(1,0,0), Vec3(1,0,0), Vec3(1), 128, Vec3(0) ),
247 |
248 | normal : Materials.material( 'normal' ),
249 | noise : Materials.material( 'noise', Vec3( .15 ), Vec3(1,0,0), Vec3(1), 8, Vec3( 0, 1, .5 ))
250 | })
251 |
252 | return Materials
253 | }
254 |
255 | module.exports = __Materials
256 |
--------------------------------------------------------------------------------
/playground/main.css:
--------------------------------------------------------------------------------
1 | canvas {
2 | width:100%;
3 | height:100%;
4 | position:relative;
5 | }
6 |
7 | body {
8 | margin:0;
9 | background:black;
10 | font-family:monospace
11 | }
12 |
13 | .CodeMirror{
14 | background:transparent !important
15 | }
16 | .CodeMirror-highlight {
17 | background-color:rgba(89, 151, 198, .85) !important
18 | }
19 | .CodeMirror-selected {
20 | background-color:rgba(89, 151, 198, .85) !important
21 | }
22 |
23 | .Codemirror-activeline-background {
24 | background:rgba(255,255,255,.2)
25 | }
26 | .CodeMirror pre {
27 | background-color: rgba( 0,0,0,.75 ) !important;
28 | float:left !important;
29 | clear:left !important;
30 | font-family:Menlo, "Courier New", monospace !important;
31 | }
32 |
33 | .CodeMirror-linenumber {
34 | float:none !important;
35 | clear:none !important;
36 | }
37 |
38 | .CodeMirror-gutter {
39 | background:black
40 | }
41 |
42 | #menu {
43 | position: fixed;
44 | top: 1em;
45 | right: 1.5em;
46 | z-index: 999;
47 | color: white;
48 | display: inline-block;
49 | line-height: 2em;
50 | vertical-align: top;
51 | font-family: sans-serif;
52 | font-weight: bold;
53 | font-size: 12px;
54 | text-decoration: none;
55 | }
56 |
57 | #menu #help, #cameracontrols {
58 | color: white;
59 | display: inline-block;
60 | line-height: 2em;
61 | vertical-align: top;
62 | font-family: sans-serif;
63 | font-weight: bold;
64 | font-size: 12px;
65 | text-decoration: none;
66 | }
67 |
68 | #cameracontrols { user-select:none }
69 |
70 | #help { margin:0 1em }
71 |
72 | input {
73 | vertical-align:super;
74 | margin-right:1em
75 | }
76 |
77 | #menu #help:hover {
78 | text-decoration: underline;
79 | }
80 |
81 | #menu #source {
82 | width: 2em;
83 | height: 2em;
84 | display: inline-block;
85 | background-image: url(./github.png);
86 | background-size: cover;
87 | background-position: center;
88 | margin-top: -0.2em;
89 | }
90 |
91 | #menu #demo {
92 | vertical-align: top;
93 | margin: 0.4em;
94 | }
95 |
96 | .panel{
97 | position:absolute;
98 | bottom:0; left:0;
99 | width:100%;
100 | height:20em;
101 | background:gray;
102 | font-family:sans-serif;
103 | padding: 0 5px;
104 | box-sizing:border-box;
105 | font-size:.85em;
106 | overflow:scroll;
107 | }
108 | .remove-panel{
109 | position:absolute;
110 | right:0;
111 | color:darkred;
112 | }
113 |
114 | .panel ul {
115 | padding:0;
116 | margin:0
117 | }
118 |
119 | .panel ul li {
120 | margin-top:.5em
121 | }
122 |
123 | img {
124 | position:absolute;
125 | right: 0em;
126 | top:0;
127 | width:45px;
128 | height:38px;
129 | z-index:10
130 | }
131 |
132 | .CodeMirror {
133 | font-family: 'Menlo','Source Code Pro', monospace;
134 | font-size: .7em;
135 | background-color:#bbb;
136 | color:#000;
137 | font-weight:500;
138 | }
139 |
140 | .CodeMirror {
141 | font-family:Menlo, monospace;
142 | font-size:.8em;
143 | background:rgba(46,50,53,1);
144 | color:rgb( 153 ) !important;
145 | padding:1.5em;
146 | }
147 |
148 | span.cm-comment { color:rgb(121,121,121) !important }
149 |
150 | span.cm-property,
151 | span.cm-attribute,
152 | span.cm-variable,
153 | span.cm-variable-2,
154 | .cm-def,
155 | .cm-s-default { color: rgba(173,173,173,1) !important }
156 |
157 | span.cm-keyword,
158 | span.cm-number, span.cm-atom { color:rgba(89, 151, 198, 1) !important }
159 |
160 | span.cm-string, span.cm-string-2 {color: rgb(225, 225, 225) !important }
161 |
162 |
163 | .CodeMirror-cursor {
164 | background: rgba(255,255,255,.5);
165 | border: none !important;
166 | width: .5em !important;
167 | display: block;
168 | }
169 |
170 | .tp-rotv_t { background:black !important }
171 | /*.CodeMirror-selected {*/
172 | /* background:rgba(84,84,84,.5) !important;*/
173 | /*}*/
174 |
175 | /* toastr v2.1.3 */
176 |
177 | .toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}
178 |
--------------------------------------------------------------------------------
/playground/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | color: black;
8 | direction: ltr;
9 | }
10 |
11 | /* PADDING */
12 |
13 | .CodeMirror-lines {
14 | padding: 4px 0; /* Vertical padding around content */
15 | }
16 | .CodeMirror pre {
17 | padding: 0 4px; /* Horizontal padding of content */
18 | }
19 |
20 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
21 | background-color: white; /* The little square between H and V scrollbars */
22 | }
23 |
24 | /* GUTTER */
25 |
26 | .CodeMirror-gutters {
27 | border-right: 1px solid #ddd;
28 | background-color: #f7f7f7;
29 | white-space: nowrap;
30 | }
31 | .CodeMirror-linenumbers {}
32 | .CodeMirror-linenumber {
33 | padding: 0 3px 0 5px;
34 | min-width: 20px;
35 | text-align: right;
36 | color: #999;
37 | white-space: nowrap;
38 | }
39 |
40 | .CodeMirror-guttermarker { color: black; }
41 | .CodeMirror-guttermarker-subtle { color: #999; }
42 |
43 | /* CURSOR */
44 |
45 | .CodeMirror-cursor {
46 | border-left: 1px solid black;
47 | border-right: none;
48 | width: 0;
49 | }
50 | /* Shown when moving in bi-directional text */
51 | .CodeMirror div.CodeMirror-secondarycursor {
52 | border-left: 1px solid silver;
53 | }
54 | .cm-fat-cursor .CodeMirror-cursor {
55 | width: auto;
56 | border: 0 !important;
57 | background: #7e7;
58 | }
59 | .cm-fat-cursor div.CodeMirror-cursors {
60 | z-index: 1;
61 | }
62 | .cm-fat-cursor-mark {
63 | background-color: rgba(20, 255, 20, 0.5);
64 | -webkit-animation: blink 1.06s steps(1) infinite;
65 | -moz-animation: blink 1.06s steps(1) infinite;
66 | animation: blink 1.06s steps(1) infinite;
67 | }
68 | .cm-animate-fat-cursor {
69 | width: auto;
70 | border: 0;
71 | -webkit-animation: blink 1.06s steps(1) infinite;
72 | -moz-animation: blink 1.06s steps(1) infinite;
73 | animation: blink 1.06s steps(1) infinite;
74 | background-color: #7e7;
75 | }
76 | @-moz-keyframes blink {
77 | 0% {}
78 | 50% { background-color: transparent; }
79 | 100% {}
80 | }
81 | @-webkit-keyframes blink {
82 | 0% {}
83 | 50% { background-color: transparent; }
84 | 100% {}
85 | }
86 | @keyframes blink {
87 | 0% {}
88 | 50% { background-color: transparent; }
89 | 100% {}
90 | }
91 |
92 | /* Can style cursor different in overwrite (non-insert) mode */
93 | .CodeMirror-overwrite .CodeMirror-cursor {}
94 |
95 | .cm-tab { display: inline-block; text-decoration: inherit; }
96 |
97 | .CodeMirror-rulers {
98 | position: absolute;
99 | left: 0; right: 0; top: -50px; bottom: -20px;
100 | overflow: hidden;
101 | }
102 | .CodeMirror-ruler {
103 | border-left: 1px solid #ccc;
104 | top: 0; bottom: 0;
105 | position: absolute;
106 | }
107 |
108 | /* DEFAULT THEME */
109 |
110 | .cm-s-default .cm-header {color: blue;}
111 | .cm-s-default .cm-quote {color: #090;}
112 | .cm-negative {color: #d44;}
113 | .cm-positive {color: #292;}
114 | .cm-header, .cm-strong {font-weight: bold;}
115 | .cm-em {font-style: italic;}
116 | .cm-link {text-decoration: underline;}
117 | .cm-strikethrough {text-decoration: line-through;}
118 |
119 | .cm-s-default .cm-keyword {color: #708;}
120 | .cm-s-default .cm-atom {color: #219;}
121 | .cm-s-default .cm-number {color: #164;}
122 | .cm-s-default .cm-def {color: #00f;}
123 | .cm-s-default .cm-variable,
124 | .cm-s-default .cm-punctuation,
125 | .cm-s-default .cm-property,
126 | .cm-s-default .cm-operator {}
127 | .cm-s-default .cm-variable-2 {color: #05a;}
128 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
129 | .cm-s-default .cm-comment {color: #a50;}
130 | .cm-s-default .cm-string {color: #a11;}
131 | .cm-s-default .cm-string-2 {color: #f50;}
132 | .cm-s-default .cm-meta {color: #555;}
133 | .cm-s-default .cm-qualifier {color: #555;}
134 | .cm-s-default .cm-builtin {color: #30a;}
135 | .cm-s-default .cm-bracket {color: #997;}
136 | .cm-s-default .cm-tag {color: #170;}
137 | .cm-s-default .cm-attribute {color: #00c;}
138 | .cm-s-default .cm-hr {color: #999;}
139 | .cm-s-default .cm-link {color: #00c;}
140 |
141 | .cm-s-default .cm-error {color: #f00;}
142 | .cm-invalidchar {color: #f00;}
143 |
144 | .CodeMirror-composing { border-bottom: 2px solid; }
145 |
146 | /* Default styles for common addons */
147 |
148 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
149 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
150 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
151 | .CodeMirror-activeline-background {background: #e8f2ff;}
152 |
153 | /* STOP */
154 |
155 | /* The rest of this file contains styles related to the mechanics of
156 | the editor. You probably shouldn't touch them. */
157 |
158 | .CodeMirror {
159 | position: relative;
160 | overflow: hidden;
161 | background: white;
162 | }
163 |
164 | .CodeMirror-scroll {
165 | overflow: scroll !important; /* Things will break if this is overridden */
166 | /* 30px is the magic margin used to hide the element's real scrollbars */
167 | /* See overflow: hidden in .CodeMirror */
168 | margin-bottom: -30px; margin-right: -30px;
169 | padding-bottom: 30px;
170 | height: 100%;
171 | outline: none; /* Prevent dragging from highlighting the element */
172 | position: relative;
173 | }
174 | .CodeMirror-sizer {
175 | position: relative;
176 | border-right: 30px solid transparent;
177 | }
178 |
179 | /* The fake, visible scrollbars. Used to force redraw during scrolling
180 | before actual scrolling happens, thus preventing shaking and
181 | flickering artifacts. */
182 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
183 | position: absolute;
184 | z-index: 6;
185 | display: none;
186 | }
187 | .CodeMirror-vscrollbar {
188 | right: 0; top: 0;
189 | overflow-x: hidden;
190 | overflow-y: scroll;
191 | }
192 | .CodeMirror-hscrollbar {
193 | bottom: 0; left: 0;
194 | overflow-y: hidden;
195 | overflow-x: scroll;
196 | }
197 | .CodeMirror-scrollbar-filler {
198 | right: 0; bottom: 0;
199 | }
200 | .CodeMirror-gutter-filler {
201 | left: 0; bottom: 0;
202 | }
203 |
204 | .CodeMirror-gutters {
205 | position: absolute; left: 0; top: 0;
206 | min-height: 100%;
207 | z-index: 3;
208 | }
209 | .CodeMirror-gutter {
210 | white-space: normal;
211 | height: 100%;
212 | display: inline-block;
213 | vertical-align: top;
214 | margin-bottom: -30px;
215 | }
216 | .CodeMirror-gutter-wrapper {
217 | position: absolute;
218 | z-index: 4;
219 | background: none !important;
220 | border: none !important;
221 | }
222 | .CodeMirror-gutter-background {
223 | position: absolute;
224 | top: 0; bottom: 0;
225 | z-index: 4;
226 | }
227 | .CodeMirror-gutter-elt {
228 | position: absolute;
229 | cursor: default;
230 | z-index: 4;
231 | }
232 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
233 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
234 |
235 | .CodeMirror-lines {
236 | cursor: text;
237 | min-height: 1px; /* prevents collapsing before first draw */
238 | }
239 | .CodeMirror pre {
240 | /* Reset some styles that the rest of the page might have set */
241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
242 | border-width: 0;
243 | background: transparent;
244 | font-family: inherit;
245 | font-size: inherit;
246 | margin: 0;
247 | white-space: pre;
248 | word-wrap: normal;
249 | line-height: inherit;
250 | color: inherit;
251 | z-index: 2;
252 | position: relative;
253 | overflow: visible;
254 | -webkit-tap-highlight-color: transparent;
255 | -webkit-font-variant-ligatures: contextual;
256 | font-variant-ligatures: contextual;
257 | }
258 | .CodeMirror-wrap pre {
259 | word-wrap: break-word;
260 | white-space: pre-wrap;
261 | word-break: normal;
262 | }
263 |
264 | .CodeMirror-linebackground {
265 | position: absolute;
266 | left: 0; right: 0; top: 0; bottom: 0;
267 | z-index: 0;
268 | }
269 |
270 | .CodeMirror-linewidget {
271 | position: relative;
272 | z-index: 2;
273 | padding: 0.1px; /* Force widget margins to stay inside of the container */
274 | }
275 |
276 | .CodeMirror-widget {}
277 |
278 | .CodeMirror-rtl pre { direction: rtl; }
279 |
280 | .CodeMirror-code {
281 | outline: none;
282 | }
283 |
284 | /* Force content-box sizing for the elements where we expect it */
285 | .CodeMirror-scroll,
286 | .CodeMirror-sizer,
287 | .CodeMirror-gutter,
288 | .CodeMirror-gutters,
289 | .CodeMirror-linenumber {
290 | -moz-box-sizing: content-box;
291 | box-sizing: content-box;
292 | }
293 |
294 | .CodeMirror-measure {
295 | position: absolute;
296 | width: 100%;
297 | height: 0;
298 | overflow: hidden;
299 | visibility: hidden;
300 | }
301 |
302 | .CodeMirror-cursor {
303 | position: absolute;
304 | pointer-events: none;
305 | }
306 | .CodeMirror-measure pre { position: static; }
307 |
308 | div.CodeMirror-cursors {
309 | visibility: hidden;
310 | position: relative;
311 | z-index: 3;
312 | }
313 | div.CodeMirror-dragcursors {
314 | visibility: visible;
315 | }
316 |
317 | .CodeMirror-focused div.CodeMirror-cursors {
318 | visibility: visible;
319 | }
320 |
321 | .CodeMirror-selected { background: #d9d9d9; }
322 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
323 | .CodeMirror-crosshair { cursor: crosshair; }
324 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
325 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
326 |
327 | .cm-searching {
328 | background-color: #ffa;
329 | background-color: rgba(255, 255, 0, .4);
330 | }
331 |
332 | /* Used to force a border model for a node */
333 | .cm-force-border { padding-right: .1px; }
334 |
335 | @media print {
336 | /* Hide the cursor when printing */
337 | .CodeMirror div.CodeMirror-cursors {
338 | visibility: hidden;
339 | }
340 | }
341 |
342 | /* See issue #2901 */
343 | .cm-tab-wrap-hack:after { content: ''; }
344 |
345 | /* Help users use markselection to safely style text background */
346 | span.CodeMirror-selectedtext { background: none; }
347 |
--------------------------------------------------------------------------------
/js/distanceDeformations.js:
--------------------------------------------------------------------------------
1 | const SceneNode = require( './sceneNode.js' )
2 | const { param_wrap, MaterialID } = require( './utils.js' )
3 | const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' )
4 | const Transform = require( './transform.js' )
5 |
6 | const ops = {
7 | // this needs to create an opOut, not return a vec2
8 | Displace( __name ) {
9 | let name = __name === undefined ? 'p' : __name
10 | const sdf = this.sdf.emit( name );
11 |
12 | const sdfStr = `float d1${this.id} = ${sdf.out}.x;\n`
13 |
14 | let displaceString = `float d2${this.id} = sin( ${this.amount.emit()}.x * ${name}.x ) * `
15 | displaceString += `sin( ${this.amount.emit()}.y * ${name}.y ) * `
16 | displaceString += `sin( ${this.amount.emit()}.z * ${name}.z );\n`
17 | displaceString += `${sdf.out}.x = (d1${this.id} + d2${this.id}*${this.size.emit()})*.5;\n`
18 |
19 | const output = {
20 | out: `${sdf.out}`,
21 | preface: sdf.preface + sdfStr + displaceString
22 | }
23 |
24 | return output
25 | },
26 | Halve( __name ) {
27 | let name = __name === undefined ? 'p' : __name
28 |
29 | // XXX why?
30 | this.transform.invert()
31 |
32 | const bumpString = ` vec4 transformBump${this.id} = ${name} * ${this.transform.emit()};\n`
33 | const pointString = ` vec4 transformBump2${this.id} = (transformBump${this.id} * ${this.sdf.transform.emit()});\n`
34 | const sdf = this.sdf.emit( `transformBump2${this.id}` );
35 |
36 | let displaceString = `${sdf.out}.x = opHalve( ${sdf.out}.x, transformBump2${this.id}, int(${this.amount.emit()}) );`
37 |
38 | const output = {
39 | out: `${sdf.out}`,
40 | preface: bumpString + pointString + sdf.preface + displaceString
41 | }
42 |
43 | return output
44 | },
45 | Bend( __name ) {
46 | let name = __name === undefined ? 'p' : __name
47 | const sdf = this.sdf.emit( 'q'+this.id );
48 |
49 | let preface=` float c${this.id} = cos( ${this.amount.emit()}.x * ${name}.x );
50 | float s${this.id} = sin( ${this.amount.emit()}.x * ${name}.x );
51 | mat2 m${this.id} = mat2( c${this.id},-s${this.id},s${this.id},c${this.id} );
52 | vec4 q${this.id} = vec4( m${this.id} * ${name}.xy, ${name}.z, 1. );\n`
53 |
54 | if( typeof sdf.preface === 'string' ) {
55 | preface += sdf.preface
56 | }
57 |
58 | return { preface, out:sdf.out }
59 | },
60 |
61 | __Twist( __name ) {
62 | let name = __name === undefined ? 'p' : __name
63 |
64 | const sdf = this.sdf.emit( 'q'+this.id );
65 |
66 | let preface=` float c${this.id} = cos( ${this.amount.emit()}.x * ${name}.y );
67 | float s${this.id} = sin( ${this.amount.emit()}.x * ${name}.y );
68 | mat2 m${this.id} = mat2( c${this.id},-s${this.id},s${this.id},c${this.id} );
69 | vec4 q${this.id} = vec4( m${this.id} * ${name}.xz, ${name}.y, 1. );\n`
70 |
71 | if( typeof sdf.preface === 'string' ) {
72 | preface += sdf.preface
73 | }
74 |
75 | return { preface, out:sdf.out }
76 | },
77 |
78 | Twist( __name ) {
79 | let name = __name === undefined ? 'p' : __name
80 | this.transform.invert()
81 |
82 | const bumpString = ` vec4 twist${this.id} = ${name} * ${this.transform.emit()};\n`
83 | const bumpString2 = ` vec4 twist2${this.id} = (twist${this.id} * ${this.sdf.transform.emit()});\n`
84 | //const sdf = this.sdf.emit( 'q'+this.id );
85 |
86 | //let preface=` float c${this.id} = cos( ${this.amount.emit()}.x * ${name}.y );
87 | // float s${this.id} = sin( ${this.amount.emit()}.x * ${name}.y );
88 | // mat2 m${this.id} = mat2( c${this.id},-s${this.id},s${this.id},c${this.id} );
89 | // vec4 q${this.id} = vec4( m${this.id} * ${name}.xz, ${name}.y, 1. );\n`
90 |
91 | name = `twist2${this.id}`
92 | let preface=` float c${this.id} = cos( ${this.amount.emit()}.x * ${name}.y );
93 | float s${this.id} = sin( ${this.amount.emit()}.x * ${name}.y );
94 | mat2 m${this.id} = mat2( c${this.id},-s${this.id},s${this.id},c${this.id} );
95 | vec4 q${this.id} = vec4( m${this.id} * ${name}.xz, ${name}.y, 1. );\n`
96 |
97 | const sdf = this.sdf.emit( `q${this.id}` );
98 | if( typeof sdf.preface === 'string' ) {
99 | preface += sdf.preface
100 | }
101 |
102 | return { preface:bumpString + bumpString2 + preface, out:sdf.out }
103 | },
104 |
105 | __Bump( __name ) {
106 | let name = __name === undefined ? 'p' : __name
107 |
108 | const bumpString = ` vec4 transformBump${this.id} = ${name} * ${this.transform.emit()};\n`
109 | const tex = this.amount.emit( name )
110 |
111 | const pointString = `(transformBump${this.id} * ${this.sdf.transform.emit()})`
112 |
113 | const sdf = this.sdf.emit( pointString, this.transform, `tex${this.id}` )
114 |
115 | Marching.textures.addTexture( this.amount.value )
116 |
117 | let preface=` vec3 tex${this.id} = getTexture( ${this.amount.value.id}, ${pointString}.xyz ) * ${this.size.emit()};\n
118 | //vec4 displaceBump${this.id} = vec4((${pointString} - tex${this.id}), 1.);
119 | `
120 | //${sdf.out}.x = (tex${this.id}.x + tex${this.id}.y + tex${this.id}.z ) / 3. * .5 + ${sdf.out}.x;\n`
121 | //vec4 ${'p'+this.id} = vec4(${pointString} + tex${this.id}, 1.);\n`
122 |
123 | //sdf.preface += `\n
124 | // ${sdf.out}.x -= min(tex${this.id}.x, min(tex${this.id}.y, tex${this.id}.z));\n`
125 |
126 | if( typeof sdf.preface === 'string' ) {
127 | preface = preface + sdf.preface
128 | }
129 |
130 | preface = bumpString + preface
131 |
132 | return { preface, out:sdf.out }
133 | },
134 | // XXX todo: something like https://www.shadertoy.com/view/ldSGzR
135 | // https://www.dropbox.com/s/l1yl164jb3rhomq/mm_sfgrad_bump.pdf?dl=0
136 | Bump( __name ) {
137 | let name = __name === undefined ? 'p' : __name
138 |
139 | const bumpString = ` vec4 transformBump${this.id} = ${name} * ${this.transform.emit()};\n`
140 | const tex = this.amount.emit( name )
141 |
142 | const pointString = `(transformBump${this.id} * ${this.sdf.transform.emit()}).xyz`
143 |
144 | const sdf = this.sdf.emit( `transformBump${this.id}`, this.transform )
145 |
146 | Marching.textures.addTexture( this.amount.value )
147 |
148 | let preface=` vec3 tex${this.id} = getTexture( ${this.amount.value.id}, ${pointString}) * ${this.size.emit()};
149 | ${sdf.out}.x += (tex${this.id}.x + tex${this.id}.y + tex${this.id}.z)/3.;\n`
150 |
151 | if( typeof sdf.preface === 'string' ) {
152 | preface = sdf.preface + preface
153 | }
154 |
155 | preface = bumpString + preface
156 |
157 | return { preface, out:sdf.out }
158 | },
159 | }
160 |
161 | const DistanceOps = {}
162 |
163 | for( let name in ops ) {
164 |
165 | // get codegen function
166 | let __op = ops[ name ]
167 |
168 | // create constructor
169 | DistanceOps[ name ] = function( a,b,c ) {
170 | const op = Object.create( DistanceOps[ name ].prototype )
171 | op.sdf = a
172 | op.amount = b
173 | op.emit = __op
174 | op.name = name
175 | op.transform = Transform()
176 |
177 | const defaultValues = [.5,.5,.5]
178 |
179 | op.id = VarAlloc.alloc()
180 | const isArray = true
181 |
182 | if( name === 'Halve' ) {
183 | op.amount = param_wrap( b, float_var_gen( b ) )
184 | }else{
185 | if( typeof b === 'number' ) {
186 | b = [b,b,b]
187 | b.type = 'vec3'
188 | }
189 | }
190 |
191 | if( name !== 'Bumpz' ) {
192 | let __var = param_wrap(
193 | b,
194 | vec3_var_gen( ...defaultValues )
195 | )
196 |
197 | // for assigning entire new vectors to property
198 | Object.defineProperty( op, 'amount', {
199 | get() { return __var },
200 | set(v) {
201 | if( typeof v === 'object' ) {
202 | __var.set( v )
203 | }else{
204 | __var.value.x = v
205 | __var.value.y = v
206 | __var.value.z = v
207 | __var.value.w = v
208 | __var.dirty = true
209 | }
210 | }
211 | })
212 |
213 | op.params = [{ name:'amount' }]
214 | }else{
215 | op.params = []
216 | op.emit_decl = function() {}
217 | op.emit = function() {}
218 | op.update_data= function() {}
219 | op.upload_location = function() {}
220 | }
221 | op.__setMaterial = function(mat) {
222 | if( typeof mat === 'string' ) mat = Marching.Material[ mat ]
223 | this.__material = this.mat = Marching.materials.addMaterial( mat )
224 | op.sdf.material( this.__material )
225 | }
226 | if( name === 'Displace' || name === 'Bump' ) {
227 | let __var2 = param_wrap(
228 | c,
229 | float_var_gen( .03 )
230 | )
231 | Object.defineProperty( op, 'size', {
232 | get() { return __var2 },
233 | set(v) {
234 | __var2.set( v )
235 | __var2.dirty = true
236 | }
237 | })
238 |
239 | op.params.push({ name:'size' })
240 | }
241 | op.__desc = { parameters:op.params }
242 | return op
243 | }
244 |
245 | DistanceOps[ name ].prototype = SceneNode()
246 |
247 | DistanceOps[name].prototype.emit_decl = function () {
248 | let str = this.sdf.emit_decl() + (this.name !== 'Bump' ? this.amount.emit_decl() : '')
249 | str += this.transform.emit_decl()
250 | if( this.name === 'Displace' || this.name === 'Bump' ) str += this.size.emit_decl()
251 |
252 | return str
253 | };
254 |
255 | DistanceOps[name].prototype.update_location = function(gl, program) {
256 | this.sdf.update_location( gl, program )
257 | if( this.name !== 'Bump' ) this.amount.update_location( gl, program )
258 | if( this.name === 'Displace' || this.name === 'Bump') this.size.update_location( gl, program )
259 | this.transform.update_location( gl, program )
260 | }
261 |
262 | DistanceOps[name].prototype.upload_data = function(gl) {
263 | this.sdf.upload_data( gl )
264 | if( this.name !== 'Bump' ) this.amount.upload_data( gl )
265 | if( this.name === 'Displace' || this.name === 'Bump' ) this.size.upload_data( gl )
266 | this.transform.upload_data( gl )
267 | }
268 | }
269 | DistanceOps.Halve.UP = 0
270 | DistanceOps.Halve.DOWN = 1
271 | DistanceOps.Halve.LEFT = 3
272 | DistanceOps.Halve.RIGHT = 2
273 |
274 | module.exports = DistanceOps
275 |
276 |
--------------------------------------------------------------------------------
/js/distanceOperations.js:
--------------------------------------------------------------------------------
1 | const SceneNode = require( './sceneNode.js' )
2 | const { param_wrap, MaterialID } = require( './utils.js' )
3 | const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' )
4 | const Transform = require( './transform.js' )
5 | const glslops = require( './distanceOperationsGLSL.js' )
6 |
7 | const opslen = {
8 | Union:2,
9 | Intersection:2,
10 | Difference:2,
11 | ColumnsUnion:4,
12 | ColumnsIntersection:4,
13 | ColumnsDifference:4,
14 | StairsUnion:4,
15 | StairsIntersection:4,
16 | StairsDifference:4,
17 | RoundUnion:3,
18 | RoundDifference:3,
19 | RoundIntersection:3,
20 | ChamferUnion:3,
21 | ChamferDifference:3,
22 | ChamferIntersection:3,
23 | Pipe:3,
24 | Engrave:3,
25 | Groove:4,
26 | Tongue:4,
27 |
28 | // these two do not currently have support for transforms or repeats...
29 | Onion:2,
30 | Switch:2
31 | }
32 |
33 | const ops = {
34 | Union( ...args ) { return `opU( ${args.join(',')} )` },
35 | SmoothUnion( ...args ) { return `opSmoothUnion( ${args.join(',')} )` },
36 | Intersection( ...args ) { return `opI( ${args.join(',')} )` },
37 | SmoothIntersection( ...args ) { return `opSmoothIntersection( ${args.join(',')} )` },
38 | Difference( ...args ) { return `opS( ${args.join(',')} )` },
39 | SmoothDifference( ...args ) { return `opSmoothSubtraction( ${args.join(',')} )` },
40 | StairsUnion( ...args ) { return `fOpUnionStairs( ${args.join(',')} )` },
41 | StairsIntersection( ...args ) { return `fOpIntersectionStairs( ${args.join(',')} )` },
42 | StairsDifference( ...args ) { return `fOpSubstractionStairs( ${args.join(',')} )` },
43 | ColumnsUnion( ...args ) { return `fOpUnionColumns( ${args.join(',')} )` },
44 | ColumnsIntersection( ...args ) { return `fOpIntersectionColumns( ${args.join(',')} )` },
45 | ColumnsDifference( ...args ) { return `fOpDifferenceColumns( ${args.join(',')} )` },
46 | RoundUnion( ...args ) { return `fOpUnionRound( ${args.join(',')} )` },
47 | RoundDifference( ...args ) { return `fOpDifferenceRound( ${args.join(',')} )` },
48 | RoundIntersection( ...args ) { return `fOpIntersectionRound( ${args.join(',')} )` },
49 | ChamferUnion( ...args ) { return `fOpUnionChamfer( ${args.join(',')} )` },
50 | ChamferDifference( ...args ) { return `fOpDifferenceChamfer( ${args.join(',')} )` },
51 | ChamferIntersection( ...args ) { return `fOpIntersectionChamfer( ${args.join(',')} )` },
52 | Pipe( ...args ) { return `fOpPipe( ${args.join(',')} )` },
53 | Engrave( ...args ) { return `fOpEngrave( ${args.join(',')} )` },
54 | Groove( ...args ) { return `fOpGroove( ${args.join(',')} )` },
55 | Tongue( ...args ) { return `fOpTongue( ${args.join(',')} )` },
56 |
57 | // these two do not currently have support for transforms or repeats...
58 | Onion( ...args ) { return `opOnion( ${args.join(',')} )` },
59 | Switch( a,b,c,d,e,f ) { return `opSwitch( ${a}, ${b}, ${c} )` }
60 | }
61 |
62 | const emit_float = function( a ) {
63 | if (a % 1 === 0)
64 | return a.toFixed( 1 )
65 | else
66 | return a
67 | }
68 |
69 | const DistanceOps = {
70 | __glsl:[],
71 | __getGLSL() {
72 | return this.__glsl.join('\n')
73 | },
74 | __clear() { this.__glsl.length = 0 }
75 | }
76 |
77 | const oneops = ['Onion','Halve']
78 |
79 | for( let name in ops ) {
80 |
81 | // get codegen function
82 | let op = ops[ name ]
83 | const name2 = name + '2'
84 |
85 | // create constructor
86 | DistanceOps[ name ] = function( a,b,c,d ) {
87 | const op = Object.create( DistanceOps[ name ].prototype )
88 | op.a = a
89 | op.b = oneops.indexOf( name ) === -1 ? b : param_wrap( b, float_var_gen(.2 ) )
90 | op.transform = Transform( false )
91 | op.id = VarAlloc.alloc()
92 | op.type = 'distance_op'
93 | op.name = name
94 |
95 | let __c = param_wrap( c, float_var_gen(.3) )
96 |
97 | op.__len = opslen[ name ]
98 | if( op.__len > 2 ) {
99 | Object.defineProperty( op, 'c', {
100 | get() { return __c },
101 | set(v) {
102 | __c.set( v )
103 | }
104 | })
105 |
106 | if( op.__len > 3 ) {
107 | let __d = param_wrap( d, float_var_gen(4) )
108 |
109 | Object.defineProperty( op, 'd', {
110 | get() { return __d },
111 | set(v) {
112 | __d.set( v )
113 | }
114 | })
115 | }
116 | }
117 |
118 | op.__setTexture = function(tex,props) {
119 | if( typeof tex === 'string' ) {
120 | this.texture = op.texture.bind( this )
121 | this.__textureObj = this.tex = Marching.Texture( tex,props,this.texture )
122 | this.__textureID = this.__textureObj.id
123 | }else{
124 | this.__textureObj = this.tex = Object.assign( tex, props )
125 | this.__textureID = this.__textureObj.id
126 | }
127 | }
128 | op.__setMaterial = function(mat) {
129 | if( typeof mat === 'string' ) mat = Marching.Material[ mat ]
130 | this.__material = this.mat = Marching.materials.addMaterial( mat )
131 | }
132 | op.__setBump = function(tex,props) {
133 | //this.bump = p.bump.bind( this )
134 | const b = this.bump = this.__bumpObj = Marching.Bump( this, tex, props )
135 | this.bump.texture = this.bump.amount.value
136 | this.__bumpID = this.__bumpObj.id
137 | this.rotate = this.bump.rotate
138 | this.translate = this.bump.translate
139 | this.scale = this.bump.scale
140 | Object.defineProperty( this.bump, 'strength', {
141 | get() { return b.size },
142 | set(v){ b.size = v }
143 | })
144 | }
145 | Object.assign( op, {
146 | renderingBump : false,
147 | emittingDecl : false,
148 | uploading : false,
149 | updating : false
150 | })
151 |
152 | let repeat = null
153 | Object.defineProperty( op, 'repeat', {
154 | get() { return repeat },
155 | set(v){
156 | repeat = v
157 | this.a.repeat = v
158 | this.b.repeat = v
159 | }
160 | })
161 |
162 | op.matId = MaterialID.alloc()
163 |
164 | op.params = [{name:'c'},{ name:'d'}]
165 | op.__desc = { parameters: op.params }
166 |
167 | return op
168 | }
169 |
170 | DistanceOps[ name2 ] = function( ...args ) {
171 | // accepts unlimited arguments, but the last one could be a blending coefficient
172 | let blend = .25, coeff=4, u
173 |
174 | if( typeof args[ args.length - 1 ] === 'number' ) {
175 | blend = args.pop()
176 |
177 | // if there are two non-sdf arguments to the function...
178 | if( typeof args[ args.length - 1 ] === 'number' ) {
179 | coeff = blend
180 | blend = args.pop()
181 | }
182 |
183 | u = args.reduce( (state,next) => DistanceOps[ name ]( state, next, blend, coeff ) )
184 | }else{
185 | u = args.reduce( (state,next) => DistanceOps[ name ]( state, next ) )
186 | }
187 |
188 | return u
189 | }
190 |
191 | DistanceOps[ name ].prototype = SceneNode()
192 |
193 | DistanceOps[ name ].prototype.texture = function( ...args ) {
194 | this.__setTexture( ...args )
195 | this.a.texture( this.__textureObj )
196 | if( oneops.indexOf( name ) === -1 )
197 | this.b.texture( this.__textureObj )
198 |
199 | return this
200 | }
201 | DistanceOps[ name ].prototype.material = function( ...args ) {
202 | this.__setMaterial( ...args )
203 | this.a.material( this.__material )
204 | if( oneops.indexOf( name ) === -1 )
205 | this.b.material( this.__material )
206 |
207 | return this
208 | }
209 |
210 | const pushString = function( name ) {
211 | const glslobj = glslops[ name ]
212 |
213 | // some definitions are a single string, and not split into
214 | // separate float and opOut functions
215 | if( typeof glslobj === 'string' ) {
216 | if( DistanceOps.__glsl.indexOf( glslobj ) === -1 ) {
217 | DistanceOps.__glsl.push( glslobj )
218 | }
219 | }else{
220 | // some distance operations are dependent on other ones...
221 | // if this one has dependencies add them.
222 | // dependencies must be added before adding other functions
223 | // so that they're above them in the final GLSL code.
224 | if( glslobj.dependencies !== undefined ) {
225 | for( let dname of glslobj.dependencies ) {
226 | const d = glslops[ dname ]
227 | if( DistanceOps.__glsl.indexOf( d.float ) === -1 ) {
228 | DistanceOps.__glsl.push( d.float )
229 | }
230 | }
231 | }
232 | if( DistanceOps.__glsl.indexOf( glslobj.float ) === -1 ) {
233 | DistanceOps.__glsl.push( glslobj.float )
234 | }
235 | if( DistanceOps.__glsl.indexOf( glslobj.vec2) === -1 ) {
236 | DistanceOps.__glsl.push( glslobj.vec2 )
237 | }
238 | }
239 | }
240 |
241 | DistanceOps[ name ].prototype.emit = function ( pname='p', transform = null ){
242 | this.__dirty()
243 | const isNotOneop = oneops.indexOf( name ) === -1
244 | if( this.__bumpObj !== undefined && this.renderingBump === false) {
245 | this.renderingBump = true
246 | return this.__bumpObj.emit( pname, transform )
247 | }
248 | pushString( name )
249 |
250 | if( transform !== null ) this.transform.apply( transform, false )
251 | //this.transform.internal()
252 |
253 | // first two args are fixed, rest are variable
254 | let emitters = []
255 | const a = this.a.emit( pname, this.transform )
256 | const b = isNotOneop ? this.b.emit( pname, this.transform ) : this.b.emit()
257 |
258 | emitters[0] = a.out
259 | emitters[1] = isNotOneop ? b.out : b
260 | if( this.__len > 2 ) emitters.push( this.c.emit() )
261 | if( this.__len > 3 ) emitters.push( this.d.emit() )
262 |
263 | const body = `
264 | vec2 do${this.id} = ${op( ...emitters )};
265 | do${this.id}.x *= ${this.transform.emit()}_scale;
266 | `
267 |
268 | const output = {
269 | out: 'do'+this.id,
270 | preface: (a.preface || '') + ( isNotOneop ? b.preface || '' : '') + body
271 | }
272 |
273 | this.renderingBump = false
274 | return output
275 | }
276 |
277 | DistanceOps[name].prototype.emit_decl = function () {
278 | if( this.__bumpObj !== undefined && this.emittingDecl === false) {
279 | this.emittingDecl = true
280 | return this.__bumpObj.emit_decl()
281 | }
282 | let str = this.transform.emit_decl() + this.a.emit_decl() + this.b.emit_decl()
283 | if( this.c !== undefined ) str += this.c.emit_decl()
284 | if( this.d !== undefined ) str += this.d.emit_decl()
285 |
286 | if( ops[ name ].code !== undefined ) {
287 | //str += ops[ name ].code
288 | if( Marching.requiredOps.indexOf( ops[ name ].code ) === - 1 ) {
289 | Marching.requiredOps.push( ops[ name ].code )
290 | }
291 | }
292 |
293 | this.emittingDecl = false
294 | return str
295 | };
296 |
297 | DistanceOps[name].prototype.update_location = function(gl, program) {
298 | if( this.__bumpObj !== undefined && this.updating === false) {
299 | this.updating = true
300 | return this.__bumpObj.update_location( gl, program )
301 | }
302 | this.a.update_location( gl, program )
303 | this.transform.update_location( gl, program )
304 | this.b.update_location( gl, program )
305 | if( this.c !== undefined ) this.c.update_location( gl, program )
306 | if( this.d !== undefined ) this.d.update_location( gl, program )
307 |
308 | this.updating = false
309 | }
310 |
311 | DistanceOps[name].prototype.upload_data = function(gl) {
312 | if( this.__bumpObj !== undefined && this.uploading === false ) {
313 | this.uploading = true
314 | return this.__bumpObj.upload_data( gl )
315 | }
316 | this.transform.internal()
317 | this.transform.upload_data( gl )
318 | this.a.transform.apply( this.transform )
319 | if( oneops.indexOf( name ) === -1 )
320 | this.b.transform.apply( this.transform )
321 |
322 | this.a.upload_data( gl )
323 | this.b.upload_data( gl )
324 | if( this.c !== undefined ) this.c.upload_data( gl )
325 | if( this.d !== undefined ) this.d.upload_data( gl )
326 | this.uploading = false
327 |
328 | }
329 | }
330 |
331 | module.exports = DistanceOps
332 |
--------------------------------------------------------------------------------