From c7925972b0dd8fbaf63aaebf8f996e0fb9b00989 Mon Sep 17 00:00:00 2001 From: trollodel <33117082+trollodel@users.noreply.github.com> Date: Sat, 18 Sep 2021 09:26:34 +0200 Subject: [PATCH] Allow to use CSG without nodes Add CSG shape resources. Add CSGTool for CSG operations. Make CSG nodes using CSG resources and CSGTool. --- doc/classes/CSGTool.xml | 48 + modules/csg/config.py | 7 + modules/csg/csg_gizmos.cpp | 18 +- modules/csg/csg_shape.cpp | 1600 ++------------- modules/csg/csg_shape.h | 184 +- modules/csg/csg_tool.cpp | 1720 +++++++++++++++++ modules/csg/csg_tool.h | 322 +++ modules/csg/doc_classes/CSGBox3D.xml | 3 - modules/csg/doc_classes/CSGBoxShape3D.xml | 19 + modules/csg/doc_classes/CSGCylinder3D.xml | 6 - .../csg/doc_classes/CSGCylinderShape3D.xml | 28 + modules/csg/doc_classes/CSGMesh3D.xml | 3 - modules/csg/doc_classes/CSGMeshShape3D.xml | 19 + modules/csg/doc_classes/CSGPolygon3D.xml | 8 +- modules/csg/doc_classes/CSGPolygonShape3D.xml | 65 + modules/csg/doc_classes/CSGPrimitive3D.xml | 4 + .../csg/doc_classes/CSGPrimitiveShape3D.xml | 23 + modules/csg/doc_classes/CSGSphere3D.xml | 6 - modules/csg/doc_classes/CSGSphereShape3D.xml | 25 + modules/csg/doc_classes/CSGTorus3D.xml | 6 - modules/csg/doc_classes/CSGTorusShape3D.xml | 28 + modules/csg/icons/CSGBoxShape3D.svg | 1 + modules/csg/icons/CSGCapsuleShape3D.svg | 1 + modules/csg/icons/CSGCylinderShape3D.svg | 1 + modules/csg/icons/CSGMeshShape3D.svg | 1 + modules/csg/icons/CSGPolygonShape3D.svg | 1 + modules/csg/icons/CSGSphereShape3D.svg | 1 + modules/csg/icons/CSGTorusShape3D.svg | 1 + modules/csg/register_types.cpp | 9 + 29 files changed, 2540 insertions(+), 1618 deletions(-) create mode 100644 doc/classes/CSGTool.xml create mode 100644 modules/csg/csg_tool.cpp create mode 100644 modules/csg/csg_tool.h create mode 100644 modules/csg/doc_classes/CSGBoxShape3D.xml create mode 100644 modules/csg/doc_classes/CSGCylinderShape3D.xml create mode 100644 modules/csg/doc_classes/CSGMeshShape3D.xml create mode 100644 modules/csg/doc_classes/CSGPolygonShape3D.xml create mode 100644 modules/csg/doc_classes/CSGPrimitiveShape3D.xml create mode 100644 modules/csg/doc_classes/CSGSphereShape3D.xml create mode 100644 modules/csg/doc_classes/CSGTorusShape3D.xml create mode 100644 modules/csg/icons/CSGBoxShape3D.svg create mode 100644 modules/csg/icons/CSGCapsuleShape3D.svg create mode 100644 modules/csg/icons/CSGCylinderShape3D.svg create mode 100644 modules/csg/icons/CSGMeshShape3D.svg create mode 100644 modules/csg/icons/CSGPolygonShape3D.svg create mode 100644 modules/csg/icons/CSGSphereShape3D.svg create mode 100644 modules/csg/icons/CSGTorusShape3D.svg diff --git a/doc/classes/CSGTool.xml b/doc/classes/CSGTool.xml new file mode 100644 index 000000000000..c809ff99128d --- /dev/null +++ b/doc/classes/CSGTool.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Geometry of both primitives is merged, intersecting geometry is removed. + + + Only intersecting geometry remains, the rest is removed. + + + The second shape is subtracted from the first, leaving a dent with its shape. + + + diff --git a/modules/csg/config.py b/modules/csg/config.py index 3991b846f96f..1f74b6bbb54c 100644 --- a/modules/csg/config.py +++ b/modules/csg/config.py @@ -17,6 +17,13 @@ def get_doc_classes(): "CSGShape3D", "CSGSphere3D", "CSGTorus3D", + "CSGBoxShape3D", + "CSGCylinderShape3D", + "CSGMeshShape3D", + "CSGPolygonShape3D", + "CSGPrimitiveShape3D", + "CSGSphereShape3D", + "CSGTorusShape3D" ] diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp index 2f8b354bb717..22bea8da3a18 100644 --- a/modules/csg/csg_gizmos.cpp +++ b/modules/csg/csg_gizmos.cpp @@ -324,16 +324,16 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } } - + CSGTool::Operation op = static_cast(cs->get_operation()); Ref material; - switch (cs->get_operation()) { - case CSGShape3D::OPERATION_UNION: + switch (op) { + case CSGTool::OPERATION_UNION: material = get_material("shape_union_material", p_gizmo); break; - case CSGShape3D::OPERATION_INTERSECTION: + case CSGTool::OPERATION_INTERSECTION: material = get_material("shape_intersection_material", p_gizmo); break; - case CSGShape3D::OPERATION_SUBTRACTION: + case CSGTool::OPERATION_SUBTRACTION: material = get_material("shape_subtraction_material", p_gizmo); break; } @@ -352,14 +352,14 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); Ref solid_material; - switch (cs->get_operation()) { - case CSGShape3D::OPERATION_UNION: + switch (op) { + case CSGTool::OPERATION_UNION: solid_material = get_material("shape_union_solid_material", p_gizmo); break; - case CSGShape3D::OPERATION_INTERSECTION: + case CSGTool::OPERATION_INTERSECTION: solid_material = get_material("shape_intersection_solid_material", p_gizmo); break; - case CSGShape3D::OPERATION_SUBTRACTION: + case CSGTool::OPERATION_SUBTRACTION: solid_material = get_material("shape_subtraction_solid_material", p_gizmo); break; } diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index bf11cc7f68ae..d75f8ca07e42 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -150,14 +150,12 @@ void CSGShape3D::_make_dirty() { dirty = true; } -CSGBrush *CSGShape3D::_get_brush() { +CSGBrush CSGShape3D::_get_brush() { if (dirty) { - if (brush) { - memdelete(brush); - } - brush = nullptr; + csg_tool.instantiate(); - CSGBrush *n = _build_brush(); + CSGBrush self_brush = _build_brush(); + csg_tool->add_brush(self_brush, static_cast(operation), snap); for (int i = 0; i < get_child_count(); i++) { CSGShape3D *child = Object::cast_to(get_child(i)); @@ -168,115 +166,16 @@ CSGBrush *CSGShape3D::_get_brush() { continue; } - 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_SUBSTRACTION, *n, *nn2, *nn, snap); - break; - } - memdelete(n); - memdelete(nn2); - n = nn; - } + CSGBrush child_brush = child->_get_brush(); + CSGBrush n; + n.copy_from(child_brush, child->get_transform()); + csg_tool->add_brush(n, static_cast(child->get_operation()), snap); } - - 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(); - } - - brush = n; - + node_aabb = csg_tool->get_aabb(); dirty = false; } - return brush; -} - -int CSGShape3D::mikktGetNumFaces(const SMikkTSpaceContext *pContext) { - ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); - - return surface.vertices.size() / 3; -} - -int CSGShape3D::mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace) { - // always 3 - return 3; -} - -void CSGShape3D::mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert) { - ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); - - Vector3 v = surface.verticesw[iFace * 3 + iVert]; - fvPosOut[0] = v.x; - fvPosOut[1] = v.y; - fvPosOut[2] = v.z; -} - -void CSGShape3D::mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert) { - ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); - - Vector3 n = surface.normalsw[iFace * 3 + iVert]; - fvNormOut[0] = n.x; - fvNormOut[1] = n.y; - fvNormOut[2] = n.z; -} - -void CSGShape3D::mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert) { - ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); - - Vector2 t = surface.uvsw[iFace * 3 + iVert]; - fvTexcOut[0] = t.x; - fvTexcOut[1] = t.y; -} - -void CSGShape3D::mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, - const tbool bIsOrientationPreserving, const int iFace, const int iVert) { - ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); - - int i = iFace * 3 + iVert; - Vector3 normal = surface.normalsw[i]; - Vector3 tangent = Vector3(fvTangent[0], fvTangent[1], fvTangent[2]); - Vector3 bitangent = Vector3(-fvBiTangent[0], -fvBiTangent[1], -fvBiTangent[2]); // for some reason these are reversed, something with the coordinate system in Godot - float d = bitangent.dot(normal.cross(tangent)); - - i *= 4; - surface.tansw[i++] = tangent.x; - surface.tansw[i++] = tangent.y; - surface.tansw[i++] = tangent.z; - surface.tansw[i++] = d < 0 ? -1 : 1; + return csg_tool->get_brush(); } void CSGShape3D::_update_shape() { @@ -287,174 +186,12 @@ void CSGShape3D::_update_shape() { set_base(RID()); root_mesh.unref(); //byebye root mesh - CSGBrush *n = _get_brush(); - ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush."); - - OAHashMap vec_map; - - Vector face_count; - face_count.resize(n->materials.size() + 1); - for (int i = 0; i < face_count.size(); i++) { - face_count.write[i] = 0; - } - - for (int i = 0; i < n->faces.size(); i++) { - int mat = n->faces[i].material; - ERR_CONTINUE(mat < -1 || mat >= face_count.size()); - int idx = mat == -1 ? face_count.size() - 1 : mat; - - Plane p(n->faces[i].vertices[0], n->faces[i].vertices[1], n->faces[i].vertices[2]); - - for (int j = 0; j < 3; j++) { - Vector3 v = n->faces[i].vertices[j]; - Vector3 add; - if (vec_map.lookup(v, add)) { - add += p.normal; - } else { - add = p.normal; - } - vec_map.set(v, add); - } - - face_count.write[idx]++; - } - - Vector surfaces; - - surfaces.resize(face_count.size()); - - //create arrays - for (int i = 0; i < surfaces.size(); i++) { - surfaces.write[i].vertices.resize(face_count[i] * 3); - surfaces.write[i].normals.resize(face_count[i] * 3); - surfaces.write[i].uvs.resize(face_count[i] * 3); - if (calculate_tangents) { - surfaces.write[i].tans.resize(face_count[i] * 3 * 4); - } - surfaces.write[i].last_added = 0; + _get_brush(); + root_mesh = csg_tool->commit(Variant(), calculate_tangents); - if (i != surfaces.size() - 1) { - surfaces.write[i].material = n->materials[i]; - } - - surfaces.write[i].verticesw = surfaces.write[i].vertices.ptrw(); - surfaces.write[i].normalsw = surfaces.write[i].normals.ptrw(); - surfaces.write[i].uvsw = surfaces.write[i].uvs.ptrw(); - if (calculate_tangents) { - surfaces.write[i].tansw = surfaces.write[i].tans.ptrw(); - } - } - - // Update collision faces. if (root_collision_shape.is_valid()) { - Vector physics_faces; - physics_faces.resize(n->faces.size() * 3); - Vector3 *physicsw = physics_faces.ptrw(); - - 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]]; - } - - root_collision_shape->set_faces(physics_faces); - } - - //fill arrays - { - 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]); - } - - int mat = n->faces[i].material; - ERR_CONTINUE(mat < -1 || mat >= face_count.size()); - int idx = mat == -1 ? face_count.size() - 1 : mat; - - int last = surfaces[idx].last_added; - - Plane p(n->faces[i].vertices[0], n->faces[i].vertices[1], n->faces[i].vertices[2]); - - for (int j = 0; j < 3; j++) { - Vector3 v = n->faces[i].vertices[j]; - - Vector3 normal = p.normal; - - if (n->faces[i].smooth && vec_map.lookup(v, normal)) { - normal.normalize(); - } - - if (n->faces[i].invert) { - normal = -normal; - } - - int k = last + order[j]; - surfaces[idx].verticesw[k] = v; - surfaces[idx].uvsw[k] = n->faces[i].uvs[j]; - surfaces[idx].normalsw[k] = normal; - - if (calculate_tangents) { - // zero out our tangents for now - k *= 4; - surfaces[idx].tansw[k++] = 0.0; - surfaces[idx].tansw[k++] = 0.0; - surfaces[idx].tansw[k++] = 0.0; - surfaces[idx].tansw[k++] = 0.0; - } - } - - surfaces.write[idx].last_added += 3; - } - } - - root_mesh.instantiate(); - //create surfaces - - for (int i = 0; i < surfaces.size(); i++) { - // calculate tangents for this surface - bool have_tangents = calculate_tangents; - if (have_tangents) { - SMikkTSpaceInterface mkif; - mkif.m_getNormal = mikktGetNormal; - mkif.m_getNumFaces = mikktGetNumFaces; - mkif.m_getNumVerticesOfFace = mikktGetNumVerticesOfFace; - mkif.m_getPosition = mikktGetPosition; - mkif.m_getTexCoord = mikktGetTexCoord; - mkif.m_setTSpace = mikktSetTSpaceDefault; - mkif.m_setTSpaceBasic = nullptr; - - SMikkTSpaceContext msc; - msc.m_pInterface = &mkif; - msc.m_pUserData = &surfaces.write[i]; - have_tangents = genTangSpaceDefault(&msc); - } - - if (surfaces[i].last_added == 0) { - continue; - } - - // and convert to surface array - Array array; - array.resize(Mesh::ARRAY_MAX); - - array[Mesh::ARRAY_VERTEX] = surfaces[i].vertices; - array[Mesh::ARRAY_NORMAL] = surfaces[i].normals; - array[Mesh::ARRAY_TEX_UV] = surfaces[i].uvs; - if (have_tangents) { - array[Mesh::ARRAY_TANGENT] = surfaces[i].tans; - } - - int idx = root_mesh->get_surface_count(); - root_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); - root_mesh->surface_set_material(idx, surfaces[i].material); + Ref shape = csg_tool->create_trimesh_shape(); + root_collision_shape->set_faces(shape->get_faces()); } set_base(root_mesh->get_rid()); @@ -464,26 +201,9 @@ AABB CSGShape3D::get_aabb() const { return node_aabb; } -Vector CSGShape3D::get_brush_faces() { - ERR_FAIL_COND_V(!is_inside_tree(), Vector()); - CSGBrush *b = _get_brush(); - if (!b) { - return Vector(); - } - - Vector faces; - int fc = b->faces.size(); - faces.resize(fc * 3); - { - Vector3 *w = faces.ptrw(); - for (int i = 0; i < fc; i++) { - w[i * 3 + 0] = b->faces[i].vertices[0]; - w[i * 3 + 1] = b->faces[i].vertices[1]; - w[i * 3 + 2] = b->faces[i].vertices[2]; - } - } - - return faces; +Vector CSGShape3D::get_brush_faces() const { + ERR_FAIL_COND_V(!is_inside_tree() || csg_tool.is_null(), Vector()); + return csg_tool->get_brush_faces(); } Vector CSGShape3D::get_faces(uint32_t p_usage_flags) const { @@ -635,20 +355,14 @@ void CSGShape3D::_bind_methods() { } CSGShape3D::CSGShape3D() { + csg_tool.instantiate(); set_notify_local_transform(true); } -CSGShape3D::~CSGShape3D() { - if (brush) { - memdelete(brush); - brush = nullptr; - } -} - ////////////////////////////////// -CSGBrush *CSGCombiner3D::_build_brush() { - return memnew(CSGBrush); //does not build anything +CSGBrush CSGCombiner3D::_build_brush() { + return CSGBrush(); //does not build anything } CSGCombiner3D::CSGCombiner3D() { @@ -656,233 +370,86 @@ CSGCombiner3D::CSGCombiner3D() { ///////////////////// -CSGBrush *CSGPrimitive3D::_create_brush_from_arrays(const Vector &p_vertices, const Vector &p_uv, const Vector &p_smooth, const Vector> &p_materials) { - CSGBrush *brush = memnew(CSGBrush); - - Vector invert; - invert.resize(p_vertices.size() / 3); - { - int ic = invert.size(); - bool *w = invert.ptrw(); - for (int i = 0; i < ic; i++) { - w[i] = invert_faces; - } - } - brush->build_from_faces(p_vertices, p_uv, p_smooth, p_materials, invert); - - return brush; -} - void CSGPrimitive3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_invert_faces", "invert_faces"), &CSGPrimitive3D::set_invert_faces); ClassDB::bind_method(D_METHOD("is_inverting_faces"), &CSGPrimitive3D::is_inverting_faces); + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGPrimitive3D::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGPrimitive3D::get_smooth_faces); + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGPrimitive3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGPrimitive3D::get_material); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_faces"), "set_invert_faces", "is_inverting_faces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); } void CSGPrimitive3D::set_invert_faces(bool p_invert) { - if (invert_faces == p_invert) { - return; - } - - invert_faces = p_invert; - + Ref csg_primitive = get_csg_primitive(); + csg_primitive->set_invert_faces(p_invert); _make_dirty(); } -bool CSGPrimitive3D::is_inverting_faces() { - return invert_faces; +bool CSGPrimitive3D::is_inverting_faces() const { + const Ref csg_primitive = get_csg_primitive(); + return csg_primitive->is_inverting_faces(); } -CSGPrimitive3D::CSGPrimitive3D() { - invert_faces = false; +void CSGPrimitive3D::set_smooth_faces(bool p_smooth_faces) { + Ref csg_primitive = get_csg_primitive(); + csg_primitive->set_smooth_faces(p_smooth_faces); + _make_dirty(); } -///////////////////// - -CSGBrush *CSGMesh3D::_build_brush() { - if (!mesh.is_valid()) { - return memnew(CSGBrush); - } - - Vector vertices; - Vector smooth; - Vector> materials; - Vector uvs; - Ref material = get_material(); - - for (int i = 0; i < mesh->get_surface_count(); i++) { - if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { - continue; - } - - Array arrays = mesh->surface_get_arrays(i); - - if (arrays.size() == 0) { - _make_dirty(); - ERR_FAIL_COND_V(arrays.size() == 0, memnew(CSGBrush)); - } - - Vector avertices = arrays[Mesh::ARRAY_VERTEX]; - if (avertices.size() == 0) { - continue; - } - - const Vector3 *vr = avertices.ptr(); - - Vector anormals = arrays[Mesh::ARRAY_NORMAL]; - const Vector3 *nr = nullptr; - if (anormals.size()) { - nr = anormals.ptr(); - } - - Vector auvs = arrays[Mesh::ARRAY_TEX_UV]; - const Vector2 *uvr = nullptr; - if (auvs.size()) { - uvr = auvs.ptr(); - } +bool CSGPrimitive3D::get_smooth_faces() const { + const Ref csg_primitive = get_csg_primitive(); + return csg_primitive->get_smooth_faces(); +} - Ref mat; - if (material.is_valid()) { - mat = material; - } else { - mat = mesh->surface_get_material(i); - } +void CSGPrimitive3D::set_material(const Ref &p_material) { + Ref csg_primitive = get_csg_primitive(); + csg_primitive->set_material(p_material); + _make_dirty(); +} - Vector aindices = arrays[Mesh::ARRAY_INDEX]; - if (aindices.size()) { - int as = vertices.size(); - int is = aindices.size(); - - vertices.resize(as + is); - smooth.resize((as + is) / 3); - materials.resize((as + is) / 3); - uvs.resize(as + is); - - Vector3 *vw = vertices.ptrw(); - bool *sw = smooth.ptrw(); - Vector2 *uvw = uvs.ptrw(); - Ref *mw = materials.ptrw(); - - const int *ir = aindices.ptr(); - - for (int j = 0; j < is; j += 3) { - Vector3 vertex[3]; - Vector3 normal[3]; - Vector2 uv[3]; - - for (int k = 0; k < 3; k++) { - int idx = ir[j + k]; - vertex[k] = vr[idx]; - if (nr) { - normal[k] = nr[idx]; - } - if (uvr) { - uv[k] = uvr[idx]; - } - } - - bool flat = normal[0].is_equal_approx(normal[1]) && normal[0].is_equal_approx(normal[2]); - - vw[as + j + 0] = vertex[0]; - vw[as + j + 1] = vertex[1]; - vw[as + j + 2] = vertex[2]; - - uvw[as + j + 0] = uv[0]; - uvw[as + j + 1] = uv[1]; - uvw[as + j + 2] = uv[2]; - - sw[(as + j) / 3] = !flat; - mw[(as + j) / 3] = mat; - } - } else { - int as = vertices.size(); - int is = avertices.size(); - - vertices.resize(as + is); - smooth.resize((as + is) / 3); - uvs.resize(as + is); - materials.resize((as + is) / 3); - - Vector3 *vw = vertices.ptrw(); - bool *sw = smooth.ptrw(); - Vector2 *uvw = uvs.ptrw(); - Ref *mw = materials.ptrw(); - - for (int j = 0; j < is; j += 3) { - Vector3 vertex[3]; - Vector3 normal[3]; - Vector2 uv[3]; - - for (int k = 0; k < 3; k++) { - vertex[k] = vr[j + k]; - if (nr) { - normal[k] = nr[j + k]; - } - if (uvr) { - uv[k] = uvr[j + k]; - } - } - - bool flat = normal[0].is_equal_approx(normal[1]) && normal[0].is_equal_approx(normal[2]); - - vw[as + j + 0] = vertex[0]; - vw[as + j + 1] = vertex[1]; - vw[as + j + 2] = vertex[2]; - - uvw[as + j + 0] = uv[0]; - uvw[as + j + 1] = uv[1]; - uvw[as + j + 2] = uv[2]; - - sw[(as + j) / 3] = !flat; - mw[(as + j) / 3] = mat; - } - } - } +Ref CSGPrimitive3D::get_material() const { + const Ref csg_primitive = get_csg_primitive(); + return csg_primitive->get_material(); +} - if (vertices.size() == 0) { - return memnew(CSGBrush); +CSGBrush CSGPrimitive3D::_build_brush() { + Ref csg_primitive = get_csg_primitive(); + if (csg_primitive.is_null()) { + return CSGBrush(); + } else { + CSGBrush brush = csg_primitive->get_brush(); + return brush; } - - return _create_brush_from_arrays(vertices, uvs, smooth, materials); } +///////////////////// + void CSGMesh3D::_mesh_changed() { _make_dirty(); update_gizmos(); } -void CSGMesh3D::set_material(const Ref &p_material) { - if (material == p_material) { - return; - } - material = p_material; - _make_dirty(); -} - -Ref CSGMesh3D::get_material() const { - return material; -} - void CSGMesh3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CSGMesh3D::set_mesh); ClassDB::bind_method(D_METHOD("get_mesh"), &CSGMesh3D::get_mesh); - 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"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); } void CSGMesh3D::set_mesh(const Ref &p_mesh) { + Ref mesh = csg_primitive->get_mesh(); if (mesh == p_mesh) { return; } if (mesh.is_valid()) { mesh->disconnect("changed", callable_mp(this, &CSGMesh3D::_mesh_changed)); } - mesh = p_mesh; + csg_primitive->set_mesh(p_mesh); + mesh = csg_primitive->get_mesh(); if (mesh.is_valid()) { mesh->connect("changed", callable_mp(this, &CSGMesh3D::_mesh_changed)); @@ -891,131 +458,16 @@ void CSGMesh3D::set_mesh(const Ref &p_mesh) { _mesh_changed(); } -Ref CSGMesh3D::get_mesh() { - return mesh; +Ref CSGMesh3D::get_mesh() const { + return csg_primitive->get_mesh(); } -//////////////////////////////// - -CSGBrush *CSGSphere3D::_build_brush() { - // set our bounding box - - CSGBrush *brush = memnew(CSGBrush); - - int face_count = rings * radial_segments * 2 - radial_segments * 2; - - bool invert_val = is_inverting_faces(); - Ref material = get_material(); - - Vector faces; - Vector uvs; - Vector smooth; - Vector> materials; - Vector invert; - - faces.resize(face_count * 3); - uvs.resize(face_count * 3); - - smooth.resize(face_count); - materials.resize(face_count); - invert.resize(face_count); - - { - Vector3 *facesw = faces.ptrw(); - Vector2 *uvsw = uvs.ptrw(); - bool *smoothw = smooth.ptrw(); - Ref *materialsw = materials.ptrw(); - bool *invertw = invert.ptrw(); - - // We want to follow an order that's convenient for UVs. - // For latitude step we start at the top and move down like in an image. - const double latitude_step = -Math_PI / rings; - 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 v0 = double(i) / rings; - - double latitude1 = latitude_step * (i + 1) + Math_TAU / 4; - double cos1 = Math::cos(latitude1); - double sin1 = Math::sin(latitude1); - double v1 = double(i + 1) / rings; - - for (int j = 0; j < radial_segments; j++) { - double longitude0 = longitude_step * j; - // We give sin to X and cos to Z on purpose. - // This allows UVs to be CCW on +X so it maps to images well. - double x0 = Math::sin(longitude0); - double z0 = Math::cos(longitude0); - double u0 = double(j) / radial_segments; - - double longitude1 = longitude_step * (j + 1); - double x1 = Math::sin(longitude1); - double z1 = Math::cos(longitude1); - double u1 = double(j + 1) / radial_segments; - - Vector3 v[4] = { - Vector3(x0 * cos0, sin0, z0 * cos0) * radius, - Vector3(x1 * cos0, sin0, z1 * cos0) * radius, - Vector3(x1 * cos1, sin1, z1 * cos1) * radius, - Vector3(x0 * cos1, sin1, z0 * cos1) * radius, - }; - - Vector2 u[4] = { - Vector2(u0, v0), - Vector2(u1, v0), - Vector2(u1, v1), - Vector2(u0, v1), - }; - - // Draw the first face, but skip this at the north pole (i == 0). - if (i > 0) { - facesw[face * 3 + 0] = v[0]; - facesw[face * 3 + 1] = v[1]; - facesw[face * 3 + 2] = v[2]; - - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[1]; - uvsw[face * 3 + 2] = u[2]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - } - - // Draw the second face, but skip this at the south pole (i == rings - 1). - if (i < rings - 1) { - facesw[face * 3 + 0] = v[2]; - facesw[face * 3 + 1] = v[3]; - facesw[face * 3 + 2] = v[0]; - - uvsw[face * 3 + 0] = u[2]; - uvsw[face * 3 + 1] = u[3]; - uvsw[face * 3 + 2] = u[0]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - } - } - } - - if (face != face_count) { - ERR_PRINT("Face mismatch bug! fix code"); - } - } - - brush->build_from_faces(faces, uvs, smooth, materials, invert); - - return brush; +CSGMesh3D::CSGMesh3D() { + csg_primitive.instantiate(); } +//////////////////////////////// + void CSGSphere3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGSphere3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &CSGSphere3D::get_radius); @@ -1025,343 +477,70 @@ void CSGSphere3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CSGSphere3D::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CSGSphere3D::get_rings); - ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGSphere3D::set_smooth_faces); - ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGSphere3D::get_smooth_faces); - - ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGSphere3D::set_material); - ClassDB::bind_method(D_METHOD("get_material"), &CSGSphere3D::get_material); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); } void CSGSphere3D::set_radius(const float p_radius) { - ERR_FAIL_COND(p_radius <= 0); - radius = p_radius; + csg_primitive->set_radius(p_radius); _make_dirty(); update_gizmos(); } float CSGSphere3D::get_radius() const { - return radius; + return csg_primitive->get_radius(); } void CSGSphere3D::set_radial_segments(const int p_radial_segments) { - radial_segments = p_radial_segments > 4 ? p_radial_segments : 4; + csg_primitive->set_radial_segments(p_radial_segments); _make_dirty(); update_gizmos(); } int CSGSphere3D::get_radial_segments() const { - return radial_segments; + return csg_primitive->get_radial_segments(); } void CSGSphere3D::set_rings(const int p_rings) { - rings = p_rings > 1 ? p_rings : 1; + csg_primitive->set_rings(p_rings); _make_dirty(); update_gizmos(); } int CSGSphere3D::get_rings() const { - return rings; -} - -void CSGSphere3D::set_smooth_faces(const bool p_smooth_faces) { - smooth_faces = p_smooth_faces; - _make_dirty(); -} - -bool CSGSphere3D::get_smooth_faces() const { - return smooth_faces; -} - -void CSGSphere3D::set_material(const Ref &p_material) { - material = p_material; - _make_dirty(); -} - -Ref CSGSphere3D::get_material() const { - return material; + return csg_primitive->get_rings(); } CSGSphere3D::CSGSphere3D() { - // defaults - radius = 1.0; - radial_segments = 12; - rings = 6; - smooth_faces = true; + csg_primitive.instantiate(); } /////////////// -CSGBrush *CSGBox3D::_build_brush() { - // set our bounding box - - CSGBrush *brush = memnew(CSGBrush); - - int face_count = 12; //it's a cube.. - - bool invert_val = is_inverting_faces(); - Ref material = get_material(); - - Vector faces; - Vector uvs; - Vector smooth; - Vector> materials; - Vector invert; - - faces.resize(face_count * 3); - uvs.resize(face_count * 3); - - smooth.resize(face_count); - materials.resize(face_count); - invert.resize(face_count); - - { - Vector3 *facesw = faces.ptrw(); - Vector2 *uvsw = uvs.ptrw(); - bool *smoothw = smooth.ptrw(); - Ref *materialsw = materials.ptrw(); - bool *invertw = invert.ptrw(); - - int face = 0; - - Vector3 vertex_mul = size / 2; - - { - for (int i = 0; i < 6; i++) { - Vector3 face_points[4]; - float uv_points[8] = { 0, 0, 0, 1, 1, 1, 1, 0 }; - - for (int j = 0; j < 4; j++) { - float v[3]; - v[0] = 1.0; - v[1] = 1 - 2 * ((j >> 1) & 1); - v[2] = v[1] * (1 - 2 * (j & 1)); - - for (int k = 0; k < 3; k++) { - if (i < 3) { - face_points[j][(i + k) % 3] = v[k]; - } else { - face_points[3 - j][(i + k) % 3] = -v[k]; - } - } - } - - Vector2 u[4]; - for (int j = 0; j < 4; j++) { - u[j] = Vector2(uv_points[j * 2 + 0], uv_points[j * 2 + 1]); - } - - //face 1 - facesw[face * 3 + 0] = face_points[0] * vertex_mul; - facesw[face * 3 + 1] = face_points[1] * vertex_mul; - facesw[face * 3 + 2] = face_points[2] * vertex_mul; - - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[1]; - uvsw[face * 3 + 2] = u[2]; - - smoothw[face] = false; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - //face 2 - facesw[face * 3 + 0] = face_points[2] * vertex_mul; - facesw[face * 3 + 1] = face_points[3] * vertex_mul; - facesw[face * 3 + 2] = face_points[0] * vertex_mul; - - uvsw[face * 3 + 0] = u[2]; - uvsw[face * 3 + 1] = u[3]; - uvsw[face * 3 + 2] = u[0]; - - smoothw[face] = false; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - } - } - - if (face != face_count) { - ERR_PRINT("Face mismatch bug! fix code"); - } - } - - brush->build_from_faces(faces, uvs, smooth, materials, invert); - - return brush; -} - void CSGBox3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &CSGBox3D::set_size); ClassDB::bind_method(D_METHOD("get_size"), &CSGBox3D::get_size); - ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGBox3D::set_material); - ClassDB::bind_method(D_METHOD("get_material"), &CSGBox3D::get_material); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); } void CSGBox3D::set_size(const Vector3 &p_size) { - size = p_size; + csg_primitive->set_size(p_size); _make_dirty(); update_gizmos(); } Vector3 CSGBox3D::get_size() const { - return size; + return csg_primitive->get_size(); } -void CSGBox3D::set_material(const Ref &p_material) { - material = p_material; - _make_dirty(); - update_gizmos(); -} - -Ref CSGBox3D::get_material() const { - return material; +CSGBox3D::CSGBox3D() { + csg_primitive.instantiate(); } /////////////// -CSGBrush *CSGCylinder3D::_build_brush() { - // set our bounding box - - CSGBrush *brush = memnew(CSGBrush); - - int face_count = sides * (cone ? 1 : 2) + sides + (cone ? 0 : sides); - - bool invert_val = is_inverting_faces(); - Ref material = get_material(); - - Vector faces; - Vector uvs; - Vector smooth; - Vector> materials; - Vector invert; - - faces.resize(face_count * 3); - uvs.resize(face_count * 3); - - smooth.resize(face_count); - materials.resize(face_count); - invert.resize(face_count); - - { - Vector3 *facesw = faces.ptrw(); - Vector2 *uvsw = uvs.ptrw(); - bool *smoothw = smooth.ptrw(); - Ref *materialsw = materials.ptrw(); - bool *invertw = invert.ptrw(); - - int face = 0; - - Vector3 vertex_mul(radius, height * 0.5, radius); - - { - for (int i = 0; i < sides; i++) { - float inc = float(i) / sides; - float inc_n = float((i + 1)) / sides; - - float ang = inc * Math_TAU; - float ang_n = inc_n * Math_TAU; - - Vector3 base(Math::cos(ang), 0, Math::sin(ang)); - Vector3 base_n(Math::cos(ang_n), 0, Math::sin(ang_n)); - - Vector3 face_points[4] = { - base + Vector3(0, -1, 0), - base_n + Vector3(0, -1, 0), - base_n * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), - base * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), - }; - - Vector2 u[4] = { - Vector2(inc, 0), - Vector2(inc_n, 0), - Vector2(inc_n, 1), - Vector2(inc, 1), - }; - - //side face 1 - facesw[face * 3 + 0] = face_points[0] * vertex_mul; - facesw[face * 3 + 1] = face_points[1] * vertex_mul; - facesw[face * 3 + 2] = face_points[2] * vertex_mul; - - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[1]; - uvsw[face * 3 + 2] = u[2]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - - if (!cone) { - //side face 2 - facesw[face * 3 + 0] = face_points[2] * vertex_mul; - facesw[face * 3 + 1] = face_points[3] * vertex_mul; - facesw[face * 3 + 2] = face_points[0] * vertex_mul; - - uvsw[face * 3 + 0] = u[2]; - uvsw[face * 3 + 1] = u[3]; - uvsw[face * 3 + 2] = u[0]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - face++; - } - - //bottom face 1 - facesw[face * 3 + 0] = face_points[1] * vertex_mul; - facesw[face * 3 + 1] = face_points[0] * vertex_mul; - facesw[face * 3 + 2] = Vector3(0, -1, 0) * vertex_mul; - - uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); - uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); - uvsw[face * 3 + 2] = Vector2(0.5, 0.5); - - smoothw[face] = false; - invertw[face] = invert_val; - materialsw[face] = material; - face++; - - if (!cone) { - //top face 1 - facesw[face * 3 + 0] = face_points[3] * vertex_mul; - facesw[face * 3 + 1] = face_points[2] * vertex_mul; - facesw[face * 3 + 2] = Vector3(0, 1, 0) * vertex_mul; - - uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); - uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); - uvsw[face * 3 + 2] = Vector2(0.5, 0.5); - - smoothw[face] = false; - invertw[face] = invert_val; - materialsw[face] = material; - face++; - } - } - } - - if (face != face_count) { - ERR_PRINT("Face mismatch bug! fix code"); - } - } - - brush->build_from_faces(faces, uvs, smooth, materials, invert); - - return brush; -} - void CSGCylinder3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGCylinder3D::set_radius); ClassDB::bind_method(D_METHOD("get_radius"), &CSGCylinder3D::get_radius); @@ -1375,211 +554,57 @@ void CSGCylinder3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cone", "cone"), &CSGCylinder3D::set_cone); ClassDB::bind_method(D_METHOD("is_cone"), &CSGCylinder3D::is_cone); - ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGCylinder3D::set_material); - ClassDB::bind_method(D_METHOD("get_material"), &CSGCylinder3D::get_material); - - ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGCylinder3D::set_smooth_faces); - ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGCylinder3D::get_smooth_faces); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cone"), "set_cone", "is_cone"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); } void CSGCylinder3D::set_radius(const float p_radius) { - radius = p_radius; + csg_primitive->set_radius(p_radius); _make_dirty(); update_gizmos(); } float CSGCylinder3D::get_radius() const { - return radius; + return csg_primitive->get_radius(); } void CSGCylinder3D::set_height(const float p_height) { - height = p_height; + csg_primitive->set_height(p_height); _make_dirty(); update_gizmos(); } float CSGCylinder3D::get_height() const { - return height; + return csg_primitive->get_height(); } void CSGCylinder3D::set_sides(const int p_sides) { - ERR_FAIL_COND(p_sides < 3); - sides = p_sides; + csg_primitive->set_sides(p_sides); _make_dirty(); update_gizmos(); } int CSGCylinder3D::get_sides() const { - return sides; + return csg_primitive->get_sides(); } void CSGCylinder3D::set_cone(const bool p_cone) { - cone = p_cone; + csg_primitive->set_cone(p_cone); _make_dirty(); update_gizmos(); } bool CSGCylinder3D::is_cone() const { - return cone; -} - -void CSGCylinder3D::set_smooth_faces(const bool p_smooth_faces) { - smooth_faces = p_smooth_faces; - _make_dirty(); -} - -bool CSGCylinder3D::get_smooth_faces() const { - return smooth_faces; -} - -void CSGCylinder3D::set_material(const Ref &p_material) { - material = p_material; - _make_dirty(); -} - -Ref CSGCylinder3D::get_material() const { - return material; + return csg_primitive->is_cone(); } CSGCylinder3D::CSGCylinder3D() { - // defaults - radius = 1.0; - height = 1.0; - sides = 8; - cone = false; - smooth_faces = true; + csg_primitive.instantiate(); } -/////////////// - -CSGBrush *CSGTorus3D::_build_brush() { - // set our bounding box - - float min_radius = inner_radius; - float max_radius = outer_radius; - - if (min_radius == max_radius) { - return memnew(CSGBrush); //sorry, can't - } - - if (min_radius > max_radius) { - SWAP(min_radius, max_radius); - } - - float radius = (max_radius - min_radius) * 0.5; - - CSGBrush *brush = memnew(CSGBrush); - - int face_count = ring_sides * sides * 2; - - bool invert_val = is_inverting_faces(); - Ref material = get_material(); - - Vector faces; - Vector uvs; - Vector smooth; - Vector> materials; - Vector invert; - - faces.resize(face_count * 3); - uvs.resize(face_count * 3); - - smooth.resize(face_count); - materials.resize(face_count); - invert.resize(face_count); - - { - Vector3 *facesw = faces.ptrw(); - Vector2 *uvsw = uvs.ptrw(); - bool *smoothw = smooth.ptrw(); - Ref *materialsw = materials.ptrw(); - bool *invertw = invert.ptrw(); - - int face = 0; - - { - for (int i = 0; i < sides; i++) { - float inci = float(i) / sides; - float inci_n = float((i + 1)) / sides; - - float angi = inci * Math_TAU; - float angi_n = inci_n * Math_TAU; - - Vector3 normali = Vector3(Math::cos(angi), 0, Math::sin(angi)); - Vector3 normali_n = Vector3(Math::cos(angi_n), 0, Math::sin(angi_n)); - - for (int j = 0; j < ring_sides; j++) { - float incj = float(j) / ring_sides; - float incj_n = float((j + 1)) / ring_sides; - - float angj = incj * Math_TAU; - float angj_n = incj_n * Math_TAU; - - Vector2 normalj = Vector2(Math::cos(angj), Math::sin(angj)) * radius + Vector2(min_radius + radius, 0); - Vector2 normalj_n = Vector2(Math::cos(angj_n), Math::sin(angj_n)) * radius + Vector2(min_radius + radius, 0); - - Vector3 face_points[4] = { - Vector3(normali.x * normalj.x, normalj.y, normali.z * normalj.x), - Vector3(normali.x * normalj_n.x, normalj_n.y, normali.z * normalj_n.x), - Vector3(normali_n.x * normalj_n.x, normalj_n.y, normali_n.z * normalj_n.x), - Vector3(normali_n.x * normalj.x, normalj.y, normali_n.z * normalj.x) - }; - - Vector2 u[4] = { - Vector2(inci, incj), - Vector2(inci, incj_n), - Vector2(inci_n, incj_n), - Vector2(inci_n, incj), - }; - - // face 1 - facesw[face * 3 + 0] = face_points[0]; - facesw[face * 3 + 1] = face_points[2]; - facesw[face * 3 + 2] = face_points[1]; - - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[2]; - uvsw[face * 3 + 2] = u[1]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - - face++; - - //face 2 - facesw[face * 3 + 0] = face_points[3]; - facesw[face * 3 + 1] = face_points[2]; - facesw[face * 3 + 2] = face_points[0]; - - uvsw[face * 3 + 0] = u[3]; - uvsw[face * 3 + 1] = u[2]; - uvsw[face * 3 + 2] = u[0]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_val; - materialsw[face] = material; - face++; - } - } - } - - if (face != face_count) { - ERR_PRINT("Face mismatch bug! fix code"); - } - } - - brush->build_from_faces(faces, uvs, smooth, materials, invert); - - return brush; -} +//////////// void CSGTorus3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_inner_radius", "radius"), &CSGTorus3D::set_inner_radius); @@ -1590,393 +615,96 @@ void CSGTorus3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGTorus3D::set_sides); ClassDB::bind_method(D_METHOD("get_sides"), &CSGTorus3D::get_sides); - - ClassDB::bind_method(D_METHOD("set_ring_sides", "sides"), &CSGTorus3D::set_ring_sides); + ClassDB::bind_method(D_METHOD("set_ring_sides", "ring_sides"), &CSGTorus3D::set_ring_sides); ClassDB::bind_method(D_METHOD("get_ring_sides"), &CSGTorus3D::get_ring_sides); - ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGTorus3D::set_material); - ClassDB::bind_method(D_METHOD("get_material"), &CSGTorus3D::get_material); - - ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGTorus3D::set_smooth_faces); - ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGTorus3D::get_smooth_faces); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_inner_radius", "get_inner_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_outer_radius", "get_outer_radius"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_ring_sides", "get_ring_sides"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); } void CSGTorus3D::set_inner_radius(const float p_inner_radius) { - inner_radius = p_inner_radius; + csg_primitive->set_inner_radius(p_inner_radius); _make_dirty(); update_gizmos(); } float CSGTorus3D::get_inner_radius() const { - return inner_radius; + return csg_primitive->get_inner_radius(); } void CSGTorus3D::set_outer_radius(const float p_outer_radius) { - outer_radius = p_outer_radius; + csg_primitive->set_outer_radius(p_outer_radius); _make_dirty(); update_gizmos(); } float CSGTorus3D::get_outer_radius() const { - return outer_radius; + return csg_primitive->get_outer_radius(); } void CSGTorus3D::set_sides(const int p_sides) { - ERR_FAIL_COND(p_sides < 3); - sides = p_sides; + csg_primitive->set_sides(p_sides); _make_dirty(); update_gizmos(); } int CSGTorus3D::get_sides() const { - return sides; + return csg_primitive->get_sides(); } void CSGTorus3D::set_ring_sides(const int p_ring_sides) { - ERR_FAIL_COND(p_ring_sides < 3); - ring_sides = p_ring_sides; + csg_primitive->set_ring_sides(p_ring_sides); _make_dirty(); update_gizmos(); } int CSGTorus3D::get_ring_sides() const { - return ring_sides; -} - -void CSGTorus3D::set_smooth_faces(const bool p_smooth_faces) { - smooth_faces = p_smooth_faces; - _make_dirty(); -} - -bool CSGTorus3D::get_smooth_faces() const { - return smooth_faces; -} - -void CSGTorus3D::set_material(const Ref &p_material) { - material = p_material; - _make_dirty(); -} - -Ref CSGTorus3D::get_material() const { - return material; + return csg_primitive->get_ring_sides(); } CSGTorus3D::CSGTorus3D() { // defaults - inner_radius = 2.0; - outer_radius = 3.0; - sides = 8; - ring_sides = 6; - smooth_faces = true; + csg_primitive.instantiate(); } /////////////// -CSGBrush *CSGPolygon3D::_build_brush() { - CSGBrush *brush = memnew(CSGBrush); - - if (polygon.size() < 3) { - return brush; - } - - // Triangulate polygon shape. - Vector shape_polygon = polygon; - if (Triangulate::get_area(shape_polygon) > 0) { - shape_polygon.reverse(); - } - int shape_sides = shape_polygon.size(); - Vector shape_faces = Geometry2D::triangulate_polygon(shape_polygon); - ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon"); - - // Get polygon enclosing Rect2. - Rect2 shape_rect(shape_polygon[0], Vector2()); - for (int i = 1; i < shape_sides; i++) { - shape_rect.expand_to(shape_polygon[i]); - } +CSGBrush CSGPolygon3D::_build_brush() { + // set our bounding box - // If MODE_PATH, check if curve has changed. - Ref curve; - if (mode == MODE_PATH) { - Path3D *current_path = Object::cast_to(get_node_or_null(path_node)); - if (path != current_path) { - if (path) { - path->disconnect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); - path->disconnect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); - } - path = current_path; - if (path) { - path->connect("tree_exited", callable_mp(this, &CSGPolygon3D::_path_exited)); - path->connect("curve_changed", callable_mp(this, &CSGPolygon3D::_path_changed)); - } + if (csg_primitive->get_mode() == CSGPolygonShape3D::MODE_PATH) { + if (!has_node(path_node)) { + return CSGBrush(); } - + Node *n = get_node(path_node); + if (!n) { + return CSGBrush(); + } + Path3D *path = Object::cast_to(n); if (!path) { - return brush; + return CSGBrush(); } - curve = path->get_curve(); - if (curve.is_null() || curve->get_point_count() < 2) { - return brush; + Ref curve = path->get_curve(); + if (curve.is_null()) { + return CSGBrush(); } - } - - // Calculate the number extrusions, ends and faces. - int extrusions = 0; - int extrusion_face_count = shape_sides * 2; - int end_count = 0; - int shape_face_count = shape_faces.size() / 3; - switch (mode) { - case MODE_DEPTH: - extrusions = 1; - end_count = 2; - break; - case MODE_SPIN: - extrusions = spin_sides; - if (spin_degrees < 360) { - end_count = 2; - } - break; - case MODE_PATH: { - extrusions = Math::ceil(1.0 * curve->get_point_count() / path_interval); - if (!path_joined) { - end_count = 2; - extrusions -= 1; - } - } break; - } - int face_count = extrusions * extrusion_face_count + end_count * shape_face_count; - - // Intialize variables used to create the mesh. - Ref material = get_material(); - - Vector faces; - Vector uvs; - Vector smooth; - Vector> materials; - Vector invert; - - faces.resize(face_count * 3); - uvs.resize(face_count * 3); - smooth.resize(face_count); - materials.resize(face_count); - invert.resize(face_count); - - Vector3 *facesw = faces.ptrw(); - Vector2 *uvsw = uvs.ptrw(); - bool *smoothw = smooth.ptrw(); - Ref *materialsw = materials.ptrw(); - bool *invertw = invert.ptrw(); - - int face = 0; - Transform3D base_xform; - Transform3D current_xform; - Transform3D previous_xform; - double u_step = 1.0 / extrusions; - double v_step = 1.0 / shape_sides; - double spin_step = Math::deg2rad(spin_degrees / spin_sides); - double extrusion_step = 1.0 / extrusions; - if (mode == MODE_PATH) { - if (path_joined) { - extrusion_step = 1.0 / (extrusions - 1); + if (curve->get_baked_length() <= 0) { + return CSGBrush(); } - extrusion_step *= curve->get_baked_length(); - } + csg_primitive->set_path_curve(curve); - if (mode == MODE_PATH) { + Transform3D path_to_this; if (!path_local) { - base_xform = path->get_global_transform(); - } - - Vector3 current_point = curve->interpolate_baked(0); - Vector3 next_point = curve->interpolate_baked(extrusion_step); - Vector3 current_up = Vector3(0, 1, 0); - Vector3 direction = next_point - current_point; - - if (path_joined) { - Vector3 last_point = curve->interpolate_baked(curve->get_baked_length()); - direction = next_point - last_point; - } - - switch (path_rotation) { - case PATH_ROTATION_POLYGON: - direction = Vector3(0, 0, -1); - break; - case PATH_ROTATION_PATH: - break; - case PATH_ROTATION_PATH_FOLLOW: - current_up = curve->interpolate_baked_up_vector(0); - break; - } - - Transform3D facing = Transform3D().looking_at(direction, current_up); - current_xform = base_xform.translated(current_point) * facing; - } - - // Create the mesh. - if (end_count > 0) { - // Add front end face. - for (int face_idx = 0; face_idx < shape_face_count; face_idx++) { - for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) { - // We need to reverse the rotation of the shape face vertices. - int index = shape_faces[face_idx * 3 + 2 - face_vertex_idx]; - Point2 p = shape_polygon[index]; - Point2 uv = (p - shape_rect.position) / shape_rect.size; - - // Use the left side of the bottom half of the y-inverted texture. - uv.x = uv.x / 2; - uv.y = 1 - (uv.y / 2); - - facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0)); - uvsw[face * 3 + face_vertex_idx] = uv; - } - - smoothw[face] = false; - materialsw[face] = material; - invertw[face] = invert_faces; - face++; - } - } - - // Add extrusion faces. - for (int x0 = 0; x0 < extrusions; x0++) { - previous_xform = current_xform; - - switch (mode) { - case MODE_DEPTH: { - current_xform.translate(Vector3(0, 0, -depth)); - } break; - case MODE_SPIN: { - 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; - } - } - - Vector3 previous_point = curve->interpolate_baked(previous_offset); - Vector3 current_point = curve->interpolate_baked(current_offset); - Vector3 next_point = curve->interpolate_baked(next_offset); - Vector3 current_up = Vector3(0, 1, 0); - Vector3 direction = next_point - previous_point; - - switch (path_rotation) { - case PATH_ROTATION_POLYGON: - direction = Vector3(0, 0, -1); - break; - case PATH_ROTATION_PATH: - break; - case PATH_ROTATION_PATH_FOLLOW: - current_up = curve->interpolate_baked_up_vector(current_offset); - break; - } - - Transform3D facing = Transform3D().looking_at(direction, current_up); - current_xform = base_xform.translated(current_point) * facing; - } break; - } - - double u0 = x0 * u_step; - double u1 = ((x0 + 1) * u_step); - if (mode == MODE_PATH && !path_continuous_u) { - u0 = 0.0; - u1 = 1.0; - } - - for (int y0 = 0; y0 < shape_sides; y0++) { - int y1 = (y0 + 1) % shape_sides; - // Use the top half of the texture. - double v0 = (y0 * v_step) / 2; - double v1 = ((y0 + 1) * v_step) / 2; - - Vector3 v[4] = { - previous_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)), - current_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)), - current_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)), - previous_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)), - }; - - Vector2 u[4] = { - Vector2(u0, v0), - Vector2(u1, v0), - Vector2(u1, v1), - Vector2(u0, v1), - }; - - // Face 1 - facesw[face * 3 + 0] = v[0]; - facesw[face * 3 + 1] = v[1]; - facesw[face * 3 + 2] = v[2]; - - uvsw[face * 3 + 0] = u[0]; - uvsw[face * 3 + 1] = u[1]; - uvsw[face * 3 + 2] = u[2]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_faces; - materialsw[face] = material; - - face++; - - // Face 2 - facesw[face * 3 + 0] = v[2]; - facesw[face * 3 + 1] = v[3]; - facesw[face * 3 + 2] = v[0]; - - uvsw[face * 3 + 0] = u[2]; - uvsw[face * 3 + 1] = u[3]; - uvsw[face * 3 + 2] = u[0]; - - smoothw[face] = smooth_faces; - invertw[face] = invert_faces; - materialsw[face] = material; - - face++; - } - } - - if (end_count > 1) { - // Add back end face. - for (int face_idx = 0; face_idx < shape_face_count; face_idx++) { - for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) { - int index = shape_faces[face_idx * 3 + face_vertex_idx]; - Point2 p = shape_polygon[index]; - Point2 uv = (p - shape_rect.position) / shape_rect.size; - - // Use the x-inverted ride side of the bottom half of the y-inverted texture. - uv.x = 1 - uv.x / 2; - uv.y = 1 - (uv.y / 2); - - facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0)); - uvsw[face * 3 + face_vertex_idx] = uv; - } - - smoothw[face] = false; - materialsw[face] = material; - invertw[face] = invert_faces; - face++; + // center on paths origin + path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); } + csg_primitive->set_path_transform(path_to_this); } - ERR_FAIL_COND_V_MSG(face != face_count, brush, "Bug: Failed to create the CSGPolygon mesh correctly."); - - brush->build_from_faces(faces, uvs, smooth, materials, invert); - - return brush; + return CSGPrimitive3D::_build_brush(); } void CSGPolygon3D::_notification(int p_what) { @@ -1990,13 +718,14 @@ void CSGPolygon3D::_notification(int p_what) { } void CSGPolygon3D::_validate_property(PropertyInfo &property) const { - if (property.name.begins_with("spin") && mode != MODE_SPIN) { + CSGPolygonShape3D::Mode mode = csg_primitive->get_mode(); + if (property.name.begins_with("spin") && mode != CSGPolygonShape3D::MODE_SPIN) { property.usage = PROPERTY_USAGE_NONE; } - if (property.name.begins_with("path") && mode != MODE_PATH) { + if (property.name.begins_with("path") && mode != CSGPolygonShape3D::MODE_PATH) { property.usage = PROPERTY_USAGE_NONE; } - if (property.name == "depth" && mode != MODE_DEPTH) { + if (property.name == "depth" && mode != CSGPolygonShape3D::MODE_DEPTH) { property.usage = PROPERTY_USAGE_NONE; } @@ -2046,12 +775,6 @@ void CSGPolygon3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_path_joined", "enable"), &CSGPolygon3D::set_path_joined); ClassDB::bind_method(D_METHOD("is_path_joined"), &CSGPolygon3D::is_path_joined); - ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGPolygon3D::set_material); - ClassDB::bind_method(D_METHOD("get_material"), &CSGPolygon3D::get_material); - - ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGPolygon3D::set_smooth_faces); - ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGPolygon3D::get_smooth_faces); - ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CSGPolygon3D::_is_editable_3d_polygon); ClassDB::bind_method(D_METHOD("_has_editable_3d_polygon_no_depth"), &CSGPolygon3D::_has_editable_3d_polygon_no_depth); @@ -2066,8 +789,6 @@ void CSGPolygon3D::_bind_methods() { 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::BOOL, "path_joined"), "set_path_joined", "is_path_joined"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); BIND_ENUM_CONSTANT(MODE_DEPTH); BIND_ENUM_CONSTANT(MODE_SPIN); @@ -2079,66 +800,63 @@ void CSGPolygon3D::_bind_methods() { } void CSGPolygon3D::set_polygon(const Vector &p_polygon) { - polygon = p_polygon; + csg_primitive->set_polygon(p_polygon); _make_dirty(); update_gizmos(); } Vector CSGPolygon3D::get_polygon() const { - return polygon; + return csg_primitive->get_polygon(); } void CSGPolygon3D::set_mode(Mode p_mode) { - mode = p_mode; + csg_primitive->set_mode(static_cast(p_mode)); _make_dirty(); update_gizmos(); notify_property_list_changed(); } CSGPolygon3D::Mode CSGPolygon3D::get_mode() const { - return mode; + return static_cast(csg_primitive->get_mode()); } void CSGPolygon3D::set_depth(const float p_depth) { - ERR_FAIL_COND(p_depth < 0.001); - depth = p_depth; + csg_primitive->set_depth(p_depth); _make_dirty(); update_gizmos(); } float CSGPolygon3D::get_depth() const { - return depth; + return csg_primitive->get_depth(); } void CSGPolygon3D::set_path_continuous_u(bool p_enable) { - path_continuous_u = p_enable; + csg_primitive->set_path_continuous_u(p_enable); _make_dirty(); } bool CSGPolygon3D::is_path_continuous_u() const { - return path_continuous_u; + return csg_primitive->is_path_continuous_u(); } void CSGPolygon3D::set_spin_degrees(const float p_spin_degrees) { - ERR_FAIL_COND(p_spin_degrees < 0.01 || p_spin_degrees > 360); - spin_degrees = p_spin_degrees; + csg_primitive->set_spin_degrees(p_spin_degrees); _make_dirty(); update_gizmos(); } float CSGPolygon3D::get_spin_degrees() const { - return spin_degrees; + return csg_primitive->get_spin_degrees(); } void CSGPolygon3D::set_spin_sides(int p_spin_sides) { - ERR_FAIL_COND(p_spin_sides < 3); - spin_sides = p_spin_sides; + csg_primitive->set_spin_sides(p_spin_sides); _make_dirty(); update_gizmos(); } int CSGPolygon3D::get_spin_sides() const { - return spin_sides; + return csg_primitive->get_spin_sides(); } void CSGPolygon3D::set_path_node(const NodePath &p_path) { @@ -2152,24 +870,23 @@ NodePath CSGPolygon3D::get_path_node() const { } void CSGPolygon3D::set_path_interval(float p_interval) { - ERR_FAIL_COND_MSG(p_interval <= 0 || p_interval > 1, "Path interval must be greater than 0 and less than or equal to 1.0."); - path_interval = p_interval; + csg_primitive->set_path_interval(p_interval); _make_dirty(); update_gizmos(); } float CSGPolygon3D::get_path_interval() const { - return path_interval; + return csg_primitive->get_path_interval(); } -void CSGPolygon3D::set_path_rotation(PathRotation p_rotation) { - path_rotation = p_rotation; +void CSGPolygon3D::set_path_rotation(CSGPolygon3D::PathRotation p_rotation) { + csg_primitive->set_path_rotation(static_cast(p_rotation)); _make_dirty(); update_gizmos(); } CSGPolygon3D::PathRotation CSGPolygon3D::get_path_rotation() const { - return path_rotation; + return static_cast(csg_primitive->get_path_rotation()); } void CSGPolygon3D::set_path_local(bool p_enable) { @@ -2183,31 +900,13 @@ bool CSGPolygon3D::is_path_local() const { } void CSGPolygon3D::set_path_joined(bool p_enable) { - path_joined = p_enable; + csg_primitive->set_path_joined(p_enable); _make_dirty(); update_gizmos(); } bool CSGPolygon3D::is_path_joined() const { - return path_joined; -} - -void CSGPolygon3D::set_smooth_faces(const bool p_smooth_faces) { - smooth_faces = p_smooth_faces; - _make_dirty(); -} - -bool CSGPolygon3D::get_smooth_faces() const { - return smooth_faces; -} - -void CSGPolygon3D::set_material(const Ref &p_material) { - material = p_material; - _make_dirty(); -} - -Ref CSGPolygon3D::get_material() const { - return material; + return csg_primitive->is_path_joined(); } bool CSGPolygon3D::_is_editable_3d_polygon() const { @@ -2219,20 +918,11 @@ bool CSGPolygon3D::_has_editable_3d_polygon_no_depth() const { } CSGPolygon3D::CSGPolygon3D() { - // defaults - mode = MODE_DEPTH; + csg_primitive.instantiate(); + Vector polygon; polygon.push_back(Vector2(0, 0)); polygon.push_back(Vector2(0, 1)); polygon.push_back(Vector2(1, 1)); polygon.push_back(Vector2(1, 0)); - depth = 1.0; - spin_degrees = 360; - spin_sides = 8; - smooth_faces = false; - path_interval = 1.0; - path_rotation = PATH_ROTATION_PATH_FOLLOW; - path_local = false; - path_continuous_u = true; - path_joined = false; - path = nullptr; + set_polygon(polygon); } diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 5cf371665e67..84a0362c355f 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -33,28 +33,26 @@ #define CSGJS_HEADER_ONLY -#include "csg.h" +#include "csg_tool.h" #include "scene/3d/path_3d.h" #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "thirdparty/misc/mikktspace.h" class CSGShape3D : public GeometryInstance3D { GDCLASS(CSGShape3D, GeometryInstance3D); public: + // This is very ugly enum Operation { - OPERATION_UNION, - OPERATION_INTERSECTION, - OPERATION_SUBTRACTION, - + OPERATION_UNION = CSGTool::OPERATION_UNION, + OPERATION_INTERSECTION = CSGTool::OPERATION_INTERSECTION, + OPERATION_SUBTRACTION = CSGTool::OPERATION_SUBTRACTION, }; private: Operation operation = OPERATION_UNION; CSGShape3D *parent = nullptr; - CSGBrush *brush = nullptr; + Ref csg_tool; AABB node_aabb; @@ -71,49 +69,17 @@ class CSGShape3D : public GeometryInstance3D { Ref root_mesh; - struct Vector3Hasher { - _ALWAYS_INLINE_ uint32_t hash(const Vector3 &p_vec3) const { - uint32_t h = hash_djb2_one_float(p_vec3.x); - h = hash_djb2_one_float(p_vec3.y, h); - h = hash_djb2_one_float(p_vec3.z, h); - return h; - } - }; - - struct ShapeUpdateSurface { - Vector vertices; - Vector normals; - Vector uvs; - Vector tans; - Ref material; - int last_added = 0; - - Vector3 *verticesw = nullptr; - Vector3 *normalsw = nullptr; - Vector2 *uvsw = nullptr; - real_t *tansw = nullptr; - }; - - //mikktspace callbacks - static int mikktGetNumFaces(const SMikkTSpaceContext *pContext); - static int mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace); - static void mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert); - static void mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert); - static void mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert); - static void mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, - const tbool bIsOrientationPreserving, const int iFace, const int iVert); - void _update_shape(); protected: void _notification(int p_what); - virtual CSGBrush *_build_brush() = 0; + virtual CSGBrush _build_brush() = 0; void _make_dirty(); static void _bind_methods(); friend class CSGCombiner3D; - CSGBrush *_get_brush(); + CSGBrush _get_brush(); virtual void _validate_property(PropertyInfo &property) const override; @@ -123,7 +89,7 @@ class CSGShape3D : public GeometryInstance3D { void set_operation(Operation p_operation); Operation get_operation() const; - virtual Vector get_brush_faces(); + virtual Vector get_brush_faces() const; virtual AABB get_aabb() const override; virtual Vector get_faces(uint32_t p_usage_flags) const override; @@ -151,16 +117,14 @@ class CSGShape3D : public GeometryInstance3D { bool is_root_shape() const; CSGShape3D(); - ~CSGShape3D(); }; -VARIANT_ENUM_CAST(CSGShape3D::Operation) +VARIANT_ENUM_CAST(CSGShape3D::Operation); class CSGCombiner3D : public CSGShape3D { GDCLASS(CSGCombiner3D, CSGShape3D); -private: - virtual CSGBrush *_build_brush() override; + virtual CSGBrush _build_brush() override; public: CSGCombiner3D(); @@ -170,24 +134,31 @@ class CSGPrimitive3D : public CSGShape3D { GDCLASS(CSGPrimitive3D, CSGShape3D); protected: - bool invert_faces; - CSGBrush *_create_brush_from_arrays(const Vector &p_vertices, const Vector &p_uv, const Vector &p_smooth, const Vector> &p_materials); + virtual CSGBrush _build_brush() override; + virtual Ref get_csg_primitive() const = 0; static void _bind_methods(); public: void set_invert_faces(bool p_invert); - bool is_inverting_faces(); + bool is_inverting_faces() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref &p_material); + Ref get_material() const; - CSGPrimitive3D(); + CSGPrimitive3D() {} }; class CSGMesh3D : public CSGPrimitive3D { GDCLASS(CSGMesh3D, CSGPrimitive3D); - virtual CSGBrush *_build_brush() override; + virtual Ref get_csg_primitive() const override { + return csg_primitive; + } - Ref mesh; - Ref material; + Ref csg_primitive; void _mesh_changed(); @@ -196,21 +167,19 @@ class CSGMesh3D : public CSGPrimitive3D { public: void set_mesh(const Ref &p_mesh); - Ref get_mesh(); + Ref get_mesh() const; - void set_material(const Ref &p_material); - Ref get_material() const; + CSGMesh3D(); }; class CSGSphere3D : public CSGPrimitive3D { GDCLASS(CSGSphere3D, CSGPrimitive3D); - virtual CSGBrush *_build_brush() override; - Ref material; - bool smooth_faces; - float radius; - int radial_segments; - int rings; + virtual Ref get_csg_primitive() const override { + return csg_primitive; + } + + Ref csg_primitive; protected: static void _bind_methods(); @@ -225,21 +194,17 @@ class CSGSphere3D : public CSGPrimitive3D { void set_rings(const int p_rings); int get_rings() const; - void set_material(const Ref &p_material); - Ref get_material() const; - - void set_smooth_faces(bool p_smooth_faces); - bool get_smooth_faces() const; - CSGSphere3D(); }; class CSGBox3D : public CSGPrimitive3D { GDCLASS(CSGBox3D, CSGPrimitive3D); - virtual CSGBrush *_build_brush() override; - Ref material; - Vector3 size = Vector3(2, 2, 2); + virtual Ref get_csg_primitive() const override { + return csg_primitive; + } + + Ref csg_primitive; protected: static void _bind_methods(); @@ -248,22 +213,17 @@ class CSGBox3D : public CSGPrimitive3D { void set_size(const Vector3 &p_size); Vector3 get_size() const; - void set_material(const Ref &p_material); - Ref get_material() const; - - CSGBox3D() {} + CSGBox3D(); }; class CSGCylinder3D : public CSGPrimitive3D { GDCLASS(CSGCylinder3D, CSGPrimitive3D); - virtual CSGBrush *_build_brush() override; - Ref material; - float radius; - float height; - int sides; - bool cone; - bool smooth_faces; + virtual Ref get_csg_primitive() const override { + return csg_primitive; + } + + Ref csg_primitive; protected: static void _bind_methods(); @@ -281,25 +241,17 @@ class CSGCylinder3D : public CSGPrimitive3D { void set_cone(const bool p_cone); bool is_cone() const; - void set_smooth_faces(bool p_smooth_faces); - bool get_smooth_faces() const; - - void set_material(const Ref &p_material); - Ref get_material() const; - CSGCylinder3D(); }; class CSGTorus3D : public CSGPrimitive3D { GDCLASS(CSGTorus3D, CSGPrimitive3D); - virtual CSGBrush *_build_brush() override; - Ref material; - float inner_radius; - float outer_radius; - int sides; - int ring_sides; - bool smooth_faces; + virtual Ref get_csg_primitive() const override { + return csg_primitive; + } + + Ref csg_primitive; protected: static void _bind_methods(); @@ -317,12 +269,6 @@ class CSGTorus3D : public CSGPrimitive3D { void set_ring_sides(const int p_ring_sides); int get_ring_sides() const; - void set_smooth_faces(bool p_smooth_faces); - bool get_smooth_faces() const; - - void set_material(const Ref &p_material); - Ref get_material() const; - CSGTorus3D(); }; @@ -330,30 +276,28 @@ class CSGPolygon3D : public CSGPrimitive3D { GDCLASS(CSGPolygon3D, CSGPrimitive3D); public: + // This is very ugly enum Mode { - MODE_DEPTH, - MODE_SPIN, - MODE_PATH + MODE_DEPTH = CSGPolygonShape3D::MODE_DEPTH, + MODE_SPIN = CSGPolygonShape3D::MODE_SPIN, + MODE_PATH = CSGPolygonShape3D::MODE_PATH }; + // and this too enum PathRotation { - PATH_ROTATION_POLYGON, - PATH_ROTATION_PATH, - PATH_ROTATION_PATH_FOLLOW, + PATH_ROTATION_POLYGON = CSGPolygonShape3D::PATH_ROTATION_POLYGON, + PATH_ROTATION_PATH = CSGPolygonShape3D::PATH_ROTATION_PATH, + PATH_ROTATION_PATH_FOLLOW = CSGPolygonShape3D::PATH_ROTATION_PATH_FOLLOW, }; private: - virtual CSGBrush *_build_brush() override; - - Vector polygon; - Ref material; - - Mode mode; + virtual Ref get_csg_primitive() const override { + return csg_primitive; + } - float depth; + Ref csg_primitive; - float spin_degrees; - int spin_sides; + virtual CSGBrush _build_brush() override; NodePath path_node; float path_interval; @@ -411,12 +355,6 @@ class CSGPolygon3D : public CSGPrimitive3D { void set_path_joined(bool p_enable); bool is_path_joined() const; - void set_smooth_faces(bool p_smooth_faces); - bool get_smooth_faces() const; - - void set_material(const Ref &p_material); - Ref get_material() const; - CSGPolygon3D(); }; diff --git a/modules/csg/csg_tool.cpp b/modules/csg/csg_tool.cpp new file mode 100644 index 000000000000..b55f8d9227f8 --- /dev/null +++ b/modules/csg/csg_tool.cpp @@ -0,0 +1,1720 @@ +/*************************************************************************/ +/* csg_tool.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "csg_tool.h" + +#include "scene/3d/path_3d.h" + +// CSGPrimitive3D +void CSGPrimitiveShape3D::_create_brush_from_arrays(const Vector &p_vertices, const Vector &p_uv, const Vector &p_smooth, const Vector> &p_materials) { + brush = CSGBrush(); + + Vector invert; + invert.resize(p_vertices.size() / 3); + { + int ic = invert.size(); + bool *w = invert.ptrw(); + for (int i = 0; i < ic; i++) { + w[i] = invert_faces; + } + } + brush.build_from_faces(p_vertices, p_uv, p_smooth, p_materials, invert); +} + +CSGBrush CSGPrimitiveShape3D::get_brush() { + _update_brush(); + return brush; +} + +void CSGPrimitiveShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_invert_faces", "invert_faces"), &CSGPrimitiveShape3D::set_invert_faces); + ClassDB::bind_method(D_METHOD("is_inverting_faces"), &CSGPrimitiveShape3D::is_inverting_faces); + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGPrimitiveShape3D::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGPrimitiveShape3D::get_smooth_faces); + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGPrimitiveShape3D::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGPrimitiveShape3D::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_faces"), "set_invert_faces", "is_inverting_faces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "StandardMaterial3D,ShaderMaterial"), "set_material", "get_material"); +} + +bool CSGPrimitiveShape3D::is_inverting_faces() const { + return invert_faces; +} + +void CSGPrimitiveShape3D::set_invert_faces(bool p_invert) { + if (invert_faces == p_invert) { + return; + } + dirty = true; + emit_changed(); + invert_faces = p_invert; +} + +bool CSGPrimitiveShape3D::get_smooth_faces() const { + return smooth_faces; +} + +void CSGPrimitiveShape3D::set_smooth_faces(bool p_smooth_faces) { + if (smooth_faces == p_smooth_faces) { + return; + } + dirty = true; + emit_changed(); + smooth_faces = p_smooth_faces; +} + +Ref CSGPrimitiveShape3D::get_material() const { + return material; +} + +void CSGPrimitiveShape3D::set_material(const Ref &p_material) { + if (material == p_material) { + return; + } + dirty = true; + emit_changed(); + material = p_material; +} + +// CSGMesh3D +void CSGMeshShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CSGMeshShape3D::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &CSGMeshShape3D::get_mesh); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); +} + +Ref CSGMeshShape3D::get_mesh() const { + return mesh; +} + +void CSGMeshShape3D::set_mesh(const Ref &p_mesh) { + if (mesh == p_mesh) { + return; + } + dirty = true; + emit_changed(); + mesh = p_mesh; +} + +void CSGMeshShape3D::_update_brush() { + if (dirty) { + if (!mesh.is_valid()) { + return; + } + + Vector vertices; + Vector smooth; + Vector> materials; + Vector uvs; + Ref material = get_material(); + + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + Array arrays = mesh->surface_get_arrays(i); + + if (arrays.size() == 0) { + dirty = true; + ERR_FAIL_COND(arrays.size() == 0); + } + + Vector avertices = arrays[Mesh::ARRAY_VERTEX]; + if (avertices.size() == 0) { + continue; + } + + const Vector3 *vr = avertices.ptr(); + + Vector anormals = arrays[Mesh::ARRAY_NORMAL]; + const Vector3 *nr = nullptr; + if (anormals.size()) { + nr = anormals.ptr(); + } + + Vector auvs = arrays[Mesh::ARRAY_TEX_UV]; + const Vector2 *uvr = nullptr; + if (auvs.size()) { + uvr = auvs.ptr(); + } + + Ref mat; + if (material.is_valid()) { + mat = material; + } else { + mat = mesh->surface_get_material(i); + } + + Vector aindices = arrays[Mesh::ARRAY_INDEX]; + if (aindices.size()) { + int as = vertices.size(); + int is = aindices.size(); + + vertices.resize(as + is); + smooth.resize((as + is) / 3); + materials.resize((as + is) / 3); + uvs.resize(as + is); + + Vector3 *vw = vertices.ptrw(); + bool *sw = smooth.ptrw(); + Vector2 *uvw = uvs.ptrw(); + Ref *mw = materials.ptrw(); + + const int *ir = aindices.ptr(); + + for (int j = 0; j < is; j += 3) { + Vector3 vertex[3]; + Vector3 normal[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + int idx = ir[j + k]; + vertex[k] = vr[idx]; + if (nr) { + normal[k] = nr[idx]; + } + if (uvr) { + uv[k] = uvr[idx]; + } + } + + bool flat = normal[0].is_equal_approx(normal[1]) && normal[0].is_equal_approx(normal[2]); + + vw[as + j + 0] = vertex[0]; + vw[as + j + 1] = vertex[1]; + vw[as + j + 2] = vertex[2]; + + uvw[as + j + 0] = uv[0]; + uvw[as + j + 1] = uv[1]; + uvw[as + j + 2] = uv[2]; + + sw[(as + j) / 3] = !flat; + mw[(as + j) / 3] = mat; + } + } else { + int as = vertices.size(); + int is = avertices.size(); + + vertices.resize(as + is); + smooth.resize((as + is) / 3); + uvs.resize(as + is); + materials.resize((as + is) / 3); + + Vector3 *vw = vertices.ptrw(); + bool *sw = smooth.ptrw(); + Vector2 *uvw = uvs.ptrw(); + Ref *mw = materials.ptrw(); + + for (int j = 0; j < is; j += 3) { + Vector3 vertex[3]; + Vector3 normal[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + vertex[k] = vr[j + k]; + if (nr) { + normal[k] = nr[j + k]; + } + if (uvr) { + uv[k] = uvr[j + k]; + } + } + + bool flat = normal[0].is_equal_approx(normal[1]) && normal[0].is_equal_approx(normal[2]); + + vw[as + j + 0] = vertex[0]; + vw[as + j + 1] = vertex[1]; + vw[as + j + 2] = vertex[2]; + + uvw[as + j + 0] = uv[0]; + uvw[as + j + 1] = uv[1]; + uvw[as + j + 2] = uv[2]; + + sw[(as + j) / 3] = !flat; + mw[(as + j) / 3] = mat; + } + } + } + + if (vertices.size() == 0) { + return; + } + + _create_brush_from_arrays(vertices, uvs, smooth, materials); + + dirty = false; + } +} + +// CSGBox3D +void CSGBoxShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &CSGBoxShape3D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &CSGBoxShape3D::get_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); +} + +Vector3 CSGBoxShape3D::get_size() const { + return size; +} + +void CSGBoxShape3D::set_size(const Vector3 &p_size) { + if (size == p_size) { + return; + } + dirty = true; + emit_changed(); + size = p_size; +} + +void CSGBoxShape3D::_update_brush() { + if (dirty) { + brush = CSGBrush(); + + int face_count = 12; //it's a cube.. + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + + Vector3 vertex_mul = size / 2; + + { + for (int i = 0; i < 6; i++) { + Vector3 face_points[4]; + float uv_points[8] = { 0, 0, 0, 1, 1, 1, 1, 0 }; + + for (int j = 0; j < 4; j++) { + float v[3]; + v[0] = 1.0; + v[1] = 1 - 2 * ((j >> 1) & 1); + v[2] = v[1] * (1 - 2 * (j & 1)); + + for (int k = 0; k < 3; k++) { + if (i < 3) { + face_points[j][(i + k) % 3] = v[k]; + } else { + face_points[3 - j][(i + k) % 3] = -v[k]; + } + } + } + + Vector2 u[4]; + for (int j = 0; j < 4; j++) { + u[j] = Vector2(uv_points[j * 2 + 0], uv_points[j * 2 + 1]); + } + + //face 1 + facesw[face * 3 + 0] = face_points[0] * vertex_mul; + facesw[face * 3 + 1] = face_points[1] * vertex_mul; + facesw[face * 3 + 2] = face_points[2] * vertex_mul; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + //face 2 + facesw[face * 3 + 0] = face_points[2] * vertex_mul; + facesw[face * 3 + 1] = face_points[3] * vertex_mul; + facesw[face * 3 + 2] = face_points[0] * vertex_mul; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush.build_from_faces(faces, uvs, smooth, materials, invert); + dirty = false; + } +} + +// CSGCylinder3D +void CSGCylinderShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGCylinderShape3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGCylinderShape3D::get_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &CSGCylinderShape3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CSGCylinderShape3D::get_height); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGCylinderShape3D::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGCylinderShape3D::get_sides); + ClassDB::bind_method(D_METHOD("set_cone", "cone"), &CSGCylinderShape3D::set_cone); + ClassDB::bind_method(D_METHOD("is_cone"), &CSGCylinderShape3D::is_cone); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cone"), "set_cone", "is_cone"); +} + +float CSGCylinderShape3D::get_radius() const { + return radius; +} + +void CSGCylinderShape3D::set_radius(float p_radius) { + ERR_FAIL_COND(p_radius <= 0); + if (radius == p_radius) { + return; + } + dirty = true; + emit_changed(); + radius = p_radius; +} + +float CSGCylinderShape3D::get_height() const { + return height; +} + +void CSGCylinderShape3D::set_height(float p_height) { + if (height == p_height) { + return; + } + dirty = true; + emit_changed(); + height = p_height; +} + +int CSGCylinderShape3D::get_sides() const { + return sides; +} + +void CSGCylinderShape3D::set_sides(int p_sides) { + ERR_FAIL_COND(p_sides < 3); + if (sides == p_sides) { + return; + } + dirty = true; + emit_changed(); + sides = p_sides; +} + +bool CSGCylinderShape3D::is_cone() const { + return cone; +} + +void CSGCylinderShape3D::set_cone(bool p_cone) { + if (cone == p_cone) { + return; + } + dirty = true; + emit_changed(); + cone = p_cone; +} + +void CSGCylinderShape3D::_update_brush() { + if (dirty) { + brush = CSGBrush(); + + // set our bounding box + + int face_count = sides * (cone ? 1 : 2) + sides + (cone ? 0 : sides); + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + + Vector3 vertex_mul(radius, height * 0.5, radius); + + { + for (int i = 0; i < sides; i++) { + float inc = float(i) / sides; + float inc_n = float((i + 1)) / sides; + + float ang = inc * Math_TAU; + float ang_n = inc_n * Math_TAU; + + Vector3 base(Math::cos(ang), 0, Math::sin(ang)); + Vector3 base_n(Math::cos(ang_n), 0, Math::sin(ang_n)); + + Vector3 face_points[4] = { + base + Vector3(0, -1, 0), + base_n + Vector3(0, -1, 0), + base_n * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), + base * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), + }; + + Vector2 u[4] = { + Vector2(inc, 0), + Vector2(inc_n, 0), + Vector2(inc_n, 1), + Vector2(inc, 1), + }; + + //side face 1 + facesw[face * 3 + 0] = face_points[0] * vertex_mul; + facesw[face * 3 + 1] = face_points[1] * vertex_mul; + facesw[face * 3 + 2] = face_points[2] * vertex_mul; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + if (!cone) { + //side face 2 + facesw[face * 3 + 0] = face_points[2] * vertex_mul; + facesw[face * 3 + 1] = face_points[3] * vertex_mul; + facesw[face * 3 + 2] = face_points[0] * vertex_mul; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + + //bottom face 1 + facesw[face * 3 + 0] = face_points[1] * vertex_mul; + facesw[face * 3 + 1] = face_points[0] * vertex_mul; + facesw[face * 3 + 2] = Vector3(0, -1, 0) * vertex_mul; + + uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 2] = Vector2(0.5, 0.5); + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + + if (!cone) { + //top face 1 + facesw[face * 3 + 0] = face_points[3] * vertex_mul; + facesw[face * 3 + 1] = face_points[2] * vertex_mul; + facesw[face * 3 + 2] = Vector3(0, 1, 0) * vertex_mul; + + uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 2] = Vector2(0.5, 0.5); + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush.build_from_faces(faces, uvs, smooth, materials, invert); + dirty = false; + } +} + +// CSGSphere3D +void CSGSphereShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGSphereShape3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGSphereShape3D::get_radius); + ClassDB::bind_method(D_METHOD("set_radial_segments", "radial_segments"), &CSGSphereShape3D::set_radial_segments); + ClassDB::bind_method(D_METHOD("get_radial_segments"), &CSGSphereShape3D::get_radial_segments); + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CSGSphereShape3D::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &CSGSphereShape3D::get_rings); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); +} + +float CSGSphereShape3D::get_radius() const { + return radius; +} + +void CSGSphereShape3D::set_radius(float p_radius) { + if (radius == p_radius) { + return; + } + dirty = true; + emit_changed(); + radius = p_radius; +} + +int CSGSphereShape3D::get_radial_segments() const { + return radial_segments; +} + +void CSGSphereShape3D::set_radial_segments(int p_radial_segments) { + p_radial_segments = p_radial_segments > 4 ? p_radial_segments : 4; + if (radial_segments == p_radial_segments) { + return; + } + + dirty = true; + emit_changed(); + radial_segments = p_radial_segments; +} + +int CSGSphereShape3D::get_rings() const { + return rings; +} + +void CSGSphereShape3D::set_rings(int p_rings) { + p_rings = p_rings > 1 ? p_rings : 1; + if (rings == p_rings) { + return; + } + dirty = true; + emit_changed(); + rings = p_rings; +} + +void CSGSphereShape3D::_update_brush() { + // set our bounding box + if (dirty) { + brush = CSGBrush(); + + int face_count = rings * radial_segments * 2 - radial_segments * 2; + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + const double lat_step = Math_TAU / rings; + const double lon_step = Math_TAU / radial_segments; + + for (int i = 1; i <= rings; i++) { + double lat0 = lat_step * (i - 1) - Math_TAU / 4; + double z0 = Math::sin(lat0); + double zr0 = Math::cos(lat0); + double u0 = double(i - 1) / rings; + + double lat1 = lat_step * i - Math_TAU / 4; + double z1 = Math::sin(lat1); + double zr1 = Math::cos(lat1); + double u1 = double(i) / rings; + + for (int j = radial_segments; j >= 1; j--) { + double lng0 = lon_step * (j - 1); + double x0 = Math::cos(lng0); + double y0 = Math::sin(lng0); + double v0 = double(i - 1) / radial_segments; + + double lng1 = lon_step * j; + double x1 = Math::cos(lng1); + double y1 = Math::sin(lng1); + double v1 = double(i) / radial_segments; + + Vector3 v[4] = { + Vector3(x1 * zr0, z0, y1 * zr0) * radius, + Vector3(x1 * zr1, z1, y1 * zr1) * radius, + Vector3(x0 * zr1, z1, y0 * zr1) * radius, + Vector3(x0 * zr0, z0, y0 * zr0) * radius + }; + + Vector2 u[4] = { + Vector2(v1, u0), + Vector2(v1, u1), + Vector2(v0, u1), + Vector2(v0, u0), + + }; + + if (i < rings) { + //face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + + if (i > 1) { + //face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush.build_from_faces(faces, uvs, smooth, materials, invert); + } +} + +// CSGTorus3D +void CSGTorusShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_inner_radius", "radius"), &CSGTorusShape3D::set_inner_radius); + ClassDB::bind_method(D_METHOD("get_inner_radius"), &CSGTorusShape3D::get_inner_radius); + ClassDB::bind_method(D_METHOD("set_outer_radius", "radius"), &CSGTorusShape3D::set_outer_radius); + ClassDB::bind_method(D_METHOD("get_outer_radius"), &CSGTorusShape3D::get_outer_radius); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGTorusShape3D::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGTorusShape3D::get_sides); + ClassDB::bind_method(D_METHOD("set_ring_sides", "sides"), &CSGTorusShape3D::set_ring_sides); + ClassDB::bind_method(D_METHOD("get_ring_sides"), &CSGTorusShape3D::get_ring_sides); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_inner_radius", "get_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_outer_radius", "get_outer_radius"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_ring_sides", "get_ring_sides"); +} + +float CSGTorusShape3D::get_inner_radius() const { + return inner_radius; +} + +void CSGTorusShape3D::set_inner_radius(float p_inner_radius) { + if (inner_radius == p_inner_radius) { + return; + } + dirty = true; + emit_changed(); + inner_radius = p_inner_radius; +} + +float CSGTorusShape3D::get_outer_radius() const { + return outer_radius; +} + +void CSGTorusShape3D::set_outer_radius(float p_outer_radius) { + if (outer_radius == p_outer_radius) { + return; + } + + dirty = true; + emit_changed(); + outer_radius = p_outer_radius; +} + +int CSGTorusShape3D::get_sides() const { + return sides; +} + +void CSGTorusShape3D::set_sides(int p_sides) { + ERR_FAIL_COND(p_sides < 3); + if (sides == p_sides) { + return; + } + dirty = true; + emit_changed(); + sides = p_sides; +} + +int CSGTorusShape3D::get_ring_sides() const { + return ring_sides; +} + +void CSGTorusShape3D::set_ring_sides(int p_ring_sides) { + ERR_FAIL_COND(p_ring_sides < 3); + if (ring_sides == p_ring_sides) { + return; + } + dirty = true; + emit_changed(); + ring_sides = p_ring_sides; +} + +void CSGTorusShape3D::_update_brush() { + if (dirty) { + brush = CSGBrush(); + + // set our bounding box + + float min_radius = inner_radius; + float max_radius = outer_radius; + + ERR_FAIL_COND_MSG(min_radius == max_radius, "Condition \"inner_radius == outer_radius\" is true"); + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + int face_count = ring_sides * sides * 2; + + bool invert_val = is_inverting_faces(); + Ref material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + + { + for (int i = 0; i < sides; i++) { + float inci = float(i) / sides; + float inci_n = float((i + 1)) / sides; + + float angi = inci * Math_TAU; + float angi_n = inci_n * Math_TAU; + + Vector3 normali = Vector3(Math::cos(angi), 0, Math::sin(angi)); + Vector3 normali_n = Vector3(Math::cos(angi_n), 0, Math::sin(angi_n)); + + for (int j = 0; j < ring_sides; j++) { + float incj = float(j) / ring_sides; + float incj_n = float((j + 1)) / ring_sides; + + float angj = incj * Math_TAU; + float angj_n = incj_n * Math_TAU; + + Vector2 normalj = Vector2(Math::cos(angj), Math::sin(angj)) * radius + Vector2(min_radius + radius, 0); + Vector2 normalj_n = Vector2(Math::cos(angj_n), Math::sin(angj_n)) * radius + Vector2(min_radius + radius, 0); + + Vector3 face_points[4] = { + Vector3(normali.x * normalj.x, normalj.y, normali.z * normalj.x), + Vector3(normali.x * normalj_n.x, normalj_n.y, normali.z * normalj_n.x), + Vector3(normali_n.x * normalj_n.x, normalj_n.y, normali_n.z * normalj_n.x), + Vector3(normali_n.x * normalj.x, normalj.y, normali_n.z * normalj.x) + }; + + Vector2 u[4] = { + Vector2(inci, incj), + Vector2(inci, incj_n), + Vector2(inci_n, incj_n), + Vector2(inci_n, incj), + }; + + // face 1 + facesw[face * 3 + 0] = face_points[0]; + facesw[face * 3 + 1] = face_points[2]; + facesw[face * 3 + 2] = face_points[1]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[1]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + //face 2 + facesw[face * 3 + 0] = face_points[3]; + facesw[face * 3 + 1] = face_points[2]; + facesw[face * 3 + 2] = face_points[0]; + + uvsw[face * 3 + 0] = u[3]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush.build_from_faces(faces, uvs, smooth, materials, invert); + dirty = false; + } +} + +// CSGPolygonShape3D +void CSGPolygonShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CSGPolygonShape3D::set_polygon); + ClassDB::bind_method(D_METHOD("get_polygon"), &CSGPolygonShape3D::get_polygon); + + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &CSGPolygonShape3D::set_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &CSGPolygonShape3D::get_mode); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CSGPolygonShape3D::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &CSGPolygonShape3D::get_depth); + + ClassDB::bind_method(D_METHOD("set_spin_degrees", "degrees"), &CSGPolygonShape3D::set_spin_degrees); + ClassDB::bind_method(D_METHOD("get_spin_degrees"), &CSGPolygonShape3D::get_spin_degrees); + + ClassDB::bind_method(D_METHOD("set_spin_sides", "spin_sides"), &CSGPolygonShape3D::set_spin_sides); + ClassDB::bind_method(D_METHOD("get_spin_sides"), &CSGPolygonShape3D::get_spin_sides); + + ClassDB::bind_method(D_METHOD("set_path_curve", "curve"), &CSGPolygonShape3D::set_path_curve); + ClassDB::bind_method(D_METHOD("get_path_curve"), &CSGPolygonShape3D::get_path_curve); + + ClassDB::bind_method(D_METHOD("set_path_transform", "xform"), &CSGPolygonShape3D::set_path_transform); + ClassDB::bind_method(D_METHOD("get_path_transform"), &CSGPolygonShape3D::get_path_transform); + + ClassDB::bind_method(D_METHOD("set_path_interval", "distance"), &CSGPolygonShape3D::set_path_interval); + ClassDB::bind_method(D_METHOD("get_path_interval"), &CSGPolygonShape3D::get_path_interval); + + ClassDB::bind_method(D_METHOD("set_path_rotation", "mode"), &CSGPolygonShape3D::set_path_rotation); + ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygonShape3D::get_path_rotation); + + ClassDB::bind_method(D_METHOD("set_path_continuous_u", "enable"), &CSGPolygonShape3D::set_path_continuous_u); + ClassDB::bind_method(D_METHOD("is_path_continuous_u"), &CSGPolygonShape3D::is_path_continuous_u); + + ClassDB::bind_method(D_METHOD("set_path_joined", "enable"), &CSGPolygonShape3D::set_path_joined); + ClassDB::bind_method(D_METHOD("is_path_joined"), &CSGPolygonShape3D::is_path_joined); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Depth,Spin,Path"), "set_mode", "get_mode"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.01,100.0,0.01,or_greater,exp"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spin_degrees", PROPERTY_HINT_RANGE, "1,360,0.1"), "set_spin_degrees", "get_spin_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "spin_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_spin_sides", "get_spin_sides"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "path_curve", PROPERTY_HINT_RESOURCE_TYPE), "set_path_curve", "get_path_curve"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "path_transform"), "set_path_transform", "get_path_transform"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.1,1.0,0.05,exp"), "set_path_interval", "get_path_interval"); + 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_continuous_u"), "set_path_continuous_u", "is_path_continuous_u"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_joined"), "set_path_joined", "is_path_joined"); + + BIND_ENUM_CONSTANT(MODE_DEPTH); + BIND_ENUM_CONSTANT(MODE_SPIN); + BIND_ENUM_CONSTANT(MODE_PATH); + + BIND_ENUM_CONSTANT(PATH_ROTATION_POLYGON); + BIND_ENUM_CONSTANT(PATH_ROTATION_PATH); + BIND_ENUM_CONSTANT(PATH_ROTATION_PATH_FOLLOW); +} + +void CSGPolygonShape3D::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("spin") && mode != CSGPolygonShape3D::MODE_SPIN) { + property.usage = PROPERTY_USAGE_NONE; + } + if (property.name.begins_with("path") && mode != CSGPolygonShape3D::MODE_PATH) { + property.usage = PROPERTY_USAGE_NONE; + } + if (property.name == "depth" && mode != CSGPolygonShape3D::MODE_DEPTH) { + property.usage = PROPERTY_USAGE_NONE; + } + + CSGPrimitiveShape3D::_validate_property(property); +} + +void CSGPolygonShape3D::set_polygon(const Vector &p_polygon) { + dirty = true; + emit_changed(); + polygon = p_polygon; +} + +Vector CSGPolygonShape3D::get_polygon() const { + return polygon; +} + +void CSGPolygonShape3D::set_mode(Mode p_mode) { + dirty = true; + emit_changed(); + mode = p_mode; + notify_property_list_changed(); +} + +CSGPolygonShape3D::Mode CSGPolygonShape3D::get_mode() const { + return mode; +} + +void CSGPolygonShape3D::set_depth(const float p_depth) { + ERR_FAIL_COND(p_depth < 0.001); + dirty = true; + emit_changed(); + depth = p_depth; +} + +float CSGPolygonShape3D::get_depth() const { + return depth; +} + +void CSGPolygonShape3D::set_path_curve(const Ref &p_curve) { + dirty = true; + emit_changed(); + path_curve = p_curve; +} + +Ref CSGPolygonShape3D::get_path_curve() const { + return path_curve; +} + +void CSGPolygonShape3D::set_path_transform(Transform3D p_xform) { + dirty = true; + emit_changed(); + path_transform = p_xform; +} + +Transform3D CSGPolygonShape3D::get_path_transform() const { + return path_transform; +} + +void CSGPolygonShape3D::set_path_continuous_u(bool p_enable) { + dirty = true; + emit_changed(); + path_continuous_u = p_enable; +} + +bool CSGPolygonShape3D::is_path_continuous_u() const { + return path_continuous_u; +} + +void CSGPolygonShape3D::set_spin_degrees(const float p_spin_degrees) { + ERR_FAIL_COND(p_spin_degrees < 0.01 || p_spin_degrees > 360); + dirty = true; + emit_changed(); + spin_degrees = p_spin_degrees; +} + +float CSGPolygonShape3D::get_spin_degrees() const { + return spin_degrees; +} + +void CSGPolygonShape3D::set_spin_sides(const int p_spin_sides) { + ERR_FAIL_COND(p_spin_sides < 3); + dirty = true; + emit_changed(); + spin_sides = p_spin_sides; +} + +int CSGPolygonShape3D::get_spin_sides() const { + return spin_sides; +} + +void CSGPolygonShape3D::set_path_interval(float p_interval) { + ERR_FAIL_COND_MSG(p_interval < 0.001, "Path interval cannot be smaller than 0.001."); + dirty = true; + emit_changed(); + path_interval = p_interval; +} + +float CSGPolygonShape3D::get_path_interval() const { + return path_interval; +} + +void CSGPolygonShape3D::set_path_rotation(PathRotation p_rotation) { + dirty = true; + emit_changed(); + path_rotation = p_rotation; +} + +CSGPolygonShape3D::PathRotation CSGPolygonShape3D::get_path_rotation() const { + return path_rotation; +} + +void CSGPolygonShape3D::set_path_joined(bool p_enable) { + dirty = true; + emit_changed(); + path_joined = p_enable; +} + +bool CSGPolygonShape3D::is_path_joined() const { + return path_joined; +} + +void CSGPolygonShape3D::_update_brush() { + if (dirty) { + brush = CSGBrush(); + + if (polygon.size() < 3) { + dirty = false; + return; + } + + // Triangulate polygon shape. + Vector shape_polygon = polygon; + if (Triangulate::get_area(shape_polygon) > 0) { + shape_polygon.reverse(); + } + int shape_sides = shape_polygon.size(); + Vector shape_faces = Geometry2D::triangulate_polygon(shape_polygon); + ERR_FAIL_COND_MSG(shape_faces.size() < 3, "Failed to triangulate CSGPolygon"); + + // Get polygon enclosing Rect2. + Rect2 shape_rect(shape_polygon[0], Vector2()); + for (int i = 1; i < shape_sides; i++) { + shape_rect.expand_to(shape_polygon[i]); + } + + // If MODE_PATH, check if curve has changed. + if (mode == MODE_PATH) { + ERR_FAIL_COND(path_curve.is_null()); + } + + // Calculate the number extrusions, ends and faces. + int extrusions = 0; + int extrusion_face_count = shape_sides * 2; + int end_count = 0; + int shape_face_count = shape_faces.size() / 3; + switch (mode) { + case MODE_DEPTH: + extrusions = 1; + end_count = 2; + break; + case MODE_SPIN: + extrusions = spin_sides; + if (spin_degrees < 360) { + end_count = 2; + } + break; + case MODE_PATH: { + extrusions = Math::ceil(1.0 * path_curve->get_point_count() / path_interval); + if (!path_joined) { + end_count = 2; + extrusions -= 1; + } + } break; + } + int face_count = extrusions * extrusion_face_count + end_count * shape_face_count; + + // Intialize variables used to create the mesh. + Ref material = get_material(); + + Vector faces; + Vector uvs; + Vector smooth; + Vector> materials; + Vector invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + Vector3 *facesw = faces.ptrw(); + Vector2 *uvsw = uvs.ptrw(); + bool *smoothw = smooth.ptrw(); + Ref *materialsw = materials.ptrw(); + bool *invertw = invert.ptrw(); + + int face = 0; + Transform3D current_xform; + Transform3D previous_xform; + double u_step = 1.0 / extrusions; + double v_step = 1.0 / shape_sides; + double spin_step = Math::deg2rad(spin_degrees / spin_sides); + double extrusion_step = 1.0 / extrusions; + if (mode == MODE_PATH) { + if (path_joined) { + extrusion_step = 1.0 / (extrusions - 1); + } + extrusion_step *= path_curve->get_baked_length(); + } + + if (mode == MODE_PATH) { + Vector3 current_point = path_curve->interpolate_baked(0); + Vector3 next_point = path_curve->interpolate_baked(extrusion_step); + Vector3 current_up = Vector3(0, 1, 0); + Vector3 direction = next_point - current_point; + + if (path_joined) { + Vector3 last_point = path_curve->interpolate_baked(path_curve->get_baked_length()); + direction = next_point - last_point; + } + + switch (path_rotation) { + case PATH_ROTATION_POLYGON: + direction = Vector3(0, 0, -1); + break; + case PATH_ROTATION_PATH: + break; + case PATH_ROTATION_PATH_FOLLOW: + current_up = path_curve->interpolate_baked_up_vector(0); + break; + } + + Transform3D facing = Transform3D().looking_at(direction, current_up); + current_xform = path_transform.translated(current_point) * facing; + } + + // Create the mesh. + if (end_count > 0) { + // Add front end face. + for (int face_idx = 0; face_idx < shape_face_count; face_idx++) { + for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) { + // We need to reverse the rotation of the shape face vertices. + int index = shape_faces[face_idx * 3 + 2 - face_vertex_idx]; + Point2 p = shape_polygon[index]; + Point2 uv = (p - shape_rect.position) / shape_rect.size; + + // Use the left side of the bottom half of the y-inverted texture. + uv.x = uv.x / 2; + uv.y = 1 - (uv.y / 2); + + facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0)); + uvsw[face * 3 + face_vertex_idx] = uv; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = is_inverting_faces(); + face++; + } + } + + // Add extrusion faces. + for (int x0 = 0; x0 < extrusions; x0++) { + previous_xform = current_xform; + + switch (mode) { + case MODE_DEPTH: { + current_xform.translate(Vector3(0, 0, -depth)); + } break; + case MODE_SPIN: { + 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; + } + } + + Vector3 previous_point = path_curve->interpolate_baked(previous_offset); + Vector3 current_point = path_curve->interpolate_baked(current_offset); + Vector3 next_point = path_curve->interpolate_baked(next_offset); + Vector3 current_up = Vector3(0, 1, 0); + Vector3 direction = next_point - previous_point; + + switch (path_rotation) { + case PATH_ROTATION_POLYGON: + direction = Vector3(0, 0, -1); + break; + case PATH_ROTATION_PATH: + break; + case PATH_ROTATION_PATH_FOLLOW: + current_up = path_curve->interpolate_baked_up_vector(current_offset); + break; + } + + Transform3D facing = Transform3D().looking_at(direction, current_up); + current_xform = path_transform.translated(current_point) * facing; + } break; + } + + double u0 = x0 * u_step; + double u1 = ((x0 + 1) * u_step); + if (mode == MODE_PATH && !path_continuous_u) { + u0 = 0.0; + u1 = 1.0; + } + + for (int y0 = 0; y0 < shape_sides; y0++) { + int y1 = (y0 + 1) % shape_sides; + // Use the top half of the texture. + double v0 = (y0 * v_step) / 2; + double v1 = ((y0 + 1) * v_step) / 2; + + Vector3 v[4] = { + previous_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)), + current_xform.xform(Vector3(shape_polygon[y0].x, shape_polygon[y0].y, 0)), + current_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)), + previous_xform.xform(Vector3(shape_polygon[y1].x, shape_polygon[y1].y, 0)), + }; + + Vector2 u[4] = { + Vector2(u0, v0), + Vector2(u1, v0), + Vector2(u1, v1), + Vector2(u0, v1), + }; + + // Face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = is_inverting_faces(); + materialsw[face] = material; + + face++; + + // Face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = get_smooth_faces(); + invertw[face] = is_inverting_faces(); + materialsw[face] = material; + + face++; + } + } + + if (end_count > 1) { + // Add back end face. + for (int face_idx = 0; face_idx < shape_face_count; face_idx++) { + for (int face_vertex_idx = 0; face_vertex_idx < 3; face_vertex_idx++) { + int index = shape_faces[face_idx * 3 + face_vertex_idx]; + Point2 p = shape_polygon[index]; + Point2 uv = (p - shape_rect.position) / shape_rect.size; + + // Use the x-inverted ride side of the bottom half of the y-inverted texture. + uv.x = 1 - uv.x / 2; + uv.y = 1 - (uv.y / 2); + + facesw[face * 3 + face_vertex_idx] = current_xform.xform(Vector3(p.x, p.y, 0)); + uvsw[face * 3 + face_vertex_idx] = uv; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = is_inverting_faces(); + face++; + } + } + + ERR_FAIL_COND_MSG(face != face_count, "Bug: Failed to create the CSGPolygon mesh correctly."); + + brush.build_from_faces(faces, uvs, smooth, materials, invert); + } +} + +// CSGTool + +int CSGTool::mikktGetNumFaces(const SMikkTSpaceContext *pContext) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + return surface.vertices.size() / 3; +} + +int CSGTool::mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace) { + // always 3 + return 3; +} + +void CSGTool::mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector3 v = surface.verticesw[iFace * 3 + iVert]; + fvPosOut[0] = v.x; + fvPosOut[1] = v.y; + fvPosOut[2] = v.z; +} + +void CSGTool::mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector3 n = surface.normalsw[iFace * 3 + iVert]; + fvNormOut[0] = n.x; + fvNormOut[1] = n.y; + fvNormOut[2] = n.z; +} + +void CSGTool::mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + Vector2 t = surface.uvsw[iFace * 3 + iVert]; + fvTexcOut[0] = t.x; + fvTexcOut[1] = t.y; +} + +void CSGTool::mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert) { + ShapeUpdateSurface &surface = *((ShapeUpdateSurface *)pContext->m_pUserData); + + int i = iFace * 3 + iVert; + Vector3 normal = surface.normalsw[i]; + Vector3 tangent = Vector3(fvTangent[0], fvTangent[1], fvTangent[2]); + Vector3 bitangent = Vector3(-fvBiTangent[0], -fvBiTangent[1], -fvBiTangent[2]); // for some reason these are reversed, something with the coordinate system in Godot + float d = bitangent.dot(normal.cross(tangent)); + + i *= 4; + surface.tansw[i++] = tangent.x; + surface.tansw[i++] = tangent.y; + surface.tansw[i++] = tangent.z; + surface.tansw[i++] = d < 0 ? -1 : 1; +} + +void CSGTool::add_brush(CSGBrush &p_brush, Operation p_operation, float p_vertex_snap) { + if (brush.faces.is_empty()) { + brush = p_brush; + } else { + CSGBrush newbrush; + CSGBrushOperation brush_operation; + brush_operation.merge_brushes(static_cast(p_operation), brush, p_brush, newbrush, p_vertex_snap); + brush = newbrush; + } +} + +Vector CSGTool::get_brush_faces() const { + Vector faces; + int fc = brush.faces.size(); + faces.resize(fc * 3); + { + Vector3 *w = faces.ptrw(); + for (int i = 0; i < fc; i++) { + w[i * 3 + 0] = brush.faces[i].vertices[0]; + w[i * 3 + 1] = brush.faces[i].vertices[1]; + w[i * 3 + 2] = brush.faces[i].vertices[2]; + } + } + + return faces; +} + +void CSGTool::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_primitive", "primitive", "operation", "xform", "vertex_snap"), &CSGTool::add_primitive, DEFVAL(OPERATION_UNION), DEFVAL(Transform3D()), DEFVAL(0.001)); + ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &CSGTool::create_trimesh_shape); + ClassDB::bind_method(D_METHOD("commit", "existing", "generate_tangents"), &CSGTool::commit, DEFVAL(Variant()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_aabb"), &CSGTool::get_aabb); + + BIND_ENUM_CONSTANT(OPERATION_UNION); + BIND_ENUM_CONSTANT(OPERATION_INTERSECTION); + BIND_ENUM_CONSTANT(OPERATION_SUBTRACTION); +} + +void CSGTool::add_primitive(Ref p_primitive, Operation p_operation, Transform3D p_xform, float p_vertex_snap) { + CSGBrush newbrush; + newbrush.copy_from(p_primitive->get_brush(), p_xform); + add_brush(newbrush, p_operation, p_vertex_snap); +} + +Ref CSGTool::create_trimesh_shape() const { + Ref shape; + shape.instantiate(); + + Vector physics_faces; + physics_faces.resize(brush.faces.size() * 3); + Vector3 *physicsw = physics_faces.ptrw(); + + for (int i = 0; i < brush.faces.size(); i++) { + int order[3] = { 0, 1, 2 }; + + if (brush.faces[i].invert) { + SWAP(order[1], order[2]); + } + + physicsw[i * 3 + 0] = brush.faces[i].vertices[order[0]]; + physicsw[i * 3 + 1] = brush.faces[i].vertices[order[1]]; + physicsw[i * 3 + 2] = brush.faces[i].vertices[order[2]]; + } + return shape; +} + +Ref CSGTool::commit(const Ref &p_existing, bool p_generate_tangents) { + Ref mesh; + if (p_existing.is_valid()) { + mesh = p_existing; + } else { + mesh.instantiate(); + } + + OAHashMap vec_map; + + Vector face_count; + face_count.resize(brush.materials.size() + 1); + for (int i = 0; i < face_count.size(); i++) { + face_count.write[i] = 0; + } + + for (int i = 0; i < brush.faces.size(); i++) { + int mat = brush.faces[i].material; + ERR_CONTINUE(mat < -1 || mat >= face_count.size()); + int idx = mat == -1 ? face_count.size() - 1 : mat; + + Plane p(brush.faces[i].vertices[0], brush.faces[i].vertices[1], brush.faces[i].vertices[2]); + + for (int j = 0; j < 3; j++) { + Vector3 v = brush.faces[i].vertices[j]; + Vector3 add; + if (vec_map.lookup(v, add)) { + add += p.normal; + } else { + add = p.normal; + } + vec_map.set(v, add); + } + + face_count.write[idx]++; + } + + Vector surfaces; + + surfaces.resize(face_count.size()); + + //create arrays + for (int i = 0; i < surfaces.size(); i++) { + surfaces.write[i].vertices.resize(face_count[i] * 3); + surfaces.write[i].normals.resize(face_count[i] * 3); + surfaces.write[i].uvs.resize(face_count[i] * 3); + if (p_generate_tangents) { + surfaces.write[i].tans.resize(face_count[i] * 3 * 4); + } + surfaces.write[i].last_added = 0; + + if (i != surfaces.size() - 1) { + surfaces.write[i].material = brush.materials[i]; + } + + surfaces.write[i].verticesw = surfaces.write[i].vertices.ptrw(); + surfaces.write[i].normalsw = surfaces.write[i].normals.ptrw(); + surfaces.write[i].uvsw = surfaces.write[i].uvs.ptrw(); + if (p_generate_tangents) { + surfaces.write[i].tansw = surfaces.write[i].tans.ptrw(); + } + } + + //fill arrays + { + for (int i = 0; i < brush.faces.size(); i++) { + int order[3] = { 0, 1, 2 }; + + if (brush.faces[i].invert) { + SWAP(order[1], order[2]); + } + + int mat = brush.faces[i].material; + ERR_CONTINUE(mat < -1 || mat >= face_count.size()); + int idx = mat == -1 ? face_count.size() - 1 : mat; + + int last = surfaces[idx].last_added; + + Plane p(brush.faces[i].vertices[0], brush.faces[i].vertices[1], brush.faces[i].vertices[2]); + + for (int j = 0; j < 3; j++) { + Vector3 v = brush.faces[i].vertices[j]; + + Vector3 normal = p.normal; + + if (brush.faces[i].smooth && vec_map.lookup(v, normal)) { + normal.normalize(); + } + + if (brush.faces[i].invert) { + normal = -normal; + } + + int k = last + order[j]; + surfaces[idx].verticesw[k] = v; + surfaces[idx].uvsw[k] = brush.faces[i].uvs[j]; + surfaces[idx].normalsw[k] = normal; + + if (p_generate_tangents) { + // zero out our tangents for now + k *= 4; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + surfaces[idx].tansw[k++] = 0.0; + } + } + + surfaces.write[idx].last_added += 3; + } + } + + //create surfaces + + for (int i = 0; i < surfaces.size(); i++) { + // generate tangents for this surface + bool have_tangents = p_generate_tangents; + if (have_tangents) { + SMikkTSpaceInterface mkif; + mkif.m_getNormal = mikktGetNormal; + mkif.m_getNumFaces = mikktGetNumFaces; + mkif.m_getNumVerticesOfFace = mikktGetNumVerticesOfFace; + mkif.m_getPosition = mikktGetPosition; + mkif.m_getTexCoord = mikktGetTexCoord; + mkif.m_setTSpace = mikktSetTSpaceDefault; + mkif.m_setTSpaceBasic = nullptr; + + SMikkTSpaceContext msc; + msc.m_pInterface = &mkif; + msc.m_pUserData = &surfaces.write[i]; + have_tangents = genTangSpaceDefault(&msc); + } + + if (surfaces[i].last_added == 0) { + continue; + } + + // and convert to surface array + Array array; + array.resize(Mesh::ARRAY_MAX); + + array[Mesh::ARRAY_VERTEX] = surfaces[i].vertices; + array[Mesh::ARRAY_NORMAL] = surfaces[i].normals; + array[Mesh::ARRAY_TEX_UV] = surfaces[i].uvs; + if (have_tangents) { + array[Mesh::ARRAY_TANGENT] = surfaces[i].tans; + } + + int idx = mesh->get_surface_count(); + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); + mesh->surface_set_material(idx, surfaces[i].material); + } + return mesh; +} + +AABB CSGTool::get_aabb() const { + AABB aabb; + for (int i = 0; i < brush.faces.size(); i++) { + for (int j = 0; j < 3; j++) { + if (i == 0 && j == 0) { + aabb.position = brush.faces[i].vertices[j]; + } else { + aabb.expand_to(brush.faces[i].vertices[j]); + } + } + } + return aabb; +} diff --git a/modules/csg/csg_tool.h b/modules/csg/csg_tool.h new file mode 100644 index 000000000000..3956eaf64d4d --- /dev/null +++ b/modules/csg/csg_tool.h @@ -0,0 +1,322 @@ +/*************************************************************************/ +/* csg_tool.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 CSG_TOOL_H +#define CSG_TOOL_H + +#include "csg.h" + +#include "core/math/geometry_2d.h" +#include "scene/resources/concave_polygon_shape_3d.h" +#include "scene/resources/material.h" +#include "scene/resources/mesh.h" +#include "thirdparty/misc/mikktspace.h" + +class CSGPrimitiveShape3D : public Resource { + GDCLASS(CSGPrimitiveShape3D, Resource); + + bool invert_faces = false; + bool smooth_faces = true; + Ref material; + +protected: + CSGBrush brush; + bool dirty = true; + + virtual void _update_brush() = 0; + + void _create_brush_from_arrays(const Vector &p_vertices, const Vector &p_uv, const Vector &p_smooth, const Vector> &p_materials); + + _ALWAYS_INLINE_ void _recreate_brush() { + brush = CSGBrush(); + } + + static void _bind_methods(); + +public: + CSGBrush get_brush(); + + bool is_inverting_faces() const; + void set_invert_faces(bool p_invert); + + bool get_smooth_faces() const; + void set_smooth_faces(bool p_smooth_faces); + + Ref get_material() const; + void set_material(const Ref &p_material); +}; + +class CSGMeshShape3D : public CSGPrimitiveShape3D { + GDCLASS(CSGMeshShape3D, CSGPrimitiveShape3D); + + Ref mesh; + + virtual void _update_brush() override; + +protected: + static void _bind_methods(); + +public: + Ref get_mesh() const; + void set_mesh(const Ref &p_mesh); +}; + +class CSGBoxShape3D : public CSGPrimitiveShape3D { + GDCLASS(CSGBoxShape3D, CSGPrimitiveShape3D); + + Vector3 size = Vector3(2, 2, 2); + + virtual void _update_brush() override; + +protected: + static void _bind_methods(); + +public: + Vector3 get_size() const; + void set_size(const Vector3 &p_size); +}; + +class CSGCylinderShape3D : public CSGPrimitiveShape3D { + GDCLASS(CSGCylinderShape3D, CSGPrimitiveShape3D); + + float radius = 1.0; + float height = 1.0; + int sides = 8; + bool cone = false; + + virtual void _update_brush() override; + +protected: + static void _bind_methods(); + +public: + float get_radius() const; + void set_radius(float p_radius); + + float get_height() const; + void set_height(float p_height); + + int get_sides() const; + void set_sides(int p_sides); + + bool is_cone() const; + void set_cone(bool p_cone); +}; + +class CSGSphereShape3D : public CSGPrimitiveShape3D { + GDCLASS(CSGSphereShape3D, CSGPrimitiveShape3D); + + float radius = 1.0; + // Should they match SphereMesh3D? + int radial_segments = 12; + int rings = 6; + + virtual void _update_brush() override; + +protected: + static void _bind_methods(); + +public: + float get_radius() const; + void set_radius(float p_radius); + + int get_radial_segments() const; + void set_radial_segments(int p_radial_segments); + + int get_rings() const; + void set_rings(int p_rings); +}; + +class CSGTorusShape3D : public CSGPrimitiveShape3D { + GDCLASS(CSGTorusShape3D, CSGPrimitiveShape3D); + + float inner_radius = 2.0; + float outer_radius = 3.0; + int sides = 8; + int ring_sides = 6; + + virtual void _update_brush() override; + +protected: + static void _bind_methods(); + +public: + float get_inner_radius() const; + void set_inner_radius(float p_inner_radius); + float get_outer_radius() const; + void set_outer_radius(float p_outer_radius); + int get_sides() const; + void set_sides(int p_sides); + int get_ring_sides() const; + void set_ring_sides(int p_ring_sides); +}; + +// TODO: migrate this + +class CSGPolygonShape3D : public CSGPrimitiveShape3D { + GDCLASS(CSGPolygonShape3D, CSGPrimitiveShape3D); + +public: + enum Mode { + MODE_DEPTH, + MODE_SPIN, + MODE_PATH + }; + + enum PathRotation { + PATH_ROTATION_POLYGON, + PATH_ROTATION_PATH, + PATH_ROTATION_PATH_FOLLOW, + }; + +private: + Vector polygon; + + Mode mode = MODE_DEPTH; + + float depth = 1.0; + + float spin_degrees = 360.0; + int spin_sides = 8; + + Ref path_curve; + float path_interval = 1.0; + PathRotation path_rotation = PATH_ROTATION_PATH; + Transform3D path_transform; + + bool path_continuous_u = false; + bool path_joined = false; + + void _path_changed(); + void _path_exited(); + + virtual void _update_brush() override; + +protected: + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const override; + +public: + void set_polygon(const Vector &p_polygon); + Vector get_polygon() const; + + void set_mode(Mode p_mode); + Mode get_mode() const; + + void set_depth(float p_depth); + float get_depth() const; + + void set_spin_degrees(float p_spin_degrees); + float get_spin_degrees() const; + + void set_spin_sides(int p_spin_sides); + int get_spin_sides() const; + + void set_path_curve(const Ref &p_curve); + Ref get_path_curve() const; + + void set_path_interval(float p_interval); + float get_path_interval() const; + + void set_path_rotation(PathRotation p_rotation); + PathRotation get_path_rotation() const; + + void set_path_transform(Transform3D p_xform); + Transform3D get_path_transform() const; + + void set_path_continuous_u(bool p_enable); + bool is_path_continuous_u() const; + + void set_path_joined(bool p_enable); + bool is_path_joined() const; +}; + +VARIANT_ENUM_CAST(CSGPolygonShape3D::Mode) +VARIANT_ENUM_CAST(CSGPolygonShape3D::PathRotation) + +class CSGTool : public RefCounted { + GDCLASS(CSGTool, RefCounted); + +public: + enum Operation { + OPERATION_UNION = CSGBrushOperation::OPERATION_UNION, + OPERATION_INTERSECTION = CSGBrushOperation::OPERATION_INTERSECTION, + OPERATION_SUBTRACTION = CSGBrushOperation::OPERATION_SUBSTRACTION, + }; + +private: + CSGBrush brush; + + struct ShapeUpdateSurface { + Vector vertices; + Vector normals; + Vector uvs; + Vector tans; + Ref material; + int last_added = 0; + + Vector3 *verticesw = nullptr; + Vector3 *normalsw = nullptr; + Vector2 *uvsw = nullptr; + float *tansw = nullptr; + }; + + //mikktspace callbacks + static int mikktGetNumFaces(const SMikkTSpaceContext *pContext); + static int mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace); + static void mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert); + static void mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert); + static void mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert); + static void mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); + +protected: + static void _bind_methods(); + +public: + void add_brush(CSGBrush &p_brush, Operation p_operation = OPERATION_UNION, float p_vertex_snap = 0.001); + Vector get_brush_faces() const; + + _ALWAYS_INLINE_ CSGBrush get_brush() { + return brush; + } + + void add_primitive(Ref p_primitive, Operation p_operation = OPERATION_UNION, Transform3D p_xform = Transform3D(), float p_vertex_snap = 0.001); + + Ref create_trimesh_shape() const; + // Ref create_convex_shape(); + + Ref commit(const Ref &p_existing, bool p_generate_tangents = false); + AABB get_aabb() const; +}; + +VARIANT_ENUM_CAST(CSGTool::Operation) + +#endif // CSG_TOOL_H diff --git a/modules/csg/doc_classes/CSGBox3D.xml b/modules/csg/doc_classes/CSGBox3D.xml index 5bb1c4e75b73..b80272d3c747 100644 --- a/modules/csg/doc_classes/CSGBox3D.xml +++ b/modules/csg/doc_classes/CSGBox3D.xml @@ -11,9 +11,6 @@ - - The material used to render the box. - The box's width, height and depth. diff --git a/modules/csg/doc_classes/CSGBoxShape3D.xml b/modules/csg/doc_classes/CSGBoxShape3D.xml new file mode 100644 index 000000000000..3e4a719af395 --- /dev/null +++ b/modules/csg/doc_classes/CSGBoxShape3D.xml @@ -0,0 +1,19 @@ + + + + A CSG Box shape. + + + + + + + + + + The box's width, height and depth. + + + + + diff --git a/modules/csg/doc_classes/CSGCylinder3D.xml b/modules/csg/doc_classes/CSGCylinder3D.xml index bfd2a5d5f2df..86741e006a4c 100644 --- a/modules/csg/doc_classes/CSGCylinder3D.xml +++ b/modules/csg/doc_classes/CSGCylinder3D.xml @@ -17,18 +17,12 @@ The height of the cylinder. - - The material used to render the cylinder. - The radius of the cylinder. The number of sides of the cylinder, the higher this number the more detail there will be in the cylinder. - - If [code]true[/code] the normals of the cylinder are set to give a smooth effect making the cylinder seem rounded. If [code]false[/code] the cylinder will have a flat shaded look. - diff --git a/modules/csg/doc_classes/CSGCylinderShape3D.xml b/modules/csg/doc_classes/CSGCylinderShape3D.xml new file mode 100644 index 000000000000..03d24ab51467 --- /dev/null +++ b/modules/csg/doc_classes/CSGCylinderShape3D.xml @@ -0,0 +1,28 @@ + + + + A CSG Cylinder shape. + + + + + + + + + + If [code]true[/code] a cone is created, the [member radius] will only apply to one side. + + + The height of the cylinder. + + + The radius of the cylinder. + + + The number of sides of the cylinder, the higher this number the more detail there will be in the cylinder. + + + + + diff --git a/modules/csg/doc_classes/CSGMesh3D.xml b/modules/csg/doc_classes/CSGMesh3D.xml index 5fa8427843bf..449f4f66f2e8 100644 --- a/modules/csg/doc_classes/CSGMesh3D.xml +++ b/modules/csg/doc_classes/CSGMesh3D.xml @@ -11,9 +11,6 @@ - - The [Material] used in drawing the CSG shape. - The [Mesh] resource to use as a CSG shape. [b]Note:[/b] When using an [ArrayMesh], avoid meshes with vertex normals unless a flat shader is required. By default, CSGMesh will ignore the mesh's vertex normals and use a smooth shader calculated using the faces' normals. If a flat shader is required, ensure that all faces' vertex normals are parallel. diff --git a/modules/csg/doc_classes/CSGMeshShape3D.xml b/modules/csg/doc_classes/CSGMeshShape3D.xml new file mode 100644 index 000000000000..452dee12e9af --- /dev/null +++ b/modules/csg/doc_classes/CSGMeshShape3D.xml @@ -0,0 +1,19 @@ + + + + A CSG Mesh shape that uses a mesh resource. + + + + + + + + + + The [Mesh] resource to use as a CSG shape. + + + + + diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml index 5309cde9561a..6a4166b784b5 100644 --- a/modules/csg/doc_classes/CSGPolygon3D.xml +++ b/modules/csg/doc_classes/CSGPolygon3D.xml @@ -14,14 +14,11 @@ When [member mode] is [constant MODE_DEPTH], the depth of the extrusion. - - Material to use for the resulting mesh. The UV maps the top half of the material to the extruded shape (U along the length of the extrusions and V around the outline of the [member polygon]), the bottom-left quarter to the front end face, and the bottom-right quarter to the back end face. - The [member mode] used to extrude the [member polygon]. - When [member mode] is [constant MODE_PATH], by default, the top half of the [member material] is stretched along the entire length of the extruded shape. If [code]false[/code] the top half of the material is repeated every step of the extrusion. + When [member mode] is [constant MODE_PATH], by default, the top half of the [member CSGPrimitive3D.material] is stretched along the entire length of the extruded shape. If [code]false[/code] the top half of the material is repeated every step of the extrusion. When [member mode] is [constant MODE_PATH], the path interval or ratio of path points to extrusions. @@ -41,9 +38,6 @@ The point array that defines the 2D polygon that is extruded. - - If [code]true[/code], applies smooth shading to the extrusions. - When [member mode] is [constant MODE_SPIN], the total number of degrees the [member polygon] is rotated when extruding. diff --git a/modules/csg/doc_classes/CSGPolygonShape3D.xml b/modules/csg/doc_classes/CSGPolygonShape3D.xml new file mode 100644 index 000000000000..883cb88665e5 --- /dev/null +++ b/modules/csg/doc_classes/CSGPolygonShape3D.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + Extrusion depth when [member mode] is [constant MODE_DEPTH]. + + + Extrusion mode. + + + If [code]true[/code] the u component of our uv will continuously increase in unison with the distance traveled along our path when [member mode] is [constant MODE_PATH]. + + + + + Interval at which a new extrusion slice is added along the path when [member mode] is [constant MODE_PATH]. + + + If [code]true[/code] the start and end of our path are joined together ensuring there is no seam when [member mode] is [constant MODE_PATH]. + + + The method by which each slice is rotated along the path when [member mode] is [constant MODE_PATH]. + + + When [member mode] is [constant MODE_PATH], the [Curve3D] object used to extrude the [member polygon]. + + + Point array that defines the shape that we'll extrude. + + + Degrees to rotate our extrusion for each slice when [member mode] is [constant MODE_SPIN]. + + + Number of extrusion when [member mode] is [constant MODE_SPIN]. + + + + + Curve3D is extruded to [member depth]. + + + Curve3D is extruded by rotating it around an axis. + + + Curve3D is extruded along the path set in [member path_curve]. + + + Slice is not rotated. + + + Slice is rotated around the up vector of the path. + + + Slice is rotate to match the path exactly. + + + diff --git a/modules/csg/doc_classes/CSGPrimitive3D.xml b/modules/csg/doc_classes/CSGPrimitive3D.xml index 31b7360fac6f..e427ca435b08 100644 --- a/modules/csg/doc_classes/CSGPrimitive3D.xml +++ b/modules/csg/doc_classes/CSGPrimitive3D.xml @@ -14,6 +14,10 @@ Invert the faces of the mesh. + + + + diff --git a/modules/csg/doc_classes/CSGPrimitiveShape3D.xml b/modules/csg/doc_classes/CSGPrimitiveShape3D.xml new file mode 100644 index 000000000000..7b19738ddbb2 --- /dev/null +++ b/modules/csg/doc_classes/CSGPrimitiveShape3D.xml @@ -0,0 +1,23 @@ + + + + Base class for CSG primitives. + + + + + + + + + + Invert the faces of the mesh. + + + + + + + + + diff --git a/modules/csg/doc_classes/CSGSphere3D.xml b/modules/csg/doc_classes/CSGSphere3D.xml index 4d5b3be0992e..6e7484a5fdd9 100644 --- a/modules/csg/doc_classes/CSGSphere3D.xml +++ b/modules/csg/doc_classes/CSGSphere3D.xml @@ -11,9 +11,6 @@ - - The material used to render the sphere. - Number of vertical slices for the sphere. @@ -23,9 +20,6 @@ Number of horizontal slices for the sphere. - - If [code]true[/code] the normals of the sphere are set to give a smooth effect making the sphere seem rounded. If [code]false[/code] the sphere will have a flat shaded look. - diff --git a/modules/csg/doc_classes/CSGSphereShape3D.xml b/modules/csg/doc_classes/CSGSphereShape3D.xml new file mode 100644 index 000000000000..e4e473806321 --- /dev/null +++ b/modules/csg/doc_classes/CSGSphereShape3D.xml @@ -0,0 +1,25 @@ + + + + A CSG Sphere shape. + + + + + + + + + + Number of vertical slices for the sphere. + + + Radius of the sphere. + + + Number of horizontal slices for the sphere. + + + + + diff --git a/modules/csg/doc_classes/CSGTorus3D.xml b/modules/csg/doc_classes/CSGTorus3D.xml index abe3eab9132d..b2d7e38458db 100644 --- a/modules/csg/doc_classes/CSGTorus3D.xml +++ b/modules/csg/doc_classes/CSGTorus3D.xml @@ -14,9 +14,6 @@ The inner radius of the torus. - - The material used to render the torus. - The outer radius of the torus. @@ -26,9 +23,6 @@ The number of slices the torus is constructed of. - - If [code]true[/code] the normals of the torus are set to give a smooth effect making the torus seem rounded. If [code]false[/code] the torus will have a flat shaded look. - diff --git a/modules/csg/doc_classes/CSGTorusShape3D.xml b/modules/csg/doc_classes/CSGTorusShape3D.xml new file mode 100644 index 000000000000..d4f6bdffaca3 --- /dev/null +++ b/modules/csg/doc_classes/CSGTorusShape3D.xml @@ -0,0 +1,28 @@ + + + + A CSG Torus shape. + + + + + + + + + + The inner radius of the torus. + + + The outer radius of the torus. + + + The number of edges each ring of the torus is constructed of. + + + The number of slices the torus is constructed of. + + + + + diff --git a/modules/csg/icons/CSGBoxShape3D.svg b/modules/csg/icons/CSGBoxShape3D.svg new file mode 100644 index 000000000000..fb15ec2db5d4 --- /dev/null +++ b/modules/csg/icons/CSGBoxShape3D.svg @@ -0,0 +1 @@ + diff --git a/modules/csg/icons/CSGCapsuleShape3D.svg b/modules/csg/icons/CSGCapsuleShape3D.svg new file mode 100644 index 000000000000..625f3b8e148e --- /dev/null +++ b/modules/csg/icons/CSGCapsuleShape3D.svg @@ -0,0 +1 @@ + diff --git a/modules/csg/icons/CSGCylinderShape3D.svg b/modules/csg/icons/CSGCylinderShape3D.svg new file mode 100644 index 000000000000..758a26f8515f --- /dev/null +++ b/modules/csg/icons/CSGCylinderShape3D.svg @@ -0,0 +1 @@ + diff --git a/modules/csg/icons/CSGMeshShape3D.svg b/modules/csg/icons/CSGMeshShape3D.svg new file mode 100644 index 000000000000..010c539d4172 --- /dev/null +++ b/modules/csg/icons/CSGMeshShape3D.svg @@ -0,0 +1 @@ + diff --git a/modules/csg/icons/CSGPolygonShape3D.svg b/modules/csg/icons/CSGPolygonShape3D.svg new file mode 100644 index 000000000000..e746edb65ec5 --- /dev/null +++ b/modules/csg/icons/CSGPolygonShape3D.svg @@ -0,0 +1 @@ + diff --git a/modules/csg/icons/CSGSphereShape3D.svg b/modules/csg/icons/CSGSphereShape3D.svg new file mode 100644 index 000000000000..8618b7b40d36 --- /dev/null +++ b/modules/csg/icons/CSGSphereShape3D.svg @@ -0,0 +1 @@ + diff --git a/modules/csg/icons/CSGTorusShape3D.svg b/modules/csg/icons/CSGTorusShape3D.svg new file mode 100644 index 000000000000..4909c4782e85 --- /dev/null +++ b/modules/csg/icons/CSGTorusShape3D.svg @@ -0,0 +1 @@ + diff --git a/modules/csg/register_types.cpp b/modules/csg/register_types.cpp index a47390c2b2b4..2655cf112ef3 100644 --- a/modules/csg/register_types.cpp +++ b/modules/csg/register_types.cpp @@ -46,6 +46,15 @@ void register_csg_types() { GDREGISTER_CLASS(CSGPolygon3D); GDREGISTER_CLASS(CSGCombiner3D); + GDREGISTER_VIRTUAL_CLASS(CSGPrimitiveShape3D); + GDREGISTER_CLASS(CSGMeshShape3D); + GDREGISTER_CLASS(CSGBoxShape3D); + GDREGISTER_CLASS(CSGCylinderShape3D); + GDREGISTER_CLASS(CSGSphereShape3D); + GDREGISTER_CLASS(CSGTorusShape3D); + GDREGISTER_CLASS(CSGPolygonShape3D); + GDREGISTER_CLASS(CSGTool); + #ifdef TOOLS_ENABLED EditorPlugins::add_by_type(); #endif