Convert instancer MMI nodes to RS

Fix default to false for show instances if no instancer (+21 squashed commit)

Squashed commit:

[818231f] Use SET_IF_DIFF()

[a2cee65] Prevent !is_inside_world() error

[a300aac] Fix do not free shared shadow MM

[badefa1] Fix clear MA after freeing MMIs

[6f64fc0] More null mesh checks (+1 squashed commits)

Squashed commits:

[c185cd4] Check for invalid mesh before using

[c6b22fd] Do not erare the dictionary we are iterating on

[3a50b8e] rename _mmi_nodes to _mmi_rids

[114352f] Remove commented #include

[f8f977b] Fix type for visibility layers and only emit signal if it changes in setter

[e4f266b] Clarify data structure and usage

[58d141e] Remove traces of _mmi_parent

[6b2eb3c] Remove _mmi_parent and dump_tree function. Handle instancer visibility

[a996c9c] Rename region MMI nodes and reinstate _mmi_nodes.erase(p_region_loc);

[3317ff7] Drop blank line separating associated code

[f98a346] Set MMI AABB, not MM AABB, for shadow imposter

[e2073d5] Use LOD_0, not LAST_LOD, for instance count updates

[99da7a7] Move definition of RID shadow_impostor_source_mm;

[18e96f6] Remove surplus MeshMMIDict definition

[415ad9e] remove blank line as per clang advice

[c4e9016] Add blank line between Godot and T3D headers

[4bdb15c] Convert instancer MMI nodes to RS
This commit is contained in:
aidandavey 2025-10-05 21:30:16 +01:00
parent dd7e34ddeb
commit dd50bfed30
7 changed files with 174 additions and 159 deletions

View File

@ -141,14 +141,10 @@ void Terrain3D::_build_containers() {
_label_parent = memnew(Node3D);
_label_parent->set_name("Labels");
add_child(_label_parent, true);
_mmi_parent = memnew(Node3D);
_mmi_parent->set_name("MMI");
add_child(_mmi_parent, true);
}
void Terrain3D::_destroy_containers() {
memdelete_safely(_label_parent);
memdelete_safely(_mmi_parent);
}
void Terrain3D::_destroy_labels() {

View File

@ -93,7 +93,6 @@ private:
// Parent containers for child nodes
Node3D *_label_parent;
Node3D *_mmi_parent;
void _initialize();
void __physics_process(const double p_delta);
@ -138,7 +137,6 @@ public:
Ref<Terrain3DAssets> get_assets() const { return _assets; }
Terrain3DCollision *get_collision() const { return _collision; }
Terrain3DInstancer *get_instancer() const { return _instancer; }
Node *get_mmi_parent() const { return _mmi_parent; }
void set_editor(Terrain3DEditor *p_editor);
Terrain3DEditor *get_editor() const { return _editor; }
void set_plugin(Object *p_plugin);
@ -188,8 +186,8 @@ public:
real_t get_cull_margin() const { return _cull_margin; };
void set_free_editor_textures(const bool p_free_textures) { _free_editor_textures = p_free_textures; }
bool get_free_editor_textures() const { return _free_editor_textures; };
void set_show_instances(const bool p_visible) { _mmi_parent ? _mmi_parent->set_visible(p_visible) : void(); }
bool get_show_instances() const { return _mmi_parent ? _mmi_parent->is_visible() : false; }
void set_show_instances(const bool p_visible) { _instancer ? _instancer->set_show_instances(p_visible) : void(); }
bool get_show_instances() const { return _instancer ? _instancer->get_show_instances() : false; }
// Utility
Vector3 get_intersection(const Vector3 &p_src_pos, const Vector3 &p_direction, const bool p_gpu_mode = false);

View File

@ -544,11 +544,11 @@ void Terrain3DAssets::set_mesh_asset(const int p_id, const Ref<Terrain3DMeshAsse
return;
}
LOG(INFO, "Setting mesh id: ", p_id, ", ", p_mesh_asset);
_set_asset(TYPE_MESH, p_id, p_mesh_asset);
if (p_mesh_asset.is_null()) {
IS_INSTANCER_INIT(VOID);
_terrain->get_instancer()->clear_by_mesh(p_id);
}
_set_asset(TYPE_MESH, p_id, p_mesh_asset);
update_mesh_list();
}

View File

@ -1,6 +1,7 @@
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
#include <godot_cpp/classes/resource_saver.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include "constants.h"
#include "logger.h"
@ -22,6 +23,9 @@ void Terrain3DInstancer::_process_updates() {
return;
}
IS_DATA_INIT(VOID);
if (!_terrain->is_inside_tree()) {
return;
}
const Terrain3DData *data = _terrain->get_data();
TypedArray<Vector2i> region_locations = data->get_region_locations();
int mesh_count = _terrain->get_assets()->get_mesh_count();
@ -30,10 +34,11 @@ void Terrain3DInstancer::_process_updates() {
bool update_all = false;
if (_queued_updates.find({ V2I_MAX, -2 }) != _queued_updates.end()) {
destroy();
update_all = true;
update_all = _show_instances;
} else if (_queued_updates.find({ V2I_MAX, -1 }) != _queued_updates.end()) {
update_all = true;
update_all = _show_instances;
}
if (update_all) {
LOG(DEBUG, "Updating all regions, all mesh_ids");
for (int i = 0; i < region_locations.size(); i++) {
@ -51,6 +56,7 @@ void Terrain3DInstancer::_process_updates() {
}
}
_queued_updates.clear();
_rebuild_ma_meshes();
return;
}
@ -97,6 +103,7 @@ void Terrain3DInstancer::_process_updates() {
_update_mmi_by_region(region, mesh_id);
}
_queued_updates.clear();
_rebuild_ma_meshes();
}
void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region, const int p_mesh_id) {
@ -111,16 +118,6 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
Vector2i region_loc = p_region->get_location();
Dictionary mesh_inst_dict = p_region->get_instances();
// Create MMI container if needed (always, per region)
String rname("Region" + Util::location_to_string(region_loc));
if (_mmi_containers.count(region_loc) == 0) {
LOG(DEBUG, "Creating new region MMI container Terrain3D/MMI/", rname);
Node3D *node = memnew(Node3D);
node->set_name(rname);
_mmi_containers[region_loc] = node;
_terrain->get_mmi_parent()->add_child(node, true);
}
// Verify mesh id is valid, enabled, and has MeshInstance3Ds
Ref<Terrain3DMeshAsset> ma = _terrain->get_assets()->get_mesh_asset(p_mesh_id);
if (!ma.is_valid()) {
@ -139,6 +136,12 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
return;
}
if (ma->get_scene_file_changed()) {
LOG(EXTREME, "MeshAsset ", p_mesh_id, " has no scene file, destroying MMIs in region ", region_loc);
_destroy_mmi_by_location(region_loc, p_mesh_id);
return;
}
// Process cells
Dictionary cell_inst_dict = mesh_inst_dict[p_mesh_id];
Array cell_locations = cell_inst_dict.keys();
@ -167,6 +170,7 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
LOG(EXTREME, "Destroyed old MMIs mesh ", p_mesh_id, " cell ", cell, ", LOD ", lod);
}
}
// Clean Shadow MMIs
bool shadow_lod_disabled = (ma->get_shadow_impostor() == 0 ||
ma->get_cast_shadows() == SHADOWS_OFF);
@ -178,8 +182,8 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
// Setup MMIs for each LOD + shadows
// Get or create mesh dict (defined here as cleanup above might invalidate it)
MeshMMIDict &mesh_mmi_dict = _mmi_nodes[region_loc];
Ref<MultiMesh> shadow_impostor_source_mm;
MeshMMIDict &mesh_mmi_dict = _mmi_rids[region_loc];
RID shadow_impostor_source_mm;
for (int lod = ma->get_last_lod(); lod >= Terrain3DMeshAsset::SHADOW_LOD_ID; lod--) {
// Don't create shadow MMI if not needed
@ -195,38 +199,23 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
// Get or create MMI - [] creates key if missing
Vector2i mesh_key(p_mesh_id, lod);
CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key];
MultiMeshInstance3D *mmi = cell_mmi_dict[cell]; // null if missing
if (!mmi) {
mmi = memnew(MultiMeshInstance3D);
LOG(EXTREME, "No MMI found, Created new MultiMeshInstance3D for cell ", cell, ": ", ptr_to_str(mmi));
// Node name is MMI3D_Cell##_##_Mesh#_LOD#
String cstring = "_C" + Util::location_to_string(cell).trim_prefix("_");
String mstring = "_M" + String::num_int64(p_mesh_id);
String lstring = "_L" + ((lod == Terrain3DMeshAsset::SHADOW_LOD_ID) ? "S" : String::num_int64(lod));
mmi->set_name("MMI3D" + cstring + mstring + lstring);
cell_mmi_dict[cell] = mmi;
//Attach to tree
Node *node_container = _terrain->get_mmi_parent()->get_node_internal(rname);
if (!node_container) {
LOG(ERROR, rname, " isn't attached to the tree.");
memdelete(mmi);
cell_mmi_dict.erase(cell);
continue;
}
node_container->add_child(mmi, true);
RID &mmi = cell_mmi_dict[cell].first; // null if missing
if (!mmi.is_valid()) {
mmi = RS->instance_create();
RS->instance_set_scenario(mmi, _terrain->get_world_3d()->get_scenario());
modified = true; // New MMI needs full update
}
// Always update MMI propertiess
if (ma->is_highlighted()) {
mmi->set_material_override(ma->get_highlight_material());
mmi->set_material_overlay(Ref<Material>());
RS->instance_geometry_set_material_override(mmi, ma->get_highlight_material().is_valid() ? ma->get_highlight_material()->get_rid() : RID());
RS->instance_geometry_set_material_overlay(mmi, RID());
} else {
mmi->set_material_override(ma->get_material_override());
mmi->set_material_overlay(ma->get_material_overlay());
RS->instance_geometry_set_material_override(mmi, ma->get_material_override().is_valid() ? ma->get_material_override()->get_rid() : RID());
RS->instance_geometry_set_material_overlay(mmi, ma->get_material_overlay().is_valid() ? ma->get_material_overlay()->get_rid() : RID());
}
mmi->set_cast_shadows_setting(ma->get_lod_cast_shadows(lod));
RS->instance_geometry_set_cast_shadows_setting(mmi, ma->get_lod_cast_shadows(lod));
RS->instance_set_layer_mask(mmi, ma->get_visibility_layers());
_set_mmi_lod_ranges(mmi, ma, lod);
// Reposition MMI to region location
@ -235,40 +224,37 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
real_t vertex_spacing = _terrain->get_vertex_spacing();
t.origin.x += region_loc.x * region_size * vertex_spacing;
t.origin.z += region_loc.y * region_size * vertex_spacing;
mmi->set_as_top_level(true);
mmi->set_global_transform(t);
RS->instance_set_transform(mmi, t);
RID &mm = cell_mmi_dict[cell].second;
// Only recreate MultiMesh if modified/no existing (with override for shadow)
// Always update shadow MMI (source may have changed)
if (modified || mmi->get_multimesh().is_null() ||
if (modified || !mm.is_valid() ||
lod == Terrain3DMeshAsset::SHADOW_LOD_ID) {
// Subtract previous instance count of this MMI
if (lod == 0) {
if (mmi->get_multimesh().is_valid()) {
ma->update_instance_count(-mmi->get_multimesh()->get_instance_count());
}
// Subtract previous instance count for this cell
if (mm.is_valid() && lod == 0) {
ma->update_instance_count(-RS->multimesh_get_instance_count(mm));
}
Ref<MultiMesh> mm;
if (lod == Terrain3DMeshAsset::SHADOW_LOD_ID) {
// Reuse impostor LOD MM as shadow impostor
mm = shadow_impostor_source_mm;
if (mm.is_null()) {
if (!mm.is_valid()) {
LOG(ERROR, "Shadow MM is null for cell ", cell, " lod ", lod, " xforms: ", xforms.size());
continue;
}
} else {
mm = _create_multimesh(p_mesh_id, lod, xforms, colors);
}
if (mm.is_null()) {
if (!mm.is_valid()) {
LOG(ERROR, "Null MM for cell ", cell, " lod ", lod, " xforms: ", xforms.size());
continue;
}
mmi->set_multimesh(mm);
RS->instance_set_base(mmi, mm);
// Add current instance count of this new MMI
// Add current instance count for this cell
if (lod == 0) {
ma->update_instance_count(mm->get_instance_count());
ma->update_instance_count(RS->multimesh_get_instance_count(mm));
}
// Clear modified only for visible LODs
@ -277,40 +263,66 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
}
} else {
// Needed to update generated mesh changes
mmi->get_multimesh()->set_mesh(ma->get_mesh(lod));
Ref<Mesh> mesh = ma->get_mesh(lod);
if (!mesh.is_valid()) {
LOG(ERROR, "Mesh is null for LOD ", lod);
RS->free_rid(mmi);
RS->free_rid(mm);
continue;
}
RS->multimesh_set_mesh(mm, mesh->get_rid());
}
// Capture source MM from shadow impostor LOD
if (lod == ma->get_shadow_impostor()) {
shadow_impostor_source_mm = mmi->get_multimesh();
shadow_impostor_source_mm = mm;
}
} // End for LOD loop
// Set all LOD mmi AABB to match LOD0 to ensure no gaps between transitions.
AABB mmi_custom_aabb;
AABB mm_custom_aabb;
for (int lod = 0; lod <= ma->get_last_lod(); lod++) {
Vector2i mesh_key(p_mesh_id, lod);
CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key];
MultiMeshInstance3D *mmi = cell_mmi_dict[cell];
if (mmi) {
RID &mmi = cell_mmi_dict[cell].first;
RID &mm = cell_mmi_dict[cell].second;
if (mm.is_valid() && mmi.is_valid()) {
if (lod == 0) {
mmi_custom_aabb = mmi->get_aabb();
mm_custom_aabb = RS->multimesh_get_aabb(mm);
} else {
mmi->set_custom_aabb(mmi_custom_aabb);
RS->multimesh_set_custom_aabb(mm, mm_custom_aabb);
}
RS->instance_set_custom_aabb(mmi, mm_custom_aabb);
}
}
if (ma->get_shadow_impostor() > 0) {
Vector2i mesh_key(p_mesh_id, Terrain3DMeshAsset::SHADOW_LOD_ID);
CellMMIDict &cell_mmi_dict = mesh_mmi_dict[mesh_key];
MultiMeshInstance3D *mmi = cell_mmi_dict[cell];
if (mmi) {
mmi->set_custom_aabb(mmi_custom_aabb);
RID &mmi = cell_mmi_dict[cell].first;
if (mmi.is_valid()) {
RS->instance_set_custom_aabb(mmi, mm_custom_aabb);
}
}
}
}
void Terrain3DInstancer::_set_mmi_lod_ranges(MultiMeshInstance3D *p_mmi, const Ref<Terrain3DMeshAsset> &p_ma, const int p_lod) {
void Terrain3DInstancer::_rebuild_ma_meshes() {
// Rebuild all meshes for all mesh assets that have changed
int mesh_count = _terrain->get_assets()->get_mesh_count();
for (int mesh_id = 0; mesh_id < mesh_count; mesh_id++) {
Ref<Terrain3DMeshAsset> ma = _terrain->get_assets()->get_mesh_asset(mesh_id);
if (ma.is_valid() && ma->get_scene_file_changed()) {
LOG(DEBUG, "Rebuilding meshes for MeshAsset ID: ", mesh_id);
if (ma->get_scene_file().is_valid()) {
ma->parse_scene_meshes();
} else {
ma->set_generated_type(Terrain3DMeshAsset::TYPE_TEXTURE_CARD);
}
ma->set_scene_file_changed(false);
}
}
}
void Terrain3DInstancer::_set_mmi_lod_ranges(RID p_mmi, const Ref<Terrain3DMeshAsset> &p_ma, const int p_lod) {
if (!p_mmi || p_ma.is_null()) {
return;
}
@ -326,17 +338,12 @@ void Terrain3DInstancer::_set_mmi_lod_ranges(MultiMeshInstance3D *p_mmi, const R
if (margin > 0.f) {
lod_begin = MAX(lod_begin < 0.001f ? 0.f : lod_begin - margin, 0.f);
lod_end = MAX(lod_end < 0.001f ? 0.f : lod_end + margin, 0.f);
p_mmi->set_visibility_range_begin_margin(lod_begin < 0.001f ? 0.f : margin);
p_mmi->set_visibility_range_end_margin(lod_end < 0.001f ? 0.f : margin);
p_mmi->set_visibility_range_fade_mode(GeometryInstance3D::VISIBILITY_RANGE_FADE_SELF);
real_t begin_margin = lod_begin < 0.001f ? 0.f : margin;
real_t end_margin = lod_end < 0.001f ? 0.f : margin;
RS->instance_geometry_set_visibility_range(p_mmi, lod_begin, lod_end, begin_margin, end_margin, RenderingServer::VISIBILITY_RANGE_FADE_SELF);
} else {
// Fade mode unset (Godot default)
p_mmi->set_visibility_range_begin_margin(0.f);
p_mmi->set_visibility_range_end_margin(0.f);
p_mmi->set_visibility_range_fade_mode(GeometryInstance3D::VISIBILITY_RANGE_FADE_DISABLED);
RS->instance_geometry_set_visibility_range(p_mmi, lod_begin, lod_end, 0.f, 0.f, RenderingServer::VISIBILITY_RANGE_FADE_DISABLED);
}
p_mmi->set_visibility_range_begin(lod_begin);
p_mmi->set_visibility_range_end(lod_end);
}
void Terrain3DInstancer::_update_vertex_spacing(const real_t p_vertex_spacing) {
@ -402,8 +409,8 @@ void Terrain3DInstancer::_destroy_mmi_by_mesh(const int p_mesh_id) {
void Terrain3DInstancer::_destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id) {
LOG(DEBUG, "Deleting all MMIs in region: ", p_region_loc, " for mesh_id: ", p_mesh_id);
std::unordered_set<Vector2i, Vector2iHash> cells;
if (_mmi_nodes.count(p_region_loc) > 0) {
MeshMMIDict &mesh_mmi_dict = _mmi_nodes[p_region_loc];
if (_mmi_rids.count(p_region_loc) > 0) {
MeshMMIDict &mesh_mmi_dict = _mmi_rids[p_region_loc];
for (const auto &mesh_entry : mesh_mmi_dict) {
const Vector2i &mesh_key = mesh_entry.first;
if (mesh_key.x != p_mesh_id) {
@ -419,13 +426,18 @@ void Terrain3DInstancer::_destroy_mmi_by_location(const Vector2i &p_region_loc,
for (const auto &cell : cells) {
_destroy_mmi_by_cell(p_region_loc, p_mesh_id, cell);
}
// After all cells are destroyed, if the region is now empty, erase it
if (_mmi_rids.count(p_region_loc) > 0 && _mmi_rids[p_region_loc].empty()) {
_mmi_rids.erase(p_region_loc);
}
}
void Terrain3DInstancer::_destroy_mmi_by_cell(const Vector2i &p_region_loc, const int p_mesh_id, const Vector2i p_cell, const int p_lod) {
if (_mmi_nodes.count(p_region_loc) == 0) {
if (_mmi_rids.count(p_region_loc) == 0) {
return;
}
MeshMMIDict &mesh_mmi_dict = _mmi_nodes[p_region_loc];
MeshMMIDict &mesh_mmi_dict = _mmi_rids[p_region_loc];
Ref<Terrain3DMeshAsset> ma = _terrain->get_assets()->get_mesh_asset(p_mesh_id);
for (int lod = Terrain3DMeshAsset::SHADOW_LOD_ID; lod < Terrain3DMeshAsset::MAX_LOD_COUNT; lod++) {
@ -441,37 +453,27 @@ void Terrain3DInstancer::_destroy_mmi_by_cell(const Vector2i &p_region_loc, cons
if (cell_mmi_dict.count(p_cell) == 0) {
continue;
}
MultiMeshInstance3D *mmi = cell_mmi_dict[p_cell];
if (ma.is_valid() && mmi && mmi->get_multimesh().is_valid()) {
RID &mmi = cell_mmi_dict[p_cell].first;
RID &mm = cell_mmi_dict[p_cell].second;
if (ma.is_valid() && mm.is_valid()) {
if (lod == 0) {
ma->update_instance_count(-mmi->get_multimesh()->get_instance_count());
ma->update_instance_count(-RS->multimesh_get_instance_count(mm));
}
}
LOG(EXTREME, "Freeing ", ptr_to_str(mmi), " and erasing mmi cell ", p_cell);
remove_from_tree(mmi);
memdelete_safely(mmi);
LOG(EXTREME, "Freeing ", mmi, " and erasing mmi cell ", p_cell);
RS->free_rid(mmi);
if (lod != Terrain3DMeshAsset::SHADOW_LOD_ID) {
RS->free_rid(mm);
}
cell_mmi_dict.erase(p_cell);
if (cell_mmi_dict.empty()) {
LOG(EXTREME, "Removing mesh ", mesh_key, " from cell MMI dictionary");
mesh_mmi_dict.erase(mesh_key); // invalidates cell_mmi_dict
}
}
// Clean up region if we've removed the last MMI and cell
if (mesh_mmi_dict.empty()) {
LOG(EXTREME, "Removing region ", p_region_loc, " from mesh MMI dictionary");
if (_mmi_containers.count(p_region_loc) > 0) {
Node *node = _mmi_containers[p_region_loc];
if (node && node->get_child_count() == 0) {
LOG(EXTREME, "Removing ", node->get_name());
_mmi_containers.erase(p_region_loc);
remove_from_tree(node);
memdelete_safely(node);
}
}
// This invalidates mesh_mmi_dict here and for calling functions
_mmi_nodes.erase(p_region_loc);
}
}
void Terrain3DInstancer::_backup_region(const Ref<Terrain3DRegion> &p_region) {
@ -485,8 +487,8 @@ void Terrain3DInstancer::_backup_region(const Ref<Terrain3DRegion> &p_region) {
}
}
Ref<MultiMesh> Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const int p_lod, const TypedArray<Transform3D> &p_xforms, const PackedColorArray &p_colors) const {
Ref<MultiMesh> mm;
RID Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const int p_lod, const TypedArray<Transform3D> &p_xforms, const PackedColorArray &p_colors) const {
RID mm;
IS_INIT(mm);
Ref<Terrain3DMeshAsset> mesh_asset = _terrain->get_assets()->get_mesh_asset(p_mesh_id);
if (mesh_asset.is_null()) {
@ -498,17 +500,13 @@ Ref<MultiMesh> Terrain3DInstancer::_create_multimesh(const int p_mesh_id, const
LOG(ERROR, "No LOD ", p_lod, " for mesh id ", p_mesh_id, " found. Max: ", mesh_asset->get_lod_count());
return mm;
}
mm.instantiate();
mm->set_transform_format(MultiMesh::TRANSFORM_3D);
mm->set_use_colors(true);
mm->set_mesh(mesh);
if (p_xforms.size() > 0) {
mm->set_instance_count(p_xforms.size());
for (int i = 0; i < p_xforms.size(); i++) {
mm->set_instance_transform(i, p_xforms[i]);
if (i < p_colors.size()) {
mm->set_instance_color(i, p_colors[i]);
}
mm = RS->multimesh_create();
RS->multimesh_allocate_data(mm, p_xforms.size(), RenderingServer::MULTIMESH_TRANSFORM_3D, true, false, false);
RS->multimesh_set_mesh(mm, mesh->get_rid());
for (int i = 0; i < p_xforms.size(); i++) {
RS->multimesh_instance_set_transform(mm, i, p_xforms[i]);
if (i < p_colors.size()) {
RS->multimesh_instance_set_color(mm, i, p_colors[i]);
}
}
return mm;
@ -1336,24 +1334,10 @@ void Terrain3DInstancer::update_mmis(const int p_mesh_id, const Vector2i &p_regi
}
}
void Terrain3DInstancer::dump_mmis() {
LOG(WARN, "Dumping MMI tree and node containers");
LOG(MESG, "_mmi_containers size: ", int(_mmi_containers.size()));
for (auto &it : _mmi_containers) {
LOG(MESG, "_mmi_containers region: ", it.first, ", node ptr: ", ptr_to_str(it.second));
}
LOG(MESG, "_mmi tree: ");
_terrain->get_mmi_parent()->print_tree();
LOG(MESG, "_mmi_nodes size: ", int(_mmi_nodes.size()));
for (auto &i : _mmi_nodes) {
LOG(MESG, "_mmi_nodes region: ", i.first, ", dict ptr: ", ptr_to_str(&i.second));
for (auto &j : i.second) {
LOG(MESG, "mesh_mmi_dict mesh: ", j.first, ", dict ptr: ", ptr_to_str(&j.second));
for (auto &k : j.second) {
LOG(MESG, "cell_mmi_dict cell: ", k.first, ", mmi ptr: ", ptr_to_str(k.second));
}
}
}
void Terrain3DInstancer::set_show_instances(const bool p_visible) {
SET_IF_DIFF(_show_instances, p_visible);
LOG(INFO, "Setting instancer visibility to: ", p_visible);
update_mmis(-1, V2I_MAX, true);
}
///////////////////////////
@ -1374,5 +1358,6 @@ void Terrain3DInstancer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_closest_mesh_id", "global_position"), &Terrain3DInstancer::get_closest_mesh_id);
ClassDB::bind_method(D_METHOD("update_mmis", "mesh_id", "region_location", "rebuild_all"), &Terrain3DInstancer::update_mmis, DEFVAL(-1), DEFVAL(V2I_MAX), DEFVAL(false));
ClassDB::bind_method(D_METHOD("swap_ids", "src_id", "dest_id"), &Terrain3DInstancer::swap_ids);
ClassDB::bind_method(D_METHOD("dump_mmis"), &Terrain3DInstancer::dump_mmis);
ClassDB::bind_method(D_METHOD("set_show_instances", "p_visible"), &Terrain3DInstancer::set_show_instances);
ClassDB::bind_method(D_METHOD("get_show_instances"), &Terrain3DInstancer::get_show_instances);
}

View File

@ -28,15 +28,12 @@ private:
// MM Resources stored in Terrain3DRegion::_instances as
// Region::_instances{mesh_id:int} -> cell{v2i} -> [ TypedArray<Transform3D>, PackedColorArray, modified:bool ]
// MMI Objects attached to tree, freed in destructor, stored as
// _mmi_nodes{region_loc} -> mesh{v2i(mesh_id,lod)} -> cell{v2i} -> MultiMeshInstance3D
using CellMMIDict = std::unordered_map<Vector2i, MultiMeshInstance3D *, Vector2iHash>;
using MeshMMIDict = std::unordered_map<Vector2i, CellMMIDict, Vector2iHash>;
std::unordered_map<Vector2i, MeshMMIDict, Vector2iHash> _mmi_nodes;
// A pair of MMI and MM RIDs, freed in destructor, stored as
// _mmi_rids{region_loc} -> mesh{v2i(mesh_id,lod)} -> cell{v2i} -> std::pair<mmi_RID, mm_RID>
// Region MMI containers named Terrain3D/MMI/Region* are stored here as
// _mmi_containers{region_loc} -> Node3D
std::unordered_map<Vector2i, Node3D *, Vector2iHash> _mmi_containers;
using CellMMIDict = std::unordered_map<Vector2i, std::pair<RID, RID>, Vector2iHash>;
using MeshMMIDict = std::unordered_map<Vector2i, CellMMIDict, Vector2iHash>;
std::unordered_map<Vector2i, MeshMMIDict, Vector2iHash> _mmi_rids;
// MMI Updates tracked in a unique Set of <region_location, mesh_id>
// <V2I_MAX, -2> means destroy first, then update everything
@ -47,17 +44,19 @@ private:
V2IIntPair _queued_updates;
uint32_t _density_counter = 0;
bool _show_instances = true;
uint32_t _get_density_count(const real_t p_density);
void _process_updates();
void _update_mmi_by_region(const Terrain3DRegion *p_region, const int p_mesh_id);
void _set_mmi_lod_ranges(MultiMeshInstance3D *p_mmi, const Ref<Terrain3DMeshAsset> &p_ma, const int p_lod);
void _rebuild_ma_meshes();
void _set_mmi_lod_ranges(RID p_mmi, const Ref<Terrain3DMeshAsset> &p_ma, const int p_lod);
void _update_vertex_spacing(const real_t p_vertex_spacing);
void _destroy_mmi_by_mesh(const int p_mesh_id);
void _destroy_mmi_by_location(const Vector2i &p_region_loc, const int p_mesh_id);
void _destroy_mmi_by_cell(const Vector2i &p_region_loc, const int p_mesh_id, const Vector2i p_cell, const int p_lod = INT32_MAX);
void _backup_region(const Ref<Terrain3DRegion> &p_region);
Ref<MultiMesh> _create_multimesh(const int p_mesh_id, const int p_lod, const TypedArray<Transform3D> &p_xforms = TypedArray<Transform3D>(), const PackedColorArray &p_colors = PackedColorArray()) const;
RID _create_multimesh(const int p_mesh_id, const int p_lod, const TypedArray<Transform3D> &p_xforms = TypedArray<Transform3D>(), const PackedColorArray &p_colors = PackedColorArray()) const;
Vector2i _get_cell(const Vector3 &p_global_position, const int p_region_size) const;
Array _get_usable_height(const Vector3 &p_global_position, const Vector2 &p_slope_range, const bool p_on_collision, const real_t p_raycast_start) const;
@ -88,7 +87,8 @@ public:
void update_mmis(const int p_mesh_id = -1, const Vector2i &p_region_loc = V2I_MAX, const bool p_rebuild = false);
void reset_density_counter() { _density_counter = 0; }
void dump_mmis();
void set_show_instances(const bool p_visible);
bool get_show_instances() const { return _show_instances; }
protected:
static void _bind_methods();

View File

@ -10,7 +10,6 @@
#include <godot_cpp/classes/standard_material3d.hpp>
#include "logger.h"
#include "terrain_3d_instancer.h"
#include "terrain_3d_mesh_asset.h"
///////////////////////////
@ -134,6 +133,7 @@ void Terrain3DMeshAsset::clear() {
_height_offset = 0.f;
_density = 10.f;
_cast_shadows = SHADOWS_ON;
_visibility_layers = 1;
_material_override.unref();
_material_overlay.unref();
_last_lod = MAX_LOD_COUNT - 1;
@ -217,6 +217,14 @@ void Terrain3DMeshAsset::set_instance_count(const uint32_t p_amount) {
void Terrain3DMeshAsset::set_scene_file(const Ref<PackedScene> &p_scene_file) {
SET_IF_DIFF(_packed_scene, p_scene_file);
scene_file_changed = true;
LOG(INFO, "Setting scene file: ", p_scene_file);
LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id);
emit_signal("instancer_setting_changed", _id);
}
void Terrain3DMeshAsset::parse_scene_meshes() {
LOG(INFO, " parsing scene file and storing meshes");
_meshes.clear();
if (_packed_scene.is_valid()) {
Node *node = _packed_scene->instantiate();
@ -225,7 +233,7 @@ void Terrain3DMeshAsset::set_scene_file(const Ref<PackedScene> &p_scene_file) {
_packed_scene.unref();
return;
}
LOG(INFO, "ID ", _id, ", ", _name, ": Setting scene file and instantiating node: ", p_scene_file);
LOG(INFO, "ID ", _id, ", ", _name, ": Instantiating node: ", _packed_scene->get_name());
_generated_type = TYPE_NONE;
_height_offset = 0.0f;
_material_override.unref();
@ -271,6 +279,10 @@ void Terrain3DMeshAsset::set_scene_file(const Ref<PackedScene> &p_scene_file) {
}
// Duplicate the mesh to make each Terrain3DMeshAsset unique
Ref<Mesh> mesh = mi->get_mesh()->duplicate();
if (mesh.is_null()) {
LOG(WARN, "MeshInstance3D ", mi->get_name(), " has no mesh, skipping");
continue;
}
// Apply the active material from the scene to the mesh, including MI or Geom overrides
for (int j = 0; j < mi->get_surface_override_material_count(); j++) {
Ref<Material> mat = mi->get_active_material(j);
@ -282,6 +294,10 @@ void Terrain3DMeshAsset::set_scene_file(const Ref<PackedScene> &p_scene_file) {
}
if (_meshes.size() > 0) {
Ref<Mesh> mesh = _meshes[0];
if (mesh.is_null()) {
LOG(ERROR, "First mesh is null after loading scene");
return;
}
_density = CLAMP(10.f / mesh->get_aabb().get_volume(), 0.01f, 10.0f);
} else {
set_generated_type(TYPE_TEXTURE_CARD);
@ -404,6 +420,13 @@ ShadowCasting Terrain3DMeshAsset::get_lod_cast_shadows(const int p_lod_id) const
return _cast_shadows;
}
inline void Terrain3DMeshAsset::set_visibility_layers(const uint32_t p_layers) {
SET_IF_DIFF(_visibility_layers, p_layers);
LOG(INFO, _name, ": Setting visibility layers: ", p_layers);
LOG(DEBUG, "Emitting instancer_setting_changed, ID: ", _id);
emit_signal("instancer_setting_changed", _id);
}
void Terrain3DMeshAsset::set_material_override(const Ref<Material> &p_material) {
SET_IF_DIFF(_material_override, p_material);
LOG(INFO, "ID ", _id, ", ", _name, ": Setting material override: ", p_material);
@ -521,6 +544,10 @@ void Terrain3DMeshAsset::set_fade_margin(const real_t p_fade_margin) {
emit_signal("instancer_setting_changed", _id);
}
void Terrain3DMeshAsset::set_scene_file_changed(const bool p_changed) {
SET_IF_DIFF(scene_file_changed, p_changed);
}
///////////////////////////
// Protected Functions
///////////////////////////
@ -592,6 +619,8 @@ void Terrain3DMeshAsset::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_density"), &Terrain3DMeshAsset::get_density);
ClassDB::bind_method(D_METHOD("set_cast_shadows", "mode"), &Terrain3DMeshAsset::set_cast_shadows);
ClassDB::bind_method(D_METHOD("get_cast_shadows"), &Terrain3DMeshAsset::get_cast_shadows);
ClassDB::bind_method(D_METHOD("set_visibility_layers", "layers"), &Terrain3DMeshAsset::set_visibility_layers);
ClassDB::bind_method(D_METHOD("get_visibility_layers"), &Terrain3DMeshAsset::get_visibility_layers);
ClassDB::bind_method(D_METHOD("set_material_override", "material"), &Terrain3DMeshAsset::set_material_override);
ClassDB::bind_method(D_METHOD("get_material_override"), &Terrain3DMeshAsset::get_material_override);
ClassDB::bind_method(D_METHOD("set_material_overlay", "material"), &Terrain3DMeshAsset::set_material_overlay);
@ -645,6 +674,7 @@ void Terrain3DMeshAsset::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_offset", PROPERTY_HINT_RANGE, "-20.0,20.0,.005"), "set_height_offset", "get_height_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, ".01,10.0,.005"), "set_density", "get_density");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadows", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows", "get_cast_shadows");
ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_visibility_layers", "get_visibility_layers");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material_override", "get_material_override");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_overlay", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material_overlay", "get_material_overlay");

View File

@ -4,7 +4,6 @@
#define TERRAIN3D_MESH_ASSET_CLASS_H
#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/geometry_instance3d.hpp>
#include <godot_cpp/classes/material.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/resource.hpp>
@ -13,10 +12,10 @@
#include "constants.h"
#include "terrain_3d_asset_resource.h"
using ShadowCasting = GeometryInstance3D::ShadowCastingSetting;
constexpr ShadowCasting SHADOWS_ON = GeometryInstance3D::SHADOW_CASTING_SETTING_ON;
constexpr ShadowCasting SHADOWS_OFF = GeometryInstance3D::SHADOW_CASTING_SETTING_OFF;
constexpr ShadowCasting SHADOWS_ONLY = GeometryInstance3D::SHADOW_CASTING_SETTING_SHADOWS_ONLY;
using ShadowCasting = RenderingServer::ShadowCastingSetting;
constexpr ShadowCasting SHADOWS_ON = RenderingServer::SHADOW_CASTING_SETTING_ON;
constexpr ShadowCasting SHADOWS_OFF = RenderingServer::SHADOW_CASTING_SETTING_OFF;
constexpr ShadowCasting SHADOWS_ONLY = RenderingServer::SHADOW_CASTING_SETTING_SHADOWS_ONLY;
class Terrain3DMeshAsset : public Terrain3DAssetResource {
GDCLASS(Terrain3DMeshAsset, Terrain3DAssetResource);
@ -42,6 +41,7 @@ private:
real_t _height_offset = 0.f;
real_t _density = 10.f;
ShadowCasting _cast_shadows = SHADOWS_ON;
uint32_t _visibility_layers = 1;
Ref<Material> _material_override;
Ref<Material> _material_overlay;
int _last_lod = MAX_LOD_COUNT - 1;
@ -59,6 +59,7 @@ private:
static bool _sort_lod_nodes(const Node *a, const Node *b);
Ref<ArrayMesh> _create_generated_mesh(const GenType p_type = TYPE_TEXTURE_CARD) const;
Ref<Material> _get_material();
bool scene_file_changed = false;
public:
Terrain3DMeshAsset() { clear(); }
@ -83,6 +84,7 @@ public:
uint32_t get_instance_count() const { return _instance_count; }
void set_scene_file(const Ref<PackedScene> &p_scene_file);
void parse_scene_meshes();
Ref<PackedScene> get_scene_file() const { return _packed_scene; }
void set_generated_type(const GenType p_type);
GenType get_generated_type() const { return _generated_type; }
@ -96,6 +98,8 @@ public:
void set_cast_shadows(const ShadowCasting p_cast_shadows);
ShadowCasting get_cast_shadows() const { return _cast_shadows; };
ShadowCasting get_lod_cast_shadows(const int p_lod_id) const;
void set_visibility_layers(const uint32_t p_layers);
uint32_t get_visibility_layers() const { return _visibility_layers; }
void set_material_override(const Ref<Material> &p_material);
Ref<Material> get_material_override() const { return _material_override; }
void set_material_overlay(const Ref<Material> &p_material);
@ -140,6 +144,8 @@ public:
real_t get_lod9_range() const { return _lod_ranges[9]; }
void set_fade_margin(const real_t p_fade_margin);
real_t get_fade_margin() const { return _fade_margin; };
bool get_scene_file_changed() const { return scene_file_changed; }
void set_scene_file_changed(const bool p_changed);
protected:
void _validate_property(PropertyInfo &p_property) const;