extends RefCounted
class_name BrushProxies
## The brushes being transformed, SHOWN AS THEMSELVES while the tool is up
## (docs/editor-texturing-selection-move.md §4). Measured: a full rebuild per step costs
## 104 ms at 800 brushes; a proxy moved by transform costs ~0. So: hide the brushes (one
## rebuild), show their own meshes (DCLevel.get_brush_mesh, level material), move the
## proxies by transform, and on drop unhide (the caller's edit then rebuilds once).
## While transformed the proxies are not clipped against their neighbours.
##
## Shared by the rotate tool (and next, move). `level` must be a real DCLevel; on a stub
## (operator gates) lift() does nothing and the caller's outline preview still works.
var level: Node = null
var proxies: Array[MeshInstance3D] = []
var lifted: Array[int] = []
func lift(ids: Array) -> void:
if level == null or not (level.has_method("get_brush_mesh") and level.has_method("set_brush_hidden")):
return
var lm: Node = level.get_node_or_null("LevelMesh")
var mat: Material = (lm as MeshInstance3D).material_override if lm is MeshInstance3D else null
for b: int in ids:
var mesh: Mesh = level.get_brush_mesh(b)
if mesh == null or mesh.get_surface_count() == 0:
continue
var mi := MeshInstance3D.new()
mi.name = "MoveProxy_%d" % b
mi.mesh = mesh
mi.material_override = mat
level.add_child(mi)
proxies.append(mi)
level.set_brush_hidden(b, true)
lifted.append(b)
if not lifted.is_empty():
level.rebuild()
## Every proxy gets the same transform (the tool's rigid motion). `tex_shift` is the
## instance uniform the level shader subtracts from the projection position: exact for a
## translation (texture lock); a rotation preview leaves it zero (the texture slides in
## the preview only — the committed edit is locked exactly by libdc).
func set_transform(t: Transform3D, tex_shift: Vector3 = Vector3.ZERO) -> void:
for mi: MeshInstance3D in proxies:
if is_instance_valid(mi):
mi.transform = t
mi.set_instance_shader_parameter("tex_lock_shift", tex_shift)
## INDIVIDUAL ORIGINS (Ctrl+.): each proxy its OWN transform, by the brush it stands for
## (`by_brush`: brush id → Transform3D; missing = identity). lift() fills `proxies` and
## `lifted` in the same loop, so they are parallel.
func set_transform_per(by_brush: Dictionary) -> void:
for i in mini(proxies.size(), lifted.size()):
var mi: MeshInstance3D = proxies[i]
if is_instance_valid(mi):
mi.transform = by_brush.get(lifted[i], Transform3D.IDENTITY)
mi.set_instance_shader_parameter("tex_lock_shift", Vector3.ZERO)
## Free the proxies and unhide. rebuild = false when the caller's edit rebuilds anyway.
func drop(rebuild: bool) -> void:
for mi: MeshInstance3D in proxies:
if is_instance_valid(mi):
mi.queue_free()
proxies.clear()
if lifted.is_empty():
return
for b: int in lifted:
level.set_brush_hidden(b, false)
lifted.clear()
if rebuild:
level.rebuild()