Merge branch 'master' into full_load_mode

# Conflicts:
#	edition/voxel_tool_lod_terrain.cpp
#	generators/graph/voxel_generator_graph.cpp
#	terrain/voxel_lod_terrain.cpp
master
Marc Gilleron 2021-10-31 16:17:27 +00:00
commit ca7ac42cde
38 changed files with 751 additions and 193 deletions

166
.github/workflows/fuzzer.yml vendored Normal file
View File

@ -0,0 +1,166 @@
name: 🐺️ Fuzzer
on:
push:
pull_request:
schedule:
- cron: '0 0 * * *'
# Global Cache Settings
env:
GODOT_BASE_BRANCH: 3.x
SCONS_CACHE_LIMIT: 4096
jobs:
test-project:
runs-on: "ubuntu-20.04"
name: Editor
steps:
# Clone Godot
- uses: actions/checkout@v2
with:
repository: godotengine/godot
ref: 3.x
# Clone our module under the correct directory
- uses: actions/checkout@v2
with:
path: modules/voxel
# Clone fuzzer
- name: Checkout Goost fuzzer backend
uses: actions/checkout@v2
with:
repository: qarmin/Qarminer
ref: 3.x
path: fuzzer
# Currently configuration is done by modifing source code
- name: Configure fuzzer
working-directory: fuzzer
run: |
sed -i 's/var use_parent_methods: bool = false/var use_parent_methods: bool = true/' FunctionExecutor.gd
sed -i 's/var add_to_tree: bool = false/var add_to_tree: bool = true/' FunctionExecutor.gd
sed -i 's/var delay_removing_added_nodes_to_next_frame: bool = false/var delay_removing_added_nodes_to_next_frame: bool = true/' FunctionExecutor.gd
echo "FastNoiseLite" >> classes.txt
echo "FastNoiseLiteGradient" >> classes.txt
echo "Voxel" >> classes.txt
echo "VoxelBlockSerializer" >> classes.txt
echo "VoxelBoxMover" >> classes.txt
echo "VoxelBuffer" >> classes.txt
echo "VoxelColorPalette" >> classes.txt
echo "VoxelGenerator" >> classes.txt
echo "VoxelGeneratorFlat" >> classes.txt
echo "VoxelGeneratorGraph" >> classes.txt
echo "VoxelGeneratorHeightmap" >> classes.txt
echo "VoxelGeneratorImage" >> classes.txt
echo "VoxelGeneratorNoise" >> classes.txt
echo "VoxelGeneratorNoise2D" >> classes.txt
echo "VoxelGeneratorScript" >> classes.txt
echo "VoxelGeneratorWaves" >> classes.txt
echo "VoxelInstanceComponent" >> classes.txt
echo "VoxelInstanceGenerator" >> classes.txt
echo "VoxelInstanceLibrary" >> classes.txt
echo "VoxelInstanceLibraryItem" >> classes.txt
echo "VoxelInstanceLibraryItemBase" >> classes.txt
echo "VoxelInstanceLibrarySceneItem" >> classes.txt
echo "VoxelInstancer" >> classes.txt
echo "VoxelLibrary" >> classes.txt
echo "VoxelLodTerrain" >> classes.txt
echo "VoxelMesher" >> classes.txt
echo "VoxelMesherBlocky" >> classes.txt
echo "VoxelMesherCubes" >> classes.txt
echo "VoxelMesherDMC" >> classes.txt
echo "VoxelMesherTransvoxel" >> classes.txt
echo "VoxelNode" >> classes.txt
echo "VoxelRaycastResult" >> classes.txt
echo "VoxelServer" >> classes.txt
echo "VoxelStream" >> classes.txt
echo "VoxelStreamBlockFiles" >> classes.txt
echo "VoxelStreamRegionFiles" >> classes.txt
echo "VoxelStreamSQLite" >> classes.txt
echo "VoxelStreamScript" >> classes.txt
echo "VoxelTerrain" >> classes.txt
echo "VoxelTool" >> classes.txt
echo "VoxelToolBuffer" >> classes.txt
echo "VoxelToolLodTerrain" >> classes.txt
echo "VoxelToolTerrain" >> classes.txt
echo "VoxelViewer" >> classes.txt
echo "VoxelVoxLoader" >> classes.txt
# Azure repositories are not reliable, we need to prevent azure giving us packages.
- name: Make apt sources.list use the default Ubuntu repositories
run: |
sudo rm -f /etc/apt/sources.list.d/*
sudo cp -f misc/ci/sources.list /etc/apt/sources.list
sudo apt-get update
# Install all packages (except scons)
- name: Configure dependencies
run: |
sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev \
libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev yasm \
xvfb wget unzip
# Upload cache on completion and check it out now
- name: Load .scons_cache directory
id: fuzzer-cache
uses: actions/cache@v2
with:
path: ${{github.workspace}}/.scons_cache/
key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
restore-keys: |
${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
${{github.job}}-${{env.GODOT_BASE_BRANCH}}
# Use python 3.x release (works cross platform; best to keep self contained in it's own step)
- name: Set up Python 3.x
uses: actions/setup-python@v2
with:
# Semantic version range syntax or exact version of a Python version
python-version: '3.x'
# Optional - x64 or x86 architecture, defaults to x64
architecture: 'x64'
# Setup scons, print python version and scons version info, so if anything is broken it won't run the build.
- name: Configuring Python packages
run: |
python -c "import sys; print(sys.version)"
python -m pip install scons
python --version
scons --version
# We should always be explicit with our flags usage here since it's gonna be sure to always set those flags
- name: Compilation
env:
SCONS_CACHE: ${{github.workspace}}/.scons_cache/
run: |
scons -j2 use_asan=yes use_ubsan=yes
- name: Run fuzzer
run: |
DRI_PRIME=0 xvfb-run bin/godot.x11.tools.64s FunctionExecutor.tscn 600 --audio-driver Dummy --video-driver GLES3 --path fuzzer 2>&1 | tee sanitizers_log.txt || true
tail -n 300 sanitizers_log.txt > project_results.txt
- name: Store project results
uses: actions/upload-artifact@v2
with:
name: project-results
path: project_results.txt
- name: Store test results
uses: actions/upload-artifact@v2
with:
name: last-run-results
path: fuzzer/results.txt
- name: Store time results
uses: actions/upload-artifact@v2
with:
name: timer
path: fuzzer/timer.txt
- name: Check fuzzer output
run: |
fuzzer/misc/check_ci_log.py sanitizers_log.txt

97
.github/workflows/test_project.yml vendored Normal file
View File

@ -0,0 +1,97 @@
name: ✈️ Test Project
on:
push:
branches: [ master, github_actions ]
pull_request:
branches: [ master ]
# Global Cache Settings
env:
GODOT_BASE_BRANCH: 3.x
SCONS_CACHE_LIMIT: 4096
jobs:
test-project:
runs-on: "ubuntu-20.04"
name: Editor
steps:
# Clone Godot
- uses: actions/checkout@v2
with:
repository: godotengine/godot
ref: 3.x
# Clone our module under the correct directory
- uses: actions/checkout@v2
with:
path: modules/voxel
# Azure repositories are not reliable, we need to prevent azure giving us packages.
- name: Make apt sources.list use the default Ubuntu repositories
run: |
sudo rm -f /etc/apt/sources.list.d/*
sudo cp -f misc/ci/sources.list /etc/apt/sources.list
sudo apt-get update
# Install all packages (except scons)
- name: Configure dependencies
run: |
sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev \
libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev yasm \
xvfb wget unzip
# Upload cache on completion and check it out now
- name: Load .scons_cache directory
id: test-project-cache
uses: actions/cache@v2
with:
path: ${{github.workspace}}/.scons_cache/
key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
restore-keys: |
${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
${{github.job}}-${{env.GODOT_BASE_BRANCH}}
# Use python 3.x release (works cross platform; best to keep self contained in it's own step)
- name: Set up Python 3.x
uses: actions/setup-python@v2
with:
# Semantic version range syntax or exact version of a Python version
python-version: '3.x'
# Optional - x64 or x86 architecture, defaults to x64
architecture: 'x64'
# Setup scons, print python version and scons version info, so if anything is broken it won't run the build.
- name: Configuring Python packages
run: |
python -c "import sys; print(sys.version)"
python -m pip install scons
python --version
scons --version
# We should always be explicit with our flags usage here since it's gonna be sure to always set those flags
- name: Compilation
env:
SCONS_CACHE: ${{github.workspace}}/.scons_cache/
run: |
scons -j2 use_asan=yes use_ubsan=yes
# Download and extract zip archive with project, folder is renamed to be able to easy change used project
- name: Download test project
run: |
wget https://github.com/qarmin/RegressionTestProject/archive/3.x.zip
unzip 3.x.zip
mv "RegressionTestProject-3.x" "test_project"
# Editor is quite complicated piece of software, so it is easy to introduce bug here
- name: Open and close editor
run: |
DRI_PRIME=0 xvfb-run bin/godot.x11.tools.64s --audio-driver Dummy -e -q --path test_project 2>&1 | tee sanitizers_log.txt || true
modules/voxel/misc/check_ci_log.py sanitizers_log.txt
# Run test project
- name: Run project
run: |
DRI_PRIME=0 xvfb-run bin/godot.x11.tools.64s 30 --video-driver GLES3 --audio-driver Dummy --path test_project 2>&1 | tee sanitizers_log.txt || true
modules/voxel/misc/check_ci_log.py sanitizers_log.txt

View File

@ -14,7 +14,12 @@ static const unsigned int MAX_BLOCK_SIZE = 32;
static const unsigned int MAX_BLOCK_COUNT_PER_REQUEST = 4 * 4 * 4;
static const unsigned int MAX_LOD = 32;
// 24 should be largely enough.
// With a block size of 32 voxels, and if 1 voxel is 1m large,
// then the largest blocks will span 268,435.456 kilometers, which is roughly 20 times Earth's diameter.
// Using a higher maximum can cause int32 overflows when calculating dimensions. There is no use case for it.
static const unsigned int MAX_LOD = 24;
static const unsigned int MAX_VOLUME_EXTENT = 0x1fffffff;
static const unsigned int MAX_VOLUME_SIZE = 2 * MAX_VOLUME_EXTENT; // 1,073,741,822 voxels

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelToolLodTerrain" inherits="VoxelTool" version="3.4">
<brief_description>
Implementation of [VoxelTool] specialized for uses on [VoxelLodTerrain].
</brief_description>
<description>
It's not a class to instantiate alone, you may get it from [VoxelLodTerrain] using the `get_voxel_tool()` method.
</description>
<tutorials>
</tutorials>

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="VoxelToolTerrain" inherits="VoxelTool" version="3.4">
<brief_description>
Implementation of [VoxelTool] optimized for uses on [VoxelTerrain].
Implementation of [VoxelTool] specialized for uses on [VoxelTerrain].
</brief_description>
<description>
It's not a class to instantiate alone, you may get it from [VoxelTerrain] using the `get_voxel_tool()` method.
</description>
<tutorials>
</tutorials>

View File

@ -34,16 +34,21 @@ Ongoing development - `master`
- `VoxelInstancer`: added menu to setup a multimesh item from a scene (similarly to GridMap), which also allows to set up colliders
- `VoxelInstancer`: added initial support for instancing regular scenes (slower than multimeshes)
- `VoxelInstancer`: added option to turn off random rotation
- `VoxelInstanceLibrary`: moved menu to add/remove/update items to the inspector, instead of the 3D editor toolbar
- Breaking changes
- `VoxelBuffer`: channels `DATA3` and `DATA4` were renamed `INDICES` and `WEIGHTS`
- `VoxelInstanceGenerator`: `EMIT_FROM_FACES` got renamed `EMIT_FROM_FACES_FAST`. `EMIT_FROM_FACES` still exists but is a different algorithm.
- `VoxelServer`: `get_stats()` format has changed, check documentation
- `VoxelLodTerrain`: `get_statistics()` format has changed: `time_process_update_responses` and `remaining_main_thread_blocks` are no longer available
- `VoxelLodTerrain`: Maximum LOD count was decreased to 24, which is still largely enough. Higher maximums were likely to cause integer overflows.
- `VoxelTerrain`: `get_statistics()` format has changed: `time_process_update_responses` and `remaining_main_thread_blocks` are no longer available
- `VoxelViewer`: `requires_collisions` is now `true` by default
- Fixes
- `VoxelGeneratorGraph`: changes to node properties are now saved properly
- `VoxelGeneratorGraph`: fix some per-thread memory not freed on exit
- `VoxelGeneratorGraph`: `debug_analyze_range` was crashing when given a negative-size area
- `VoxelBuffer`: `copy_voxel_metadata_in_area` was checking the source box incorrectly
- `VoxelBuffer`: multiple calls to `create()` with different sizes could lead to heap corruption if a channel was not uniform
- `VoxelBuffer`: `copy_channel_from_area` could lead to heap corruption if the source and destination had the same size and were copied entirely
@ -59,6 +64,7 @@ Ongoing development - `master`
- `VoxelStreamBlockFiles`: fixed warning about channels always shown in the scene tree
- `VoxelStreamSQLite`: fixed blocks above LOD0 being saved at wrong locations, causing them to be reloaded often floating in the air
- Fix some crashes occurring when all PoolVector allocs are in use (Godot 3.x limitation). It will print errors instead, but crashes can still occur inside Godot's code as it's not often checking for this
- Fix some crashes occurring when negative sizes are sent to AABB function parameters
09/05/2021 - `godot3.3`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -4,6 +4,7 @@ Instancing
The module provides an instancing system with the [VoxelInstancer](api/VoxelInstancer.md) node. This node must be added as child of a voxel terrain. It allows to spawn 3D models on top of the terrain's surface, which can later be removed when modified.
It can spawn two different kinds of objects:
- **Multimesh instances**. They can be extremely numerous, and can optionally have collision.
- **Scene instances**. They use regular scenes, however it is much slower so should be tuned to low numbers.
@ -19,11 +20,11 @@ VoxelInstanceLibrary
In order to spawn items, `VoxelInstancer` needs a [VoxelInstanceLibrary](api/VoxelInstanceLibrary.md) resource. This resource contains a list of all the items that can be spawned, and how they will be placed.
Select a `VoxelInstancer`. In the inspector, assign a library to the `library` property, or create a new embedded one. Then click on the library resource. Now a menu should show up in the top bar of the main viewport:
Select a `VoxelInstancer`. In the inspector, assign a library to the `library` property, or create a new embedded one. Then click on the library resource. Buttons appear at the top of the inspector:
![Screenshot of the VoxelInstanceLibrary menu](images/instance_library_menu.png)
In this menu, you can add items to the library by clicking `VoxelInstanceLibrary -> Add Multimesh item`.
You can add items to the library by clicking the "+" icon, and choose `Add Multimesh item`.
Items created this way come with a default setup, so you should be able to see something appear on top of the voxel surface.
@ -97,7 +98,7 @@ The save format is described in [this document](specs/instances_format.md).
### Setting up a Multimesh item from a scene
It is possible to setup a Multimesh Item from an existing scene, as an alternative to setting it up in the inspector. One reason you could need this is to setup colliders, because although they are supported, it is not possible to set them in the inspector at the moment. It might also be more convenient to design instances in the 3D editor using nodes.
It is possible to setup a Multimesh Item from an existing scene, as an alternative to setting it up in the inspector. One reason you could need this is to setup colliders, because although they are supported, it is not possible to set them in the inspector at the moment. It is also more convenient to design instances in the 3D editor using nodes.
This conversion process expects your scene to follow a specific structure:
@ -110,10 +111,11 @@ This conversion process expects your scene to follow a specific structure:
```
Materials can be setup in two ways:
- `material_override` on the MeshInstance
- Materials on the mesh resource directly
Surface material properties on the MeshInstance are not supported.
Surface material properties on the `MeshInstance` node are not supported.
### Scene instances

View File

@ -179,7 +179,7 @@ inline float sdf_blend(float src_value, float dst_value, VoxelTool::Mode mode) {
void VoxelTool::do_sphere(Vector3 center, float radius) {
VOXEL_PROFILE_SCOPE();
const Box3i box(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
const Box3i box(Vector3i::from_floored(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
if (!is_area_editable(box)) {
PRINT_VERBOSE("Area not editable");
@ -211,7 +211,7 @@ void VoxelTool::sdf_stamp_erase(Ref<VoxelBuffer> stamp, Vector3i pos) {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND_MSG(get_channel() != VoxelBuffer::CHANNEL_SDF, "This function only works when channel is set to SDF");
const Box3i box(pos, stamp->get_size());
const Box3i box(pos, stamp->get_buffer().get_size());
if (!is_area_editable(box)) {
PRINT_VERBOSE("Area not editable");
return;

View File

@ -192,7 +192,7 @@ private:
do_sphere(pos, radius);
}
void _b_do_box(Vector3 begin, Vector3 end) {
do_box(Vector3i::from_floored(begin), Vector3i(end));
do_box(Vector3i::from_floored(begin), Vector3i::from_floored(end));
}
void _b_copy(Vector3 pos, Ref<Reference> voxels, int channel_mask) {
copy(Vector3i::from_floored(pos), voxels, channel_mask);

View File

@ -10,7 +10,7 @@ VoxelToolBuffer::VoxelToolBuffer(Ref<VoxelBuffer> vb) {
bool VoxelToolBuffer::is_area_editable(const Box3i &box) const {
ERR_FAIL_COND_V(_buffer.is_null(), false);
return Box3i(Vector3i(), _buffer->get_size()).encloses(box);
return Box3i(Vector3i(), _buffer->get_buffer().get_size()).encloses(box);
}
void VoxelToolBuffer::do_sphere(Vector3 center, float radius) {
@ -24,8 +24,8 @@ void VoxelToolBuffer::do_sphere(Vector3 center, float radius) {
VOXEL_PROFILE_SCOPE();
Box3i box(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
box.clip(Box3i(Vector3i(), _buffer->get_size()));
Box3i box(Vector3i::from_floored(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
box.clip(Box3i(Vector3i(), _buffer->get_buffer().get_size()));
_buffer->get_buffer().write_box_2_template<VoxelToolOps::TextureBlendSphereOp, uint16_t, uint16_t>(box,
VoxelBufferInternal::CHANNEL_INDICES,
@ -84,9 +84,9 @@ void VoxelToolBuffer::paste(Vector3i p_pos, Ref<VoxelBuffer> p_voxels, uint8_t c
VoxelBufferInternal &dst = _buffer->get_buffer();
const VoxelBufferInternal &src = p_voxels->get_buffer();
Box3i box(p_pos, p_voxels->get_size());
Box3i box(p_pos, p_voxels->get_buffer().get_size());
const Vector3i min_noclamp = box.pos;
box.clip(Box3i(Vector3i(), _buffer->get_size()));
box.clip(Box3i(Vector3i(), _buffer->get_buffer().get_size()));
if (channels_mask == 0) {
channels_mask = (1 << get_channel());
@ -123,5 +123,5 @@ void VoxelToolBuffer::paste(Vector3i p_pos, Ref<VoxelBuffer> p_voxels, uint8_t c
}
_buffer->get_buffer().copy_voxel_metadata_in_area(
p_voxels->get_buffer(), Box3i(Vector3i(), p_voxels->get_size()), p_pos);
p_voxels->get_buffer(), Box3i(Vector3i(), p_voxels->get_buffer().get_size()), p_pos);
}

View File

@ -3,8 +3,6 @@
#include "voxel_tool.h"
class VoxelBuffer;
class VoxelToolBuffer : public VoxelTool {
GDCLASS(VoxelToolBuffer, VoxelTool)
public:

View File

@ -223,8 +223,11 @@ void VoxelToolLodTerrain::do_sphere(Vector3 center, float radius) {
VOXEL_PROFILE_SCOPE();
ERR_FAIL_COND(_terrain == nullptr);
const Box3i box = Box3i(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2))
const Box3i box = Box3i(
Vector3i::from_floored(center) - Vector3i(Math::floor(radius)),
Vector3i(Math::ceil(radius) * 2))
.clipped(_terrain->get_voxel_bounds());
if (!is_area_editable(box)) {
PRINT_VERBOSE("Area not editable");
return;
@ -291,8 +294,12 @@ private:
void VoxelToolLodTerrain::do_sphere_async(Vector3 center, float radius) {
ERR_FAIL_COND(_terrain == nullptr);
const Box3i box = Box3i(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2))
const Box3i box = Box3i(
Vector3i::from_floored(center) - Vector3i(Math::floor(radius)),
Vector3i(Math::ceil(radius) * 2))
.clipped(_terrain->get_voxel_bounds());
if (!is_area_editable(box)) {
PRINT_VERBOSE("Area not editable");
return;
@ -707,10 +714,11 @@ static Array separate_floating_chunks(VoxelTool &voxel_tool, Box3i world_box, No
Array VoxelToolLodTerrain::separate_floating_chunks(AABB world_box, Node *parent_node) {
ERR_FAIL_COND_V(_terrain == nullptr, Array());
ERR_FAIL_COND_V(!is_valid_size(world_box.size), Array());
Ref<VoxelMesher> mesher = _terrain->get_mesher();
Array materials;
materials.append(_terrain->get_material());
const Box3i int_world_box(world_box.position.floor(), world_box.size.ceil());
const Box3i int_world_box(Vector3i::from_floored(world_box.position), Vector3i::from_ceiled(world_box.size));
return ::separate_floating_chunks(
*this, int_world_box, parent_node, _terrain->get_global_transform(), mesher, materials);
}

View File

@ -149,7 +149,7 @@ void VoxelToolTerrain::paste(Vector3i pos, Ref<VoxelBuffer> p_voxels, uint8_t ch
channels_mask = (1 << _channel);
}
_terrain->get_storage().paste(pos, p_voxels->get_buffer(), channels_mask, use_mask, mask_value, false);
_post_edit(Box3i(pos, p_voxels->get_size()));
_post_edit(Box3i(pos, p_voxels->get_buffer().get_size()));
}
void VoxelToolTerrain::do_sphere(Vector3 center, float radius) {
@ -162,7 +162,7 @@ void VoxelToolTerrain::do_sphere(Vector3 center, float radius) {
VOXEL_PROFILE_SCOPE();
const Box3i box(Vector3i(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
const Box3i box(Vector3i::from_floored(center) - Vector3i(Math::floor(radius)), Vector3i(Math::ceil(radius) * 2));
if (!is_area_editable(box)) {
PRINT_VERBOSE("Area not editable");
@ -229,6 +229,7 @@ void VoxelToolTerrain::run_blocky_random_tick(AABB voxel_area, int voxel_count,
ERR_FAIL_COND(callback.is_null());
ERR_FAIL_COND(batch_count <= 0);
ERR_FAIL_COND(voxel_count < 0);
ERR_FAIL_COND(!is_valid_size(voxel_area.size));
if (voxel_count == 0) {
return;
@ -236,8 +237,8 @@ void VoxelToolTerrain::run_blocky_random_tick(AABB voxel_area, int voxel_count,
const VoxelLibrary &lib = **_terrain->get_voxel_library();
const Vector3i min_pos = Vector3i(voxel_area.position);
const Vector3i max_pos = min_pos + Vector3i(voxel_area.size);
const Vector3i min_pos = Vector3i::from_floored(voxel_area.position);
const Vector3i max_pos = min_pos + Vector3i::from_floored(voxel_area.size);
VoxelDataMap &map = _terrain->get_storage();
@ -327,8 +328,9 @@ void VoxelToolTerrain::run_blocky_random_tick(AABB voxel_area, int voxel_count,
void VoxelToolTerrain::for_each_voxel_metadata_in_area(AABB voxel_area, Ref<FuncRef> callback) {
ERR_FAIL_COND(_terrain == nullptr);
ERR_FAIL_COND(callback.is_null());
ERR_FAIL_COND(!is_valid_size(voxel_area.size));
const Box3i voxel_box = Box3i(Vector3i(voxel_area.position), Vector3i(voxel_area.size));
const Box3i voxel_box = Box3i(Vector3i::from_floored(voxel_area.position), Vector3i::from_floored(voxel_area.size));
ERR_FAIL_COND(!is_area_editable(voxel_box));
const Box3i data_block_box = voxel_box.downscaled(_terrain->get_data_block_size());

View File

@ -740,7 +740,8 @@ void VoxelGraphEditor::update_range_analysis_previews() {
ERR_FAIL_COND(!_graph->is_good());
const AABB aabb = _range_analysis_dialog->get_aabb();
_graph->debug_analyze_range(aabb.position, aabb.position + aabb.size, true);
_graph->debug_analyze_range(
Vector3i::from_floored(aabb.position), Vector3i::from_floored(aabb.position + aabb.size), true);
const VoxelGraphRuntime::State &state = _graph->get_last_state_from_current_thread();

View File

@ -6,21 +6,66 @@
#include <scene/gui/menu_button.h>
#include <scene/resources/primitive_meshes.h>
VoxelInstanceLibraryEditorPlugin::VoxelInstanceLibraryEditorPlugin(EditorNode *p_node) {
_menu_button = memnew(MenuButton);
_menu_button->set_text(TTR("VoxelInstanceLibrary"));
// TODO Icon
//_menu_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("MeshLibrary", "EditorIcons"));
_menu_button->get_popup()->add_item(TTR("Add Multimesh Item (fast)"), MENU_ADD_MULTIMESH_ITEM);
_menu_button->get_popup()->add_item(TTR("Update Multimesh Item From Scene"), MENU_UPDATE_MULTIMESH_ITEM_FROM_SCENE);
_menu_button->get_popup()->add_separator();
_menu_button->get_popup()->add_item(TTR("Add Scene Item (slow)"), MENU_ADD_SCENE_ITEM);
_menu_button->get_popup()->add_separator();
_menu_button->get_popup()->add_item(TTR("Remove Selected Item"), MENU_REMOVE_ITEM);
// TODO Add and update from scene
_menu_button->get_popup()->connect("id_pressed", this, "_on_menu_id_pressed");
_menu_button->hide();
namespace {
enum Buttons {
BUTTON_ADD_MULTIMESH_ITEM,
BUTTON_UPDATE_MULTIMESH_ITEM_FROM_SCENE,
BUTTON_ADD_SCENE_ITEM,
BUTTON_REMOVE_ITEM
};
} // namespace
bool VoxelInstanceLibraryEditorInspectorPlugin::can_handle(Object *p_object) {
return Object::cast_to<VoxelInstanceLibrary>(p_object) != nullptr;
}
void VoxelInstanceLibraryEditorInspectorPlugin::parse_begin(Object *p_object) {
// TODO How can I make sure the buttons will be at the beginning of the "VoxelInstanceLibrary" category?
// This is a better place than the Spatial editor toolbar (which would get hidden if you are not in the 3D tab
// of the editor), but it will appear at the very top of the inspector, even above the "VoxelInstanceLibrary"
// catgeory of properties. That looks a bit off, and if the class were to be inherited, it would start to be
// confusing because these buttons are about the property list of "VoxelInstanceLibrary" specifically.
// I could neither use `parse_property` nor `parse_category`, because when the list is empty,
// the class returns no properties AND no category.
add_buttons();
}
void VoxelInstanceLibraryEditorInspectorPlugin::add_buttons() {
CRASH_COND(icon_provider == nullptr);
CRASH_COND(button_listener == nullptr);
// Put buttons on top of the list of items
HBoxContainer *hb = memnew(HBoxContainer);
MenuButton *button_add = memnew(MenuButton);
button_add->set_icon(icon_provider->get_icon("Add", "EditorIcons"));
button_add->get_popup()->add_item("MultiMesh item (fast)", BUTTON_ADD_MULTIMESH_ITEM);
button_add->get_popup()->add_item("Scene item (slow)", BUTTON_ADD_SCENE_ITEM);
button_add->get_popup()->connect("id_pressed", button_listener, "_on_button_pressed");
hb->add_child(button_add);
Button *button_remove = memnew(Button);
button_remove->set_icon(icon_provider->get_icon("Remove", "EditorIcons"));
button_remove->set_flat(true);
button_remove->connect("pressed", button_listener, "_on_button_pressed", varray(BUTTON_REMOVE_ITEM));
hb->add_child(button_remove);
Control *spacer = memnew(Control);
spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hb->add_child(spacer);
Button *button_update = memnew(Button);
button_update->set_text(TTR("Update From Scene..."));
button_update->connect("pressed", button_listener, "_on_button_pressed",
varray(BUTTON_UPDATE_MULTIMESH_ITEM_FROM_SCENE));
hb->add_child(button_update);
add_custom_control(hb);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VoxelInstanceLibraryEditorPlugin::VoxelInstanceLibraryEditorPlugin(EditorNode *p_node) {
Control *base_control = get_editor_interface()->get_base_control();
_confirmation_dialog = memnew(ConfirmationDialog);
@ -39,11 +84,6 @@ VoxelInstanceLibraryEditorPlugin::VoxelInstanceLibraryEditorPlugin(EditorNode *p
_open_scene_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE);
base_control->add_child(_open_scene_dialog);
_open_scene_dialog->connect("file_selected", this, "_on_open_scene_dialog_file_selected");
// TODO Perhaps it's a better idea to put this menu in the inspector directly?
// Because it won't be visible if the user is in 2D or script mode,
// and it's confusing to see it outside the inspector
add_control_to_container(EditorPlugin::CONTAINER_SPATIAL_EDITOR_MENU, _menu_button);
}
bool VoxelInstanceLibraryEditorPlugin::handles(Object *p_object) const {
@ -56,15 +96,27 @@ void VoxelInstanceLibraryEditorPlugin::edit(Object *p_object) {
_library.reference_ptr(lib);
}
void VoxelInstanceLibraryEditorPlugin::make_visible(bool visible) {
_menu_button->set_visible(visible);
void VoxelInstanceLibraryEditorPlugin::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
Control *base_control = get_editor_interface()->get_base_control();
_inspector_plugin.instance();
_inspector_plugin->button_listener = this;
_inspector_plugin->icon_provider = base_control;
// TODO Why can other Godot plugins do this in the constructor??
// I found I could not put this in the constructor,
// otherwise `add_inspector_plugin` causes ANOTHER editor plugin to leak on exit... Oo
add_inspector_plugin(_inspector_plugin);
} else if (p_what == NOTIFICATION_EXIT_TREE) {
remove_inspector_plugin(_inspector_plugin);
}
}
void VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed(int id) {
_last_used_menu_option = MenuOption(id);
void VoxelInstanceLibraryEditorPlugin::_on_button_pressed(int id) {
_last_used_button = id;
switch (id) {
case MENU_ADD_MULTIMESH_ITEM: {
case BUTTON_ADD_MULTIMESH_ITEM: {
ERR_FAIL_COND(_library.is_null());
Ref<VoxelInstanceLibraryItem> item;
@ -87,7 +139,7 @@ void VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed(int id) {
ur.commit_action();
} break;
case MENU_UPDATE_MULTIMESH_ITEM_FROM_SCENE: {
case BUTTON_UPDATE_MULTIMESH_ITEM_FROM_SCENE: {
ERR_FAIL_COND(_library.is_null());
const int item_id = try_get_selected_item_id();
if (item_id != -1) {
@ -96,11 +148,11 @@ void VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed(int id) {
}
} break;
case MENU_ADD_SCENE_ITEM: {
case BUTTON_ADD_SCENE_ITEM: {
_open_scene_dialog->popup_centered_ratio();
} break;
case MENU_REMOVE_ITEM: {
case BUTTON_REMOVE_ITEM: {
ERR_FAIL_COND(_library.is_null());
const int item_id = try_get_selected_item_id();
if (item_id != -1) {
@ -134,7 +186,7 @@ int VoxelInstanceLibraryEditorPlugin::try_get_selected_item_id() {
TTR(String("Could not determine selected item from property path: `{0}`.\n"
"You must select the `item_X` property label of the item you want to remove."))
.format(varray(path)));
_info_dialog->popup();
_info_dialog->popup_centered();
return -1;
}
}
@ -155,12 +207,12 @@ void VoxelInstanceLibraryEditorPlugin::_on_remove_item_confirmed() {
}
void VoxelInstanceLibraryEditorPlugin::_on_open_scene_dialog_file_selected(String fpath) {
switch (_last_used_menu_option) {
case MENU_ADD_SCENE_ITEM:
switch (_last_used_button) {
case BUTTON_ADD_SCENE_ITEM:
add_scene_item(fpath);
break;
case MENU_UPDATE_MULTIMESH_ITEM_FROM_SCENE:
case BUTTON_UPDATE_MULTIMESH_ITEM_FROM_SCENE:
update_multimesh_item_from_scene(fpath, _item_id_to_update);
break;
@ -223,7 +275,7 @@ void VoxelInstanceLibraryEditorPlugin::update_multimesh_item_from_scene(String f
}
void VoxelInstanceLibraryEditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("_on_menu_id_pressed", "id"), &VoxelInstanceLibraryEditorPlugin::_on_menu_id_pressed);
ClassDB::bind_method(D_METHOD("_on_button_pressed", "id"), &VoxelInstanceLibraryEditorPlugin::_on_button_pressed);
ClassDB::bind_method(D_METHOD("_on_remove_item_confirmed"),
&VoxelInstanceLibraryEditorPlugin::_on_remove_item_confirmed);
ClassDB::bind_method(D_METHOD("_on_open_scene_dialog_file_selected", "fpath"),

View File

@ -4,8 +4,23 @@
#include "../../terrain/instancing/voxel_instance_library.h"
#include <editor/editor_plugin.h>
class Control;
class MenuButton;
class ConfirmationDialog;
class VoxelInstanceLibraryEditorPlugin;
class VoxelInstanceLibraryEditorInspectorPlugin : public EditorInspectorPlugin {
GDCLASS(VoxelInstanceLibraryEditorInspectorPlugin, EditorInspectorPlugin)
public:
Control *icon_provider = nullptr;
VoxelInstanceLibraryEditorPlugin *button_listener = nullptr;
bool can_handle(Object *p_object) override;
void parse_begin(Object *p_object) override;
private:
void add_buttons();
};
class VoxelInstanceLibraryEditorPlugin : public EditorPlugin {
GDCLASS(VoxelInstanceLibraryEditorPlugin, EditorPlugin)
@ -16,35 +31,29 @@ public:
bool handles(Object *p_object) const override;
void edit(Object *p_object) override;
void make_visible(bool visible) override;
private:
void _notification(int p_what);
int try_get_selected_item_id();
void add_scene_item(String fpath);
void update_multimesh_item_from_scene(String fpath, int item_id);
void _on_menu_id_pressed(int id);
void _on_button_pressed(int id);
void _on_remove_item_confirmed();
void _on_open_scene_dialog_file_selected(String fpath);
static void _bind_methods();
enum MenuOption {
MENU_ADD_MULTIMESH_ITEM,
MENU_UPDATE_MULTIMESH_ITEM_FROM_SCENE,
MENU_ADD_SCENE_ITEM,
MENU_REMOVE_ITEM
};
MenuButton *_menu_button = nullptr;
ConfirmationDialog *_confirmation_dialog = nullptr;
AcceptDialog *_info_dialog = nullptr;
int _item_id_to_remove = -1;
int _item_id_to_update = -1;
EditorFileDialog *_open_scene_dialog;
MenuOption _last_used_menu_option;
int _last_used_button;
Ref<VoxelInstanceLibrary> _library;
Ref<VoxelInstanceLibraryEditorInspectorPlugin> _inspector_plugin;
};
#endif // VOXEL_INSTANCE_LIBRARY_EDITOR_PLUGIN_H

View File

@ -190,6 +190,8 @@ void VoxelTerrainEditorPlugin::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE:
_editor_viewer_id = VoxelServer::get_singleton()->add_viewer();
VoxelServer::get_singleton()->set_viewer_distance(_editor_viewer_id, 512);
// No collision needed in editor, also it updates faster without
VoxelServer::get_singleton()->set_viewer_requires_collisions(_editor_viewer_id, false);
break;
case NOTIFICATION_EXIT_TREE:

View File

@ -150,9 +150,9 @@ static void extract_model_instances(const vox::Data &vox_data, std::vector<Model
src_color_indices = to_span_const(model.color_indexes);
} else {
IntBasis basis;
basis.x = args.basis.get_axis(Vector3::AXIS_X);
basis.y = args.basis.get_axis(Vector3::AXIS_Y);
basis.z = args.basis.get_axis(Vector3::AXIS_Z);
basis.x = Vector3i::from_cast(args.basis.get_axis(Vector3::AXIS_X));
basis.y = Vector3i::from_cast(args.basis.get_axis(Vector3::AXIS_Y));
basis.z = Vector3i::from_cast(args.basis.get_axis(Vector3::AXIS_Z));
temp_voxels.resize(model.color_indexes.size());
dst_size = transform_3d_array_zxy(
to_span_const(model.color_indexes), to_span(temp_voxels), model.size, basis);

View File

@ -1453,10 +1453,13 @@ void VoxelGeneratorGraph::_b_set_node_param_null(int node_id, int param_index) {
}
float VoxelGeneratorGraph::_b_generate_single(Vector3 pos) {
return generate_single(Vector3i(pos), VoxelBufferInternal::CHANNEL_SDF).f;
return generate_single(Vector3i::from_floored(pos), VoxelBufferInternal::CHANNEL_SDF).f;
}
Vector2 VoxelGeneratorGraph::_b_debug_analyze_range(Vector3 min_pos, Vector3 max_pos) const {
ERR_FAIL_COND_V(min_pos.x > max_pos.x, Vector2());
ERR_FAIL_COND_V(min_pos.y > max_pos.y, Vector2());
ERR_FAIL_COND_V(min_pos.z > max_pos.z, Vector2());
const Interval r = debug_analyze_range(
Vector3i::from_floored(min_pos), Vector3i::from_floored(max_pos), false);
return Vector2(r.min, r.max);

View File

@ -17,23 +17,6 @@
//#define VOXEL_DEBUG_GRAPH_PROG_SENTINEL uint16_t(12345) // 48, 57 (base 10)
//#endif
template <typename T>
inline const T &read(const Span<const uint8_t> &mem, uint32_t &p) {
#ifdef DEBUG_ENABLED
CRASH_COND(p + sizeof(T) > mem.size());
#endif
const T *v = reinterpret_cast<const T *>(&mem[p]);
p += sizeof(T);
return *v;
}
template <typename T>
inline void append(std::vector<uint8_t> &mem, const T &v) {
size_t p = mem.size();
mem.resize(p + sizeof(T));
*(T *)(&mem[p]) = v;
}
// The Image lock() API prevents us from reading the same image in multiple threads.
// Compiling makes a read-only copy of all resources, so we can lock all images up-front if successful.
// This might no longer needed in Godot 4.
@ -217,7 +200,7 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
_program.y_input_address = mem.add_binding();
_program.z_input_address = mem.add_binding();
std::vector<uint8_t> &operations = _program.operations;
std::vector<uint16_t> &operations = _program.operations;
const VoxelGraphNodeDB &type_db = *VoxelGraphNodeDB::get_singleton();
// Run through each node in order, and turn them into program instructions
@ -284,7 +267,7 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
}
_program.default_execution_map.operation_adresses.push_back(operations.size());
append(operations, static_cast<uint8_t>(node->type_id));
operations.push_back(node->type_id);
// Inputs and outputs use a convention so we can have generic code for them.
// Parameters are more specific, and may be affected by alignment so better just do them by hand
@ -314,7 +297,7 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
++dg_node.end_dependency;
}
append(operations, a);
operations.push_back(a);
BufferSpec &bs = _program.buffer_specs[a];
++bs.users_count;
@ -328,12 +311,12 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
const ProgramGraph::PortLocation op{ node_id, static_cast<uint32_t>(j) };
_program.output_port_addresses[op] = a;
append(operations, a);
operations.push_back(a);
}
// Add space for params size, default is no params so size is 0
size_t params_size_index = operations.size();
append<uint16_t>(operations, 0);
operations.push_back(0);
// Get params, copy resources when used, and hold a reference to them
std::vector<Variant> params_copy;
@ -364,7 +347,6 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
}
if (type.compile_func != nullptr) {
const size_t size_before = operations.size();
CompileContext ctx(*node, operations, _program.heap_resources, params_copy);
type.compile_func(ctx);
if (ctx.has_error()) {
@ -374,9 +356,9 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
result.node_id = node_id;
return result;
}
const size_t params_size = operations.size() - size_before;
const size_t params_size = ctx.get_params_size_in_words();
CRASH_COND(params_size > std::numeric_limits<uint16_t>::max());
*reinterpret_cast<uint16_t *>(&operations[params_size_index]) = params_size;
operations[params_size_index] = params_size;
}
if (type.category == VoxelGraphNodeDB::CATEGORY_OUTPUT) {
@ -414,7 +396,7 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
PRINT_VERBOSE(String("Compiled voxel graph. Program size: {0}b, buffers: {1}")
.format(varray(
SIZE_T_TO_VARIANT(_program.operations.size() * sizeof(float)),
SIZE_T_TO_VARIANT(_program.operations.size() * sizeof(uint16_t)),
SIZE_T_TO_VARIANT(_program.buffer_count))));
_program.lock_images();
@ -425,15 +407,15 @@ VoxelGraphRuntime::CompilationResult VoxelGraphRuntime::_compile(const ProgramGr
}
static Span<const uint16_t> get_outputs_from_op_address(
Span<const uint8_t> operations, uint16_t op_address) {
const uint8_t opid = operations[op_address];
Span<const uint16_t> operations, uint16_t op_address) {
const uint16_t opid = operations[op_address];
const VoxelGraphNodeDB::NodeType &node_type = VoxelGraphNodeDB::get_singleton()->get_type(opid);
const uint32_t inputs_size = node_type.inputs.size() * sizeof(uint16_t);
const uint32_t outputs_size = node_type.outputs.size() * sizeof(uint16_t);
const uint32_t inputs_count = node_type.inputs.size();
const uint32_t outputs_count = node_type.outputs.size();
// The +1 is for `opid`
return operations.sub(op_address + 1 + inputs_size, outputs_size).reinterpret_cast_to<const uint16_t>();
return operations.sub(op_address + 1 + inputs_count, outputs_count);
}
bool VoxelGraphRuntime::is_operation_constant(const State &state, uint16_t op_address) const {
@ -548,7 +530,7 @@ void VoxelGraphRuntime::generate_optimized_execution_map(const State &state, Exe
}
}
Span<const uint8_t> operations(program.operations.data(), 0, program.operations.size());
Span<const uint16_t> operations(program.operations.data(), 0, program.operations.size());
bool xzy_start_not_assigned = true;
// Now we have to fill buffers with the local constants we may have found.
@ -637,7 +619,7 @@ void VoxelGraphRuntime::prepare_state(State &state, unsigned int buffer_size) co
CRASH_COND(buffer.data != nullptr);
} else if (buffer.data != nullptr) {
// Deallocate this buffer if it wasnt a binding and contained something
memdelete(buffer.data);
memfree(buffer.data);
}
}
@ -730,6 +712,20 @@ void VoxelGraphRuntime::prepare_state(State &state, unsigned int buffer_size) co
}*/
}
static inline Span<const uint8_t> read_params(Span<const uint16_t> operations, unsigned int &pc) {
const uint16_t params_size_in_words = operations[pc];
++pc;
Span<const uint8_t> params;
if (params_size_in_words > 0) {
const size_t params_offset_in_words = operations[pc];
// Seek to aligned position where params start
pc += params_offset_in_words;
params = operations.sub(pc, params_size_in_words).reinterpret_cast_to<const uint8_t>();
pc += params_size_in_words;
}
return params;
}
void VoxelGraphRuntime::generate_set(State &state,
Span<float> in_x, Span<float> in_y, Span<float> in_z, bool skip_xz,
const ExecutionMap *execution_map) const {
@ -789,7 +785,7 @@ void VoxelGraphRuntime::generate_set(State &state,
L::bind_buffer(buffers, _program.z_input_address, in_z);
}
const Span<const uint8_t> operations(_program.operations.data(), 0, _program.operations.size());
const Span<const uint16_t> operations(_program.operations.data(), 0, _program.operations.size());
Span<const uint16_t> op_adresses = execution_map != nullptr ?
to_span_const(execution_map->operation_adresses) :
@ -804,25 +800,18 @@ void VoxelGraphRuntime::generate_set(State &state,
for (unsigned int execution_map_index = 0; execution_map_index < op_adresses.size(); ++execution_map_index) {
unsigned int pc = op_adresses[execution_map_index];
const uint8_t opid = operations[pc++];
const uint16_t opid = operations[pc++];
const VoxelGraphNodeDB::NodeType &node_type = VoxelGraphNodeDB::get_singleton()->get_type(opid);
const uint32_t inputs_size = node_type.inputs.size() * sizeof(uint16_t);
const uint32_t outputs_size = node_type.outputs.size() * sizeof(uint16_t);
const uint32_t inputs_count = node_type.inputs.size();
const uint32_t outputs_count = node_type.outputs.size();
const Span<const uint16_t> inputs =
operations.sub(pc, inputs_size).reinterpret_cast_to<const uint16_t>();
pc += inputs_size;
const Span<const uint16_t> outputs =
operations.sub(pc, outputs_size).reinterpret_cast_to<const uint16_t>();
pc += outputs_size;
const Span<const uint16_t> inputs = operations.sub(pc, inputs_count);
pc += inputs_count;
const Span<const uint16_t> outputs = operations.sub(pc, outputs_count);
pc += outputs_count;
const uint16_t params_size = read<uint16_t>(operations, pc);
Span<const uint8_t> params;
if (params_size > 0) {
params = operations.sub(pc, params_size);
//pc += params_size;
}
Span<const uint8_t> params = read_params(operations, pc);
ERR_FAIL_COND(node_type.process_buffer_func == nullptr);
ProcessBufferContext ctx(inputs, outputs, params, buffers, execution_map != nullptr);
@ -863,31 +852,24 @@ void VoxelGraphRuntime::analyze_range(State &state, Vector3i min_pos, Vector3i m
ranges[_program.y_input_address] = Interval(min_pos.y, max_pos.y);
ranges[_program.z_input_address] = Interval(min_pos.z, max_pos.z);
const Span<const uint8_t> operations(_program.operations.data(), 0, _program.operations.size());
const Span<const uint16_t> operations(_program.operations.data(), 0, _program.operations.size());
// Here operations must all be analyzed, because we do this as a broad-phase.
// Only narrow-phase may skip some operations eventually.
uint32_t pc = 0;
while (pc < operations.size()) {
const uint8_t opid = operations[pc++];
const uint16_t opid = operations[pc++];
const VoxelGraphNodeDB::NodeType &node_type = VoxelGraphNodeDB::get_singleton()->get_type(opid);
const uint32_t inputs_size = node_type.inputs.size() * sizeof(uint16_t);
const uint32_t outputs_size = node_type.outputs.size() * sizeof(uint16_t);
const uint32_t inputs_count = node_type.inputs.size();
const uint32_t outputs_count = node_type.outputs.size();
const Span<const uint16_t> inputs =
operations.sub(pc, inputs_size).reinterpret_cast_to<const uint16_t>();
pc += inputs_size;
const Span<const uint16_t> outputs =
operations.sub(pc, outputs_size).reinterpret_cast_to<const uint16_t>();
pc += outputs_size;
const Span<const uint16_t> inputs = operations.sub(pc, inputs_count);
pc += inputs_count;
const Span<const uint16_t> outputs = operations.sub(pc, outputs_count);
pc += outputs_count;
const uint16_t params_size = read<uint16_t>(operations, pc);
Span<const uint8_t> params;
if (params_size > 0) {
params = operations.sub(pc, params_size);
pc += params_size;
}
Span<const uint8_t> params = read_params(operations, pc);
ERR_FAIL_COND(node_type.range_analysis_func == nullptr);
RangeAnalysisContext ctx(inputs, outputs, params, ranges, buffers);

View File

@ -64,6 +64,10 @@ public:
// The same state can be re-used with multiple programs, but it should be prepared before doing that.
class State {
public:
~State() {
clear();
}
inline const Buffer &get_buffer(uint16_t address) const {
// TODO Just for convenience because STL bound checks aren't working in Godot 3
CRASH_COND(address >= buffers.size());
@ -156,11 +160,10 @@ public:
// Functions usable by node implementations during the compilation stage
class CompileContext {
public:
CompileContext(const ProgramGraph::Node &node, std::vector<uint8_t> &program,
CompileContext(const ProgramGraph::Node &node, std::vector<uint16_t> &program,
std::vector<HeapResource> &heap_resources,
std::vector<Variant> &params) :
_node(node),
_offset(program.size()),
_program(program),
_heap_resources(heap_resources),
_params(params) {}
@ -174,10 +177,33 @@ public:
template <typename T>
void set_params(T params) {
// Can be called only once per node
CRASH_COND(_offset != _program.size());
_program.resize(_program.size() + sizeof(T));
T &p = *reinterpret_cast<T *>(&_program[_offset]);
CRASH_COND(_params_added);
// We will need to align memory, so the struct will not be immediately stored here.
// Instead we put a header that tells how much to advance in order to reach the beginning of the struct,
// which will be at an aligned position.
// We align to the maximum alignment between the struct,
// and the type of word we store inside the program buffer, which is uint16.
const size_t params_alignment = max(alignof(T), alignof(uint16_t));
const size_t params_offset_index = _program.size();
// Prepare space to store the offset (at least 1 since that header is one word)
_program.push_back(1);
// Align memory for the struct.
// Note, we index with words, not bytes.
const size_t struct_offset =
alignup(_program.size() * sizeof(uint16_t), params_alignment) / sizeof(uint16_t);
if (struct_offset > _program.size()) {
_program.resize(struct_offset);
}
// Write offset in header
_program[params_offset_index] = struct_offset - params_offset_index;
// Allocate space for the struct. It is measured in words, so it can be up to 1 byte larger.
_params_size_in_words = (sizeof(T) + sizeof(uint16_t) - 1) / sizeof(uint16_t);
_program.resize(_program.size() + _params_size_in_words);
// Write struct
T &p = *reinterpret_cast<T *>(&_program[struct_offset]);
p = params;
_params_added = true;
}
// In case the compilation step produces a resource to be deleted
@ -206,14 +232,19 @@ public:
return _error_message;
}
size_t get_params_size_in_words() const {
return _params_size_in_words;
}
private:
const ProgramGraph::Node &_node;
const size_t _offset;
std::vector<uint8_t> &_program;
std::vector<uint16_t> &_program;
std::vector<HeapResource> &_heap_resources;
std::vector<Variant> &_params;
String _error_message;
size_t _params_size_in_words = 0;
bool _has_error = false;
bool _params_added = false;
};
class _ProcessContext {
@ -228,6 +259,9 @@ public:
template <typename T>
inline const T &get_params() const {
#ifdef DEBUG_ENABLED
CRASH_COND(sizeof(T) > _params.size());
#endif
return *reinterpret_cast<const T *>(_params.data());
}
@ -370,11 +404,21 @@ private:
// Precalculated program data.
// Remains constant and read-only after compilation.
struct Program {
// Serialized operations and arguments.
// They come up as series of <opid><inputs><outputs><parameters_size><parameters>.
// Serialized operations and arguments, aligned at minimum with uint16.
// They come up as series of:
//
// - uint16 opid
// - uint16 inputs[0..*]
// - uint16 outputs[0..*]
// - uint16 parameters_size
// - uint16 parameters_offset // how much to advance from here to reach the beginning of `parameters`
// - <optional padding>
// - T parameters, where T could be any struct
// - <optional padding to keep alignment with uint16>
//
// They should be laid out in the same order they will be run in, although it's not absolutely required.
// It's better to have it ordered because memory access will be more predictable.
std::vector<uint8_t> operations;
std::vector<uint16_t> operations;
// Describes dependencies between operations. It is generated at compile time.
// It is used to perform dynamic optimization in case some operations can be predicted as constant.

View File

@ -30,7 +30,7 @@ VoxelSingleValue VoxelGenerator::generate_single(Vector3i pos, unsigned int chan
void VoxelGenerator::_b_generate_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND(lod < 0);
ERR_FAIL_COND(out_buffer.is_null());
VoxelBlockRequest r = { out_buffer->get_buffer(), Vector3i(origin_in_voxels), lod };
VoxelBlockRequest r = { out_buffer->get_buffer(), Vector3i::from_floored(origin_in_voxels), lod };
generate_block(r);
}

128
misc/check_ci_log.py Executable file
View File

@ -0,0 +1,128 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
if len(sys.argv) < 2:
print("ERROR: You must run program with file name as argument.")
sys.exit(50)
fname = sys.argv[1]
fileread = open(fname.strip(), "r")
file_contents = fileread.read()
# If find "ERROR: AddressSanitizer:", then happens invalid read or write
# This is critical bug, so we need to fix this as fast as possible
# Example - https://github.com/godotengine/godot/issues/48114#issue-865502805
if file_contents.find("ERROR: AddressSanitizer:") != -1:
print("FATAL ERROR: An incorrectly used memory was found.")
sys.exit(51)
# In test project may be put several assert functions which will control if
# project is executed with right parameters etc. which normally will not stop
# execution of project
# Example - https://github.com/godotengine/godot/issues/37980#issue-602414919
if file_contents.find("Assertion failed") != -1:
print(
'ERROR: Assertion failed in project, check execution log for more info (search for "Assertion failed" in CI log)'
)
sys.exit(55)
# There is also possible, that program crashed with or without backtrace.
# Example - https://github.com/godotengine/godot/issues/51620#issue-970308653
if (
file_contents.find("Program crashed with signal") != -1
or file_contents.find("Dumping the backtrace") != -1
or file_contents.find("Segmentation fault (core dumped)") != -1
or file_contents.find("Aborted (core dumped)") != -1
or file_contents.find("(core dumped)") != -1
or file_contents.find("Aborted") != -1
or file_contents.find("Assertion") != -1
):
print("FATAL ERROR: Godot has been crashed.")
sys.exit(52)
# Finding memory leaks in Godot is quite difficult, because we need to take into
# account leaks also in external libraries. They are usually provided without
# debugging symbols, so the leak report from it usually has only 2/3 lines,
# so searching for 5 element - "#4 0x" - should correctly detect the vast
# majority of memory leaks
# Example - https://github.com/godotengine/godot/issues/34495#issue-541154242
if file_contents.find("ERROR: LeakSanitizer:") != -1:
if file_contents.find("#4 0x") != -1:
print("ERROR: Memory leak was found")
sys.exit(53)
# It may happen that Godot detects leaking nodes/resources and removes them, so
# this possibility should also be handled as a potential error, even if
# LeakSanitizer doesn't report anything
# Example - https://github.com/godotengine/godot/issues/49438#issue-915348285
if file_contents.find("ObjectDB instances leaked at exit") != -1:
print('ERROR: Memory leak was found (search for "ObjectDB instances leaked at exit" in CI log)')
sys.exit(54)
# Sometimes pointers points at objects of different types, which may cause
# to show errors like "runtime error: member call on address 0x1 which does not point to an object of type"
# Example - https://github.com/godotengine/godot/issues/51351#issue-963170661
# Waiting for https://github.com/godotengine/godot/issues/51888
if file_contents.find("vptr for") != -1:
print('WARNING: Found pointer which not point at valid object (search for "vptr for" in CI log)')
sys.exit(56)
# By default overflow or underflow of signed values are in C++ just
# undefined behavior(most of the time value is wrapped)
# Example - https://github.com/godotengine/godot/issues/33644#issue-523623547
if file_contents.find("cannot be represented in type") != -1 or file_contents.find("is outside the range") != -1:
print(
'ERROR: Found pointer which not point at valid object (search for "cannot be represented in type" or "is outside the range" in CI log)'
)
#sys.exit(57)
# Some functions like memcpy doesn't expect that its argument is null pointer.
# This may later be cause of bugs or crashes.
# Example - https://github.com/godotengine/godot/issues/48215#issue-867854743
if file_contents.find("null pointer passed as argument") != -1:
print(
'ERROR: Found null pointer passed as argument to function which not expect it (search for "null pointer passed as argument" in CI log)'
)
#sys.exit(58)
# Casting or pointer moving caused that code trying to violate alignement rules
# Example - https://github.com/godotengine/godot/issues/31203#issue-478487290
if file_contents.find("misaligned address") != -1:
print('ERROR: Found usage of misaligned pointer (search for "misaligned address" in CI log)')
sys.exit(59)
# For now Godot leaks a lot of rendering stuff so for now we just show info
# about it and this needs to be re-enabled after fixing this memory leaks.
# Example - https://github.com/godotengine/godot/issues/47941#issue-859356605
# Blocked by https://github.com/godotengine/godot/issues/46833
if file_contents.find("were leaked") != -1 or file_contents.find("were never freed") != -1:
print('WARNING: Memory leak was found (search for "were leaked" or "were never freed" in CI log)')
sys.exit(60)
# Usually error about trying to free invalid ID is caused by removing wrong object
# Example - https://github.com/godotengine/godot/issues/49623#issue-921610423
# Blocked by Swiftshader bugs in CI
if file_contents.find("Attempted to free invalid ID") != -1:
print('WARNING: Trying to free invalid object (search for "Attempted to free invalid ID" in CI log)')
#sys.exit(61)
sys.exit(0)

View File

@ -109,12 +109,12 @@ void register_voxel_types() {
// Utilities
ClassDB::register_class<VoxelBoxMover>();
ClassDB::register_class<VoxelRaycastResult>();
ClassDB::register_class<VoxelTool>();
ClassDB::register_class<VoxelToolTerrain>();
ClassDB::register_class<VoxelToolLodTerrain>();
ClassDB::register_virtual_class<VoxelTool>();
ClassDB::register_virtual_class<VoxelToolTerrain>();
ClassDB::register_virtual_class<VoxelToolLodTerrain>();
// I had to bind this one despite it being useless as-is because otherwise Godot lazily initializes its class.
// And this can happen in a thread, causing crashes due to the concurrent access
ClassDB::register_class<VoxelToolBuffer>();
ClassDB::register_virtual_class<VoxelToolBuffer>();
ClassDB::register_class<VoxelBlockSerializer>();
ClassDB::register_class<VoxelVoxLoader>();
ClassDB::register_class<FastNoiseLite>();
@ -142,6 +142,7 @@ void register_voxel_types() {
PRINT_VERBOSE(String("Size of VoxelTerrain: {0}").format(varray((int)sizeof(VoxelTerrain))));
PRINT_VERBOSE(String("Size of VoxelLodTerrain: {0}").format(varray((int)sizeof(VoxelLodTerrain))));
PRINT_VERBOSE(String("Size of VoxelInstancer: {0}").format(varray((int)sizeof(VoxelInstancer))));
PRINT_VERBOSE(String("Size of VoxelDataMap: {0}").format(varray((int)sizeof(VoxelDataMap))));
#ifdef TOOLS_ENABLED
EditorPlugins::add_by_type<VoxelGraphEditorPlugin>();

View File

@ -1098,7 +1098,7 @@ static void copy_block_and_neighbors(Span<std::shared_ptr<VoxelBufferInternal>>
{
RWLockRead read(src->get_lock());
for (unsigned int ci = 0; ci < channels_count; ++ci) {
dst.copy_from(*src, src_min, src_max, Vector3(), channels[ci]);
dst.copy_from(*src, src_min, src_max, Vector3i(), channels[ci]);
}
}

View File

@ -100,7 +100,7 @@ public:
// };
Vector3 world_position;
unsigned int view_distance = 128;
bool require_collisions = false;
bool require_collisions = true;
bool require_visuals = true;
};

View File

@ -39,7 +39,10 @@ void VoxelBuffer::copy_channel_from(Ref<VoxelBuffer> other, unsigned int channel
void VoxelBuffer::copy_channel_from_area(Ref<VoxelBuffer> other, Vector3 src_min, Vector3 src_max, Vector3 dst_min,
unsigned int channel) {
ERR_FAIL_COND(other.is_null());
_buffer->copy_from(other->get_buffer(), Vector3i(src_min), Vector3i(src_max), Vector3i(dst_min), channel);
_buffer->copy_from(other->get_buffer(),
Vector3i::from_floored(src_min),
Vector3i::from_floored(src_max),
Vector3i::from_floored(dst_min), channel);
}
void VoxelBuffer::fill(uint64_t defval, unsigned int channel_index) {
@ -68,7 +71,10 @@ VoxelBuffer::Compression VoxelBuffer::get_channel_compression(unsigned int chann
void VoxelBuffer::downscale_to(Ref<VoxelBuffer> dst, Vector3 src_min, Vector3 src_max, Vector3 dst_min) const {
ERR_FAIL_COND(dst.is_null());
_buffer->downscale_to(dst->get_buffer(), Vector3i(src_min), Vector3i(src_max), Vector3i(dst_min));
_buffer->downscale_to(dst->get_buffer(),
Vector3i::from_floored(src_min),
Vector3i::from_floored(src_max),
Vector3i::from_floored(dst_min));
}
Ref<VoxelBuffer> VoxelBuffer::duplicate(bool include_metadata) const {
@ -104,18 +110,22 @@ void VoxelBuffer::for_each_voxel_metadata(Ref<FuncRef> callback) const {
void VoxelBuffer::for_each_voxel_metadata_in_area(Ref<FuncRef> callback, Vector3 min_pos, Vector3 max_pos) {
ERR_FAIL_COND(callback.is_null());
_buffer->for_each_voxel_metadata_in_area(callback, Box3i::from_min_max(Vector3i(min_pos), Vector3i(max_pos)));
_buffer->for_each_voxel_metadata_in_area(callback,
Box3i::from_min_max(Vector3i::from_floored(min_pos), Vector3i::from_floored(max_pos)));
}
void VoxelBuffer::copy_voxel_metadata_in_area(Ref<VoxelBuffer> src_buffer, Vector3 src_min_pos, Vector3 src_max_pos,
Vector3 dst_pos) {
ERR_FAIL_COND(src_buffer.is_null());
_buffer->copy_voxel_metadata_in_area(
src_buffer->get_buffer(), Box3i::from_min_max(Vector3i(src_min_pos), Vector3i(src_max_pos)), dst_pos);
src_buffer->get_buffer(),
Box3i::from_min_max(Vector3i::from_floored(src_min_pos), Vector3i::from_floored(src_max_pos)),
Vector3i::from_floored(dst_pos));
}
void VoxelBuffer::clear_voxel_metadata_in_area(Vector3 min_pos, Vector3 max_pos) {
_buffer->clear_voxel_metadata_in_area(Box3i::from_min_max(Vector3i(min_pos), Vector3i(max_pos)));
_buffer->clear_voxel_metadata_in_area(
Box3i::from_min_max(Vector3i::from_floored(min_pos), Vector3i::from_floored(max_pos)));
}
void VoxelBuffer::clear_voxel_metadata() {

View File

@ -57,8 +57,19 @@ public:
~VoxelBuffer();
inline const VoxelBufferInternal &get_buffer() const { return *_buffer; }
inline VoxelBufferInternal &get_buffer() { return *_buffer; }
inline const VoxelBufferInternal &get_buffer() const {
#ifdef DEBUG_ENABLED
CRASH_COND(_buffer == nullptr);
#endif
return *_buffer;
}
inline VoxelBufferInternal &get_buffer() {
#ifdef DEBUG_ENABLED
CRASH_COND(_buffer == nullptr);
#endif
return *_buffer;
}
//inline std::shared_ptr<VoxelBufferInternal> get_buffer_shared() { return _buffer; }
@ -94,7 +105,7 @@ public:
void fill(uint64_t defval, unsigned int channel_index = 0);
void fill_f(real_t value, unsigned int channel = 0);
void fill_area(uint64_t defval, Vector3 min, Vector3 max, unsigned int channel_index) {
_buffer->fill_area(defval, Vector3i(min), Vector3i(max), channel_index);
_buffer->fill_area(defval, Vector3i::from_floored(min), Vector3i::from_floored(max), channel_index);
}
bool is_uniform(unsigned int channel_index) const;
@ -122,10 +133,10 @@ public:
void set_block_metadata(Variant meta);
Variant get_voxel_metadata(Vector3 pos) const {
return _buffer->get_voxel_metadata(Vector3i(pos));
return _buffer->get_voxel_metadata(Vector3i::from_floored(pos));
}
void set_voxel_metadata(Vector3 pos, Variant meta) {
_buffer->set_voxel_metadata(Vector3i(pos), meta);
_buffer->set_voxel_metadata(Vector3i::from_floored(pos), meta);
}
void for_each_voxel_metadata(Ref<FuncRef> callback) const;
void for_each_voxel_metadata_in_area(Ref<FuncRef> callback, Vector3 min_pos, Vector3 max_pos);

View File

@ -80,13 +80,13 @@ int VoxelStream::get_lod_count() const {
VoxelStream::Result VoxelStream::_b_emerge_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND_V(lod < 0, RESULT_ERROR);
ERR_FAIL_COND_V(out_buffer.is_null(), RESULT_ERROR);
return emerge_block(out_buffer->get_buffer(), Vector3i(origin_in_voxels), lod);
return emerge_block(out_buffer->get_buffer(), Vector3i::from_floored(origin_in_voxels), lod);
}
void VoxelStream::_b_immerge_block(Ref<VoxelBuffer> buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND(lod < 0);
ERR_FAIL_COND(buffer.is_null());
immerge_block(buffer->get_buffer(), Vector3i(origin_in_voxels), lod);
immerge_block(buffer->get_buffer(), Vector3i::from_floored(origin_in_voxels), lod);
}
int VoxelStream::_b_get_used_channels_mask() const {

View File

@ -177,7 +177,7 @@ Array VoxelInstanceLibraryItem::serialize_multimesh_item_properties() const {
}
void VoxelInstanceLibraryItem::deserialize_multimesh_item_properties(Array a) {
ERR_FAIL_COND(a.size() != _mesh_lods.size() + 6);
ERR_FAIL_COND(a.size() != int(_mesh_lods.size()) + 6);
int ai = 0;
for (unsigned int i = 0; i < _mesh_lods.size(); ++i) {
_mesh_lods[i] = a[ai++];

View File

@ -1180,7 +1180,7 @@ void VoxelInstancer::remove_floating_multimesh_instances(Block &block, const Tra
for (int instance_index = 0; instance_index < instance_count; ++instance_index) {
// TODO This is terrible in MT mode! Think about keeping a local copy...
const Transform mm_transform = multimesh->get_instance_transform(instance_index);
const Vector3i voxel_pos(mm_transform.origin + block_global_transform.origin);
const Vector3i voxel_pos(Vector3i::from_floored(mm_transform.origin + block_global_transform.origin));
if (!p_voxel_box.contains(voxel_pos)) {
continue;
@ -1261,7 +1261,7 @@ void VoxelInstancer::remove_floating_scene_instances(Block &block, const Transfo
SceneInstance instance = block.scene_instances[instance_index];
ERR_CONTINUE(instance.root == nullptr);
const Transform scene_transform = instance.root->get_transform();
const Vector3i voxel_pos(scene_transform.origin + block_global_transform.origin);
const Vector3i voxel_pos(Vector3i::from_floored(scene_transform.origin + block_global_transform.origin));
if (!p_voxel_box.contains(voxel_pos)) {
continue;

View File

@ -376,6 +376,7 @@ void VoxelLodTerrain::_on_stream_params_changed() {
}*/
void VoxelLodTerrain::set_mesh_block_size(unsigned int mesh_block_size) {
// Mesh block size cannot be smaller than data block size, for now
mesh_block_size = clamp(mesh_block_size, get_data_block_size(), VoxelConstants::MAX_BLOCK_SIZE);
// Only these sizes are allowed at the moment. This stuff is still not supported in a generic way yet,
@ -959,7 +960,7 @@ Vector3 VoxelLodTerrain::voxel_to_data_block_position(Vector3 vpos, int lod_inde
ERR_FAIL_COND_V(lod_index < 0, Vector3());
ERR_FAIL_COND_V(lod_index >= get_lod_count(), Vector3());
const VoxelDataLodMap::Lod &lod = _data->lods[lod_index];
Vector3i bpos = lod.map.voxel_to_block(Vector3i(vpos)) >> lod_index;
Vector3i bpos = lod.map.voxel_to_block(Vector3i::from_floored(vpos)) >> lod_index;
return bpos.to_vec3();
}
@ -967,7 +968,7 @@ Vector3 VoxelLodTerrain::voxel_to_mesh_block_position(Vector3 vpos, int lod_inde
ERR_FAIL_COND_V(lod_index < 0, Vector3());
ERR_FAIL_COND_V(lod_index >= get_lod_count(), Vector3());
const Lod &lod = _lods[lod_index];
Vector3i bpos = lod.mesh_map.voxel_to_block(Vector3i(vpos)) >> lod_index;
Vector3i bpos = lod.mesh_map.voxel_to_block(Vector3i::from_floored(vpos)) >> lod_index;
return bpos.to_vec3();
}
@ -1412,7 +1413,8 @@ void VoxelLodTerrain::process_unload_data_blocks_sliding_box(Vector3 p_viewer_po
// The player can edit them so changes can be propagated to lower lods.
const unsigned int block_size_po2 = get_data_block_size_pow2() + lod_index;
const Vector3i viewer_block_pos_within_lod = VoxelDataMap::voxel_to_block_b(p_viewer_pos, block_size_po2);
const Vector3i viewer_block_pos_within_lod =
VoxelDataMap::voxel_to_block_b(Vector3i::from_floored(p_viewer_pos), block_size_po2);
const Box3i bounds_in_blocks = Box3i(
_bounds_in_voxels.pos >> block_size_po2,
@ -1491,7 +1493,8 @@ void VoxelLodTerrain::process_unload_mesh_blocks_sliding_box(Vector3 p_viewer_po
Lod &lod = _lods[lod_index];
unsigned int block_size_po2 = _lods[0].mesh_map.get_block_size_pow2() + lod_index;
Vector3i viewer_block_pos_within_lod = VoxelMeshMap::voxel_to_block_b(p_viewer_pos, block_size_po2);
const Vector3i viewer_block_pos_within_lod =
VoxelMeshMap::voxel_to_block_b(Vector3i::from_floored(p_viewer_pos), block_size_po2);
const Box3i bounds_in_blocks = Box3i(
_bounds_in_voxels.pos >> block_size_po2,
@ -1540,7 +1543,8 @@ void VoxelLodTerrain::process_octrees_sliding_box(Vector3 p_viewer_pos) {
const unsigned int octree_size = 1 << octree_size_po2;
const unsigned int octree_region_extent = 1 + _view_distance_voxels / (1 << octree_size_po2);
const Vector3i viewer_octree_pos = (Vector3i(p_viewer_pos) + Vector3i(octree_size / 2)) >> octree_size_po2;
const Vector3i viewer_octree_pos =
(Vector3i::from_floored(p_viewer_pos) + Vector3i(octree_size / 2)) >> octree_size_po2;
const Box3i bounds_in_octrees = _bounds_in_voxels.downscaled(octree_size);
@ -2605,8 +2609,8 @@ void VoxelLodTerrain::_b_save_modified_blocks() {
}
void VoxelLodTerrain::_b_set_voxel_bounds(AABB aabb) {
// TODO Please Godot, have an integer AABB!
set_voxel_bounds(Box3i(aabb.position.round(), aabb.size.round()));
ERR_FAIL_COND(!is_valid_size(aabb.size));
set_voxel_bounds(Box3i(Vector3i::from_rounded(aabb.position), Vector3i::from_rounded(aabb.size)));
}
AABB VoxelLodTerrain::_b_get_voxel_bounds() const {
@ -2628,7 +2632,7 @@ Array VoxelLodTerrain::debug_raycast_mesh_block(Vector3 world_origin, Vector3 wo
while (distance < max_distance && hits.size() == 0) {
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
const Lod &lod = _lods[lod_index];
Vector3i bpos = lod.mesh_map.voxel_to_block(Vector3i(pos)) >> lod_index;
const Vector3i bpos = lod.mesh_map.voxel_to_block(Vector3i::from_floored(pos)) >> lod_index;
const VoxelMeshBlock *block = lod.mesh_map.get_block(bpos);
if (block != nullptr && block->is_visible() && block->has_mesh()) {
Dictionary d;
@ -2871,11 +2875,14 @@ void VoxelLodTerrain::set_show_gizmos(bool enable) {
// This copies at multiple LOD levels to debug mips
Array VoxelLodTerrain::_b_debug_print_sdf_top_down(Vector3 center, Vector3 extents) {
ERR_FAIL_COND_V(!is_valid_size(extents), Array());
Array image_array;
image_array.resize(get_lod_count());
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
const Box3i world_box = Box3i::from_center_extents(Vector3i(center) >> lod_index, Vector3i(extents) >> lod_index);
const Box3i world_box = Box3i::from_center_extents(
Vector3i::from_floored(center) >> lod_index, Vector3i::from_floored(extents) >> lod_index);
if (world_box.size.volume() == 0) {
continue;

View File

@ -1316,11 +1316,11 @@ Box3i VoxelTerrain::get_bounds() const {
}
Vector3 VoxelTerrain::_b_voxel_to_data_block(Vector3 pos) const {
return Vector3i(_data_map.voxel_to_block(pos)).to_vec3();
return Vector3i(_data_map.voxel_to_block(Vector3i::from_floored(pos))).to_vec3();
}
Vector3 VoxelTerrain::_b_data_block_to_voxel(Vector3 pos) const {
return Vector3i(_data_map.block_to_voxel(pos)).to_vec3();
return Vector3i(_data_map.block_to_voxel(Vector3i::from_floored(pos))).to_vec3();
}
void VoxelTerrain::_b_save_modified_blocks() {
@ -1329,7 +1329,7 @@ void VoxelTerrain::_b_save_modified_blocks() {
// Explicitely ask to save a block if it was modified
void VoxelTerrain::_b_save_block(Vector3 p_block_pos) {
const Vector3i block_pos(p_block_pos);
const Vector3i block_pos(Vector3i::from_floored(p_block_pos));
VoxelDataBlock *block = _data_map.get_block(block_pos);
ERR_FAIL_COND(block == nullptr);
@ -1342,8 +1342,8 @@ void VoxelTerrain::_b_save_block(Vector3 p_block_pos) {
}
void VoxelTerrain::_b_set_bounds(AABB aabb) {
// TODO Please Godot, have an integer AABB!
set_bounds(Box3i(aabb.position.round(), aabb.size.round()));
ERR_FAIL_COND(!is_valid_size(aabb.size));
set_bounds(Box3i(Vector3i::from_rounded(aabb.position), Vector3i::from_rounded(aabb.size)));
}
AABB VoxelTerrain::_b_get_bounds() const {

View File

@ -33,7 +33,7 @@ private:
uint32_t _viewer_id = 0;
unsigned int _view_distance = 128;
bool _requires_visuals = true;
bool _requires_collisions = false;
bool _requires_collisions = true;
};
#endif // VOXEL_VIEWER_H

View File

@ -493,7 +493,7 @@ void test_voxel_graph_generator_texturing() {
VoxelBufferInternal buffer0;
{
buffer0.create(Vector3i(16, 16, 16));
VoxelBlockRequest request{ buffer0, Vector3(0, -16, 0), 0 };
VoxelBlockRequest request{ buffer0, Vector3i(0, -16, 0), 0 };
generator->generate_block(request);
}
@ -501,7 +501,7 @@ void test_voxel_graph_generator_texturing() {
VoxelBufferInternal buffer1;
{
buffer1.create(Vector3i(16, 16, 16));
VoxelBlockRequest request{ buffer1, Vector3(0, 0, 0), 0 };
VoxelBlockRequest request{ buffer1, Vector3i(0, 0, 0), 0 };
generator->generate_block(request);
}

View File

@ -112,7 +112,7 @@ inline T squared(const T x) {
// 6 | 2 | 2
inline int floordiv(int x, int d) {
#ifdef DEBUG_ENABLED
CRASH_COND(d < 0);
CRASH_COND(d <= 0);
#endif
if (x < 0) {
return (x - d + 1) / d;
@ -154,6 +154,23 @@ inline Vector3 fract(const Vector3 &p) {
return Vector3(fract(p.x), fract(p.y), fract(p.z));
}
inline bool is_valid_size(const Vector3 &s) {
return s.x >= 0 && s.y >= 0 && s.z >= 0;
}
inline bool is_power_of_two(size_t x) {
return x != 0 && (x & (x - 1)) == 0;
}
// If the provided address `a` is not aligned to the number of bytes specified in `align`,
// returns the next aligned address. `align` must be a power of two.
inline size_t alignup(size_t a, size_t align) {
#ifdef DEBUG_ENABLED
CRASH_COND(!is_power_of_two(align));
#endif
return (a + align - 1) & ~(align - 1);
}
// inline bool is_power_of_two(int i) {
// return i & (i - 1);
// }

View File

@ -41,11 +41,8 @@ struct Vector3i {
*this = other;
}
// TODO Deprecate this constructor, it is ambiguous because there are multiple ways to convert a float to an int
_FORCE_INLINE_ Vector3i(const Vector3 &f) {
x = Math::floor(f.x);
y = Math::floor(f.y);
z = Math::floor(f.z);
static inline Vector3i from_cast(const Vector3 &f) {
return Vector3i(f.x, f.y, f.z);
}
static inline Vector3i from_floored(const Vector3 &f) {
@ -62,6 +59,13 @@ struct Vector3i {
Math::round(f.z));
}
static inline Vector3i from_ceiled(const Vector3 &f) {
return Vector3i(
Math::ceil(f.x),
Math::ceil(f.y),
Math::ceil(f.z));
}
_FORCE_INLINE_ Vector3 to_vec3() const {
return Vector3(x, y, z);
}