Added OutputSingleTexture node for outputting a single texture index per voxel

This commit is contained in:
Marc Gilleron 2022-02-27 22:07:42 +00:00
parent 6b6255e93d
commit 72e714e15b
4 changed files with 151 additions and 52 deletions

View File

@ -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*.

View File

@ -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<const WeightOutput> 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<const float> buffer_data = Span<const float>(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 <typename F, typename Data_T>
void fill_zx_sdf_slice(Span<Data_T> 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);
}

View File

@ -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<uint8_t, 4> 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<WeightOutput, 16> weight_outputs;
// List of indices to feed queries. The order doesn't matter, can be different from `weight_outputs`.
FixedArray<unsigned int, 16> weight_output_indices;

View File

@ -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";