feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -1,11 +1,52 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_csg = env_modules.Clone()
|
||||
|
||||
env_csg.Append(CPPDEFINES=("MANIFOLD_PAR", "-1"))
|
||||
|
||||
# Thirdparty source files
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
thirdparty_dir = "#thirdparty/manifold/"
|
||||
thirdparty_sources = [
|
||||
"src/boolean_result.cpp",
|
||||
"src/boolean3.cpp",
|
||||
"src/constructors.cpp",
|
||||
"src/csg_tree.cpp",
|
||||
"src/edge_op.cpp",
|
||||
"src/face_op.cpp",
|
||||
"src/impl.cpp",
|
||||
"src/manifold.cpp",
|
||||
"src/polygon.cpp",
|
||||
"src/properties.cpp",
|
||||
"src/quickhull.cpp",
|
||||
"src/smoothing.cpp",
|
||||
"src/sort.cpp",
|
||||
"src/subdivision.cpp",
|
||||
]
|
||||
|
||||
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||
env_csg.Prepend(CPPPATH=[thirdparty_dir + "include"])
|
||||
env_thirdparty = env_csg.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
|
||||
env.modules_sources += thirdparty_obj
|
||||
|
||||
# Godot source files
|
||||
env_csg.add_source_files(env.modules_sources, "*.cpp")
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_csg.add_source_files(module_obj, "*.cpp")
|
||||
|
||||
if env.editor_build:
|
||||
env_csg.add_source_files(env.modules_sources, "editor/*.cpp")
|
||||
env_csg.add_source_files(module_obj, "editor/*.cpp")
|
||||
|
||||
env.modules_sources += module_obj
|
||||
|
||||
# Needed to force rebuilding the module files when the thirdparty library is updated.
|
||||
env.Depends(module_obj, thirdparty_obj)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -32,13 +32,10 @@
|
|||
#define CSG_H
|
||||
|
||||
#include "core/math/aabb.h"
|
||||
#include "core/math/plane.h"
|
||||
#include "core/math/transform_3d.h"
|
||||
#include "core/math/vector2.h"
|
||||
#include "core/math/vector3.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/templates/list.h"
|
||||
#include "core/templates/oa_hash_map.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
||||
|
|
@ -55,150 +52,18 @@ struct CSGBrush {
|
|||
Vector<Face> faces;
|
||||
Vector<Ref<Material>> materials;
|
||||
|
||||
inline void _regen_face_aabbs();
|
||||
inline void _regen_face_aabbs() {
|
||||
for (int i = 0; i < faces.size(); i++) {
|
||||
faces.write[i].aabb = AABB();
|
||||
faces.write[i].aabb.position = faces[i].vertices[0];
|
||||
faces.write[i].aabb.expand_to(faces[i].vertices[1]);
|
||||
faces.write[i].aabb.expand_to(faces[i].vertices[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a brush from faces.
|
||||
void build_from_faces(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials, const Vector<bool> &p_invert_faces);
|
||||
void copy_from(const CSGBrush &p_brush, const Transform3D &p_xform);
|
||||
};
|
||||
|
||||
struct CSGBrushOperation {
|
||||
enum Operation {
|
||||
OPERATION_UNION,
|
||||
OPERATION_INTERSECTION,
|
||||
OPERATION_SUBTRACTION,
|
||||
};
|
||||
|
||||
void merge_brushes(Operation p_operation, const CSGBrush &p_brush_a, const CSGBrush &p_brush_b, CSGBrush &r_merged_brush, float p_vertex_snap);
|
||||
|
||||
struct MeshMerge {
|
||||
struct Face {
|
||||
bool from_b = false;
|
||||
bool inside = false;
|
||||
int points[3] = {};
|
||||
Vector2 uvs[3];
|
||||
bool smooth = false;
|
||||
bool invert = false;
|
||||
int material_idx = 0;
|
||||
};
|
||||
|
||||
struct FaceBVH {
|
||||
int face = 0;
|
||||
int left = 0;
|
||||
int right = 0;
|
||||
int next = 0;
|
||||
Vector3 center;
|
||||
AABB aabb;
|
||||
};
|
||||
|
||||
struct FaceBVHCmpX {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.x < p_right->center.x;
|
||||
}
|
||||
};
|
||||
|
||||
struct FaceBVHCmpY {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.y < p_right->center.y;
|
||||
}
|
||||
};
|
||||
struct FaceBVHCmpZ {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.z < p_right->center.z;
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexKey {
|
||||
int32_t x, y, z;
|
||||
_FORCE_INLINE_ bool operator<(const VertexKey &p_key) const {
|
||||
if (x == p_key.x) {
|
||||
if (y == p_key.y) {
|
||||
return z < p_key.z;
|
||||
} else {
|
||||
return y < p_key.y;
|
||||
}
|
||||
} else {
|
||||
return x < p_key.x;
|
||||
}
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool operator==(const VertexKey &p_key) const {
|
||||
return (x == p_key.x && y == p_key.y && z == p_key.z);
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexKeyHash {
|
||||
static _FORCE_INLINE_ uint32_t hash(const VertexKey &p_vk) {
|
||||
uint32_t h = hash_murmur3_one_32(p_vk.x);
|
||||
h = hash_murmur3_one_32(p_vk.y, h);
|
||||
h = hash_murmur3_one_32(p_vk.z, h);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
struct Intersection {
|
||||
bool found = false;
|
||||
real_t conormal = FLT_MAX;
|
||||
real_t distance_squared = FLT_MAX;
|
||||
real_t origin_angle = FLT_MAX;
|
||||
};
|
||||
|
||||
struct IntersectionDistance {
|
||||
bool is_conormal;
|
||||
real_t distance_squared;
|
||||
};
|
||||
|
||||
Vector<Vector3> points;
|
||||
Vector<Face> faces;
|
||||
HashMap<Ref<Material>, int> materials;
|
||||
HashMap<Vector3, int> vertex_map;
|
||||
OAHashMap<VertexKey, int, VertexKeyHash> snap_cache;
|
||||
float vertex_snap = 0.0;
|
||||
|
||||
inline void _add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const;
|
||||
inline bool _bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const;
|
||||
inline int _create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc);
|
||||
|
||||
void add_face(const Vector3 p_points[3], const Vector2 p_uvs[3], bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
|
||||
void mark_inside_faces();
|
||||
};
|
||||
|
||||
struct Build2DFaces {
|
||||
struct Vertex2D {
|
||||
Vector2 point;
|
||||
Vector2 uv;
|
||||
};
|
||||
|
||||
struct Face2D {
|
||||
int vertex_idx[3] = {};
|
||||
};
|
||||
|
||||
Vector<Vertex2D> vertices;
|
||||
Vector<Face2D> faces;
|
||||
Plane plane;
|
||||
Transform3D to_2D;
|
||||
Transform3D to_3D;
|
||||
float vertex_snap2 = 0.0;
|
||||
|
||||
inline int _get_point_idx(const Vector2 &p_point);
|
||||
inline int _add_vertex(const Vertex2D &p_vertex);
|
||||
inline void _add_vertex_idx_sorted(Vector<int> &r_vertex_indices, int p_new_vertex_index);
|
||||
inline void _merge_faces(const Vector<int> &p_segment_indices);
|
||||
inline void _find_edge_intersections(const Vector2 p_segment_points[2], Vector<int> &r_segment_indices);
|
||||
inline int _insert_point(const Vector2 &p_point);
|
||||
|
||||
void insert(const CSGBrush &p_brush, int p_brush_face);
|
||||
void addFacesToMesh(MeshMerge &r_mesh_merge, bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
|
||||
|
||||
Build2DFaces() {}
|
||||
Build2DFaces(const CSGBrush &p_brush, int p_brush_face, float p_vertex_snap2);
|
||||
};
|
||||
|
||||
struct Build2DFaceCollection {
|
||||
HashMap<int, Build2DFaces> build2DFacesA;
|
||||
HashMap<int, Build2DFaces> build2DFacesB;
|
||||
};
|
||||
|
||||
void update_faces(const CSGBrush &p_brush_a, const int p_face_idx_a, const CSGBrush &p_brush_b, const int p_face_idx_b, Build2DFaceCollection &p_collection, float p_vertex_snap);
|
||||
};
|
||||
|
||||
#endif // CSG_H
|
||||
|
|
|
|||
|
|
@ -30,7 +30,48 @@
|
|||
|
||||
#include "csg_shape.h"
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
#include "core/io/json.h"
|
||||
#endif // DEV_ENABLED
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
|
||||
#include "scene/resources/navigation_mesh.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
|
||||
#include <manifold/manifold.h>
|
||||
|
||||
Callable CSGShape3D::_navmesh_source_geometry_parsing_callback;
|
||||
RID CSGShape3D::_navmesh_source_geometry_parser;
|
||||
|
||||
void CSGShape3D::navmesh_parse_init() {
|
||||
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
|
||||
if (!_navmesh_source_geometry_parser.is_valid()) {
|
||||
_navmesh_source_geometry_parsing_callback = callable_mp_static(&CSGShape3D::navmesh_parse_source_geometry);
|
||||
_navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create();
|
||||
NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShape3D::navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
|
||||
CSGShape3D *csgshape3d = Object::cast_to<CSGShape3D>(p_node);
|
||||
|
||||
if (csgshape3d == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
|
||||
uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
|
||||
|
||||
if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS && csgshape3d->is_using_collision() && (csgshape3d->get_collision_layer() & parsed_collision_mask)) || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
|
||||
Array meshes = csgshape3d->get_meshes();
|
||||
if (!meshes.is_empty()) {
|
||||
Ref<Mesh> mesh = meshes[1];
|
||||
if (mesh.is_valid()) {
|
||||
p_source_geometry_data->add_mesh(mesh, csgshape3d->get_global_transform());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShape3D::set_use_collision(bool p_enable) {
|
||||
if (use_collision == p_enable) {
|
||||
|
|
@ -125,6 +166,16 @@ bool CSGShape3D::get_collision_mask_value(int p_layer_number) const {
|
|||
return get_collision_mask() & (1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
RID CSGShape3D::_get_root_collision_instance() const {
|
||||
if (root_collision_instance.is_valid()) {
|
||||
return root_collision_instance;
|
||||
} else if (parent_shape) {
|
||||
return parent_shape->_get_root_collision_instance();
|
||||
}
|
||||
|
||||
return RID();
|
||||
}
|
||||
|
||||
void CSGShape3D::set_collision_priority(real_t p_priority) {
|
||||
collision_priority = p_priority;
|
||||
if (root_collision_instance.is_valid()) {
|
||||
|
|
@ -140,6 +191,7 @@ bool CSGShape3D::is_root_shape() const {
|
|||
return !parent_shape;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void CSGShape3D::set_snap(float p_snap) {
|
||||
if (snap == p_snap) {
|
||||
return;
|
||||
|
|
@ -152,6 +204,7 @@ void CSGShape3D::set_snap(float p_snap) {
|
|||
float CSGShape3D::get_snap() const {
|
||||
return snap;
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void CSGShape3D::_make_dirty(bool p_parent_removing) {
|
||||
if ((p_parent_removing || is_root_shape()) && !dirty) {
|
||||
|
|
@ -167,78 +220,279 @@ void CSGShape3D::_make_dirty(bool p_parent_removing) {
|
|||
dirty = true;
|
||||
}
|
||||
|
||||
CSGBrush *CSGShape3D::_get_brush() {
|
||||
if (dirty) {
|
||||
if (brush) {
|
||||
memdelete(brush);
|
||||
}
|
||||
brush = nullptr;
|
||||
enum ManifoldProperty {
|
||||
MANIFOLD_PROPERTY_POSITION_X = 0,
|
||||
MANIFOLD_PROPERTY_POSITION_Y,
|
||||
MANIFOLD_PROPERTY_POSITION_Z,
|
||||
MANIFOLD_PROPERTY_INVERT,
|
||||
MANIFOLD_PROPERTY_SMOOTH_GROUP,
|
||||
MANIFOLD_PROPERTY_UV_X_0,
|
||||
MANIFOLD_PROPERTY_UV_Y_0,
|
||||
MANIFOLD_PROPERTY_MAX
|
||||
};
|
||||
|
||||
CSGBrush *n = _build_brush();
|
||||
static void _unpack_manifold(
|
||||
const manifold::Manifold &p_manifold,
|
||||
const HashMap<int32_t, Ref<Material>> &p_mesh_materials,
|
||||
CSGBrush *r_mesh_merge) {
|
||||
manifold::MeshGL64 mesh = p_manifold.GetMeshGL64();
|
||||
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
CSGShape3D *child = Object::cast_to<CSGShape3D>(get_child(i));
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
if (!child->is_visible()) {
|
||||
continue;
|
||||
}
|
||||
constexpr int32_t order[3] = { 0, 2, 1 };
|
||||
|
||||
CSGBrush *n2 = child->_get_brush();
|
||||
if (!n2) {
|
||||
continue;
|
||||
}
|
||||
if (!n) {
|
||||
n = memnew(CSGBrush);
|
||||
|
||||
n->copy_from(*n2, child->get_transform());
|
||||
|
||||
} else {
|
||||
CSGBrush *nn = memnew(CSGBrush);
|
||||
CSGBrush *nn2 = memnew(CSGBrush);
|
||||
nn2->copy_from(*n2, child->get_transform());
|
||||
|
||||
CSGBrushOperation bop;
|
||||
|
||||
switch (child->get_operation()) {
|
||||
case CSGShape3D::OPERATION_UNION:
|
||||
bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_INTERSECTION:
|
||||
bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_SUBTRACTION:
|
||||
bop.merge_brushes(CSGBrushOperation::OPERATION_SUBTRACTION, *n, *nn2, *nn, snap);
|
||||
break;
|
||||
}
|
||||
memdelete(n);
|
||||
memdelete(nn2);
|
||||
n = nn;
|
||||
}
|
||||
for (size_t run_i = 0; run_i < mesh.runIndex.size() - 1; run_i++) {
|
||||
uint32_t original_id = -1;
|
||||
if (run_i < mesh.runOriginalID.size()) {
|
||||
original_id = mesh.runOriginalID[run_i];
|
||||
}
|
||||
|
||||
if (n) {
|
||||
AABB aabb;
|
||||
for (int i = 0; i < n->faces.size(); i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (i == 0 && j == 0) {
|
||||
aabb.position = n->faces[i].vertices[j];
|
||||
} else {
|
||||
aabb.expand_to(n->faces[i].vertices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
node_aabb = aabb;
|
||||
} else {
|
||||
node_aabb = AABB();
|
||||
Ref<Material> material;
|
||||
if (p_mesh_materials.has(original_id)) {
|
||||
material = p_mesh_materials[original_id];
|
||||
}
|
||||
// Find or reserve a material ID in the brush.
|
||||
int32_t material_id = r_mesh_merge->materials.find(material);
|
||||
if (material_id == -1) {
|
||||
material_id = r_mesh_merge->materials.size();
|
||||
r_mesh_merge->materials.push_back(material);
|
||||
}
|
||||
|
||||
brush = n;
|
||||
size_t begin = mesh.runIndex[run_i];
|
||||
size_t end = mesh.runIndex[run_i + 1];
|
||||
for (size_t vert_i = begin; vert_i < end; vert_i += 3) {
|
||||
CSGBrush::Face face;
|
||||
face.material = material_id;
|
||||
int32_t first_property_index = mesh.triVerts[vert_i + order[0]];
|
||||
face.smooth = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_SMOOTH_GROUP] > 0.5f;
|
||||
face.invert = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_INVERT] > 0.5f;
|
||||
|
||||
dirty = false;
|
||||
for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) {
|
||||
int32_t property_i = mesh.triVerts[vert_i + order[tri_order_i]];
|
||||
ERR_FAIL_COND_MSG(property_i * mesh.numProp >= mesh.vertProperties.size(), "Invalid index into vertex properties");
|
||||
face.vertices[tri_order_i] = Vector3(
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_X],
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Y],
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Z]);
|
||||
face.uvs[tri_order_i] = Vector2(
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_X_0],
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_Y_0]);
|
||||
}
|
||||
r_mesh_merge->faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
r_mesh_merge->_regen_face_aabbs();
|
||||
}
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
static String _export_meshgl_as_json(const manifold::MeshGL64 &p_mesh) {
|
||||
Dictionary mesh_dict;
|
||||
mesh_dict["numProp"] = p_mesh.numProp;
|
||||
|
||||
Array vert_properties;
|
||||
for (const double &val : p_mesh.vertProperties) {
|
||||
vert_properties.append(val);
|
||||
}
|
||||
mesh_dict["vertProperties"] = vert_properties;
|
||||
|
||||
Array tri_verts;
|
||||
for (const uint64_t &val : p_mesh.triVerts) {
|
||||
tri_verts.append(val);
|
||||
}
|
||||
mesh_dict["triVerts"] = tri_verts;
|
||||
|
||||
Array merge_from_vert;
|
||||
for (const uint64_t &val : p_mesh.mergeFromVert) {
|
||||
merge_from_vert.append(val);
|
||||
}
|
||||
mesh_dict["mergeFromVert"] = merge_from_vert;
|
||||
|
||||
Array merge_to_vert;
|
||||
for (const uint64_t &val : p_mesh.mergeToVert) {
|
||||
merge_to_vert.append(val);
|
||||
}
|
||||
mesh_dict["mergeToVert"] = merge_to_vert;
|
||||
|
||||
Array run_index;
|
||||
for (const uint64_t &val : p_mesh.runIndex) {
|
||||
run_index.append(val);
|
||||
}
|
||||
mesh_dict["runIndex"] = run_index;
|
||||
|
||||
Array run_original_id;
|
||||
for (const uint32_t &val : p_mesh.runOriginalID) {
|
||||
run_original_id.append(val);
|
||||
}
|
||||
mesh_dict["runOriginalID"] = run_original_id;
|
||||
|
||||
Array run_transform;
|
||||
for (const double &val : p_mesh.runTransform) {
|
||||
run_transform.append(val);
|
||||
}
|
||||
mesh_dict["runTransform"] = run_transform;
|
||||
|
||||
Array face_id;
|
||||
for (const uint64_t &val : p_mesh.faceID) {
|
||||
face_id.append(val);
|
||||
}
|
||||
mesh_dict["faceID"] = face_id;
|
||||
|
||||
Array halfedge_tangent;
|
||||
for (const double &val : p_mesh.halfedgeTangent) {
|
||||
halfedge_tangent.append(val);
|
||||
}
|
||||
mesh_dict["halfedgeTangent"] = halfedge_tangent;
|
||||
|
||||
mesh_dict["tolerance"] = p_mesh.tolerance;
|
||||
|
||||
String json_string = JSON::stringify(mesh_dict);
|
||||
return json_string;
|
||||
}
|
||||
#endif // DEV_ENABLED
|
||||
|
||||
static void _pack_manifold(
|
||||
const CSGBrush *const p_mesh_merge,
|
||||
manifold::Manifold &r_manifold,
|
||||
HashMap<int32_t, Ref<Material>> &p_mesh_materials,
|
||||
CSGShape3D *p_csg_shape) {
|
||||
ERR_FAIL_NULL_MSG(p_mesh_merge, "p_mesh_merge is null");
|
||||
ERR_FAIL_NULL_MSG(p_csg_shape, "p_shape is null");
|
||||
HashMap<uint32_t, Vector<CSGBrush::Face>> faces_by_material;
|
||||
for (int face_i = 0; face_i < p_mesh_merge->faces.size(); face_i++) {
|
||||
const CSGBrush::Face &face = p_mesh_merge->faces[face_i];
|
||||
faces_by_material[face.material].push_back(face);
|
||||
}
|
||||
|
||||
manifold::MeshGL64 mesh;
|
||||
mesh.numProp = MANIFOLD_PROPERTY_MAX;
|
||||
mesh.runOriginalID.reserve(faces_by_material.size());
|
||||
mesh.runIndex.reserve(faces_by_material.size() + 1);
|
||||
mesh.vertProperties.reserve(p_mesh_merge->faces.size() * 3 * MANIFOLD_PROPERTY_MAX);
|
||||
|
||||
// Make a run of triangles for each material.
|
||||
for (const KeyValue<uint32_t, Vector<CSGBrush::Face>> &E : faces_by_material) {
|
||||
const uint32_t material_id = E.key;
|
||||
const Vector<CSGBrush::Face> &faces = E.value;
|
||||
mesh.runIndex.push_back(mesh.triVerts.size());
|
||||
|
||||
// Associate the material with an ID.
|
||||
uint32_t reserved_id = r_manifold.ReserveIDs(1);
|
||||
mesh.runOriginalID.push_back(reserved_id);
|
||||
Ref<Material> material;
|
||||
if (material_id < p_mesh_merge->materials.size()) {
|
||||
material = p_mesh_merge->materials[material_id];
|
||||
}
|
||||
|
||||
p_mesh_materials.insert(reserved_id, material);
|
||||
for (const CSGBrush::Face &face : faces) {
|
||||
for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) {
|
||||
constexpr int32_t order[3] = { 0, 2, 1 };
|
||||
int i = order[tri_order_i];
|
||||
|
||||
mesh.triVerts.push_back(mesh.vertProperties.size() / MANIFOLD_PROPERTY_MAX);
|
||||
|
||||
size_t begin = mesh.vertProperties.size();
|
||||
mesh.vertProperties.resize(mesh.vertProperties.size() + MANIFOLD_PROPERTY_MAX);
|
||||
// Add the vertex properties.
|
||||
// Use CSGBrush constants rather than push_back for clarity.
|
||||
double *vert = &mesh.vertProperties[begin];
|
||||
vert[MANIFOLD_PROPERTY_POSITION_X] = face.vertices[i].x;
|
||||
vert[MANIFOLD_PROPERTY_POSITION_Y] = face.vertices[i].y;
|
||||
vert[MANIFOLD_PROPERTY_POSITION_Z] = face.vertices[i].z;
|
||||
vert[MANIFOLD_PROPERTY_UV_X_0] = face.uvs[i].x;
|
||||
vert[MANIFOLD_PROPERTY_UV_Y_0] = face.uvs[i].y;
|
||||
vert[MANIFOLD_PROPERTY_SMOOTH_GROUP] = face.smooth ? 1.0f : 0.0f;
|
||||
vert[MANIFOLD_PROPERTY_INVERT] = face.invert ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// runIndex needs an explicit end value.
|
||||
mesh.runIndex.push_back(mesh.triVerts.size());
|
||||
mesh.tolerance = 2 * FLT_EPSILON;
|
||||
ERR_FAIL_COND_MSG(mesh.vertProperties.size() % mesh.numProp != 0, "Invalid vertex properties size.");
|
||||
mesh.Merge();
|
||||
#ifdef DEV_ENABLED
|
||||
print_verbose(_export_meshgl_as_json(mesh));
|
||||
#endif // DEV_ENABLED
|
||||
r_manifold = manifold::Manifold(mesh);
|
||||
}
|
||||
|
||||
struct ManifoldOperation {
|
||||
manifold::Manifold manifold;
|
||||
manifold::OpType operation;
|
||||
static manifold::OpType convert_csg_op(CSGShape3D::Operation op) {
|
||||
switch (op) {
|
||||
case CSGShape3D::OPERATION_SUBTRACTION:
|
||||
return manifold::OpType::Subtract;
|
||||
case CSGShape3D::OPERATION_INTERSECTION:
|
||||
return manifold::OpType::Intersect;
|
||||
default:
|
||||
return manifold::OpType::Add;
|
||||
}
|
||||
}
|
||||
ManifoldOperation() :
|
||||
operation(manifold::OpType::Add) {}
|
||||
ManifoldOperation(const manifold::Manifold &m, manifold::OpType op) :
|
||||
manifold(m), operation(op) {}
|
||||
};
|
||||
|
||||
CSGBrush *CSGShape3D::_get_brush() {
|
||||
if (!dirty) {
|
||||
return brush;
|
||||
}
|
||||
if (brush) {
|
||||
memdelete(brush);
|
||||
}
|
||||
brush = nullptr;
|
||||
CSGBrush *n = _build_brush();
|
||||
HashMap<int32_t, Ref<Material>> mesh_materials;
|
||||
manifold::Manifold root_manifold;
|
||||
_pack_manifold(n, root_manifold, mesh_materials, this);
|
||||
manifold::OpType current_op = ManifoldOperation::convert_csg_op(get_operation());
|
||||
std::vector<manifold::Manifold> manifolds;
|
||||
manifolds.push_back(root_manifold);
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
CSGShape3D *child = Object::cast_to<CSGShape3D>(get_child(i));
|
||||
if (!child || !child->is_visible()) {
|
||||
continue;
|
||||
}
|
||||
CSGBrush *child_brush = child->_get_brush();
|
||||
if (!child_brush) {
|
||||
continue;
|
||||
}
|
||||
CSGBrush transformed_brush;
|
||||
transformed_brush.copy_from(*child_brush, child->get_transform());
|
||||
manifold::Manifold child_manifold;
|
||||
_pack_manifold(&transformed_brush, child_manifold, mesh_materials, child);
|
||||
manifold::OpType child_operation = ManifoldOperation::convert_csg_op(child->get_operation());
|
||||
if (child_operation != current_op) {
|
||||
manifold::Manifold result = manifold::Manifold::BatchBoolean(manifolds, current_op);
|
||||
manifolds.clear();
|
||||
manifolds.push_back(result);
|
||||
current_op = child_operation;
|
||||
}
|
||||
manifolds.push_back(child_manifold);
|
||||
}
|
||||
if (!manifolds.empty()) {
|
||||
manifold::Manifold manifold_result = manifold::Manifold::BatchBoolean(manifolds, current_op);
|
||||
if (n) {
|
||||
memdelete(n);
|
||||
}
|
||||
n = memnew(CSGBrush);
|
||||
_unpack_manifold(manifold_result, mesh_materials, n);
|
||||
}
|
||||
AABB aabb;
|
||||
if (n && !n->faces.is_empty()) {
|
||||
aabb.position = n->faces[0].vertices[0];
|
||||
for (const CSGBrush::Face &face : n->faces) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
aabb.expand_to(face.vertices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
node_aabb = aabb;
|
||||
brush = n;
|
||||
dirty = false;
|
||||
update_configuration_warnings();
|
||||
return brush;
|
||||
}
|
||||
|
||||
|
|
@ -460,27 +714,31 @@ void CSGShape3D::_update_shape() {
|
|||
_update_collision_faces();
|
||||
}
|
||||
|
||||
void CSGShape3D::_update_collision_faces() {
|
||||
if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
|
||||
CSGBrush *n = _get_brush();
|
||||
ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush.");
|
||||
Vector<Vector3> physics_faces;
|
||||
physics_faces.resize(n->faces.size() * 3);
|
||||
Vector3 *physicsw = physics_faces.ptrw();
|
||||
Vector<Vector3> CSGShape3D::_get_brush_collision_faces() {
|
||||
Vector<Vector3> collision_faces;
|
||||
CSGBrush *n = _get_brush();
|
||||
ERR_FAIL_NULL_V_MSG(n, collision_faces, "Cannot get CSGBrush.");
|
||||
collision_faces.resize(n->faces.size() * 3);
|
||||
Vector3 *collision_faces_ptrw = collision_faces.ptrw();
|
||||
|
||||
for (int i = 0; i < n->faces.size(); i++) {
|
||||
int order[3] = { 0, 1, 2 };
|
||||
for (int i = 0; i < n->faces.size(); i++) {
|
||||
int order[3] = { 0, 1, 2 };
|
||||
|
||||
if (n->faces[i].invert) {
|
||||
SWAP(order[1], order[2]);
|
||||
}
|
||||
|
||||
physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]];
|
||||
physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]];
|
||||
physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]];
|
||||
if (n->faces[i].invert) {
|
||||
SWAP(order[1], order[2]);
|
||||
}
|
||||
|
||||
root_collision_shape->set_faces(physics_faces);
|
||||
collision_faces_ptrw[i * 3 + 0] = n->faces[i].vertices[order[0]];
|
||||
collision_faces_ptrw[i * 3 + 1] = n->faces[i].vertices[order[1]];
|
||||
collision_faces_ptrw[i * 3 + 2] = n->faces[i].vertices[order[2]];
|
||||
}
|
||||
|
||||
return collision_faces;
|
||||
}
|
||||
|
||||
void CSGShape3D::_update_collision_faces() {
|
||||
if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
|
||||
root_collision_shape->set_faces(_get_brush_collision_faces());
|
||||
|
||||
if (_is_debug_collision_shape_visible()) {
|
||||
_update_debug_collision_shape();
|
||||
|
|
@ -488,12 +746,32 @@ void CSGShape3D::_update_collision_faces() {
|
|||
}
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> CSGShape3D::bake_static_mesh() {
|
||||
Ref<ArrayMesh> baked_mesh;
|
||||
if (is_root_shape() && root_mesh.is_valid()) {
|
||||
baked_mesh = root_mesh;
|
||||
}
|
||||
return baked_mesh;
|
||||
}
|
||||
|
||||
Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() {
|
||||
Ref<ConcavePolygonShape3D> baked_collision_shape;
|
||||
if (is_root_shape() && root_collision_shape.is_valid()) {
|
||||
baked_collision_shape.instantiate();
|
||||
baked_collision_shape->set_faces(root_collision_shape->get_faces());
|
||||
} else if (is_root_shape()) {
|
||||
baked_collision_shape.instantiate();
|
||||
baked_collision_shape->set_faces(_get_brush_collision_faces());
|
||||
}
|
||||
return baked_collision_shape;
|
||||
}
|
||||
|
||||
bool CSGShape3D::_is_debug_collision_shape_visible() {
|
||||
return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint());
|
||||
return !Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->is_debugging_collisions_hint();
|
||||
}
|
||||
|
||||
void CSGShape3D::_update_debug_collision_shape() {
|
||||
if (!use_collision || !is_root_shape() || !root_collision_shape.is_valid() || !_is_debug_collision_shape_visible()) {
|
||||
if (!use_collision || !is_root_shape() || root_collision_shape.is_null() || !_is_debug_collision_shape_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -575,16 +853,15 @@ void CSGShape3D::_notification(int p_what) {
|
|||
parent_shape = nullptr;
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_CHILD_ORDER_CHANGED: {
|
||||
_make_dirty();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (!is_root_shape() && last_visible != is_visible()) {
|
||||
// Update this node's parent only if its own visibility has changed, not the visibility of parent nodes
|
||||
parent_shape->_make_dirty();
|
||||
}
|
||||
if (is_visible()) {
|
||||
_update_debug_collision_shape();
|
||||
} else {
|
||||
_clear_debug_collision_shape();
|
||||
}
|
||||
last_visible = is_visible();
|
||||
} break;
|
||||
|
||||
|
|
@ -671,6 +948,26 @@ Array CSGShape3D::get_meshes() const {
|
|||
return Array();
|
||||
}
|
||||
|
||||
PackedStringArray CSGShape3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
const CSGShape3D *current_shape = this;
|
||||
while (current_shape) {
|
||||
if (!current_shape->brush || current_shape->brush->faces.is_empty()) {
|
||||
warnings.push_back(RTR("The CSGShape3D has an empty shape.\nCSGShape3D empty shapes typically occur because the mesh is not manifold.\nA manifold mesh forms a solid object without gaps, holes, or loose edges.\nEach edge must be a member of exactly two faces."));
|
||||
break;
|
||||
}
|
||||
current_shape = current_shape->parent_shape;
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
Ref<TriangleMesh> CSGShape3D::generate_triangle_mesh() const {
|
||||
if (root_mesh.is_valid()) {
|
||||
return root_mesh->generate_triangle_mesh();
|
||||
}
|
||||
return Ref<TriangleMesh>();
|
||||
}
|
||||
|
||||
void CSGShape3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_update_shape"), &CSGShape3D::_update_shape);
|
||||
ClassDB::bind_method(D_METHOD("is_root_shape"), &CSGShape3D::is_root_shape);
|
||||
|
|
@ -678,8 +975,10 @@ void CSGShape3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_operation", "operation"), &CSGShape3D::set_operation);
|
||||
ClassDB::bind_method(D_METHOD("get_operation"), &CSGShape3D::get_operation);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGShape3D::set_snap);
|
||||
ClassDB::bind_method(D_METHOD("get_snap"), &CSGShape3D::get_snap);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_collision", "operation"), &CSGShape3D::set_use_collision);
|
||||
ClassDB::bind_method(D_METHOD("is_using_collision"), &CSGShape3D::is_using_collision);
|
||||
|
|
@ -693,6 +992,8 @@ void CSGShape3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CSGShape3D::set_collision_mask_value);
|
||||
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CSGShape3D::get_collision_mask_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_get_root_collision_instance"), &CSGShape3D::_get_root_collision_instance);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CSGShape3D::set_collision_layer_value);
|
||||
ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CSGShape3D::get_collision_layer_value);
|
||||
|
||||
|
|
@ -704,8 +1005,13 @@ void CSGShape3D::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGShape3D::bake_static_mesh);
|
||||
ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap");
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m", PROPERTY_USAGE_NONE), "set_snap", "get_snap");
|
||||
#endif // DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents");
|
||||
|
||||
ADD_GROUP("Collision", "collision_");
|
||||
|
|
@ -786,7 +1092,7 @@ CSGPrimitive3D::CSGPrimitive3D() {
|
|||
/////////////////////
|
||||
|
||||
CSGBrush *CSGMesh3D::_build_brush() {
|
||||
if (!mesh.is_valid()) {
|
||||
if (mesh.is_null()) {
|
||||
return memnew(CSGBrush);
|
||||
}
|
||||
|
||||
|
|
@ -934,7 +1240,8 @@ CSGBrush *CSGMesh3D::_build_brush() {
|
|||
|
||||
void CSGMesh3D::_mesh_changed() {
|
||||
_make_dirty();
|
||||
update_gizmos();
|
||||
|
||||
callable_mp((Node3D *)this, &Node3D::update_gizmos).call_deferred();
|
||||
}
|
||||
|
||||
void CSGMesh3D::set_material(const Ref<Material> &p_material) {
|
||||
|
|
@ -956,7 +1263,8 @@ void CSGMesh3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGMesh3D::set_material);
|
||||
ClassDB::bind_method(D_METHOD("get_material"), &CSGMesh3D::get_material);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
|
||||
// Hide PrimitiveMeshes that are always non-manifold and therefore can't be used as CSG meshes.
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh,-PlaneMesh,-PointMesh,-QuadMesh,-RibbonTrailMesh"), "set_mesh", "get_mesh");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material");
|
||||
}
|
||||
|
||||
|
|
@ -1018,14 +1326,22 @@ CSGBrush *CSGSphere3D::_build_brush() {
|
|||
const double longitude_step = Math_TAU / radial_segments;
|
||||
int face = 0;
|
||||
for (int i = 0; i < rings; i++) {
|
||||
double latitude0 = latitude_step * i + Math_TAU / 4;
|
||||
double cos0 = Math::cos(latitude0);
|
||||
double sin0 = Math::sin(latitude0);
|
||||
double cos0 = 0;
|
||||
double sin0 = 1;
|
||||
if (i > 0) {
|
||||
double latitude0 = latitude_step * i + Math_TAU / 4;
|
||||
cos0 = Math::cos(latitude0);
|
||||
sin0 = Math::sin(latitude0);
|
||||
}
|
||||
double v0 = double(i) / rings;
|
||||
|
||||
double latitude1 = latitude_step * (i + 1) + Math_TAU / 4;
|
||||
double cos1 = Math::cos(latitude1);
|
||||
double sin1 = Math::sin(latitude1);
|
||||
double cos1 = 0;
|
||||
double sin1 = -1;
|
||||
if (i < rings - 1) {
|
||||
double latitude1 = latitude_step * (i + 1) + Math_TAU / 4;
|
||||
cos1 = Math::cos(latitude1);
|
||||
sin1 = Math::sin(latitude1);
|
||||
}
|
||||
double v1 = double(i + 1) / rings;
|
||||
|
||||
for (int j = 0; j < radial_segments; j++) {
|
||||
|
|
@ -1930,24 +2246,35 @@ CSGBrush *CSGPolygon3D::_build_brush() {
|
|||
base_xform = path->get_global_transform();
|
||||
}
|
||||
|
||||
Vector3 current_point = curve->sample_baked(0);
|
||||
Vector3 next_point = curve->sample_baked(extrusion_step);
|
||||
Vector3 current_point;
|
||||
Vector3 current_up = Vector3(0, 1, 0);
|
||||
Vector3 direction = next_point - current_point;
|
||||
|
||||
if (path_joined) {
|
||||
Vector3 last_point = curve->sample_baked(curve->get_baked_length());
|
||||
direction = next_point - last_point;
|
||||
}
|
||||
Vector3 direction;
|
||||
|
||||
switch (path_rotation) {
|
||||
case PATH_ROTATION_POLYGON:
|
||||
current_point = curve->sample_baked(0);
|
||||
direction = Vector3(0, 0, -1);
|
||||
break;
|
||||
case PATH_ROTATION_PATH:
|
||||
break;
|
||||
case PATH_ROTATION_PATH_FOLLOW:
|
||||
current_up = curve->sample_baked_up_vector(0, true);
|
||||
if (!path_rotation_accurate) {
|
||||
current_point = curve->sample_baked(0);
|
||||
Vector3 next_point = curve->sample_baked(extrusion_step);
|
||||
direction = next_point - current_point;
|
||||
|
||||
if (path_joined) {
|
||||
Vector3 last_point = curve->sample_baked(curve->get_baked_length());
|
||||
direction = next_point - last_point;
|
||||
}
|
||||
} else {
|
||||
Transform3D current_sample_xform = curve->sample_baked_with_rotation(0);
|
||||
current_point = current_sample_xform.get_origin();
|
||||
direction = current_sample_xform.get_basis().xform(Vector3(0, 0, -1));
|
||||
}
|
||||
|
||||
if (path_rotation == PATH_ROTATION_PATH_FOLLOW) {
|
||||
current_up = curve->sample_baked_up_vector(0, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1994,37 +2321,35 @@ CSGBrush *CSGPolygon3D::_build_brush() {
|
|||
current_xform.translate_local(Vector3(0, 0, -depth));
|
||||
} break;
|
||||
case MODE_SPIN: {
|
||||
current_xform.rotate(Vector3(0, 1, 0), spin_step);
|
||||
if (end_count == 0 && x0 == extrusions - 1) {
|
||||
current_xform = base_xform;
|
||||
} else {
|
||||
current_xform.rotate(Vector3(0, 1, 0), spin_step);
|
||||
}
|
||||
} break;
|
||||
case MODE_PATH: {
|
||||
double previous_offset = x0 * extrusion_step;
|
||||
double current_offset = (x0 + 1) * extrusion_step;
|
||||
double next_offset = (x0 + 2) * extrusion_step;
|
||||
if (x0 == extrusions - 1) {
|
||||
if (path_joined) {
|
||||
current_offset = 0;
|
||||
next_offset = extrusion_step;
|
||||
} else {
|
||||
next_offset = current_offset;
|
||||
}
|
||||
if (path_joined && x0 == extrusions - 1) {
|
||||
current_offset = 0;
|
||||
}
|
||||
|
||||
Vector3 previous_point = curve->sample_baked(previous_offset);
|
||||
Vector3 current_point = curve->sample_baked(current_offset);
|
||||
Vector3 next_point = curve->sample_baked(next_offset);
|
||||
Transform3D current_sample_xform = curve->sample_baked_with_rotation(current_offset);
|
||||
Vector3 current_point = current_sample_xform.get_origin();
|
||||
Vector3 current_up = Vector3(0, 1, 0);
|
||||
Vector3 direction = next_point - previous_point;
|
||||
Vector3 current_dir = (current_point - previous_point).normalized();
|
||||
Vector3 current_extrusion_dir = (current_point - previous_point).normalized();
|
||||
Vector3 direction;
|
||||
|
||||
// If the angles are similar, remove the previous face and replace it with this one.
|
||||
if (path_simplify_angle > 0.0 && x0 > 0 && previous_simplify_dir.dot(current_dir) > angle_simplify_dot) {
|
||||
if (path_simplify_angle > 0.0 && x0 > 0 && previous_simplify_dir.dot(current_extrusion_dir) > angle_simplify_dot) {
|
||||
faces_combined += 1;
|
||||
previous_xform = previous_previous_xform;
|
||||
face -= extrusion_face_count;
|
||||
faces_removed += extrusion_face_count;
|
||||
} else {
|
||||
faces_combined = 0;
|
||||
previous_simplify_dir = current_dir;
|
||||
previous_simplify_dir = current_extrusion_dir;
|
||||
}
|
||||
|
||||
switch (path_rotation) {
|
||||
|
|
@ -2032,9 +2357,21 @@ CSGBrush *CSGPolygon3D::_build_brush() {
|
|||
direction = Vector3(0, 0, -1);
|
||||
break;
|
||||
case PATH_ROTATION_PATH:
|
||||
break;
|
||||
case PATH_ROTATION_PATH_FOLLOW:
|
||||
current_up = curve->sample_baked_up_vector(current_offset, true);
|
||||
if (!path_rotation_accurate) {
|
||||
double next_offset = (x0 + 2) * extrusion_step;
|
||||
if (x0 == extrusions - 1) {
|
||||
next_offset = path_joined ? extrusion_step : current_offset;
|
||||
}
|
||||
Vector3 next_point = curve->sample_baked(next_offset);
|
||||
direction = next_point - previous_point;
|
||||
} else {
|
||||
direction = current_sample_xform.get_basis().xform(Vector3(0, 0, -1));
|
||||
}
|
||||
|
||||
if (path_rotation == PATH_ROTATION_PATH_FOLLOW) {
|
||||
current_up = curve->sample_baked_up_vector(current_offset, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2204,6 +2541,9 @@ void CSGPolygon3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_path_rotation", "path_rotation"), &CSGPolygon3D::set_path_rotation);
|
||||
ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon3D::get_path_rotation);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_path_rotation_accurate", "enable"), &CSGPolygon3D::set_path_rotation_accurate);
|
||||
ClassDB::bind_method(D_METHOD("get_path_rotation_accurate"), &CSGPolygon3D::get_path_rotation_accurate);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGPolygon3D::set_path_local);
|
||||
ClassDB::bind_method(D_METHOD("is_path_local"), &CSGPolygon3D::is_path_local);
|
||||
|
||||
|
|
@ -2235,6 +2575,7 @@ void CSGPolygon3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.01,1.0,0.01,exp,or_greater"), "set_path_interval", "get_path_interval");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_simplify_angle", PROPERTY_HINT_RANGE, "0.0,180.0,0.1"), "set_path_simplify_angle", "get_path_simplify_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_rotation_accurate"), "set_path_rotation_accurate", "get_path_rotation_accurate");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_local"), "set_path_local", "is_path_local");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_u_distance", PROPERTY_HINT_RANGE, "0.0,10.0,0.01,or_greater,suffix:m"), "set_path_u_distance", "get_path_u_distance");
|
||||
|
|
@ -2377,6 +2718,16 @@ CSGPolygon3D::PathRotation CSGPolygon3D::get_path_rotation() const {
|
|||
return path_rotation;
|
||||
}
|
||||
|
||||
void CSGPolygon3D::set_path_rotation_accurate(bool p_enabled) {
|
||||
path_rotation_accurate = p_enabled;
|
||||
_make_dirty();
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
bool CSGPolygon3D::get_path_rotation_accurate() const {
|
||||
return path_rotation_accurate;
|
||||
}
|
||||
|
||||
void CSGPolygon3D::set_path_local(bool p_enable) {
|
||||
path_local = p_enable;
|
||||
_make_dirty();
|
||||
|
|
@ -2438,6 +2789,7 @@ CSGPolygon3D::CSGPolygon3D() {
|
|||
path_interval = 1.0;
|
||||
path_simplify_angle = 0.0;
|
||||
path_rotation = PATH_ROTATION_PATH_FOLLOW;
|
||||
path_rotation_accurate = false;
|
||||
path_local = false;
|
||||
path_continuous_u = true;
|
||||
path_u_distance = 1.0;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@
|
|||
|
||||
#include "thirdparty/misc/mikktspace.h"
|
||||
|
||||
class NavigationMesh;
|
||||
class NavigationMeshSourceGeometryData3D;
|
||||
|
||||
class CSGShape3D : public GeometryInstance3D {
|
||||
GDCLASS(CSGShape3D, GeometryInstance3D);
|
||||
|
||||
|
|
@ -113,11 +116,13 @@ private:
|
|||
void _update_debug_collision_shape();
|
||||
void _clear_debug_collision_shape();
|
||||
void _on_transform_changed();
|
||||
Vector<Vector3> _get_brush_collision_faces();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual CSGBrush *_build_brush() = 0;
|
||||
void _make_dirty(bool p_parent_removing = false);
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
|
|
@ -151,16 +156,34 @@ public:
|
|||
void set_collision_mask_value(int p_layer_number, bool p_value);
|
||||
bool get_collision_mask_value(int p_layer_number) const;
|
||||
|
||||
RID _get_root_collision_instance() const;
|
||||
|
||||
void set_collision_priority(real_t p_priority);
|
||||
real_t get_collision_priority() const;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void set_snap(float p_snap);
|
||||
float get_snap() const;
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void set_calculate_tangents(bool p_calculate_tangents);
|
||||
bool is_calculating_tangents() const;
|
||||
|
||||
bool is_root_shape() const;
|
||||
|
||||
Ref<ArrayMesh> bake_static_mesh();
|
||||
Ref<ConcavePolygonShape3D> bake_collision_shape();
|
||||
|
||||
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
|
||||
|
||||
private:
|
||||
static Callable _navmesh_source_geometry_parsing_callback;
|
||||
static RID _navmesh_source_geometry_parser;
|
||||
|
||||
public:
|
||||
static void navmesh_parse_init();
|
||||
static void navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
|
||||
|
||||
CSGShape3D();
|
||||
~CSGShape3D();
|
||||
};
|
||||
|
|
@ -380,6 +403,7 @@ private:
|
|||
float path_interval;
|
||||
float path_simplify_angle;
|
||||
PathRotation path_rotation;
|
||||
bool path_rotation_accurate;
|
||||
bool path_local;
|
||||
|
||||
Path3D *path = nullptr;
|
||||
|
|
@ -431,6 +455,9 @@ public:
|
|||
void set_path_rotation(PathRotation p_rotation);
|
||||
PathRotation get_path_rotation() const;
|
||||
|
||||
void set_path_rotation_accurate(bool p_enable);
|
||||
bool get_path_rotation_accurate() const;
|
||||
|
||||
void set_path_local(bool p_enable);
|
||||
bool is_path_local() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
A CSG Mesh shape that uses a mesh resource.
|
||||
</brief_description>
|
||||
<description>
|
||||
This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes.
|
||||
This CSG node allows you to use any mesh resource as a CSG shape, provided it is [i]manifold[/i]. A manifold shape is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
</member>
|
||||
<member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh">
|
||||
The [Mesh] resource to use as a CSG shape.
|
||||
[b]Note:[/b] Some [Mesh] types such as [PlaneMesh], [PointMesh], [QuadMesh], and [RibbonTrailMesh] are excluded from the type hint for this property, as these primitives are non-[i]manifold[/i] and thus not compatible with the CSG algorithm.
|
||||
[b]Note:[/b] When using an [ArrayMesh], all vertex attributes except [constant Mesh.ARRAY_VERTEX], [constant Mesh.ARRAY_NORMAL] and [constant Mesh.ARRAY_TEX_UV] are left unused. Only [constant Mesh.ARRAY_VERTEX] and [constant Mesh.ARRAY_TEX_UV] will be passed to the GPU.
|
||||
[constant Mesh.ARRAY_NORMAL] is only used to determine which faces require the use of flat shading. By default, CSGMesh will ignore the mesh's vertex normals, recalculate them for each vertex and use a smooth shader. If a flat shader is required for a face, ensure that all vertex normals of the face are approximately equal.
|
||||
</member>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@
|
|||
<member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon3D.PathRotation">
|
||||
When [member mode] is [constant MODE_PATH], the path rotation method used to rotate the [member polygon] as it is extruded.
|
||||
</member>
|
||||
<member name="path_rotation_accurate" type="bool" setter="set_path_rotation_accurate" getter="get_path_rotation_accurate">
|
||||
When [member mode] is [constant MODE_PATH], if [code]true[/code] the polygon will be rotated according to the proper tangent of the path at the sampled points. If [code]false[/code] an approximation is used, which decreases in accuracy as the number of subdivisions decreases.
|
||||
</member>
|
||||
<member name="path_simplify_angle" type="float" setter="set_path_simplify_angle" getter="get_path_simplify_angle">
|
||||
When [member mode] is [constant MODE_PATH], extrusions that are less than this angle, will be merged together to reduce polygon count.
|
||||
</member>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,29 @@
|
|||
</brief_description>
|
||||
<description>
|
||||
This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
[b]Performance:[/b] CSG nodes are only intended for prototyping as they have a significant CPU performance cost.
|
||||
Consider baking final CSG operation results into static geometry that replaces the CSG nodes.
|
||||
Individual CSG root node results can be baked to nodes with static resources with the editor menu that appears when a CSG root node is selected.
|
||||
Individual CSG root nodes can also be baked to static resources with scripts by calling [method bake_static_mesh] for the visual mesh or [method bake_collision_shape] for the physics collision.
|
||||
Entire scenes of CSG nodes can be baked to static geometry and exported with the editor gltf scene exporter.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="bake_collision_shape">
|
||||
<return type="ConcavePolygonShape3D" />
|
||||
<description>
|
||||
Returns a baked physics [ConcavePolygonShape3D] of this node's CSG operation result. Returns an empty shape if the node is not a CSG root node or has no valid geometry.
|
||||
[b]Performance:[/b] If the CSG operation results in a very detailed geometry with many faces physics performance will be very slow. Concave shapes should in general only be used for static level geometry and not with dynamic objects that are moving.
|
||||
</description>
|
||||
</method>
|
||||
<method name="bake_static_mesh">
|
||||
<return type="ArrayMesh" />
|
||||
<description>
|
||||
Returns a baked static [ArrayMesh] of this node's CSG operation result. Materials from involved CSG nodes are added as extra mesh surfaces. Returns an empty mesh if the node is not a CSG root node or has no valid geometry.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_collision_layer_value" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="layer_number" type="int" />
|
||||
|
|
@ -72,8 +89,8 @@
|
|||
<member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape3D.Operation" default="0">
|
||||
The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent.
|
||||
</member>
|
||||
<member name="snap" type="float" setter="set_snap" getter="get_snap" default="0.001">
|
||||
Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust. The top-level CSG shape's snap value is used for the entire CSG tree.
|
||||
<member name="snap" type="float" setter="set_snap" getter="get_snap" deprecated="The CSG library no longer uses snapping.">
|
||||
This property does nothing.
|
||||
</member>
|
||||
<member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision" default="false">
|
||||
Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority].
|
||||
|
|
|
|||
|
|
@ -38,13 +38,141 @@
|
|||
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
|
||||
#include "editor/plugins/node_3d_editor_plugin.h"
|
||||
#include "scene/3d/camera_3d.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/physics/collision_shape_3d.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
|
||||
void CSGShapeEditor::_node_removed(Node *p_node) {
|
||||
if (p_node == node) {
|
||||
node = nullptr;
|
||||
options->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) {
|
||||
node = p_csg_shape;
|
||||
if (node) {
|
||||
options->show();
|
||||
} else {
|
||||
options->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShapeEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
options->set_button_icon(get_editor_theme_icon(SNAME("CSGCombiner3D")));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShapeEditor::_menu_option(int p_option) {
|
||||
Array meshes = node->get_meshes();
|
||||
if (meshes.is_empty()) {
|
||||
err_dialog->set_text(TTR("CSG operation returned an empty array."));
|
||||
err_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (p_option) {
|
||||
case MENU_OPTION_BAKE_MESH_INSTANCE: {
|
||||
_create_baked_mesh_instance();
|
||||
} break;
|
||||
case MENU_OPTION_BAKE_COLLISION_SHAPE: {
|
||||
_create_baked_collision_shape();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShapeEditor::_create_baked_mesh_instance() {
|
||||
if (node == get_tree()->get_edited_scene_root()) {
|
||||
err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node."));
|
||||
err_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> mesh = node->bake_static_mesh();
|
||||
if (mesh.is_null()) {
|
||||
err_dialog->set_text(TTR("CSG operation returned an empty mesh."));
|
||||
err_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
ur->create_action(TTR("Create baked CSGShape3D Mesh Instance"));
|
||||
|
||||
Node *owner = get_tree()->get_edited_scene_root();
|
||||
|
||||
MeshInstance3D *mi = memnew(MeshInstance3D);
|
||||
mi->set_mesh(mesh);
|
||||
mi->set_name("CSGBakedMeshInstance3D");
|
||||
mi->set_transform(node->get_transform());
|
||||
ur->add_do_method(node, "add_sibling", mi, true);
|
||||
ur->add_do_method(mi, "set_owner", owner);
|
||||
ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi);
|
||||
|
||||
ur->add_do_reference(mi);
|
||||
ur->add_undo_method(node->get_parent(), "remove_child", mi);
|
||||
|
||||
ur->commit_action();
|
||||
}
|
||||
|
||||
void CSGShapeEditor::_create_baked_collision_shape() {
|
||||
if (node == get_tree()->get_edited_scene_root()) {
|
||||
err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node."));
|
||||
err_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<Shape3D> shape = node->bake_collision_shape();
|
||||
if (shape.is_null()) {
|
||||
err_dialog->set_text(TTR("CSG operation returned an empty shape."));
|
||||
err_dialog->popup_centered();
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
ur->create_action(TTR("Create baked CSGShape3D Collision Shape"));
|
||||
|
||||
Node *owner = get_tree()->get_edited_scene_root();
|
||||
|
||||
CollisionShape3D *cshape = memnew(CollisionShape3D);
|
||||
cshape->set_shape(shape);
|
||||
cshape->set_name("CSGBakedCollisionShape3D");
|
||||
cshape->set_transform(node->get_transform());
|
||||
ur->add_do_method(node, "add_sibling", cshape, true);
|
||||
ur->add_do_method(cshape, "set_owner", owner);
|
||||
ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape);
|
||||
|
||||
ur->add_do_reference(cshape);
|
||||
ur->add_undo_method(node->get_parent(), "remove_child", cshape);
|
||||
|
||||
ur->commit_action();
|
||||
}
|
||||
|
||||
CSGShapeEditor::CSGShapeEditor() {
|
||||
options = memnew(MenuButton);
|
||||
options->hide();
|
||||
options->set_text(TTR("CSG"));
|
||||
options->set_switch_on_hover(true);
|
||||
Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
|
||||
|
||||
options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE);
|
||||
options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE);
|
||||
|
||||
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGShapeEditor::_menu_option));
|
||||
|
||||
err_dialog = memnew(AcceptDialog);
|
||||
add_child(err_dialog);
|
||||
}
|
||||
|
||||
///////////
|
||||
|
||||
CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
|
||||
helper.instantiate();
|
||||
|
||||
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15));
|
||||
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg");
|
||||
create_material("shape_union_material", gizmo_color);
|
||||
create_material("shape_union_solid_material", gizmo_color);
|
||||
gizmo_color.invert();
|
||||
|
|
@ -149,24 +277,13 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i
|
|||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
|
||||
Vector3 axis;
|
||||
axis[p_id == 0 ? 0 : 1] = 1.0;
|
||||
Vector3 ra, rb;
|
||||
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
|
||||
float d = axis.dot(ra);
|
||||
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
||||
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
||||
}
|
||||
|
||||
if (d < 0.001) {
|
||||
d = 0.001;
|
||||
}
|
||||
|
||||
if (p_id == 0) {
|
||||
s->set_radius(d);
|
||||
} else if (p_id == 1) {
|
||||
s->set_height(d * 2.0);
|
||||
}
|
||||
real_t height = s->get_height();
|
||||
real_t radius = s->get_radius();
|
||||
Vector3 position;
|
||||
helper->cylinder_set_handle(sg, p_id, height, radius, position);
|
||||
s->set_height(height);
|
||||
s->set_radius(radius);
|
||||
s->set_global_position(position);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGTorus3D>(cs)) {
|
||||
|
|
@ -211,32 +328,11 @@ void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int
|
|||
}
|
||||
|
||||
if (Object::cast_to<CSGBox3D>(cs)) {
|
||||
helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs);
|
||||
helper->box_commit_handle(TTR("Change CSG Box Size"), p_cancel, cs);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
if (p_cancel) {
|
||||
if (p_id == 0) {
|
||||
s->set_radius(p_restore);
|
||||
} else {
|
||||
s->set_height(p_restore);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
if (p_id == 0) {
|
||||
ur->create_action(TTR("Change Cylinder Radius"));
|
||||
ur->add_do_method(s, "set_radius", s->get_radius());
|
||||
ur->add_undo_method(s, "set_radius", p_restore);
|
||||
} else {
|
||||
ur->create_action(TTR("Change Cylinder Height"));
|
||||
ur->add_do_method(s, "set_height", s->get_height());
|
||||
ur->add_undo_method(s, "set_height", p_restore);
|
||||
}
|
||||
|
||||
ur->commit_action();
|
||||
helper->cylinder_commit_handle(p_id, TTR("Change CSG Cylinder Radius"), TTR("Change CSG Cylinder Height"), p_cancel, cs);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGTorus3D>(cs)) {
|
||||
|
|
@ -377,9 +473,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
|
||||
Vector<Vector3> handles;
|
||||
handles.push_back(Vector3(s->get_radius(), 0, 0));
|
||||
handles.push_back(Vector3(0, s->get_height() * 0.5, 0));
|
||||
Vector<Vector3> handles = helper->cylinder_get_handles(s->get_height(), s->get_radius());
|
||||
p_gizmo->add_handles(handles, handles_material);
|
||||
}
|
||||
|
||||
|
|
@ -393,9 +487,26 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|||
}
|
||||
}
|
||||
|
||||
void EditorPluginCSG::edit(Object *p_object) {
|
||||
CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
|
||||
if (csg_shape && csg_shape->is_root_shape()) {
|
||||
csg_shape_editor->edit(csg_shape);
|
||||
} else {
|
||||
csg_shape_editor->edit(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorPluginCSG::handles(Object *p_object) const {
|
||||
CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
|
||||
return csg_shape && csg_shape->is_root_shape();
|
||||
}
|
||||
|
||||
EditorPluginCSG::EditorPluginCSG() {
|
||||
Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin));
|
||||
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
|
||||
|
||||
csg_shape_editor = memnew(CSGShapeEditor);
|
||||
EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor);
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
|
|
|||
|
|
@ -37,8 +37,11 @@
|
|||
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "editor/plugins/node_3d_editor_gizmos.h"
|
||||
#include "scene/gui/control.h"
|
||||
|
||||
class AcceptDialog;
|
||||
class Gizmo3DHelper;
|
||||
class MenuButton;
|
||||
|
||||
class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
|
||||
GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
|
||||
|
|
@ -62,10 +65,43 @@ public:
|
|||
~CSGShape3DGizmoPlugin();
|
||||
};
|
||||
|
||||
class CSGShapeEditor : public Control {
|
||||
GDCLASS(CSGShapeEditor, Control);
|
||||
|
||||
enum Menu {
|
||||
MENU_OPTION_BAKE_MESH_INSTANCE,
|
||||
MENU_OPTION_BAKE_COLLISION_SHAPE,
|
||||
};
|
||||
|
||||
CSGShape3D *node = nullptr;
|
||||
MenuButton *options = nullptr;
|
||||
AcceptDialog *err_dialog = nullptr;
|
||||
|
||||
void _menu_option(int p_option);
|
||||
|
||||
void _create_baked_mesh_instance();
|
||||
void _create_baked_collision_shape();
|
||||
|
||||
protected:
|
||||
void _node_removed(Node *p_node);
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void edit(CSGShape3D *p_csg_shape);
|
||||
CSGShapeEditor();
|
||||
};
|
||||
|
||||
class EditorPluginCSG : public EditorPlugin {
|
||||
GDCLASS(EditorPluginCSG, EditorPlugin);
|
||||
|
||||
CSGShapeEditor *csg_shape_editor = nullptr;
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "CSGShape3D"; }
|
||||
virtual void edit(Object *p_object) override;
|
||||
virtual bool handles(Object *p_object) const override;
|
||||
|
||||
EditorPluginCSG();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@
|
|||
|
||||
#include "register_types.h"
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
#include "csg_shape.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
|
@ -49,6 +47,7 @@ void initialize_csg_module(ModuleInitializationLevel p_level) {
|
|||
GDREGISTER_CLASS(CSGTorus3D);
|
||||
GDREGISTER_CLASS(CSGPolygon3D);
|
||||
GDREGISTER_CLASS(CSGCombiner3D);
|
||||
CSGShape3D::navmesh_parse_init();
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
|
||||
|
|
@ -62,5 +61,3 @@ void uninitialize_csg_module(ModuleInitializationLevel p_level) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _3D_DISABLED
|
||||
|
|
|
|||
114
engine/modules/csg/tests/test_csg.h
Normal file
114
engine/modules/csg/tests/test_csg.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**************************************************************************/
|
||||
/* test_csg.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_CSG_H
|
||||
#define TEST_CSG_H
|
||||
|
||||
#include "../csg.h"
|
||||
#include "../csg_shape.h"
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestCSG {
|
||||
|
||||
TEST_CASE("[SceneTree][CSG] CSGPolygon3D") {
|
||||
SUBCASE("[SceneTree][CSG] CSGPolygon3D: using accurate path tangent for polygon rotation") {
|
||||
const float polygon_radius = 10.0f;
|
||||
|
||||
const Vector3 expected_min_bounds = Vector3(-polygon_radius, -polygon_radius, 0);
|
||||
const Vector3 expected_max_bounds = Vector3(100 + polygon_radius, polygon_radius, 100);
|
||||
const AABB expected_aabb = AABB(expected_min_bounds, expected_max_bounds - expected_min_bounds);
|
||||
|
||||
Ref<Curve3D> curve;
|
||||
curve.instantiate();
|
||||
curve->add_point(
|
||||
// p_position
|
||||
Vector3(0, 0, 0),
|
||||
// p_in
|
||||
Vector3(),
|
||||
// p_out
|
||||
Vector3(0, 0, 60));
|
||||
curve->add_point(
|
||||
// p_position
|
||||
Vector3(100, 0, 100),
|
||||
// p_in
|
||||
Vector3(0, 0, -60),
|
||||
// p_out
|
||||
Vector3());
|
||||
|
||||
Path3D *path = memnew(Path3D);
|
||||
path->set_curve(curve);
|
||||
|
||||
CSGPolygon3D *csg_polygon_3d = memnew(CSGPolygon3D);
|
||||
SceneTree::get_singleton()->get_root()->add_child(csg_polygon_3d);
|
||||
|
||||
csg_polygon_3d->add_child(path);
|
||||
csg_polygon_3d->set_path_node(csg_polygon_3d->get_path_to(path));
|
||||
csg_polygon_3d->set_mode(CSGPolygon3D::Mode::MODE_PATH);
|
||||
|
||||
PackedVector2Array polygon;
|
||||
polygon.append(Vector2(-polygon_radius, 0));
|
||||
polygon.append(Vector2(0, polygon_radius));
|
||||
polygon.append(Vector2(polygon_radius, 0));
|
||||
polygon.append(Vector2(0, -polygon_radius));
|
||||
csg_polygon_3d->set_polygon(polygon);
|
||||
|
||||
csg_polygon_3d->set_path_rotation(CSGPolygon3D::PathRotation::PATH_ROTATION_PATH);
|
||||
csg_polygon_3d->set_path_rotation_accurate(true);
|
||||
|
||||
// Minimize the number of extrusions.
|
||||
// This decreases the number of samples taken from the curve.
|
||||
// Having fewer samples increases the inaccuracy of the line between samples as an approximation of the tangent of the curve.
|
||||
// With correct polygon orientation, the bounding box for the given curve should be independent of the number of extrusions.
|
||||
csg_polygon_3d->set_path_interval_type(CSGPolygon3D::PathIntervalType::PATH_INTERVAL_DISTANCE);
|
||||
csg_polygon_3d->set_path_interval(1000.0f);
|
||||
|
||||
// Call get_brush_faces to force the bounding box to update.
|
||||
csg_polygon_3d->get_brush_faces();
|
||||
|
||||
CHECK(csg_polygon_3d->get_aabb().is_equal_approx(expected_aabb));
|
||||
|
||||
// Perform the bounding box check again with a greater number of extrusions.
|
||||
csg_polygon_3d->set_path_interval(1.0f);
|
||||
csg_polygon_3d->get_brush_faces();
|
||||
|
||||
CHECK(csg_polygon_3d->get_aabb().is_equal_approx(expected_aabb));
|
||||
|
||||
csg_polygon_3d->remove_child(path);
|
||||
SceneTree::get_singleton()->get_root()->remove_child(csg_polygon_3d);
|
||||
|
||||
memdelete(csg_polygon_3d);
|
||||
memdelete(path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TestCSG
|
||||
|
||||
#endif // TEST_CSG_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue