└── addons
└── uv_tools
├── icon.png
├── thumbnail.png
├── plugin.cfg
├── icons
├── proto_cube.svg
├── proto_body.svg
├── proto_body.svg.import
└── proto_cube.svg.import
├── uv_tools.gd
└── scripts
├── proto_cube.gd
├── uv_tools.gd
└── proto_body.gd
/addons/uv_tools/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bikemurt/godot-uv-tools/HEAD/addons/uv_tools/icon.png
--------------------------------------------------------------------------------
/addons/uv_tools/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bikemurt/godot-uv-tools/HEAD/addons/uv_tools/thumbnail.png
--------------------------------------------------------------------------------
/addons/uv_tools/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="UVTools"
4 | description="Simple Godot 4 plugin to that includes various tools to manipulate UVs."
5 | author="Michael Jared"
6 | version=""
7 | script="uv_tools.gd"
8 |
--------------------------------------------------------------------------------
/addons/uv_tools/icons/proto_cube.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/addons/uv_tools/uv_tools.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | func _enter_tree():
5 | add_custom_type("ProtoCube", "MeshInstance3D", preload("scripts/proto_cube.gd"), preload("icons/proto_cube.svg"))
6 | add_custom_type("ProtoBody", "Node3D", preload("scripts/proto_body.gd"), preload("icons/proto_body.svg"))
7 |
8 | func _exit_tree():
9 | remove_custom_type("ProtoCube")
10 | remove_custom_type("ProtoBody")
11 |
--------------------------------------------------------------------------------
/addons/uv_tools/icons/proto_body.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/addons/uv_tools/icons/proto_body.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c4t8hqwt3bati"
6 | path="res://.godot/imported/proto_body.svg-1dce473616ba97add0b3907e6e4ce633.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/uv_tools/icons/proto_body.svg"
14 | dest_files=["res://.godot/imported/proto_body.svg-1dce473616ba97add0b3907e6e4ce633.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/uv_tools/icons/proto_cube.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c06x378ji7jjb"
6 | path="res://.godot/imported/proto_cube.svg-38cfcbb8e337e965b7f7ab59545551d8.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/uv_tools/icons/proto_cube.svg"
14 | dest_files=["res://.godot/imported/proto_cube.svg-38cfcbb8e337e965b7f7ab59545551d8.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/uv_tools/scripts/proto_cube.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Node3D
3 |
4 | ## Size of the box mesh and box collision shape
5 | @export var size := Vector3(1,1,1):
6 | set(value):
7 | size = value
8 | if auto_reload:
9 | _update_size()
10 | get:
11 | return size
12 |
13 | ## Texture to be used for the albedo of the box's surface material
14 | @export var albedo_texture : Texture2D:
15 | set(value):
16 | albedo_texture = value
17 | if auto_reload:
18 | _update_texture()
19 | get:
20 | return albedo_texture
21 |
22 | @export_group("Editor Settings")
23 | ## Automatically reload the proto node as parameters change
24 | @export var auto_reload = true
25 |
26 | ## Only reload manually by toggling this export
27 | @export var manual_reload = false:
28 | set(value):
29 | _load()
30 |
31 | var top_node
32 | var mesh_instance : MeshInstance3D
33 | var mat : StandardMaterial3D
34 |
35 | var uv_tools = preload("res://addons/uv_tools/scripts/uv_tools.gd").new()
36 |
37 | # Called when the node enters the scene tree for the first time.
38 | func _ready():
39 | _load()
40 |
41 | func _update_texture():
42 | if mesh_instance == null: return
43 |
44 | mat.albedo_texture = albedo_texture
45 | mesh_instance.set_surface_override_material(0, mat)
46 |
47 | func _update_uv():
48 | return uv_tools.cube_project(mesh_instance)
49 |
50 | func _update_size():
51 | if mesh_instance == null: return
52 |
53 | var box_mesh = BoxMesh.new()
54 | box_mesh.size = size
55 | mesh_instance.mesh = box_mesh
56 |
57 | mesh_instance = _update_uv()
58 |
59 | func _load():
60 | if not Engine.is_editor_hint(): return
61 |
62 | print("Loading ProtoCube")
63 |
64 | var delete = []
65 | for child in get_children():
66 | delete.append(child)
67 |
68 | for node in delete:
69 | remove_child(node)
70 |
71 | mesh_instance = MeshInstance3D.new()
72 | mesh_instance.name = "MeshInstance3D"
73 |
74 | var box_mesh = BoxMesh.new()
75 | box_mesh.size = size
76 |
77 | mesh_instance.mesh = box_mesh
78 |
79 | mat = StandardMaterial3D.new()
80 |
81 | if albedo_texture != null:
82 | mat.albedo_texture = albedo_texture
83 |
84 | mesh_instance.set_surface_override_material(0, mat)
85 |
86 | add_child(mesh_instance)
87 |
88 | mesh_instance = _update_uv()
89 |
90 | mesh_instance.owner = get_tree().edited_scene_root
91 |
92 | func _process(delta):
93 | pass
94 |
--------------------------------------------------------------------------------
/addons/uv_tools/scripts/uv_tools.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | # Called when the node enters the scene tree for the first time.
4 | func _ready():
5 | pass # Replace with function body.
6 |
7 |
8 | # Called every frame. 'delta' is the elapsed time since the previous frame.
9 | func _process(delta):
10 | pass
11 |
12 |
13 | func cube_project(mesh_instance : MeshInstance3D, node_scale := Vector3(1,1,1)):
14 | if not Engine.is_editor_hint():
15 | return
16 |
17 | var new_mesh = ArrayMesh.new()
18 | new_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_instance.mesh.get_mesh_arrays())
19 |
20 | var mdt = MeshDataTool.new()
21 | mdt.create_from_surface(new_mesh, 0)
22 |
23 | for i in range(mdt.get_face_count()):
24 | var normal = mdt.get_face_normal(i)
25 |
26 | var idx_0 = mdt.get_face_vertex(i, 0)
27 | var idx_1 = mdt.get_face_vertex(i, 1)
28 | var idx_2 = mdt.get_face_vertex(i, 2)
29 |
30 | var v0 = mdt.get_vertex(idx_0)
31 | var v1 = mdt.get_vertex(idx_1)
32 | var v2 = mdt.get_vertex(idx_2)
33 |
34 | if normal.x != 0:
35 | # use y and z
36 | var p0 = Vector2(v0.y, v0.z)
37 | var p1 = Vector2(v1.y, v1.z)
38 | var p2 = Vector2(v2.y, v2.z)
39 |
40 | var scale_uv = Vector2(node_scale.y, node_scale.z)
41 |
42 | p0 *= scale_uv
43 | p1 *= scale_uv
44 | p2 *= scale_uv
45 |
46 | var uv0 = Vector2(p0.x + 0.5, p0.y + 0.5)
47 | var uv1 = Vector2(p1.x + 0.5, p1.y + 0.5)
48 | var uv2 = Vector2(p2.x + 0.5, p2.y + 0.5)
49 |
50 | mdt.set_vertex_uv(idx_0, uv0)
51 | mdt.set_vertex_uv(idx_1, uv1)
52 | mdt.set_vertex_uv(idx_2, uv2)
53 |
54 | if normal.y != 0:
55 | # use x and z
56 | var p0 = Vector2(v0.x, v0.z)
57 | var p1 = Vector2(v1.x, v1.z)
58 | var p2 = Vector2(v2.x, v2.z)
59 |
60 | var scale_uv = Vector2(node_scale.x, node_scale.z)
61 |
62 | p0 *= scale_uv
63 | p1 *= scale_uv
64 | p2 *= scale_uv
65 |
66 | var uv0 = Vector2(p0.x + 0.5, p0.y + 0.5)
67 | var uv1 = Vector2(p1.x + 0.5, p1.y + 0.5)
68 | var uv2 = Vector2(p2.x + 0.5, p2.y + 0.5)
69 |
70 | mdt.set_vertex_uv(idx_0, uv0)
71 | mdt.set_vertex_uv(idx_1, uv1)
72 | mdt.set_vertex_uv(idx_2, uv2)
73 |
74 | if normal.z != 0:
75 | # use x and y
76 | var p0 = Vector2(v0.x, v0.y)
77 | var p1 = Vector2(v1.x, v1.y)
78 | var p2 = Vector2(v2.x, v2.y)
79 |
80 | var scale_uv = Vector2(node_scale.x, node_scale.y)
81 |
82 | p0 *= scale_uv
83 | p1 *= scale_uv
84 | p2 *= scale_uv
85 |
86 | var uv0 = Vector2(p0.x + 0.5, p0.y + 0.5)
87 | var uv1 = Vector2(p1.x + 0.5, p1.y + 0.5)
88 | var uv2 = Vector2(p2.x + 0.5, p2.y + 0.5)
89 |
90 | mdt.set_vertex_uv(idx_0, uv0)
91 | mdt.set_vertex_uv(idx_1, uv1)
92 | mdt.set_vertex_uv(idx_2, uv2)
93 |
94 | new_mesh.clear_surfaces()
95 | mdt.commit_to_surface(new_mesh)
96 |
97 | mesh_instance.mesh = new_mesh
98 |
99 | return mesh_instance
100 |
--------------------------------------------------------------------------------
/addons/uv_tools/scripts/proto_body.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Node3D
3 |
4 | ## Body type. If this is changed the entire node will be reloaded
5 | @export_enum("Static Body", "Rigid Body") var body_type : int = 0:
6 | set(value):
7 | body_type = value
8 | if auto_reload:
9 | _load()
10 | get:
11 | return body_type
12 |
13 | ## Size of the box mesh and box collision shape
14 | @export var size := Vector3(1,1,1):
15 | set(value):
16 | size = value
17 | if auto_reload:
18 | _update_size()
19 | get:
20 | return size
21 |
22 | ## Texture to be used for the albedo of the box's surface material
23 | @export var albedo_texture : Texture2D:
24 | set(value):
25 | albedo_texture = value
26 | if auto_reload:
27 | _update_texture()
28 | get:
29 | return albedo_texture
30 |
31 | @export_group("Editor Settings")
32 | ## Automatically reload the proto node as parameters change
33 | @export var auto_reload = true
34 |
35 | ## Only reload manually by toggling this export
36 | @export var manual_reload = false:
37 | set(value):
38 | _load()
39 |
40 | var top_node
41 | var collision_shape : CollisionShape3D
42 | var mesh_instance : MeshInstance3D
43 | var mat : StandardMaterial3D
44 |
45 | var uv_tools = preload("res://addons/uv_tools/scripts/uv_tools.gd").new()
46 |
47 | # Called when the node enters the scene tree for the first time.
48 | func _ready():
49 | _load()
50 |
51 | func _update_texture():
52 | if mesh_instance == null: return
53 |
54 | mat.albedo_texture = albedo_texture
55 | mesh_instance.set_surface_override_material(0, mat)
56 |
57 | func _update_uv():
58 | return uv_tools.cube_project(mesh_instance)
59 |
60 | func _update_size():
61 | if mesh_instance == null: return
62 |
63 | var box_mesh = BoxMesh.new()
64 | box_mesh.size = size
65 | mesh_instance.mesh = box_mesh
66 |
67 | mesh_instance = _update_uv()
68 |
69 | var box_shape = collision_shape.shape
70 | if box_shape != null:
71 | box_shape.size = size
72 |
73 | func _load():
74 | if not Engine.is_editor_hint(): return
75 |
76 | print("Loading ProtoBody")
77 |
78 | var delete = []
79 | for child in get_children():
80 | delete.append(child)
81 |
82 | for node in delete:
83 | node.queue_free()
84 | remove_child(node)
85 |
86 | var top_node
87 | if body_type == 0:
88 | top_node = StaticBody3D.new()
89 | top_node.name = "StaticBody3D"
90 | if body_type == 1:
91 | top_node = RigidBody3D.new()
92 | top_node.name = "RigidBody3D"
93 |
94 | collision_shape = CollisionShape3D.new()
95 | collision_shape.name = "CollisionShape3D"
96 |
97 | var box_shape = BoxShape3D.new()
98 | box_shape.size = size
99 | collision_shape.shape = box_shape
100 |
101 | mesh_instance = MeshInstance3D.new()
102 | mesh_instance.name = "MeshInstance3D"
103 |
104 | var box_mesh = BoxMesh.new()
105 | box_mesh.size = size
106 |
107 | mesh_instance.mesh = box_mesh
108 |
109 | mat = StandardMaterial3D.new()
110 |
111 | if albedo_texture != null:
112 | mat.albedo_texture = albedo_texture
113 |
114 | mesh_instance.set_surface_override_material(0, mat)
115 |
116 | top_node.add_child(collision_shape)
117 | top_node.add_child(mesh_instance)
118 |
119 | add_child(top_node)
120 |
121 | mesh_instance = _update_uv()
122 |
123 | var set_roots = [top_node, collision_shape, mesh_instance]
124 | for node in set_roots:
125 | node.owner = get_tree().edited_scene_root
126 |
127 | func _process(delta):
128 | pass
129 |
--------------------------------------------------------------------------------