Compare commits

...
Author SHA1 Message Date
ActualHorse 4fd24f03a1 Slicing function now has multiple deferred stages. 2026-10-01 11:42:21 -04:00
ActualHorse 223eaf2f32 Reworked slicing code in various ways:
- slice_mesh takes an input struct, and returns all data in an output struct.
- Separated loops to ease the possibility of making them deferred functions.
2026-09-29 04:19:39 -04:00
ActualHorse 6e508ccde2 - slice_mesh now supports interpolating custom Float32 data arrays.
- get_skinned_vertex_positions now uses Ref<ArrayMesh> instead of a pointer.
2026-09-27 17:36:49 -04:00
ActualHorse e35adc23b2 Removed fix_polygon_winding's ability to delete edges, since it caused problems
with polygons that have only three vertices and also that isn't really its job
to do anyway.
2026-09-26 14:04:32 -04:00
ActualHorse b5fe9f7c8d HurtboxBase now sends hit updates on a timer instead of on a new frame. 2026-09-26 01:42:38 -04:00
ActualHorse 64421d8ff7 Added a macro to easily call functions on a timer. 2026-09-26 01:34:33 -04:00
ActualHorse b6315f3076 Sliced mesh caps now generate proper normals, thanks to SurfaceTool. 2026-09-26 01:34:01 -04:00
ActualHorse 306db05bfc - Improved loading of newly-sliced MeshInstance3D nodes.
- Character3D now handles slicing one skeleton per frame.
- Fixed an issue where many identical HurtboxDamages were sent to one body.
2026-09-25 03:56:05 -04:00
ActualHorse a9fd36efa6 - Finally fixed that crash caused by accessing invalid bone transform data.
- Skinned vertices are generated per surface, instead of all at once.
2026-09-24 14:41:11 -04:00
11 changed files with 917 additions and 470 deletions
+53 -1
View File
@@ -1,8 +1,60 @@
/*
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
* ©2026 Batty Bovine Productions, LLC. All Rights Reserved.
*
* If this source code makes it to the public internet, this software can be
* considered to be protected by the MIT licence. Have fun with it.
*/
#include "i_damageable.h"
#include "orng_macros.h"
#include "godot_cpp/classes/scene_tree.hpp"
#include "godot_cpp/classes/scene_tree_timer.hpp"
void IDamageable::_slice_meshes(Ref<MeshEditSliceInput> data)
{
this->_is_slicing = true;
this->_input_queue.emplace_back(data);
if (this->_slice_state_queue.empty())
this->_new_slice_loop();
}
void IDamageable::_new_slice_loop()
{
this->_slice_state_queue.emplace_back(MeshEditingLibrary::slice_mesh(this->_input_queue.front()));
this->_slice_loop();
}
void IDamageable::_slice_loop()
{
Ref<MeshSliceState> current_state = this->_slice_state_queue.front();
switch(current_state->current_stage)
{
case MeshEditingLibrary::MeshEditSliceStage::SLICE_STAGE_INIT:
MeshEditingLibrary::slice_mesh_init(current_state);
break;
case MeshEditingLibrary::MeshEditSliceStage::SLICE_STAGE_PLANE_SPLIT:
MeshEditingLibrary::slice_mesh_plane_split(current_state);
break;
case MeshEditingLibrary::MeshEditSliceStage::SLICE_STAGE_EDGE_SPLIT:
MeshEditingLibrary::slice_mesh_edge_split(current_state);
break;
case MeshEditingLibrary::MeshEditSliceStage::SLICE_STAGE_APPLY_CAP:
MeshEditingLibrary::slice_mesh_apply_cap(current_state);
break;
case MeshEditingLibrary::MeshEditSliceStage::SLICE_STAGE_COMPLETE:
this->_slice_complete(current_state->output);
this->_input_queue.erase(this->_input_queue.begin());
this->_slice_state_queue.erase(this->_slice_state_queue.begin());
if (!this->_input_queue.empty())
return this->_new_slice_loop();
break;
}
if (!this->_input_queue.empty())
CALL_NEXT_FRAME(callable_mp(this, &IDamageable::_slice_loop));
else
this->_is_slicing = false;
}
+25 -2
View File
@@ -1,5 +1,5 @@
/*
* ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
* ©2026 Batty Bovine Productions, LLC. All Rights Reserved.
*
* If this source code makes it to the public internet, this software can be
* considered to be protected by the MIT licence. Have fun with it.
@@ -7,9 +7,32 @@
#pragma once
#include "nodes/mesh_editing_library.h"
class IDamageable
#include "godot_cpp/classes/node.hpp"
using namespace godot;
class IDamageable : public Node3D
{
public:
virtual void take_damage(const struct HurtboxDamage &hit_data) = 0;
protected:
void _slice_meshes(Ref<MeshEditSliceInput> data);
virtual void _slice_complete(Ref<MeshEditSliceOutput> data) = 0;
private:
void _new_slice_loop();
void _slice_loop();
bool _is_slicing = false;
std::vector<Ref<MeshSliceState>> _slice_state_queue;
std::vector<Ref<MeshEditSliceInput>> _input_queue;
// Godot boilerplate below
protected:
static void _bind_methods() {}
};
@@ -18,6 +18,8 @@
#include <godot_cpp/classes/physical_bone3d.hpp>
#include <godot_cpp/classes/physical_bone_simulator3d.hpp>
#include <godot_cpp/classes/random_number_generator.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/scene_tree_timer.hpp>
#include <godot_cpp/classes/skin.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot;
@@ -25,7 +27,6 @@ using namespace godot;
void Character3D::_ready()
{
this->skeleton_list.clear();
if (this->character_skeleton)
{
this->_rebuild_mesh_instances();
@@ -35,74 +36,119 @@ void Character3D::_ready()
void Character3D::set_meshes(TypedArray<ArrayMesh> m)
{
this->meshes = m;
this->full_aabb = AABB();
this->_full_aabb = AABB();
this->_rebuild_mesh_instances();
}
void Character3D::set_character_skeleton(CharacterSkeleton3D *cs)
{
this->character_skeleton = cs;
this->_rebuild_mesh_instances();
if (cs)
{
if (this->character_skeleton)
{
this->skeleton_set.erase(this->character_skeleton);
this->character_skeleton->queue_free();
}
this->character_skeleton = cs;
this->skeleton_set.emplace(this->character_skeleton);
this->_rebuild_mesh_instances();
}
}
void Character3D::take_damage(const HurtboxDamage &hit_data)
{
for (const auto &[character_skeleton, bones] : hit_data.bones)
if (this->_hurtbox_queue.empty())
CALL_NEXT_FRAME(callable_mp(this, &Character3D::_next_hurtbox_in_queue));
this->_hurtbox_queue.emplace_back(hit_data);
}
void Character3D::_next_hurtbox_in_queue()
{
HurtboxDamage &hit_data = this->_hurtbox_queue.front();
if (!hit_data.bones.empty())
{
Skeleton3D *skeleton = character_skeleton->get_skeleton();
CharacterSkeleton3D *character_skeleton = hit_data.bones.begin()->first;
const std::vector<int32_t> &bone_hits = hit_data.bones.begin()->second;
hit_data.bones.erase(this->_hurtbox_queue.begin()->bones.begin());
Skeleton3D *skeleton = character_skeleton->get_skeleton();
const Plane &local_slice_plane = skeleton->get_global_transform().inverse().xform(Plane(hit_data.global_hit_transform.get_basis().get_column(1), hit_data.global_hit_transform.origin));
std::vector<MeshInstance3D*> sliced_mesh_instances = character_skeleton->get_mesh_instances();
TypedArray<MeshInstance3D> sliced_mesh_instances = character_skeleton->get_mesh_instances();
const uint32_t num_mesh_instances = sliced_mesh_instances.size();
TypedArray<ArrayMesh> first_half_meshes_to_add;
TypedArray<ArrayMesh> other_half_meshes_to_add;
for (uint32_t i = 0; i < num_mesh_instances; i++)
{
MeshInstance3D *original_mesh_instance = sliced_mesh_instances[i];
Ref<ArrayMesh> first_half; first_half.instantiate();
Ref<ArrayMesh> other_half; other_half.instantiate();
PackedVector3Array &impact_points = MeshEditingLibrary::slice_mesh(original_mesh_instance, skeleton, this->full_aabb, local_slice_plane, first_half, other_half, MeshEditingLibrary::CAP_UV_FILL_MESH_BOUNDS, this->inner_materials);
if (first_half->get_surface_count() <= 0 && other_half->get_surface_count() > 0)
{
Ref<ArrayMesh> temp = first_half;
first_half = other_half;
other_half = temp;
}
if (first_half->get_surface_count() > 0)
{
first_half_meshes_to_add.append(first_half);
}
if (other_half->get_surface_count() > 0)
{
other_half_meshes_to_add.append(other_half);
}
}
character_skeleton->set_meshes(first_half_meshes_to_add);
if (other_half_meshes_to_add.size() > 0)
Ref<MeshEditSliceInput> input; input.instantiate();
input->mesh_instance = cast_to<MeshInstance3D>(sliced_mesh_instances[0]);
input->skeleton = skeleton;
input->full_aabb = this->_full_aabb;
input->local_plane = local_slice_plane;
input->cap_option = MeshEditingLibrary::CAP_UV_FILL_MESH_BOUNDS;
input->cap_materials = this->inner_materials;
this->_slice_meshes(input);
}
else
{
this->_hurtbox_queue.erase(this->_hurtbox_queue.begin());
}
if (!this->_hurtbox_queue.empty())
CALL_NEXT_FRAME(callable_mp(this, &Character3D::_next_hurtbox_in_queue));
}
void Character3D::_slice_complete(Ref<MeshEditSliceOutput> data)
{
if (data->main_half->get_surface_count() <= 0 && data->secondary_half->get_surface_count() > 0)
{
Ref<ArrayMesh> temp = data->main_half;
data->main_half = data->secondary_half;
data->secondary_half = temp;
}
TypedArray<ArrayMesh> first_half_meshes_to_add;
TypedArray<ArrayMesh> other_half_meshes_to_add;
if (data->main_half->get_surface_count() > 0)
{
first_half_meshes_to_add.append(data->main_half);
}
if (data->secondary_half->get_surface_count() > 0)
{
other_half_meshes_to_add.append(data->secondary_half);
}
for (CharacterSkeleton3D *skeleton : this->skeleton_set)
{
if (skeleton->get_skeleton() == data->original_skeleton)
{
RandomNumberGenerator *rng = memnew(RandomNumberGenerator);
const float random_x = rng->randf_range(-0.05f, 0.05f);
const float random_z = rng->randf_range(-0.05f, 0.05f);
CharacterSkeleton3D *new_character_skeleton = cast_to<CharacterSkeleton3D>(this->character_skeleton->duplicate());
this->character_skeleton->add_sibling(new_character_skeleton);
new_character_skeleton->set_position(Vector3(random_x, 0.0f, random_z));
PRINT_LOG(CHARACTER3D_TAG, "Adding new meshes:");
for (uint16_t i = 0; i < other_half_meshes_to_add.size(); i++)
{
const Ref<ArrayMesh> &new_array_mesh = other_half_meshes_to_add[i];
PRINT_LOG(CHARACTER3D_TAG, " Mesh ", i+1, " size: ", new_array_mesh->get_surface_count());
}
new_character_skeleton->set_meshes(other_half_meshes_to_add);
skeleton->set_meshes(first_half_meshes_to_add);
break;
}
}
if (other_half_meshes_to_add.size() > 0)
{
RandomNumberGenerator *rng = memnew(RandomNumberGenerator);
const float random_x = rng->randf_range(-0.05f, 0.05f);
const float random_z = rng->randf_range(-0.05f, 0.05f);
CharacterSkeleton3D *new_character_skeleton = cast_to<CharacterSkeleton3D>(this->character_skeleton->duplicate());
this->character_skeleton->add_sibling(new_character_skeleton);
new_character_skeleton->set_position(Vector3(random_x, 0.0f, random_z));
PRINT_LOG(CHARACTER3D_TAG, "Adding new meshes:");
for (uint16_t i = 0; i < other_half_meshes_to_add.size(); i++)
{
const Ref<ArrayMesh> &new_array_mesh = other_half_meshes_to_add[i];
PRINT_LOG(CHARACTER3D_TAG, " Mesh ", i+1, " size: ", new_array_mesh->get_surface_count());
}
new_character_skeleton->set_meshes(other_half_meshes_to_add);
this->skeleton_set.emplace(new_character_skeleton);
}
}
@@ -114,34 +160,30 @@ void Character3D::_on_fell_out_of_world()
void Character3D::_rebuild_mesh_instances()
{
if (!this->is_node_ready()) return;
if (!this->character_skeleton || !this->is_node_ready())
return;
uint16_t num_mesh_instances = this->_mesh_instances.size();
for (uint16_t i = 0; i < num_mesh_instances; i++)
TypedArray<MeshInstance3D> mesh_instances = this->character_skeleton->set_meshes(this->meshes);
const uint32_t num_meshes = mesh_instances.size();
this->_full_aabb = AABB();
for (uint32_t i = 0; i < num_meshes; i++)
{
this->_mesh_instances[i]->queue_free();
}
if (!this->character_skeleton) return;
this->_mesh_instances = this->character_skeleton->set_meshes(this->meshes);
const uint32_t num_meshes = this->_mesh_instances.size();
for (const MeshInstance3D *mesh_instance : this->_mesh_instances)
{
const Vector3 &full_aabb_position = this->full_aabb.get_position();
const Vector3 &full_aabb_size = this->full_aabb.get_size();
const MeshInstance3D *mesh_instance = cast_to<MeshInstance3D>(mesh_instances[i]);
const Vector3 &full_aabb_position = this->_full_aabb.get_position();
const Vector3 &full_aabb_size = this->_full_aabb.get_size();
const AABB &aabb = mesh_instance->get_aabb();
const Vector3 &aabb_position = aabb.get_position();
const Vector3 &aabb_size = aabb.get_size();
this->full_aabb.set_position(Vector3(
this->_full_aabb.set_position(Vector3(
UtilityFunctions::minf(full_aabb_position.x, aabb_position.x),
UtilityFunctions::minf(full_aabb_position.y, aabb_position.y),
UtilityFunctions::minf(full_aabb_position.z, aabb_position.z)
));
this->full_aabb.set_size(Vector3(
this->_full_aabb.set_size(Vector3(
UtilityFunctions::maxf(full_aabb_position.x, aabb_size.x),
UtilityFunctions::maxf(full_aabb_position.y, aabb_size.y),
UtilityFunctions::maxf(full_aabb_position.z, aabb_size.z)
@@ -13,6 +13,8 @@
#include "nodes/character_nodes/character_skeleton3d.h"
#include <map>
#include <unordered_set>
#include <godot_cpp/classes/character_body3d.hpp>
#include <godot_cpp/classes/material.hpp>
#include <godot_cpp/classes/mesh_instance3d.hpp>
@@ -23,9 +25,9 @@ using namespace godot;
#define CHARACTER3D_TAG "Character3D"
class Character3D : public CharacterBody3D, public IDamageable
class Character3D : public IDamageable
{
GDCLASS(Character3D, CharacterBody3D);
GDCLASS(Character3D, IDamageable);
public:
virtual void _ready() override;
@@ -47,15 +49,19 @@ protected:
private:
void _rebuild_mesh_instances();
void _next_hurtbox_in_queue();
virtual void _slice_complete(Ref<MeshEditSliceOutput> data) override;
TypedArray<ArrayMesh> meshes;
CharacterSkeleton3D *character_skeleton = nullptr;
AABB full_aabb;
std::unordered_set<CharacterSkeleton3D*> skeleton_set;
AABB _full_aabb;
TypedArray<Material> inner_materials;
std::vector<MeshInstance3D*> _mesh_instances;
std::vector<CharacterSkeleton3D*> skeleton_list;
std::vector<HurtboxDamage> _hurtbox_queue;
// Godot boilerplate below
protected:
@@ -9,36 +9,42 @@
#include "orng_macros.h"
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/scene_tree_timer.hpp>
#include <godot_cpp/classes/physical_bone3d.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot;
std::vector<MeshInstance3D*> CharacterSkeleton3D::set_meshes(TypedArray<ArrayMesh> meshes)
TypedArray<MeshInstance3D> CharacterSkeleton3D::set_meshes(TypedArray<ArrayMesh> meshes)
{
if (this->skeleton)
{
uint16_t num_mesh_instances = this->_mesh_instances.size();
for (uint16_t i = 0; i < num_mesh_instances; i++)
{
this->_mesh_instances[i]->queue_free();
}
this->_old_mesh_instances = this->_mesh_instances.duplicate(true);
this->_mesh_instances.clear();
uint16_t num_new_meshes = meshes.size();
this->_mesh_instances.resize(num_new_meshes);
for (uint16_t i = 0; i < num_new_meshes; i++)
{
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
this->skeleton->add_child(mesh_instance, false, Node::INTERNAL_MODE_BACK);
mesh_instance->set_owner(this->get_owner());
mesh_instance->set_mesh(meshes[i]);
mesh_instance->set_skeleton_path(this->skeleton->get_path());
this->_mesh_instances.emplace_back(mesh_instance);
this->skeleton->add_child(mesh_instance, false, Node::INTERNAL_MODE_BACK);
this->_mesh_instances[i] = mesh_instance;
}
CALL_NEXT_FRAME(callable_mp(this, &CharacterSkeleton3D::_delete_old_meshes));
}
return this->_mesh_instances;
}
void CharacterSkeleton3D::_delete_old_meshes()
{
for (uint16_t i = 0; i < this->_old_mesh_instances.size(); i++)
cast_to<MeshInstance3D>(this->_old_mesh_instances[i])->queue_free();
this->_old_mesh_instances.clear();
}
void CharacterSkeleton3D::_activate_ragdoll()
@@ -26,15 +26,15 @@ class CharacterSkeleton3D : public Node3D
GDCLASS(CharacterSkeleton3D, Node3D);
public:
std::vector<MeshInstance3D*> set_meshes(TypedArray<ArrayMesh> meshes);
TypedArray<MeshInstance3D> set_meshes(TypedArray<ArrayMesh> meshes);
Skeleton3D *get_skeleton() const { return this->skeleton; }
PhysicalBoneSimulator3D *get_physical_bone_simulator() const { return this->physical_bone_simulator; }
AnimationPlayer *get_animation_player() const { return this->animation_player; }
AnimationTree *get_animation_tree() const { return this->animation_tree; }
void replace_mesh_instance(const uint16_t index, Ref<ArrayMesh> new_mesh) { this->_mesh_instances[index]->set_mesh(new_mesh); }
std::vector<MeshInstance3D*> get_mesh_instances() const { return this->_mesh_instances; }
void replace_mesh_instance(const uint16_t index, Ref<ArrayMesh> new_mesh) { cast_to<MeshInstance3D>(this->_mesh_instances[index])->set_mesh(new_mesh); }
TypedArray<MeshInstance3D> get_mesh_instances() const { return this->_mesh_instances; }
protected:
void set_skeleton(Skeleton3D *s) { this->skeleton = s; }
@@ -43,7 +43,8 @@ protected:
void set_animation_tree(AnimationTree *at) { this->animation_tree = at; }
private:
void _refresh_skins_and_meshes();
// void _refresh_skins_and_meshes();
void _delete_old_meshes();
void _activate_ragdoll();
@@ -52,7 +53,8 @@ private:
AnimationPlayer *animation_player = nullptr;
AnimationTree *animation_tree = nullptr;
std::vector<MeshInstance3D*> _mesh_instances;
TypedArray<MeshInstance3D> _mesh_instances;
TypedArray<MeshInstance3D> _old_mesh_instances;
// Godot boilerplate below
protected:
+30 -8
View File
@@ -17,6 +17,8 @@
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot;
#include <unordered_set>
void HurtboxBase::_enter_tree()
{
@@ -50,7 +52,7 @@ void HurtboxBase::_ready()
this->set_process(false);
this->remaining_life = this->lifetime;
CALL_NEXT_FRAME(callable_mp(this, &HurtboxBase::_send_next_in_queue));
this->_send_next_in_queue();
}
void HurtboxBase::_physics_process(double delta)
@@ -58,7 +60,7 @@ void HurtboxBase::_physics_process(double delta)
const TypedArray<Node3D> &bodies = this->get_overlapping_bodies();
const uint32_t num_bodies = bodies.size();
std::vector<IDamageable*> new_hit_bodies;
std::unordered_set<IDamageable*> new_hit_bodies;
std::vector<CharacterSkeleton3D*> new_hit_skeletons;
for (uint32_t i = 0; i < num_bodies; i++)
@@ -71,7 +73,7 @@ void HurtboxBase::_physics_process(double delta)
{
if (std::find(this->hit_bodies.begin(), this->hit_bodies.end(), body) == this->hit_bodies.end())
{
new_hit_bodies.emplace_back(body);
new_hit_bodies.emplace(body);
if (std::find(new_hit_skeletons.begin(), new_hit_skeletons.end(), character_skeleton) == new_hit_skeletons.end())
{
@@ -110,12 +112,32 @@ void HurtboxBase::_send_next_in_queue()
{
if (!this->hit_data_queue.empty())
{
const std::map<IDamageable*, std::vector<HurtboxDamage>>::iterator &first_in_queue = this->hit_data_queue.begin();
for (const HurtboxDamage &damage : first_in_queue->second)
IDamageable *closest_damageable = nullptr;
float shortest_distance_from_hurtbox = FLT_MAX;
bool found_character_3d = false;
for (const auto &[damageable, hit_data_list] : this->hit_data_queue)
{
first_in_queue->first->take_damage(damage);
if (const Character3D *node = (Character3D*)(damageable))
{
const float distance_to_hurtbox = node->get_global_position().distance_squared_to(this->get_global_position());
if (distance_to_hurtbox < shortest_distance_from_hurtbox)
{
closest_damageable = damageable;
shortest_distance_from_hurtbox = distance_to_hurtbox;
found_character_3d = true;
}
// if (!found_character_3d && const SliceablePhysicsObject3D *node = (SliceablePhysicsObject3D*)(damageable) )
// {
// // This is where we handle regular physics objects.
// }
}
}
if (closest_damageable)
{
for (const HurtboxDamage &damage : this->hit_data_queue[closest_damageable])
closest_damageable->take_damage(damage);
this->hit_data_queue.erase(closest_damageable);
}
this->hit_data_queue.erase(this->hit_data_queue.begin());
}
if (this->hit_data_queue.empty() && this->remaining_life <= 0.0f)
@@ -124,6 +146,6 @@ void HurtboxBase::_send_next_in_queue()
}
else
{
CALL_NEXT_FRAME(callable_mp(this, &HurtboxBase::_send_next_in_queue));
CALL_ON_TIMEOUT(1.0f / 8.0f, callable_mp(this, &HurtboxBase::_send_next_in_queue));
}
}
File diff suppressed because it is too large Load Diff
+144 -4
View File
@@ -15,11 +15,12 @@
#include <godot_cpp/variant/vector3.hpp>
using namespace godot;
#include <map>
#include <vector>
#define MESH_EDITING_LIBRARY_TAG "MeshEditingLibrary"
typedef std::vector<Vector3> SkinnedVertices;
typedef PackedVector3Array SkinnedVertices;
typedef std::vector<SkinnedVertices> SkinnedSurfaces;
@@ -55,6 +56,11 @@ struct MeshEditPolygon2D
};
class MeshEditSliceInput;
class MeshEditSliceOutput;
class MeshSliceState;
class MeshEditingLibrary : public Node
{
GDCLASS(MeshEditingLibrary, Node);
@@ -63,6 +69,15 @@ public:
MeshEditingLibrary(){}
~MeshEditingLibrary(){}
enum MeshEditSliceStage
{
SLICE_STAGE_INIT,
SLICE_STAGE_PLANE_SPLIT,
SLICE_STAGE_EDGE_SPLIT,
SLICE_STAGE_APPLY_CAP,
SLICE_STAGE_COMPLETE
};
enum MeshEditCapUV
{
CAP_UV_FILL_MESH_BOUNDS,
@@ -70,7 +85,12 @@ public:
CAP_UV_TILED
};
static PackedVector3Array slice_mesh(MeshInstance3D *original_mesh_instance, const Skeleton3D *original_skeleton, const AABB &original_aabb, const Plane &local_plane, Ref<ArrayMesh> out_first_half, Ref<ArrayMesh> out_other_half, const MeshEditCapUV cap_option, const TypedArray<Material> &cap_materials);
static Ref<class MeshSliceState> slice_mesh(const Ref<class MeshEditSliceInput> input);
static void slice_mesh_init(Ref<class MeshSliceState> state);
static void slice_mesh_plane_split(Ref<class MeshSliceState> state);
static void slice_mesh_edge_split(Ref<class MeshSliceState> state);
static void slice_mesh_apply_cap(Ref<class MeshSliceState> state);
protected:
enum MeshSlicePlaneOrientation
@@ -83,7 +103,9 @@ protected:
private:
static const Vector2 calculate_planar_uv(const Vector3 &vertex, const Vector3 &mesh_bounds, const MeshSlicePlaneOrientation &axis);
static SkinnedSurfaces get_skinned_vertex_positions(const Mesh *mesh, const Skeleton3D *skeleton = nullptr, std::vector<std::vector<Transform3D>> &bone_transforms = std::vector<std::vector<Transform3D>>());
static SkinnedVertices get_skinned_vertex_positions(const uint8_t surface, const Ref<ArrayMesh> mesh, const Skeleton3D *skeleton = nullptr, std::vector<Transform3D> &bone_transforms = std::vector<Transform3D>());
static constexpr const uint8_t uv_to_body_part_shift(const float x, const float y) { return (uint8_t(y * 5.0f) * 5) + uint8_t(x * 5.0f); }
static const Transform3D project_edges(std::vector<MeshEditEdge2D> &out_2d_edges, const std::vector<MeshEditEdge3D> &in_3d_edges, const Plane &plane);
static void build_2d_polygons_from_edges(std::vector<MeshEditPolygon2D> &out_polygons, const std::vector<MeshEditEdge2D> &in_edges, std::vector<MeshEditPolygon2D> &error_polygons);
@@ -96,7 +118,13 @@ private:
protected:
static void _bind_methods()
{
ClassDB::bind_static_method("MeshEditingLibrary", D_METHOD("slice_mesh", "original_mesh_instance", "original_skeleton", "local_plane", "out_first_half", "out_other_half", "cap_option", "cap_material"), &MeshEditingLibrary::slice_mesh);
ClassDB::bind_static_method("MeshEditingLibrary", D_METHOD("slice_mesh", "input"), &MeshEditingLibrary::slice_mesh);
BIND_ENUM_CONSTANT(SLICE_STAGE_INIT);
BIND_ENUM_CONSTANT(SLICE_STAGE_PLANE_SPLIT);
BIND_ENUM_CONSTANT(SLICE_STAGE_EDGE_SPLIT);
BIND_ENUM_CONSTANT(SLICE_STAGE_APPLY_CAP);
BIND_ENUM_CONSTANT(SLICE_STAGE_COMPLETE);
BIND_ENUM_CONSTANT(CAP_UV_FILL_MESH_BOUNDS);
BIND_ENUM_CONSTANT(CAP_UV_FILL_CAP_BOUNDS);
@@ -104,4 +132,116 @@ protected:
}
};
VARIANT_ENUM_CAST(MeshEditingLibrary::MeshEditSliceStage);
VARIANT_ENUM_CAST(MeshEditingLibrary::MeshEditCapUV);
class MeshEditSliceInput : public RefCounted
{
GDCLASS(MeshEditSliceInput, RefCounted);
public:
MeshInstance3D *mesh_instance = nullptr;
const Skeleton3D *skeleton = nullptr;
AABB full_aabb;
Plane local_plane;
uint32_t hit_mask = 0b1111111111111111111111111;
MeshEditingLibrary::MeshEditCapUV cap_option;
TypedArray<Material> cap_materials;
protected:
static void _bind_methods(){}
};
class MeshEditSliceOutput : public RefCounted
{
GDCLASS(MeshEditSliceOutput, RefCounted);
public:
const Skeleton3D *original_skeleton = nullptr;
Ref<ArrayMesh> main_half;
Ref<ArrayMesh> secondary_half;
PackedVector3Array impact_points;
uint32_t hit_map = 0b0000000000000000000000000;
protected:
static void _bind_methods(){}
};
class MeshSliceState : public RefCounted
{
GDCLASS(MeshSliceState, RefCounted);
public:
MeshEditingLibrary::MeshEditSliceStage current_stage = MeshEditingLibrary::MeshEditSliceStage::SLICE_STAGE_INIT;
MeshInstance3D *current_mesh = nullptr;
Ref<MeshEditSliceInput> input;
uint8_t surface = 0;
uint32_t element = 0;
Ref<ArrayMesh> first_half;
Ref<ArrayMesh> other_half;
Ref<ArrayMesh> original_mesh;
uint8_t num_surfaces;
std::vector<Array> surface_arrays_list;
std::vector<SkinnedVertices> skinned_vertex_positions;
std::vector<std::vector<Transform3D>> skinned_vertex_transforms;
std::vector<bool> has_normal;
std::vector<bool> has_tangent;
std::vector<bool> has_uv;
std::vector<bool> has_uv2;
std::vector<bool> has_colour;
std::vector<bool> has_bone;
std::vector<bool> has_weight;
std::vector<bool> has_custom0;
std::vector<uint8_t> num_bones_per_vertex;
std::vector<uint8_t> num_custom0_per_vertex;
std::vector<PackedVector3Array> first_half_vertices;
std::vector<PackedVector3Array> first_half_normals;
std::vector<PackedFloat32Array> first_half_tangents;
std::vector<PackedVector2Array> first_half_uvs;
std::vector<PackedVector2Array> first_half_uv2s;
std::vector<PackedColorArray> first_half_colours;
std::vector<PackedInt32Array> first_half_bones;
std::vector<PackedFloat32Array> first_half_weights;
std::vector<PackedFloat32Array> first_half_custom0;
std::vector<PackedVector3Array> other_half_vertices;
std::vector<PackedVector3Array> other_half_normals;
std::vector<PackedFloat32Array> other_half_tangents;
std::vector<PackedVector2Array> other_half_uvs;
std::vector<PackedVector2Array> other_half_uv2s;
std::vector<PackedColorArray> other_half_colours;
std::vector<PackedInt32Array> other_half_bones;
std::vector<PackedFloat32Array> other_half_weights;
std::vector<PackedFloat32Array> other_half_custom0;
std::vector<PackedInt32Array> first_half_indices;
std::vector<PackedInt32Array> other_half_indices;
std::vector<std::vector<float>> vertex_distances;
std::vector<std::map<uint32_t, uint32_t>> base_to_first_sliced_vert_indices;
std::vector<std::map<uint32_t, uint32_t>> base_to_other_sliced_vert_indices;
uint32_t first_half_split_map = 0x00000000;
uint32_t other_half_split_map = 0x00000000;
std::vector<MeshEditEdge3D> clip_edges;
Ref<MeshEditSliceOutput> output;
protected:
static void _bind_methods(){}
};
+3 -1
View File
@@ -34,8 +34,10 @@
/**
* Deferred function helpers
*/
#define CALL_ON_TIMEOUT(T,C) \
this->get_tree()->create_timer(T)->connect("timeout", C)
#define CALL_NEXT_FRAME(C) \
this->get_tree()->create_timer(SMALL_NUMBER)->connect("timeout", C)
CALL_ON_TIMEOUT(SMALL_NUMBER,C)
/**
+3
View File
@@ -78,6 +78,9 @@ void initialize_orng_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<SaveFileData>();
ClassDB::register_class<MeshEditingLibrary>();
ClassDB::register_class<MeshEditSliceInput>();
ClassDB::register_class<MeshEditSliceOutput>();
ClassDB::register_class<MeshSliceState>();
GDSINGLETON_REGISTER_CLASS(InputHandler, _input_handler_singleton);
GDSINGLETON_REGISTER_CLASS(SaveManager, _save_manager_singleton);