Convert instancer MMI nodes to RS

This commit is contained in:
aidandavey 2025-10-05 21:30:16 +01:00
parent 8cade44ba9
commit 4bdb15c476
4 changed files with 93 additions and 130 deletions

View File

@ -1,7 +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"
#include "terrain_3d_instancer.h"
@ -111,15 +111,8 @@ 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);
}
// Get or create mesh dict
MeshMMIDict &mesh_mmi_dict = _mmi_nodes[region_loc];
// Verify mesh id is valid, enabled, and has MeshInstance3Ds
Ref<Terrain3DMeshAsset> ma = _terrain->get_assets()->get_mesh_asset(p_mesh_id);
@ -142,6 +135,7 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
// Process cells
Dictionary cell_inst_dict = mesh_inst_dict[p_mesh_id];
Array cell_locations = cell_inst_dict.keys();
RID shadow_impostor_source_mm;
for (int c = 0; c < cell_locations.size(); c++) {
Vector2i cell = cell_locations[c];
@ -179,7 +173,6 @@ void Terrain3DInstancer::_update_mmi_by_region(const Terrain3DRegion *p_region,
// 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;
for (int lod = ma->get_last_lod(); lod >= Terrain3DMeshAsset::SHADOW_LOD_ID; lod--) {
// Don't create shadow MMI if not needed
@ -195,38 +188,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 +213,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 == ma->get_last_lod()) {
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 +252,43 @@ 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));
RS->multimesh_set_mesh(mm, ma->get_mesh(lod)->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 = RS->multimesh_get_mesh(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()) {
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;
RID &mm = cell_mmi_dict[cell].second;
if (mm.is_valid()) {
RS->multimesh_set_custom_aabb(mm, mm_custom_aabb);
}
}
}
}
void Terrain3DInstancer::_set_mmi_lod_ranges(MultiMeshInstance3D *p_mmi, const Ref<Terrain3DMeshAsset> &p_ma, const int p_lod) {
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 +304,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) {
@ -441,37 +414,25 @@ 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()) {
if (lod == 0) {
ma->update_instance_count(-mmi->get_multimesh()->get_instance_count());
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 == ma->get_last_lod()) {
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);
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 +446,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 +459,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;
@ -1337,20 +1294,16 @@ 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(WARN, "Dumping MMI tree");
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));
//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));
//LOG(MESG, "mesh_mmi_dict mesh: ", j.first, ", dict ptr: ", &j.second.first);
for (auto &k : j.second) {
LOG(MESG, "cell_mmi_dict cell: ", k.first, ", mmi ptr: ", ptr_to_str(k.second));
//LOG(MESG, "cell_mmi_dict cell: ", k.first, ", mmi ptr: ", (k.second));
}
}
}

View File

@ -28,16 +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>;
// MMI Objects, freed in destructor, stored as
// _mmi_nodes{region_loc} -> mesh{v2i(mesh_id,lod)} -> cell{v2i} -> RID
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_nodes;
// Region MMI containers named Terrain3D/MMI/Region* are stored here as
// _mmi_containers{region_loc} -> Node3D
std::unordered_map<Vector2i, Node3D *, Vector2iHash> _mmi_containers;
// MMI Updates tracked in a unique Set of <region_location, mesh_id>
// <V2I_MAX, -2> means destroy first, then update everything
// <V2I_MAX, -1> means update everything
@ -51,13 +47,13 @@ private:
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 _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;

View File

@ -132,6 +132,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;
@ -377,6 +378,13 @@ ShadowCasting Terrain3DMeshAsset::get_lod_cast_shadows(const int p_lod_id) const
return _cast_shadows;
}
inline void Terrain3DMeshAsset::set_visibility_layers(const int p_layers) {
LOG(INFO, _name, ": Setting visibility layers: ", p_layers);
_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, _name, ": Setting material override: ", p_material);
@ -562,6 +570,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);
@ -615,6 +625,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,7 @@
#define TERRAIN3D_MESH_ASSET_CLASS_H
#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/geometry_instance3d.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 +13,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 +42,7 @@ private:
real_t _height_offset = 0.f;
real_t _density = 10.f;
ShadowCasting _cast_shadows = SHADOWS_ON;
int _visibility_layers = 1;
Ref<Material> _material_override;
Ref<Material> _material_overlay;
int _last_lod = MAX_LOD_COUNT - 1;
@ -96,6 +97,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 int p_layers);
int 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);