From 72e714e15be9e1b4b7c69c9896368c10da004951 Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sun, 27 Feb 2022 22:07:42 +0000 Subject: [PATCH] Added `OutputSingleTexture` node for outputting a single texture index per voxel --- doc/source/changelog.md | 1 + generators/graph/voxel_generator_graph.cpp | 78 ++++++++++++++- generators/graph/voxel_generator_graph.h | 108 +++++++++++---------- generators/graph/voxel_graph_node_db.cpp | 16 +++ 4 files changed, 151 insertions(+), 52 deletions(-) diff --git a/doc/source/changelog.md b/doc/source/changelog.md index 23827b76..b282103c 100644 --- a/doc/source/changelog.md +++ b/doc/source/changelog.md @@ -22,6 +22,7 @@ Godot 4 is required from this version. - `VoxelGeneratorGraph`: editor: unconnected inputs show their default value directly on the node - `VoxelGeneratorGraph`: editor: allow to change the axes on preview nodes 3D slices - `VoxelGeneratorGraph`: editor: replace existing connection if dragging from/to an input port having one already + - `VoxelGeneratorGraph`: added `OutputSingleTexture` node for outputting a single texture index per voxel, as an alternative to weights. This is specific to smooth voxels. - Smooth voxels - SDF data is now encoded with `inorm8` and `inorm16`, instead of an arbitrary version of `unorm8` and `unorm16`. Migration code is in place to load old save files, but *do a backup before running your project with the new version*. diff --git a/generators/graph/voxel_generator_graph.cpp b/generators/graph/voxel_generator_graph.cpp index d83e5824..143c164c 100644 --- a/generators/graph/voxel_generator_graph.cpp +++ b/generators/graph/voxel_generator_graph.cpp @@ -271,7 +271,7 @@ int VoxelGeneratorGraph::get_used_channels_mask() const { if (runtime_ptr->type_output_index != -1) { mask |= (1 << VoxelBufferInternal::CHANNEL_TYPE); } - if (runtime_ptr->weight_outputs_count > 0) { + if (runtime_ptr->weight_outputs_count > 0 || runtime_ptr->single_texture_output_index != -1) { mask |= (1 << VoxelBufferInternal::CHANNEL_INDICES); mask |= (1 << VoxelBufferInternal::CHANNEL_WEIGHTS); } @@ -427,6 +427,34 @@ void VoxelGeneratorGraph::gather_indices_and_weights(Span we } } +// TODO Optimization: this is a simplified output using a complex system. +// We should implement a texturing system that knows each voxel has a single texture. +void gather_indices_and_weights_from_single_texture(unsigned int output_buffer_index, + const VoxelGraphRuntime::State &state, Vector3i rmin, Vector3i rmax, int ry, + VoxelBufferInternal &out_voxel_buffer) { + VOXEL_PROFILE_SCOPE(); + + const VoxelGraphRuntime::Buffer &buffer = state.get_buffer(output_buffer_index); + Span buffer_data = Span(buffer.data, buffer.size); + + // TODO Should not really be here, but may work. Left here for now so all code for this is in one place + const uint16_t encoded_weights = encode_weights_to_packed_u16(255, 0, 0, 0); + out_voxel_buffer.clear_channel(VoxelBufferInternal::CHANNEL_WEIGHTS, encoded_weights); + + unsigned int value_index = 0; + for (int rz = rmin.z; rz < rmax.z; ++rz) { + for (int rx = rmin.x; rx < rmax.x; ++rx) { + const uint8_t index = math::clamp(int(Math::round(buffer_data[value_index])), 0, 15); + // Make sure other indices are different so the weights associated with them don't override the first + // index's weight + const uint8_t other_index = (index == 0 ? 1 : 0); + const uint16_t encoded_indices = encode_indices_to_packed_u16(index, other_index, other_index, other_index); + out_voxel_buffer.set_voxel(encoded_indices, rx, ry, rz, VoxelBufferInternal::CHANNEL_INDICES); + ++value_index; + } + } +} + template void fill_zx_sdf_slice(Span channel_data, float sdf_scale, Vector3i rmin, Vector3i rmax, int ry, int x_stride, const float *src_data, Vector3i buffer_size, F convert_func) { @@ -670,6 +698,16 @@ VoxelGenerator::Result VoxelGeneratorGraph::generate_block(VoxelGenerator::Voxel } } + if (runtime_ptr->single_texture_output_index != -1 && !sdf_is_air) { + const math::Interval index_range = cache.state.get_range(runtime_ptr->single_texture_output_index); + if (index_range.is_single_value()) { + out_buffer.fill_area(int(index_range.min), rmin, rmax, type_channel); + } else { + required_outputs[required_outputs_count] = runtime_ptr->single_texture_output_index; + ++required_outputs_count; + } + } + if (required_outputs_count == 0) { // We found all we need with range analysis, no need to calculate per voxel. continue; @@ -714,6 +752,11 @@ VoxelGenerator::Result VoxelGeneratorGraph::generate_block(VoxelGenerator::Voxel type_buffer, out_buffer, type_channel, type_channel_depth, rmin, rmax, ry); } + if (runtime_ptr->single_texture_output_index != -1) { + gather_indices_and_weights_from_single_texture(runtime_ptr->single_texture_output_buffer_index, + cache.state, rmin, rmax, ry, out_buffer); + } + if (runtime_ptr->weight_outputs_count > 0) { gather_indices_and_weights( to_span_const(runtime_ptr->weight_outputs, runtime_ptr->weight_outputs_count), @@ -746,6 +789,19 @@ VoxelGenerator::Result VoxelGeneratorGraph::generate_block(VoxelGenerator::Voxel return result; } +static bool has_output_type( + const VoxelGraphRuntime &runtime, const ProgramGraph &graph, VoxelGeneratorGraph::NodeTypeID node_type_id) { + for (unsigned int other_output_index = 0; other_output_index < runtime.get_output_count(); ++other_output_index) { + const VoxelGraphRuntime::OutputInfo output = runtime.get_output_info(other_output_index); + const ProgramGraph::Node *node = graph.get_node(output.node_id); + ERR_CONTINUE(node == nullptr); + if (node->type_id == VoxelGeneratorGraph::NODE_OUTPUT_WEIGHT) { + return true; + } + } + return false; +} + VoxelGraphRuntime::CompilationResult VoxelGeneratorGraph::compile() { const int64_t time_before = Time::get_singleton()->get_ticks_usec(); @@ -841,6 +897,25 @@ VoxelGraphRuntime::CompilationResult VoxelGeneratorGraph::compile() { } break; + case NODE_OUTPUT_SINGLE_TEXTURE: + if (r->single_texture_output_buffer_index != -1) { + VoxelGraphRuntime::CompilationResult error; + error.success = false; + error.message = TTR("Multiple TYPE outputs are not supported"); + error.node_id = output.node_id; + return error; + } + if (has_output_type(runtime, _graph, VoxelGeneratorGraph::NODE_OUTPUT_WEIGHT)) { + VoxelGraphRuntime::CompilationResult error; + error.success = false; + error.message = TTR("Using both OutputWeight nodes and an OutputSingleTexture node is not allowed"); + error.node_id = output.node_id; + return error; + } + r->single_texture_output_index = output_index; + r->single_texture_output_buffer_index = output.buffer_address; + break; + default: break; } @@ -1790,6 +1865,7 @@ void VoxelGeneratorGraph::_bind_methods() { BIND_ENUM_CONSTANT(NODE_FAST_NOISE_2_2D); BIND_ENUM_CONSTANT(NODE_FAST_NOISE_2_3D); #endif + BIND_ENUM_CONSTANT(NODE_OUTPUT_SINGLE_TEXTURE); BIND_ENUM_CONSTANT(NODE_TYPE_COUNT); } diff --git a/generators/graph/voxel_generator_graph.h b/generators/graph/voxel_generator_graph.h index 0d1fed58..438bb3cc 100644 --- a/generators/graph/voxel_generator_graph.h +++ b/generators/graph/voxel_generator_graph.h @@ -15,61 +15,61 @@ class VoxelGeneratorGraph : public VoxelGenerator { public: static const char *SIGNAL_NODE_NAME_CHANGED; + // Node indexes within the DB. + // Don't use these in saved data, + // they can change depending on which features the module is compiled with. enum NodeTypeID { - NODE_CONSTANT = 0, - NODE_INPUT_X = 1, - NODE_INPUT_Y = 2, - NODE_INPUT_Z = 3, - NODE_OUTPUT_SDF = 4, - NODE_ADD = 5, - NODE_SUBTRACT = 6, - NODE_MULTIPLY = 7, - NODE_DIVIDE = 8, - NODE_SIN = 9, - NODE_FLOOR = 10, - NODE_ABS = 11, - NODE_SQRT = 12, - NODE_FRACT = 13, - NODE_STEPIFY = 14, - NODE_WRAP = 15, - NODE_MIN = 16, - NODE_MAX = 17, - NODE_DISTANCE_2D = 18, - NODE_DISTANCE_3D = 19, - NODE_CLAMP = 20, - NODE_MIX = 21, - NODE_REMAP = 22, - NODE_SMOOTHSTEP = 23, - NODE_CURVE = 24, - NODE_SELECT = 25, - NODE_NOISE_2D = 26, - NODE_NOISE_3D = 27, - NODE_IMAGE_2D = 28, - NODE_SDF_PLANE = 29, - NODE_SDF_BOX = 30, - NODE_SDF_SPHERE = 31, - NODE_SDF_TORUS = 32, - NODE_SDF_PREVIEW = 33, // For debugging - NODE_SDF_SPHERE_HEIGHTMAP = 34, - NODE_SDF_SMOOTH_UNION = 35, - NODE_SDF_SMOOTH_SUBTRACT = 36, - NODE_NORMALIZE_3D = 37, - NODE_FAST_NOISE_2D = 38, - NODE_FAST_NOISE_3D = 39, - NODE_FAST_NOISE_GRADIENT_2D = 40, - NODE_FAST_NOISE_GRADIENT_3D = 41, - NODE_OUTPUT_WEIGHT = 42, - NODE_OUTPUT_TYPE = 43, + NODE_CONSTANT, + NODE_INPUT_X, + NODE_INPUT_Y, + NODE_INPUT_Z, + NODE_OUTPUT_SDF, + NODE_ADD, + NODE_SUBTRACT, + NODE_MULTIPLY, + NODE_DIVIDE, + NODE_SIN, + NODE_FLOOR, + NODE_ABS, + NODE_SQRT, + NODE_FRACT, + NODE_STEPIFY, + NODE_WRAP, + NODE_MIN, + NODE_MAX, + NODE_DISTANCE_2D, + NODE_DISTANCE_3D, + NODE_CLAMP, + NODE_MIX, + NODE_REMAP, + NODE_SMOOTHSTEP, + NODE_CURVE, + NODE_SELECT, + NODE_NOISE_2D, + NODE_NOISE_3D, + NODE_IMAGE_2D, + NODE_SDF_PLANE, + NODE_SDF_BOX, + NODE_SDF_SPHERE, + NODE_SDF_TORUS, + NODE_SDF_PREVIEW, // For debugging + NODE_SDF_SPHERE_HEIGHTMAP, + NODE_SDF_SMOOTH_UNION, + NODE_SDF_SMOOTH_SUBTRACT, + NODE_NORMALIZE_3D, + NODE_FAST_NOISE_2D, + NODE_FAST_NOISE_3D, + NODE_FAST_NOISE_GRADIENT_2D, + NODE_FAST_NOISE_GRADIENT_3D, + NODE_OUTPUT_WEIGHT, + NODE_OUTPUT_TYPE, #ifdef VOXEL_ENABLE_FAST_NOISE_2 - NODE_FAST_NOISE_2_2D = 44, - NODE_FAST_NOISE_2_3D = 45, + NODE_FAST_NOISE_2_2D, + NODE_FAST_NOISE_2_3D, #endif + NODE_OUTPUT_SINGLE_TEXTURE, -#ifdef VOXEL_ENABLE_FAST_NOISE_2 - NODE_TYPE_COUNT = 46 -#else - NODE_TYPE_COUNT = 44 -#endif + NODE_TYPE_COUNT }; VoxelGeneratorGraph(); @@ -245,10 +245,16 @@ private: // Indices that are not used in the graph. // This is used when there are less than 4 texture weight outputs. FixedArray spare_texture_indices; + int sdf_output_index = -1; int sdf_output_buffer_index = -1; + int type_output_index = -1; int type_output_buffer_index = -1; + + int single_texture_output_index = -1; + int single_texture_output_buffer_index = -1; + FixedArray weight_outputs; // List of indices to feed queries. The order doesn't matter, can be different from `weight_outputs`. FixedArray weight_output_indices; diff --git a/generators/graph/voxel_graph_node_db.cpp b/generators/graph/voxel_graph_node_db.cpp index 880f0f06..42042c51 100644 --- a/generators/graph/voxel_graph_node_db.cpp +++ b/generators/graph/voxel_graph_node_db.cpp @@ -343,6 +343,22 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() { ctx.set_output(0, a); }; } + { + NodeType &t = types[VoxelGeneratorGraph::NODE_OUTPUT_SINGLE_TEXTURE]; + t.name = "OutputSingleTexture"; + t.category = CATEGORY_OUTPUT; + t.inputs.push_back(Port("index")); + t.outputs.push_back(Port("_out")); + t.process_buffer_func = [](ProcessBufferContext &ctx) { + const VoxelGraphRuntime::Buffer &input = ctx.get_input(0); + VoxelGraphRuntime::Buffer &out = ctx.get_output(0); + memcpy(out.data, input.data, input.size * sizeof(float)); + }; + t.range_analysis_func = [](RangeAnalysisContext &ctx) { + const Interval a = ctx.get_input(0); + ctx.set_output(0, a); + }; + } { NodeType &t = types[VoxelGeneratorGraph::NODE_ADD]; t.name = "Add";