mirror of
https://github.com/lihop/godot-xterm.git
synced 2024-11-25 10:40:25 +01:00
Add 3D Text plugin
This commit is contained in:
parent
57aed28a0e
commit
0130ce96db
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
|
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={
|
_global_script_class_icons={
|
||||||
|
"ArrayUtils": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
@ -20,7 +25,7 @@ config/icon="res://icon.png"
|
||||||
|
|
||||||
[editor_plugins]
|
[editor_plugins]
|
||||||
|
|
||||||
enabled=PoolStringArray( "godot_xterm" )
|
enabled=PoolStringArray( "SIsilicon.3d.text", "godot_xterm" )
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue