diff --git a/addons/SIsilicon.3d.text/3d_text_plugin.gd b/addons/SIsilicon.3d.text/3d_text_plugin.gd new file mode 100644 index 0000000..240a584 --- /dev/null +++ b/addons/SIsilicon.3d.text/3d_text_plugin.gd @@ -0,0 +1,64 @@ +tool +extends EditorPlugin + +const Label3D = preload("label_3d.gd") + +var converter_button : Button +var edited_node : Label3D + +func _enter_tree(): + yield(get_tree(), "idle_frame") + + add_custom_type( + "Label3D", "Spatial", + Label3D, + preload("icon_label_3d.svg") + ) + + if not converter_button: + converter_button = preload("label_3d_converter.tscn").instance() + add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, converter_button) + converter_button.connect("mesh_generated", self, "generate_mesh") + converter_button.hide() + + print("3d text plugin added to project.") + + +func _exit_tree(): + remove_custom_type("Label3D") + remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, converter_button) + + print("3d text plugin removed from project.") + + +func handles(object : Object) -> bool: + var handle = object is Label3D + + if not handle: + converter_button.hide() + + return handle + + +func edit(object): + edited_node = object + if edited_node is Label3D: + converter_button.show() + converter_button.label3d = object + else: + converter_button.hide() + + +func clear(): + edited_node = null + converter_button.hide() + +func generate_mesh(mesh_inst): + var undo_redo = get_undo_redo() + undo_redo.create_action("Convert Text") + + undo_redo.add_do_method(edited_node.get_parent(), "add_child", mesh_inst) + undo_redo.add_undo_method(edited_node.get_parent(), "remove_child", mesh_inst) + undo_redo.commit_action() + + mesh_inst.set_owner(get_editor_interface().get_edited_scene_root()) diff --git a/addons/SIsilicon.3d.text/LICENSE b/addons/SIsilicon.3d.text/LICENSE new file mode 100644 index 0000000..e3f824b --- /dev/null +++ b/addons/SIsilicon.3d.text/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Roujel Williams + +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. diff --git a/addons/SIsilicon.3d.text/array_utils.gd b/addons/SIsilicon.3d.text/array_utils.gd new file mode 100644 index 0000000..257e53a --- /dev/null +++ b/addons/SIsilicon.3d.text/array_utils.gd @@ -0,0 +1,47 @@ +class_name ArrayUtils + +# Shifts the array's values by a specified amount. +# Positive amount shifts right while negative shifts left. +static func shift_array(array : Array, amount : int): + var right = bool(sign(amount) * 0.5 + 0.5) + for i in abs(amount): + var value + if right: + value = array.pop_back() + array.push_front(value) + else: + value = array.pop_front() + array.push_back(value) + +# Combines the array with the other array. +static func append_array(array : Array, other : Array): + for i in other: + array.append(i) + +# Splits the array between a and b at index. +static func split_array(array : Array, index : int, a : Array, b : Array): + for i in range(0, index): + a.append(array[i]) + + for i in range(index, array.size()): + b.append(array[i]) + +# Converts array to dictionary +static func to_dictionary(array : Array) -> Dictionary: + var dict = {} + for i in array.size(): + dict[i] = array[i] + return dict + +# Calls a function per object in the array with variable arguments. +static func call_per_element(array : Array, function : String, vars = null): + match typeof(vars): + TYPE_NIL: + for e in array: + e.call(function) + TYPE_ARRAY: + for e in array: + e.callv(function, vars) + _: + for e in array: + e.call(function, vars) diff --git a/addons/SIsilicon.3d.text/default_font.tres b/addons/SIsilicon.3d.text/default_font.tres new file mode 100644 index 0000000..34c9b2b --- /dev/null +++ b/addons/SIsilicon.3d.text/default_font.tres @@ -0,0 +1,8 @@ +[gd_resource type="DynamicFont" load_steps=2 format=2] + +[ext_resource path="res://addons/SIsilicon.3d.text/default_font.ttf" type="DynamicFontData" id=1] + +[resource] +size = 100 +use_filter = true +font_data = ExtResource( 1 ) diff --git a/addons/SIsilicon.3d.text/default_font.ttf b/addons/SIsilicon.3d.text/default_font.ttf new file mode 100644 index 0000000..e5f34b2 Binary files /dev/null and b/addons/SIsilicon.3d.text/default_font.ttf differ diff --git a/addons/SIsilicon.3d.text/icon_label_3d.svg b/addons/SIsilicon.3d.text/icon_label_3d.svg new file mode 100644 index 0000000..4cf4522 --- /dev/null +++ b/addons/SIsilicon.3d.text/icon_label_3d.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/SIsilicon.3d.text/icon_label_3d.svg.import b/addons/SIsilicon.3d.text/icon_label_3d.svg.import new file mode 100644 index 0000000..6a1752e --- /dev/null +++ b/addons/SIsilicon.3d.text/icon_label_3d.svg.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_label_3d.svg-66ebb0fa17446da110a5ed153702e3c3.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/SIsilicon.3d.text/icon_label_3d.svg" +dest_files=[ "res://.import/icon_label_3d.svg-66ebb0fa17446da110a5ed153702e3c3.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/addons/SIsilicon.3d.text/label_3d.gd b/addons/SIsilicon.3d.text/label_3d.gd new file mode 100644 index 0000000..95788b6 --- /dev/null +++ b/addons/SIsilicon.3d.text/label_3d.gd @@ -0,0 +1,148 @@ +tool +extends Spatial + +export(String, MULTILINE) var text = "Text" setget set_text +export(float) var text_scale = 0.01 setget set_text_scale +export(float) var extrude = 0.0 setget set_extrude +export(Font) var font setget set_font; + +export(int, "Left", "Right", "Center", "Fill") var align setget set_align + +export(Color) var color = Color(0.6, 0.6, 0.6) setget set_color +export(float, 0, 1) var metallic = 0.0 setget set_metallic +export(float, 0, 1) var roughness = 0.5 setget set_roughness + +export(int) var max_steps = 256 setget set_max_steps +export(float) var step_size = 1.0 setget set_step_size + +var label +var viewport +var proxy +var material + +func _ready(): + for i in range(get_child_count()): + remove_child(get_child(0)) + + viewport = preload("text_viewport.tscn").instance() + label = viewport.get_node("Label") + add_child(viewport) + + proxy = MeshInstance.new() + proxy.mesh = CubeMesh.new() + proxy.material_override = preload("label_3d.material").duplicate() + material = proxy.material_override + + var view_texture = viewport.get_texture() + view_texture.flags = Texture.FLAG_FILTER + material.set_shader_param("text", view_texture) + add_child(proxy) + + set_align(align) + set_font(font) + set_text(text) + set_text_scale(text_scale) + set_extrude(extrude) + + set_color(color) + set_metallic(metallic) + set_roughness(roughness) + + set_max_steps(max_steps) + set_step_size(step_size) + + +func set_text(string): + text = string; + if label: + label.text = text + label.rect_size = Vector2() + label.force_update_transform() + + var size = label.rect_size + viewport.size = size + + viewport.render_target_update_mode = Viewport.UPDATE_ALWAYS + yield(get_tree(), "idle_frame") + + label.rect_size = Vector2() + label.force_update_transform() + + size = label.rect_size + viewport.size = size + + yield(get_tree(), "idle_frame") + viewport.render_target_update_mode = Viewport.UPDATE_DISABLED + + proxy.scale.x = size.x * text_scale + proxy.scale.y = size.y * text_scale + +func set_text_scale(scale): + text_scale = scale + if label: + var size = label.rect_size + if proxy: + proxy.scale.x = size.x * text_scale + proxy.scale.y = size.y * text_scale + +func set_extrude(ext): + extrude = ext + + if proxy: + proxy.scale.z = extrude if extrude != 0 else 1 + material.set_shader_param("extrude", extrude != 0) + + if extrude == 0 and proxy.mesh is CubeMesh: + proxy.mesh = QuadMesh.new() + proxy.mesh.size = Vector2(2, 2) + elif proxy.mesh is QuadMesh: + proxy.mesh = CubeMesh.new() + +func set_font(f): + font = f + if label: + if font: + label.add_font_override("font", font) + else: + label.add_font_override("font", preload("default_font.tres")) + set_text(text) + +func set_align(al): + align = al + if label: + match align: + 0: + label.align = Label.ALIGN_LEFT + 1: + label.align = Label.ALIGN_RIGHT + 2: + label.align = Label.ALIGN_CENTER + 3: + label.align = Label.ALIGN_FILL + + set_text(text) + +func set_color(col): + color = col + if material: + material.set_shader_param("albedo", color) + +func set_metallic(metal): + metallic = metal + if material: + material.set_shader_param("metallic", metallic) + +func set_roughness(rough): + roughness = rough + if material: + material.set_shader_param("roughness", roughness) + +func set_max_steps(max_s): + max_steps = max(max_s, 8) + if material: + material.set_shader_param("maxSteps", max_steps) + +func set_step_size(step_s): + step_size = max(step_s, 0) + if material: + material.set_shader_param("stepSize", step_size) diff --git a/addons/SIsilicon.3d.text/label_3d.material b/addons/SIsilicon.3d.text/label_3d.material new file mode 100644 index 0000000..b9c6f1e Binary files /dev/null and b/addons/SIsilicon.3d.text/label_3d.material differ diff --git a/addons/SIsilicon.3d.text/label_3d_converter.gd b/addons/SIsilicon.3d.text/label_3d_converter.gd new file mode 100644 index 0000000..fdbffae --- /dev/null +++ b/addons/SIsilicon.3d.text/label_3d_converter.gd @@ -0,0 +1,461 @@ +tool +extends Button + +signal mesh_generated(mesh_inst) + +const Label3D = preload("label_3d.gd") + +var label3d : Label3D + +var image_cache = PoolRealArray() + +# These variables are used to spread the marching_square function +# across multiple frames. +var finished_marching = false +var os_time + +func _on_Button_pressed(): + generate_geometry() + +func generate_geometry(): + $PopupDialog.popup_centered() + var text_scale = label3d.text_scale + var extrude = label3d.extrude + var viewport = label3d.get_node("Viewport") + + viewport.render_target_update_mode = Viewport.UPDATE_ALWAYS + yield(get_tree(), "idle_frame") + yield(get_tree(), "idle_frame") + viewport.render_target_update_mode = Viewport.UPDATE_DISABLED + + var image = viewport.get_texture().get_data() + image.lock() + + var edges = Dictionary() + finished_marching = false + os_time = OS.get_ticks_msec() + do_marching_squares(image, edges) + while not finished_marching: + yield(get_tree(), "idle_frame") + + # Contours are the edges and holes of the text. + # The paths are said edges combined with the holes for easier triangulation. + var contours = [] + collect_contours(edges.duplicate(), contours) + var paths = contours.duplicate(true) + decimate_holes(edges, paths, Rect2(0, 0, image.get_width(), image.get_height())) + + var triangle_data = [] + var vertex_data = [] + for path in paths: + path.points = douglas_peucker(path.points, 0.125) + # Edge data beyond this point will no longer be valid + + var triangles = [] + ear_clipping(path, triangles) + + # points are converted into vector3 here. + for p in path.points.size(): + var point = path.points[p] + point -= Vector2(image.get_width(), image.get_height()) / 2.0 + point *= text_scale * 2.0 + path.points[p] = Vector3(point.x, point.y, extrude) + + ArrayUtils.call_per_element(triangles, "offset_indices", vertex_data.size()) + + ArrayUtils.append_array(vertex_data, path.points) + ArrayUtils.append_array(triangle_data, triangles) + + if extrude != 0: + # Generate the back triangles + var original_vert_size = vertex_data.size() + for v in original_vert_size: + var vertex = vertex_data[v] + vertex_data.append(vertex * Vector3(1, 1, -1)) + + var tri_size = triangle_data.size() + for t in tri_size: + var triangle = triangle_data[t].duplicate() + triangle.offset_indices(original_vert_size) + triangle.reverse_order() + triangle_data.append(triangle) + + # And now the triangles inbetween + for path in paths: + var vertices = path.points.duplicate() + var points_size = path.points.size() + for vert in points_size: + var back_vertex = path.points[vert] * Vector3(1, 1, -1) + vertices.append(back_vertex) + + var triangles = [] + for i in points_size: + var index = i + vertex_data.size() + var a = index + var b = (index + 1) if i != points_size - 1 else vertex_data.size() + var c = index + points_size + var d = ((index + 1) if i != points_size - 1 else vertex_data.size()) + points_size + + triangles.append(Triangle.new(c, b, a)) + triangles.append(Triangle.new(d, b, c)) + + ArrayUtils.append_array(vertex_data, vertices) + ArrayUtils.append_array(triangle_data, triangles) + + var geom = SurfaceTool.new() + geom.clear() + + geom.begin(Mesh.PRIMITIVE_TRIANGLES) + for vert in vertex_data: + geom.add_vertex(vert) + for tri in triangle_data: + geom.add_index(tri.a) + geom.add_index(tri.b) + geom.add_index(tri.c) + geom.generate_normals() + + var material = SpatialMaterial.new() + material.albedo_color = label3d.color + material.metallic = label3d.metallic + material.roughness = label3d.roughness + + var mesh_inst = MeshInstance.new() + mesh_inst.mesh = geom.commit() + mesh_inst.material_override = material + mesh_inst.transform = label3d.transform + mesh_inst.name = label3d.name + "-mesh" + + emit_signal("mesh_generated", mesh_inst) + + image_cache.resize(0) + image.unlock() + $PopupDialog.hide() + + +func do_marching_squares(image, edges): + for y in image.get_height() - 1: + for x in image.get_width() - 1: + var i = Vector2(x, y) + + var p_ul = get_pixel(image, x, y) + var p_ll = get_pixel(image, x, y+1) + var p_ur = get_pixel(image, x+1, y) + var p_lr = get_pixel(image, x+1, y+1) + + var top = inverse_lerp(p_ul, p_ur, 1) + var bottom = inverse_lerp(p_ll, p_lr, 1) + var left = inverse_lerp(p_ul, p_ll, 1) + var right = inverse_lerp(p_ur, p_lr, 1) + + var ul = int(p_ul > 1) * 1 + var ll = int(p_ll > 1) * 2 + var ur = int(p_ur > 1) * 4 + var lr = int(p_lr > 1) * 8 + + var bit = ul | ll | ur | lr + + # Notice: cases 6 and 9 have not been implemented. + match bit: + # Corner Cases + 1: + create_edge(edges, i, x, y + left, x + top, y, Vector2.UP) + 2: + create_edge(edges, i, x + bottom, y + 1, x, y + left, Vector2.LEFT) + 4: + create_edge(edges, i, x + top, y, x + 1, y + right, Vector2.RIGHT) + 8: + create_edge(edges, i, x + 1, y + right, x + bottom, y + 1, Vector2.DOWN) + + # Edge Cases + 3: + create_edge(edges, i, x + bottom, y + 1, x + top, y, Vector2.UP) + 5: + create_edge(edges, i, x, y + left, x + 1, y + right, Vector2.RIGHT) + 10: + create_edge(edges, i, x + 1, y + right, x, y + left, Vector2.LEFT) + 12: + create_edge(edges, i, x + top, y, x + bottom, y + 1, Vector2.DOWN) + + # Inner Corner cases + 14: + create_edge(edges, i, x + top, y, x, y + left, Vector2.LEFT) + 13: + create_edge(edges, i, x, y + left, x + bottom, y + 1, Vector2.DOWN) + 11: + create_edge(edges, i, x + 1, y + right, x + top, y, Vector2.UP) + 7: + create_edge(edges, i, x + bottom, y + 1, x + 1, y + right, Vector2.RIGHT) + + if OS.get_ticks_msec() - os_time > 20: + var max_i = (image.get_width() - 1) * (image.get_height() - 1) + var curr_i = x + (y * image.get_width() - 1) + $PopupDialog/VBoxContainer/ProgressBar.value = float(curr_i) / max_i * 100 + + yield(get_tree(), "idle_frame") + os_time = OS.get_ticks_msec() + + finished_marching = true + +func collect_contours(edges, contours): + + var directions = [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT] + var coord = edges.keys()[0] + + var start_edge = edges[coord] + var contour = Contour.new(start_edge.direction == Vector2.UP || \ + start_edge.direction == Vector2.RIGHT) + contours.append(contour) + + contour.points.append(edges[coord].end) + contour.edges.append(edges[coord]) + var edge = edges[coord] + + edges.erase(coord) + + var loop_started = true + while loop_started or edge != start_edge: + var edge_found = false + + for dir in directions: + if edges.has(coord + dir) and edges[coord + dir].begin == edge.end: + edge_found = true + + coord += dir + contour.points.append(edges[coord].end) + contour.edges.append(edges[coord]) + edge = edges[coord] + edges.erase(coord) + + break + + if not edge_found: + break + + loop_started = false + + if not edges.empty(): + collect_contours(edges, contours) + +func decimate_holes(edges, contours, bounds): + for c in range(contours.size()-1, -1, -1): + var contour = contours[c] + if contour.is_hole: + for i in contour.edges.size(): + var edge = contour.edges[i] + var edge_found = false + + var dir = edge.direction + dir = Vector2(dir.y, -dir.x) + var pos = ((edge.begin + edge.end) / 2.0).floor() + var other_edge + + while not other_edge and bounds.has_point(pos): + pos += dir + if edges.has(pos): + other_edge = edges[pos] + + if other_edge: + + for other_contour in contours: + if other_contour == contour: + continue + + for j in other_contour.edges.size(): + var other_point = other_contour.edges[j] + if other_edge == other_point: + edge_found = true + other_contour.fuse_with(contour, j, i + 1) + break + + if edge_found: + break + + if edge_found: + break + + contours.remove(c) + +func douglas_peucker(points, tolerance): + var farthest = farthest_point(points) + + + # Farthest point not existing must mean the points only made of two, + # and cannot be simplified any further. + if not farthest: + return points + + if farthest.distance < tolerance: + return [points[0], points[-1]] + else: + var left = [] + var right = [] + ArrayUtils.split_array(points, farthest.index, left, right) + + left = douglas_peucker(left, tolerance) + right = douglas_peucker(right, tolerance) + + ArrayUtils.append_array(left, right) + return left + +func ear_clipping(contour, triangles): + var points = ArrayUtils.to_dictionary(contour.points) + + var i = 0 + var counter = 0 + while points.size() > 3: + counter += 1 + if(counter > 4000): + printerr("Hmmm... Infinite loop much?") + break + + var keys = points.keys() + var point_a = points[keys[i-1]] + var point_b = points[keys[i]] + var point_c = points[keys[(i+1) % keys.size()]] + + if (point_b - point_a).cross(point_c - point_b) < 0: + var has_point = false + for p in points: + var point = points[p] + if point == point_a or point == point_b or point == point_c: + continue + if point_inside_triangle(point, point_a, point_b, point_c): + has_point = true + break + + if not has_point: + var tri = Triangle.new(keys[i-1], keys[i], keys[(i+1) % keys.size()]) + triangles.append(tri) + points.erase(keys[i]) + + i = wrapi(i + 1, 0, points.size()) + + var keys = points.keys() + triangles.append(Triangle.new(keys[0], keys[1], keys[2])) + +# This returns a dictionary containing the farthest point, +# the distance associated with it, and its index in the array. +func farthest_point(points): + var first = points[0] + var last = points[-1] + + if points.size() < 3: + return + + var farthest + var max_dist = -1 + var index + for i in range(1, points.size() - 1): + var distance = distance_to_segment(points[i], first, last) + if distance > max_dist: + farthest = points[i] + max_dist = distance + index = i + + return {"point": farthest, "distance": max_dist, "index": index} + +func distance_to_segment(point, a, b): + # This is because I don't know how to snap a point onto a line, + # so I'm relying on Plane in the meantime. + var plane = Plane(Vector3(a.x, a.y, 0), + Vector3(b.x, b.y, 0), Vector3(a.x, a.y, 1)) + + var projected = plane.project(Vector3(point.x, point.y, 0)) + var t = inverse_lerp(a.x, b.x, projected.x) if a.x != b.x else \ + inverse_lerp(a.y, b.y, projected.y) + + var snapped = a.linear_interpolate(b, t) + return point.distance_squared_to(snapped) + +func vec_sign(p1, p2, p3): + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y) + +func point_inside_triangle(point, a, b, c): + var d1 = vec_sign(point, a, b) + var d2 = vec_sign(point, b, c) + var d3 = vec_sign(point, c, a) + + var has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) + var has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) + return not (has_neg and has_pos) + +func create_edge(edges, offset, p1x, p1y, p2x, p2y, dir): + edges[offset] = MSEdge.new(Vector2(p1x, p1y), Vector2(p2x, p2y), dir) + +func get_pixel(image : Image, x : int, y : int): + if x < 1 || y < 1 || x > image.get_width()-1 || y > image.get_height()-1: + return 0 + + var i = x + y * image.get_width() + if image_cache.size() > i and image_cache[i] != null: + return image_cache[i] + else: + var real = image.get_pixel(x, y).r; + if image_cache.size() <= i: + image_cache.resize(i+1) + image_cache[i] = real + return real + + +class MSEdge: + var begin : Vector2 + var end : Vector2 + + var coordinate : Vector2 + + # One of the four cardinal directions + var direction : Vector2 + + func _init(begin, end, direction): + self.begin = begin + self.end = end + self.direction = direction + + +class Contour: + var points := [] + var edges := [] + var is_hole : bool + + func _init(is_hole): + self.is_hole = is_hole + + func fuse_with(contour, self_point, other_point): + var points_behind = [] + var points_after = [] + + ArrayUtils.split_array(points, self_point + 1, points_behind, points_after) + + var shifted_contour = contour.points.duplicate() + ArrayUtils.shift_array(shifted_contour, other_point) + + ArrayUtils.append_array(points_behind, shifted_contour) + points_behind.append(shifted_contour[0]) + points_behind.append(points[self_point]) + ArrayUtils.append_array(points_behind, points_after) + + points = points_behind + + +class Triangle: + var a : int + var b : int + var c : int + + func _init(a, b, c): + self.a = a + self.b = b + self.c = c + + func offset_indices(offset): + a += offset + b += offset + c += offset + + func reverse_order(): + var temp = a + a = b + b = temp + + func duplicate(): + return Triangle.new(a, b, c) diff --git a/addons/SIsilicon.3d.text/label_3d_converter.tscn b/addons/SIsilicon.3d.text/label_3d_converter.tscn new file mode 100644 index 0000000..1c02ff0 --- /dev/null +++ b/addons/SIsilicon.3d.text/label_3d_converter.tscn @@ -0,0 +1,40 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/SIsilicon.3d.text/label_3d_converter.gd" type="Script" id=1] + +[node name="Button" type="Button"] +margin_right = 172.0 +margin_bottom = 20.0 +text = "Convert to MeshInstance" +flat = true +script = ExtResource( 1 ) + +[node name="PopupDialog" type="PopupDialog" parent="."] +visible = true +margin_left = 340.0 +margin_top = 200.0 +margin_right = 610.0 +margin_bottom = 268.0 +mouse_default_cursor_shape = 5 +popup_exclusive = true + +[node name="VBoxContainer" type="VBoxContainer" parent="PopupDialog"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 6.0 +margin_top = 6.0 +margin_right = -6.0 +margin_bottom = -6.0 + +[node name="Label" type="Label" parent="PopupDialog/VBoxContainer"] +margin_right = 258.0 +margin_bottom = 14.0 +text = "Generating geometry" +align = 1 + +[node name="ProgressBar" type="ProgressBar" parent="PopupDialog/VBoxContainer"] +margin_top = 18.0 +margin_right = 258.0 +margin_bottom = 32.0 +step = 1.0 +[connection signal="pressed" from="." to="." method="_on_Button_pressed"] diff --git a/addons/SIsilicon.3d.text/plugin.cfg b/addons/SIsilicon.3d.text/plugin.cfg new file mode 100644 index 0000000..8c20b54 --- /dev/null +++ b/addons/SIsilicon.3d.text/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="3D Text" +description="This plugin allows you to make 3D text within the Godot engine." +author="SIsilicon" +version="0.6.1" +script="3d_text_plugin.gd" \ No newline at end of file diff --git a/addons/SIsilicon.3d.text/text_viewport.tscn b/addons/SIsilicon.3d.text/text_viewport.tscn new file mode 100644 index 0000000..18e2279 --- /dev/null +++ b/addons/SIsilicon.3d.text/text_viewport.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=7 format=2] + +[ext_resource path="res://addons/SIsilicon.3d.text/default_font.ttf" type="DynamicFontData" id=1] + +[sub_resource type="Environment" id=1] +background_mode = 1 +background_energy = 0.0 + +[sub_resource type="World" id=2] +environment = SubResource( 1 ) + +[sub_resource type="DynamicFont" id=3] +size = 100 +font_data = ExtResource( 1 ) + +[sub_resource type="Shader" id=4] +code = "shader_type canvas_item; + +uniform int Max = 3; + +float getAlpha(sampler2D tex, ivec2 coord) { + return texelFetch(tex, coord, 0).r; +} + +void fragment() { + ivec2 coord = ivec2(FRAGCOORD.xy); + bool inside = getAlpha(SCREEN_TEXTURE, coord) > 0.5; + + float dist = float((Max+1) * (Max+1)); + for(int y = -Max; y <= Max; y++) { + for(int x = -Max; x <= Max; x++) { + if(x == 0 && y == 0) continue; + ivec2 delta = ivec2(x,y); + + if(inside) { + if(getAlpha(SCREEN_TEXTURE, coord + delta) < 0.5) { + dist = min(dist, float(x*x + y*y)); + } + } else { + if(getAlpha(SCREEN_TEXTURE, coord + delta) > 0.5) { + dist = min(dist, float(x*x + y*y)); + } + } + } + } + dist = sqrt(dist) * (float(!inside) * 2.0 - 1.0); + + COLOR = vec4(dist / float(Max+1)); +}" + +[sub_resource type="ShaderMaterial" id=5] +shader = SubResource( 4 ) +shader_param/Max = 10 + +[node name="Viewport" type="Viewport"] +size = Vector2( 190, 114 ) +own_world = true +world = SubResource( 2 ) +transparent_bg = true +handle_input_locally = false +render_target_update_mode = 1 +gui_disable_input = true + +[node name="Label" type="Label" parent="."] +margin_right = 190.0 +margin_bottom = 114.0 +size_flags_horizontal = 4 +custom_fonts/font = SubResource( 3 ) +text = "Text" + +[node name="ColorRect" type="ColorRect" parent="Label"] +material = SubResource( 5 ) +anchor_right = 1.0 +anchor_bottom = 1.0 diff --git a/project.godot b/project.godot index 0a0e2db..09f49ea 100644 --- a/project.godot +++ b/project.godot @@ -8,9 +8,14 @@ config_version=4 -_global_script_classes=[ ] +_global_script_classes=[ { +"base": "Reference", +"class": "ArrayUtils", +"language": "GDScript", +"path": "res://addons/SIsilicon.3d.text/array_utils.gd" +} ] _global_script_class_icons={ - +"ArrayUtils": "" } [application] @@ -20,7 +25,7 @@ config/icon="res://icon.png" [editor_plugins] -enabled=PoolStringArray( "godot_xterm" ) +enabled=PoolStringArray( "SIsilicon.3d.text", "godot_xterm" ) [rendering]