Add 3D Text plugin

Former-commit-id: 0130ce96db
This commit is contained in:
Leroy Hopson 2020-09-28 10:30:11 +07:00
parent 2a5e07aa48
commit 5a487a67c2
14 changed files with 913 additions and 3 deletions

View 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())

View 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.

View 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)

View 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 )

Binary file not shown.

View 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

View 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

View 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)

Binary file not shown.

View 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)

View 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"]

View 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"

View 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

View file

@ -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]