UNPKG

polygonjs-engine

Version:

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

372 lines (371 loc) 13.9 kB
import {Vector2 as Vector22} from "three/src/math/Vector2"; import {MathUtils as MathUtils2} from "three/src/math/MathUtils"; import {InstancedBufferAttribute as InstancedBufferAttribute2} from "three/src/core/InstancedBufferAttribute"; import {BufferAttribute as BufferAttribute2} from "three/src/core/BufferAttribute"; import {GlConstant as GlConstant2} from "../../../../../core/geometry/GlConstant"; import {CoreMath} from "../../../../../core/math/_Module"; import {GlobalsTextureHandler} from "../../../gl/code/globals/Texture"; import {GPUComputationRenderer as GPUComputationRenderer2} from "./GPUComputationRenderer"; import {Poly as Poly2} from "../../../../Poly"; import {GlParamConfig} from "../../../gl/code/utils/ParamConfig"; import {CoreGraphNode as CoreGraphNode2} from "../../../../../core/graph/CoreGraphNode"; import {FloatType, HalfFloatType} from "three/src/constants"; export var ParticlesDataType; (function(ParticlesDataType2) { ParticlesDataType2["FLOAT"] = "float"; ParticlesDataType2["HALF_FLOAT"] = "half"; })(ParticlesDataType || (ParticlesDataType = {})); export const PARTICLE_DATA_TYPES = [ParticlesDataType.FLOAT, ParticlesDataType.HALF_FLOAT]; const DATA_TYPE_BY_ENUM = { [ParticlesDataType.FLOAT]: FloatType, [ParticlesDataType.HALF_FLOAT]: HalfFloatType }; export class ParticlesSystemGpuComputeController { constructor(node) { this.node = node; this._simulation_restart_required = false; this._points = []; this.variables_by_name = new Map(); this._all_variables = []; this._created_textures_by_name = new Map(); this._delta_time = 0; this._used_textures_size = new Vector22(); } dispose() { if (this._graph_node) { this._graph_node.dispose(); } } set_persisted_texture_allocation_controller(controller) { this._persisted_texture_allocations_controller = controller; } set_shaders_by_name(shaders_by_name) { this._shaders_by_name = shaders_by_name; this.reset_gpu_compute(); } all_variables() { return this._all_variables; } async init(core_group) { this.init_particle_group_points(core_group); await this.create_gpu_compute(); } getCurrentRenderTarget(shader_name) { const variable = this.variables_by_name.get(shader_name); if (variable) { return this._gpu_compute?.getCurrentRenderTarget(variable); } } init_particle_group_points(core_group) { this.reset_gpu_compute(); if (!core_group) { return; } this._particles_core_group = core_group; this._points = this._get_points() || []; } compute_similation_if_required() { const frame = this.node.scene().frame(); const start_frame = this.node.pv.startFrame; if (frame >= start_frame) { if (this._last_simulated_frame == null) { this._last_simulated_frame = start_frame - 1; } if (this._last_simulated_time == null) { this._last_simulated_time = this.node.scene().time(); } if (frame > this._last_simulated_frame) { this._compute_simulation(frame - this._last_simulated_frame); } } } _compute_simulation(iterations_count = 1) { if (!this._gpu_compute || this._last_simulated_time == null) { return; } this.update_simulation_material_uniforms(); for (let i = 0; i < iterations_count; i++) { this._gpu_compute.compute(); } this.node.render_controller.update_render_material_uniforms(); this._last_simulated_frame = this.node.scene().frame(); const time = this.node.scene().time(); this._delta_time = time - this._last_simulated_time; this._last_simulated_time = time; } _data_type() { const data_type_name = PARTICLE_DATA_TYPES[this.node.pv.dataType]; return DATA_TYPE_BY_ENUM[data_type_name]; } async create_gpu_compute() { if (this.node.pv.auto_textures_size) { const nearest_power_of_two = CoreMath.nearestPower2(Math.sqrt(this._points.length)); this._used_textures_size.x = Math.min(nearest_power_of_two, this.node.pv.maxTexturesSize.x); this._used_textures_size.y = Math.min(nearest_power_of_two, this.node.pv.maxTexturesSize.y); } else { if (!(MathUtils2.isPowerOfTwo(this.node.pv.texturesSize.x) && MathUtils2.isPowerOfTwo(this.node.pv.texturesSize.y))) { this.node.states.error.set("texture size must be a power of 2"); return; } const max_particles_count = this.node.pv.texturesSize.x * this.node.pv.texturesSize.y; if (this._points.length > max_particles_count) { this.node.states.error.set(`max particles is set to (${this.node.pv.texturesSize.x}x${this.node.pv.texturesSize.y}=) ${max_particles_count}`); return; } this._used_textures_size.copy(this.node.pv.texturesSize); } this._force_time_dependent(); this._init_particles_uvs(); this.node.render_controller.reset_render_material(); const renderer = await Poly2.renderersController.waitForRenderer(); if (renderer) { this._renderer = renderer; } else { this.node.states.error.set("no renderer found"); } if (!this._renderer) { return; } const compute = new GPUComputationRenderer2(this._used_textures_size.x, this._used_textures_size.y, this._renderer); compute.setDataType(this._data_type()); this._gpu_compute = compute; if (!this._gpu_compute) { this.node.states.error.set("failed to create the GPUComputationRenderer"); return; } this._last_simulated_frame = void 0; this.variables_by_name.forEach((variable, shader_name) => { variable.renderTargets[0].dispose(); variable.renderTargets[1].dispose(); this.variables_by_name.delete(shader_name); }); this._all_variables = []; this._shaders_by_name?.forEach((shader, shader_name) => { if (this._gpu_compute) { const variable = this._gpu_compute.addVariable(`texture_${shader_name}`, shader, this._created_textures_by_name.get(shader_name)); this.variables_by_name.set(shader_name, variable); this._all_variables.push(variable); } }); this.variables_by_name?.forEach((variable, shader_name) => { if (this._gpu_compute) { this._gpu_compute.setVariableDependencies(variable, this._all_variables); } }); this._create_texture_render_targets(); this._fill_textures(); this.create_simulation_material_uniforms(); var error = this._gpu_compute.init(); if (error !== null) { console.error(error); this.node.states.error.set(error); } } _force_time_dependent() { if (!this._graph_node) { this._graph_node = new CoreGraphNode2(this.node.scene(), "gpu_compute"); this._graph_node.addGraphInput(this.node.scene().timeController.graph_node); this._graph_node.addPostDirtyHook("on_time_change", this._on_graph_node_dirty.bind(this)); } } _on_graph_node_dirty() { if (this.node.is_on_frame_start()) { this.node.setDirty(); return; } else { this.compute_similation_if_required(); } } create_simulation_material_uniforms() { const assemblerController = this.node.assemblerController; const assembler = assemblerController?.assembler; if (!assembler && !this._persisted_texture_allocations_controller) { return; } const all_materials = []; this.variables_by_name.forEach((variable, shader_name) => { all_materials.push(variable.material); }); for (let material of all_materials) { material.uniforms[GlConstant2.TIME] = {value: this.node.scene().time()}; material.uniforms[GlConstant2.DELTA_TIME] = {value: this.node.scene().time()}; } if (assembler) { for (let material of all_materials) { for (let param_config of assembler.param_configs()) { material.uniforms[param_config.uniform_name] = param_config.uniform; } } } else { const persisted_data = this.node.persisted_config.loaded_data(); if (persisted_data) { const persisted_uniforms = this.node.persisted_config.uniforms(); if (persisted_uniforms) { const param_uniform_pairs = persisted_data.param_uniform_pairs; for (let pair of param_uniform_pairs) { const param_name = pair[0]; const uniform_name = pair[1]; const param = this.node.params.get(param_name); const uniform = persisted_uniforms[uniform_name]; for (let material of all_materials) { material.uniforms[uniform_name] = uniform; } if (param && uniform) { param.options.set_option("callback", () => { for (let material of all_materials) { GlParamConfig.callback(param, material.uniforms[uniform_name]); } }); } } } } } } update_simulation_material_uniforms() { for (let variable of this._all_variables) { variable.material.uniforms[GlConstant2.TIME].value = this.node.scene().time(); variable.material.uniforms[GlConstant2.DELTA_TIME].value = this._delta_time; } } _init_particles_uvs() { var uvs = new Float32Array(this._points.length * 2); let p = 0; var cmptr = 0; for (var j = 0; j < this._used_textures_size.x; j++) { for (var i = 0; i < this._used_textures_size.y; i++) { uvs[p++] = i / (this._used_textures_size.x - 1); uvs[p++] = j / (this._used_textures_size.y - 1); cmptr += 2; if (cmptr >= uvs.length) { break; } } } const uv_attrib_name = GlobalsTextureHandler.UV_ATTRIB; if (this._particles_core_group) { for (let core_geometry of this._particles_core_group.coreGeometries()) { const geometry = core_geometry.geometry(); const attribute_constructor = core_geometry.markedAsInstance() ? InstancedBufferAttribute2 : BufferAttribute2; geometry.setAttribute(uv_attrib_name, new attribute_constructor(uvs, 2)); } } } created_textures_by_name() { return this._created_textures_by_name; } _fill_textures() { const assemblerController = this.node.assemblerController; const assembler = assemblerController?.assembler; const texture_allocations_controller = assembler ? assembler.texture_allocations_controller : this._persisted_texture_allocations_controller; if (!texture_allocations_controller) { return; } this._created_textures_by_name.forEach((texture, shader_name) => { const texture_allocation = texture_allocations_controller.allocation_for_shader_name(shader_name); if (!texture_allocation) { return; } const texture_variables = texture_allocation.variables; if (!texture_variables) { return; } const array = texture.image.data; for (let texture_variable of texture_variables) { const texture_position = texture_variable.position; let variable_name = texture_variable.name(); const first_point = this._points[0]; if (first_point) { const has_attrib = first_point.hasAttrib(variable_name); if (has_attrib) { const attrib_size = first_point.attribSize(variable_name); let cmptr = texture_position; for (let point of this._points) { if (attrib_size == 1) { const val = point.attribValue(variable_name); array[cmptr] = val; } else { point.attribValue(variable_name).toArray(array, cmptr); } cmptr += 4; } } } } }); } reset_gpu_compute() { this._gpu_compute = void 0; this._simulation_restart_required = true; } set_restart_not_required() { this._simulation_restart_required = false; } reset_gpu_compute_and_set_dirty() { this.reset_gpu_compute(); this.node.setDirty(); } reset_particle_groups() { this._particles_core_group = void 0; } get initialized() { return this._particles_core_group != null && this._gpu_compute != null; } _create_texture_render_targets() { this._created_textures_by_name.forEach((texture, shader_name) => { texture.dispose(); }); this._created_textures_by_name.clear(); this.variables_by_name.forEach((texture_variable, shader_name) => { if (this._gpu_compute) { this._created_textures_by_name.set(shader_name, this._gpu_compute.createTexture()); } }); } restart_simulation_if_required() { if (this._simulation_restart_required) { this._restart_simulation(); } } _restart_simulation() { this._last_simulated_time = void 0; this._create_texture_render_targets(); const points = this._get_points(); if (!points) { return; } this._fill_textures(); this.variables_by_name.forEach((variable, shader_name) => { const texture = this._created_textures_by_name.get(shader_name); if (this._gpu_compute && texture) { this._gpu_compute.renderTexture(texture, variable.renderTargets[0]); this._gpu_compute.renderTexture(texture, variable.renderTargets[1]); } }); } _get_points() { if (!this._particles_core_group) { return; } let geometries = this._particles_core_group.coreGeometries(); const first_geometry = geometries[0]; if (first_geometry) { const type = first_geometry.markedAsInstance(); const selected_geometries = []; for (let geometry of geometries) { if (geometry.markedAsInstance() == type) { selected_geometries.push(geometry); } } const points = []; for (let geometry of selected_geometries) { for (let point of geometry.points()) { points.push(point); } } return points; } else { return []; } } }