marching
Version:
Marching.js is a JavaScript library that compiles GLSL ray marchers.
462 lines (393 loc) • 15 kB
JavaScript
const glsl = require( 'glslify' )
module.exports = {
Box: {
parameters:[
{ name:'size', type:'vec3', default:[1,1,1], min:.001, max:5 },
],
primitiveString( pName ) {
return `sdBox( ${pName}, ${this.size.emit()} )`;
},
glslify:glsl` #pragma glslify: sdBox = require('glsl-sdf-primitives/sdBox')`
},
// XXX we should normalize dimensions in the shader...
Cone: {
parameters:[
{ name:'dimensions', type:'vec3', default:[.8,.6,.3], min:.001, max:5 },
],
primitiveString( pName ) {
return `sdCone( ${pName}, ${this.dimensions.emit()} )`
},
glslify:glsl` #pragma glslify: sdCone = require('glsl-sdf-primitives/sdCappedCone')`
},
Cylinder: {
parameters:[
{ name:'dimensions', type:'vec2', default:[.8,.6], min:.001, max:5 },
],
primitiveString( pName ) {
return `sdCappedCylinder( ${pName}, ${this.dimensions.emit()} )`
},
glslify:` float sdCappedCylinder( vec3 p, vec2 h ) {
vec2 d = abs(vec2(length(p.xz),p.y)) - h;
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}`
},
Capsule: {
parameters:[
{ name:'start', type:'vec3', default:[0,0,0], min:0, max:.5 },
{ name:'end', type:'vec3', default:[.8,1,0], min:.5, max:1 },
{ name:'radius', type:'float', default:.5, min:.001, max:5 },
],
primitiveString( pName ) {
return `sdCapsule( ${pName}, ${this.start.emit()}, ${this.end.emit()}, ${this.radius.emit()} )`
},
glslify:glsl` #pragma glslify: sdCapsule = require('glsl-sdf-primitives/sdCapsule')`
},
// XXX No cylinder description
//` #pragma glslify: sdCylinder = require('glsl-sdf-primitives/sdCylinder')`
HexPrism: {
parameters:[
{ name:'dimensions', type:'vec2', default:[.8,.6], min:.001, max:5 },
],
primitiveString( pName ) {
return `sdHexPrism( ${pName}, ${this.dimensions.emit()} )`
},
glslify:glsl` #pragma glslify: sdHexPrism = require('glsl-sdf-primitives/sdHexPrism')`
},
Julia: {
parameters:[
{ name:'fold', type:'float', default:0, min:0, max:10 },
],
primitiveString( pName ) {
return `julia( ${pName}, ${this.fold.emit()} )`
},
// https://www.shadertoy.com/view/MsfGRr
glslify:glsl` vec4 qsqr( in vec4 a ) {
return vec4( a.x*a.x - a.y*a.y - a.z*a.z - a.w*a.w,
2.0*a.x*a.y,
2.0*a.x*a.z,
2.0*a.x*a.w );
}
float julia( in vec3 p, float atime ){
vec4 c = 0.45*cos( vec4(0.5,3.9,1.4,1.1) + atime * vec4(1.2,1.7,1.3,2.5) ) - vec4(0.3,0.0,0.0,0.0);
vec4 z = vec4(p,0.);
float md2 = 1.0;
float mz2 = dot(z,z);
for( int i=0; i<11; i++ ){
md2 *= 4.0*mz2;
// dz -> 2·z·dz, meaning |dz| -> 2·|z|·|dz| (can take the 4 out of the loop and do an exp2() afterwards)
z = qsqr(z) + c; // z -> z^2 + c
mz2 = dot(z,z);
if(mz2>4.0) break;
}
return 0.25*sqrt(mz2/md2)*log(mz2); // d = 0.5·|z|·log|z| / |dz|
}`,
},
KIFS: {
parameters:[
{ name:'count', type:'float', default:8 },
{ name:'fold', type:'float', default:0 },
{ name:'radius', type:'float', default:.01 },
{ name:'threshold', type:'float', default:.004 },
{ name:'scale', type:'float', default:2 },
],
primitiveString( pName ) {
return `kifs( ${pName}, ${this.count.emit()}, ${this.fold.emit()}, ${this.radius.emit()}, ${this.threshold.emit()}, ${this.scale.emit()} )`
},
// adapted from http://roy.red/folding-the-koch-snowflake-.html
glslify:glsl` float box( vec3 p, vec3 b ){
vec3 d = abs(p) - b;
return min(max(d.x,max(d.y,d.z)),0.0) +
length(max(d,0.0));
}
vec2 fold(vec2 p, float ang){
vec2 n=vec2(cos(-ang),sin(-ang));
p-=2.*min(0.,dot(p,n))*n;
return p;
}
#define KPI 3.14159
vec3 tri_fold(vec3 pt, float foldamt) {
pt.xy = fold(pt.xy,KPI/3. + foldamt );
pt.xy = fold(pt.xy,-KPI/3. + foldamt );
pt.yz = fold(pt.yz,KPI/6.+.7 + foldamt );
pt.yz = fold(pt.yz,-KPI/6. + foldamt );
return pt;
}
vec3 tri_curve(vec3 pt, float iter, float fold, float scale ) {
int count = int(iter);
for(int i=0;i<count;i++){
pt*=scale;
pt.x-=2.6;
pt=tri_fold(pt,fold);
}
return pt;
}
float kifs(in vec3 p, float a, float fold, float radius, float thresh, float scale ){
p.x+=1.5;
p=tri_curve(p,a,fold,scale);
// uncomment below line to use spheres instead of boxes
return (length( p*thresh ) - radius );
//return box( p*thresh, vec3(radius) );
}
`,
},
Mandalay: {
parameters:[
{ name:'size', type:'float', default:5, min:1, max:10 },
{ name:'minrad', type:'float', default:1/3, min:0, max:1 },
{ name:'iterations', type:'float', default:5, min:1, max:10, step:1 },
],
glslify:`
float sr = 4.0;
vec3 fo =vec3 (0.7,.9528,.9);
vec3 gh = vec3 (.8,.7,0.5638);
vec3 gw = vec3 (.3, 0.5 ,.2);
vec4 X = vec4( .1,0.5,0.1,.3);
vec4 Y = vec4(.1, 0.8, .1, .1);
vec4 Z = vec4(.2,0.2,.2,.45902);
vec4 R = vec4(0.19,.1,.1,.2);
vec4 orbitTrap = vec4(40000.0);
float DBFold( vec3 p, float fo, float g, float w ){
if(p.z>p.y) p.yz=p.zy;
float vx=p.x-2.*fo;
float vy=p.y-4.*fo;
float v=max(abs(vx+fo)-fo,vy);
float v1=max(vx-g,p.y-w);
v=min(v,v1);
v1=max(v1,-abs(p.x));
return min(v,p.x);
}
vec3 DBFoldParallel(vec3 p, vec3 fo, vec3 g, vec3 w){
vec3 p1=p;
p.x=DBFold(p1,fo.x,g.x,w.x);
p.y=DBFold(p1.yzx,fo.y,g.y,w.y);
p.z=DBFold(p1.zxy,fo.z,g.z,w.z);
return p;
}
vec3 DBFoldSerial(vec3 p, vec3 fo, vec3 g,vec3 w){
p.x=DBFold(p,fo.x,g.x,w.x);
p.y=DBFold(p.yzx,fo.y,g.y,w.y);
p.z=DBFold(p.zxy,fo.z,g.z,w.z);
return p;
}
float sineSponge(vec3 p, float scale, float minrad, float iterations ) {
vec4 JC=vec4(p,1.);
float r2=dot(p,p);
float dd = 1.;
for(int i = 0; i<int(iterations); i++){
p = p - clamp(p.xyz, -1.0, 1.0) * 2.0; // mandelbox's box fold
vec3 signs=sign(p);//Save the original signs
p=abs(p);
p=DBFoldParallel(p,fo,gh,gw);
p*=signs;//resore signs: this way the mandelbrot set won't extend in negative directions
r2=dot(p,p);
float t = clamp(1./r2, 1., 1./minrad);
p*=t; dd*=t;
p=p*scale+JC.xyz; dd=dd*scale+JC.w;
p=vec3(1.0,1.0,.92)*p;
r2=dot(p,p);
orbitTrap = min(orbitTrap, abs(vec4(p.x,p.y,p.z,r2)));
}
dd=abs(dd);
#if 0
return (sqrt(r2)-sr)/dd;//bounding volume is a sphere
#else
p=abs(p); return (max(p.x,max(p.y,p.z))-sr)/dd;//bounding volume is a cube
#endif
}
`,
primitiveString( pName ) {
return `sineSponge( ${pName}, ${this.size.emit()}, ${this.minrad.emit()}, ${this.iterations.emit()} )`
}
},
Mandelbulb: {
parameters:[
{ name:'fold', type:'float', default:8, min:1, max:15 },
{ name:'iterations', type:'float', default:4, min:1, max:6, step:1 },
],
primitiveString( pName ) {
return `mandelbulb( ${pName}, ${this.fold.emit()}, ${this.iterations.emit()} )`
},
// adapted from: https://www.shadertoy.com/view/ltfSWn
glslify:glsl` float mandelbulb( in vec3 p, in float aa, float iterations ){
vec3 w = p;
float m = dot(w,w);
vec4 trap = vec4(abs(w),m);
float dz = 1.0;
for( int i=0; i<int(iterations); i++ ) {
dz = aa*pow(sqrt(m),aa - 1.)*dz + 1.0;
float r = length(w);
float b = aa*acos( w.y /r);
float a = aa*atan( w.x, w.z );
w = p + pow(r,aa) * vec3( sin(b)*sin(a), cos(b), sin(b)*cos(a) );
trap = min( trap, vec4(abs(w),m) );
m = dot(w,w);
if( m > 256.0 ) {
break;
}
}
return 0.25*log(m)*sqrt(m)/dz;
}
`,
},
// adapted from https://www.shadertoy.com/view/llGXDR
Mandelbox: {
parameters:[
{ name:'fold', type:'float', default:.1 },
{ name:'size', type:'float', default:3., min:1, max:10 },
{ name:'iterations', type:'float', default:5, min:1, max:10, step:1 },
],
glslify:`float mandelbox( float MR2, float SCALE, float ITER, vec3 position ){
vec4 scalevec = vec4(SCALE, SCALE, SCALE, abs(SCALE)) / MR2;
float C1 = abs(SCALE-1.0), C2 = pow(abs(SCALE), 1.-ITER); // 10 is ITERS
vec4 p = vec4(position.xyz, 1.0), p0 = vec4(position.xyz, 1.0); // p.w is knighty's DEfactor
for (int i=0; i<int(ITER); i++) {
p.xyz = clamp(p.xyz, -1.0, 1.0) * 2.0 - p.xyz; // box fold: min3, max3gg, mad3
float r2 = dot(p.xyz, p.xyz); // dp3
p.xyzw *= clamp(max(MR2/r2, MR2), 0.0, 1.0); // sphere fold: div1, max1.sat, mul4
p.xyzw = p*scalevec + p0; // mad4
}
return (length(p.xyz) - C1) / p.w - C2;
}`,
primitiveString( pName ) {
return `mandelbox( ${this.fold.emit()}, ${this.size.emit()}, ${this.iterations.emit()}, ${pName} )`
}
},
Octahedron: {
parameters:[
{ name:'radius', type:'float', default:1, min:0, max:4 },
],
primitiveString( pName ) {
return `sdOctahedron( ${pName}, ${this.radius.emit()} )`
},
glslify:` float sdOctahedron(vec3 p, float h) {
p.y = p.y + h; // center vertically... is it centered on the z-axis?
vec2 d = .5*(abs(p.xz)+p.y) - min(h,p.y);
return length(max(d,0.)) + min(max(d.x,d.y), 0.);
}`
},
Plane: {
parameters:[
{ name:'normal', type:'vec3', default:[0,1,0], min:0, max:1 },
{ name:'distance', type:'float', default:1, min:0, max:5 },
],
primitiveString( pName ) {
return `sdPlane( ${pName}, vec4( ${this.normal.emit()}, ${this.distance.emit()} ))`
},
glslify:glsl`#pragma glslify: sdPlane = require('glsl-sdf-primitives/sdPlane')`
},
Quad: {
parameters:[
{ name:'v1', type:'vec3', default:[-.5,-.5,0] },
{ name:'v2', type:'vec3', default:[.5,-.5,0] },
{ name:'v3', type:'vec3', default:[.5,.5,0] },
{ name:'v4', type:'vec3', default:[-.5,.5,0] },
],
primitiveString( pName ) {
return `udQuad( ${pName}, ${this.v1.emit()}, ${this.v2.emit()}, ${this.v3.emit()}, ${this.v4.emit()} )`
},
glslify:glsl` #pragma glslify: udQuad = require('glsl-sdf-primitives/udQuad')`
},
RoundBox: {
parameters:[
{ name:'size', type:'vec3', default:[1,1,1], min:0, max:3 },
{ name:'radius', type:'float', default:1, min:0, max:3 },
],
primitiveString( pName ) {
return `udRoundBox( ${pName}, ${this.size.emit()}, ${this.radius.emit()} )`
},
glslify:glsl` #pragma glslify: udRoundBox = require('glsl-sdf-primitives/udRoundBox')`
},
Sphere:{
parameters:[
{ name:'radius', type:'float', default:1, min:0, max:3 },
],
primitiveString( pName ) {
return `(length(${pName}) - ${this.radius.emit()})`
},
glslify:glsl` #pragma glslify: sdSphere = require('glsl-sdf-primitives/sdSphere' )`
},
// phi, m, n1, n2, n3, a, b
SuperFormula:{
parameters:[
{ name:'m_1', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'n1_1', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'n2_1', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'n3_1', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'a_1', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'b_1', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'m_2', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'n1_2', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'n2_2', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'n3_2', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'a_2', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
{ name:'b_2', type:'float', default:1, min:-Math.PI*4, max:Math.PI*4 },
],
primitiveString( pName ) {
return `superformula( ${pName}, ${this.m_1.emit()}, ${this.n1_1.emit()},${this.n2_1.emit()},${this.n3_1.emit()},${this.a_1.emit()},${this.b_1.emit()}, ${this.m_2.emit()}, ${this.n1_2.emit()},${this.n2_2.emit()},${this.n3_2.emit()},${this.a_2.emit()},${this.b_2.emit()} )`
},
glslify:glsl` #pragma glslify: SuperFormula = require( 'glsl-superformula' )
float superformula( vec3 p, float m_1, float n1_1, float n2_1, float n3_1, float a_1, float b_1, float m_2, float n1_2, float n2_2, float n3_2, float a_2, float b_2 ) {
float d = length( p );
float theta = atan(p.y, p.x);
float phi = d == 0. ? 0. : asin(p.z / d);
float r1 = SuperFormula( theta, m_1, n1_1, n2_1, n3_1, a_1, b_1 );
float r2 = SuperFormula( phi, m_2, n1_2, n2_2, n3_2, a_2, b_2 );
vec3 q = r2 * vec3(r1 * cos(theta) * cos(phi), r1 * sin(theta) * cos(phi), sin(phi));
d = d - length(q);
return d;
}
` },
Torus:{
parameters:[
{ name:'radii', type:'vec2', default:[.5,.1], min:0, max:3 },
],
primitiveString( pname ) {
return `sdTorus( ${pname}, ${this.radii.emit()} )`
},
glslify:glsl` #pragma glslify: sdTorus = require('glsl-sdf-primitives/sdTorus')`
},
Torus88:{
parameters:[
{ name:'radii', type:'vec2', default:[.5,.1], min:0, max:3 },
],
primitiveString( pname ) {
return `sdTorus88( ${pname}, ${this.radii.emit()} )`
},
glslify:`float sdTorus88( vec3 p, vec2 t ) {
vec2 q = vec2( length8( p.xz ) - t.x, p.y );
return length8( q ) - t.y;
}\n`,
},
Torus82:{
parameters:[
{ name:'radii', type:'vec2', default:[.5,.1], min:0, max:3 },
],
primitiveString( pname ) {
return `sdTorus82( ${pname}, ${this.radii.emit()} )`
},
glslify:`float sdTorus82( vec3 p, vec2 t ) {
vec2 q = vec2( length( p.xz ) - t.x, p.y );
return length8( q ) - t.y;
}\n`
},
Triangle: {
parameters:[
{ name:'v1', type:'vec3', default:[0,-.5,0] },
{ name:'v2', type:'vec3', default:[-.5,.0,0] },
{ name:'v3', type:'vec3', default:[.5,.0,0] },
],
primitiveString( pname ) {
return `udTriangle( ${pname}, ${this.v1.emit()}, ${this.v2.emit()}, ${this.v3.emit()} )`
},
glslify:glsl` #pragma glslify: udTriangle = require('glsl-sdf-primitives/udTriangle')`
},
TriPrism: {
parameters:[
{ name:'dimensions', type:'vec2', default:[.5,.5], min:0, max:3 },
],
primitiveString( pName ) {
return `sdTriPrism( ${pName}, ${this.dimensions.emit()})`
},
glslify:glsl` #pragma glslify: sdTriPrism = require('glsl-sdf-primitives/sdTriPrism')`
},
}