Commit before damnger
This commit is contained in:
parent
fe1eb58626
commit
14f0200b50
101 changed files with 8087 additions and 15 deletions
21
addons/vizpath/LICENSE
Normal file
21
addons/vizpath/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 markeel
|
||||
|
||||
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.
|
90
addons/vizpath/README.md
Normal file
90
addons/vizpath/README.md
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Visualized Path plugin
|
||||
|
||||
A plugin for Godot 4 to visualize a path
|
||||
|
||||
## Overview
|
||||
|
||||
This plugin is designed to create a 3D mesh to visualize a path. It generates that mesh using
|
||||
an array of spots, where each spot has a normal defining the mesh face at that spot.
|
||||
|
||||
The path created will be directly between each spot and in the order specified. The direction
|
||||
between the spots is direct but the path can be bent based on the normals at each spot.
|
||||
|
||||
An optional tail and head can be placed at the beginning and ending of the path, to provide a
|
||||
decoration.
|
||||
|
||||
## Installation
|
||||
|
||||
The plugin is written in 100% GDScript so no compilation is required and should work on any
|
||||
platform.
|
||||
|
||||
To install it from the Godot Asset Library, within your project:
|
||||
- Select the AssetLib tab
|
||||
- Search for "3D Visualized Path"
|
||||
- Click Download button
|
||||
- After the dialog is presented with files, at least include the "addons" directory
|
||||
- From Project -> Project Settings...
|
||||
- Go to Plugins tab and enable vizpath plugin (not doing this will not let the path be selectable
|
||||
in the 3D view and will not present the spot control points
|
||||
|
||||
There are 3 main directories
|
||||
- addons/vizpath
|
||||
- examples/vizpath
|
||||
- source/vizpath
|
||||
|
||||
When loading as an asset in another project (as opposed to working on this asset) the following files
|
||||
should NOT be imported, they will likely conflict with your project
|
||||
- README.md
|
||||
- LICENSE
|
||||
- icon.svg
|
||||
- icon.svg.import
|
||||
- project.godot
|
||||
|
||||
After using this asset, the examples/vizpath and source/vizpath folders can be removed so as not
|
||||
to pollute your project.
|
||||
|
||||
## Usage
|
||||
|
||||
The typical use of this asset is to create the path from an array of VisualizationSpot resources
|
||||
in response to user input or actions by the AI during game play. The plugin does support manually
|
||||
creating a path in the editor and editing in the 3D view.
|
||||
|
||||
### Programatically through a script
|
||||
|
||||
To change the path, simply construct an array of VisualizationSpot objects (which define the
|
||||
point in local space to the VisualizedPath and the normal in local space). There are changed signals
|
||||
that detect the resource being updated and the path will be reconstructed.
|
||||
|
||||
#### Handling errors
|
||||
|
||||
If the path cannot be constructed (because the spots are too close to create a bend with the curve
|
||||
specified), the underlying mesh will not be created and the error list will be updated. To retrieve
|
||||
these errors a call to get_errors() can be made.
|
||||
|
||||
Based on the properties and how close the spots are allowed to be, you can guarantee that the path will
|
||||
always be constructed. If you allow unrestrained movement of the spots and the normals, you will
|
||||
need to report the error (which is what is done when using the Godot Editor)
|
||||
|
||||
### Directly in the editor
|
||||
|
||||
The VisualizedPath class will appear in the list of nodes when adding a node to a scene. The VisualizedPath
|
||||
class supports editing all the properties through the Inspector and updating
|
||||
the view immediately. It also allows a VisualizationSpot to be moved and its normal rotated using
|
||||
a subgizmo. At least 2 spots must be added. If the path is not valid, a warning will be presented
|
||||
on the VisualizedPath instance.
|
||||
|
||||
### Using subgizmo
|
||||
|
||||
To use the subgizmo, click on the VisualizedPath and in the 3D view click on the yellow cone for
|
||||
one of the spots. The manipulation gizmo changes to that spot and then it can be moved (changing
|
||||
the position of the spot), or rotated (changing the normal at that spot)
|
||||
|
||||
## Examples
|
||||
|
||||
Examples of using the plugin can be found in the "example/vizpath" directory. Feel free to
|
||||
delete this directory for your project.
|
||||
|
||||
## Source
|
||||
|
||||
Blender and Inkscape files used to create the meshes and the icon are included in the "source/vizpath"
|
||||
directory, which can also be removed without impacting its usage.
|
BIN
addons/vizpath/images/path.png
Normal file
BIN
addons/vizpath/images/path.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 811 B |
34
addons/vizpath/images/path.png.import
Normal file
34
addons/vizpath/images/path.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d0ljaahn5bykd"
|
||||
path="res://.godot/imported/path.png-9af8c406cc1948c301cd69299f1231f1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/vizpath/images/path.png"
|
||||
dest_files=["res://.godot/imported/path.png-9af8c406cc1948c301cd69299f1231f1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
12
addons/vizpath/mesh/spot.mtl
Normal file
12
addons/vizpath/mesh/spot.mtl
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Blender 3.5.0 MTL File: 'spot.blend'
|
||||
# www.blender.org
|
||||
|
||||
newmtl Material
|
||||
Ns 250.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
66
addons/vizpath/mesh/spot.obj
Normal file
66
addons/vizpath/mesh/spot.obj
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Blender 3.5.0
|
||||
# www.blender.org
|
||||
mtllib spot.mtl
|
||||
o Cube
|
||||
v 0.250000 -0.250000 -0.056205
|
||||
v 0.250000 -0.250000 -0.000000
|
||||
v 0.250000 0.250000 -0.056205
|
||||
v 0.250000 0.250000 0.000000
|
||||
v -0.250000 -0.250000 -0.056205
|
||||
v -0.250000 -0.250000 -0.000000
|
||||
v -0.250000 0.250000 -0.056205
|
||||
v -0.250000 0.250000 0.000000
|
||||
v 0.095182 -0.095182 -0.071294
|
||||
v 0.095182 0.095182 -0.071294
|
||||
v -0.095182 -0.095182 -0.071294
|
||||
v -0.095182 0.095182 -0.071294
|
||||
v 0.000000 0.000000 -0.500000
|
||||
vn -0.0970 -0.0000 -0.9953
|
||||
vn -0.0000 1.0000 -0.0000
|
||||
vn -1.0000 -0.0000 -0.0000
|
||||
vn -0.0000 -0.0000 1.0000
|
||||
vn 1.0000 -0.0000 -0.0000
|
||||
vn -0.0000 -1.0000 -0.0000
|
||||
vn -0.9762 -0.0000 -0.2167
|
||||
vn 0.0970 -0.0000 -0.9953
|
||||
vn -0.0000 -0.0970 -0.9953
|
||||
vn -0.0000 0.0970 -0.9953
|
||||
vn -0.0000 0.9762 -0.2167
|
||||
vn -0.0000 -0.9762 -0.2167
|
||||
vn 0.9762 -0.0000 -0.2167
|
||||
vt 0.625000 0.500000
|
||||
vt 0.375000 0.500000
|
||||
vt 0.625000 0.750000
|
||||
vt 0.375000 0.750000
|
||||
vt 0.625000 0.250000
|
||||
vt 0.125000 0.500000
|
||||
vt 0.375000 0.250000
|
||||
vt 0.625000 0.000000
|
||||
vt 0.625000 1.000000
|
||||
vt 0.375000 1.000000
|
||||
vt 0.375000 0.000000
|
||||
vt 0.125000 0.750000
|
||||
vt 0.625000 0.500000
|
||||
vt 0.625000 0.750000
|
||||
vt 0.625000 0.250000
|
||||
vt 0.625000 0.000000
|
||||
vt 0.625000 1.000000
|
||||
vt 0.625000 0.250000
|
||||
vt 0.625000 1.000000
|
||||
vt 0.625000 0.500000
|
||||
vt 0.625000 0.750000
|
||||
s 0
|
||||
usemtl Material
|
||||
f 5/5/1 7/8/1 12/16/1 11/15/1
|
||||
f 4/4/2 3/3/2 7/9/2 8/10/2
|
||||
f 8/11/3 7/8/3 5/5/3 6/7/3
|
||||
f 6/6/4 2/2/4 4/4/4 8/12/4
|
||||
f 2/2/5 1/1/5 3/3/5 4/4/5
|
||||
f 6/7/6 5/5/6 1/1/6 2/2/6
|
||||
f 11/15/7 12/16/7 13/18/7
|
||||
f 3/3/8 1/1/8 9/13/8 10/14/8
|
||||
f 1/1/9 5/5/9 11/15/9 9/13/9
|
||||
f 7/9/10 3/3/10 10/14/10 12/17/10
|
||||
f 12/17/11 10/14/11 13/19/11
|
||||
f 9/13/12 11/15/12 13/20/12
|
||||
f 10/14/13 9/13/13 13/21/13
|
25
addons/vizpath/mesh/spot.obj.import
Normal file
25
addons/vizpath/mesh/spot.obj.import
Normal file
|
@ -0,0 +1,25 @@
|
|||
[remap]
|
||||
|
||||
importer="wavefront_obj"
|
||||
importer_version=1
|
||||
type="Mesh"
|
||||
uid="uid://b2tsm0h3jdgvy"
|
||||
path="res://.godot/imported/spot.obj-f00a82a9756945bf04ad3b38eae5a1b4.mesh"
|
||||
|
||||
[deps]
|
||||
|
||||
files=["res://.godot/imported/spot.obj-f00a82a9756945bf04ad3b38eae5a1b4.mesh"]
|
||||
|
||||
source_file="res://addons/vizpath/mesh/spot.obj"
|
||||
dest_files=["res://.godot/imported/spot.obj-f00a82a9756945bf04ad3b38eae5a1b4.mesh", "res://.godot/imported/spot.obj-f00a82a9756945bf04ad3b38eae5a1b4.mesh"]
|
||||
|
||||
[params]
|
||||
|
||||
generate_tangents=true
|
||||
generate_lods=true
|
||||
generate_shadow_mesh=true
|
||||
generate_lightmap_uv2=false
|
||||
generate_lightmap_uv2_texel_size=0.2
|
||||
scale_mesh=Vector3(1, 1, 1)
|
||||
offset_mesh=Vector3(0, 0, 0)
|
||||
force_disable_mesh_compression=false
|
7
addons/vizpath/plugin.cfg
Normal file
7
addons/vizpath/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="vizpath"
|
||||
description=""
|
||||
author="markeel"
|
||||
version="1.0.2"
|
||||
script="vizpath.gd"
|
36
addons/vizpath/resources/spot.gd
Normal file
36
addons/vizpath/resources/spot.gd
Normal file
|
@ -0,0 +1,36 @@
|
|||
@tool
|
||||
extends Resource
|
||||
class_name VisualizationSpot
|
||||
|
||||
## The VisualizationSpot class is a resource that allows the definition
|
||||
## of a spot for a VisualizedPath.
|
||||
##
|
||||
## A VisualizationSpot contains a point (local space) and a normal (local space)
|
||||
## that identifies where the path starts, ends, or turns.
|
||||
|
||||
## The point in local space where the path originates, terminates, or turns
|
||||
@export var point : Vector3 :
|
||||
set(p):
|
||||
point = p
|
||||
emit_changed()
|
||||
|
||||
## The normal defining the up direction of the plane this path will be in at this
|
||||
## point.
|
||||
@export var normal : Vector3 :
|
||||
set(n):
|
||||
normal = n
|
||||
emit_changed()
|
||||
|
||||
func _init():
|
||||
point = Vector3.ZERO
|
||||
normal = Vector3.FORWARD
|
||||
|
||||
func is_equal(other):
|
||||
if not point.is_equal_approx(other.point):
|
||||
return false
|
||||
if not normal.is_equal_approx(other.normal):
|
||||
return false
|
||||
return true
|
||||
|
||||
func _to_string():
|
||||
return "%s/%s" % [point, normal]
|
1
addons/vizpath/resources/spot.gd.uid
Normal file
1
addons/vizpath/resources/spot.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://d28qf56fgf0e4
|
8
addons/vizpath/resources/spot.tres
Normal file
8
addons/vizpath/resources/spot.tres
Normal file
|
@ -0,0 +1,8 @@
|
|||
[gd_resource type="Resource" script_class="VisualizationSpot" load_steps=2 format=3 uid="uid://xujlcvwtxkk8"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/vizpath/resources/spot.gd" id="1_yfhm3"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_yfhm3")
|
||||
point = Vector3(0, 0, 0)
|
||||
normal = Vector3(0, 0, -1)
|
87
addons/vizpath/resources/viz_head.gd
Normal file
87
addons/vizpath/resources/viz_head.gd
Normal file
|
@ -0,0 +1,87 @@
|
|||
@tool
|
||||
extends Resource
|
||||
class_name VizHead
|
||||
|
||||
# The VizHead class is a resource that allows the creation
|
||||
# of a simple arrow head at the end of the path showing the
|
||||
# final destination of the path.
|
||||
#
|
||||
# It is designed to be used interchangeably with any resource
|
||||
# that defines an apply method with the signature defined
|
||||
# below.
|
||||
|
||||
## The width of the arrow head as a percentage of the
|
||||
## path width it is associated with.
|
||||
@export var width_factor : float = 2.0 :
|
||||
set(w):
|
||||
width_factor = w
|
||||
emit_changed()
|
||||
|
||||
## The length of the arrow head as a percentage of the
|
||||
## path width it is associated with
|
||||
@export var length_factor : float = 1.20 :
|
||||
set(l):
|
||||
length_factor = l
|
||||
emit_changed()
|
||||
|
||||
## The get_offset function will be called to shift the end of the last
|
||||
## segment to accomodate the head mesh
|
||||
##
|
||||
## This class can be overridden to provide a custom head to the path
|
||||
## by defining a resource that has an apply method with the following
|
||||
## definition:
|
||||
##
|
||||
## The [code]left[/code] and [code]right[/code] positions at the end of the path, where the
|
||||
## V texture coordinate is 0.0 for [code]left[/code] and 1.0 for [code]right[/code].
|
||||
## The [code]normal[/code] and the [code]direction[/code] define the position of the face and the
|
||||
## direction that the path is going.
|
||||
func get_offset(left : Vector3, right : Vector3, normal : Vector3, direction : Vector3) -> float:
|
||||
var segment := right - left
|
||||
var segment_len := segment.length()
|
||||
var arrow_point_len := segment_len * length_factor
|
||||
return arrow_point_len
|
||||
|
||||
## The apply function will be called when the path
|
||||
## mesh is created.
|
||||
##
|
||||
## This class can be overridden to provide a custom head to the path
|
||||
## by defining a resource that has an apply method with the following
|
||||
## definition:
|
||||
##
|
||||
## The [code]mesh_node[/code] parameter is the mesh instance of the visualized path.
|
||||
## Its interior mesh can be updated (typically with [SurfaceTool]).
|
||||
## The [code]u[/code] parameter is the U texture coordinate of the [code]left[/code] and
|
||||
## [code]right[/code] positions at the end of the path, where the V texture coordinate
|
||||
## is 0.0 for [code]left[/code] and 1.0 for [code]right[/code]. The [code]normal[/code] and
|
||||
## the [code]direction[/code] define the position of the face and the direction that the
|
||||
## path is going. The [code]path_mat[/code] is the material that was used
|
||||
## in the original path definition [member VisualizedPath.path_mat].
|
||||
func apply(mesh_node : MeshInstance3D, u : float, left : Vector3, right : Vector3, normal : Vector3, direction : Vector3, path_mat : Material):
|
||||
var segment := right - left
|
||||
var mid_segment := segment / 2.0
|
||||
var binormal := direction.cross(normal).normalized()
|
||||
var segment_len := segment.length()
|
||||
var arrow_base_len := segment_len * width_factor
|
||||
var extent_len := (arrow_base_len - segment_len)/2.0
|
||||
var arrow_left_extent := left - extent_len * binormal
|
||||
var arrow_right_extent := right + extent_len * binormal
|
||||
var arrow_point_len := segment_len * length_factor
|
||||
var arrow_fwd_extent := left + mid_segment + arrow_point_len * direction
|
||||
var v_extent = width_factor * 0.5
|
||||
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
st.set_color(Color(1, 0, 0))
|
||||
st.set_normal(normal)
|
||||
st.set_material(path_mat)
|
||||
|
||||
st.set_uv(Vector2(u, 0.5 + v_extent))
|
||||
st.add_vertex(arrow_right_extent)
|
||||
|
||||
st.set_uv(Vector2(u, 0.5 - v_extent))
|
||||
st.add_vertex(arrow_left_extent)
|
||||
|
||||
st.set_uv(Vector2(u+arrow_point_len, 0.5))
|
||||
st.add_vertex(arrow_fwd_extent)
|
||||
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
1
addons/vizpath/resources/viz_head.gd.uid
Normal file
1
addons/vizpath/resources/viz_head.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cfan7h7m4fi7q
|
8
addons/vizpath/resources/viz_head.tres
Normal file
8
addons/vizpath/resources/viz_head.tres
Normal file
|
@ -0,0 +1,8 @@
|
|||
[gd_resource type="Resource" script_class="VizHead" load_steps=2 format=3 uid="uid://ca6a3fjxcmbhc"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/vizpath/resources/viz_head.gd" id="1_7yqvb"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_7yqvb")
|
||||
width_factor = 2.0
|
||||
length_factor = 1.2
|
80
addons/vizpath/resources/viz_tail.gd
Normal file
80
addons/vizpath/resources/viz_tail.gd
Normal file
|
@ -0,0 +1,80 @@
|
|||
@tool
|
||||
extends Resource
|
||||
class_name VizTail
|
||||
|
||||
# The VizTail class is a resource that allows the creation
|
||||
# of a simple rounded tail at the beginning of the path showing the
|
||||
# start of the path.
|
||||
#
|
||||
# It is designed to be used interchangeably with any resource
|
||||
# that defines an apply method with the signature defined
|
||||
# below.
|
||||
|
||||
## The number of segments in the semi-sphere of tail
|
||||
@export var num_segs := 16 :
|
||||
set(s):
|
||||
num_segs = s
|
||||
emit_changed()
|
||||
|
||||
## The get_offset function will be called to shift the beginning of the first
|
||||
## segment to accomodate the tail mesh
|
||||
##
|
||||
## This class can be overridden to provide a custom head to the path
|
||||
## by defining a resource that has an apply method with the following
|
||||
## definition:
|
||||
##
|
||||
## The [code]left[/code] and [code]right[/code] positions at the start of the path, where the
|
||||
## V texture coordinate is 0.0 for [code]left[/code] and 1.0 for [code]right[/code].
|
||||
## The [code]normal[/code] and the [code]direction[/code] define the position of the face and the
|
||||
## direction that the path is going.
|
||||
func get_offset(left : Vector3, right : Vector3, normal : Vector3, direction : Vector3) -> float:
|
||||
return 0.0
|
||||
|
||||
## The apply function will be called when the path
|
||||
## mesh is created.
|
||||
##
|
||||
## This class can be overridden to provide a custom tail to the path
|
||||
## by defining a resource that has an apply method with the following
|
||||
## definition:
|
||||
##
|
||||
## The [code]mesh_node[/code] parameter is the mesh instance of the visualized path.
|
||||
## Its interior mesh can be updated (typically with [SurfaceTool]).
|
||||
## The [code]u[/code] parameter is the U texture coordinate of the [code]left[/code] and
|
||||
## [code]right[/code] positions at the end of the path, where the V texture coordinate
|
||||
## is 0.0 for [code]left[/code] and 1.0 for [code]right[/code]. The [code]normal[/code] and
|
||||
## the [code]direction[/code] define the position of the face and the direction that the
|
||||
## path is going. The [code]path_mat[/code] is the material that was used
|
||||
## in the original path definition [member VisualizedPath.path_mat].
|
||||
func apply(mesh_node : MeshInstance3D, u : float, left : Vector3, right : Vector3, normal : Vector3, direction : Vector3, path_mat : Material):
|
||||
var segment := right - left
|
||||
var mid_segment := segment / 2.0
|
||||
var mid_point := left + mid_segment
|
||||
var binormal := direction.cross(normal).normalized()
|
||||
var segment_len := segment.length()
|
||||
var mid_segment_len := mid_segment.length()
|
||||
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
st.set_color(Color(1, 0, 0))
|
||||
st.set_normal(normal)
|
||||
var rotation_dir := -signf(direction.dot(mid_segment.rotated(normal, PI/2.0)))
|
||||
var last_pos := right
|
||||
var last_pu := u
|
||||
var last_pv := 1.0
|
||||
for i in range(num_segs):
|
||||
var weight := rotation_dir * float(i+1) / float(num_segs)
|
||||
var angle := weight * PI
|
||||
var pv := (1.0 + cos(angle))/2.0
|
||||
var pu := u - rotation_dir * mid_segment_len * sin(angle)
|
||||
var pos := mid_point + mid_segment.rotated(normal, angle)
|
||||
st.set_uv(Vector2(u, 0.5))
|
||||
st.add_vertex(mid_point)
|
||||
st.set_uv(Vector2(last_pu, last_pv))
|
||||
st.add_vertex(last_pos)
|
||||
st.set_uv(Vector2(pu, pv))
|
||||
st.add_vertex(pos)
|
||||
last_pos = pos
|
||||
last_pu = pu
|
||||
last_pv = pv
|
||||
st.set_material(path_mat)
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
1
addons/vizpath/resources/viz_tail.gd.uid
Normal file
1
addons/vizpath/resources/viz_tail.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://b6jnvwm4145s3
|
7
addons/vizpath/resources/viz_tail.tres
Normal file
7
addons/vizpath/resources/viz_tail.tres
Normal file
|
@ -0,0 +1,7 @@
|
|||
[gd_resource type="Resource" script_class="VizTail" load_steps=2 format=3 uid="uid://dnaxoh1d4t82c"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/vizpath/resources/viz_tail.gd" id="1_npnpb"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_npnpb")
|
||||
num_segs = 12
|
202
addons/vizpath/utilities/viz_bend.gd
Normal file
202
addons/vizpath/utilities/viz_bend.gd
Normal file
|
@ -0,0 +1,202 @@
|
|||
extends RefCounted
|
||||
class_name VizBend
|
||||
|
||||
var _error : String
|
||||
|
||||
var _bend_normal : Vector3
|
||||
var _bend_plane : Plane
|
||||
var _begin_on_bend_plane : Vector3
|
||||
var _end_on_bend_plane : Vector3
|
||||
var _intersect_data : OptVector3
|
||||
|
||||
var _plane_join_point : Vector3
|
||||
|
||||
var _bend_side : float
|
||||
var _curve_side : float
|
||||
|
||||
var _bend_begin_triangle : VizTriangle
|
||||
var _bend_end_triangle : VizTriangle
|
||||
|
||||
var _arc_begin_mid : Vector3
|
||||
var _arc_end_mid : Vector3
|
||||
|
||||
var _begin : Vector3
|
||||
var _begin_dir : Vector3
|
||||
var _end : Vector3
|
||||
var _end_dir : Vector3
|
||||
|
||||
class OptVector3:
|
||||
var vector : Vector3
|
||||
func _init(p_data : Vector3):
|
||||
vector = p_data
|
||||
|
||||
func _init(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
|
||||
if not _is_bendable(p_begin, p_end):
|
||||
return
|
||||
|
||||
_calc_bend_normal(p_begin, p_end)
|
||||
if not _calc_intersection_point(p_begin, p_end):
|
||||
return
|
||||
_calc_join_point(p_begin, p_end)
|
||||
_calc_bend_side(p_begin, p_end)
|
||||
_calc_triangles(p_begin, p_end, p_width, p_bend_lip)
|
||||
_calc_arc_plane()
|
||||
_begin = _bend_begin_triangle.get_leg_mid()
|
||||
if not _is_in_order(p_begin.point, _begin, _plane_join_point):
|
||||
_error = "bend point is too close to beginning for %s to %s with lip %s" % [p_begin.point, p_end.point, p_bend_lip]
|
||||
return
|
||||
_end = _bend_end_triangle.get_leg_mid()
|
||||
if not _is_in_order(_plane_join_point, _end, p_end.point):
|
||||
_error = "bend point is too close to end for %s to %s with lip %s" % [p_begin.point, p_end.point, p_bend_lip]
|
||||
return
|
||||
|
||||
func _to_string():
|
||||
return "%s->%s->%s" % [_begin, _plane_join_point, _end]
|
||||
|
||||
# ---------------------------------------------
|
||||
# Public Methoeds
|
||||
|
||||
func get_error() -> String:
|
||||
return _error
|
||||
|
||||
func is_invalid() -> bool:
|
||||
if _error != "":
|
||||
return true
|
||||
return false
|
||||
|
||||
func get_begin() -> Vector3:
|
||||
return _begin
|
||||
|
||||
func get_end() -> Vector3:
|
||||
return _end
|
||||
|
||||
func is_angled() -> bool:
|
||||
return not is_zero_approx(_bend_side)
|
||||
|
||||
func is_angled_against_curve() -> bool:
|
||||
if _bend_side * _curve_side < 0:
|
||||
return true
|
||||
return false
|
||||
|
||||
func get_half_leg_width() -> Vector3:
|
||||
return (_bend_begin_triangle.leg_left - _bend_begin_triangle.leg_right) / 2.0
|
||||
|
||||
func get_half_bend_width() -> Vector3:
|
||||
return (_bend_begin_triangle.bend_left - _bend_begin_triangle.bend_right) / 2.0
|
||||
|
||||
func get_begin_triangle() -> VizTriangle:
|
||||
return _bend_begin_triangle
|
||||
|
||||
func get_end_triangle() -> VizTriangle:
|
||||
return _bend_end_triangle
|
||||
|
||||
func get_shift_width() -> Vector3:
|
||||
return _arc_begin_mid - _bend_begin_triangle.get_bend_mid() + _bend_end_triangle.get_bend_mid() - _arc_end_mid
|
||||
|
||||
func get_shift_offset() -> Vector3:
|
||||
return _arc_begin_mid - _bend_begin_triangle.get_bend_mid()
|
||||
|
||||
func get_arc_begin_point() -> Vector3:
|
||||
return _arc_begin_mid
|
||||
|
||||
func get_arc_end_point() -> Vector3:
|
||||
return _arc_end_mid
|
||||
|
||||
func get_arc_point(weight : float, sharpness : float) -> Vector3:
|
||||
return _curve_point(_arc_begin_mid, _plane_join_point, _arc_end_mid, weight, sharpness)
|
||||
|
||||
# ---------------------------------------------
|
||||
# Private Methods
|
||||
|
||||
func _curve_point(p0 : Vector3, p1 : Vector3, p2 : Vector3, t : float, s : float) -> Vector3:
|
||||
var p := p1 + pow(1-t, 2.0) * s * (p0-p1) + pow(t, 2.0) * s * (p2-p1)
|
||||
return p
|
||||
|
||||
func _is_in_order(b : Vector3, m : Vector3, e : Vector3) -> bool:
|
||||
return (e-m).normalized().dot((m-b).normalized()) > 0
|
||||
|
||||
func _is_bendable(p_begin : VisualizationSpot, p_end : VisualizationSpot) -> bool:
|
||||
var dir := (p_end.point - p_begin.point).normalized()
|
||||
var begin_norm_sign := signf(dir.dot(p_begin.normal))
|
||||
var end_norm_sign := signf(dir.dot(p_end.normal))
|
||||
if begin_norm_sign == end_norm_sign:
|
||||
_error = "%s cannot be twisted into %s" % [ p_begin.normal, p_end.normal ]
|
||||
return false
|
||||
_curve_side = end_norm_sign
|
||||
return true
|
||||
|
||||
func _calc_bend_normal(p_begin : VisualizationSpot, p_end : VisualizationSpot):
|
||||
var middle = p_begin.point + (p_end.point - p_begin.point) / 2.0
|
||||
_bend_normal = p_begin.normal.cross(p_end.normal).normalized()
|
||||
_bend_plane = Plane(_bend_normal, middle)
|
||||
|
||||
func _calc_intersection_point(p_begin : VisualizationSpot, p_end : VisualizationSpot) -> bool:
|
||||
_begin_on_bend_plane = _bend_plane.project(p_begin.point)
|
||||
_end_on_bend_plane = _bend_plane.project(p_end.point)
|
||||
var dir := _end_on_bend_plane - _begin_on_bend_plane
|
||||
var begin_normal_on_bend_plane := _bend_plane.project(p_begin.point + p_begin.normal) - _begin_on_bend_plane
|
||||
var end_normal_on_bend_plane := _bend_plane.project(p_end.point + p_end.normal) - _end_on_bend_plane
|
||||
var begin_bend_binormal := begin_normal_on_bend_plane.cross(dir)
|
||||
var begin_bend_tangent := begin_bend_binormal.cross(begin_normal_on_bend_plane).normalized()
|
||||
var end_bend_binormal := end_normal_on_bend_plane.cross(dir)
|
||||
var end_bend_tangent := end_bend_binormal.cross(end_normal_on_bend_plane).normalized()
|
||||
_intersect_data = _intersect(_begin_on_bend_plane, begin_bend_tangent, _end_on_bend_plane, end_bend_tangent, _bend_normal)
|
||||
if _intersect_data == null:
|
||||
_error = "intersection failed unexpectedly: normal %s at %s is parallel with normal %s at %s" % [ begin_normal_on_bend_plane, _begin_on_bend_plane, end_normal_on_bend_plane, _end_on_bend_plane ]
|
||||
return false
|
||||
return true
|
||||
|
||||
func _calc_join_point(p_begin : VisualizationSpot, p_end : VisualizationSpot):
|
||||
var begin_bend_from_intersect := p_begin.point + _intersect_data.vector - _begin_on_bend_plane
|
||||
var end_bend_from_intersect := p_end.point + _intersect_data.vector - _end_on_bend_plane
|
||||
var begin_intersect_distance := begin_bend_from_intersect.distance_to(p_begin.point)
|
||||
var end_intersect_distance := end_bend_from_intersect.distance_to(p_end.point)
|
||||
var begin_to_end_on_bend_distance := end_bend_from_intersect.distance_to(begin_bend_from_intersect)
|
||||
var begin_to_end_intersect_join_numerator := begin_to_end_on_bend_distance * begin_intersect_distance / end_intersect_distance
|
||||
var begin_to_end_intersect_join_denominator := 1.0 + begin_intersect_distance / end_intersect_distance
|
||||
var begin_on_end_intersect_join_distance := begin_to_end_intersect_join_numerator / begin_to_end_intersect_join_denominator
|
||||
var begin_bend_dir := begin_bend_from_intersect - p_begin.point
|
||||
var end_bend_dir := end_bend_from_intersect - p_begin.point
|
||||
var begin_to_end_normal := begin_bend_dir.cross(end_bend_dir)
|
||||
var begin_to_end_binormal := begin_to_end_normal.cross(_bend_normal)
|
||||
var begin_on_end_intersect_side = signf(end_bend_dir.dot(begin_to_end_binormal))
|
||||
_plane_join_point = begin_bend_from_intersect - begin_on_end_intersect_side * begin_on_end_intersect_join_distance * _bend_normal
|
||||
|
||||
func _calc_bend_side(p_begin : VisualizationSpot, p_end : VisualizationSpot):
|
||||
_begin_dir = Vector3(_plane_join_point - p_begin.point).normalized()
|
||||
_end_dir = Vector3(p_end.point - _plane_join_point).normalized()
|
||||
_bend_side = signf(_bend_normal.dot(_end_dir))
|
||||
|
||||
func _calc_triangles(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
|
||||
var begin_binormal := _begin_dir.cross(p_begin.normal).normalized()
|
||||
var end_binormal := _end_dir.cross(p_end.normal).normalized()
|
||||
_bend_begin_triangle = VizTriangle.new()
|
||||
_bend_begin_triangle.update(_begin_dir, _plane_join_point, begin_binormal, p_width, p_bend_lip, _bend_normal, _bend_side, _curve_side)
|
||||
_bend_end_triangle = VizTriangle.new()
|
||||
_bend_end_triangle.update(-_end_dir, _plane_join_point, end_binormal, p_width, p_bend_lip, _bend_normal, -_bend_side, _curve_side)
|
||||
|
||||
func _calc_arc_plane():
|
||||
var begin_mid := _bend_begin_triangle.get_leg_mid()
|
||||
var end_mid := _bend_end_triangle.get_leg_mid()
|
||||
var arc_center := _plane_join_point - (_plane_join_point - begin_mid) - (_plane_join_point - end_mid)
|
||||
var arc_plane := Plane(_bend_normal, arc_center)
|
||||
_arc_begin_mid = arc_plane.project(_bend_begin_triangle.get_bend_mid())
|
||||
_arc_end_mid = arc_plane.project(_bend_end_triangle.get_bend_mid())
|
||||
|
||||
func _intersect(a : Vector3, a_norm : Vector3, b : Vector3, b_norm : Vector3, plane_norm : Vector3) -> OptVector3:
|
||||
var seg := b - a
|
||||
var seg_len := seg.length()
|
||||
var seg_norm := seg.normalized()
|
||||
var alpha_sign := signf(b_norm.cross(a_norm).dot(plane_norm))
|
||||
var cos_alpha := b_norm.dot(a_norm)
|
||||
var beta_sign := signf(seg_norm.cross(b_norm).dot(plane_norm))
|
||||
var cos_beta := seg_norm.dot(b_norm)
|
||||
var intersect_side := alpha_sign * beta_sign
|
||||
var sin_alpha := sqrt(1 - cos_alpha*cos_alpha)
|
||||
var sin_beta := sqrt(1 - cos_beta*cos_beta)
|
||||
if is_equal_approx(sin_alpha, 0.0):
|
||||
return null
|
||||
var q := seg_len * sin_beta / sin_alpha
|
||||
var p := a - intersect_side * q * a_norm
|
||||
return OptVector3.new(p)
|
||||
|
1
addons/vizpath/utilities/viz_bend.gd.uid
Normal file
1
addons/vizpath/utilities/viz_bend.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://g81gb6sdvfbe
|
120
addons/vizpath/utilities/viz_compound_leg.gd
Normal file
120
addons/vizpath/utilities/viz_compound_leg.gd
Normal file
|
@ -0,0 +1,120 @@
|
|||
extends RefCounted
|
||||
class_name VizCompoundLeg
|
||||
|
||||
var _error : String = ""
|
||||
var _begin : VisualizationSpot
|
||||
var _end : VisualizationSpot
|
||||
var _width : float
|
||||
var _bend_lip : float
|
||||
var _intermediate_spot : VisualizationSpot = null
|
||||
var _norm_distance : float
|
||||
var _max_join_point_on_begin : Vector3
|
||||
var _max_join_point_on_end : Vector3
|
||||
|
||||
func _init(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
|
||||
_begin = p_begin
|
||||
_end = p_end
|
||||
_width = p_width
|
||||
_bend_lip = p_bend_lip
|
||||
_calc_intermediate_point()
|
||||
# _calc_max_offset()
|
||||
_calc_intermediate_normal()
|
||||
|
||||
func _to_string():
|
||||
return "CLEG(%s to %s through %s)" % [_begin, _end, _intermediate_spot]
|
||||
|
||||
# ---------------------------------------------
|
||||
# Public Methoeds
|
||||
|
||||
func is_invalid() -> bool:
|
||||
return _error != ""
|
||||
|
||||
func get_error() -> String:
|
||||
return _error
|
||||
|
||||
func get_begin() -> VisualizationSpot:
|
||||
return _begin
|
||||
|
||||
func get_begin_ray() -> Vector3:
|
||||
return (_max_join_point_on_begin - _begin.point).normalized()
|
||||
|
||||
func adjust_begin(distance : float):
|
||||
var new_begin := VisualizationSpot.new()
|
||||
new_begin.point = _begin.point + distance * get_begin_ray()
|
||||
new_begin.normal = _begin.normal
|
||||
_begin = new_begin
|
||||
_calc_intermediate_normal()
|
||||
|
||||
func get_end() -> VisualizationSpot:
|
||||
return _end
|
||||
|
||||
func get_end_ray() -> Vector3:
|
||||
return (_end.point - _max_join_point_on_end).normalized()
|
||||
|
||||
func adjust_end(distance : float):
|
||||
var new_end := VisualizationSpot.new()
|
||||
new_end.point = _end.point - distance * get_end_ray()
|
||||
new_end.normal = _end.normal
|
||||
_end = new_end
|
||||
_calc_intermediate_normal()
|
||||
|
||||
var test_mat : Material = load("res://example/common/materials/white.tres")
|
||||
|
||||
func update_mesh(mesh_node : Node3D, u : float, segs : int, sharpness : float, mat : Material) -> float:
|
||||
var leg1 := VizSimpleLeg.new(_begin, _intermediate_spot, _width, _bend_lip)
|
||||
if leg1.is_invalid():
|
||||
print("leg1=%s: error=%s" % [ leg1, leg1.get_error() ])
|
||||
assert(!leg1.is_invalid())
|
||||
var leg2 := VizSimpleLeg.new(_intermediate_spot, _end, _width, _bend_lip)
|
||||
if leg2.is_invalid():
|
||||
print("leg2: error=%s" % leg2.get_error())
|
||||
assert(!leg2.is_invalid())
|
||||
u = leg1.update_mesh(mesh_node, u, segs, sharpness, mat)
|
||||
var left := leg1.get_end_left()
|
||||
var right := leg1.get_end_right()
|
||||
u = leg2.extend_mesh(mesh_node, u, left, right, segs, sharpness, mat)
|
||||
return u
|
||||
|
||||
# ---------------------------------------------
|
||||
# Local Methods
|
||||
|
||||
func _calc_intermediate_point():
|
||||
_intermediate_spot = VisualizationSpot.new()
|
||||
var intermediate_segment := _end.point - _begin.point
|
||||
_intermediate_spot.point = _begin.point + intermediate_segment / 2.0
|
||||
|
||||
func _calc_intermediate_normal():
|
||||
var begin_plane := Plane(_begin.normal, _begin.point)
|
||||
var max_point := begin_plane.project(_intermediate_spot.point)
|
||||
# var mid_point_distance := _intermediate_spot.point.distance_to(max_point)
|
||||
var mid_point_distance := _begin.point.distance_to(max_point)
|
||||
var cur_norm_distance = _bend_lip + VizSimpleLeg.MIN_SEGMENT_LENGTH
|
||||
var dir := (max_point - _begin.point).normalized()
|
||||
var valid := false
|
||||
while cur_norm_distance < mid_point_distance:
|
||||
var data := _check_normal(dir, cur_norm_distance)
|
||||
if data.size() == 3:
|
||||
_max_join_point_on_begin = data[0]
|
||||
_max_join_point_on_end = data[1]
|
||||
_intermediate_spot.normal = data[2]
|
||||
valid = true
|
||||
break
|
||||
cur_norm_distance += 0.01
|
||||
if not valid:
|
||||
_error = "%s is too close to %s to make compund leg with lip %s" % [ _begin.point, _end.point, _bend_lip ]
|
||||
|
||||
func _check_normal(dir : Vector3, norm_distance : float) -> Array:
|
||||
var candidate_segment := _intermediate_spot.point - (_begin.point + norm_distance * dir)
|
||||
var candidate_segment_dir := candidate_segment.normalized()
|
||||
var candidate_normal := ((_begin.normal + _end.normal)/2.0).normalized()
|
||||
var candidate_binormal := candidate_normal.cross(candidate_segment_dir).normalized()
|
||||
var candidate_mid := VisualizationSpot.new()
|
||||
candidate_mid.point = _intermediate_spot.point
|
||||
candidate_mid.normal = candidate_segment_dir.cross(candidate_binormal).normalized()
|
||||
var leg1 := VizSimpleLeg.new(_begin, candidate_mid, _width, _bend_lip)
|
||||
if not leg1.is_invalid():
|
||||
var leg2 := VizSimpleLeg.new(candidate_mid, _end, _width, _bend_lip)
|
||||
if not leg2.is_invalid():
|
||||
return [ leg1.get_leg_bend_begin(), leg2.get_leg_bend_end(), candidate_mid.normal ]
|
||||
return []
|
||||
|
1
addons/vizpath/utilities/viz_compound_leg.gd.uid
Normal file
1
addons/vizpath/utilities/viz_compound_leg.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://ce4k02xlfpr00
|
102
addons/vizpath/utilities/viz_mid.gd
Normal file
102
addons/vizpath/utilities/viz_mid.gd
Normal file
|
@ -0,0 +1,102 @@
|
|||
extends RefCounted
|
||||
class_name VizMid
|
||||
|
||||
var _seg1 : VizSegment
|
||||
var _seg2 : VizSegment
|
||||
var _width : float
|
||||
var _inner_radius : float
|
||||
|
||||
var _rotation_point : VisualizationSpot
|
||||
var _angle : float
|
||||
var _error : String
|
||||
|
||||
func _init(p_seg1 : VizSegment, p_seg2 : VizSegment, p_stroke_width : float, p_inner_radius : float):
|
||||
_seg1 = p_seg1
|
||||
_seg2 = p_seg2
|
||||
_width = p_stroke_width
|
||||
_inner_radius = p_inner_radius
|
||||
assert(p_seg1.get_end().is_equal(p_seg2.get_begin()))
|
||||
_calc_rotation_point()
|
||||
var center_distance := calc_center_distance(p_stroke_width, p_inner_radius)
|
||||
var turn_distance := calc_turn_distance(center_distance, _angle)
|
||||
p_seg1.adjust_end(turn_distance)
|
||||
if p_seg1.is_invalid():
|
||||
return
|
||||
p_seg2.adjust_begin(turn_distance)
|
||||
if p_seg2.is_invalid():
|
||||
return
|
||||
|
||||
# ---------------------------------------------
|
||||
# Public Methoeds
|
||||
|
||||
func is_invalid() -> bool:
|
||||
return _error != ""
|
||||
|
||||
func get_error() -> String:
|
||||
return _error
|
||||
|
||||
func update_mesh(_mesh_instance : MeshInstance3D, u : float, num_segs : int, mat : Material):
|
||||
return _create_fan(_mesh_instance, u, num_segs, mat)
|
||||
|
||||
# ---------------------------------------------
|
||||
# Private Methods
|
||||
|
||||
func _calc_rotation_point():
|
||||
var seg1_ray := _seg1.get_end_ray()
|
||||
var seg2_ray := _seg2.get_begin_ray()
|
||||
_angle = seg1_ray.angle_to(seg2_ray)
|
||||
var join_point := _seg1.get_end()
|
||||
var plane_normal := seg1_ray.cross(seg2_ray).normalized()
|
||||
if plane_normal == Vector3.ZERO:
|
||||
plane_normal = join_point.normal
|
||||
var binormal = plane_normal.cross(seg1_ray).normalized()
|
||||
var d := calc_center_distance(_width, _inner_radius)
|
||||
var e := calc_turn_distance(d, _angle)
|
||||
_rotation_point = VisualizationSpot.new()
|
||||
_rotation_point.point = join_point.point - e * seg1_ray + d * binormal
|
||||
_rotation_point.normal = plane_normal
|
||||
|
||||
func _create_fan(mesh_node : Node3D, u : float, num_segs : int, mat : Material) -> float:
|
||||
var radius := _width/2.0 + _inner_radius
|
||||
var arc_len := absf(_angle) * radius
|
||||
|
||||
var begin_half_width := _seg1.get_end_binormal() * _width/2.0
|
||||
var end_half_width := _seg2.get_begin_binormal() * _width/2.0
|
||||
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
|
||||
st.set_color(Color(1, 0, 0))
|
||||
st.set_normal(_seg1.get_end().normal)
|
||||
|
||||
st.set_uv(Vector2(u, 1))
|
||||
st.add_vertex(_seg1.get_end().point + begin_half_width)
|
||||
st.set_uv(Vector2(u, 0))
|
||||
st.add_vertex(_seg1.get_end().point - begin_half_width)
|
||||
|
||||
var outer_base := _seg1.get_end().point + begin_half_width - _rotation_point.point
|
||||
var inner_base := _seg1.get_end().point - begin_half_width - _rotation_point.point
|
||||
for i in range(1, num_segs-1):
|
||||
var cur_angle : float = _angle * float(i) / float(num_segs-1)
|
||||
var cur_arc_len = absf(cur_angle) * radius
|
||||
var inner := _rotation_point.point + inner_base.rotated(_rotation_point.normal, cur_angle)
|
||||
var outer := _rotation_point.point + outer_base.rotated(_rotation_point.normal, cur_angle)
|
||||
st.set_uv(Vector2(u+cur_arc_len, 1))
|
||||
st.add_vertex(outer)
|
||||
st.set_uv(Vector2(u+cur_arc_len, 0))
|
||||
st.add_vertex(inner)
|
||||
|
||||
st.set_uv(Vector2(u+arc_len, 1))
|
||||
st.add_vertex(_seg2.get_begin().point + end_half_width)
|
||||
st.set_uv(Vector2(u+arc_len, 0))
|
||||
st.add_vertex(_seg2.get_begin().point - end_half_width)
|
||||
|
||||
st.set_material(mat)
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
||||
|
||||
return u + arc_len
|
||||
|
||||
static func calc_center_distance(stroke_width : float, inner_radius : float) -> float:
|
||||
return stroke_width / 2.0 + inner_radius
|
||||
|
||||
static func calc_turn_distance(center_distance : float, angle : float) -> float:
|
||||
return center_distance * tan(abs(angle)/2.0)
|
1
addons/vizpath/utilities/viz_mid.gd.uid
Normal file
1
addons/vizpath/utilities/viz_mid.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://b4vo8e10344ls
|
86
addons/vizpath/utilities/viz_segment.gd
Normal file
86
addons/vizpath/utilities/viz_segment.gd
Normal file
|
@ -0,0 +1,86 @@
|
|||
extends RefCounted
|
||||
class_name VizSegment
|
||||
|
||||
var _simple_leg : VizSimpleLeg = null
|
||||
var _compound_leg : VizCompoundLeg = null
|
||||
|
||||
func _init(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
|
||||
_simple_leg = VizSimpleLeg.new(p_begin, p_end, p_width, p_bend_lip)
|
||||
if _simple_leg.is_invalid():
|
||||
_compound_leg = VizCompoundLeg.new(p_begin, p_end, p_width, p_bend_lip)
|
||||
|
||||
func is_invalid() -> bool:
|
||||
if _simple_leg.is_invalid():
|
||||
if _compound_leg.is_invalid():
|
||||
return true
|
||||
return false
|
||||
|
||||
func get_error() -> String:
|
||||
if _simple_leg.is_invalid():
|
||||
if _compound_leg.is_invalid():
|
||||
return _compound_leg.get_error()
|
||||
|
||||
return _simple_leg.get_error()
|
||||
return ""
|
||||
|
||||
func get_begin() -> VisualizationSpot:
|
||||
if not _simple_leg.is_invalid():
|
||||
return _simple_leg.get_begin()
|
||||
return _compound_leg.get_begin()
|
||||
|
||||
func get_begin_ray() -> Vector3:
|
||||
if not _simple_leg.is_invalid():
|
||||
return _simple_leg.get_begin_ray()
|
||||
return _compound_leg.get_begin_ray()
|
||||
|
||||
func get_begin_binormal() -> Vector3:
|
||||
if not _simple_leg.is_invalid():
|
||||
return _simple_leg.get_begin_ray().cross(_simple_leg.get_begin().normal)
|
||||
return _compound_leg.get_begin_ray().cross(_compound_leg.get_begin().normal)
|
||||
|
||||
func adjust_begin(distance : float):
|
||||
if not _simple_leg.is_invalid():
|
||||
_simple_leg.adjust_begin(distance)
|
||||
if _simple_leg.is_invalid():
|
||||
_compound_leg = VizCompoundLeg.new(_simple_leg.get_begin(), _simple_leg.get_end(), _simple_leg._width, _simple_leg._bend_lip)
|
||||
else:
|
||||
_compound_leg.adjust_begin(distance)
|
||||
|
||||
func get_end() -> VisualizationSpot:
|
||||
if not _simple_leg.is_invalid():
|
||||
return _simple_leg.get_end()
|
||||
return _compound_leg.get_end()
|
||||
|
||||
func get_end_ray() -> Vector3:
|
||||
if not _simple_leg.is_invalid():
|
||||
return _simple_leg.get_end_ray()
|
||||
return _compound_leg.get_end_ray()
|
||||
|
||||
func get_end_binormal() -> Vector3:
|
||||
if not _simple_leg.is_invalid():
|
||||
return _simple_leg.get_end_ray().cross(_simple_leg.get_end().normal)
|
||||
return _compound_leg.get_end_ray().cross(_compound_leg.get_end().normal)
|
||||
|
||||
func adjust_end(distance : float):
|
||||
if not _simple_leg.is_invalid():
|
||||
_simple_leg.adjust_end(distance)
|
||||
if _simple_leg.is_invalid():
|
||||
_compound_leg = VizCompoundLeg.new(_simple_leg.get_begin(), _simple_leg.get_end(), _simple_leg._width, _simple_leg._bend_lip)
|
||||
else:
|
||||
_compound_leg.adjust_end(distance)
|
||||
|
||||
func update_mesh(mesh_instance : MeshInstance3D, u : float, segs : int, sharpness : float, mat : Material) -> float:
|
||||
if _simple_leg.is_invalid():
|
||||
if _compound_leg.is_invalid():
|
||||
return u
|
||||
u = _compound_leg.update_mesh(mesh_instance, u, segs, sharpness, mat)
|
||||
else:
|
||||
u = _simple_leg.update_mesh(mesh_instance, u, segs, sharpness, mat)
|
||||
return u
|
||||
|
||||
func _to_string():
|
||||
if not _simple_leg.is_invalid():
|
||||
return _simple_leg._to_string()
|
||||
if not _compound_leg.is_invalid():
|
||||
return _compound_leg._to_string()
|
||||
return "invalid segment"
|
1
addons/vizpath/utilities/viz_segment.gd.uid
Normal file
1
addons/vizpath/utilities/viz_segment.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://dx5hc2jl5ow46
|
307
addons/vizpath/utilities/viz_simple_leg.gd
Normal file
307
addons/vizpath/utilities/viz_simple_leg.gd
Normal file
|
@ -0,0 +1,307 @@
|
|||
extends RefCounted
|
||||
class_name VizSimpleLeg
|
||||
|
||||
var _error : String = ""
|
||||
var _begin : VisualizationSpot
|
||||
var _end : VisualizationSpot
|
||||
var _width : float
|
||||
var _bend_lip : float
|
||||
var _bend : VizBend
|
||||
|
||||
const MIN_SEGMENT_LENGTH=0.01
|
||||
const EPSILON=0.00001
|
||||
|
||||
func _init(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
|
||||
_begin = p_begin
|
||||
_end = p_end
|
||||
_width = p_width
|
||||
_bend_lip = p_bend_lip
|
||||
if not _has_valid_normal():
|
||||
return
|
||||
if _is_straight():
|
||||
return
|
||||
_bend = VizBend.new(_begin, _end, p_width, p_bend_lip)
|
||||
if not _bend.is_invalid():
|
||||
_check_offsets(_begin, _end)
|
||||
|
||||
# ---------------------------------------------
|
||||
# Public Methods
|
||||
|
||||
func get_error() -> String:
|
||||
if _error != "":
|
||||
return _error
|
||||
if _bend != null:
|
||||
return _bend.get_error()
|
||||
return ""
|
||||
|
||||
func is_invalid() -> bool:
|
||||
if _error != "":
|
||||
return true
|
||||
if _bend != null:
|
||||
return _bend.is_invalid()
|
||||
return false
|
||||
|
||||
func extend_mesh(mesh_node : Node3D, u : float, left : Vector3, right : Vector3, segs : int, sharpness : float, mat : Material) -> float:
|
||||
if _bend == null:
|
||||
u = _extend_straight_leg(mesh_node, u, left, right, _begin.point, _end.point, _begin.normal, mat)
|
||||
else:
|
||||
u = _extend_straight_leg(mesh_node, u, left, right, _begin.point, _bend.get_begin(), _begin.normal, mat)
|
||||
if _bend.is_angled():
|
||||
u = _create_begin_triangle(mesh_node, u, _begin.normal, _bend.get_begin_triangle(), mat)
|
||||
u = _create_bend(mesh_node, u, segs, sharpness, mat)
|
||||
if _bend.is_angled():
|
||||
u = _create_end_triangle(mesh_node, u, _end.normal, _bend.get_end_triangle(), mat)
|
||||
u = _create_straight_leg(mesh_node, u, _bend.get_end(), _end.point, _end.normal, mat)
|
||||
return u
|
||||
|
||||
func update_mesh(mesh_node : Node3D, u : float, segs : int, sharpness : float, mat : Material) -> float:
|
||||
if _bend == null:
|
||||
u = _create_straight_leg(mesh_node, u, _begin.point, _end.point, _begin.normal, mat)
|
||||
else:
|
||||
u = _create_straight_leg(mesh_node, u, _begin.point, _bend.get_begin(), _begin.normal, mat)
|
||||
if _bend.is_angled():
|
||||
u = _create_begin_triangle(mesh_node, u, _begin.normal, _bend.get_begin_triangle(), mat)
|
||||
u = _create_bend(mesh_node, u, segs, sharpness, mat)
|
||||
if _bend.is_angled():
|
||||
u = _create_end_triangle(mesh_node, u, _end.normal, _bend.get_end_triangle(), mat)
|
||||
u = _create_straight_leg(mesh_node, u, _bend.get_end(), _end.point, _end.normal, mat)
|
||||
return u
|
||||
|
||||
func get_begin() -> VisualizationSpot:
|
||||
return _begin
|
||||
|
||||
func get_leg_bend_begin() -> Vector3:
|
||||
if _bend == null:
|
||||
return (_end.point - _begin.point)/2.0
|
||||
return _bend.get_begin_triangle().get_leg_mid()
|
||||
|
||||
func get_begin_ray() -> Vector3:
|
||||
if _bend == null:
|
||||
return (_end.point - _begin.point).normalized()
|
||||
return (_bend.get_begin() - _begin.point).normalized()
|
||||
|
||||
func adjust_begin(distance : float):
|
||||
var new_begin := VisualizationSpot.new()
|
||||
new_begin.point = _begin.point + distance * get_begin_ray()
|
||||
new_begin.normal = _begin.normal
|
||||
if _check_offsets(new_begin, _end):
|
||||
_begin = new_begin
|
||||
|
||||
func get_end() -> VisualizationSpot:
|
||||
return _end
|
||||
|
||||
func get_leg_bend_end() -> Vector3:
|
||||
if _bend == null:
|
||||
return (_end.point - _begin.point)/2.0
|
||||
return _bend.get_end_triangle().get_leg_mid()
|
||||
|
||||
func get_end_left() -> Vector3:
|
||||
var segment := _end.point - get_leg_bend_end()
|
||||
var binormal := segment.cross(_end.normal).normalized()
|
||||
var half_width = binormal*_width/2.0
|
||||
return _end.point - half_width
|
||||
|
||||
func get_end_right() -> Vector3:
|
||||
var segment := _end.point - get_leg_bend_end()
|
||||
var binormal := segment.cross(_end.normal).normalized()
|
||||
var half_width = binormal*_width/2.0
|
||||
return _end.point + half_width
|
||||
|
||||
func get_end_ray() -> Vector3:
|
||||
if _bend == null:
|
||||
return (_end.point - _begin.point).normalized()
|
||||
return (_end.point - _bend.get_end()).normalized()
|
||||
|
||||
func adjust_end(distance : float):
|
||||
var new_end := VisualizationSpot.new()
|
||||
new_end.point = _end.point - distance * get_end_ray()
|
||||
new_end.normal = _end.normal
|
||||
if _check_offsets(_begin, new_end):
|
||||
_end = new_end
|
||||
|
||||
# ---------------------------------------------
|
||||
# Local Methods
|
||||
|
||||
func _check_offsets(p_begin : VisualizationSpot, p_end : VisualizationSpot) -> bool:
|
||||
var dir := (_end.point - _begin.point).normalized()
|
||||
var min_len := MIN_SEGMENT_LENGTH + EPSILON
|
||||
var max_begin := _end.point - dir * min_len
|
||||
var max_end := _begin.point + dir * min_len
|
||||
if _bend != null:
|
||||
max_begin = _bend.get_begin() - dir * min_len
|
||||
max_end = _bend.get_end() + dir * min_len
|
||||
var new_begin_dir := (max_begin - p_begin.point).normalized()
|
||||
var new_end_dir := (p_end.point - max_end).normalized()
|
||||
if new_begin_dir.dot(dir) <= 0.0:
|
||||
_error = "bend start %s is too near %s to be bent with this width and lip" % [ _begin.point, _end.point ]
|
||||
return false
|
||||
if new_end_dir.dot(dir) <= 0.0:
|
||||
_error = "bend end %s is too near %s to be bent with this width and lip" % [ _end.point, _begin.point ]
|
||||
return false
|
||||
return true
|
||||
|
||||
func _has_valid_normal() -> bool:
|
||||
var segment = _end.point - _begin.point
|
||||
var binormal = segment.cross(_begin.normal).normalized()
|
||||
if not binormal.is_normalized():
|
||||
_error = "%s to %s is parallel to normal %s" % [ _begin.point, _end.point, _begin.normal ]
|
||||
return false
|
||||
return true
|
||||
|
||||
func _is_straight() -> bool:
|
||||
if _begin.normal.is_equal_approx(_end.normal):
|
||||
var segment = _end.point - _begin.point
|
||||
if is_equal_approx(segment.dot(_begin.normal), 0.0):
|
||||
return true
|
||||
_error = "%s to %s have same normal but are not in same plane" % [ _begin.point, _end.point ]
|
||||
return false
|
||||
|
||||
func _extend_straight_leg(mesh_node : Node3D, u : float, left : Vector3, right : Vector3, begin : Vector3, end : Vector3, normal : Vector3, mat : Material):
|
||||
var segment := end - begin
|
||||
var binormal := segment.cross(normal).normalized()
|
||||
var half_width = binormal*_width/2.0
|
||||
var seg := begin - end
|
||||
var seg_len := seg.length()
|
||||
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
|
||||
st.set_color(Color(1, 0, 0))
|
||||
st.set_normal(normal)
|
||||
|
||||
st.set_uv(Vector2(u, 1))
|
||||
st.add_vertex(right)
|
||||
|
||||
st.set_uv(Vector2(u, 0))
|
||||
st.add_vertex(left)
|
||||
|
||||
st.set_uv(Vector2(u+seg_len, 1))
|
||||
st.add_vertex(end+half_width)
|
||||
|
||||
st.set_uv(Vector2(u+seg_len, 0))
|
||||
st.add_vertex(end-half_width)
|
||||
|
||||
st.set_material(mat)
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
||||
|
||||
return u + seg_len
|
||||
|
||||
func _create_straight_leg(mesh_node : Node3D, u : float, begin : Vector3, end : Vector3, normal : Vector3, mat : Material):
|
||||
var segment := end - begin
|
||||
var binormal := segment.cross(normal).normalized()
|
||||
var half_width = binormal*_width/2.0
|
||||
var seg := begin - end
|
||||
var seg_len := seg.length()
|
||||
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
|
||||
st.set_color(Color(1, 0, 0))
|
||||
st.set_normal(normal)
|
||||
|
||||
st.set_uv(Vector2(u, 1))
|
||||
st.add_vertex(begin+half_width)
|
||||
|
||||
st.set_uv(Vector2(u, 0))
|
||||
st.add_vertex(begin-half_width)
|
||||
|
||||
st.set_uv(Vector2(u+seg_len, 1))
|
||||
st.add_vertex(end+half_width)
|
||||
|
||||
st.set_uv(Vector2(u+seg_len, 0))
|
||||
st.add_vertex(end-half_width)
|
||||
|
||||
st.set_material(mat)
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
||||
|
||||
return u + seg_len
|
||||
|
||||
func _create_begin_triangle(mesh_node : Node3D, u : float, normal : Vector3, triangle : VizTriangle, mat : Material):
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
st.set_color(Color(1, 0, 0))
|
||||
st.set_normal(normal)
|
||||
|
||||
st.set_uv(Vector2(u, 0))
|
||||
st.add_vertex(triangle.leg_left)
|
||||
st.set_uv(Vector2(u + triangle.distance, triangle.get_fwd_side()))
|
||||
st.add_vertex(triangle.leg_fwd)
|
||||
st.set_uv(Vector2(u, 1))
|
||||
st.add_vertex(triangle.leg_right)
|
||||
st.set_material(mat)
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
||||
return u + triangle.distance
|
||||
|
||||
func _create_end_triangle(mesh_node : Node3D, u : float, normal : Vector3, triangle : VizTriangle, mat : Material):
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
st.set_color(Color(1, 0, 0))
|
||||
st.set_normal(normal)
|
||||
|
||||
st.set_uv(Vector2(u + triangle.distance, 0))
|
||||
st.add_vertex(triangle.leg_left)
|
||||
st.set_uv(Vector2(u + triangle.distance, 1))
|
||||
st.add_vertex(triangle.leg_right)
|
||||
st.set_uv(Vector2(u, triangle.get_fwd_side()))
|
||||
st.add_vertex(triangle.leg_fwd)
|
||||
st.set_material(mat)
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
||||
return u + triangle.distance
|
||||
|
||||
func _create_bend(mesh_node : Node3D, u : float, segs : int, sharpness : float, mat : Material):
|
||||
var right_u := u - _bend.get_begin_triangle().distance
|
||||
var left_u := u
|
||||
if _bend.is_angled_against_curve():
|
||||
right_u = u
|
||||
left_u = u - _bend.get_begin_triangle().distance
|
||||
var arc_len := 0.0
|
||||
|
||||
var segment := _bend.get_begin() - _begin.point
|
||||
var binormal := segment.cross(_begin.normal).normalized()
|
||||
var half_width = _bend.get_half_bend_width()
|
||||
var half_width_normal := half_width.normalized()
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
|
||||
st.set_normal(_begin.normal)
|
||||
st.set_uv(Vector2(right_u, 1))
|
||||
st.add_vertex(_bend.get_begin_triangle().bend_right)
|
||||
st.set_uv(Vector2(left_u, 0))
|
||||
st.add_vertex(_bend.get_begin_triangle().bend_left)
|
||||
var shift_width := _bend.get_shift_width()
|
||||
var last_point := _bend.get_arc_begin_point()
|
||||
var total_len := 0.0
|
||||
for i in range(0, segs):
|
||||
var weight := float(i) / float(segs-1)
|
||||
var cur_point := _bend.get_arc_point(weight, sharpness)
|
||||
total_len += cur_point.distance_to(last_point)
|
||||
last_point = cur_point
|
||||
total_len += _bend.get_arc_end_point().distance_to(last_point)
|
||||
last_point = _bend.get_arc_begin_point()
|
||||
for i in range(1, segs-1):
|
||||
var weight := float(i) / float(segs-1)
|
||||
var cur_point := _bend.get_arc_point(weight, sharpness)
|
||||
arc_len += cur_point.distance_to(last_point)
|
||||
var shift := shift_width * (arc_len / total_len)
|
||||
var pos := cur_point + shift - _bend.get_shift_offset()
|
||||
var cur_normal := Vector3(cur_point - last_point).normalized().cross(half_width_normal)
|
||||
last_point = cur_point
|
||||
st.set_normal(cur_normal)
|
||||
st.set_uv(Vector2(right_u + arc_len, 1))
|
||||
st.add_vertex(pos-half_width)
|
||||
st.set_uv(Vector2(left_u + arc_len, 0))
|
||||
st.add_vertex(pos+half_width)
|
||||
st.set_normal(_end.normal)
|
||||
st.set_uv(Vector2(right_u + total_len, 1))
|
||||
st.add_vertex(_bend.get_end_triangle().bend_right)
|
||||
st.set_uv(Vector2(left_u + total_len, 0))
|
||||
st.add_vertex(_bend.get_end_triangle().bend_left)
|
||||
st.set_material(mat)
|
||||
mesh_node.mesh = st.commit(mesh_node.mesh)
|
||||
u = right_u + total_len
|
||||
if _bend.is_angled_against_curve():
|
||||
u = left_u + total_len
|
||||
return u
|
||||
|
||||
func _to_string():
|
||||
if _bend != null:
|
||||
return "SLEG(%s->%s through %s)" % [ _begin, _end, _bend ]
|
||||
return "SLEG(%s->%s)" % [ _begin, _end ]
|
||||
|
1
addons/vizpath/utilities/viz_simple_leg.gd.uid
Normal file
1
addons/vizpath/utilities/viz_simple_leg.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cb1qc8ghusxos
|
46
addons/vizpath/utilities/viz_triangle.gd
Normal file
46
addons/vizpath/utilities/viz_triangle.gd
Normal file
|
@ -0,0 +1,46 @@
|
|||
extends RefCounted
|
||||
class_name VizTriangle
|
||||
|
||||
var leg_right : Vector3
|
||||
var leg_left : Vector3
|
||||
var leg_fwd : Vector3
|
||||
var bend_right : Vector3
|
||||
var bend_left : Vector3
|
||||
var distance : float
|
||||
var fwd_side : float
|
||||
|
||||
func update(dir : Vector3, join_point : Vector3, binormal : Vector3, p_width : float, p_bend_lip : float, bend_normal : Vector3, bend_side : float, curve_side : float):
|
||||
var half_width := p_width / 2.0
|
||||
if bend_side == 0:
|
||||
leg_left = join_point - half_width * binormal - p_bend_lip * dir
|
||||
leg_right = join_point + half_width * binormal - p_bend_lip * dir
|
||||
bend_left = leg_left
|
||||
bend_right = leg_right
|
||||
distance = 0.0
|
||||
else:
|
||||
var cos_theta := dir.dot(bend_side * bend_normal)
|
||||
var sin_theta := sqrt(1-cos_theta*cos_theta)
|
||||
var width := half_width / sin_theta
|
||||
var lip_distance := p_bend_lip / sin_theta
|
||||
var bend_width = width * cos_theta
|
||||
leg_left = join_point - half_width * binormal - bend_width * dir - lip_distance * dir
|
||||
leg_right = join_point + half_width * binormal - bend_width * dir - lip_distance * dir
|
||||
leg_fwd = join_point - curve_side * bend_side * half_width * binormal + bend_width * dir - lip_distance * dir
|
||||
distance = bend_width * 2.0
|
||||
if bend_side * curve_side < 0:
|
||||
bend_left = leg_left
|
||||
bend_right = leg_fwd
|
||||
fwd_side = 1.0
|
||||
else:
|
||||
bend_left = leg_fwd
|
||||
bend_right = leg_right
|
||||
fwd_side = 0.0
|
||||
|
||||
func get_fwd_side():
|
||||
return fwd_side
|
||||
|
||||
func get_leg_mid() -> Vector3:
|
||||
return leg_left - (leg_left - leg_right) / 2.0
|
||||
|
||||
func get_bend_mid() -> Vector3:
|
||||
return bend_left - (bend_left - bend_right) / 2.0
|
1
addons/vizpath/utilities/viz_triangle.gd.uid
Normal file
1
addons/vizpath/utilities/viz_triangle.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://b07jnfj4y8ef7
|
200
addons/vizpath/visualized_path.gd
Normal file
200
addons/vizpath/visualized_path.gd
Normal file
|
@ -0,0 +1,200 @@
|
|||
## Ths class is designed to create a mesh that
|
||||
## will display a path between pairs of VisualizationSpots.
|
||||
##
|
||||
## To use a VisualizedPath it must be populated with an array
|
||||
## of spots which define the local object space positions and
|
||||
## normals for the path at that point.
|
||||
##
|
||||
## The spots array can be defined programmatically such as by
|
||||
## user action (such as clicking) or defined in the editor using
|
||||
## the VizPath gizmo.
|
||||
##
|
||||
## Not all paths are valid. Putting the spots too close together
|
||||
## or with normals that do not leave enough room for bends to be
|
||||
## created with the defined path characteristics (width, inner radius,
|
||||
## etc.), will result in the path not being displayed and the "error"
|
||||
## attribute being set to the underlying problem.
|
||||
##
|
||||
|
||||
@tool
|
||||
@icon("res://addons/vizpath/images/path.png")
|
||||
extends Node3D
|
||||
class_name VisualizedPath
|
||||
|
||||
signal changed_layout
|
||||
|
||||
## The spots array defines where the path will go between.
|
||||
## See [VisualizationSpot]
|
||||
@export var spots : Array[VisualizationSpot] :
|
||||
set(p_spots):
|
||||
spots = p_spots
|
||||
for spot in spots:
|
||||
if not spot.changed.is_connected(_rebuild):
|
||||
spot.changed.connect(_rebuild)
|
||||
_rebuild()
|
||||
|
||||
## The path_width defines the width of the path, which indirectly
|
||||
## determines how tight the path can turn at a midpoint spot.
|
||||
@export_range(0.005, 100, 0.001) var path_width := 0.1 :
|
||||
set(w):
|
||||
path_width = w
|
||||
_rebuild()
|
||||
|
||||
## The inner_curve_radius defines how tight a turn can be made
|
||||
## at a midpoint spot within the path.
|
||||
@export_range(0.005, 100, 0.001) var inner_curve_radius := 0.1 :
|
||||
set(r):
|
||||
inner_curve_radius = r
|
||||
_rebuild()
|
||||
|
||||
## The num_curve_segs defines how many points will be defined in the
|
||||
## arc that are used to create a turn. More segments will
|
||||
## make a smoother UV mapping and therefore the display of associated material
|
||||
## more accurate.
|
||||
@export_range(4, 128) var num_curve_segs := 32 :
|
||||
set(r):
|
||||
num_curve_segs = r
|
||||
_rebuild()
|
||||
|
||||
## The bend_segs defines the segments in a bend of the path when transitioning
|
||||
## between two spots with normals not in the same plane.
|
||||
@export_range(4, 128) var bend_segs := 6 :
|
||||
set(r):
|
||||
bend_segs = r
|
||||
_rebuild()
|
||||
|
||||
## The bend_lip defines the distance that a segment extends from the intersection
|
||||
## of the planes defined by the beginning and ending normals when those normals
|
||||
## are not in the same plane. Making this a smaller value will make the
|
||||
## segment flatter for more of its length.
|
||||
@export_range(0.005, 100, 0.001) var bend_lip := 0.1 :
|
||||
set(r):
|
||||
bend_lip = r
|
||||
_rebuild()
|
||||
|
||||
## The bend_sharpness defines how sharp the bend in a path. Making this
|
||||
## smaller will result in a tighter bend.
|
||||
@export var bend_sharpness := 1.0 :
|
||||
set(r):
|
||||
bend_sharpness = r
|
||||
_rebuild()
|
||||
|
||||
## The path_mat is the material that will be applied to the underlying mesh
|
||||
## that displays the path. The initial spot in the path will have texture coordinates
|
||||
## that start with U equal to 0.0 and ending at the value calculated as the actual length of the path
|
||||
## in local space. The V will range between 0.0 at the left side of the path, and 1.0 at the
|
||||
## right side of the path.
|
||||
@export var path_mat : Material :
|
||||
set(m):
|
||||
path_mat = m
|
||||
_rebuild()
|
||||
|
||||
## The path_head is a resource (optional) that defines the end of the path by
|
||||
## providing an "apply" method. The provided [VizHead] resource is an example
|
||||
## that draws an arrow head on the end.
|
||||
@export var path_head : VizHead :
|
||||
set(m):
|
||||
path_head = m
|
||||
path_head.changed.connect(_rebuild)
|
||||
_rebuild()
|
||||
|
||||
## The path_tail is a resource (optional) that defines the start of the path by
|
||||
## providing an "apply" method. The provided [VizTail] resource is an example
|
||||
## that draws a rounded cap on the end.
|
||||
@export var path_tail : VizTail :
|
||||
set(m):
|
||||
path_tail = m
|
||||
path_tail.changed.connect(_rebuild)
|
||||
_rebuild()
|
||||
|
||||
@export var suppress_warnings := false :
|
||||
set(s):
|
||||
suppress_warnings = s
|
||||
_rebuild()
|
||||
|
||||
var _mesh_instance : MeshInstance3D
|
||||
var _errors : Array[String]
|
||||
|
||||
func get_triangle_mesh() -> TriangleMesh:
|
||||
if _mesh_instance.mesh != null:
|
||||
return _mesh_instance.mesh.generate_triangle_mesh()
|
||||
return null
|
||||
|
||||
func _ready():
|
||||
_mesh_instance = MeshInstance3D.new()
|
||||
add_child(_mesh_instance)
|
||||
_rebuild()
|
||||
|
||||
func _get_configuration_warnings():
|
||||
if not suppress_warnings:
|
||||
return PackedStringArray(_errors)
|
||||
return PackedStringArray()
|
||||
|
||||
func get_errors() -> Array[String]:
|
||||
return _errors
|
||||
|
||||
func _rebuild():
|
||||
_errors = []
|
||||
if _mesh_instance == null:
|
||||
return
|
||||
if spots.size() < 2:
|
||||
_errors.append("the spots array must have at least 2 entries")
|
||||
_mesh_instance.mesh = null
|
||||
var u := 0.0
|
||||
var segments : Array[VizSegment] = []
|
||||
for idx in range(1, spots.size()):
|
||||
segments.push_back(VizSegment.new(spots[idx-1], spots[idx], path_width, bend_lip))
|
||||
for idx in range(1, segments.size()):
|
||||
if segments[idx-1].is_invalid():
|
||||
_errors.push_back(segments[idx-1].get_error())
|
||||
break
|
||||
var midpoint := VizMid.new(segments[idx-1], segments[idx], path_width, inner_curve_radius)
|
||||
if midpoint.is_invalid():
|
||||
_errors.push_back(midpoint.get_error())
|
||||
break
|
||||
# Creating midpoint may invalidate prior segment
|
||||
if segments[idx-1].is_invalid():
|
||||
_errors.push_back(segments[idx-1].get_error())
|
||||
break
|
||||
if idx == 1:
|
||||
u = _add_tail(segments[idx-1], u)
|
||||
u = segments[idx-1].update_mesh(_mesh_instance, u, bend_segs, bend_sharpness, path_mat)
|
||||
u = midpoint.update_mesh(_mesh_instance, u, num_curve_segs, path_mat)
|
||||
if _errors.size() == 0:
|
||||
if segments[segments.size()-1].is_invalid():
|
||||
_errors.push_back(segments[segments.size()-1].get_error())
|
||||
else:
|
||||
if segments.size() == 1:
|
||||
u = _add_tail(segments[0], u)
|
||||
_add_head(segments[segments.size()-1], u)
|
||||
update_configuration_warnings()
|
||||
changed_layout.emit()
|
||||
|
||||
func _add_head(head : VizSegment, u : float):
|
||||
if path_head != null:
|
||||
var end := head.get_end().point
|
||||
var binormal := head.get_end_binormal()
|
||||
var left := end - binormal * path_width / 2.0
|
||||
var right := end + binormal * path_width / 2.0
|
||||
var normal := head.get_end().normal
|
||||
var direction := head.get_end_ray()
|
||||
var offset := path_head.get_offset(left, right, normal, direction)
|
||||
head.adjust_end(offset)
|
||||
u = head.update_mesh(_mesh_instance, u, bend_segs, bend_sharpness, path_mat)
|
||||
path_head.apply(_mesh_instance, u, left - direction * offset, right - direction * offset, normal, direction, path_mat)
|
||||
else:
|
||||
head.update_mesh(_mesh_instance, u, bend_segs, bend_sharpness, path_mat)
|
||||
|
||||
func _add_tail(tail : VizSegment, u : float) -> float:
|
||||
if path_tail != null:
|
||||
var begin := tail.get_begin().point
|
||||
var binormal := tail.get_begin_binormal()
|
||||
var left := begin - binormal * path_width / 2.0
|
||||
var right := begin + binormal * path_width / 2.0
|
||||
var normal := tail.get_begin().normal
|
||||
var direction := tail.get_begin_ray()
|
||||
var offset := path_tail.get_offset(left, right, normal, direction)
|
||||
tail.adjust_begin(offset)
|
||||
path_tail.apply(_mesh_instance, u, left - direction * offset, right - direction * offset, normal, direction, path_mat)
|
||||
return u + offset
|
||||
return u
|
1
addons/vizpath/visualized_path.gd.uid
Normal file
1
addons/vizpath/visualized_path.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cg3nxpmodi5j2
|
7
addons/vizpath/visualized_path.tscn
Normal file
7
addons/vizpath/visualized_path.tscn
Normal file
|
@ -0,0 +1,7 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://b01j6blghusie"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/vizpath/visualized_path.gd" id="1_7md1l"]
|
||||
|
||||
[node name="visualized_path" type="Node3D"]
|
||||
script = ExtResource("1_7md1l")
|
||||
bend_sharpness = 0.0
|
15
addons/vizpath/vizpath.gd
Normal file
15
addons/vizpath/vizpath.gd
Normal file
|
@ -0,0 +1,15 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const VizpathGizmoPlugin = preload("res://addons/vizpath/vizpath_gizmo_plugin.gd")
|
||||
|
||||
var gizmo_plugin = VizpathGizmoPlugin.new()
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
gizmo_plugin.set_editor_plugin(self)
|
||||
add_node_3d_gizmo_plugin(gizmo_plugin)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
remove_node_3d_gizmo_plugin(gizmo_plugin)
|
1
addons/vizpath/vizpath.gd.uid
Normal file
1
addons/vizpath/vizpath.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://cf8dbwlct6q32
|
80
addons/vizpath/vizpath_gizmo.gd
Normal file
80
addons/vizpath/vizpath_gizmo.gd
Normal file
|
@ -0,0 +1,80 @@
|
|||
extends EditorNode3DGizmo
|
||||
|
||||
func _redraw():
|
||||
clear()
|
||||
var path := get_node_3d() as VisualizedPath
|
||||
if not path.changed_layout.is_connected(_redraw):
|
||||
path.changed_layout.connect(_redraw)
|
||||
|
||||
var lines = PackedVector3Array()
|
||||
for i in range(path.spots.size()):
|
||||
lines.push_back(path.spots[i].point)
|
||||
lines.push_back(path.spots[i].point + path.spots[i].normal * path.path_width * 2.0)
|
||||
|
||||
var line_material = get_plugin().get_material("line", self)
|
||||
add_lines(lines, line_material, false)
|
||||
|
||||
for i in range(path.spots.size()):
|
||||
var rotation := Quaternion(Vector3.FORWARD, path.spots[i].normal)
|
||||
var basis := Basis(rotation)
|
||||
basis = basis.scaled(Vector3(path.path_width,path.path_width,path.path_width))
|
||||
var transform := Transform3D(basis, path.spots[i].point)
|
||||
add_mesh(get_plugin().spot_mesh, line_material, transform)
|
||||
|
||||
var triangle_mesh := path.get_triangle_mesh()
|
||||
if triangle_mesh != null:
|
||||
add_collision_triangles(triangle_mesh)
|
||||
|
||||
func _subgizmos_intersect_frustum(camera : Camera3D, frustrum : Array[Plane]) -> PackedInt32Array:
|
||||
var path := get_node_3d() as VisualizedPath
|
||||
var gizmos := PackedInt32Array()
|
||||
for i in range(path.spots.size()):
|
||||
var origin := path.global_transform * path.spots[i].point
|
||||
var any_out := false
|
||||
for plane in frustrum:
|
||||
if plane.is_point_over(origin):
|
||||
any_out = true
|
||||
if not any_out:
|
||||
gizmos.push_back(i)
|
||||
return gizmos
|
||||
|
||||
func _subgizmos_intersect_ray(camera : Camera3D, point : Vector2) -> int:
|
||||
var path := get_node_3d() as VisualizedPath
|
||||
for i in range(path.spots.size()):
|
||||
var origin := path.global_transform * path.spots[i].point
|
||||
var p := camera.unproject_position(origin)
|
||||
if p.distance_to(point) < 20:
|
||||
return i
|
||||
return -1
|
||||
|
||||
func _get_subgizmo_transform(id : int) -> Transform3D:
|
||||
var path := get_node_3d() as VisualizedPath
|
||||
var rotation := Quaternion(Vector3.FORWARD, path.spots[id].normal)
|
||||
var basis := Basis(rotation)
|
||||
var transform := Transform3D(basis, path.spots[id].point)
|
||||
return transform
|
||||
|
||||
func _set_subgizmo_transform(id : int, transform : Transform3D):
|
||||
var path := get_node_3d() as VisualizedPath
|
||||
path.spots[id].point = transform.origin
|
||||
path.spots[id].normal = transform.basis * Vector3.FORWARD
|
||||
|
||||
func _commit_subgizmos( ids : PackedInt32Array, restores : Array[Transform3D], cancel : bool):
|
||||
var path := get_node_3d() as VisualizedPath
|
||||
if cancel:
|
||||
for idx in range(ids.size()):
|
||||
var spot := path.spots[ids[idx]]
|
||||
spot.point = restores[idx].origin
|
||||
spot.normal = restores[idx].basis * Vector3.FORWARD
|
||||
else:
|
||||
var vizpath_gizmo_plugin := get_plugin() as EditorNode3DGizmoPlugin
|
||||
var undo_redo = vizpath_gizmo_plugin.editor_plugin.get_undo_redo()
|
||||
undo_redo.create_action("Modify spots")
|
||||
for idx in range(ids.size()):
|
||||
var spot := path.spots[ids[idx]]
|
||||
undo_redo.add_do_property(spot, "point", spot.point)
|
||||
undo_redo.add_undo_property(spot, "point", restores[idx].origin)
|
||||
undo_redo.add_do_property(spot, "normal", spot.normal)
|
||||
undo_redo.add_undo_property(spot, "normal", restores[idx].basis * Vector3.FORWARD)
|
||||
undo_redo.commit_action()
|
||||
|
1
addons/vizpath/vizpath_gizmo.gd.uid
Normal file
1
addons/vizpath/vizpath_gizmo.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://qo6xoq0v7djx
|
24
addons/vizpath/vizpath_gizmo_plugin.gd
Normal file
24
addons/vizpath/vizpath_gizmo_plugin.gd
Normal file
|
@ -0,0 +1,24 @@
|
|||
extends EditorNode3DGizmoPlugin
|
||||
|
||||
const vizpath_gizmo = preload("res://addons/vizpath/vizpath_gizmo.gd")
|
||||
const spot_mesh = preload("res://addons/vizpath/mesh/spot.obj")
|
||||
|
||||
var editor_plugin : EditorPlugin
|
||||
|
||||
func _init():
|
||||
create_material("line", Color(1, 1, 0))
|
||||
create_handle_material("handles")
|
||||
|
||||
func _create_gizmo(node):
|
||||
if node is VisualizedPath:
|
||||
var viz_path_node := node as VisualizedPath
|
||||
var gizmo = vizpath_gizmo.new()
|
||||
return gizmo
|
||||
else:
|
||||
return null
|
||||
|
||||
func _get_gizmo_name():
|
||||
return "VizPath Gizmo"
|
||||
|
||||
func set_editor_plugin(p_plugin : EditorPlugin):
|
||||
editor_plugin = p_plugin
|
1
addons/vizpath/vizpath_gizmo_plugin.gd.uid
Normal file
1
addons/vizpath/vizpath_gizmo_plugin.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://28ida0ysqdac
|
Loading…
Add table
Add a link
Reference in a new issue