UNPKG

polygonjs-engine

Version:

node-based webgl 3D engine https://polygonjs.com

247 lines (218 loc) 8.24 kB
/** * Copies a geometry onto every point from the right input. * * @remarks * This is different than the instance SOP, as the operation here is more expensive, but allows for more flexibility. * * */ import {TypedSopNode} from './_Base'; import {CoreGroup, Object3DWithGeometry} from '../../../core/geometry/Group'; import {CoreObject} from '../../../core/geometry/Object'; import {CoreInstancer} from '../../../core/geometry/Instancer'; import {CoreString} from '../../../core/String'; import {CopyStamp} from './utils/CopyStamp'; import {Matrix4} from 'three/src/math/Matrix4'; import {CorePoint} from '../../../core/geometry/Point'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {InputCloneMode} from '../../poly/InputCloneMode'; import {Object3D} from 'three/src/core/Object3D'; import {Vector3} from 'three/src/math/Vector3'; import {Quaternion} from 'three/src/math/Quaternion'; import {ArrayUtils} from '../../../core/ArrayUtils'; class CopySopParamsConfig extends NodeParamsConfig { /** @param copies count, used when the second input is not given */ count = ParamConfig.INTEGER(1, { range: [1, 20], rangeLocked: [true, false], }); /** @param transforms every input object each on a single input point */ transformOnly = ParamConfig.BOOLEAN(0); /** @param toggles on to copy attributes from the input points to the created objects. Note that the vertex attributes from the points become object attributes */ copyAttributes = ParamConfig.BOOLEAN(0); /** @param names of attributes to copy */ attributesToCopy = ParamConfig.STRING('', { visibleIf: {copyAttributes: true}, }); /** @param toggle on to use the `copy` expression, which allows to change how the left input is evaluated for each point */ useCopyExpr = ParamConfig.BOOLEAN(0); } const ParamsConfig = new CopySopParamsConfig(); export class CopySopNode extends TypedSopNode<CopySopParamsConfig> { params_config = ParamsConfig; static type() { return 'copy'; } private _attribute_names_to_copy: string[] = []; private _objects: Object3D[] = []; private _stamp_node!: CopyStamp; static displayedInputNames(): string[] { return ['geometry to be copied', 'points to copy to']; } initializeNode() { this.io.inputs.setCount(1, 2); this.io.inputs.initInputsClonedState([InputCloneMode.ALWAYS, InputCloneMode.NEVER]); } async cook(input_contents: CoreGroup[]) { const core_group0 = input_contents[0]; if (!this.io.inputs.has_input(1)) { await this.cook_without_template(core_group0); return; } const core_group1 = input_contents[1]; if (!core_group1) { this.states.error.set('second input invalid'); return; } await this.cook_with_template(core_group0, core_group1); } private async cook_with_template(instance_core_group: CoreGroup, template_core_group: CoreGroup) { this._objects = []; const template_points = template_core_group.points(); const instancer = new CoreInstancer(template_core_group); let instance_matrices = instancer.matrices(); const t = new Vector3(); const q = new Quaternion(); const s = new Vector3(); instance_matrices[0].decompose(t, q, s); this._attribute_names_to_copy = CoreString.attribNames(this.pv.attributesToCopy).filter((attrib_name) => template_core_group.hasAttrib(attrib_name) ); await this._copy_moved_objects_on_template_points(instance_core_group, instance_matrices, template_points); this.setObjects(this._objects); } // https://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence private async _copy_moved_objects_on_template_points( instance_core_group: CoreGroup, instance_matrices: Matrix4[], template_points: CorePoint[] ) { for (let point_index = 0; point_index < template_points.length; point_index++) { await this._copy_moved_object_on_template_point( instance_core_group, instance_matrices, template_points, point_index ); } } private async _copy_moved_object_on_template_point( instance_core_group: CoreGroup, instance_matrices: Matrix4[], template_points: CorePoint[], point_index: number ) { const matrix = instance_matrices[point_index]; const template_point = template_points[point_index]; this.stamp_node.set_point(template_point); const moved_objects = await this._get_moved_objects_for_template_point(instance_core_group, point_index); for (let moved_object of moved_objects) { if (this.pv.copyAttributes) { this._copyAttributes_from_template(moved_object, template_point); } // TODO: that node is getting inconsistent... // should I always only move the object? // and have a toggle to bake back to the geo? // or just enfore the use of a merge? if (this.pv.transformOnly) { moved_object.applyMatrix4(matrix); } else { const geometry = moved_object.geometry; if (geometry) { moved_object.geometry.applyMatrix4(matrix); } //else { //moved_object.applyMatrix4(matrix); //} } this._objects.push(moved_object); } } private async _get_moved_objects_for_template_point( instance_core_group: CoreGroup, point_index: number ): Promise<Object3DWithGeometry[]> { const stamped_instance_core_group = await this._stamp_instance_group_if_required(instance_core_group); if (stamped_instance_core_group) { // duplicate or select from instance children const moved_objects = this.pv.transformOnly ? // TODO: why is doing a transform slower than cloning the input?? ArrayUtils.compact([stamped_instance_core_group.objectsWithGeo()[point_index]]) : stamped_instance_core_group.clone().objectsWithGeo(); return moved_objects; } else { return []; } } private async _stamp_instance_group_if_required(instance_core_group: CoreGroup): Promise<CoreGroup | undefined> { if (this.pv.useCopyExpr) { const container0 = await this.container_controller.requestInputContainer(0); if (container0) { const core_group0 = container0.coreContent(); if (core_group0) { return core_group0; } else { return; } } else { this.states.error.set(`input failed for index ${this.stamp_value()}`); return; } } else { return instance_core_group; } } private async _copy_moved_objects_for_each_instance(instance_core_group: CoreGroup) { for (let i = 0; i < this.pv.count; i++) { await this._copy_moved_objects_for_instance(instance_core_group, i); } } private async _copy_moved_objects_for_instance(instance_core_group: CoreGroup, i: number) { this.stamp_node.set_global_index(i); const stamped_instance_core_group = await this._stamp_instance_group_if_required(instance_core_group); if (stamped_instance_core_group) { stamped_instance_core_group.objects().forEach((object) => { // TODO: I should use the Core Group, to ensure that material.linewidth is properly cloned const new_object = CoreObject.clone(object); this._objects.push(new_object); }); } } // TODO: what if I combine both param_count and stamping?! private async cook_without_template(instance_core_group: CoreGroup) { this._objects = []; await this._copy_moved_objects_for_each_instance(instance_core_group); this.setObjects(this._objects); } private _copyAttributes_from_template(object: Object3D, template_point: CorePoint) { this._attribute_names_to_copy.forEach((attrib_name, i) => { const attrib_value = template_point.attribValue(attrib_name); const object_wrapper = new CoreObject(object, i); object_wrapper.addAttribute(attrib_name, attrib_value); }); } // // // STAMP // // stamp_value(attrib_name?: string) { return this.stamp_node.value(attrib_name); } get stamp_node() { return (this._stamp_node = this._stamp_node || this.create_stamp_node()); } private create_stamp_node() { const stamp_node = new CopyStamp(this.scene()); this.dirtyController.set_forbidden_trigger_nodes([stamp_node]); return stamp_node; } dispose() { super.dispose(); if (this._stamp_node) { this._stamp_node.dispose(); } } // private set_dirty_allowed(original_trigger_graph_node: CoreGraphNode): boolean { // return original_trigger_graph_node.graphNodeId() !== this.stamp_node.graphNodeId(); // } }