@tool # Brush properties (shape, transform, timing and opacity). # Other attributes like color, height or texture index are tool-specific, # while brush properties apply to all of them. # This is separate from Painter because it could apply to multiple Painters at once. const HT_Errors = preload("../../util/errors.gd") const HT_Painter = preload("./painter.gd") const SHAPES_DIR = "addons/zylann.hterrain/tools/brush/shapes" const DEFAULT_BRUSH_TEXTURE_PATH = SHAPES_DIR + "/round2.exr" # Reasonable size for sliders to be usable const MAX_SIZE_FOR_SLIDERS = 500 # Absolute size limit. Terrains can't be larger than that, and it will be very slow to paint const MAX_SIZE = 4000 signal size_changed(new_size) signal shapes_changed var _size := 32 var _opacity := 1.0 var _random_rotation := false var _pressure_enabled := false var _pressure_over_scale := 0.5 var _pressure_over_opacity := 0.5 # TODO Rename stamp_*? var _frequency_distance := 0.0 var _frequency_time_ms := 0 # Array of greyscale textures var _shapes : Array[Texture2D] = [] var _shape_index := 0 var _prev_position := Vector2(-999, -999) var _prev_time_ms := 0 func set_size(size: int): if size < 1: size = 1 if size != _size: _size = size size_changed.emit(_size) func get_size() -> int: return _size func set_opacity(opacity: float): _opacity = clampf(opacity, 0.0, 1.0) func get_opacity() -> float: return _opacity func set_random_rotation_enabled(enabled: bool): _random_rotation = enabled func is_random_rotation_enabled() -> bool: return _random_rotation func set_pressure_enabled(enabled: bool): _pressure_enabled = enabled func is_pressure_enabled() -> bool: return _pressure_enabled func set_pressure_over_scale(amount: float): _pressure_over_scale = clampf(amount, 0.0, 1.0) func get_pressure_over_scale() -> float: return _pressure_over_scale func set_pressure_over_opacity(amount: float): _pressure_over_opacity = clampf(amount, 0.0, 1.0) func get_pressure_over_opacity() -> float: return _pressure_over_opacity func set_frequency_distance(d: float): _frequency_distance = maxf(d, 0.0) func get_frequency_distance() -> float: return _frequency_distance func set_frequency_time_ms(t: int): if t < 0: t = 0 _frequency_time_ms = t func get_frequency_time_ms() -> int: return _frequency_time_ms func set_shapes(shapes: Array[Texture2D]): assert(len(shapes) >= 1) for s in shapes: assert(s != null) assert(s is Texture2D) _shapes = shapes.duplicate(false) if _shape_index >= len(_shapes): _shape_index = len(_shapes) - 1 shapes_changed.emit() func get_shapes() -> Array[Texture2D]: return _shapes.duplicate(false) func get_shape(i: int) -> Texture2D: return _shapes[i] static func load_shape_from_image_file(fpath: String, logger, retries := 1) -> Texture2D: var im := Image.new() var err := im.load(fpath) if err != OK: if retries > 0: # TODO There is a bug with Godot randomly being unable to load images. # See https://github.com/Zylann/godot_heightmap_plugin/issues/219 # Attempting to workaround this by retrying (I suspect it's because of non-initialized # variable in Godot's C++ code...) logger.error("Could not load image at '{0}', error {1}. Retrying..." \ .format([fpath, HT_Errors.get_message(err)])) return load_shape_from_image_file(fpath, logger, retries - 1) else: logger.error("Could not load image at '{0}', error {1}" \ .format([fpath, HT_Errors.get_message(err)])) return null var tex := ImageTexture.create_from_image(im) return tex # Call this while handling mouse or pen input. # If it returns false, painting should not run. func configure_paint_input(painters: Array[HT_Painter], position: Vector2, pressure: float) -> bool: assert(len(_shapes) != 0) # DEBUG #pressure = 0.5 + 0.5 * sin(OS.get_ticks_msec() / 200.0) if position.distance_to(_prev_position) < _frequency_distance: return false var now := Time.get_ticks_msec() if (now - _prev_time_ms) < _frequency_time_ms: return false _prev_position = position _prev_time_ms = now for painter_index in len(painters): var painter : HT_Painter = painters[painter_index] if _random_rotation: painter.set_brush_rotation(randf_range(-PI, PI)) else: painter.set_brush_rotation(0.0) painter.set_brush_texture(_shapes[_shape_index]) painter.set_brush_size(_size) if _pressure_enabled: painter.set_brush_scale(lerpf(1.0, pressure, _pressure_over_scale)) painter.set_brush_opacity(_opacity * lerpf(1.0, pressure, _pressure_over_opacity)) else: painter.set_brush_scale(1.0) painter.set_brush_opacity(_opacity) #painter.paint_input(position) _shape_index += 1 if _shape_index >= len(_shapes): _shape_index = 0 return true # Call this when the user releases the pen or mouse button func on_paint_end(): _prev_position = Vector2(-999, -999) _prev_time_ms = 0