marching
Version:
Marching.js is a JavaScript library that compiles GLSL ray marchers.
356 lines (291 loc) • 10.7 kB
JavaScript
const SceneNode = require( './sceneNode.js' ),
getPixels = require( 'get-pixels' ),
createTexture = require( 'gl-texture2d' ),
{ param_wrap, MaterialID } = require( './utils.js' ),
{ Var, float_var_gen, vec2_var_gen, vec3_var_gen, vec4_var_gen, int_var_gen, VarAlloc } = require( './var.js' ),
{ Vec2, Vec3, Vec4 } = require( './vec.js' )
const __Textures = function( SDF ) {
const gens = {
int: int_var_gen,
float: float_var_gen,
vec2: vec2_var_gen,
vec3: vec3_var_gen,
vec4: vec4_var_gen,
}
const vars = {
vec2: Vec2,
vec3: Vec3,
vec4: Vec4
}
const Textures = {
textures:[],
__textures:[],
__texturePrefaces:[],
__textureBodies: [],
__types: require( './textureDescriptions.js' ),
__wrap : require( './textureWrap.js' ),
__emitFunction() {
let pushedWrap = false
let decl = `
vec3 getTexture( int id, vec3 pos ) {
vec3 tex;
vec2 pos2;
switch( id ) {\n`
Textures.__textureBodies.length = 0
let funcdefs = ''
this.textures.forEach( (t,i) => {
const mode = t.mode !== '2d' && t.glsl !== undefined ? '3d' : '2d'
// add texture wrap function if needed
if( mode === '2d' && pushedWrap === false ) {
Textures.__textureBodies.push( Textures.__wrap )
pushedWrap = true
}
let glsl = mode === '3d' ? t.glsl : t.glsl2d
const usesId = glsl.indexOf( '_id_' ) > -1
let functionName = mode === '2d' ? t.name + '2d' : t.name
if( usesId ) {
glsl = glsl.replace( /\_id\_/g, t.id )
glsl = glsl.replace( new RegExp( `${functionName}`, 'g' ), functionName+t.id )
functionName = functionName+t.id
}
if( Textures.__textureBodies.indexOf( glsl ) === -1 ) {
Textures.__textureBodies.push( glsl )
}
const args = t.parameters.map( p => t.__target[ p.name ].emit() )
decl +=`
case ${i}:
${mode === '2d' ? ` pos2 = getUVCubic( pos );\n` : ''}
tex = ${functionName}( ${mode === '2d' ?'pos2':'pos'} ${ args.length > 0 ? ',' + args.join(',') : ''} );
break;\n`
})
decl += `
default:
tex = vec3(0.);
break;
}
return tex;
}
vec3 getTexture( int id, vec3 pos, vec3 nor, SDF sdf, bool useTransform ) {
vec3 tex;
vec2 pos2;
vec3 tpos = pos;
if( useTransform == true ) {
if( length(sdf.repeat) != 0. ) {
tpos = mod( (vec4(pos,1.) * sdf.repeatTransform).xyz, sdf.repeat) - .5 * sdf.repeat;
tpos = ( vec4(tpos, 1.) * sdf.transform).xyz;
}else{
tpos = (vec4(tpos,1.) * sdf.transform).xyz;
}
}
return getTexture( id, tpos );
}
`
return { glsldefs: Textures.__textureBodies.join( '\n' ), mainfunc:decl }
},
clear() {
Textures.textures.length = 0
},
addTexture( tex ) {
// we have to dirty the texture so that its data
// will be uploaded to new shaders, otherwise the
// texture will only work the first time it's used, when
// it's dirty on initialization.
Textures.dirty( tex )
// if texture with same name is already found, replace it,
// otherwise push texture
//const oldTex = Textures.textures.find( __tex => tex.name === __tex.name )
//if( oldTex !== undefined ) {
// const idx = Textures.textures.indexOf( oldTex )
// Textures.textures.splice( idx, 1, tex )
// tex.id = idx
//}else{
tex.id = Textures.textures.length
Textures.textures.push( tex )
//}
return tex
},
texture( presetName='noise', props={}, target=null ){
//const isPreset = filenameOrPreset.indexOf( '.' ) === -1
//const defaults = { wrap:SDF.gl.MIRRORED_REPEAT }
if( Textures.__types[ presetName ] === undefined ) {
console.log( `the texture type '${presetName}' does not exist.` )
}
const tex = Object.assign( { mode:'3d' }, Textures.__types[ presetName ], props )
if( target === null ) target = tex
tex.__target = target
for( let param of tex.parameters ) {
const defaultValues = param.default
const isArray = Array.isArray( defaultValues )
let count = 0
if( isArray ) {
let val = props[ param.name ], __var
if( typeof val === 'number' ) {
__var = Var( vars[ param.type ]( val ), null, 'vec3' )
}else{
const initvalues = val !== undefined ? val : defaultValues
__var = Var( vars[ param.type ]( ...initvalues ), null, param.type )
}
// for assigning entire new vectors to property
Object.defineProperty( target, param.name, {
configurable:true,
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
}
}
})
}else{
let __var = param_wrap(
props[ param.name ],
gens[ param.type ]( defaultValues )
)
//__var.set( defaultValues )
Object.defineProperty( target, param.name, {
configurable:true,
get() { return __var },
set(v) {
__var.set( v )
}
})
}
}
if( presetName === 'image' ) {
if( props.filename !== undefined ) {
tex.image = getPixels( props.filename, (err,pixels) => {
if( err !== null ) {
console.error( err )
return
}
tex.pixels = pixels
tex.gltexture = createTexture( SDF.gl, pixels )
tex.gltexture.wrap = props.wrap === undefined ? Marching.gl.REPEAT : props.wrap
})
}else{
tex.image = null
console.error('You must specify a filename when using the iamge preset.')
}
}else if( presetName === 'canvas' ) {
if( props.canvas === undefined ) {
tex.canvas = tex.image = document.createElement('canvas')
}else{
tex.image = tex.canvas = props.canvas
}
if( props.height !== undefined ) {
tex.canvas.height = props.height
}
if( props.width !== undefined ) {
tex.canvas.width = props.width
}
tex.ctx = tex.canvas.getContext('2d')
tex.update = function() {
//tex.gltexture.setPixels( SDF.fx.merger === null ? tex.image : SDF.fx.merger.tex.front.tex )
tex.gltexture.setPixels( tex.image )
}
tex.gltexture = createTexture( SDF.gl, tex.image )
tex.gltexture.wrap = props.wrap === undefined ? Marching.gl.REPEAT : props.wrap
tex.update()
}else if( presetName === 'feedback' ) {
tex.canvas = tex.image = SDF.canvas
tex.ctx = tex.canvas.getContext('2d')
tex.update = function() {
tex.gltexture.setPixels( tex.image )
//tex.gltexture.setPixels( SDF.fx.merger === null ? tex.image : SDF.fx.merger.tex.front.tex )
}
tex.gltexture = createTexture( SDF.gl, tex.image )
tex.gltexture.wrap = props.wrap === undefined ? Marching.gl.REPEAT : props.wrap
tex.update()
}
Object.defineProperty( tex, 'wrap', {
get() { return this.gltexture.wrap },
set(v){
this.gltexture.wrap = v
}
})
tex.name = presetName
return tex
},
dirty( tex ) {},
emit_decl() {
if( this.textures.length === 0 ) return ''
let decl = ''
const memo = []
let imageCount = 0;
this.textures.forEach( (tex,i) => {
if( memo.indexOf( tex ) === -1 ) {
for( let param of tex.parameters ) {
if( param.name !== 'material' )
decl += tex.__target[ param.name ].emit_decl()
}
memo.push( tex )
}
if( tex.name === 'image' || tex.name === 'canvas' || tex.name === 'feedback' ) {
imageCount++
// for some reason can't immediately call update...
// have to wait for some type of dom initialization?
// so call here
if( tex.update ) tex.update()
}
})
if( imageCount > 0 ) {
decl += `\n uniform sampler2D textures[${imageCount}];\n`
}
return decl
},
update_location( gl, program ) {
if( this.textures.length > 0 ) {
this.textures.forEach( (tex,i) => {
for( let param of tex.parameters ) {
if( param.type !== 'obj' ) {
if( param.name !== 'material' )
tex.__target[ param.name ].update_location( gl,program )
}
}
if( tex.name === 'image' || tex.name === 'canvas' || tex.name === 'feedback' ) {
tex.loc = gl.getUniformLocation( program, `textures[${tex.id}]` )
tex.gltexture.bind( i )
}
})
}
//if( this.textures.length > 0 ) {
// this.textures.sort( (a,b) => a.id > b.id ? 1 : -1 )
// for( let tex of this.textures ) {
// tex.loc = gl.getUniformLocation( program, `textures[${tex.id}]` )
// tex.gltexture.bind( tex.id )
// }
// this.__textures = this.textures.slice( 0 )
// this.textures.length = 0
//}
},
upload_data( gl, program ) {
if( this.textures.length > 0 ) {
this.textures.forEach( (tex,i) => {
for( let param of tex.parameters ) {
if( param.type !== 'obj' && param.name !== 'material' )
tex.__target[ param.name ].upload_data( gl )
}
if( tex.name === 'image' || tex.name === 'canvas' || tex.name === 'feedback' ) {
gl.uniform1i( tex.loc, i )
}
})
}
}
}
Textures.texture.create = function( props ) {
Textures.__types[ props.name ] = props
}
Object.defineProperties( Textures.texture, {
'repeat': { get() { return Marching.gl.REPEAT } },
'mirror': { get() { return Marching.gl.MIRRORED_REPEAT } },
'clamp': { get() { return Marching.gl.CLAMP_TO_EDGE } },
})
const f = value => value % 1 === 0 ? value.toFixed(1) : value
return Textures
}
module.exports = __Textures