Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions addons/4d/SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ env.Append(
"../../physics/shapes",
"../../render",
"../../render/wireframe_canvas",
"../../render/cross_section"
]
)

Expand All @@ -49,8 +50,10 @@ sources = (
+ Glob("../../physics/shapes/*.cpp")
+ Glob("../../render/*.cpp")
+ Glob("../../render/wireframe_canvas/*.cpp")
+ Glob("../../render/cross_section/*.cpp")
)


env.Append(CPPDEFINES=["GDEXTENSION"])

if env["target"] == "editor":
Expand Down Expand Up @@ -132,6 +135,8 @@ env["BUILDERS"]["GLSL_HEADER"] = Builder(
suffix="glsl.gen.h",
)

env.GLSL_HEADER("../../render/cross_section/cross_section_shader.glsl")

target_file_path = "../../editor/icons/editor_4d_icons.gen.h"
icon_sources = Glob("icons/4D.svg") + Glob("icons/Node4D.svg")
header_builders.make_svg_icons_action([target_file_path], icon_sources, env)
6 changes: 6 additions & 0 deletions addons/4d/doc_classes/Material4D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
[b]Note:[/b] When making materials, use the high-level properties from the derived classes instead, such as [member WireMaterial4D.albedo_source] or [member TetraMaterial4D.albedo_source].
</description>
</method>
<method name="get_cross_section_material">
<return type="ShaderMaterial" />
<description>
Returns a 3D material to be used by the cross-section renderer. The returned material is automatically updated to match the properties of this material.
</description>
</method>
<method name="is_default_material" qualifiers="const">
<return type="bool" />
<description>
Expand Down
24 changes: 24 additions & 0 deletions addons/4d/doc_classes/Mesh4D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
Callback method that should return the vertices of a mesh. Do not call this method. This can be overridden by derived classes when creating a custom mesh type in GDScript or another scripting language. See [method get_vertices] for details of the returned data.
</description>
</method>
<method name="_update_cross_section_mesh" qualifiers="virtual">
<return type="void" />
<description>
Updates the 3D mesh this mesh uses for cross-section rendering. Can be called to force the cross-section mesh to be updated, but typically should only be called internally. Subclasses can override this, but most cases should be covered by the default implementation.
</description>
</method>
<method name="_validate_material_for_mesh" qualifiers="virtual">
<return type="void" />
<param index="0" name="material" type="Material4D" />
Expand All @@ -48,6 +54,12 @@
[b]Note[/b]: This only looks at edge indices. It does not remove duplicate vertices, nor does it have access to the vertices array.
</description>
</method>
<method name="get_cross_section_mesh">
<return type="ArrayMesh" />
<description>
Returns a 3D mesh that can be used to render 3D cross-sections of this mesh in the cross-section renderer. This mesh will only render properly with the cross-section material from [method Material4D.get_cross_section_material].
</description>
</method>
<method name="get_edge_indices">
<return type="PackedInt32Array" />
<description>
Expand Down Expand Up @@ -82,6 +94,12 @@
If a mesh has valid data, the result of the validation will be cached until the mesh data changes, so this method may be called multiple times without performance concerns. This also means this method should be called after generating a mesh. This especially useful when generating a mesh on a thread; calling this on the thread will avoid stalling the main thread with mesh validation during rendering.
</description>
</method>
<method name="mark_cross_section_mesh_dirty">
<return type="void" />
<description>
Marks the cross-section mesh as dirty, meaning it needs to be updated. Must be called by subclasses whenever the underlying 4D mesh data changes.
</description>
</method>
<method name="reset_mesh_data_validation">
<return type="void" />
<description>
Expand All @@ -101,6 +119,12 @@
Converts this Mesh4D to a [WireMesh4D]. This method will use the best conversion available. For example, a [BoxTetraMesh4D] will be converted to a [BoxWireMesh4D]. If no specialized conversion is available, the method will return an [ArrayWireMesh4D]. See [method to_array_wire_mesh] for more details.
</description>
</method>
<method name="update_cross_section_mesh">
<return type="void" />
<description>
Updates the 3D mesh this mesh uses for cross-section rendering. Can be called to force the cross-section mesh to be updated, but typically should only be called internally.
</description>
</method>
<method name="validate_material_for_mesh">
<return type="void" />
<param index="0" name="material" type="Material4D" />
Expand Down
3 changes: 3 additions & 0 deletions addons/4d/doc_classes/TetraMaterial4D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<member name="albedo_source" type="int" setter="set_albedo_source" getter="get_albedo_source" enum="TetraMaterial4D.TetraColorSource" default="0">
The albedo source of the material, an enum specific to tetrahedral meshes. Can be a single color, per-vertex, per-cell, per-cell UVW coordinates, 4D texture, or a combination of single color and the other color sources.
</member>
<member name="texture" type="Texture3D" setter="set_texture" getter="get_texture">
The albedo texture of the material as a Texture3D. Used by the cross-section renderer. Used when albedo_source is [constant TETRA_COLOR_SOURCE_CELL_UVW_ONLY] or [constant TETRA_COLOR_SOURCE_CELL_UVW_AND_SINGLE].
</member>
</members>
<constants>
<constant name="TETRA_COLOR_SOURCE_SINGLE_COLOR" value="0" enum="TetraColorSource">
Expand Down
18 changes: 18 additions & 0 deletions model/material_4d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Material4D::ColorSourceFlags Material4D::get_albedo_source_flags() const {

void Material4D::set_albedo_source_flags(const ColorSourceFlags p_albedo_source_flags) {
_albedo_source_flags = p_albedo_source_flags;
update_cross_section_material();
}

Color Material4D::get_albedo_color() const {
Expand All @@ -120,6 +121,7 @@ Color Material4D::get_albedo_color() const {

void Material4D::set_albedo_color(const Color &p_albedo_color) {
_albedo_color = p_albedo_color;
update_cross_section_material();
}

PackedColorArray Material4D::get_albedo_color_array() const {
Expand All @@ -128,10 +130,12 @@ PackedColorArray Material4D::get_albedo_color_array() const {

void Material4D::set_albedo_color_array(const PackedColorArray &p_albedo_color_array) {
_albedo_color_array = p_albedo_color_array;
update_cross_section_material();
}

void Material4D::append_albedo_color(const Color &p_albedo_color) {
_albedo_color_array.push_back(p_albedo_color);
update_cross_section_material();
}

void Material4D::resize_albedo_color_array(const int64_t p_size, const Color &p_fill_color) {
Expand All @@ -140,6 +144,18 @@ void Material4D::resize_albedo_color_array(const int64_t p_size, const Color &p_
for (int64_t i = existing_size; i < p_size; i++) {
_albedo_color_array.set(i, p_fill_color);
}
update_cross_section_material();
}

Ref<ShaderMaterial> Material4D::get_cross_section_material() {
if (_cross_section_material.is_null()) {
_cross_section_material.instantiate();
update_cross_section_material();
}
return _cross_section_material;
}

void Material4D::update_cross_section_material() {
}

void Material4D::_bind_methods() {
Expand All @@ -158,6 +174,8 @@ void Material4D::_bind_methods() {
ClassDB::bind_method(D_METHOD("append_albedo_color", "albedo_color"), &Material4D::append_albedo_color);
ClassDB::bind_method(D_METHOD("resize_albedo_color_array", "size", "fill_color"), &Material4D::resize_albedo_color_array, DEFVAL(Color(1, 1, 1, 1)));

ClassDB::bind_method(D_METHOD("get_cross_section_material"), &Material4D::get_cross_section_material);

BIND_ENUM_CONSTANT(COLOR_SOURCE_FLAG_SINGLE_COLOR);
BIND_ENUM_CONSTANT(COLOR_SOURCE_FLAG_PER_VERT);
BIND_ENUM_CONSTANT(COLOR_SOURCE_FLAG_PER_EDGE);
Expand Down
8 changes: 8 additions & 0 deletions model/material_4d.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

#if GDEXTENSION
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/classes/shader_material.hpp>
#include <godot_cpp/core/gdvirtual.gen.inc>
#elif GODOT_MODULE
#include "core/io/resource.h"
#include "scene/resources/material.h"
#endif

class Mesh4D;
Expand Down Expand Up @@ -44,6 +46,10 @@ class Material4D : public Resource {
PackedColorArray _albedo_color_array;
Color _albedo_color = Color(1, 1, 1, 1);
ColorSourceFlags _albedo_source_flags = COLOR_SOURCE_FLAG_SINGLE_COLOR;
Ref<ShaderMaterial> _cross_section_material;

// Update _cross_section_material to match current settings on the material, skip if _cross_section_material is null.
virtual void update_cross_section_material();

public:
virtual Color get_albedo_color_of_edge(const int64_t p_edge_index, const Ref<Mesh4D> &p_for_mesh);
Expand All @@ -60,6 +66,8 @@ class Material4D : public Resource {
void set_albedo_color_array(const PackedColorArray &p_albedo_color_array);
void append_albedo_color(const Color &p_albedo_color);
void resize_albedo_color_array(const int64_t p_size, const Color &p_fill_color = Color(1, 1, 1, 1));

Ref<ShaderMaterial> get_cross_section_material();
};

VARIANT_ENUM_CAST(Material4D::ColorSourceFlags);
17 changes: 17 additions & 0 deletions model/mesh_4d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ bool Mesh4D::validate_mesh_data() {
return ret;
}

void Mesh4D::update_cross_section_mesh() {
GDVIRTUAL_CALL(_update_cross_section_mesh);
}

void Mesh4D::validate_material_for_mesh(const Ref<Material4D> &p_material) {
GDVIRTUAL_CALL(_validate_material_for_mesh, p_material);
const Material4D::ColorSourceFlags albedo_source_flags = p_material->get_albedo_source_flags();
Expand Down Expand Up @@ -93,6 +97,15 @@ Ref<WireMesh4D> Mesh4D::to_wire_mesh() {
return to_array_wire_mesh();
}

Ref<ArrayMesh> Mesh4D::get_cross_section_mesh() {
if (_is_cross_section_mesh_dirty || _cross_section_mesh.is_null()) {
_cross_section_mesh.instantiate();
update_cross_section_mesh();
_is_cross_section_mesh_dirty = false;
}
return _cross_section_mesh;
}

Ref<Material4D> Mesh4D::get_material() const {
return _material;
}
Expand Down Expand Up @@ -126,9 +139,12 @@ void Mesh4D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_mesh_data_valid"), &Mesh4D::is_mesh_data_valid);
ClassDB::bind_method(D_METHOD("reset_mesh_data_validation"), &Mesh4D::reset_mesh_data_validation);
ClassDB::bind_method(D_METHOD("validate_material_for_mesh", "material"), &Mesh4D::validate_material_for_mesh);
ClassDB::bind_method(D_METHOD("mark_cross_section_mesh_dirty"), &Mesh4D::mark_cross_section_mesh_dirty);
ClassDB::bind_method(D_METHOD("update_cross_section_mesh"), &Mesh4D::update_cross_section_mesh);

ClassDB::bind_method(D_METHOD("to_array_wire_mesh"), &Mesh4D::to_array_wire_mesh);
ClassDB::bind_method(D_METHOD("to_wire_mesh"), &Mesh4D::to_wire_mesh);
ClassDB::bind_method(D_METHOD("get_cross_section_mesh"), &Mesh4D::get_cross_section_mesh);

ClassDB::bind_method(D_METHOD("get_material"), &Mesh4D::get_material);
ClassDB::bind_method(D_METHOD("set_material", "material"), &Mesh4D::set_material);
Expand All @@ -143,4 +159,5 @@ void Mesh4D::_bind_methods() {
GDVIRTUAL_BIND(_get_vertices);
GDVIRTUAL_BIND(_validate_material_for_mesh, "material");
GDVIRTUAL_BIND(_validate_mesh_data);
GDVIRTUAL_BIND(_update_cross_section_mesh);
}
18 changes: 18 additions & 0 deletions model/mesh_4d.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

#include "material_4d.h"

#if GDEXTENSION
#include <godot_cpp/classes/array_mesh.hpp>
#elif GODOT_MODULE
#include "scene/resources/mesh.h"
#endif

class ArrayWireMesh4D;
class WireMesh4D;

Expand All @@ -10,10 +16,19 @@ class Mesh4D : public Resource {

Ref<Material4D> _material;
bool _is_mesh_data_valid = false;
bool _is_cross_section_mesh_dirty = true;

protected:
Ref<ArrayMesh> _cross_section_mesh;

static void _bind_methods();
virtual bool validate_mesh_data();
// Call when the mesh is modified to indicate that
// the 3D mesh used for cross-section rendering needs to be updated.
void mark_cross_section_mesh_dirty() { _is_cross_section_mesh_dirty = true; };
// Called when the cross-section mesh is requested and the cross-section mesh has been marked dirty.
// Update the mesh referenced by _cross_section_mesh to match the current state of the mesh.
virtual void update_cross_section_mesh();

public:
static PackedInt32Array deduplicate_edge_indices(const PackedInt32Array &p_items);
Expand All @@ -25,6 +40,8 @@ class Mesh4D : public Resource {

Ref<ArrayWireMesh4D> to_array_wire_mesh();
virtual Ref<WireMesh4D> to_wire_mesh();
// Returns a reference to the mesh used for cross-section rendering.
Ref<ArrayMesh> get_cross_section_mesh();

Ref<Material4D> get_material() const;
void set_material(const Ref<Material4D> &p_material);
Expand All @@ -37,5 +54,6 @@ class Mesh4D : public Resource {
GDVIRTUAL0R(PackedVector4Array, _get_edge_positions);
GDVIRTUAL0R(PackedVector4Array, _get_vertices);
GDVIRTUAL0R(bool, _validate_mesh_data);
GDVIRTUAL0(_update_cross_section_mesh);
GDVIRTUAL1(_validate_material_for_mesh, const Ref<Material4D> &);
};
50 changes: 50 additions & 0 deletions model/tetra/tetra_material_4d.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "tetra_material_4d.h"

#include "../../render/cross_section/cross_section_shader.glsl.gen.h"
#include "tetra_mesh_4d.h"

Material4D::ColorSourceFlags TetraMaterial4D::_tetra_source_to_flags(const TetraColorSource p_tetra_source) {
Expand Down Expand Up @@ -122,6 +123,52 @@ void TetraMaterial4D::set_albedo_source(const TetraColorSource p_albedo_source)
_albedo_source = p_albedo_source;
_albedo_source_flags = _tetra_source_to_flags(_albedo_source);
notify_property_list_changed();
update_cross_section_material();
}

Ref<Texture3D> TetraMaterial4D::get_texture() const {
return _texture;
}

void TetraMaterial4D::set_texture(const Ref<Texture3D> &p_texture) {
_texture = p_texture;
update_cross_section_material();
}

void TetraMaterial4D::update_cross_section_material() {
if (_cross_section_material.is_null()) {
return;
}
if (_cross_section_material->get_shader().is_null()) {
// TODO this re-compiles the shader for every material, should cache the Shader object somewhere.
Ref<Shader> cross_section_shader;
cross_section_shader.instantiate();
cross_section_shader->set_code(cross_section_shader_shader_glsl);
_cross_section_material->set_shader(cross_section_shader);
}
Color albedo;
Variant texture;
switch (_albedo_source) {
case TETRA_COLOR_SOURCE_SINGLE_COLOR:
albedo = _albedo_color;
// Setting to a Nil variant resets to the default texture, which is white.
texture = Variant();
break;
case TETRA_COLOR_SOURCE_CELL_UVW_ONLY:
albedo = Color(1.0, 1.0, 1.0);
texture = _texture;
break;
case TETRA_COLOR_SOURCE_CELL_UVW_AND_SINGLE:
albedo = _albedo_color;
texture = _texture;
break;
default:
albedo = Color(1.0, 1.0, 1.0);
texture = Variant();
break;
}
Comment on lines +149 to +169
Copy link
Member

@aaronfranke aaronfranke Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TetraColorSource enum is intended for safe user-friendly setting, but when reading, there are bitwise flags available to simplify this. That code can be replaced with this:

	const ColorSourceFlags flags = get_albedo_source_flags();
	const Color albedo = (flags & Material4D::COLOR_SOURCE_FLAG_SINGLE_COLOR) ? _albedo_color : Color(1.0, 1.0, 1.0);
	// Setting to a Nil variant resets to the default texture, which is white.
	const Variant texture = (flags & Material4D::COLOR_SOURCE_FLAG_CELL_UVW) ? Variant(_texture) : Variant();

I've pushed this in commit f2b29bd

_cross_section_material->set_shader_parameter("albedo", albedo);
_cross_section_material->set_shader_parameter("albedo_texture", texture);
}

void TetraMaterial4D::_get_property_list(List<PropertyInfo> *p_list) const {
Expand All @@ -142,11 +189,14 @@ TetraMaterial4D::TetraMaterial4D() {
void TetraMaterial4D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_albedo_source"), &TetraMaterial4D::get_albedo_source);
ClassDB::bind_method(D_METHOD("set_albedo_source", "albedo_source"), &TetraMaterial4D::set_albedo_source);
ClassDB::bind_method(D_METHOD("get_texture"), &TetraMaterial4D::get_texture);
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TetraMaterial4D::set_texture);

//ADD_GROUP("Albedo", "albedo_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "albedo_source", PROPERTY_HINT_ENUM, "Single Color,Per Vertex Only,Per Cell Only,Cell UVW Only,Texture4D Only,Per Vertex and Single Color,Per Cell and Single Color,Cell UVW and Single Color,Texture4D and Single Color"), "set_albedo_source", "get_albedo_source");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo_color"), "set_albedo_color", "get_albedo_color");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "albedo_color_array"), "set_albedo_color_array", "get_albedo_color_array");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture");

BIND_ENUM_CONSTANT(TETRA_COLOR_SOURCE_SINGLE_COLOR);
BIND_ENUM_CONSTANT(TETRA_COLOR_SOURCE_PER_VERT_ONLY);
Expand Down
Loading