Skip to content

Commit

Permalink
Add CSG2D for boolean operations
Browse files Browse the repository at this point in the history
Adds nodes and functions for 2D CSG boolean operations.
  • Loading branch information
smix8 committed Dec 12, 2024
1 parent a372214 commit b28ad82
Show file tree
Hide file tree
Showing 34 changed files with 3,728 additions and 0 deletions.
12 changes: 12 additions & 0 deletions modules/csg_2d/SCsub
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *

Import("env")
Import("env_modules")

env_csg_2d = env_modules.Clone()

# Godot's own source files
env_csg_2d.add_source_files(env.modules_sources, "*.cpp")
if env.editor_build:
env_csg_2d.add_source_files(env.modules_sources, "editor/*.cpp")
23 changes: 23 additions & 0 deletions modules/csg_2d/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
def can_build(env, platform):
return True


def configure(env):
pass


def get_doc_classes():
return [
"CSGCapsule2D",
"CSGCircle2D",
"CSGCombiner2D",
"CSGMesh2D",
"CSGPolygon2D",
"CSGPrimitive2D",
"CSGShape2D",
"CSGRectangle2D",
]


def get_doc_path():
return "doc_classes"
86 changes: 86 additions & 0 deletions modules/csg_2d/csg_2d.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**************************************************************************/
/* csg_2d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#include "csg_2d.h"

void CSGBrush2D::copy_from(const CSGBrush2D &p_brush, const Transform2D &p_xform) {
outlines.resize(p_brush.outlines.size());

for (uint32_t i = 0; i < outlines.size(); i++) {
for (uint32_t j = 0; j < outlines[i].vertices.size(); j++) {
outlines[i].vertices[j] = p_xform.xform(p_brush.outlines[i].vertices[j]);
}
}

poly_paths.clear();

for (Clipper2Lib::PathsD::size_type i = 0; i < p_brush.poly_paths.size(); ++i) {
const Clipper2Lib::PathD &path = p_brush.poly_paths[i];

Clipper2Lib::PathD poly_path;

for (Clipper2Lib::PathsD::size_type j = 0; j < path.size(); ++j) {
Point2 vertex = Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y));
vertex = p_xform.xform(vertex);
poly_path.push_back(Clipper2Lib::PointD(vertex.x, vertex.y));
}
poly_paths.push_back(poly_path);
}

_regen_outline_rects();
}

void CSGBrush2D::build_from_outlines(const LocalVector<Outline> &p_outlines) {
poly_paths.clear();

Clipper2Lib::ClipperD clipper_D;
clipper_D.PreserveCollinear(false);

for (uint32_t i = 0; i < p_outlines.size(); i++) {
const LocalVector<Vector2> &vertices = p_outlines[i].vertices;
Clipper2Lib::PathD subject_path;
subject_path.reserve(vertices.size());
for (const Vector2 &vertex : vertices) {
Clipper2Lib::PointD point = Clipper2Lib::PointD(vertex.x, vertex.y);
subject_path.emplace_back(point);
}

Clipper2Lib::PathsD subject_paths;
subject_paths.push_back(subject_path);

if (p_outlines[i].is_hole) {
clipper_D.AddClip(subject_paths);
} else {
clipper_D.AddSubject(subject_paths);
}
}

clipper_D.Execute(Clipper2Lib::ClipType::Union, Clipper2Lib::FillRule::NonZero, poly_paths);
}
68 changes: 68 additions & 0 deletions modules/csg_2d/csg_2d.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**************************************************************************/
/* csg_2d.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#ifndef CSG_2D_H
#define CSG_2D_H

#include "core/math/rect2.h"
#include "core/math/transform_2d.h"
#include "core/templates/local_vector.h"

#include "thirdparty/clipper2/include/clipper2/clipper.h"

struct CSGBrush2D {
struct Outline {
LocalVector<Vector2> vertices;
bool is_hole = false;
Rect2 rect;
};

LocalVector<Outline> outlines;

inline void _regen_outline_rects() {
for (uint32_t i = 0; i < outlines.size(); i++) {
outlines[i].rect = Rect2();
if (outlines[i].vertices.size() > 0) {
outlines[i].rect.position = outlines[i].vertices[0];
for (uint32_t j = 1; j < outlines[i].vertices.size(); j++) {
outlines[i].rect.expand_to(outlines[i].vertices[j]);
}
}
}
}

Clipper2Lib::PathsD poly_paths;

void copy_from(const CSGBrush2D &p_brush, const Transform2D &p_xform);

void build_from_outlines(const LocalVector<Outline> &p_outlines);
};

#endif // CSG_2D_H
146 changes: 146 additions & 0 deletions modules/csg_2d/csg_capsule_2d.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**************************************************************************/
/* csg_capsule_2d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#include "csg_capsule_2d.h"

#include "core/math/geometry_2d.h"
#include "scene/resources/world_2d.h"
#include "servers/physics_server_2d.h"

#include "thirdparty/misc/polypartition.h"

#ifdef DEBUG_ENABLED
Rect2 CSGCapsule2D::_edit_get_rect() const {
Rect2 rect = Rect2(Vector2(-radius, -height * 0.5 - radius), Vector2(radius, height * 0.5 + radius) * 2.0);
rect.position -= rect.size * 0.3;
rect.size += rect.size * 0.6;
return rect;
}

bool CSGCapsule2D::_edit_use_rect() const {
return true;
}

bool CSGCapsule2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
return Geometry2D::is_point_in_polygon(p_point, _get_vertices());
}
#endif // DEBUG_ENABLED

CSGBrush2D *CSGCapsule2D::_build_brush() {
CSGBrush2D *new_brush = memnew(CSGBrush2D);

LocalVector<CSGBrush2D::Outline> &outlines = new_brush->outlines;
outlines.resize(1);
CSGBrush2D::Outline &outline = outlines[0];

const Vector<Vector2> &cached_vertices = _get_vertices();

LocalVector<Vector2> &vertices = outline.vertices;

vertices.resize(cached_vertices.size() + 1);

for (int i = 0; i < cached_vertices.size(); i++) {
vertices[i] = cached_vertices[i];
}
vertices[cached_vertices.size()] = cached_vertices[0];

brush_outlines.resize(1);
brush_outlines[0] = vertices;

new_brush->build_from_outlines(outlines);

return new_brush;
}

Vector<Vector2> CSGCapsule2D::_get_vertices() const {
if (vertices_cache_dirty) {
vertices_cache_dirty = false;

vertices_cache.resize(corner_segments * 4 + 2);
int vertices_index = 0;

const real_t turn_step = Math_TAU / float(corner_segments * 4);
for (int i = 0; i < corner_segments * 4; i++) {
Vector2 ofs = Vector2(0, (i > corner_segments && i <= corner_segments * 3) ? -height * 0.5 + radius : height * 0.5 - radius);

vertices_cache.write[vertices_index++] = Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius + ofs;
if (i == corner_segments || i == corner_segments * 3) {
vertices_cache.write[vertices_index++] = Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius - ofs;
}
}
}

return vertices_cache;
}

void CSGCapsule2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGCapsule2D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &CSGCapsule2D::get_radius);

ClassDB::bind_method(D_METHOD("set_height", "height"), &CSGCapsule2D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &CSGCapsule2D::get_height);

ClassDB::bind_method(D_METHOD("set_corner_segments", "corner_segments"), &CSGCapsule2D::set_corner_segments);
ClassDB::bind_method(D_METHOD("get_corner_segments"), &CSGCapsule2D::get_corner_segments);

ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp,suffix:m"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp,suffix:m"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "corner_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_corner_segments", "get_corner_segments");
}

void CSGCapsule2D::set_radius(const float p_radius) {
radius = p_radius;
vertices_cache_dirty = true;
_make_dirty();
}

float CSGCapsule2D::get_radius() const {
return radius;
}

void CSGCapsule2D::set_height(const float p_height) {
height = p_height;
vertices_cache_dirty = true;
_make_dirty();
}

float CSGCapsule2D::get_height() const {
return height;
}

void CSGCapsule2D::set_corner_segments(const int p_corner_segments) {
corner_segments = p_corner_segments > 0 ? p_corner_segments : 1;
vertices_cache_dirty = true;
_make_dirty();
}

int CSGCapsule2D::get_corner_segments() const {
return corner_segments;
}
Loading

0 comments on commit b28ad82

Please sign in to comment.