mirror of
https://github.com/lihop/godot-xterm.git
synced 2024-11-22 09:40:25 +01:00
parent
2a5e07aa48
commit
5a487a67c2
14 changed files with 913 additions and 3 deletions
64
addons/SIsilicon.3d.text/3d_text_plugin.gd
Normal file
64
addons/SIsilicon.3d.text/3d_text_plugin.gd
Normal file
|
@ -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())
|
21
addons/SIsilicon.3d.text/LICENSE
Normal file
21
addons/SIsilicon.3d.text/LICENSE
Normal file
|
@ -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.
|
47
addons/SIsilicon.3d.text/array_utils.gd
Normal file
47
addons/SIsilicon.3d.text/array_utils.gd
Normal file
|
@ -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)
|
8
addons/SIsilicon.3d.text/default_font.tres
Normal file
8
addons/SIsilicon.3d.text/default_font.tres
Normal file
|
@ -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 )
|
BIN
addons/SIsilicon.3d.text/default_font.ttf
Normal file
BIN
addons/SIsilicon.3d.text/default_font.ttf
Normal file
Binary file not shown.
1
addons/SIsilicon.3d.text/icon_label_3d.svg
Normal file
1
addons/SIsilicon.3d.text/icon_label_3d.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 3a1.0001 1.0001 0 0 0 -.70703.29297l-4 4a1.0001 1.0001 0 0 0 0 1.4141l4 4a1.0001 1.0001 0 0 0 .70703.29297h8a1.0001 1.0001 0 0 0 1-1v-8a1.0001 1.0001 0 0 0 -1-1h-8zm-1 4a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1z" fill="#fc9c9c" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 367 B |
34
addons/SIsilicon.3d.text/icon_label_3d.svg.import
Normal file
34
addons/SIsilicon.3d.text/icon_label_3d.svg.import
Normal file
|
@ -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
|
148
addons/SIsilicon.3d.text/label_3d.gd
Normal file
148
addons/SIsilicon.3d.text/label_3d.gd
Normal file
|
@ -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)
|
BIN
addons/SIsilicon.3d.text/label_3d.material
Normal file
BIN
addons/SIsilicon.3d.text/label_3d.material
Normal file
Binary file not shown.
461
addons/SIsilicon.3d.text/label_3d_converter.gd
Normal file
461
addons/SIsilicon.3d.text/label_3d_converter.gd
Normal file
|
@ -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)
|
40
addons/SIsilicon.3d.text/label_3d_converter.tscn
Normal file
40
addons/SIsilicon.3d.text/label_3d_converter.tscn
Normal file
|
@ -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"]
|
7
addons/SIsilicon.3d.text/plugin.cfg
Normal file
7
addons/SIsilicon.3d.text/plugin.cfg
Normal file
|
@ -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"
|
74
addons/SIsilicon.3d.text/text_viewport.tscn
Normal file
74
addons/SIsilicon.3d.text/text_viewport.tscn
Normal file
|
@ -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
|
|
@ -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]
|
||||
|
||||
|
|
Loading…
Reference in a new issue