polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
358 lines (325 loc) • 13.4 kB
text/typescript
import {ArrayUtils} from '../../../core/ArrayUtils';
import {TypedGlNode} from './_Base';
import {
GlConnectionPoint,
GlConnectionPointComponentsCountMap,
GlConnectionPointType,
} from '../utils/io/connections/Gl';
// https://github.com/stegu/webgl-noise/
import NoiseCommon from './gl/noise/common.glsl';
// import cellular2D from './Gl/noise/cellular2D.glsl'
// import cellular2x2 from './Gl/noise/cellular2x2.glsl'
// import cellular2x2x2 from './Gl/noise/cellular2x2x2.glsl'
// import cellular3D from './Gl/noise/cellular3D.glsl'
import classicnoise2D from './gl/noise/classicnoise2D.glsl';
import classicnoise3D from './gl/noise/classicnoise3D.glsl';
import classicnoise4D from './gl/noise/classicnoise4D.glsl';
import noise2D from './gl/noise/noise2D.glsl';
import noise3D from './gl/noise/noise3D.glsl';
// import noise3Dgrad from './Gl/noise/noise3Dgrad.glsl'
import noise4D from './gl/noise/noise4D.glsl';
// import psrdnoise2D from './Gl/noise/psrdnoise2D.glsl'
export enum NoiseName {
// 'cellular2D',
// 'cellular2x2',
// 'cellular2x2x2',
// 'cellular3D',
CLASSIC_PERLIN_2D = 'Classic Perlin 2D',
// 'Classic Perlin 2D with periodic variant',
CLASSIC_PERLIN_3D = 'Classic Perlin 3D',
// 'Classic Perlin 3D with periodic variant',
CLASSIC_PERLIN_4D = 'Classic Perlin 4D',
// 'Classic Perlin 4D with periodic variant',
NOISE_2D = 'noise2D',
NOISE_3D = 'noise3D',
// 'noise3Dgrad',
NOISE_4D = 'noise4D',
// 'Periodic Simplex Rotating Derivative', // psrdnoise
// 'Periodic Simplex Derivative', // psdnoise
// 'Periodic Simplex Rotating', // psrnoise
// 'Periodic Simplex', // psnoise
// 'Simplex Rotating Derivating', // srdnoise
// 'Simplex Derivating', // sdnoise
// 'Simplex Rotating', // srnoise
// 'Simplex', // snoise
}
export const NOISE_NAMES: Array<NoiseName> = [
NoiseName.CLASSIC_PERLIN_2D,
NoiseName.CLASSIC_PERLIN_3D,
NoiseName.CLASSIC_PERLIN_4D,
NoiseName.NOISE_2D,
NoiseName.NOISE_3D,
NoiseName.NOISE_4D,
];
type StringByNoise = {[key in NoiseName]: string};
const IMPORT_BY_NOISE_NAME: StringByNoise = {
[NoiseName.CLASSIC_PERLIN_2D]: classicnoise2D,
[NoiseName.CLASSIC_PERLIN_3D]: classicnoise3D,
[NoiseName.CLASSIC_PERLIN_4D]: classicnoise4D,
[NoiseName.NOISE_2D]: noise2D,
[NoiseName.NOISE_3D]: noise3D,
[NoiseName.NOISE_4D]: noise4D,
};
type ConnectionTypeByNoise = {[key in NoiseName]: GlConnectionPointType};
const INPUT_TYPES_BY_NOISE_NAME: ConnectionTypeByNoise = {
[NoiseName.CLASSIC_PERLIN_2D]: GlConnectionPointType.VEC2,
[NoiseName.CLASSIC_PERLIN_3D]: GlConnectionPointType.VEC3,
[NoiseName.CLASSIC_PERLIN_4D]: GlConnectionPointType.VEC4,
[NoiseName.NOISE_2D]: GlConnectionPointType.VEC2,
[NoiseName.NOISE_3D]: GlConnectionPointType.VEC3,
[NoiseName.NOISE_4D]: GlConnectionPointType.VEC4,
};
const OUTPUT_TYPE_BY_NOISE_NAME: ConnectionTypeByNoise = {
[NoiseName.CLASSIC_PERLIN_2D]: GlConnectionPointType.FLOAT,
[NoiseName.CLASSIC_PERLIN_3D]: GlConnectionPointType.FLOAT,
[NoiseName.CLASSIC_PERLIN_4D]: GlConnectionPointType.FLOAT,
[NoiseName.NOISE_2D]: GlConnectionPointType.FLOAT,
[NoiseName.NOISE_3D]: GlConnectionPointType.FLOAT,
[NoiseName.NOISE_4D]: GlConnectionPointType.FLOAT,
};
const METHOD_NAMES_BY_NOISE_NAME: StringByNoise = {
[NoiseName.CLASSIC_PERLIN_2D]: 'cnoise',
[NoiseName.CLASSIC_PERLIN_3D]: 'cnoise',
[NoiseName.CLASSIC_PERLIN_4D]: 'cnoise',
[NoiseName.NOISE_2D]: 'snoise',
[NoiseName.NOISE_3D]: 'snoise',
[NoiseName.NOISE_4D]: 'snoise',
};
enum OUTPUT_TYPE {
NoChange = 0,
Float = 1,
Vec2 = 2,
Vec3 = 3,
Vec4 = 4,
}
const OUTPUT_TYPES: Array<OUTPUT_TYPE> = [
OUTPUT_TYPE.NoChange,
OUTPUT_TYPE.Float,
OUTPUT_TYPE.Vec2,
OUTPUT_TYPE.Vec3,
OUTPUT_TYPE.Vec4,
];
type StringByOutputType = {[key in OUTPUT_TYPE]: string};
const OUTPUT_TYPE_LABEL: StringByOutputType = {
[OUTPUT_TYPE.NoChange]: 'Same as noise',
[OUTPUT_TYPE.Float]: 'Float',
[OUTPUT_TYPE.Vec2]: 'Vec2',
[OUTPUT_TYPE.Vec3]: 'Vec3',
[OUTPUT_TYPE.Vec4]: 'Vec4',
};
type ConnectionTypeByOutputType = {[key in OUTPUT_TYPE]: GlConnectionPointType};
const CONNECTION_TYPE_BY_OUTPUT_TYPE: ConnectionTypeByOutputType = {
[OUTPUT_TYPE.NoChange]: GlConnectionPointType.FLOAT,
[OUTPUT_TYPE.Float]: GlConnectionPointType.FLOAT,
[OUTPUT_TYPE.Vec2]: GlConnectionPointType.VEC2,
[OUTPUT_TYPE.Vec3]: GlConnectionPointType.VEC3,
[OUTPUT_TYPE.Vec4]: GlConnectionPointType.VEC4,
};
const ALL_COMPONENTS = ['x', 'y', 'z', 'w'];
const OUTPUT_NAME = 'noise';
const default_noise_type = NOISE_NAMES.indexOf(NoiseName.NOISE_3D);
const default_output_type = OUTPUT_TYPE.NoChange;
const DefaultValues: PolyDictionary<number> = {
amp: 1,
freq: 1,
};
enum InputName {
AMP = 'amp',
POSITION = 'position',
FREQ = 'freq',
OFFSET = 'offset',
}
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {ShadersCollectionController} from './code/utils/ShadersCollectionController';
import {ThreeToGl} from '../../../core/ThreeToGl';
import {FunctionGLDefinition} from './utils/GLDefinition';
import {PolyDictionary} from '../../../types/GlobalTypes';
class NoiseGlParamsConfig extends NodeParamsConfig {
type = ParamConfig.INTEGER(default_noise_type, {
menu: {
entries: NOISE_NAMES.map((noise_name, i) => {
const noise_output_type = OUTPUT_TYPE_BY_NOISE_NAME[noise_name];
const name = `${noise_name} (output: ${noise_output_type})`;
return {name: name, value: i};
}),
},
});
outputType = ParamConfig.INTEGER(default_output_type, {
menu: {
entries: OUTPUT_TYPES.map((output_type) => {
const val = OUTPUT_TYPES[output_type];
const name = OUTPUT_TYPE_LABEL[val];
return {name: name, value: val};
}),
},
});
octaves = ParamConfig.INTEGER(3, {range: [1, 10], rangeLocked: [true, false]});
ampAttenuation = ParamConfig.FLOAT(0.5, {range: [0, 1]});
freqIncrease = ParamConfig.FLOAT(2, {range: [0, 10]});
separator = ParamConfig.SEPARATOR();
}
const ParamsConfig = new NoiseGlParamsConfig();
export class NoiseGlNode extends TypedGlNode<NoiseGlParamsConfig> {
params_config = ParamsConfig;
static type() {
return 'noise';
}
// public readonly gl_connections_controller: GlConnectionsController = new GlConnectionsController(this);
initializeNode() {
super.initializeNode();
this.io.connection_points.initializeNode();
this.io.connection_points.spare_params.set_inputless_param_names(['octaves', 'ampAttenuation', 'freqIncrease']);
this.io.outputs.setNamedOutputConnectionPoints([
new GlConnectionPoint(OUTPUT_NAME, GlConnectionPointType.FLOAT),
]);
this.io.connection_points.set_expected_input_types_function(this._expected_input_types.bind(this));
this.io.connection_points.set_expected_output_types_function(this._expected_output_types.bind(this));
this.io.connection_points.set_input_name_function(this._gl_input_name.bind(this));
this.io.connection_points.set_output_name_function(() => OUTPUT_NAME);
}
protected _gl_input_name(index: number) {
return [InputName.AMP, InputName.POSITION, InputName.FREQ, InputName.OFFSET][index];
}
param_default_value(name: string) {
return DefaultValues[name];
}
private _expected_input_types(): GlConnectionPointType[] {
const noise_name = NOISE_NAMES[this.pv.type];
const amplitude_type = this._expected_output_types()[0];
const type = INPUT_TYPES_BY_NOISE_NAME[noise_name];
return [amplitude_type, type, type, type];
}
private _expected_output_types(): GlConnectionPointType[] {
const noise_name = NOISE_NAMES[this.pv.type];
const output_type = OUTPUT_TYPES[this.pv.outputType];
if (output_type == OUTPUT_TYPE.NoChange) {
return [INPUT_TYPES_BY_NOISE_NAME[noise_name]];
} else {
return [CONNECTION_TYPE_BY_OUTPUT_TYPE[output_type]];
}
}
set_lines(shaders_collection_controller: ShadersCollectionController) {
const function_declaration_lines = [];
const body_lines = [];
const noise_name = NOISE_NAMES[this.pv.type];
const noise_function = IMPORT_BY_NOISE_NAME[noise_name];
const noise_output_gl_type = OUTPUT_TYPE_BY_NOISE_NAME[noise_name];
function_declaration_lines.push(new FunctionGLDefinition(this, NoiseCommon));
function_declaration_lines.push(new FunctionGLDefinition(this, noise_function));
function_declaration_lines.push(new FunctionGLDefinition(this, this.fbm_function()));
const output_gl_type = this._expected_output_types()[0];
// if the requested output type matches the noise signature
if (output_gl_type == noise_output_gl_type) {
const line = this.single_noise_line();
// body_lines.push( `${output_gl_type} ${noise} = ${amp}*${method_name}(${joined_args})` )
body_lines.push(line);
} else {
// if the requested output type does not match the noise signature
const requested_components_count = GlConnectionPointComponentsCountMap[output_gl_type];
// const noise_output_components_count = OUTPUT_TYPE_BY_NOISE_NAME[output_gl_type]
// if(requested_components_count < noise_output_components_count){
// // not sure we ever go through here with the current noise set
// let component = ArrayUtils.range(requested_components_count).map(i=>ALL_COMPONENTS[i]).join('')
// const line = this.single_noise_line('', component)
// body_lines.push(line)
// } else {
const lines_count_required = requested_components_count;
const assembly_args: string[] = [];
const noise = this.gl_var_name('noise');
for (let i = 0; i < lines_count_required; i++) {
const component = ALL_COMPONENTS[i];
assembly_args.push(`${noise}${component}`);
const input_type = INPUT_TYPES_BY_NOISE_NAME[noise_name];
// if (CoreType.isArray(input_constructor)) {
// TODO: for noise3Dgrad and other noises with 2 inputs
// } else {
const offset_gl_type = input_type;
const offset_components_count = GlConnectionPointComponentsCountMap[offset_gl_type];
const offset_values = ArrayUtils.range(offset_components_count)
.map((j) => ThreeToGl.float(1000 * i))
.join(', ');
const offset2 = `${offset_gl_type}(${offset_values})`;
const line = this.single_noise_line(component, component, offset2);
body_lines.push(line);
// }
}
const joined_args = assembly_args.join(', ');
const assembly_line = `vec${lines_count_required} ${noise} = vec${lines_count_required}(${joined_args})`;
body_lines.push(assembly_line);
// }
}
shaders_collection_controller.add_definitions(this, function_declaration_lines);
shaders_collection_controller.add_body_lines(this, body_lines);
}
private fbm_method_name() {
const noise_name = NOISE_NAMES[this.pv.type];
const method_name = METHOD_NAMES_BY_NOISE_NAME[noise_name];
return `fbm_${method_name}_${this.name()}`;
}
private fbm_function() {
const noise_name = NOISE_NAMES[this.pv.type];
const method_name = METHOD_NAMES_BY_NOISE_NAME[noise_name];
const input_type = INPUT_TYPES_BY_NOISE_NAME[noise_name];
return `
float ${this.fbm_method_name()} (in ${input_type} st) {
float value = 0.0;
float amplitude = 1.0;
for (int i = 0; i < ${ThreeToGl.int(this.pv.octaves)}; i++) {
value += amplitude * ${method_name}(st);
st *= ${ThreeToGl.float(this.pv.freqIncrease)};
amplitude *= ${ThreeToGl.float(this.pv.ampAttenuation)};
}
return value;
}
`;
}
private single_noise_line(output_name_suffix?: string, component?: string, offset2?: string) {
// const noise_name = NOISE_NAMES[this.pv.type];
// const method_name = METHOD_NAMES_BY_NOISE_NAME[noise_name]
const method_name = this.fbm_method_name();
const amp = ThreeToGl.any(this.variable_for_input(InputName.AMP));
const position = ThreeToGl.any(this.variable_for_input(InputName.POSITION));
const freq = ThreeToGl.any(this.variable_for_input(InputName.FREQ));
let offset = ThreeToGl.any(this.variable_for_input(InputName.OFFSET));
if (offset2) {
offset = `(${offset}+${offset2})`;
}
const args = [`(${position}*${freq})+${offset}`];
// we cannot use amp as is in all cases
// if the noise outputs a vec2 and the amp is vec3, we cannot simply do vec3*vec2
// therefore, in such a case, we must only take the required component of vec3
// examples:
// - noise is cellular 2D (outputs vec2) and requested output is float:
// nothing to do
// - noise is cellular 2D (outputs vec2) and requested output is vec2:
// nothing to do
// - noise is cellular 2D (outputs vec3) and requested output is vec2:
// we have:
// x = amp.x * vec2.x
// y = amp.y * vec2.y
// z = amp.z * 0
// output = vec3(x,y,z)
// add other args if required
// const input_type = INPUT_TYPES_BY_NOISE_NAME[noise_name];
// if (CoreType.isArray(input_constructor)) {
// const properties = lodash_clone(input_constructor);
// properties.shift(); // remove position
// properties.forEach((property) => {
// const arg_name = Object.keys(property)[0];
// const arg = ThreeToGl.any(this.variable_for_input(arg_name));
// args.push(arg);
// });
// }
const joined_args = args.join(', ');
// let output_type = OUTPUT_TYPE_BY_NOISE_NAME[noise_name]
const noise = this.gl_var_name(OUTPUT_NAME);
const right_hand = `${amp}*${method_name}(${joined_args})`;
if (component) {
return `float ${noise}${output_name_suffix} = (${right_hand}).${component}`;
} else {
// it looks like we never go here with the current set of noises
const output_type = this.io.outputs.named_output_connection_points[0].type();
return `${output_type} ${noise} = ${right_hand}`;
}
}
}