extends Node3D
class_name BrushPreview
## Interactive in-viewport brush peview.
## Uses the tip and first texture and color to show a preview of what the brush
## looks like.
enum Appearance {
BRUSH, ## A preview of what the brush will paint.
CIRCLE, ## A neutral black-and-white striped circle.
}
@export var appearance: Appearance = Appearance.BRUSH:
set(to):
appearance = to
for preview in _previews:
preview.set_surface_override_material(0, _get_material())
@export var painter : Painter
## If the preview should move with the mouse.
var follow_mouse := true
var brush : Brush
## An array of `SingleBrushPreviews`.
var _previews : Array
var _brush_preview_material := ShaderMaterial.new()
func _ready():
appearance = appearance
func _input(event : InputEvent) -> void:
if not visible:
return
if not is_instance_valid(painter):
push_warning("Visible brush preview doesn't have a valid painter assigned.")
return
if not brush:
push_warning("Visible brush preview doesn't have a brush assigned.")
return
var pressure := 1.0
if event is InputEventMouseMotion and event.button_mask == MOUSE_BUTTON_LEFT\
and brush.size_pen_pressure:
pressure = event.pressure
var transforms : Array = painter.get_brush_preview_transforms(
get_viewport().get_mouse_position(), brush, pressure, follow_mouse)
# Add necessary amount of previews.
while _previews.size() < transforms.size():
var new : MeshInstance3D = preload("single_brush_preview.tscn").instantiate()
new.set_surface_override_material(0, _get_material())
add_child(new)
_previews.append(new)
appearance = appearance
# Remove excess previews.
while _previews.size() > transforms.size():
_previews.pop_front().queue_free()
# Update transform and appearance.
for transform_num in transforms.size():
var preview : MeshInstance3D = _previews[transform_num]
var material := preview.get_surface_override_material(0)
var brush_transform : Transform3D = transforms[transform_num]
if follow_mouse:
preview.transform = brush_transform
else:
# Only apply rotation and scale.
preview.transform.basis = preview.transform.basis.orthonormalized()\
.scaled(brush_transform.basis.get_scale())
if appearance == Appearance.BRUSH:
material.set_shader_parameter("albedo", brush.get_texture(0))
material.set_shader_parameter("color", brush.get_color(0))
material.set_shader_parameter("tip", brush.tip)
func _get_material() -> Material:
return preload("brush_preview.material") if appearance == Appearance.BRUSH\
else preload("circle_preview.material")