731 lines
22 KiB
GDScript
731 lines
22 KiB
GDScript
class_name SurfaceDrawingCanvas
|
|
extends Node3D
|
|
|
|
const GUIDED_PIXEL_FILL: float = 0.88
|
|
const SURFACE_OFFSET: float = 0.012
|
|
const LAYER_OFFSET_STEP: float = 0.0002
|
|
const SURFACE_SAMPLE_DEPTH: float = 0.45
|
|
const FINALIZED_VISIBILITY_RANGE: float = 96.0
|
|
const FINALIZED_VISIBILITY_MARGIN: float = 12.0
|
|
const FINALIZED_MAX_SUBDIVISIONS: int = 32
|
|
|
|
var canvas_id: String = ""
|
|
var grid_width: int = 0
|
|
var grid_height: int = 0
|
|
var cell_size: float = 0.0
|
|
var revision: int = -1
|
|
var creator_fingerprint: String = ""
|
|
var participant_fingerprints: Array[String] = []
|
|
|
|
var _surface_origin: Vector3
|
|
var _surface_normal: Vector3
|
|
var _surface_tangent: Vector3
|
|
var _surface_bitangent: Vector3
|
|
var _cell_buffer: SurfaceDrawingCellBuffer
|
|
var _cell_surface_transforms: Array[Transform3D] = []
|
|
var _pixel_image: Image
|
|
var _pixel_texture: ImageTexture
|
|
var _pixel_instance: MeshInstance3D
|
|
var _grid_instance: MeshInstance3D
|
|
var _guide_visible: bool = true
|
|
var _finalized: bool = false
|
|
var _layer: int = 0
|
|
var _stencil_requested_visible: bool = false
|
|
var _relationships: PlayerRelationshipStore
|
|
var _solid_surface_mask: int = 1
|
|
|
|
|
|
func setup(
|
|
data: Dictionary,
|
|
relationships: PlayerRelationshipStore,
|
|
solid_surface_mask: int = 1,
|
|
cell_buffer: SurfaceDrawingCellBuffer = null,
|
|
) -> bool:
|
|
if (
|
|
(cell_buffer == null and not SurfaceDrawingProtocol.validate_canvas_state(data))
|
|
or (
|
|
cell_buffer != null
|
|
and not SurfaceDrawingProtocol.validate_canvas_metadata(data)
|
|
)
|
|
):
|
|
return false
|
|
canvas_id = str(data["canvas_id"])
|
|
grid_width = int(data["width"])
|
|
grid_height = int(data["height"])
|
|
cell_size = float(data["cell_size"])
|
|
revision = int(data["revision"])
|
|
creator_fingerprint = str(data["creator_fingerprint"])
|
|
_finalized = bool(data.get("finalized", false))
|
|
_guide_visible = bool(data.get("guide_visible", true)) and not _finalized
|
|
_layer = int(data.get("layer", 0))
|
|
_surface_origin = SurfaceDrawingProtocol.array_to_vector(data["origin"])
|
|
_surface_normal = SurfaceDrawingProtocol.array_to_vector(
|
|
data["normal"]
|
|
).normalized()
|
|
_surface_tangent = SurfaceDrawingProtocol.array_to_vector(
|
|
data["tangent"]
|
|
)
|
|
_surface_tangent = (
|
|
_surface_tangent - _surface_normal * _surface_tangent.dot(_surface_normal)
|
|
).normalized()
|
|
if _surface_normal.is_zero_approx() or _surface_tangent.is_zero_approx():
|
|
return false
|
|
_surface_bitangent = _surface_normal.cross(_surface_tangent).normalized()
|
|
_relationships = relationships
|
|
_solid_surface_mask = solid_surface_mask
|
|
_cell_buffer = cell_buffer
|
|
if _cell_buffer == null:
|
|
_cell_buffer = SurfaceDrawingCellBuffer.new()
|
|
if not _cell_buffer.configure_from_state(data, true):
|
|
return false
|
|
elif _cell_buffer.width != grid_width or _cell_buffer.height != grid_height:
|
|
return false
|
|
participant_fingerprints = _cell_buffer.get_participants()
|
|
if _finalized:
|
|
_build_finalized_pixel_renderer()
|
|
else:
|
|
var samples: Dictionary = _sample_grid_vertices()
|
|
_build_grid(samples)
|
|
_build_pixel_renderer(samples)
|
|
return true
|
|
|
|
|
|
func apply_update(data: Dictionary) -> bool:
|
|
if (
|
|
not SurfaceDrawingProtocol.validate_canvas_update(data)
|
|
or str(data["canvas_id"]) != canvas_id
|
|
or int(data["revision"]) <= revision
|
|
):
|
|
return false
|
|
if _cell_buffer == null or not _cell_buffer.apply_edits(data["edits"]):
|
|
return false
|
|
return _finish_buffered_update(data)
|
|
|
|
|
|
func apply_buffered_update(data: Dictionary) -> bool:
|
|
if (
|
|
not SurfaceDrawingProtocol.validate_canvas_update(data)
|
|
or str(data["canvas_id"]) != canvas_id
|
|
or int(data["revision"]) <= revision
|
|
or _cell_buffer == null
|
|
):
|
|
return false
|
|
return _finish_buffered_update(data)
|
|
|
|
|
|
func _finish_buffered_update(data: Dictionary) -> bool:
|
|
_cell_buffer.ensure_participants(data.get("participant_fingerprints", []))
|
|
participant_fingerprints = _cell_buffer.get_participants()
|
|
revision = int(data["revision"])
|
|
if is_hidden_by_relationship():
|
|
_rebuild_pixel_image()
|
|
else:
|
|
_apply_pixel_edits(data["edits"])
|
|
_refresh_grid_visibility()
|
|
return true
|
|
|
|
|
|
func apply_guide_update(data: Dictionary) -> bool:
|
|
if (
|
|
not SurfaceDrawingProtocol.validate_guide_update(data)
|
|
or str(data["canvas_id"]) != canvas_id
|
|
or int(data["revision"]) <= revision
|
|
):
|
|
return false
|
|
revision = int(data["revision"])
|
|
participant_fingerprints = _participants_from_update(
|
|
data, participant_fingerprints
|
|
)
|
|
if _cell_buffer != null:
|
|
_cell_buffer.ensure_participants(participant_fingerprints)
|
|
participant_fingerprints = _cell_buffer.get_participants()
|
|
_finalized = bool(data["finalized"])
|
|
_guide_visible = bool(data["guide_visible"]) and not _finalized
|
|
if _finalized:
|
|
_destroy_grid()
|
|
_cell_surface_transforms.clear()
|
|
_build_finalized_pixel_renderer()
|
|
else:
|
|
_refresh_grid_visibility()
|
|
_configure_render_distance()
|
|
return true
|
|
|
|
|
|
func refresh_relationship_visibility() -> void:
|
|
_rebuild_pixel_image()
|
|
if _pixel_instance != null:
|
|
_pixel_instance.visible = not is_hidden_by_relationship()
|
|
_refresh_grid_visibility()
|
|
|
|
|
|
func is_hidden_by_relationship() -> bool:
|
|
if _relationships == null:
|
|
return false
|
|
for fingerprint: String in participant_fingerprints:
|
|
if _relationships.is_blocked(fingerprint):
|
|
return true
|
|
return false
|
|
|
|
|
|
func set_stencil_visible(should_be_visible: bool) -> void:
|
|
_stencil_requested_visible = should_be_visible
|
|
_refresh_grid_visibility()
|
|
|
|
|
|
func is_guide_visible() -> bool:
|
|
return _guide_visible
|
|
|
|
|
|
func is_finalized() -> bool:
|
|
return _finalized
|
|
|
|
|
|
func get_rendered_pixel_size() -> float:
|
|
return cell_size
|
|
|
|
|
|
func get_rendered_pixel_count() -> int:
|
|
if is_hidden_by_relationship():
|
|
return 0
|
|
return _cell_buffer.get_painted_count() if _cell_buffer != null else 0
|
|
|
|
|
|
func get_export_image() -> Image:
|
|
if _pixel_image == null or _pixel_image.is_empty():
|
|
return null
|
|
return _pixel_image.duplicate() as Image
|
|
|
|
|
|
func has_guide_geometry() -> bool:
|
|
return _grid_instance != null and is_instance_valid(_grid_instance)
|
|
|
|
|
|
func contains_world_point(world_point: Vector3, tolerance: float = 0.3) -> bool:
|
|
var relative: Vector3 = world_point - _surface_origin
|
|
if absf(relative.dot(_surface_normal)) > tolerance:
|
|
return false
|
|
var half_width: float = float(grid_width) * cell_size * 0.5
|
|
var half_height: float = float(grid_height) * cell_size * 0.5
|
|
var horizontal: float = relative.dot(_surface_tangent)
|
|
var vertical: float = relative.dot(_surface_bitangent)
|
|
return (
|
|
horizontal >= -half_width
|
|
and horizontal < half_width
|
|
and vertical >= -half_height
|
|
and vertical < half_height
|
|
)
|
|
|
|
|
|
func get_surface_plane_distance(world_point: Vector3) -> float:
|
|
return absf((world_point - _surface_origin).dot(_surface_normal))
|
|
|
|
|
|
func cell_at_world_point(world_point: Vector3) -> Vector2i:
|
|
var relative: Vector3 = world_point - _surface_origin
|
|
var half_width: float = float(grid_width) * cell_size * 0.5
|
|
var half_height: float = float(grid_height) * cell_size * 0.5
|
|
var x: int = floori(
|
|
(relative.dot(_surface_tangent) + half_width) / cell_size
|
|
)
|
|
var y: int = floori(
|
|
(relative.dot(_surface_bitangent) + half_height) / cell_size
|
|
)
|
|
if x < 0 or x >= grid_width or y < 0 or y >= grid_height:
|
|
return Vector2i(-1, -1)
|
|
return Vector2i(x, y)
|
|
|
|
|
|
func get_cell_world_position(x: int, y: int) -> Vector3:
|
|
var horizontal: float = (
|
|
(float(x) + 0.5 - float(grid_width) * 0.5) * cell_size
|
|
)
|
|
var vertical: float = (
|
|
(float(y) + 0.5 - float(grid_height) * 0.5) * cell_size
|
|
)
|
|
return (
|
|
_surface_origin
|
|
+ _surface_tangent * horizontal
|
|
+ _surface_bitangent * vertical
|
|
)
|
|
|
|
|
|
func get_surface_normal() -> Vector3:
|
|
return _surface_normal
|
|
|
|
|
|
func get_surface_tangent() -> Vector3:
|
|
return _surface_tangent
|
|
|
|
|
|
func get_surface_bitangent() -> Vector3:
|
|
return _surface_bitangent
|
|
|
|
|
|
func get_cell_surface_transform(
|
|
x: int,
|
|
y: int,
|
|
fill: float = 0.94,
|
|
) -> Transform3D:
|
|
if x < 0 or x >= grid_width or y < 0 or y >= grid_height:
|
|
return Transform3D.IDENTITY
|
|
var key: int = _cell_key(x, y)
|
|
var local_transform: Transform3D = (
|
|
_cell_surface_transforms[key]
|
|
if key >= 0 and key < _cell_surface_transforms.size()
|
|
else _sample_cell_transform(x, y, 1.0)
|
|
)
|
|
var scaled_basis: Basis = local_transform.basis
|
|
var resolved_fill: float = clampf(fill, 0.05, 1.0)
|
|
scaled_basis.x *= resolved_fill
|
|
scaled_basis.y *= resolved_fill
|
|
local_transform.basis = scaled_basis
|
|
return global_transform * local_transform
|
|
|
|
|
|
func get_authoritative_cells() -> Array[Dictionary]:
|
|
return _cell_buffer.to_cells() if _cell_buffer != null else []
|
|
|
|
|
|
func _build_grid(samples: Dictionary) -> void:
|
|
if _grid_instance != null:
|
|
_grid_instance.queue_free()
|
|
_grid_instance = null
|
|
if _finalized:
|
|
return
|
|
var immediate := ImmediateMesh.new()
|
|
var material := StandardMaterial3D.new()
|
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
material.albedo_color = Color(0.75, 0.94, 0.96, 0.38)
|
|
material.no_depth_test = false
|
|
immediate.surface_begin(Mesh.PRIMITIVE_LINES, material)
|
|
for x: int in range(grid_width + 1):
|
|
for y_segment: int in range(grid_height):
|
|
_add_grid_vertex(immediate, x, y_segment, samples)
|
|
_add_grid_vertex(immediate, x, y_segment + 1, samples)
|
|
for y: int in range(grid_height + 1):
|
|
for x_segment: int in range(grid_width):
|
|
_add_grid_vertex(immediate, x_segment, y, samples)
|
|
_add_grid_vertex(immediate, x_segment + 1, y, samples)
|
|
immediate.surface_end()
|
|
_grid_instance = MeshInstance3D.new()
|
|
_grid_instance.name = "PixelGrid"
|
|
_grid_instance.mesh = immediate
|
|
_grid_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
add_child(_grid_instance)
|
|
_refresh_grid_visibility()
|
|
|
|
|
|
func _add_grid_vertex(
|
|
immediate: ImmediateMesh,
|
|
x: int,
|
|
y: int,
|
|
samples: Dictionary,
|
|
) -> void:
|
|
immediate.surface_add_vertex(
|
|
_sample_position(samples, x, y, _surface_offset() * 1.5)
|
|
)
|
|
|
|
|
|
func _build_pixel_renderer(samples: Dictionary) -> void:
|
|
if _pixel_instance != null:
|
|
_pixel_instance.queue_free()
|
|
_pixel_instance = null
|
|
var vertex_width: int = grid_width + 1
|
|
var vertex_count: int = vertex_width * (grid_height + 1)
|
|
var cell_count: int = grid_width * grid_height
|
|
var vertices := PackedVector3Array()
|
|
var texture_coordinates := PackedVector2Array()
|
|
var indices := PackedInt32Array()
|
|
vertices.resize(vertex_count)
|
|
texture_coordinates.resize(vertex_count)
|
|
indices.resize(cell_count * 6)
|
|
_cell_surface_transforms.clear()
|
|
if not _finalized:
|
|
_cell_surface_transforms.resize(cell_count)
|
|
for y: int in range(grid_height + 1):
|
|
for x: int in range(grid_width + 1):
|
|
var vertex_index: int = y * vertex_width + x
|
|
vertices[vertex_index] = _sample_position(
|
|
samples, x, y, _surface_offset()
|
|
)
|
|
texture_coordinates[vertex_index] = Vector2(
|
|
float(x) / float(grid_width),
|
|
1.0 - float(y) / float(grid_height),
|
|
)
|
|
for y: int in range(grid_height):
|
|
for x: int in range(grid_width):
|
|
var cell_index: int = _cell_key(x, y)
|
|
if not _finalized:
|
|
_cell_surface_transforms[cell_index] = _transform_from_samples(
|
|
samples, x, y
|
|
)
|
|
var vertex_offset: int = y * vertex_width + x
|
|
var index_offset: int = cell_index * 6
|
|
indices[index_offset] = vertex_offset
|
|
indices[index_offset + 1] = vertex_offset + 1
|
|
indices[index_offset + 2] = vertex_offset + vertex_width + 1
|
|
indices[index_offset + 3] = vertex_offset
|
|
indices[index_offset + 4] = vertex_offset + vertex_width + 1
|
|
indices[index_offset + 5] = vertex_offset + vertex_width
|
|
_rebuild_pixel_image()
|
|
var arrays: Array = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_TEX_UV] = texture_coordinates
|
|
arrays[Mesh.ARRAY_INDEX] = indices
|
|
var mesh := ArrayMesh.new()
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
var material := StandardMaterial3D.new()
|
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
material.albedo_color = Color.WHITE
|
|
material.albedo_texture = _pixel_texture
|
|
material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
|
|
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
|
|
material.alpha_scissor_threshold = 0.5
|
|
material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
mesh.surface_set_material(0, material)
|
|
_pixel_instance = MeshInstance3D.new()
|
|
_pixel_instance.name = "ArtworkTexture"
|
|
_pixel_instance.mesh = mesh
|
|
_pixel_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
add_child(_pixel_instance)
|
|
_pixel_instance.visible = not is_hidden_by_relationship()
|
|
_configure_render_distance()
|
|
|
|
|
|
func _build_finalized_pixel_renderer() -> void:
|
|
if _pixel_instance != null:
|
|
_pixel_instance.queue_free()
|
|
_pixel_instance = null
|
|
var step: int = maxi(
|
|
1,
|
|
ceili(float(grid_width) / float(FINALIZED_MAX_SUBDIVISIONS)),
|
|
)
|
|
var coordinates := PackedInt32Array()
|
|
var coordinate: int = 0
|
|
while coordinate < grid_width:
|
|
coordinates.append(coordinate)
|
|
coordinate += step
|
|
if coordinates.is_empty() or coordinates[-1] != grid_width:
|
|
coordinates.append(grid_width)
|
|
var vertex_width: int = coordinates.size()
|
|
var vertices := PackedVector3Array()
|
|
var texture_coordinates := PackedVector2Array()
|
|
var indices := PackedInt32Array()
|
|
vertices.resize(vertex_width * vertex_width)
|
|
texture_coordinates.resize(vertex_width * vertex_width)
|
|
indices.resize((vertex_width - 1) * (vertex_width - 1) * 6)
|
|
var half_width: float = float(grid_width) * cell_size * 0.5
|
|
var half_height: float = float(grid_height) * cell_size * 0.5
|
|
for lattice_y: int in vertex_width:
|
|
var cell_y: int = coordinates[lattice_y]
|
|
var vertical: float = -half_height + float(cell_y) * cell_size
|
|
for lattice_x: int in vertex_width:
|
|
var cell_x: int = coordinates[lattice_x]
|
|
var horizontal: float = -half_width + float(cell_x) * cell_size
|
|
var expected: Vector3 = (
|
|
_surface_origin
|
|
+ _surface_tangent * horizontal
|
|
+ _surface_bitangent * vertical
|
|
)
|
|
var sampled: Dictionary = _sample_surface(expected)
|
|
var vertex_index: int = lattice_y * vertex_width + lattice_x
|
|
var point: Vector3 = sampled["position"]
|
|
var normal: Vector3 = sampled["normal"]
|
|
vertices[vertex_index] = to_local(
|
|
point + normal * _surface_offset()
|
|
)
|
|
texture_coordinates[vertex_index] = Vector2(
|
|
float(cell_x) / float(grid_width),
|
|
1.0 - float(cell_y) / float(grid_height),
|
|
)
|
|
for lattice_y: int in range(vertex_width - 1):
|
|
for lattice_x: int in range(vertex_width - 1):
|
|
var cell_index: int = lattice_y * (vertex_width - 1) + lattice_x
|
|
var vertex_offset: int = lattice_y * vertex_width + lattice_x
|
|
var index_offset: int = cell_index * 6
|
|
indices[index_offset] = vertex_offset
|
|
indices[index_offset + 1] = vertex_offset + 1
|
|
indices[index_offset + 2] = vertex_offset + vertex_width + 1
|
|
indices[index_offset + 3] = vertex_offset
|
|
indices[index_offset + 4] = vertex_offset + vertex_width + 1
|
|
indices[index_offset + 5] = vertex_offset + vertex_width
|
|
if _pixel_image == null or _pixel_texture == null:
|
|
_rebuild_pixel_image()
|
|
var arrays: Array = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_TEX_UV] = texture_coordinates
|
|
arrays[Mesh.ARRAY_INDEX] = indices
|
|
var mesh := ArrayMesh.new()
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
var material := StandardMaterial3D.new()
|
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
material.albedo_color = Color.WHITE
|
|
material.albedo_texture = _pixel_texture
|
|
material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
|
|
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
|
|
material.alpha_scissor_threshold = 0.5
|
|
material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
mesh.surface_set_material(0, material)
|
|
_pixel_instance = MeshInstance3D.new()
|
|
_pixel_instance.name = "ArtworkTexture"
|
|
_pixel_instance.mesh = mesh
|
|
_pixel_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
add_child(_pixel_instance)
|
|
_pixel_instance.visible = not is_hidden_by_relationship()
|
|
_configure_render_distance()
|
|
|
|
|
|
func _rebuild_pixel_image() -> void:
|
|
if _cell_buffer == null:
|
|
return
|
|
_pixel_image = Image.create_from_data(
|
|
grid_width,
|
|
grid_height,
|
|
false,
|
|
Image.FORMAT_RGBA8,
|
|
_cell_buffer.to_rgba8_data(is_hidden_by_relationship()),
|
|
)
|
|
if _pixel_texture == null:
|
|
_pixel_texture = ImageTexture.create_from_image(_pixel_image)
|
|
else:
|
|
_pixel_texture.update(_pixel_image)
|
|
|
|
|
|
func _apply_pixel_edits(edits: Array) -> void:
|
|
if _pixel_image == null or _pixel_texture == null:
|
|
_rebuild_pixel_image()
|
|
return
|
|
for edit_value: Variant in edits:
|
|
var edit: Dictionary = edit_value
|
|
var x: int = int(edit["x"])
|
|
var y: int = int(edit["y"])
|
|
var cell: Dictionary = _cell_buffer.get_cell(x, y)
|
|
_set_image_cell(x, y, cell)
|
|
_pixel_texture.update(_pixel_image)
|
|
|
|
|
|
func _set_image_cell(x: int, y: int, cell: Dictionary) -> void:
|
|
if (
|
|
_pixel_image == null
|
|
or x < 0
|
|
or x >= grid_width
|
|
or y < 0
|
|
or y >= grid_height
|
|
):
|
|
return
|
|
_pixel_image.set_pixel(x, _image_y(y), _visible_cell_color(cell))
|
|
|
|
|
|
func _visible_cell_color(cell: Dictionary) -> Color:
|
|
if cell.is_empty():
|
|
return Color.TRANSPARENT
|
|
var color_id := StringName(str(cell.get("color_id", "")))
|
|
return (
|
|
SurfaceDrawingPalette.get_color(color_id)
|
|
if SurfaceDrawingPalette.has_color(color_id)
|
|
else Color.TRANSPARENT
|
|
)
|
|
|
|
|
|
func _image_y(cell_y: int) -> int:
|
|
return grid_height - 1 - cell_y
|
|
|
|
|
|
func _sample_grid_vertices() -> Dictionary:
|
|
var vertex_width: int = grid_width + 1
|
|
var vertex_count: int = vertex_width * (grid_height + 1)
|
|
var positions := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
positions.resize(vertex_count)
|
|
normals.resize(vertex_count)
|
|
var half_width: float = float(grid_width) * cell_size * 0.5
|
|
var half_height: float = float(grid_height) * cell_size * 0.5
|
|
var inverse_basis: Basis = global_basis.inverse()
|
|
for y: int in range(grid_height + 1):
|
|
var vertical: float = -half_height + float(y) * cell_size
|
|
for x: int in range(grid_width + 1):
|
|
var horizontal: float = -half_width + float(x) * cell_size
|
|
var expected: Vector3 = (
|
|
_surface_origin
|
|
+ _surface_tangent * horizontal
|
|
+ _surface_bitangent * vertical
|
|
)
|
|
var sampled: Dictionary = _sample_surface(expected)
|
|
var index: int = y * vertex_width + x
|
|
positions[index] = to_local(sampled["position"])
|
|
normals[index] = (
|
|
inverse_basis * (sampled["normal"] as Vector3)
|
|
).normalized()
|
|
return {"positions": positions, "normals": normals}
|
|
|
|
|
|
func _sample_position(
|
|
samples: Dictionary,
|
|
x: int,
|
|
y: int,
|
|
offset: float,
|
|
) -> Vector3:
|
|
var index: int = y * (grid_width + 1) + x
|
|
var positions: PackedVector3Array = samples["positions"]
|
|
var normals: PackedVector3Array = samples["normals"]
|
|
return positions[index] + normals[index] * offset
|
|
|
|
|
|
func _transform_from_samples(
|
|
samples: Dictionary,
|
|
x: int,
|
|
y: int,
|
|
) -> Transform3D:
|
|
var bottom_left: Vector3 = _sample_position(
|
|
samples, x, y, _surface_offset()
|
|
)
|
|
var bottom_right: Vector3 = _sample_position(
|
|
samples, x + 1, y, _surface_offset()
|
|
)
|
|
var top_left: Vector3 = _sample_position(
|
|
samples, x, y + 1, _surface_offset()
|
|
)
|
|
var top_right: Vector3 = _sample_position(
|
|
samples, x + 1, y + 1, _surface_offset()
|
|
)
|
|
var horizontal: Vector3 = (
|
|
(bottom_right - bottom_left) + (top_right - top_left)
|
|
) * 0.5
|
|
var vertical: Vector3 = (
|
|
(top_left - bottom_left) + (top_right - bottom_right)
|
|
) * 0.5
|
|
var normal: Vector3 = horizontal.cross(vertical).normalized()
|
|
if horizontal.is_zero_approx() or vertical.is_zero_approx() or normal.is_zero_approx():
|
|
return _sample_cell_transform(x, y, 1.0)
|
|
var center: Vector3 = (
|
|
bottom_left + bottom_right + top_left + top_right
|
|
) * 0.25
|
|
return Transform3D(Basis(horizontal, vertical, normal), center)
|
|
|
|
|
|
func _sample_cell_transform(
|
|
x: int,
|
|
y: int,
|
|
fill: float = GUIDED_PIXEL_FILL,
|
|
) -> Transform3D:
|
|
var expected: Vector3 = get_cell_world_position(x, y)
|
|
var sampled: Dictionary = _sample_surface(expected)
|
|
var point: Vector3 = sampled["position"]
|
|
var normal: Vector3 = sampled["normal"]
|
|
var tangent: Vector3 = (
|
|
_surface_tangent - normal * _surface_tangent.dot(normal)
|
|
).normalized()
|
|
if tangent.is_zero_approx():
|
|
tangent = normal.cross(Vector3.UP).normalized()
|
|
if tangent.is_zero_approx():
|
|
tangent = Vector3.RIGHT
|
|
var bitangent: Vector3 = normal.cross(tangent).normalized()
|
|
var pixel_size: float = cell_size * clampf(fill, 0.05, 1.0)
|
|
var cell_basis := Basis(
|
|
tangent * pixel_size,
|
|
bitangent * pixel_size,
|
|
normal,
|
|
)
|
|
return Transform3D(
|
|
cell_basis,
|
|
to_local(point + normal * _surface_offset()),
|
|
)
|
|
|
|
|
|
func _sample_surface(expected: Vector3) -> Dictionary:
|
|
var point: Vector3 = expected
|
|
var normal: Vector3 = _surface_normal
|
|
var world: World3D = get_world_3d()
|
|
if world != null:
|
|
var query := PhysicsRayQueryParameters3D.create(
|
|
expected + _surface_normal * SURFACE_SAMPLE_DEPTH,
|
|
expected - _surface_normal * SURFACE_SAMPLE_DEPTH,
|
|
_solid_surface_mask,
|
|
)
|
|
query.collide_with_areas = false
|
|
query.collide_with_bodies = true
|
|
var hit: Dictionary = world.direct_space_state.intersect_ray(query)
|
|
if (
|
|
not hit.is_empty()
|
|
and hit.get("collider") is StaticBody3D
|
|
):
|
|
var hit_normal: Vector3 = hit.get("normal", _surface_normal)
|
|
if hit_normal.dot(_surface_normal) >= 0.35:
|
|
var hit_position: Vector3 = hit.get("position", expected)
|
|
point = hit_position
|
|
normal = hit_normal.normalized()
|
|
return {"position": point, "normal": normal}
|
|
|
|
|
|
func _surface_offset() -> float:
|
|
return SURFACE_OFFSET + float(_layer) * LAYER_OFFSET_STEP
|
|
|
|
|
|
func _destroy_grid() -> void:
|
|
if _grid_instance == null:
|
|
return
|
|
_grid_instance.queue_free()
|
|
_grid_instance = null
|
|
|
|
|
|
func _refresh_grid_visibility() -> void:
|
|
if _grid_instance != null:
|
|
_grid_instance.visible = (
|
|
_stencil_requested_visible
|
|
and _guide_visible
|
|
and not is_hidden_by_relationship()
|
|
)
|
|
|
|
|
|
func _configure_render_distance() -> void:
|
|
if _pixel_instance == null:
|
|
return
|
|
_pixel_instance.visibility_range_end = (
|
|
FINALIZED_VISIBILITY_RANGE if _finalized else 0.0
|
|
)
|
|
_pixel_instance.visibility_range_end_margin = (
|
|
FINALIZED_VISIBILITY_MARGIN if _finalized else 0.0
|
|
)
|
|
|
|
|
|
func _participants_from_state(data: Dictionary) -> Array[String]:
|
|
var result: Array[String] = []
|
|
_append_participant(result, str(data.get("creator_fingerprint", "")))
|
|
for value: Variant in data.get("participant_fingerprints", []):
|
|
_append_participant(result, str(value))
|
|
for cell_value: Variant in data.get("cells", []):
|
|
var cell: Dictionary = cell_value
|
|
_append_participant(
|
|
result, str(cell.get("author_fingerprint", ""))
|
|
)
|
|
return result
|
|
|
|
|
|
func _participants_from_update(
|
|
data: Dictionary,
|
|
fallback: Array[String],
|
|
) -> Array[String]:
|
|
var result: Array[String] = fallback.duplicate()
|
|
for value: Variant in data.get("participant_fingerprints", []):
|
|
_append_participant(result, str(value))
|
|
for edit_value: Variant in data.get("edits", []):
|
|
var edit: Dictionary = edit_value
|
|
_append_participant(
|
|
result, str(edit.get("author_fingerprint", ""))
|
|
)
|
|
return result
|
|
|
|
|
|
func _append_participant(result: Array[String], fingerprint: String) -> void:
|
|
if (
|
|
NetworkIdentityCrypto.valid_fingerprint(fingerprint)
|
|
and fingerprint not in result
|
|
):
|
|
result.append(fingerprint)
|
|
|
|
|
|
func _cell_key(x: int, y: int) -> int:
|
|
return y * grid_width + x
|