@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
320 lines (288 loc) • 9.86 kB
text/typescript
/**
* Set a vertex color attribute
*
* @remarks
* Note that just like the attrib_create, it is possible to use an expression to set the attribute value
*
*/
import {BufferAttribute, Mesh, BufferGeometry, Color, TypedArray} from 'three';
import {CoreColor} from '../../../core/Color';
import {TypedSopNode} from './_Base';
import {BaseCorePoint, CorePoint} from '../../../core/geometry/entities/point/CorePoint';
import {CoreGroup} from '../../../core/geometry/Group';
import {InputCloneMode} from '../../poly/InputCloneMode';
const DEFAULT_COLOR = new Color(1, 1, 1);
const COLOR_ATTRIB_NAME = 'color';
const _points: CorePoint<CoreObjectType>[] = [];
type ValueArrayByName = PolyDictionary<number[]>;
interface ArrayByGeometryUUID {
R: ValueArrayByName;
G: ValueArrayByName;
B: ValueArrayByName;
}
import {ColorSopOperation} from '../../operations/sop/Color';
import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig';
import {PolyDictionary} from '../../../types/GlobalTypes';
import {isBooleanTrue} from '../../../core/BooleanValue';
import {SopType} from '../../poly/registers/nodes/types/Sop';
import {pointsFromObject} from '../../../core/geometry/entities/point/CorePointUtils';
import {CoreObjectType, ObjectContent, isObject3D} from '../../../core/geometry/ObjectContent';
import {corePointClassFactory} from '../../../core/geometry/CoreObjectFactory';
const DEFAULT = ColorSopOperation.DEFAULT_PARAMS;
class ColorSopParamsConfig extends NodeParamsConfig {
/** @param toggle on if the color should be copied from another attribute */
fromAttribute = ParamConfig.BOOLEAN(DEFAULT.fromAttribute);
/** @param attribute name to copy value from */
attribName = ParamConfig.STRING(DEFAULT.attribName, {
visibleIf: {fromAttribute: 1},
});
/** @param color valu */
color = ParamConfig.COLOR(DEFAULT.color, {
visibleIf: {fromAttribute: 0},
expression: {forEntities: true},
});
/** @param toggle on if the value should be set with hsv values rather than rgb */
asHsv = ParamConfig.BOOLEAN(DEFAULT.asHsv, {
visibleIf: {fromAttribute: 0},
});
}
const ParamsConfig = new ColorSopParamsConfig();
export class ColorSopNode extends TypedSopNode<ColorSopParamsConfig> {
override paramsConfig = ParamsConfig;
static override type() {
return SopType.COLOR;
}
private _arrayByGeometryUUID: ArrayByGeometryUUID = {
R: {},
G: {},
B: {},
};
override initializeNode() {
this.io.inputs.setCount(1);
this.io.inputs.initInputsClonedState(InputCloneMode.FROM_NODE);
}
override async cook(inputCoreGroups: CoreGroup[]) {
const coreGroup = inputCoreGroups[0];
const objects = coreGroup.allObjects();
for (const object of objects) {
if (isBooleanTrue(this.pv.fromAttribute)) {
this._setFromAttribute(object);
} else {
const hasExpression = this.p.color.hasExpression();
if (hasExpression) {
await this._evalExpressions(object);
} else {
this._evalSimpleValues(object);
}
}
}
// needs update required for when no cloning
if (!this.io.inputs.cloneRequired(0)) {
const geometries = coreGroup.geometries();
for (const geometry of geometries) {
(geometry.getAttribute(COLOR_ATTRIB_NAME) as BufferAttribute).needsUpdate = true;
}
}
this.setCoreGroup(coreGroup);
}
private _setFromAttribute<T extends CoreObjectType>(object: ObjectContent<T>) {
const corePointClass = corePointClassFactory(object);
const attribName = this.pv.attribName;
if (attribName.trim().length == 0) {
return;
}
const srcAttrib = corePointClass.attribute(object, attribName) as BufferAttribute | undefined;
if (!srcAttrib) {
return;
}
this._createInitColor(object);
pointsFromObject(object, _points);
const pointsCount = _points.length;
const srcAttribSize = corePointClass.attribSize(object, attribName);
const srcArray = srcAttrib.array;
const destArray = (corePointClass.attribute(object, COLOR_ATTRIB_NAME) as BufferAttribute).array;
switch (srcAttribSize) {
case 1: {
for (let i = 0; i < pointsCount; i++) {
const dest_i = i * 3;
destArray[dest_i + 0] = srcArray[i];
destArray[dest_i + 1] = 1 - srcArray[i];
destArray[dest_i + 2] = 0;
}
break;
}
case 2: {
for (let i = 0; i < pointsCount; i++) {
const dest_i = i * 3;
const src_i = i * 2;
destArray[dest_i + 0] = srcArray[src_i + 0];
destArray[dest_i + 1] = srcArray[src_i + 1];
destArray[dest_i + 2] = 0;
}
break;
}
case 3: {
for (let i = 0; i < srcArray.length; i++) {
destArray[i] = srcArray[i];
}
break;
}
case 4: {
for (let i = 0; i < pointsCount; i++) {
const dest_i = i * 3;
const src_i = i * 4;
destArray[dest_i + 0] = srcArray[src_i + 0];
destArray[dest_i + 1] = srcArray[src_i + 1];
destArray[dest_i + 2] = srcArray[src_i + 2];
}
break;
}
}
}
private _createInitColor<T extends CoreObjectType>(object: ObjectContent<T>) {
const corePointClass = corePointClassFactory(object);
if (!corePointClass.hasAttribute(object, COLOR_ATTRIB_NAME)) {
corePointClass.addNumericAttribute(object, COLOR_ATTRIB_NAME, 3, DEFAULT_COLOR);
}
}
_evalSimpleValues<T extends CoreObjectType>(object: ObjectContent<T>) {
const corePointClass = corePointClassFactory(object);
this._createInitColor(object);
let newColor: Color;
if (isBooleanTrue(this.pv.asHsv)) {
newColor = new Color();
CoreColor.setHSV(this.pv.color.r, this.pv.color.g, this.pv.color.b, newColor);
} else {
newColor = this.pv.color; //.clone();
}
corePointClass.addNumericAttribute(object, COLOR_ATTRIB_NAME, 3, newColor);
}
async _evalExpressions<T extends CoreObjectType>(object: ObjectContent<T>) {
const points: CorePoint<CoreObjectType>[] = [];
pointsFromObject(object, points);
if (!isObject3D(object)) {
return;
}
// const coreGeometry = core_object.coreGeometry();
// if (coreGeometry) {
this._createInitColor(object);
// }
const geometry = (object as Mesh).geometry as BufferGeometry;
if (geometry) {
const array = (geometry.getAttribute(COLOR_ATTRIB_NAME) as BufferAttribute).array;
const tmpArrayR = await this._updateFromParam(geometry, array, points, 0);
const tmpArrayG = await this._updateFromParam(geometry, array, points, 1);
const tmpArrayB = await this._updateFromParam(geometry, array, points, 2);
if (tmpArrayR) {
this._commitTmpValues(tmpArrayR, array, 0);
}
if (tmpArrayG) {
this._commitTmpValues(tmpArrayG, array, 1);
}
if (tmpArrayB) {
this._commitTmpValues(tmpArrayB, array, 2);
}
// to hsv
if (isBooleanTrue(this.pv.asHsv)) {
let current = new Color();
let target = new Color();
let index;
for (const point of points) {
index = point.index() * 3;
current.fromArray(array, index);
CoreColor.setHSV(current.r, current.g, current.b, target);
target.toArray(array, index);
}
}
}
// const colorr_param = this.param('colorr');
// const colorg_param = this.param('colorg');
// const colorb_param = this.param('colorb');
// r
// if(colorr_param.has_expression()){
// await colorr_param.eval_expression_for_entities(points, (point, value)=>{
// array[point.index()*3+0] = value
// })
// } else {
// for(let point of points){
// array[point.index()*3+0] = this.pv.color.r
// }
// }
// g
// if(colorg_param.has_expression()){
// await colorg_param.eval_expression_for_entities(points, (point, value)=>{
// array[point.index()*3+1] = value
// })
// } else {
// for(let point of points){
// array[point.index()*3+1] = this.pv.color.g
// }
// }
// b
// if(colorb_param.has_expression()){
// await colorb_param.eval_expression_for_entities(points, (point, value)=>{
// array[point.index()*3+2] = value
// })
// } else {
// for(let point of points){
// array[point.index()*3+2] = this.pv.color.b
// }
// }
}
private async _updateFromParam(
geometry: BufferGeometry,
array: TypedArray,
points: BaseCorePoint[],
offset: number
): Promise<number[] | undefined> {
// const component_name = ['r', 'g', 'b'][offset];
const param = this.p.color.components[offset];
const paramValue = [this.pv.color.r, this.pv.color.g, this.pv.color.b][offset];
const arraysByGeometryUUID = [
this._arrayByGeometryUUID.R,
this._arrayByGeometryUUID.B,
this._arrayByGeometryUUID.G,
][offset];
let tmpArray: number[] | undefined;
if (param.hasExpression() && param.expressionController) {
tmpArray = this._initArrayIfRequired(geometry, arraysByGeometryUUID, points.length);
if (param.expressionController.entitiesDependent()) {
await param.expressionController.computeExpressionForPoints(points, (point, value) => {
// array[point.index()*3+2] = value
(tmpArray as number[])[point.index()] = value;
});
} else {
for (const point of points) {
(tmpArray as number[])[point.index()] = param.value;
}
}
} else {
for (const point of points) {
array[point.index() * 3 + offset] = paramValue;
}
}
return tmpArray;
}
private _initArrayIfRequired(
geometry: BufferGeometry,
arraysByGeometryUUID: ValueArrayByName,
pointsCount: number
) {
const uuid = geometry.uuid;
const currentArray = arraysByGeometryUUID[uuid];
if (currentArray) {
// only create new array if we need more point, or as soon as the length is different?
if (currentArray.length < pointsCount) {
arraysByGeometryUUID[uuid] = new Array(pointsCount);
}
} else {
arraysByGeometryUUID[uuid] = new Array(pointsCount);
}
return arraysByGeometryUUID[uuid];
}
private _commitTmpValues(tmpArray: number[], targetArray: TypedArray, offset: number) {
for (let i = 0; i < tmpArray.length; i++) {
targetArray[i * 3 + offset] = tmpArray[i];
}
}
}