fyrox_impl/scene/terrain/brushstroke/mod.rs
1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! The brushstroke module contains tools for modifying terrain textures.
22//! It uses a triple-buffer system to separate the UI mouse movements
23//! from the update of the data within the actual textures.
24//! 1. The first buffer is a [std::sync::mpsc::channel] that is used to send
25//! messages to control a thread that processes brush strokes.
26//! These messages are [BrushThreadMessage]. Some of the messages
27//! are processed as soon as they are received, but [BrushThreadMessage::Pixel]
28//! messages are sent to the next buffer.
29//! 2. The pixel message buffer holds a limited number of pixel messages.
30//! It serves to merge redundent pixel messages to spare the thread from
31//! repeating work. It is expected that brush operations will paint multiple
32//! times to the same pixel in quick succession.
33//! Once the new value for a pixel has been calculated, the value is stored
34//! in the third buffer.
35//! 3. The [StrokeData] buffer stores every change that a particular brush stroke
36//! has made to the texture. Because modifying a texture is a nontrivial operation,
37//! modified pixels are allowed to accumulate to some quantity before the new pixel
38//! values are actually written to the textures of the terrain.
39//! [StrokeChunks] is used to keep track of which pixels are waiting to be written
40//! to which terrain chunks.
41use super::Chunk;
42use crate::asset::ResourceDataRef;
43use crate::core::{
44 algebra::{Matrix2, Vector2},
45 log::Log,
46 math::Rect,
47 pool::Handle,
48 reflect::prelude::*,
49};
50use crate::fxhash::FxHashMap;
51use crate::resource::texture::{Texture, TextureResource};
52use crate::scene::node::Node;
53use fyrox_core::uuid_provider;
54use std::collections::VecDeque;
55use std::sync::mpsc::{Receiver, SendError, Sender};
56
57pub mod brushraster;
58use brushraster::*;
59pub mod strokechunks;
60use strokechunks::*;
61
62/// The number of pixel messages we can accept at once before we must start processing them.
63/// Often later messages will cause earlier messages to be unnecessary, so it can be more efficient
64/// to let some messages accumulate rather than process each message one-at-a-time.
65const MESSAGE_BUFFER_SIZE: usize = 40;
66/// The number of processed pixels we can hold before we must write the pixels to the targetted textures.
67/// Modifying a texture is expensive, so it is important to do it in batches of multiple pixels.
68const PIXEL_BUFFER_SIZE: usize = 40;
69/// The maximum number of pixels that are allowed to be involved in a single step of a brushstroke.
70/// This limit is arbitrarily chosen, but there should be some limit to prevent the editor
71/// from freezing as a result of an excessively large brush.
72const BRUSH_PIXEL_SANITY_LIMIT: i32 = 1000000;
73
74#[inline]
75fn mask_raise(original: u8, amount: f32) -> u8 {
76 (original as f32 + amount * 255.0).clamp(0.0, 255.0) as u8
77}
78
79#[inline]
80fn mask_lerp(original: u8, value: f32, t: f32) -> u8 {
81 let original = original as f32;
82 let value = value * 255.0;
83 (original * (1.0 - t) + value * t).clamp(0.0, 255.0) as u8
84}
85
86/// A message that can be sent to the terrain painting thread to control the painting.
87#[derive(Debug, Clone)]
88pub enum BrushThreadMessage {
89 /// Set the brush that will be used for future pixels and the textures that will be modified.
90 StartStroke(Brush, Handle<Node>, TerrainTextureData),
91 /// No futher pixels will be sent for the current stroke.
92 EndStroke,
93 /// Paint the given pixel as part of the current stroke.
94 Pixel(BrushPixelMessage),
95}
96
97/// A message that can be sent to indicate that the pixel at the given coordinates
98/// should be painted with the given alpha.
99#[derive(Debug, Clone)]
100pub struct BrushPixelMessage {
101 /// The coordinates of the pixel to paint.
102 pub position: Vector2<i32>,
103 /// The transparency of the brush, from 0.0 for transparent to 1.0 for opaque.
104 pub alpha: f32,
105 /// A value whose meaning depends on the brush.
106 /// For flatten brushes, this is the target height.
107 pub value: f32,
108}
109
110/// A queue that stores pixels that are waiting to be drawn by the brush.
111pub struct PixelMessageBuffer {
112 data: VecDeque<BrushPixelMessage>,
113 max_size: usize,
114}
115
116impl PixelMessageBuffer {
117 /// Create a new buffer with the given size.
118 #[inline]
119 pub fn new(max_size: usize) -> Self {
120 Self {
121 data: VecDeque::with_capacity(max_size),
122 max_size,
123 }
124 }
125 /// True if the buffer has reached its maximum size.
126 #[inline]
127 pub fn is_full(&self) -> bool {
128 self.max_size == self.data.len()
129 }
130 /// True if there is nothing to pop from the queue.
131 #[inline]
132 pub fn is_empty(&self) -> bool {
133 self.data.is_empty()
134 }
135 /// Remove the message from the front of the queue and return it, if the queue is not empty.
136 #[inline]
137 pub fn pop(&mut self) -> Option<BrushPixelMessage> {
138 self.data.pop_front()
139 }
140 /// Push a message onto the back of the queue, or panic of the queue is full.
141 pub fn push(&mut self, message: BrushPixelMessage) {
142 assert!(self.data.len() < self.max_size);
143 if let Some(m) = self
144 .data
145 .iter_mut()
146 .find(|m| m.position == message.position)
147 {
148 if message.alpha > m.alpha {
149 m.alpha = message.alpha;
150 m.value = message.value;
151 }
152 } else {
153 self.data.push_back(message);
154 }
155 }
156}
157
158/// Object to send to painting thread to control which textures are modified.
159#[derive(Debug, Clone)]
160pub struct TerrainTextureData {
161 /// The height and width of the texture in pixels.
162 pub chunk_size: Vector2<u32>,
163 /// The kind of texture.
164 pub kind: TerrainTextureKind,
165 /// The texture resources, organized by chunk grid position.
166 pub resources: FxHashMap<Vector2<i32>, TextureResource>,
167}
168
169/// Terrain textures come in multiple kinds.
170/// Height textures contain f32 values for each vertex of the terrain.
171/// Mask textures contain u8 values indicating transparency.
172/// Coordinates are interpreted differently between the two kinds of texture
173/// because the data in height textures overlaps with the data in neighboring chunks,
174/// so the pixels along each edge are duplicated and must be kept in sync
175/// so that the chunks do not disconnect.
176#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
177pub enum TerrainTextureKind {
178 #[default]
179 /// Height texture with f32 height values and overlapping edges between chunks.
180 Height,
181 /// Mask texture with u8 oppacity values.
182 Mask,
183}
184
185/// Sender with methods for sending the messages which control a brush painting thread.
186pub struct BrushSender(Sender<BrushThreadMessage>);
187
188impl BrushSender {
189 /// Create a new BrushSender using the given Sender.
190 pub fn new(sender: Sender<BrushThreadMessage>) -> Self {
191 Self(sender)
192 }
193 /// Begin a new stroke using the given brush.
194 pub fn start_stroke(&self, brush: Brush, node: Handle<Node>, data: TerrainTextureData) {
195 self.0
196 .send(BrushThreadMessage::StartStroke(brush, node, data))
197 .unwrap_or_else(on_send_failure);
198 }
199 /// End the current stroke.
200 pub fn end_stroke(&self) {
201 self.0
202 .send(BrushThreadMessage::EndStroke)
203 .unwrap_or_else(on_send_failure);
204 }
205 /// Draw a pixel using the brush that was set in the most recent call to [BrushSender::start_stroke].
206 #[inline]
207 pub fn draw_pixel(&self, position: Vector2<i32>, alpha: f32, value: f32) {
208 if alpha == 0.0 {
209 return;
210 }
211 self.0
212 .send(BrushThreadMessage::Pixel(BrushPixelMessage {
213 position,
214 alpha,
215 value,
216 }))
217 .unwrap_or_else(on_send_failure);
218 }
219}
220
221fn on_send_failure(error: SendError<BrushThreadMessage>) {
222 Log::err(format!("A brush painting message was not sent. {error:?}"));
223}
224
225/// Type for a callback that delivers the original data of textures that have been modified
226/// by the brush so that changes might be undone.
227pub type UndoChunkHandler = dyn FnMut(UndoData) + Send;
228
229/// A record of original data data for chunks that have been modified by a brushstroke.
230pub struct UndoData {
231 /// The handle of the terrain being edited
232 pub node: Handle<Node>,
233 /// The data of the chunks as they were before the brushstroke.
234 pub chunks: Vec<ChunkData>,
235 /// The kind of data within the terrain that is being edited.
236 pub target: BrushTarget,
237}
238
239#[derive(Default)]
240/// Data for an in-progress terrain painting operation
241pub struct BrushStroke {
242 /// The brush that is currently being used. This determines how the terrain textures are edited.
243 brush: Brush,
244 /// The textures for the terrain that is currently being edited.
245 textures: FxHashMap<Vector2<i32>, TextureResource>,
246 /// The node of the terrain being edted
247 node: Handle<Node>,
248 /// Callback to handle the saved original chunk data after each stroke.
249 /// This is called when [BrushThreadMessage::EndStroke] is received.
250 undo_chunk_handler: Option<Box<UndoChunkHandler>>,
251 /// A record of which pixels have been modified in each chunk since the last UpdateTextures.
252 /// This is cleared after [BrushThreadMessage::UpdateTextures] is received,
253 /// when the pixel data is transferred into the textures.
254 chunks: StrokeChunks,
255 /// Data copied from chunks that have been edited by the current brush stroke.
256 /// This preserves the textures as they were before the stroke began, so that
257 /// an undo command can be created at the end of the stroke.
258 undo_chunks: Vec<ChunkData>,
259 /// A record of every pixel of the stroke, including the strength of the brush at that pixel,
260 /// the original value before the stroke began, and the current value.
261 height_pixels: StrokeData<f32>,
262 /// A record of every pixel of the stroke, including the strength of the brush at that pixel,
263 /// the original value before the stroke began, and the current value.
264 mask_pixels: StrokeData<u8>,
265}
266
267/// Stores pixels that have been modified by a brush during a stroke.
268/// It remembers the strength of the brush, the value of the painted pixel,
269/// and the value of the original pixel before the brushstroke.
270/// This should be cleared after each stroke using [StrokeData::clear].
271///
272/// `V` is the type of data stored in the pixel being edited.
273#[derive(Debug, Default)]
274pub struct StrokeData<V>(FxHashMap<Vector2<i32>, StrokeElement<V>>);
275
276/// A single pixel data of a brush stroke
277#[derive(Debug, Copy, Clone)]
278pub struct StrokeElement<V> {
279 /// The intensity of the brush stroke, with 0.0 indicating a pixel that brush has not touched
280 /// and 1.0 indicates a pixel fully covered by the brush.
281 pub strength: f32,
282 /// The value of the pixel before the stroke began.
283 pub original_value: V,
284 /// The current value of the pixel.
285 pub latest_value: V,
286}
287
288impl BrushStroke {
289 /// Create a BrushStroke with the given handler for saving undo data for chunks.
290 pub fn with_chunk_handler(undo_chunk_handler: Box<UndoChunkHandler>) -> Self {
291 Self {
292 undo_chunk_handler: Some(undo_chunk_handler),
293 ..Default::default()
294 }
295 }
296 /// The brush that this stroke is using. This is immutable access only, because
297 /// the brush's target may only be changed through [BrushStroke::start_stroke] or
298 /// [BrushStroke::accept_messages].
299 ///
300 /// Mutable access to the brush's other properties is available through
301 /// [BrushStroke::shape], [BrushStroke::mode], [BrushStroke::hardness],
302 /// and [BrushStroke::alpha].
303 pub fn brush(&self) -> &Brush {
304 &self.brush
305 }
306 /// Mutable access to the brush's shape
307 pub fn shape(&mut self) -> &mut BrushShape {
308 &mut self.brush.shape
309 }
310 /// Mutable access to the brush's mode
311 pub fn mode(&mut self) -> &mut BrushMode {
312 &mut self.brush.mode
313 }
314 /// Mutable access to the brush's hardness. The hardness controls how the edges
315 /// of the brush are blended with the original value of the texture.
316 pub fn hardness(&mut self) -> &mut f32 {
317 &mut self.brush.hardness
318 }
319 /// Mutable access to the brush's alpha. The alpha value controls how
320 /// the operation's result is blended with the original value of the texture.
321 pub fn alpha(&mut self) -> &mut f32 {
322 &mut self.brush.alpha
323 }
324 /// Insert a stamp of the brush at the given position with the given texture scale and the given value.
325 /// - `position`: The center of the stamp.
326 /// - `scale`: The size of each pixel in 2D local space. This is used to convert the brush's shape from local space to texture space.
327 /// - `value`: A value that is a parameter for the brush operation.
328 pub fn stamp(&mut self, position: Vector2<f32>, scale: Vector2<f32>, value: f32) {
329 let brush = self.brush.clone();
330 brush.stamp(position, scale, |position, alpha| {
331 self.draw_pixel(BrushPixelMessage {
332 position,
333 alpha,
334 value,
335 })
336 });
337 }
338 /// Insert a smear of the brush at the given position with the given texture scale and the given value.
339 /// - `start`: The center of the smear's start.
340 /// - `end`: The center of the smear's end.
341 /// - `scale`: The size of each pixel in 2D local space. This is used to convert the brush's shape from local space to texture space.
342 /// - `value`: A value that is a parameter for the brush operation.
343 pub fn smear(
344 &mut self,
345 start: Vector2<f32>,
346 end: Vector2<f32>,
347 scale: Vector2<f32>,
348 value: f32,
349 ) {
350 let brush = self.brush.clone();
351 brush.smear(start, end, scale, |position, alpha| {
352 self.draw_pixel(BrushPixelMessage {
353 position,
354 alpha,
355 value,
356 })
357 });
358 }
359 /// Prepare this object for a new brushstroke.
360 pub fn clear(&mut self) {
361 self.height_pixels.clear();
362 self.mask_pixels.clear();
363 self.chunks.clear();
364 }
365 /// Access the data in the textures to find the value for the pixel at the given position.
366 pub fn data_pixel<V>(&self, position: Vector2<i32>) -> Option<V>
367 where
368 V: Clone,
369 {
370 // Determine which texture holds the data for the position.
371 let grid_pos = self.chunks.pixel_position_to_grid_position(position);
372 // Determine which pixel within the texture corresponds to the given position.
373 let origin = self.chunks.chunk_to_origin(grid_pos);
374 let p = position - origin;
375 // Access the texture and extract the data.
376 let texture = self.textures.get(&grid_pos)?;
377 let index = self.chunks.pixel_index(p);
378 let data = texture.data_ref();
379 Some(data.data_of_type::<V>().unwrap()[index].clone())
380 }
381 /// Block on the given receiver until its messages are exhausted and perform the painting
382 /// operations according to the messages.
383 /// It does not return until the receiver's channel no longer has senders.
384 pub fn accept_messages(&mut self, receiver: Receiver<BrushThreadMessage>) {
385 let mut message_buffer = PixelMessageBuffer::new(MESSAGE_BUFFER_SIZE);
386 loop {
387 // Collect all waiting messages, until the buffer is full.
388 while !message_buffer.is_full() {
389 if let Ok(message) = receiver.try_recv() {
390 // Act on the message, potentially adding it to the message buffer.
391 self.handle_message(message, &mut message_buffer);
392 } else {
393 break;
394 }
395 }
396 if let Some(pixel) = message_buffer.pop() {
397 // Perform the drawing operation for the current pixel message.
398 self.handle_pixel_message(pixel);
399 } else if self.chunks.count() > 0 {
400 // We have run out of pixels to process, so before we block to wait for more,
401 // write the currently processed pixels to the terrain textures.
402 self.flush();
403 } else {
404 // If the message buffer is empty, we cannot proceed, so block until a message is available.
405 // Block until either a message arrives or the channel is closed.
406 if let Ok(message) = receiver.recv() {
407 // Act on the message, potentially adding it to the message buffer.
408 self.handle_message(message, &mut message_buffer);
409 } else {
410 // The message buffer is empty and the channel is closed, so we're finished.
411 // Flush pixels to the textures.
412 self.end_stroke();
413 return;
414 }
415 }
416 }
417 }
418 fn handle_message(
419 &mut self,
420 message: BrushThreadMessage,
421 message_buffer: &mut PixelMessageBuffer,
422 ) {
423 match message {
424 BrushThreadMessage::StartStroke(brush, node, textures) => {
425 self.brush = brush;
426 self.node = node;
427 self.textures = textures.resources;
428 self.chunks.set_layout(textures.kind, textures.chunk_size);
429 }
430 BrushThreadMessage::EndStroke => {
431 // The stroke has ended, so finish processing all buffered pixel messages.
432 while let Some(p) = message_buffer.pop() {
433 self.handle_pixel_message(p);
434 }
435 // Apply buffered pixels to the terrain textures.
436 self.end_stroke();
437 }
438 BrushThreadMessage::Pixel(pixel) => {
439 message_buffer.push(pixel);
440 }
441 }
442 }
443 /// -`brush`: The brush to paint with
444 /// -`node`: The handle of the terrain being modified. It is not used except to pass to `undo_chunk_handler`,
445 /// so it can be safely [Handle::NONE] if `undo_chunk_handler` is None, or if `undo_chunk_handler` is prepared for NONE.
446 /// -`textures`: Hash map of texture resources that this stroke will edit.
447 pub fn start_stroke(&mut self, brush: Brush, node: Handle<Node>, textures: TerrainTextureData) {
448 self.brush = brush;
449 self.node = node;
450 self.chunks.set_layout(textures.kind, textures.chunk_size);
451 self.textures = textures.resources;
452 }
453 /// Send the textures that have been touched by the brush to the undo handler,
454 /// then write the current changes to the textures and clear the stroke to prepare
455 /// for starting a new stroke.
456 pub fn end_stroke(&mut self) {
457 if let Some(handler) = &mut self.undo_chunk_handler {
458 // Copy the textures that are about to be modified so that the modifications can be undone.
459 self.chunks
460 .copy_texture_data(&self.textures, &mut self.undo_chunks);
461 // Send the saved textures to the handler so that an undo command might be created.
462 handler(UndoData {
463 node: self.node,
464 chunks: std::mem::take(&mut self.undo_chunks),
465 target: self.brush.target,
466 });
467 }
468 // Flush pixels to the terrain textures
469 self.apply();
470 self.clear();
471 }
472 /// Insert a pixel with the given texture-space coordinates and strength.
473 pub fn draw_pixel(&mut self, pixel: BrushPixelMessage) {
474 let pixel = BrushPixelMessage {
475 alpha: 0.5 * (1.0 - (pixel.alpha * std::f32::consts::PI).cos()),
476 ..pixel
477 };
478 let position = pixel.position;
479 match self.chunks.kind() {
480 TerrainTextureKind::Height => self.accept_pixel_height(pixel),
481 TerrainTextureKind::Mask => self.accept_pixel_mask(pixel),
482 }
483 self.chunks.write(position);
484 }
485 fn handle_pixel_message(&mut self, pixel: BrushPixelMessage) {
486 self.draw_pixel(pixel);
487 if self.chunks.count() >= PIXEL_BUFFER_SIZE {
488 self.flush();
489 }
490 }
491 fn smooth_height(
492 &self,
493 position: Vector2<i32>,
494 kernel_radius: u32,
495 original: f32,
496 alpha: f32,
497 ) -> f32 {
498 let radius = kernel_radius as i32;
499 let diameter = kernel_radius * 2 + 1;
500 let area = (diameter * diameter - 1) as f32;
501 let mut total = 0.0;
502 for x in -radius..=radius {
503 for y in -radius..=radius {
504 if x == 0 && y == 0 {
505 continue;
506 }
507 let pos = position + Vector2::new(x, y);
508 let value = self
509 .height_pixels
510 .original_pixel_value(pos)
511 .copied()
512 .or_else(|| self.data_pixel(position))
513 .unwrap_or_default();
514 total += value;
515 }
516 }
517 let smoothed = total / area;
518 original * (1.0 - alpha) + smoothed * alpha
519 }
520 fn smooth_mask(
521 &self,
522 position: Vector2<i32>,
523 kernel_radius: u32,
524 original: u8,
525 alpha: f32,
526 ) -> u8 {
527 let radius = kernel_radius as i32;
528 let diameter = kernel_radius as u64 * 2 + 1;
529 let area = diameter * diameter - 1;
530 let mut total: u64 = 0;
531 for x in -radius..=radius {
532 for y in -radius..=radius {
533 if x == 0 && y == 0 {
534 continue;
535 }
536 let pos = position + Vector2::new(x, y);
537 let value = self
538 .mask_pixels
539 .original_pixel_value(pos)
540 .copied()
541 .or_else(|| self.data_pixel(position))
542 .unwrap_or_default();
543 total += value as u64;
544 }
545 }
546 let smoothed = total / area;
547 (original as f32 * (1.0 - alpha) + smoothed as f32 * alpha).clamp(0.0, 255.0) as u8
548 }
549 fn accept_pixel_height(&mut self, pixel: BrushPixelMessage) {
550 let position = pixel.position;
551 let mut pixels = std::mem::take(&mut self.height_pixels);
552 let original = pixels.update_strength(position, pixel.alpha, || self.data_pixel(position));
553 self.height_pixels = pixels;
554 let Some(original) = original else {
555 return;
556 };
557 let alpha = self.brush.alpha * pixel.alpha;
558 let result: f32 = match self.brush.mode {
559 BrushMode::Raise { amount } => original + amount * alpha,
560 BrushMode::Flatten => original * (1.0 - alpha) + pixel.value * alpha,
561 BrushMode::Assign { value } => original * (1.0 - alpha) + value * alpha,
562 BrushMode::Smooth { kernel_radius } => {
563 self.smooth_height(position, kernel_radius, original, alpha)
564 }
565 };
566 self.height_pixels.set_latest(position, result);
567 }
568 fn accept_pixel_mask(&mut self, pixel: BrushPixelMessage) {
569 let position = pixel.position;
570 let mut pixels = std::mem::take(&mut self.mask_pixels);
571 let original = pixels.update_strength(position, pixel.alpha, || self.data_pixel(position));
572 self.mask_pixels = pixels;
573 let Some(original) = original else {
574 return;
575 };
576 let alpha = self.brush.alpha * pixel.alpha;
577 let result: u8 = match self.brush.mode {
578 BrushMode::Raise { amount } => mask_raise(original, amount * alpha),
579 BrushMode::Flatten => mask_lerp(original, pixel.value, alpha),
580 BrushMode::Assign { value } => mask_lerp(original, value, alpha),
581 BrushMode::Smooth { kernel_radius } => {
582 self.smooth_mask(position, kernel_radius, original, alpha)
583 }
584 };
585 self.mask_pixels.set_latest(position, result);
586 }
587 /// Update the texture resources to match the current state of this stroke.
588 pub fn flush(&mut self) {
589 // chunks stores the pixels that we have modified but not yet written to the textures.
590 // If we have an undo handler, we must inform the handler of the textures we are writing to
591 // because we are about to forget that we have written to them.
592 if self.undo_chunk_handler.is_some() {
593 // Copy the textures that are about to be modified so that the modifications can be undone.
594 self.chunks
595 .copy_texture_data(&self.textures, &mut self.undo_chunks);
596 }
597 // Do the actual texture modification.
598 self.apply();
599 // Erase our memory of having modified these pixels, since they have already been finalized
600 // by writing them to the textures.
601 self.chunks.clear();
602 }
603 fn apply(&self) {
604 match self.chunks.kind() {
605 TerrainTextureKind::Height => {
606 self.chunks.apply(&self.height_pixels, &self.textures);
607 }
608 TerrainTextureKind::Mask => {
609 self.chunks.apply(&self.mask_pixels, &self.textures);
610 }
611 }
612 }
613}
614
615impl<V> StrokeData<V> {
616 /// Reset the brush stroke so it is ready to begin a new stroke.
617 #[inline]
618 pub fn clear(&mut self) {
619 self.0.clear()
620 }
621 /// For every pixel that is modified by the stroke, the original values
622 /// is stored as it was before the stroke began.
623 #[inline]
624 pub fn original_pixel_value(&self, position: Vector2<i32>) -> Option<&V> {
625 self.0.get(&position).map(|x| &x.original_value)
626 }
627 /// The updated pixel value based on whatever editing operation the stroke is performing.
628 #[inline]
629 pub fn latest_pixel_value(&self, position: Vector2<i32>) -> Option<&V> {
630 self.0.get(&position).map(|x| &x.latest_value)
631 }
632 /// Update the stroke with a new value at the given pixel position.
633 /// This must only be called after calling [StrokeData::update_strength]
634 /// to ensure that this stroke contains data for the position.
635 /// Otherwise this method may panic.
636 #[inline]
637 pub fn set_latest(&mut self, position: Vector2<i32>, value: V) {
638 if let Some(el) = self.0.get_mut(&position) {
639 el.latest_value = value;
640 } else {
641 panic!("Setting latest value of missing element");
642 }
643 }
644 /// Stores or modifies the StrokeElement at the given position.
645 /// If the element is updated, return the original pixel value of the element.
646 /// - `position`: The position of the data to modify within the terrain.
647 /// - `strength`: The strength of the brush at the position, from 0.0 to 1.0.
648 /// The element is updated if the stored strength is less than the given strength.
649 /// If there is no stored strength, that is treated as a strength of 0.0.
650 /// - `pixel_value`: The current value of the data.
651 /// This may be stored in the StrokeData if no pixel value is currently recorded for the given position.
652 /// Otherwise, this value is ignored.
653 #[inline]
654 pub fn update_strength<F>(
655 &mut self,
656 position: Vector2<i32>,
657 strength: f32,
658 pixel_value: F,
659 ) -> Option<V>
660 where
661 V: Clone,
662 F: FnOnce() -> Option<V>,
663 {
664 if strength == 0.0 {
665 None
666 } else if let Some(element) = self.0.get_mut(&position) {
667 if element.strength < strength {
668 element.strength = strength;
669 Some(element.original_value.clone())
670 } else {
671 None
672 }
673 } else {
674 let value = pixel_value()?;
675 let element = StrokeElement {
676 strength,
677 latest_value: value.clone(),
678 original_value: value.clone(),
679 };
680 self.0.insert(position, element);
681 Some(value)
682 }
683 }
684}
685
686/// Shape of a brush.
687#[derive(Copy, Clone, Reflect, Debug)]
688pub enum BrushShape {
689 /// Circle with given radius.
690 Circle {
691 /// Radius of the circle.
692 radius: f32,
693 },
694 /// Rectangle with given width and height.
695 Rectangle {
696 /// Width of the rectangle.
697 width: f32,
698 /// Length of the rectangle.
699 length: f32,
700 },
701}
702
703uuid_provider!(BrushShape = "a4dbfba0-077c-4658-9972-38384a8432f9");
704
705impl Default for BrushShape {
706 fn default() -> Self {
707 BrushShape::Circle { radius: 1.0 }
708 }
709}
710
711impl BrushShape {
712 /// Return true if the given point is within the shape when positioned at the given center point.
713 pub fn contains(&self, brush_center: Vector2<f32>, pixel_position: Vector2<f32>) -> bool {
714 match *self {
715 BrushShape::Circle { radius } => (brush_center - pixel_position).norm() < radius,
716 BrushShape::Rectangle { width, length } => Rect::new(
717 brush_center.x - width * 0.5,
718 brush_center.y - length * 0.5,
719 width,
720 length,
721 )
722 .contains(pixel_position),
723 }
724 }
725}
726
727/// Paint mode of a brush. It defines operation that will be performed on the terrain.
728#[derive(Clone, PartialEq, PartialOrd, Reflect, Debug)]
729pub enum BrushMode {
730 /// Raise or lower the value
731 Raise {
732 /// An offset to change the value by
733 amount: f32,
734 },
735 /// Flattens value of the terrain data
736 Flatten,
737 /// Assigns a particular value to anywhere the brush touches.
738 Assign {
739 /// Fixed value to paint into the data
740 value: f32,
741 },
742 /// Reduce sharp changes in the data.
743 Smooth {
744 /// Determines the size of each pixel's neighborhood in terms of
745 /// distance from the pixel.
746 /// 0 means no smoothing at all.
747 /// 1 means taking the mean of the 3x3 square of pixels surrounding each smoothed pixel.
748 /// 2 means using a 5x5 square of pixels. And so on.
749 kernel_radius: u32,
750 },
751}
752
753uuid_provider!(BrushMode = "48ad4cac-05f3-485a-b2a3-66812713841f");
754
755impl Default for BrushMode {
756 fn default() -> Self {
757 BrushMode::Raise { amount: 1.0 }
758 }
759}
760
761/// Paint target of a brush. It defines the data that the brush will operate on.
762#[derive(Copy, Default, Clone, Reflect, Debug, PartialEq, Eq)]
763pub enum BrushTarget {
764 #[default]
765 /// Modifies the height map
766 HeightMap,
767 /// Draws on a given layer
768 LayerMask {
769 /// The number of the layer to modify
770 layer: usize,
771 },
772 /// Modifies the terrain's holes
773 HoleMask,
774}
775
776uuid_provider!(BrushTarget = "461c1be7-189e-44ee-b8fd-00b8fdbc668f");
777
778/// Brush is used to modify terrain. It supports multiple shapes and modes.
779#[derive(Clone, Reflect, Debug)]
780pub struct Brush {
781 /// Shape of the brush.
782 pub shape: BrushShape,
783 /// Paint mode of the brush.
784 pub mode: BrushMode,
785 /// The data to modify with the brush
786 pub target: BrushTarget,
787 /// Transform that can modify the shape of the brush
788 pub transform: Matrix2<f32>,
789 /// The softness of the edges of the brush.
790 /// 0.0 means that the brush fades very gradually from opaque to transparent.
791 /// 1.0 means that the edges of the brush do not fade.
792 pub hardness: f32,
793 /// The transparency of the brush, allowing the values beneath the brushstroke to show through.
794 /// 0.0 means the brush is fully transparent and does not draw.
795 /// 1.0 means the brush is fully opaque.
796 pub alpha: f32,
797}
798
799impl Default for Brush {
800 fn default() -> Self {
801 Self {
802 transform: Matrix2::identity(),
803 hardness: 0.0,
804 alpha: 1.0,
805 shape: Default::default(),
806 mode: Default::default(),
807 target: Default::default(),
808 }
809 }
810}
811
812/// Verify that the brush operation is not so big that it could cause the editor to freeze.
813/// The user can type in any size of brush they please, even disastrous sizes, and
814/// this check prevents the editor from breaking.
815fn within_size_limit(bounds: &Rect<i32>) -> bool {
816 let size = bounds.size;
817 let area = size.x * size.y;
818 let accepted = area <= BRUSH_PIXEL_SANITY_LIMIT;
819 if !accepted {
820 Log::warn(format!(
821 "Terrain brush operation dropped due to sanity limit: {area}"
822 ))
823 }
824 accepted
825}
826
827impl Brush {
828 /// Send the pixels for this brush to the brush thread.
829 /// - `position`: The position of the brush in texture pixels.
830 /// - `scale`: The size of each pixel in local 2D space. This is used
831 /// to convert the brush's radius from local 2D to pixels.
832 /// - `value`: The brush's value. The meaning of this number depends on the brush.
833 /// - `draw_pixel`: The function that will draw the pixels to the terrain.
834 pub fn stamp<F>(&self, position: Vector2<f32>, scale: Vector2<f32>, mut draw_pixel: F)
835 where
836 F: FnMut(Vector2<i32>, f32),
837 {
838 let mut transform = self.transform;
839 let x_factor = scale.y / scale.x;
840 transform.m11 *= x_factor;
841 transform.m12 *= x_factor;
842 match self.shape {
843 BrushShape::Circle { radius } => {
844 let iter = StampPixels::new(
845 CircleRaster(radius / scale.y),
846 position,
847 self.hardness,
848 transform,
849 );
850 if !within_size_limit(&iter.bounds()) {
851 return;
852 }
853 for BrushPixel { position, strength } in iter {
854 draw_pixel(position, strength);
855 }
856 }
857 BrushShape::Rectangle { width, length } => {
858 let iter = StampPixels::new(
859 RectRaster(width * 0.5 / scale.y, length * 0.5 / scale.y),
860 position,
861 self.hardness,
862 transform,
863 );
864 if !within_size_limit(&iter.bounds()) {
865 return;
866 }
867 for BrushPixel { position, strength } in iter {
868 draw_pixel(position, strength);
869 }
870 }
871 }
872 }
873 /// Send the pixels for this brush to the brush thread.
874 /// - `start`: The position of the brush when it started the smear in texture pixels.
875 /// - `end`: The current position of the brush in texture pixels.
876 /// - `scale`: The size of each pixel in local 2D space. This is used
877 /// to convert the brush's radius from local 2D to pixels.
878 /// - `draw_pixel`: The function that will draw the pixels to the terrain.
879 pub fn smear<F>(
880 &self,
881 start: Vector2<f32>,
882 end: Vector2<f32>,
883 scale: Vector2<f32>,
884 mut draw_pixel: F,
885 ) where
886 F: FnMut(Vector2<i32>, f32),
887 {
888 let mut transform = self.transform;
889 let x_factor = scale.y / scale.x;
890 transform.m11 *= x_factor;
891 transform.m12 *= x_factor;
892 match self.shape {
893 BrushShape::Circle { radius } => {
894 let iter = SmearPixels::new(
895 CircleRaster(radius / scale.y),
896 start,
897 end,
898 self.hardness,
899 transform,
900 );
901 if !within_size_limit(&iter.bounds()) {
902 return;
903 }
904 for BrushPixel { position, strength } in iter {
905 draw_pixel(position, strength);
906 }
907 }
908 BrushShape::Rectangle { width, length } => {
909 let iter = SmearPixels::new(
910 RectRaster(width * 0.5 / scale.y, length * 0.5 / scale.y),
911 start,
912 end,
913 self.hardness,
914 transform,
915 );
916 if !within_size_limit(&iter.bounds()) {
917 return;
918 }
919 for BrushPixel { position, strength } in iter {
920 draw_pixel(position, strength);
921 }
922 }
923 }
924 }
925}
926
927/// A copy of a layer of data from a chunk.
928/// It can be height data or mask data, since the type is erased.
929/// The layer that this data represents must be remembered externally.
930pub struct ChunkData {
931 /// The grid position of the original chunk.
932 pub grid_position: Vector2<i32>,
933 /// The size of the original chunk, to confirm that the chunk's size has not changed since the data was copied.
934 pub size: Vector2<u32>,
935 /// The type-erased data from either the height or one of the layers of the chunk.
936 pub content: Box<[u8]>,
937}
938
939impl std::fmt::Debug for ChunkData {
940 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
941 f.debug_struct("ChunkData")
942 .field("grid_position", &self.grid_position)
943 .field("content", &format!("[..](len: {})", &self.content.len()))
944 .finish()
945 }
946}
947
948fn size_from_texture(texture: &ResourceDataRef<'_, Texture>) -> Vector2<u32> {
949 match texture.kind() {
950 crate::resource::texture::TextureKind::Rectangle { width, height } => {
951 Vector2::new(width, height)
952 }
953 _ => unreachable!("Terrain texture was not rectangle."),
954 }
955}
956
957impl ChunkData {
958 /// Extract the size from the given texture and return true if that size matches
959 /// the size required by this data. Log an error message and return false otherwise.
960 fn verify_texture_size(&self, texture: &ResourceDataRef<'_, Texture>) -> bool {
961 let size = size_from_texture(texture);
962 if size != self.size {
963 Log::err("Command swap failed due to texture size mismatch");
964 false
965 } else {
966 true
967 }
968 }
969 /// Create a ChunkData for the given texture at the given position.
970 pub fn from_texture(grid_position: Vector2<i32>, texture: &TextureResource) -> Self {
971 let data_ref = texture.data_ref();
972 let size = size_from_texture(&data_ref);
973 let data = Box::<[u8]>::from(data_ref.data());
974 Self {
975 grid_position,
976 size,
977 content: data,
978 }
979 }
980 /// Swap the content of this data with the content of the given chunk's height map.
981 pub fn swap_height(&mut self, chunk: &mut Chunk) {
982 let mut data_ref = chunk.heightmap().data_ref();
983 if !self.verify_texture_size(&data_ref) {
984 return;
985 }
986 let mut modify = data_ref.modify();
987 for (a, b) in modify.data_mut().iter_mut().zip(self.content.iter_mut()) {
988 std::mem::swap(a, b);
989 }
990 }
991 /// Swap the content of this data with the content of the given chunk's hole mask.
992 pub fn swap_holes(&mut self, chunk: &mut Chunk) {
993 let Some(mut data_ref) = chunk.hole_mask.as_ref().map(|t| t.data_ref()) else {
994 return;
995 };
996 if !self.verify_texture_size(&data_ref) {
997 return;
998 }
999 let mut modify = data_ref.modify();
1000 for (a, b) in modify.data_mut().iter_mut().zip(self.content.iter_mut()) {
1001 std::mem::swap(a, b);
1002 }
1003 }
1004 /// Swap the content of this data with the content of the given chunk's mask layer.
1005 pub fn swap_layer_mask(&mut self, chunk: &mut Chunk, layer: usize) {
1006 let mut data_ref = chunk.layer_masks[layer].data_ref();
1007 if !self.verify_texture_size(&data_ref) {
1008 return;
1009 }
1010 let mut modify = data_ref.modify();
1011 for (a, b) in modify.data_mut().iter_mut().zip(self.content.iter_mut()) {
1012 std::mem::swap(a, b);
1013 }
1014 }
1015 /// Swap the height data of the a chunk from the list with the height data in this object.
1016 /// The given list of chunks will be searched to find the chunk that matches `grid_position`.
1017 pub fn swap_height_from_list(&mut self, chunks: &mut [Chunk]) {
1018 for c in chunks {
1019 if c.grid_position == self.grid_position {
1020 self.swap_height(c);
1021 break;
1022 }
1023 }
1024 }
1025 /// Swap the hole data of the a chunk from the list with the hole data in this object.
1026 /// The given list of chunks will be searched to find the chunk that matches `grid_position`.
1027 pub fn swap_holes_from_list(&mut self, chunks: &mut [Chunk]) {
1028 for c in chunks {
1029 if c.grid_position == self.grid_position {
1030 self.swap_holes(c);
1031 break;
1032 }
1033 }
1034 }
1035 /// Swap the layer mask data of a particular layer of a chunk from the list with the data in this object.
1036 /// The given list of chunks will be searched to find the chunk that matches `grid_position`.
1037 pub fn swap_layer_mask_from_list(&mut self, chunks: &mut [Chunk], layer: usize) {
1038 for c in chunks {
1039 if c.grid_position == self.grid_position {
1040 self.swap_layer_mask(c, layer);
1041 break;
1042 }
1043 }
1044 }
1045}