UNPKG

marching

Version:

Marching.js is a JavaScript library that compiles GLSL ray marchers.

1,712 lines (1,417 loc) 1.87 MB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ const SceneNode = require( './sceneNode.js' ) const { param_wrap, MaterialID } = require( './utils.js' ) const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen } = require( './var.js' ) const ops = { Onion: { func( sdf,thickness ) { return `vec2( opOnion( ${sdf}.x, ${thickness} ), ${sdf}.y )` }, variables:[['thickness', 'float', .03]], def:` float opOnion( in float sdf, in float thickness ){ return abs(sdf)-thickness; } vec2 opOnion( vec2 sdf, float thickness ) { float x = 0.; sdf.x = opOnion( sdf.x, thickness ); return sdf; } ` }, Halve: { func( sdf, direction ) { return `vec2( opHalve( ${sdf}.x, p, ${direction} ), ${sdf}.y )` }, variables:[['direction','int',0]], def:` float opHalve( in float sdf, vec4 p, in int dir ){ float _out = 0.; switch( dir ) { case 0: _out = max( sdf, p.y ); break; case 1: _out = max( sdf, -p.y ); break; case 2: _out = max( sdf, p.x ); break; case 3: _out = max( sdf, -p.x ); break; } return _out; } vec2 opHalve( vec2 sdf, vec4 p, int dir ) { float x = 0.; x = opHalve( sdf.x, p, dir ); sdf.x = x; return sdf; } ` }, Round: { func( sdf, amount ) { return `vec2( ${sdf}.x - ${amount}, ${sdf}.y )` }, variables:[['amount','float',.1]], def:` ` } } //vec4 opElongate( in vec3 p, in vec3 h ) { // //return vec4( p-clamp(p,-h,h), 0.0 ); // faster, but produces zero in the interior elongated box // vec3 q = abs(p)-h; // return vec4( max(q,0.0), min(max(q.x,max(q.y,q.z)),0.0) ); //} const pushString = function( name ) { const glslobj = ops[ name ].def // some definitions are a single string, and not split into // separate float and opOut functions if( typeof glslobj === 'string' ) { if( Alterations.__glsl.indexOf( glslobj ) === -1 ) { Alterations.__glsl.push( glslobj ) } } } const Alterations = { __glsl:[], __getGLSL() { return this.__glsl.join('\n') }, __clear() { this.__glsl.length = 0 } } for( let name in ops ) { // get codegen function let op = ops[ name ] // create constructor Alterations[ name ] = function( sdf, ...args ) { const __op = Object.create( Alterations[ name ].prototype ) __op.sdf = sdf __op.variables = [] __op.__desc = { parameters:[] } for( let i = 0; i < op.variables.length; i++ ) { const propArray = op.variables[ i ] const propName = propArray[ 0 ] const propType = propArray[ 1 ] const propValue = args[ i ] === undefined ? propArray[ 2 ] : args[ i ] __op.__desc.parameters.push({ name:propName, value:propValue }) let param switch( propType ) { case 'int': param = int_var_gen( propValue )() break; default: param = float_var_gen( propValue )() break; } Object.defineProperty( __op, propName, { get() { return param }, set(v) { param.set( v ) } }) __op.variables.push( param ) } __op.matId = MaterialID.alloc() return __op } Alterations[ name ].prototype = SceneNode() Alterations[ name ].prototype.emit = function ( __name ) { const emitterA = this.sdf.emit( __name ) pushString( name ) //const emitterB = this.b.emit() const output = { out: op.func( emitterA.out, ...this.variables.map( v => v.emit() ) ), preface: (emitterA.preface || '') } return output } Alterations[name].prototype.emit_decl = function () { let str = this.sdf.emit_decl() for( let v of this.variables ) { str += v.emit_decl() } return str }; Alterations[name].prototype.update_location = function(gl, program) { this.sdf.update_location( gl, program ) for( let v of this.variables ) v.update_location( gl, program ) } Alterations[name].prototype.upload_data = function(gl) { this.sdf.upload_data( gl ) for( let v of this.variables ) v.upload_data( gl ) } } Alterations.Halve.UP = 0 Alterations.Halve.DOWN = 1 Alterations.Halve.LEFT = 3 Alterations.Halve.RIGHT = 2 module.exports = Alterations },{"./sceneNode.js":23,"./utils.js":28,"./var.js":29}],2:[function(require,module,exports){ const Audio = { __hasInput: false, ctx: null, bins:null, start( bins=null ) { if( Audio.__hasInput === false ) { Audio.ctx = new AudioContext() Audio.createInput().then( input => { if( bins !== null ) Audio.bins = bins Audio.createFFT() input.connect( Audio.FFT ) Audio.interval = setInterval( Audio.fftCallback, 1000/60 ) //window.FFT = Audio.FFT }) Audio.__hasInput = true }else{ if( bins !== null ) Audio.bins = bins } }, createInput() { console.log( 'connecting audio input...' ) const p = new Promise( resolve => { console.log( 'start?' ) navigator.mediaDevices.getUserMedia({ audio:true, video:false }) .then( stream => { console.log( 'audio input connected' ) Audio.input = Audio.ctx.createMediaStreamSource( stream ) //Audio.mediaStreamSource.connect( Gibberish.node ) Audio.__hasInput = true resolve( Audio.input ) }) .catch( err => { console.log( 'error opening audio input:', err ) }) }) return p }, createFFT() { Audio.FFT = Audio.ctx.createAnalyser() let __windowSize = 512 Object.defineProperty( Audio, 'windowSize', { configurable:true, get() { return __windowSize }, set(v){ __windowSize = v Audio.FFT.fftSize = v Audio.FFT.values = new Uint8Array( Audio.FFT.frequencyBinCount ) } }) Audio.windowSize = 512 }, fftCallback() { Audio.FFT.getByteFrequencyData( Audio.FFT.values ) let lowSum, midSum, highSum, lowCount, midCount, highCount lowSum = midSum = highSum = lowCount = midCount = highCount = 0 let frequencyCounter = 0 // does this start at 0Hz? ack... can't remember... does it include DC offset? const hzPerBin = (Audio.ctx.sampleRate / 2) / Audio.FFT.frequencyBinCount const lowRange = 150, midRange = 1400, highRange = Audio.ctx.sampleRate / 2 if( Audio.bins === null ) { for( let i = 1; i < Audio.FFT.frequencyBinCount; i++ ) { if( frequencyCounter < lowRange ) { lowSum += Audio.FFT.values[ i ] lowCount++ }else if( frequencyCounter < midRange ) { midSum += Audio.FFT.values[ i ] midCount++ }else{ highSum += Audio.FFT.values[ i ] highCount++ } frequencyCounter += hzPerBin } Audio.low = (lowSum / lowCount) / 255 Audio.mid = (midSum / midCount) / 255 || 0 Audio.high = (highSum / highCount) / 255 }else{ const sums = {} for( let bin = 0; bin < Audio.bins.length; bin++ ) { const frequency = Audio.bins[ bin ] sums[ bin ] = { count: 0, value: 0 } for( let i = 1; i < Audio.FFT.frequencyBinCount; i++ ) { if( frequencyCounter < frequency ) { sums[ bin ].value += Audio.FFT.values[i] sums[ bin ].count++ frequencyCounter += hzPerBin }else{ break } } Audio[ bin ] = (sums[ bin ].value / sums[ bin ].count) / 255 } } } } module.exports = Audio },{}],3:[function(require,module,exports){ const SceneNode = require( './sceneNode.js' ), { param_wrap, MaterialID } = require( './utils.js' ), { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen } = require( './var.js' ) const { Vec2, Vec3, Vec4 } = require( './vec.js' ) const BG = function( Scene, SDF ) { const Background = function( color ) { if( SDF.memo.background === undefined ) { const bg = Object.create( Background.prototype ) if( color !== undefined && color.type === 'vec3' ) color = Vec4( color.x, color.y, color.z, 1 ) const __color = param_wrap( Vec4( color ), vec4_var_gen( 0,0,0,1, 'bg' ), 'bg' ) Object.defineProperty( bg, 'color', { get() { return __color }, set( v ) { __color.var.set( v ) } }) // this refers to the current scene via implicit binding in scene.js //this.postprocessing.push( bg ) bg.__backgroundColor = color this.__background = bg SDF.memo.background = true } return this } Background.prototype = SceneNode() Object.assign( Background.prototype, { emit() { return ''// this.color.emit() }, emit_decl() { //let str = this.color.emit_decl() //SDF.memo.background = true const out = this.__backgroundColor === undefined ? 'vec4 bg = vec4(0.,0.,0.,1.);' : `vec4 bg = vec4(${ this.__backgroundColor.x }, ${this.__backgroundColor.y}, ${this.__backgroundColor.z}, 1.);` return out }, update_location( gl, program ) { this.color.update_location( gl, program ) }, upload_data( gl ) { this.color.upload_data( gl ) } }) return Background } module.exports = BG },{"./sceneNode.js":23,"./utils.js":28,"./var.js":29,"./vec.js":30}],4:[function(require,module,exports){ const vec3 = require('gl-vec3') const mat4 = require('gl-mat4') // camera adapted from https://github.com/shama/first-person-camera function FirstPersonCamera(opts) { if (!(this instanceof FirstPersonCamera)) return new FirstPersonCamera(opts) opts = opts || {} this.position = opts.position || vec3.create() this.rotation = opts.rotation || vec3.create() this.positionSpeed = opts.positionSpeed || -.5 this.rotationSpeed = opts.rotationSpeed || .01 } module.exports = FirstPersonCamera FirstPersonCamera.prototype.view = function(out) { if (!out) out = mat4.create() // altered x/y ordering from original mat4.rotateY(out, out, this.rotation[1]) mat4.rotateX(out, out, this.rotation[0]) mat4.rotateZ(out, out, this.rotation[2] - Math.PI) mat4.translate(out, out, [-this.position[0], -this.position[1], -this.position[2]]) return out } FirstPersonCamera.prototype.control = function(dt, move, mouse, prevMouse) { var speed = (this.positionSpeed / 1000) * dt var dir = [0,0,0] if (move[0]) dir[2] -= speed * (Marching.keys.Alt ? 4 : 1 ) else if (move[1]) dir[2] += speed * (Marching.keys.Alt ? 4 : 1 ) if (move[2]) dir[0] += speed * (Marching.keys.Alt ? 4 : 1 ) else if (move[3]) dir[0] -= speed * (Marching.keys.Alt ? 4 : 1 ) if (move[4]) dir[1] -= speed * (Marching.keys.Alt ? 4 : 1 ) else if (move[5]) dir[1] += speed * (Marching.keys.Alt ? 4 : 1 ) this.move(dir) // just use arrow keys instead of mouse // this.pointer(mouse, prevMouse) } FirstPersonCamera.prototype.move = function(dir) { if (dir[0] !== 0 || dir[1] !== 0 || dir[2] !== 0) { var cam = mat4.create() mat4.rotateY(cam, cam, this.rotation[1]) mat4.rotateX(cam, cam, this.rotation[0]) vec3.transformMat4(dir, dir, cam) vec3.add(this.position, this.position, dir) this.parent.pos.dirty = true } } //FirstPersonCamera.prototype.pointer = function(da, db) { // var dt = [da[0] - db[0], da[1]- db[1]] // var rot = this.rotation // rot[1] -= dt[0] * this.rotationSpeed // if (rot[1] < 0) rot[1] += Math.PI * 2 // if (rot[1] >= Math.PI * 2) rot[1] -= Math.PI * 2 // rot[0] -= dt[1] * this.rotationSpeed // if (rot[0] < -Math.PI * .5) rot[0] = -Math.PI*0.5 // if (rot[0] > Math.PI * .5) rot[0] = Math.PI*0.5 //} const Camera = { init( gl, program, handler ) { const camera = FirstPersonCamera({ fov: 190, near:.01, far:10, direction:[0,0,1], viewport:[1,1,1,-1] }) camera.rotation = [0,Math.PI,Math.PI] Camera.__camera = camera camera.parent = this const camera_pos = gl.getUniformLocation( program, 'camera_pos' ) const camera_normal = gl.getUniformLocation( program, 'camera_normal' ) const camera_rot = gl.getUniformLocation( program, 'camera_rot' ) const ucamera = gl.getUniformLocation( program, 'camera' ) this.pos = { dirty:false } this.dir = { dirty:true } this.__rot = { dirty:true, value:0 } Object.defineProperty( this, 'rotation', { configurable:true, get() { return this.__rot.value }, set(v) { this.__rot.value = v this.__rot.dirty = true } }) let px = 0, py =0, pz = 5, nx = 0, ny = 0, nz = 0 Object.defineProperties( this.pos, { x: { get() { return px }, set(v) { px = camera.position[0] = v;this.dirty = true; } }, y: { get() { return py }, set(v) { py = camera.position[1] = v; this.dirty = true; } }, z: { get() { return pz }, set(v) { pz = camera.position[2] = v; this.dirty = true; } }, }) Object.defineProperties( this.dir, { x: { get() { return nx }, set(v) { nx = camera.rotation[0] = v; this.dirty = true; } }, y: { get() { return ny }, set(v) { ny = camera.rotation[1] = v; this.dirty = true; } }, z: { get() { return nz }, set(v) { nz = camera.rotation[2] = v; this.dirty = true; } }, }) let init = false gl.uniform3f( camera_normal, this.dir.x, this.dir.y, this.dir.z ) camera.position = [this.pos.x, this.pos.y, this.pos.z ] //camera.update() gl.uniform3f( camera_pos, this.pos.x, this.pos.y, this.pos.z ) gl.uniformMatrix4fv( ucamera, false, camera.view() ) gl.uniform1f( camera_rot, this.rot ) Camera.move = (x,y,z) => { // XXX does this need to update property values? camera.move([x,y,z]) Camera.update() } Camera.moveTo = (x,y,z) => { Camera.pos.x = x Camera.pos.y = y Camera.pos.z = z } Camera.update = ()=> { const pos = camera.position gl.uniform3f( camera_pos, pos[0], pos[1], pos[2] ) gl.uniformMatrix4fv( ucamera, false, camera.view() ) } // determine an offset from the current camera position based // on the current camera rotation e.g. to always position a light // behind the camera. Camera.offset = (amt=[0,0,3]) => { const cam = mat4.create() mat4.rotateY(cam, cam, camera.rotation[1]) mat4.rotateX(cam, cam, camera.rotation[0]) vec3.transformMat4(amt, amt, cam) return amt } let prvx = 0, prvy = 0, x = 0, y = 0 Camera.__mousemovefnc = e => { prvx = x prvy = y x = e.pageX y = e.pageY } let prevTime = 0 let k = Marching.keys Camera.__framefnc = t => { if( k.ArrowLeft ) camera.rotation[1] += camera.rotationSpeed if( k.ArrowRight ) camera.rotation[1] -= camera.rotationSpeed if( k.ArrowUp && !k.Shift ) camera.rotation[0] -= camera.rotationSpeed if( k.ArrowDown && !k.Shift) camera.rotation[0] += camera.rotationSpeed if( Marching.cameraEnabled ) { camera.control( t*1000 - prevTime, [k.w,k.s,k.d,k.a,k.ArrowUp && k.Shift, k.ArrowDown && k.Shift], [x,y], [prvx,prvy] ) Camera.update() prvx = x prvy = y prevTime = t*1000 } } Camera.__mousemove = null Camera.on = ()=> { if( Camera.__mousemove === null ) { window.addEventListener( 'mousemove', Camera.__mousemovefnc ) Camera.__mousemove = true } if( Marching.callbacks.indexOf( Camera.__framefnc ) === -1 ) { Marching.callbacks.push( Camera.__framefnc ) } } handler( ()=> { if( this.pos.dirty === true ) { //camera.position = [this.pos.x, this.pos.y, this.pos.z ] //camera.position = [this.pos.x, this.pos.y, this.pos.z ] //camera.update() const pos = camera.position gl.uniform3f( camera_pos, pos[0], pos[1], pos[2] ) gl.uniformMatrix4fv( ucamera, false, camera.view() ) this.pos.dirty = false } // XXX this is broken and needs to be fixed if( this.dir.dirty === true ) { gl.uniform3f( camera_normal, this.dir.x, this.dir.y, this.dir.z ) gl.uniformMatrix4fv( ucamera, false, camera.view() ) this.dir.dirty = false } if( this.__rot.dirty === true ) { gl.uniform1f( camera_rot, this.__rot.value ) this.__rot.dirty = false } if( typeof this.onmove === 'function' ) { this.onmove( this ) } }) } } module.exports = Camera },{"gl-mat4":115,"gl-vec3":149}],5:[function(require,module,exports){ const SceneNode = require( './sceneNode.js' ) const { param_wrap, MaterialID } = require( './utils.js' ) const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' ) const Transform = require( './transform.js' ) const ops = { // this needs to create an opOut, not return a vec2 Displace( __name ) { let name = __name === undefined ? 'p' : __name const sdf = this.sdf.emit( name ); const sdfStr = `float d1${this.id} = ${sdf.out}.x;\n` let displaceString = `float d2${this.id} = sin( ${this.amount.emit()}.x * ${name}.x ) * ` displaceString += `sin( ${this.amount.emit()}.y * ${name}.y ) * ` displaceString += `sin( ${this.amount.emit()}.z * ${name}.z );\n` displaceString += `${sdf.out}.x = (d1${this.id} + d2${this.id}*${this.size.emit()})*.5;\n` const output = { out: `${sdf.out}`, preface: sdf.preface + sdfStr + displaceString } return output }, Halve( __name ) { let name = __name === undefined ? 'p' : __name // XXX why? this.transform.invert() const bumpString = ` vec4 transformBump${this.id} = ${name} * ${this.transform.emit()};\n` const pointString = ` vec4 transformBump2${this.id} = (transformBump${this.id} * ${this.sdf.transform.emit()});\n` const sdf = this.sdf.emit( `transformBump2${this.id}` ); let displaceString = `${sdf.out}.x = opHalve( ${sdf.out}.x, transformBump2${this.id}, int(${this.amount.emit()}) );` const output = { out: `${sdf.out}`, preface: bumpString + pointString + sdf.preface + displaceString } return output }, Bend( __name ) { let name = __name === undefined ? 'p' : __name const sdf = this.sdf.emit( 'q'+this.id ); let preface=` float c${this.id} = cos( ${this.amount.emit()}.x * ${name}.x ); float s${this.id} = sin( ${this.amount.emit()}.x * ${name}.x ); mat2 m${this.id} = mat2( c${this.id},-s${this.id},s${this.id},c${this.id} ); vec4 q${this.id} = vec4( m${this.id} * ${name}.xy, ${name}.z, 1. );\n` if( typeof sdf.preface === 'string' ) { preface += sdf.preface } return { preface, out:sdf.out } }, Twist( __name ) { let name = __name === undefined ? 'p' : __name const sdf = this.sdf.emit( 'q'+this.id ); let preface=` float c${this.id} = cos( ${this.amount.emit()}.x * ${name}.y ); float s${this.id} = sin( ${this.amount.emit()}.x * ${name}.y ); mat2 m${this.id} = mat2( c${this.id},-s${this.id},s${this.id},c${this.id} ); vec4 q${this.id} = vec4( m${this.id} * ${name}.xz, ${name}.y, 1. );\n` if( typeof sdf.preface === 'string' ) { preface += sdf.preface } return { preface, out:sdf.out } }, __Bump( __name ) { let name = __name === undefined ? 'p' : __name const bumpString = ` vec4 transformBump${this.id} = ${name} * ${this.transform.emit()};\n` const tex = this.amount.emit( name ) const pointString = `(transformBump${this.id} * ${this.sdf.transform.emit()})` const sdf = this.sdf.emit( pointString, this.transform, `tex${this.id}` ) Marching.textures.addTexture( this.amount.value ) let preface=` vec3 tex${this.id} = getTexture( ${this.amount.value.id}, ${pointString}.xyz ) * ${this.size.emit()};\n //vec4 displaceBump${this.id} = vec4((${pointString} - tex${this.id}), 1.); ` //${sdf.out}.x = (tex${this.id}.x + tex${this.id}.y + tex${this.id}.z ) / 3. * .5 + ${sdf.out}.x;\n` //vec4 ${'p'+this.id} = vec4(${pointString} + tex${this.id}, 1.);\n` //sdf.preface += `\n // ${sdf.out}.x -= min(tex${this.id}.x, min(tex${this.id}.y, tex${this.id}.z));\n` if( typeof sdf.preface === 'string' ) { preface = preface + sdf.preface } preface = bumpString + preface return { preface, out:sdf.out } }, // XXX todo: something like https://www.shadertoy.com/view/ldSGzR // https://www.dropbox.com/s/l1yl164jb3rhomq/mm_sfgrad_bump.pdf?dl=0 Bump( __name ) { let name = __name === undefined ? 'p' : __name const bumpString = ` vec4 transformBump${this.id} = ${name} * ${this.transform.emit()};\n` const tex = this.amount.emit( name ) const pointString = `(transformBump${this.id} * ${this.sdf.transform.emit()}).xyz` const sdf = this.sdf.emit( `transformBump${this.id}`, this.transform ) Marching.textures.addTexture( this.amount.value ) let preface=` vec3 tex${this.id} = getTexture( ${this.amount.value.id}, ${pointString}) * ${this.size.emit()}; ${sdf.out}.x = (tex${this.id}.x + tex${this.id}.y + tex${this.id}.z)/3. + ${sdf.out}.x;\n` if( typeof sdf.preface === 'string' ) { preface = sdf.preface + preface } preface = bumpString + preface return { preface, out:sdf.out } }, } const DistanceOps = {} for( let name in ops ) { // get codegen function let __op = ops[ name ] // create constructor DistanceOps[ name ] = function( a,b,c ) { const op = Object.create( DistanceOps[ name ].prototype ) op.sdf = a op.amount = b op.emit = __op op.name = name op.transform = Transform() const defaultValues = [.5,.5,.5] op.id = VarAlloc.alloc() const isArray = true if( name === 'Halve' ) { op.amount = param_wrap( b, float_var_gen( b ) ) }else{ if( typeof b === 'number' ) { b = [b,b,b] b.type = 'vec3' } } if( name !== 'Bumpz' ) { let __var = param_wrap( b, vec3_var_gen( ...defaultValues ) ) // for assigning entire new vectors to property Object.defineProperty( op, 'amount', { get() { return __var }, set(v) { if( typeof v === 'object' ) { __var.set( v ) }else{ __var.value.x = v __var.value.y = v __var.value.z = v __var.value.w = v __var.dirty = true } } }) op.params = [{ name:'amount' }] }else{ op.params = [] op.emit_decl = function() {} op.emit = function() {} op.update_data= function() {} op.upload_location = function() {} } op.__setMaterial = function(mat) { if( typeof mat === 'string' ) mat = Marching.Material[ mat ] this.__material = this.mat = Marching.materials.addMaterial( mat ) op.sdf.material( this.__material ) } if( name === 'Displace' || name === 'Bump' ) { let __var2 = param_wrap( c, float_var_gen( .03 ) ) Object.defineProperty( op, 'size', { get() { return __var2 }, set(v) { __var2.set( v ) __var2.dirty = true } }) op.params.push({ name:'size' }) } op.__desc = { parameters:op.params } return op } DistanceOps[ name ].prototype = SceneNode() DistanceOps[name].prototype.emit_decl = function () { let str = this.sdf.emit_decl() + (this.name !== 'Bump' ? this.amount.emit_decl() : '') str += this.transform.emit_decl() if( this.name === 'Displace' || this.name === 'Bump' ) str += this.size.emit_decl() return str }; DistanceOps[name].prototype.update_location = function(gl, program) { this.sdf.update_location( gl, program ) if( this.name !== 'Bump' ) this.amount.update_location( gl, program ) if( this.name === 'Displace' || this.name === 'Bump') this.size.update_location( gl, program ) this.transform.update_location( gl, program ) } DistanceOps[name].prototype.upload_data = function(gl) { this.sdf.upload_data( gl ) if( this.name !== 'Bump' ) this.amount.upload_data( gl ) if( this.name === 'Displace' || this.name === 'Bump' ) this.size.upload_data( gl ) this.transform.upload_data( gl ) } } DistanceOps.Halve.UP = 0 DistanceOps.Halve.DOWN = 1 DistanceOps.Halve.LEFT = 3 DistanceOps.Halve.RIGHT = 2 module.exports = DistanceOps },{"./sceneNode.js":23,"./transform.js":27,"./utils.js":28,"./var.js":29}],6:[function(require,module,exports){ const SceneNode = require( './sceneNode.js' ) const { param_wrap, MaterialID } = require( './utils.js' ) const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' ) const Transform = require( './transform.js' ) const glslops = require( './distanceOperationsGLSL.js' ) const opslen = { Union:2, Intersection:2, Difference:2, StairsUnion:4, StairsIntersection:4, StairsDifference:4, RoundUnion:3, RoundDifference:3, RoundIntersection:3, ChamferUnion:3, ChamferDifference:3, ChamferIntersection:3, Pipe:3, Engrave:3, Groove:4, Tongue:4, // these two do not currently have support for transforms or repeats... Onion:2, Switch:2 } const ops = { Union( ...args ) { return `opU( ${args.join(',')} )` }, SmoothUnion( ...args ) { return `opSmoothUnion( ${args.join(',')} )` }, Intersection( ...args ) { return `opI( ${args.join(',')} )` }, SmoothIntersection( ...args ) { return `opSmoothIntersection( ${args.join(',')} )` }, Difference( ...args ) { return `opS( ${args.join(',')} )` }, SmoothDifference( ...args ) { return `opSmoothSubtraction( ${args.join(',')} )` }, StairsUnion( ...args ) { return `fOpUnionStairs( ${args.join(',')} )` }, StairsIntersection( ...args ) { return `fOpIntersectionStairs( ${args.join(',')} )` }, StairsDifference( ...args ) { return `fOpSubstractionStairs( ${args.join(',')} )` }, RoundUnion( ...args ) { return `fOpUnionRound( ${args.join(',')} )` }, RoundDifference( ...args ) { return `fOpDifferenceRound( ${args.join(',')} )` }, RoundIntersection( ...args ) { return `fOpIntersectionRound( ${args.join(',')} )` }, ChamferUnion( ...args ) { return `fOpUnionChamfer( ${args.join(',')} )` }, ChamferDifference( ...args ) { return `fOpDifferenceChamfer( ${args.join(',')} )` }, ChamferIntersection( ...args ) { return `fOpIntersectionChamfer( ${args.join(',')} )` }, Pipe( ...args ) { return `fOpPipe( ${args.join(',')} )` }, Engrave( ...args ) { return `fOpEngrave( ${args.join(',')} )` }, Groove( ...args ) { return `fOpGroove( ${args.join(',')} )` }, Tongue( ...args ) { return `fOpTongue( ${args.join(',')} )` }, // these two do not currently have support for transforms or repeats... Onion( ...args ) { return `opOnion( ${args.join(',')} )` }, Switch( a,b,c,d,e,f ) { return `opSwitch( ${a}, ${b}, ${c} )` } } const emit_float = function( a ) { if (a % 1 === 0) return a.toFixed( 1 ) else return a } const DistanceOps = { __glsl:[], __getGLSL() { return this.__glsl.join('\n') }, __clear() { this.__glsl.length = 0 } } const oneops = ['Onion','Halve'] for( let name in ops ) { // get codegen function let op = ops[ name ] const name2 = name + '2' // create constructor DistanceOps[ name ] = function( a,b,c,d ) { const op = Object.create( DistanceOps[ name ].prototype ) op.a = a op.b = oneops.indexOf( name ) === -1 ? b : param_wrap( b, float_var_gen(.2 ) ) op.transform = Transform( false ) op.id = VarAlloc.alloc() op.type = 'distance_op' op.name = name let __c = param_wrap( c, float_var_gen(.3) ) op.__len = opslen[ name ] if( op.__len > 2 ) { Object.defineProperty( op, 'c', { get() { return __c }, set(v) { __c.set( v ) } }) if( op.__len > 3 ) { let __d = param_wrap( d, float_var_gen(4) ) Object.defineProperty( op, 'd', { get() { return __d }, set(v) { __d.set( v ) } }) } } op.__setTexture = function(tex,props) { if( typeof tex === 'string' ) { this.texture = op.texture.bind( this ) this.__textureObj = this.tex = Marching.Texture( tex,props,this.texture ) this.__textureID = this.__textureObj.id }else{ this.__textureObj = this.tex = Object.assign( tex, props ) this.__textureID = this.__textureObj.id } } op.__setMaterial = function(mat) { if( typeof mat === 'string' ) mat = Marching.Material[ mat ] this.__material = this.mat = Marching.materials.addMaterial( mat ) } op.__setBump = function(tex,props) { //this.bump = p.bump.bind( this ) const b = this.bump = this.__bumpObj = Marching.Bump( this, tex, props ) this.bump.texture = this.bump.amount.value this.__bumpID = this.__bumpObj.id this.rotate = this.bump.rotate this.translate = this.bump.translate this.scale = this.bump.scale Object.defineProperty( this.bump, 'strength', { get() { return b.size }, set(v){ b.size = v } }) } Object.assign( op, { renderingBump : false, emittingDecl : false, uploading : false, updating : false }) let repeat = null Object.defineProperty( op, 'repeat', { get() { return repeat }, set(v){ repeat = v this.a.repeat = v this.b.repeat = v } }) op.matId = MaterialID.alloc() op.params = [{name:'c'},{ name:'d'}] op.__desc = { parameters: op.params } return op } DistanceOps[ name2 ] = function( ...args ) { // accepts unlimited arguments, but the last one could be a blending coefficient let blend = .25, coeff=4, u if( typeof args[ args.length - 1 ] === 'number' ) { blend = args.pop() // if there are two non-sdf arguments to the function... if( typeof args[ args.length - 1 ] === 'number' ) { coeff = blend blend = args.pop() } u = args.reduce( (state,next) => DistanceOps[ name ]( state, next, blend, coeff ) ) }else{ u = args.reduce( (state,next) => DistanceOps[ name ]( state, next ) ) } return u } DistanceOps[ name ].prototype = SceneNode() DistanceOps[ name ].prototype.texture = function( ...args ) { this.__setTexture( ...args ) this.a.texture( this.__textureObj ) if( oneops.indexOf( name ) === -1 ) this.b.texture( this.__textureObj ) return this } DistanceOps[ name ].prototype.material = function( ...args ) { this.__setMaterial( ...args ) this.a.material( this.__material ) if( oneops.indexOf( name ) === -1 ) this.b.material( this.__material ) return this } const pushString = function( name ) { const glslobj = glslops[ name ] // some definitions are a single string, and not split into // separate float and opOut functions if( typeof glslobj === 'string' ) { if( DistanceOps.__glsl.indexOf( glslobj ) === -1 ) { DistanceOps.__glsl.push( glslobj ) } }else{ // some distance operations are dependent on other ones... // if this one has dependencies add them. // dependencies must be added before adding other functions // so that they're above them in the final GLSL code. if( glslobj.dependencies !== undefined ) { for( let dname of glslobj.dependencies ) { const d = glslops[ dname ] if( DistanceOps.__glsl.indexOf( d.float ) === -1 ) { DistanceOps.__glsl.push( d.float ) } } } if( DistanceOps.__glsl.indexOf( glslobj.float ) === -1 ) { DistanceOps.__glsl.push( glslobj.float ) } if( DistanceOps.__glsl.indexOf( glslobj.vec2) === -1 ) { DistanceOps.__glsl.push( glslobj.vec2 ) } } } DistanceOps[ name ].prototype.emit = function ( pname='p', transform = null ){ const isNotOneop = oneops.indexOf( name ) === -1 if( this.__bumpObj !== undefined && this.renderingBump === false) { this.renderingBump = true return this.__bumpObj.emit( pname, transform ) } pushString( name ) if( transform !== null ) this.transform.apply( transform, false ) //this.transform.internal() // first two args are fixed, rest are variable let emitters = [] const a = this.a.emit( pname, this.transform ) const b = isNotOneop ? this.b.emit( pname, this.transform ) : this.b.emit() emitters[0] = a.out emitters[1] = isNotOneop ? b.out : b if( this.__len > 2 ) emitters.push( this.c.emit() ) if( this.__len > 3 ) emitters.push( this.d.emit() ) const body = ` vec2 do${this.id} = ${op( ...emitters )}; do${this.id}.x *= ${this.transform.emit()}_scale; ` const output = { out: 'do'+this.id, preface: (a.preface || '') + ( isNotOneop ? b.preface || '' : '') + body } this.renderingBump = false return output } DistanceOps[name].prototype.emit_decl = function () { if( this.__bumpObj !== undefined && this.emittingDecl === false) { this.emittingDecl = true return this.__bumpObj.emit_decl() } let str = this.transform.emit_decl() + this.a.emit_decl() + this.b.emit_decl() if( this.c !== undefined ) str += this.c.emit_decl() if( this.d !== undefined ) str += this.d.emit_decl() if( ops[ name ].code !== undefined ) { //str += ops[ name ].code if( Marching.requiredOps.indexOf( ops[ name ].code ) === - 1 ) { Marching.requiredOps.push( ops[ name ].code ) } } this.emittingDecl = false return str }; DistanceOps[name].prototype.update_location = function(gl, program) { if( this.__bumpObj !== undefined && this.updating === false) { this.updating = true return this.__bumpObj.update_location( gl, program ) } this.a.update_location( gl, program ) this.transform.update_location( gl, program ) this.b.update_location( gl, program ) if( this.c !== undefined ) this.c.update_location( gl, program ) if( this.d !== undefined ) this.d.update_location( gl, program ) this.updating = false } DistanceOps[name].prototype.upload_data = function(gl) { if( this.__bumpObj !== undefined && this.uploading === false ) { this.uploading = true return this.__bumpObj.upload_data( gl ) } this.transform.internal() this.transform.upload_data( gl ) this.a.transform.apply( this.transform ) if( oneops.indexOf( name ) === -1 ) this.b.transform.apply( this.transform ) this.a.upload_data( gl ) this.b.upload_data( gl ) if( this.c !== undefined ) this.c.upload_data( gl ) if( this.d !== undefined ) this.d.upload_data( gl ) this.uploading = false } } module.exports = DistanceOps },{"./distanceOperationsGLSL.js":7,"./sceneNode.js":23,"./transform.js":27,"./utils.js":28,"./var.js":29}],7:[function(require,module,exports){ module.exports = { Union:{ float:` float opU( float d1, float d2 ) { return min(d1,d2); } `, vec2:` vec2 opU( vec2 d1, vec2 d2 ) { vec2 o; if( d1.x < d2.x ) { o = d1; }else{ o = d2; } return o; } ` }, Intersection:{ float:` float opI( float d1, float d2 ) { return max(d1,d2); } `, vec2:` vec2 opI( vec2 d1, vec2 d2 ) { vec2 o; if( d1.x > d2.x ) { o = d1; }else{ o = d2; } return o; } ` }, Difference:{ float:` float opS( float d1, float d2 ) { return max(d1,-d2); } `, vec2:` vec2 opS( vec2 d1, vec2 d2 ) { vec2 o; if( d1.x >= -d2.x ) { o = d1; }else{ d2.x *= -1.; o = d2; } return o; } ` }, StairsUnion:{ float:` float fOpUnionStairs(float a, float b, float r, float n) { float s = r/n; float u = b-r; return min(min(a,b), 0.5 * (u + a + abs ((mod (u - a + s, 2. * s)) - s))); }`, vec2:` vec2 fOpUnionStairs( vec2 d1, vec2 d2, float r, float n ) { vec2 o = vec2( 0., d1.y ); if( d1.x <= d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } o.x = fOpUnionStairs( d1.x, d2.x, r, n ); return o; } ` }, StairsIntersection:{ dependencies: ['StairsUnion'], float:` // We can just call Union since stairs are symmetric. float fOpIntersectionStairs(float a, float b, float r, float n) { return -fOpUnionStairs(-a, -b, r, n); } `, vec2:` vec2 fOpIntersectionStairs( vec2 d1, vec2 d2, float r, float n ) { vec2 o = vec2( 0., d1.y ); o.x = -fOpUnionStairs( -d1.x, -d2.x, r, n ); if( -d1.x <= -d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, StairsDifference:{ dependencies: ['StairsUnion'], float:` float fOpSubstractionStairs(float a, float b, float r, float n) { return -fOpUnionStairs(-a, b, r, n); }`, vec2:` vec2 fOpSubstractionStairs( vec2 d1, vec2 d2, float r, float n ) { vec2 o = vec2( 0., d1.y ); o.x = -fOpUnionStairs( -d1.x, d2.x, r, n ); if( -d1.x <= d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, RoundUnion:{ float:` float fOpUnionRound(float a, float b, float r) { vec2 u = max(vec2(r - a,r - b), vec2(0)); return max(r, min (a, b)) - length(u); }`, vec2:` vec2 fOpUnionRound( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpUnionRound( d1.x, d2.x, r ); if( d1.x <= d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, RoundIntersection:{ float:` float fOpIntersectionRound(float a, float b, float r) { vec2 u = max(vec2(r + a,r + b), vec2(0)); return min(-r, max (a, b)) + length(u); }`, vec2:` vec2 fOpIntersectionRound( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpIntersectionRound( d1.x, d2.x, r ); if( d1.x >= d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, RoundDifference:{ dependencies: ['RoundIntersection'], float:` float fOpDifferenceRound (float a, float b, float r) { return fOpIntersectionRound(a, -b, r); }`, vec2:` vec2 fOpDifferenceRound( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpDifferenceRound( d1.x, d2.x, r ); if( d1.x >= -d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, ChamferUnion:{ float:` float fOpUnionChamfer(float a, float b, float r) { return min(min(a, b), (a - r + b)*sqrt(0.5)); }`, vec2:` vec2 fOpUnionChamfer( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpUnionChamfer( d1.x, d2.x, r ); if( d1.x <= d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, ChamferIntersection:{ float:` float fOpIntersectionChamfer(float a, float b, float r) { return max(max(a, b), (a + r + b)*sqrt(0.5)); }`, vec2:` vec2 fOpIntersectionChamfer( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpIntersectionChamfer( d1.x, d2.x, r ); if( d1.x >= d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, ChamferDifference:{ dependencies:['ChamferIntersection'], float:` float fOpDifferenceChamfer (float a, float b, float r) { return fOpIntersectionChamfer(a, -b, r); }`, vec2:` vec2 fOpDifferenceChamfer( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpDifferenceChamfer( d1.x, d2.x, r ); if( d1.x >= -d2.x ) { o.y = d1.y; }else{ o.y = d2.y; } return o; } ` }, Pipe:` float fOpPipe(float a, float b, float r) { return length(vec2(a, b)) - r; } vec2 fOpPipe( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpPipe( d1.x, d2.x, r ); return o; } `, Engrave:` float fOpEngrave(float a, float b, float r) { return max(a, (a + r - abs(b))*sqrt(0.5)); } vec2 fOpEngrave( vec2 d1, vec2 d2, float r ) { vec2 o = vec2( 0., d1.y ); o.x = fOpEngrave( d1.x, d2.x, r ); return o; } `, Groove:` float fOpGroove(float a, float b, float ra, float rb) { return max(a, min(a + ra, rb - abs(b))); } vec2 fOpGroove( vec2 d1, vec2 d2, float r, float n ) { vec2 o = vec2( 0., d1.y ); o.x = fOpGroove( d1.x, d2.x, r, n ); return o; } `, Tongue:` float fOpTongue(float a, float b, float ra, float rb) { return min(a, max(a - ra, abs(b) - rb)); } vec2 fOpTongue( vec2 d1, vec2 d2, float r, float n ) { vec2 o = vec2( 0., d1.y ); o.x = fOpTongue( d1.x, d2.x, r, n ); return o; } `, Onion:` float opOnion( float sdf, float thickness ){ return abs(sdf)-thickness; } vec2 opOnion( vec2 sdf, float thickness ) { float x = 0.; sdf.x = opOnion( sdf.x, thickness ); return sdf; } `, Halve:` float opHalve( in float sdf, vec4 p, in int dir ){ float _out = 0.; switch( dir ) { case 0: _out = max( sdf, p.y ); break; case 1: _out = max( sdf, -p.y ); break; case 2: _out = max( sdf, p.x ); break; case 3: _out = max( sdf, -p.x ); break; } return _out; } vec2 opHalve( vec2 sdf, vec4 p, int dir ) { float x = 0.; x = opHalve( sdf.x, p, dir ); sdf.x = x; return sdf; } `, Switch:` vec2 opSwitch( vec2 a, vec2 b, float c ) { if( c < .5 ) { return a; }else{ return b; } } ` } },{}],8:[function(require,module,exports){ const { Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' ) const SceneNode = require( './sceneNode.js' ) const { param_wrap, MaterialID } = require( './utils.js' ) const { Vec2, Vec3, Vec4 } = require( './vec.js' ) const Transform = require( './transform.js' ) const descriptions = { Elongation: { parameters:[ { name:'active', type:'float', default:1. },{ name:'distance', type:'vec3', default:Vec3(0) } ], func:` vec4 opElongate( in vec3 p, in vec3 h ) { //return vec4( p-clamp(p,-h,h), 0.0 ); // faster, but produces zero in the interior elongated box vec3 q = abs(p)-h; return vec4( max(q,0.0), min(max(q.x,max(q.y,q.z)),0.0) ); }`, emit( name='p' ) { const pId = this.getID() const pName = 'p' + pId let preface = ` vec4 ${pName}_xyzw = opElongate( ${name}, ${this.distance.emit()} );\n vec3 ${pName} = ${pName}_xyzw.xyz;\n` const sdf = this.sdf.emit( pName ) if( typeof sdf.preface === 'string' ) preface += sdf.preface return { out:`vec2(${pName}_xyzw.w + ${sdf.out}.x, ${sdf.out}.y)`, preface } } }, PolarRepetition: { parameters:[ { name:'count', type:'float', default:5 }, { name:'distance', type:'vec3', default:Vec3(.25) }, { name:'active', type:'float', default:1. } ], emit( name='p', transform=null) { const pId = VarAlloc.alloc() const pName = 'p' + pId if( transform !== null ) this.transform.apply( transform, false ) this.transform.invert() const pointString = `( ${name} * ${this.transform.emit()} ).xyz` let preface =` vec4 ${pName} = vec4( polarRepeat( ${pointString}, ${this.__target.count.emit() } ) * ${this.transform.emit_scale()}, 1. ); ${pName} -= vec4(${this.__target.distance.emit()}.x,0.,0.,0.);\n` const sdf = this.sdf.emit( pName ) if( typeof sdf.preface === 'string' ) preface += sdf.preface return { out:sdf.out, preface } } }, Mirror: { parameters: [ { name:'distance', type:'vec3', default:Vec3(0) },{ name:'active', type:'float', default:1. } ], extra:[{ name:'dims', type:'local', default:'xyz' }], emit( name='p', transform=null, notused=null, scale=null ) { const pId = VarAlloc.alloc() const pName = 'p' + pId if( transform !== null ) { this.transform.apply( transform, false ) } this.transform.invert() const pointString = `( ${name} * ${this.transform.emit()} ).xyz`, s = scale === null ? this.transform.emit_scale() : `${this.transform.emit_scale()} * ${scale}` let preface =` vec4 ${pName} = vec4( ( ${pointString} ) , 1.);\n ${pName}.${this.dims} = abs( ${pName}.${this.dims} );\n` const sdf = this.sdf.emit( pName, null, null, s ) if( typeof sdf.preface === 'string' ) preface += sdf.preface return { out:sdf.out, preface } } }, //let preface = ` vec3 ${pName} = ${name} / ${this.amount.emit()};\n` //let sdf = this.sdf.emit( pName ) //let out = sdf.out //sdf.preface += ` ${out}.x = ${out}.x * ${this.amount.emit()};\n` //if( typeof sdf.preface === 'string' ) preface += sdf.preface Repetition: { parameters: [ { name:'distance', type:'vec3', default:Vec3(0) }, { name:'active', type:'float', default:1. }], emit( name='p', transform=null ) { const pId = VarAlloc.alloc() const pName = 'p' + pId if( transform !== null ) this.transform.apply( transform, false ) this.transform.invert() const pointString = `( ${name} * ${this.transform.emit()} ).xyz`; let preface =` vec4 ${pName} = vec4( (mod( ${pointString}, ${this.__target.distance.emit()} ) - .5 * ${this.__target.distance.emit()}) * ${this.transform.emit_scale()}, 1.);\n` const sdf = this.sdf.emit( pName )//, this.transform )//, 1, this.__target.distance ) if( typeof sdf.preface === 'string' ) preface += sdf.preface return { out:sdf.out, preface } } }, // https://www.shadertoy.com/view/wlyBWm // https://www.shadertoy.com/view/NdS3Dh SmoothRepetition: { parameters: [ { name:'distance', type:'vec3', default:Vec3(0) }, { name:'smoothness', type:'float', default:.5 }], emit( name='p', transform=null ) { const pId = VarAlloc.alloc() const pName = 'p' + pId if( transform !== null ) this.transform.apply( transform, false ) this.transform.invert() const pointString = `( ${name} * ${this.transform.emit()} ).xyz`; // vec2 smoothrepeat_asin_sin(vec2 p,float smooth_size,float size){ // p/=size; // p=asin(sin(p)*(1.0-smooth_size)); // return p*size; let preface =` vec3 ${pName}Mod = ${pointString}/${this.__target.distance.emit()}; ${pName}Mod = asin( sin( ${pName}Mod ) * (1.0 - ${this.__target.smoothness.emit()} ) ); vec4 ${pName} = vec4( ${pName}Mod * ${this.__target.distance.emit()} * ${this.transform.emit_scale()}, 1. );\n` const sdf = this.sdf.emit( pName )//, this.transform )//, 1, this.__target.distance ) if( typeof sdf.preface === 'string' ) preface += sdf.preface return { out:sdf.out, preface } } }, SmoothPolar: { parameters:[ { name:'count', type:'float', default:5 }, { name:'distance', type:'vec3', default:Vec3(.25) }, { name:'active', type:'float', default:1. } ], emit( name='p', transform=null) { const pId = VarAlloc.alloc() const pName = 'p' + pId if( transform !== null ) this.transform.apply( transform, false ) this.transform.invert() const pointString = `( ${name} * ${this.transform.emit()} ).xyz` //s repetitions ////m smoothness (0-1) ////c correction (0-1) ////d object displace from center /*vec2 smoothRot(vec2 p,float s,float m,float c,float d){ s*=0.5; float k=length(p); float x=asin(sin(atan(p.x,p.y)*s)*(1.0-m))*k; float ds=k*s; float y=mix(ds,2.0*ds-sqrt(x*x+ds*ds),c); return vec2(x/s,y/s-d); }*/ let preface =` vec4 ${pName} = vec4( polarRepeat( ${pointString}, ${this.__target.count.emit() } ) * ${this.transform.emit_scale()}, 1. ); ${pName} -= vec4(${this.__target.distance.emit()}.x,0.,