polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
372 lines (371 loc) • 13.9 kB
JavaScript
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 [];
}
}
}