└── 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 | --------------------------------------------------------------------------------