@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
231 lines (211 loc) • 8.13 kB
text/typescript
/**
* computes the gradient of an SDF function
*
* @remarks
*
* The SDF function can be generated from nodes created inside this node.
*
*
* The computation is based on [https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm](https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm)
*/
import {TypedSubnetGlNode, TypedSubnetGlParamsConfigMixin} from './Subnet';
import {GlConnectionPointType, GL_CONNECTION_POINT_TYPES} from '../utils/io/connections/Gl';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {ShadersCollectionController} from './code/utils/ShadersCollectionController';
import {ThreeToGl} from '../../../core/ThreeToGl';
import {FunctionGLDefinition} from './utils/GLDefinition';
import {LineType} from './code/utils/LineType';
import {SubnetOutputGlNode} from './SubnetOutput';
import {SubnetInputGlNode} from './SubnetInput';
import {GlType} from '../../poly/registers/nodes/types/Gl';
const POSITION_INPUT_NAME = 'position';
const GRADIENT_OUTPUT_NAME = 'gradient';
const SDF_OUTPUT_NAME = 'sdf';
const GRADIENT_FUNCTION_NAME = 'gradientFunction';
const SDF_FUNCTION_NAME = 'sdfFunction';
interface GradientFunctionOptions {
epsilon: number;
gradientFunctionName: string;
sdfFunctionName: string;
additionalArguments: string[];
}
interface FunctionArguments {
definition: string[];
call: string[];
}
interface FunctionOptions {
name: {
sdf: string;
gradient: string;
};
arguments: FunctionArguments;
}
function gradientFunction(options: GradientFunctionOptions) {
const {sdfFunctionName, gradientFunctionName, epsilon, additionalArguments} = options;
// we remove position, since it's already there
additionalArguments.shift();
const otherArgsDefinition = additionalArguments.length > 0 ? `, ${additionalArguments.join(', ')}` : '';
const otherArgsWithoutGlType = additionalArguments.map((arg) => {
const elements = arg.split(' ');
return elements[elements.length - 1];
});
const otherArgsCall = otherArgsWithoutGlType.length > 0 ? `, ${otherArgsWithoutGlType.join(', ')}` : '';
const functionDefinition = `
vec3 ${gradientFunctionName}( in vec3 p${otherArgsDefinition} )
{
const float eps = ${epsilon};
const vec2 h = vec2(eps,0);
return normalize(
vec3(
${sdfFunctionName}(p+h.xyy${otherArgsCall}) - ${sdfFunctionName}(p-h.xyy${otherArgsCall}),
${sdfFunctionName}(p+h.yxy${otherArgsCall}) - ${sdfFunctionName}(p-h.yxy${otherArgsCall}),
${sdfFunctionName}(p+h.yyx${otherArgsCall}) - ${sdfFunctionName}(p-h.yyx${otherArgsCall})
)
);
}
`;
return functionDefinition;
}
class SDFGradientGlParamsConfig extends TypedSubnetGlParamsConfigMixin(NodeParamsConfig) {
epsilon = ParamConfig.FLOAT(0.0001, {
range: [0.000000001, 1],
rangeLocked: [false, false],
});
}
const ParamsConfig = new SDFGradientGlParamsConfig();
export class SDFGradientGlNode extends TypedSubnetGlNode<SDFGradientGlParamsConfig> {
override paramsConfig = ParamsConfig;
static override type(): Readonly<GlType.SDF_GRADIENT> {
return GlType.SDF_GRADIENT;
}
protected override _expectedInputsCount() {
return super._expectedInputsCount() + 1;
}
protected override _expectedInputTypes(): GlConnectionPointType[] {
return [GlConnectionPointType.VEC3].concat(super._expectedInputTypes());
}
protected override _expectedOutputTypes() {
return [GlConnectionPointType.FLOAT, GlConnectionPointType.VEC3];
}
protected override _expectedInputName(index: number) {
if (index == 0) {
return POSITION_INPUT_NAME;
} else {
return super._expectedInputName(index - 1);
}
}
protected override _expectedOutputName(index: number) {
return [SDF_OUTPUT_NAME, GRADIENT_OUTPUT_NAME][index];
}
override childExpectedOutputConnectionPointTypes() {
return [GlConnectionPointType.FLOAT];
}
override childExpectedOutputConnectionPointName(index: number) {
return SDF_OUTPUT_NAME;
}
//
//
// set_lines
//
//
override setSubnetInputLines(
shadersCollectionController: ShadersCollectionController,
childNode: SubnetInputGlNode
) {
const bodyLines: string[] = [];
// add position, always
const position = childNode.glVarName(POSITION_INPUT_NAME);
bodyLines.push(` vec3 ${position} = ${POSITION_INPUT_NAME}`);
// add subsequent inputs
const otherInputsCount = this.pv.inputsCount;
for (let i = 0; i < otherInputsCount; i++) {
const inputName = this._inputNameParams()[i].value;
const varName = childNode.glVarName(inputName);
const glType = GL_CONNECTION_POINT_TYPES[this._inputTypeParams()[i].value];
bodyLines.push(` ${glType} ${varName} = ${inputName}`);
}
shadersCollectionController.addBodyLines(childNode, bodyLines);
}
override setSubnetOutputLines(
shadersCollectionController: ShadersCollectionController,
childNode: SubnetOutputGlNode
) {
const connections = childNode.io.connections.inputConnections();
const connection = connections ? connections[0] : null;
if (!connection) {
shadersCollectionController.addBodyLines(childNode, [`return 0.0`]);
return;
}
const connectionPoint = connection.destConnectionPoint();
if (!connectionPoint) {
return;
}
const inValue = ThreeToGl.any(childNode.variableForInput(connectionPoint.name()));
const bodyLine = `return ${inValue}`;
shadersCollectionController.addBodyLines(childNode, [bodyLine]);
}
override setLines(shadersCollectionController: ShadersCollectionController) {
const functionArguments: FunctionArguments = {
definition: [],
call: [],
};
// add position, always
const position = ThreeToGl.any(this.variableForInput(POSITION_INPUT_NAME));
functionArguments.definition.push(`vec3 ${POSITION_INPUT_NAME}`);
functionArguments.call.push(position);
// add subsequent inputs
const otherInputsCount = this.pv.inputsCount;
for (let i = 0; i < otherInputsCount; i++) {
const inputName = this._inputNameParams()[i].value;
const glType = GL_CONNECTION_POINT_TYPES[this._inputTypeParams()[i].value];
const inputVal = ThreeToGl.any(this.variableForInput(inputName));
functionArguments.definition.push(`${glType} ${inputName}`);
functionArguments.call.push(inputVal);
}
const options: FunctionOptions = {
name: {
sdf: this.glVarName(SDF_FUNCTION_NAME),
gradient: this.glVarName(GRADIENT_FUNCTION_NAME),
},
arguments: functionArguments,
};
this._declareFunctions(shadersCollectionController, options);
this._callFunctions(shadersCollectionController, options);
}
private _declareFunctions(shadersCollectionController: ShadersCollectionController, options: FunctionOptions) {
const codeBuilder = this._runCodeBuilder(shadersCollectionController);
if (!codeBuilder) {
return;
}
const shadername = shadersCollectionController.currentShaderName();
const bodyLines = codeBuilder.lines(shadername, LineType.BODY);
const sdfFunctionLines: string = [
`float ${options.name.sdf}(${options.arguments.definition.join(', ')}){`,
...this._sanitizeBodyLines(bodyLines),
`}`,
].join('\n');
const gradientFunctionLines = gradientFunction({
epsilon: this.pv.epsilon,
sdfFunctionName: options.name.sdf,
gradientFunctionName: options.name.gradient,
additionalArguments: options.arguments.definition,
});
shadersCollectionController.addDefinitions(this, [
new FunctionGLDefinition(this, sdfFunctionLines),
new FunctionGLDefinition(this, gradientFunctionLines),
]);
}
private _callFunctions(shadersCollectionController: ShadersCollectionController, options: FunctionOptions) {
const used_output_names = this.io.outputs.used_output_names();
const bodyLines: string[] = [];
if (used_output_names.indexOf(SDF_OUTPUT_NAME) >= 0) {
const varName = this.glVarName(SDF_OUTPUT_NAME);
bodyLines.push(`float ${varName} = ${options.name.sdf}(${options.arguments.call.join(', ')})`);
}
if (used_output_names.indexOf(GRADIENT_OUTPUT_NAME) >= 0) {
const varName = this.glVarName(GRADIENT_OUTPUT_NAME);
bodyLines.push(`vec3 ${varName} = ${options.name.gradient}(${options.arguments.call.join(', ')})`);
}
shadersCollectionController.addBodyLines(this, bodyLines);
}
}