import {MathUtils} from "three";
import {safeString} from "../utils.js";
/**
* @class
* @abstract
*
* @classdesc
* Generic class for Shape, Material, Solid, etc.
* Factorize ID Management and other stuff.
*/
export class Generic {
/**
* Constructor.
* The constructor takes no argument.
*/
constructor() {
/**
* Universal unique ID.
* The dashes are replaced by underscored to avoid problems in the shaders
* @type {string}
* @readonly
*/
this.uuid = MathUtils.generateUUID().replaceAll('-', '_');
/**
* ID of the object in the scene
* This number is computed automatically when the object is added to the scene.
* It should not be changed
* @type{number}
* @readonly
*/
this.id = undefined;
/**
* A list of GLSL code chunks that are needed for this shape (and could also be reused somewhere else).
* Default is the empty list.
* @type {string[]}
*/
this.imports = [];
}
/**
* The name of the class.
* Useful to generate the name of items
* @return {string}
*/
get className() {
return this.constructor.name;
}
/**
* The name of the item.
* This name is computed (from the uuid) the first time the getter is called.
* @type {string}
*/
get name() {
if (this._name === undefined) {
this._name = `${safeString(this.className)}_${this.uuid}`;
}
return this._name;
}
/**
* Return the type under which the data is passed as uniform.
* Return the empty string, if the data should not be passed as a uniform.
* @return {string}
*/
get uniformType() {
return '';
}
/**
* Set the ID of the shape.
* Propagate the process if needed
* @param {Scene} scene - the scene to which the object is added.
*/
setId(scene) {
this.id = scene.nextId;
scene.nextId = scene.nextId + 1;
}
/**
* Additional actions to perform when the object is added to the scene.
* By default, do nothing.
* @param {Scene} scene - the scene to which the object is added.
*/
onAdd(scene) {
}
/**
* Extend the list of imports
* @param {...string} imports - the imports to add
*/
addImport(imports) {
for (const imp of arguments) {
this.imports.push(imp);
}
}
/**
* Return the chunk of GLSL code defining the corresponding structure,
* And eventually basic functions associated to the structure.
* @abstract
* @return {string}
*/
static glslClass() {
throw new Error('Generic: this function should be implemented');
}
/**
* Compile all the function directly related to the instance of the class (e.g. sdf, gradient, direction field, etc).
* @return {string}
*/
glslInstance() {
throw new Error('Generic: this function should be implemented');
}
/**
* Extend the given shader with the appropriate code
* @param {ShaderBuilder} shaderBuilder - the shader builder
*/
shader(shaderBuilder) {
// add all the imports for this instance
for (const imp of this.imports) {
shaderBuilder.addImport(imp);
}
// add the struct dependencies (which only depends on the class and not the instance)
shaderBuilder.addClass(this.constructor.name, this.constructor.glslClass());
// if needed declare the uniform for this instance
if (this.uniformType !== '') {
shaderBuilder.addUniform(this.name, this.uniformType, this);
}
// add the logic specific to this instance
shaderBuilder.addInstance(this.name, this.glslInstance());
}
}