aboutsummaryrefslogtreecommitdiff
path: root/game/addons/zylann.hterrain/tools/brush/brush.gd
blob: 73d447fb20d0e81b9b387fe182d713e279340149 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
@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
signal shape_index_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 _shape_cycling_enabled := false
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]


func get_shape_index() -> int:
   return _shape_index


func set_shape_index(i: int):
   assert(i >= 0)
   assert(i < len(_shapes))
   _shape_index = i
   shape_index_changed.emit()


func set_shape_cycling_enabled(enable: bool):
   _shape_cycling_enabled = enable


func is_shape_cycling_enabled() -> bool:
   return _shape_cycling_enabled


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)

   if _shape_cycling_enabled:
      _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