Skip to content

Commit

Permalink
Merge pull request #5 from iiMidknightii/presets
Browse files Browse the repository at this point in the history
Added extrusion profiles to PathExtrude3D
  • Loading branch information
iiMidknightii authored Jan 19, 2025
2 parents d191cbf + 554d205 commit bd024d8
Show file tree
Hide file tree
Showing 27 changed files with 4,707 additions and 3,221 deletions.
34 changes: 16 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
# ![](addons/PathMesh3D/icons/PathMesh3DIcon.png) PathMesh3D
A set of simple Godot 3D nodes for extruding and instancing 3D meshes along a Path3D. Implemented as a GDExtension in C++ for speed.

## Download
### From Godot Asset Library in the Editor
Click the `AssetLib` button at the top of the Godot editor and search for `PathMesh3D`. From there, you can download it directly into your project. You only need the [addons][addons] folder for the extension to work.
## Installation
### From Godot Asset Library in the Editor
Click the `AssetLib` button at the top of the Godot editor and search for `PathMesh3D`. When prompted where to install it, you can select only the folder named "addons". If you wish to modify or recompile the addon, then you'll need to include the "godot-cpp" and "src" folders along with the "SConstruct" file.

> [!WARNING]
> If you are downloading this directly into your project, you will want to uncheck the files in the base folder, as they may overwrite your own if you aren't careful.
### From Godot Asset Library Web
Head over to [the PathMesh3D page on the asset library website](https://godotengine.org/asset-library/asset) and click the download button.
Head over to [the PathMesh3D page on the asset library website](https://godotengine.org/asset-library/asset) and click the download button. Unzip the download into a location of your choosing. To put the addon in your project, just copy the "addons" folder into the project directory.

### From GitHub.com
You can download the full repository for PathMesh3D [here](https://github.com/iiMidknightii/PathMesh3D). You can clone this repository by doing `git clone https://github.com/iiMidknightii/PathMesh3D.git` in the directory of your choosing. If you want to compile your own binaries this is the best option.


## Installation
Once you have the files downloaded, there are a couple paths you could take for installation. The [addons/PathMesh3D](addons/PathMesh3D/) folder can be directly copied into your project. It already has the binaries for debug and release builds on Windows and Linux.

If you wish to build the binaries from source, you'd need to also copy the [src](./src/), [doc_classes](./doc_classes/), and [godot-cpp](./godot-cpp/) folders along with the [SConstruct](./Sconstruct) file. [This page will tell you how to build the extension from source using the `scons` command](https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/gdextension_cpp_example.html).

The actual GDExtension classes are in the [addons/PathMesh3D/bin](addons/PathMesh3D/bin) binaries and are added to Godot via [addons/PathMesh3D/ez_pid.gdextension](addons/PathMesh3D/ez_pid.gdextension).
You can download the full repository for PathMesh3D [here](https://github.com/iiMidknightii/PathMesh3D). You can clone this repository by doing `git clone https://github.com/iiMidknightii/PathMesh3D.git` in the directory of your choosing. If you want to compile your own binaries this is the best option. To put the addon in your project, just copy the "addons" folder into the project directory.

## Tutorial
> [!NOTE]
> `PathMesh3D` and `PathExtrude3D` both have a plugin button to generate baked meshes and collision shapes. In the editor, you can enable this button by enabling the `PathMesh3D` plugin.
### PathMesh3D
`PathMesh3D` is a great node if you want to take a 3D model and "tile" or "repeat" it along a `Path3D` node within your scene. The `Mesh` model provided will be duplicated along its Z axis according to the settings chosen for each surface. Since each surface has its own independent settings, there is a high degree of customization available.

Simply add the `PathMesh3D` node to the scene, set its `path_3d` property to a `Path3D` node, and set its `mesh` property to any `Mesh` derived resource. From there, you can tweak the settings for each surface to get your desired effect.
Simply add the `PathMesh3D` node to the scene, set its `path_3d` property to a `Path3D` node, and set its `mesh` property to any `Mesh` derived resource. From there, you can tweak the settings for each surface to get your desired effect. If you want to create a permanent, separate node as a "snapshot" of the extruded mesh, hit the `PathMesh3D` button on the editor toolbar and select "Bake Mesh". You can also create collision shapes and static bodies similarly to `MeshInstance3D` with that same button.

![](screenshots/PathMesh3D.png)

### PathExtrude3D
`PathExtrude3D` works similarly to the `CSGPolygon` node when in path mode. First, define the cross section of the mesh that will be extruded in the `cross_section` property. This property stores the points as a `PackedVector2Array`. Then, set the `path_3d` property to any `Path3D` node in the scene. From there, the cross section will be extruded according to the settings you have chosen.
`PathExtrude3D` works similarly to the `CSGPolygon` node when in path mode. First, you define the cross section using the `profile` property. This property uses any one of the `PathExtrudeProfileBase` classes, including rectangular, circular, etc. You can also create your own profile by creating a custom script inheriting from `PathExtrudeProfileBase` and overriding the virtual `_generate_cross_section` method. This resource stores the points as a `PackedVector2Array` that will be used by the `PathExtrude3D`. Next, set the `path_3d` property to any `Path3D` node in the scene. From there, the cross section will be extruded according to the settings you have chosen.

![](screenshots/PathExtrude3D.png)

Expand All @@ -37,8 +35,8 @@ Simply add the `PathMesh3D` node to the scene, set its `path_3d` property to a `

![](screenshots/PathMultiMesh3D.png)

## Tagged Releases
* 1.0 - intial release, targets godot-4.3
## Latest Release
* 1.1 - Added collision object generation and the path extrusion profiles for easier extrusion

## Contributing
Feel free to leave any feedback, bug reports, and contributions to the repository at [https://github.com/iiMidknightii/PathMesh3D](https://github.com/iiMidknightii/PathMesh3D).
Feel free to leave any feedback, suggestions, bug reports, and contributions to the repository at [https://github.com/iiMidknightii/PathMesh3D](https://github.com/iiMidknightii/PathMesh3D).
2 changes: 1 addition & 1 deletion SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ bin_folder = "addons/" + plugin + "/bin/"

# tweak this if you want to use different folders, or more folders, to store your source code in.
env.Append(CPPPATH=["src/" + plugin + "/"])
sources = Glob("src/" + plugin + "/*.cpp")
sources = Glob("src/" + plugin + "/*.cpp") + Glob("src/" + plugin + "/extrude_profiles/*.cpp")

if env["target"] in ["editor", "template_debug"]:
try:
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 3 additions & 1 deletion addons/PathMesh3D/plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ extends EditorPlugin
const PathMesh3DOptions := preload("res://addons/PathMesh3D/scripts/path_mesh_3d_options.gd")
const PathMesh3DOptionsScene := preload("res://addons/PathMesh3D/scenes/path_mesh_3d_options.tscn")

var _mesh_editor_button: PathMesh3DOptions = PathMesh3DOptionsScene.instantiate()
var _mesh_editor_button: PathMesh3DOptions


func _enter_tree() -> void:
_mesh_editor_button = PathMesh3DOptionsScene.instantiate()
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, _mesh_editor_button)
_mesh_editor_button.hide()

func _exit_tree() -> void:
remove_control_from_container(CONTAINER_SPATIAL_EDITOR_MENU, _mesh_editor_button)
_mesh_editor_button.queue_free()


func _handles(object: Object) -> bool:
Expand Down
10 changes: 5 additions & 5 deletions doc_classes/PathExtrude3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
</brief_description>
<description>
The PathExtrude3D node is used to create a 3D mesh by extruding a 2D shape along a 3D path.
This can be useful for creating complex 3D models such as pipes, cables, or any other object
that follows a specific path in 3D space.
This can be useful for creating simple 3D models such as pipes, cables, or any other object that follows has a uniform cross section along specific path in 3D space.
The cross section of the extruded object is determined by a [PathExtrudeProfileBase] object in [param profile].
</description>
<tutorials>
</tutorials>
Expand Down Expand Up @@ -50,9 +50,6 @@
</method>
</methods>
<members>
<member name="cross_section" type="PackedVector2Array" setter="set_cross_section" getter="get_cross_section" default="PackedVector2Array()">
An array of Vector2 points that define the cross-section to be extruded along the [member path].
</member>
<member name="end_cap_mode" type="int" setter="set_end_cap_mode" getter="get_end_cap_mode" enum="PathExtrude3D.EndCaps" is_bitfield="true" default="3">
The end cap mode for the extrusion. This can be one of the following values:
- [constant END_CAPS_NONE]: No end caps are created.
Expand All @@ -66,6 +63,9 @@
<member name="path_3d" type="Path3D" setter="set_path_3d" getter="get_path_3d">
The 3D path along which the cross-section is extruded.
</member>
<member name="profile" type="PathExtrudeProfileBase" setter="set_profile" getter="get_profile">
The 2D profile to extrude along the path.
</member>
<member name="sample_cubic" type="bool" setter="set_sample_cubic" getter="get_sample_cubic" default="false">
If [code]true[/code], the cross-section is sampled using cubic interpolation. If [code]false[/code], the cross-section is sampled using linear interpolation.
</member>
Expand Down
37 changes: 37 additions & 0 deletions doc_classes/PathExtrudeProfileBase.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="PathExtrudeProfileBase" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
<brief_description>
A base class for defining the cross-sectional profile to extrude in a [PathExtrude3D] node.
</brief_description>
<description>
This serves as the base for other classes that return the extruded profile in [member get_cross_section]. You can extend this class with custom scripts that override [method _generate_cross_section] in order to customize your own profile.
</description>
<tutorials>
</tutorials>
<methods>
<method name="_generate_cross_section" qualifiers="virtual">
<return type="PackedVector2Array" />
<description>
An overrideable, virtual method for defining custom cross-sectional profiles using your own custom code.
[b]Note:[/b] You must use a [code]@tool[/code] script to see the mesh build in the editor.
</description>
</method>
<method name="get_cross_section" qualifiers="const">
<return type="PackedVector2Array" />
<description>
Returns an array of Vector2s defining the cross section profile to be extruded.
</description>
</method>
<method name="queue_update">
<return type="void" />
<description>
Manually updates the cross section and alerts the [PathExtrude3D] to rebuild. This is called automatically, but you can call it manually if you need to.
</description>
</method>
</methods>
<members>
<member name="flip_normals" type="bool" setter="set_flip_normals" getter="get_flip_normals" default="false">
If [code]true[/code], the extruded profile will have its faces flipped, e.g. pipes become hollow tubes.
</member>
</members>
</class>
28 changes: 28 additions & 0 deletions doc_classes/PathExtrudeProfileCircle.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="PathExtrudeProfileCircle" inherits="PathExtrudeProfileBase" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
<brief_description>
A resource to define circular extrusion profiles for [PathExtrude3D] nodes.
</brief_description>
<description>
This resource defines the parameters for the [PathExtrude3D] node to extrude a full or partial circular shape along its path.
</description>
<tutorials>
</tutorials>
<members>
<member name="closed" type="bool" setter="set_closed" getter="get_closed" default="true">
If true, any circle defined with less than [code]TAU[/code] sweep will have an extra flat face to close the mesh.
</member>
<member name="ending_angle" type="float" setter="set_ending_angle" getter="get_ending_angle" default="6.28319">
The angle in which the profile sweep will stop.
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0">
The distance from the center to the edges of the swept circle.
</member>
<member name="segments" type="int" setter="set_segments" getter="get_segments" default="32">
The number of line segments to use when constructing the circle.
</member>
<member name="starting_angle" type="float" setter="set_starting_angle" getter="get_starting_angle" default="0.0">
The angle in which the profile sweep will begin.
</member>
</members>
</class>
19 changes: 19 additions & 0 deletions doc_classes/PathExtrudeProfileManual.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="PathExtrudeProfileManual" inherits="PathExtrudeProfileBase" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
<brief_description>
A resource to define custom [PathExtrude3D] extrusion cross-section profiles.
</brief_description>
<description>
This resource allows the user to define the cross section directly via [PackedVector2Array].
</description>
<tutorials>
</tutorials>
<members>
<member name="closed" type="bool" setter="set_closed" getter="get_closed" default="true">
If [code]true[/code] and [param cross_section] is not already closed, the cross-section provided to the node will have an extra flat face to close the mesh.
</member>
<member name="cross_section" type="PackedVector2Array" setter="set_manual_cross_section" getter="get_manual_cross_section" default="PackedVector2Array()">
The cross-sectional profile to extrude in the [PathExtrude3D].
</member>
</members>
</class>
19 changes: 19 additions & 0 deletions doc_classes/PathExtrudeProfileRect.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="PathExtrudeProfileRect" inherits="PathExtrudeProfileBase" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
<brief_description>
A resource to define a rectangular extrusion profile for [PathExtrude3D].
</brief_description>
<description>
This resource defines the parameters to extrude a rectangle across the path in a [PathExtrude3D] node.
</description>
<tutorials>
</tutorials>
<members>
<member name="rect" type="Rect2" setter="set_rect" getter="get_rect" default="Rect2(0, 0, 0, 0)">
The rectangle to extrude.
</member>
<member name="subdivisions" type="Vector2i" setter="set_subdivisions" getter="get_subdivisions" default="Vector2i(0, 0)">
The number of times in width (x) and height (y) to divide the square for better twisting.
</member>
</members>
</class>
48 changes: 48 additions & 0 deletions src/PathMesh3D/extrude_profiles/path_extrude_profile_circle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "path_extrude_profile_circle.hpp"

using namespace godot;

PackedVector2Array PathExtrudeProfileCircle::_generate_cross_section() {
PackedVector2Array cs;

double swept_angle = ending_angle - starting_angle;
if (swept_angle <= 0.0) {
return cs;
}

double da = swept_angle / double(segments);

cs.resize(segments + 1);
for (uint64_t i = 0; i <= segments; ++i) {
double ang = ending_angle - da * i;
cs[i] = Vector2(Math::sin(ang) * radius, Math::cos(ang) * radius);
}

if (closed && cs[0].distance_squared_to(cs[cs.size() - 1]) > 1.0e-6) {
cs.push_back(cs[0]);
}

return cs;
}

void PathExtrudeProfileCircle::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &PathExtrudeProfileCircle::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &PathExtrudeProfileCircle::get_radius);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,100.0,0.01,or_greater"), "set_radius", "get_radius");

ClassDB::bind_method(D_METHOD("set_starting_angle", "starting_angle"), &PathExtrudeProfileCircle::set_starting_angle);
ClassDB::bind_method(D_METHOD("get_starting_angle"), &PathExtrudeProfileCircle::get_starting_angle);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "starting_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.01,radians_as_degrees"), "set_starting_angle", "get_starting_angle");

ClassDB::bind_method(D_METHOD("set_ending_angle", "ending_angle"), &PathExtrudeProfileCircle::set_ending_angle);
ClassDB::bind_method(D_METHOD("get_ending_angle"), &PathExtrudeProfileCircle::get_ending_angle);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ending_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.01,radians_as_degrees"), "set_ending_angle", "get_ending_angle");

ClassDB::bind_method(D_METHOD("set_closed", "closed"), &PathExtrudeProfileCircle::set_closed);
ClassDB::bind_method(D_METHOD("get_closed"), &PathExtrudeProfileCircle::is_closed);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "closed"), "set_closed", "get_closed");

ClassDB::bind_method(D_METHOD("set_segments", "segments"), &PathExtrudeProfileCircle::set_segments);
ClassDB::bind_method(D_METHOD("get_segments"), &PathExtrudeProfileCircle::get_segments);
ADD_PROPERTY(PropertyInfo(Variant::INT, "segments", PROPERTY_HINT_RANGE, "0,256,1,or_greater"), "set_segments", "get_segments");
}
71 changes: 71 additions & 0 deletions src/PathMesh3D/extrude_profiles/path_extrude_profile_circle.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#ifndef PATH_EXTRUDE_PROFILE_CIRCLE_H
#define PATH_EXTRUDE_PROFILE_CIRCLE_H

#include "path_extrude_profile_base.hpp"

namespace godot {

class PathExtrudeProfileCircle : public PathExtrudeProfileBase {
GDCLASS(PathExtrudeProfileCircle, PathExtrudeProfileBase)

public:
_ALWAYS_INLINE_ void set_radius(const double p_radius) {
if (p_radius != radius) {
radius = p_radius;
queue_update();
}
}
_ALWAYS_INLINE_ double get_radius() const { return radius; }

_ALWAYS_INLINE_ void set_starting_angle(const double p_starting_angle) {
if (p_starting_angle != starting_angle) {
starting_angle = Math::clamp(p_starting_angle, double(0.0), double(Math_TAU));
if (starting_angle > ending_angle) {
ending_angle = starting_angle;
}
queue_update();
}
}
_ALWAYS_INLINE_ double get_starting_angle() const { return starting_angle; }

_ALWAYS_INLINE_ void set_ending_angle(const double p_ending_angle) {
if (p_ending_angle != ending_angle) {
ending_angle = Math::clamp(p_ending_angle, double(0.0), double(Math_TAU));
if (ending_angle < starting_angle) {
starting_angle = ending_angle;
}
queue_update();
}
}
_ALWAYS_INLINE_ double get_ending_angle() const { return ending_angle; }

_ALWAYS_INLINE_ void set_closed(const bool p_closed) {
if (p_closed != closed) {
closed = p_closed;
queue_update();
}
}
_ALWAYS_INLINE_ bool is_closed() const { return closed; }

_ALWAYS_INLINE_ void set_segments(const uint64_t p_segments) {
if (p_segments != segments && p_segments > 1) {
segments = p_segments;
queue_update();
}
}
_ALWAYS_INLINE_ uint64_t get_segments() const { return segments; }

protected:
virtual PackedVector2Array _generate_cross_section() override;
static void _bind_methods();

private:
double radius = 1.0;
double starting_angle = 0.0;
double ending_angle = Math_TAU;
bool closed = true;
uint64_t segments = 32;
};
}

#endif
Loading

0 comments on commit bd024d8

Please sign in to comment.