@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
426 lines (425 loc) • 17.1 kB
JavaScript
"use strict";
import { Vector2, Vector3, Vector4 } from "three";
import { TypedSopNode } from "./_Base";
import { Attribute } from "../../../core/geometry/Attribute";
import { CoreMath } from "../../../core/math/_Module";
import { InputCloneMode } from "../../poly/InputCloneMode";
import { TypeAssert } from "../../poly/Assert";
import { SimplexNoise } from "three/examples/jsm/math/SimplexNoise";
import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig";
import { isString, isNumber } from "../../../core/Type";
import { isBooleanTrue } from "../../../core/BooleanValue";
import { AttribType } from "../../../core/geometry/Constant";
import { SopType } from "../../poly/registers/nodes/types/Sop";
export var NoiseOperation = /* @__PURE__ */ ((NoiseOperation2) => {
NoiseOperation2["ADD"] = "add";
NoiseOperation2["SET"] = "set";
NoiseOperation2["MULT"] = "mult";
NoiseOperation2["SUBTRACT"] = "subtract";
NoiseOperation2["DIVIDE"] = "divide";
return NoiseOperation2;
})(NoiseOperation || {});
const OPERATIONS = [
"add" /* ADD */,
"set" /* SET */,
"mult" /* MULT */,
"subtract" /* SUBTRACT */,
"divide" /* DIVIDE */
];
const position = new Vector3();
const normal = new Vector3();
const _destPoints = [];
const _restPos = new Vector3();
const _restValue2 = new Vector2();
const _restValue4 = new Vector4();
const _noiseValueV = new Vector3();
let _currentAttribValueF = 0;
const _currentAttribValueV2 = new Vector2();
const _currentAttribValueV3 = new Vector3();
const _currentAttribValueV4 = new Vector4();
class NoiseSopParamsConfig extends NodeParamsConfig {
constructor() {
super(...arguments);
/** @param noise amplitude */
this.amplitude = ParamConfig.FLOAT(1);
/** @param toggle on to multiply the amplitude by a vertex attribute */
this.tamplitudeAttrib = ParamConfig.BOOLEAN(0);
/** @param which vertex attribute to use */
this.amplitudeAttrib = ParamConfig.STRING("amp", { visibleIf: { tamplitudeAttrib: true } });
/** @param noise frequency */
this.freq = ParamConfig.VECTOR3([1, 1, 1]);
/** @param noise offset */
this.offset = ParamConfig.VECTOR3([0, 0, 0]);
/** @param noise octaves */
this.octaves = ParamConfig.INTEGER(3, {
range: [1, 8],
rangeLocked: [true, false]
});
/** @param amplitude attenuation for higher octaves */
this.ampAttenuation = ParamConfig.FLOAT(0.5, { range: [0, 1] });
/** @param frequency increase for higher octaves */
this.freqIncrease = ParamConfig.FLOAT(2, { range: [0, 10] });
/** @param noise seed */
this.seed = ParamConfig.INTEGER(0, {
range: [0, 100],
separatorAfter: true
});
/** @param toggle on to have the noise be multiplied by the normal */
this.useNormals = ParamConfig.BOOLEAN(0);
/** @param set which attribute will be affected by the noise */
this.attribName = ParamConfig.STRING("position");
/** @param toggle on to use rest attributes. This can be useful when the noise is animated and this node does not clone the input geometry. Without using rest attributes, the noise would be based on an already modified position, and would therefore accumulate on itself after each cook. This may be what you are after, but for a more conventional result, using a rest attribute will ensure that the noise remains stable. Note that the rest attribute can be created by a RestAttributes node */
this.useRestAttributes = ParamConfig.BOOLEAN(0);
/** @param name of rest position */
this.restP = ParamConfig.STRING("restP", { visibleIf: { useRestAttributes: true } });
/** @param name of rest normal */
this.restN = ParamConfig.STRING("restN", { visibleIf: { useRestAttributes: true } });
/** @param operation done when applying the noise (add, set, mult, subtract, divide) */
this.operation = ParamConfig.INTEGER(OPERATIONS.indexOf("add" /* ADD */), {
menu: {
entries: OPERATIONS.map((operation) => {
return {
name: operation,
value: OPERATIONS.indexOf(operation)
};
})
}
});
/** @param toggle on to recompute normals if the position has been updated */
this.computeNormals = ParamConfig.BOOLEAN(1);
}
}
const ParamsConfig = new NoiseSopParamsConfig();
export class NoiseSopNode extends TypedSopNode {
constructor() {
super(...arguments);
this.paramsConfig = ParamsConfig;
this._simplexBySeed = /* @__PURE__ */ new Map();
}
static type() {
return SopType.NOISE;
}
initializeNode() {
this.io.inputs.setCount(1);
this.io.inputs.initInputsClonedState([InputCloneMode.FROM_NODE]);
}
setOperation(operation) {
this.p.operation.set(OPERATIONS.indexOf(operation));
}
cook(inputCoreGroups) {
const coreGroup = inputCoreGroups[0];
coreGroup.points(_destPoints);
const destAttribName = this.pv.attribName;
if (!coreGroup.hasPointAttrib(destAttribName)) {
this.states.error.set(`attribute ${destAttribName} not found`);
this.cookController.endCook();
return;
}
const attribType = coreGroup.pointAttribType(destAttribName);
if (attribType != AttribType.NUMERIC) {
this.states.error.set(`attribute ${destAttribName} is not a numeric attribute`);
this.cookController.endCook();
return;
}
const targetAttribSize = coreGroup.pointAttribSize(this.pv.attribName);
const firstPt = _destPoints[0];
if (!firstPt) {
this.setCoreGroup(coreGroup);
return;
}
const currentAttribValue = firstPt.attribValue(destAttribName);
if (isString(currentAttribValue)) {
this.states.error.set("cannot add noise to a string attribute");
return;
}
const fbmParams = {
octaves: this.pv.octaves,
ampAttenuation: this.pv.ampAttenuation,
freqIncrease: this.pv.freqIncrease
};
try {
switch (targetAttribSize) {
case 1: {
this._cookForFloat(_destPoints, fbmParams);
break;
}
case 2: {
this._cookForV2(_destPoints, fbmParams);
break;
}
case 3: {
this._cookForV3(_destPoints, fbmParams);
break;
}
case 4: {
this._cookForV4(_destPoints, fbmParams);
break;
}
}
} catch (err) {
console.error("sop/noise error", err);
this.states.error.set(`cook failed for (${this.path()}). make sure the required attributes are present`);
}
if (!this.io.inputs.cloneRequired(0)) {
for (const geometry of coreGroup.geometries()) {
geometry.getAttribute(destAttribName).needsUpdate = true;
}
}
if (isBooleanTrue(this.pv.computeNormals)) {
const objects = coreGroup.threejsObjectsWithGeo();
for (const object of objects) {
object.geometry.computeVertexNormals();
}
}
this.setCoreGroup(coreGroup);
}
_cookForFloat(destPoints, fbmParams) {
const simplex = this._getSimplex();
const useRestAttributes = isBooleanTrue(this.pv.useRestAttributes);
const useNormals = isBooleanTrue(this.pv.useNormals);
const tamplitudeAttrib = isBooleanTrue(this.pv.tamplitudeAttrib);
const baseAmplitude = this.pv.amplitude;
const operation = OPERATIONS[this.pv.operation];
const attribName = this.pv.attribName;
for (const destPoint of destPoints) {
if (useRestAttributes) {
destPoint.attribValueVector3(this.pv.restP, position);
if (useNormals) {
destPoint.attribValueVector3(this.pv.restN, normal);
}
_currentAttribValueF = position.x;
} else {
destPoint.position(position);
if (useNormals) {
destPoint.attribValueVector3(Attribute.NORMAL, normal);
}
_currentAttribValueF = destPoint.attribValueNumber(attribName);
}
const amplitude = tamplitudeAttrib ? this._amplitudeFromAttrib(destPoint, baseAmplitude) : baseAmplitude;
const noiseResult = this._noiseValue(useNormals, simplex, amplitude, fbmParams, position, normal);
const noiseValue = noiseResult.x;
const newAttribValueF = NoiseSopNode._newAttribValueFromFloat(operation, _currentAttribValueF, noiseValue);
destPoint.setAttribValueFromNumber(attribName, newAttribValueF);
}
}
_cookForV2(destPoints, fbmParams) {
const simplex = this._getSimplex();
const useRestAttributes = isBooleanTrue(this.pv.useRestAttributes);
const useNormals = isBooleanTrue(this.pv.useNormals);
const tamplitudeAttrib = isBooleanTrue(this.pv.tamplitudeAttrib);
const baseAmplitude = this.pv.amplitude;
const operation = OPERATIONS[this.pv.operation];
const attribName = this.pv.attribName;
for (const destPoint of destPoints) {
if (useRestAttributes) {
destPoint.attribValueVector3(this.pv.restP, position);
if (useNormals) {
destPoint.attribValueVector3(this.pv.restN, normal);
}
_currentAttribValueV2.set(position.x, position.y);
} else {
destPoint.position(position);
if (useNormals) {
destPoint.attribValueVector3(Attribute.NORMAL, normal);
}
destPoint.attribValueVector2(attribName, _currentAttribValueV2);
}
const amplitude = tamplitudeAttrib ? this._amplitudeFromAttrib(destPoint, baseAmplitude) : baseAmplitude;
const noiseResult = this._noiseValue(useNormals, simplex, amplitude, fbmParams, position, normal);
_restValue2.set(noiseResult.x, noiseResult.y);
const noiseValue = _restValue2;
const newAttribValueV = NoiseSopNode._newAttribValueFromVector2(
operation,
_currentAttribValueV2,
noiseValue
);
destPoint.setAttribValueFromVector2(attribName, newAttribValueV);
}
}
_cookForV3(destPoints, fbmParams) {
const simplex = this._getSimplex();
const useRestAttributes = isBooleanTrue(this.pv.useRestAttributes);
const useNormals = isBooleanTrue(this.pv.useNormals);
const tamplitudeAttrib = isBooleanTrue(this.pv.tamplitudeAttrib);
const baseAmplitude = this.pv.amplitude;
const operation = OPERATIONS[this.pv.operation];
const attribName = this.pv.attribName;
for (const destPoint of destPoints) {
if (useRestAttributes) {
destPoint.attribValueVector3(this.pv.restP, position);
if (useNormals) {
destPoint.attribValueVector3(this.pv.restN, normal);
}
_currentAttribValueV3.copy(position);
} else {
destPoint.position(position);
if (useNormals) {
destPoint.attribValueVector3(Attribute.NORMAL, normal);
}
destPoint.attribValueVector3(attribName, _currentAttribValueV3);
}
const amplitude = tamplitudeAttrib ? this._amplitudeFromAttrib(destPoint, baseAmplitude) : baseAmplitude;
const noiseResult = this._noiseValue(useNormals, simplex, amplitude, fbmParams, position, normal);
const noiseValue = noiseResult;
const newAttribValueV = NoiseSopNode._newAttribValueFromVector3(
operation,
_currentAttribValueV3,
noiseValue
);
destPoint.setAttribValueFromVector3(attribName, newAttribValueV);
}
}
_cookForV4(destPoints, fbmParams) {
const simplex = this._getSimplex();
const useRestAttributes = isBooleanTrue(this.pv.useRestAttributes);
const useNormals = isBooleanTrue(this.pv.useNormals);
const tamplitudeAttrib = isBooleanTrue(this.pv.tamplitudeAttrib);
const baseAmplitude = this.pv.amplitude;
const operation = OPERATIONS[this.pv.operation];
const attribName = this.pv.attribName;
for (const destPoint of destPoints) {
if (useRestAttributes) {
destPoint.attribValueVector3(this.pv.restP, position);
if (useNormals) {
destPoint.attribValueVector3(this.pv.restN, normal);
}
_currentAttribValueV4.set(position.x, position.y, position.z, 0);
} else {
destPoint.position(position);
if (useNormals) {
destPoint.attribValueVector3(Attribute.NORMAL, normal);
}
destPoint.attribValueVector4(attribName, _currentAttribValueV4);
}
const amplitude = tamplitudeAttrib ? this._amplitudeFromAttrib(destPoint, baseAmplitude) : baseAmplitude;
const noiseResult = this._noiseValue(useNormals, simplex, amplitude, fbmParams, position, normal);
_restValue4.set(noiseResult.x, noiseResult.y, noiseResult.z, 0);
const noiseValue = _restValue4;
const newAttribValueV = NoiseSopNode._newAttribValueFromVector4(
operation,
_currentAttribValueV4,
noiseValue
);
destPoint.setAttribValueFromVector4(attribName, newAttribValueV);
}
}
_noiseValue(useNormals, simplex, amplitude, fmbParams, position2, normal2) {
_restPos.copy(position2).add(this.pv.offset).multiply(this.pv.freq);
if (useNormals && normal2) {
const noise = amplitude * this._fbm(simplex, fmbParams, _restPos.x, _restPos.y, _restPos.z);
_noiseValueV.copy(normal2);
return _noiseValueV.multiplyScalar(noise);
} else {
_noiseValueV.set(
amplitude * this._fbm(simplex, fmbParams, _restPos.x + 545, _restPos.y + 125454, _restPos.z + 2142),
amplitude * this._fbm(simplex, fmbParams, _restPos.x - 425, _restPos.y - 25746, _restPos.z + 95242),
amplitude * this._fbm(simplex, fmbParams, _restPos.x + 765132, _restPos.y + 21, _restPos.z - 9245)
);
return _noiseValueV;
}
}
static _newAttribValueFromFloat(operation, current_attrib_value, noise_value) {
switch (operation) {
case "add" /* ADD */:
return current_attrib_value + noise_value;
case "set" /* SET */:
return noise_value;
case "mult" /* MULT */:
return current_attrib_value * noise_value;
case "divide" /* DIVIDE */:
return current_attrib_value / noise_value;
case "subtract" /* SUBTRACT */:
return current_attrib_value - noise_value;
}
TypeAssert.unreachable(operation);
}
static _newAttribValueFromVector2(operation, current_attrib_value, noise_value) {
switch (operation) {
case "add" /* ADD */:
return current_attrib_value.add(noise_value);
case "set" /* SET */:
return noise_value;
case "mult" /* MULT */:
return current_attrib_value.multiply(noise_value);
case "divide" /* DIVIDE */:
return current_attrib_value.divide(noise_value);
case "subtract" /* SUBTRACT */:
return current_attrib_value.sub(noise_value);
}
TypeAssert.unreachable(operation);
}
static _newAttribValueFromVector3(operation, current_attrib_value, noise_value) {
switch (operation) {
case "add" /* ADD */:
return current_attrib_value.add(noise_value);
case "set" /* SET */:
return noise_value;
case "mult" /* MULT */:
return current_attrib_value.multiply(noise_value);
case "divide" /* DIVIDE */:
return current_attrib_value.divide(noise_value);
case "subtract" /* SUBTRACT */:
return current_attrib_value.sub(noise_value);
}
TypeAssert.unreachable(operation);
}
static _newAttribValueFromVector4(operation, current_attrib_value, noise_value) {
switch (operation) {
case "add" /* ADD */:
return current_attrib_value.add(noise_value);
case "set" /* SET */:
return noise_value;
case "mult" /* MULT */:
return current_attrib_value.multiplyScalar(noise_value.x);
case "divide" /* DIVIDE */:
return current_attrib_value.divideScalar(noise_value.x);
case "subtract" /* SUBTRACT */:
return current_attrib_value.sub(noise_value);
}
TypeAssert.unreachable(operation);
}
_amplitudeFromAttrib(point, base_amplitude) {
const attrib_value = point.attribValue(this.pv.amplitudeAttrib);
if (isNumber(attrib_value)) {
return attrib_value * base_amplitude;
} else {
if (attrib_value instanceof Vector2 || attrib_value instanceof Vector3 || attrib_value instanceof Vector4) {
return attrib_value.x * base_amplitude;
}
}
return 1;
}
_fbm(simplex, params, x, y, z) {
let value = 0;
let amplitude = 1;
for (let i = 0; i < params.octaves; i++) {
value += amplitude * simplex.noise3d(x, y, z);
x *= params.freqIncrease;
y *= params.freqIncrease;
z *= params.freqIncrease;
amplitude *= params.ampAttenuation;
}
return value;
}
_getSimplex() {
const simplex = this._simplexBySeed.get(this.pv.seed);
if (simplex) {
return simplex;
} else {
const simplex2 = this._createSimplex();
this._simplexBySeed.set(this.pv.seed, simplex2);
return simplex2;
}
}
_createSimplex() {
const seed = this.pv.seed;
const random_generator = {
random: function() {
return CoreMath.randFloat(seed);
}
};
const simplex = new SimplexNoise(random_generator);
this._simplexBySeed.delete(seed);
return simplex;
}
}