use super::Chunk;
use crate::asset::ResourceDataRef;
use crate::core::{
algebra::{Matrix2, Vector2},
log::Log,
math::Rect,
pool::Handle,
reflect::prelude::*,
};
use crate::fxhash::FxHashMap;
use crate::resource::texture::{Texture, TextureResource};
use crate::scene::node::Node;
use fyrox_core::uuid_provider;
use std::collections::VecDeque;
use std::sync::mpsc::{Receiver, SendError, Sender};
pub mod brushraster;
use brushraster::*;
pub mod strokechunks;
use strokechunks::*;
const MESSAGE_BUFFER_SIZE: usize = 40;
const PIXEL_BUFFER_SIZE: usize = 40;
const BRUSH_PIXEL_SANITY_LIMIT: i32 = 1000000;
#[inline]
fn mask_raise(original: u8, amount: f32) -> u8 {
(original as f32 + amount * 255.0).clamp(0.0, 255.0) as u8
}
#[inline]
fn mask_lerp(original: u8, value: f32, t: f32) -> u8 {
let original = original as f32;
let value = value * 255.0;
(original * (1.0 - t) + value * t).clamp(0.0, 255.0) as u8
}
#[derive(Debug, Clone)]
pub enum BrushThreadMessage {
StartStroke(Brush, Handle<Node>, TerrainTextureData),
EndStroke,
Pixel(BrushPixelMessage),
}
#[derive(Debug, Clone)]
pub struct BrushPixelMessage {
pub position: Vector2<i32>,
pub alpha: f32,
pub value: f32,
}
pub struct PixelMessageBuffer {
data: VecDeque<BrushPixelMessage>,
max_size: usize,
}
impl PixelMessageBuffer {
#[inline]
pub fn new(max_size: usize) -> Self {
Self {
data: VecDeque::with_capacity(max_size),
max_size,
}
}
#[inline]
pub fn is_full(&self) -> bool {
self.max_size == self.data.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
#[inline]
pub fn pop(&mut self) -> Option<BrushPixelMessage> {
self.data.pop_front()
}
pub fn push(&mut self, message: BrushPixelMessage) {
assert!(self.data.len() < self.max_size);
if let Some(m) = self
.data
.iter_mut()
.find(|m| m.position == message.position)
{
if message.alpha > m.alpha {
m.alpha = message.alpha;
m.value = message.value;
}
} else {
self.data.push_back(message);
}
}
}
#[derive(Debug, Clone)]
pub struct TerrainTextureData {
pub chunk_size: Vector2<u32>,
pub kind: TerrainTextureKind,
pub resources: FxHashMap<Vector2<i32>, TextureResource>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum TerrainTextureKind {
#[default]
Height,
Mask,
}
pub struct BrushSender(Sender<BrushThreadMessage>);
impl BrushSender {
pub fn new(sender: Sender<BrushThreadMessage>) -> Self {
Self(sender)
}
pub fn start_stroke(&self, brush: Brush, node: Handle<Node>, data: TerrainTextureData) {
self.0
.send(BrushThreadMessage::StartStroke(brush, node, data))
.unwrap_or_else(on_send_failure);
}
pub fn end_stroke(&self) {
self.0
.send(BrushThreadMessage::EndStroke)
.unwrap_or_else(on_send_failure);
}
#[inline]
pub fn draw_pixel(&self, position: Vector2<i32>, alpha: f32, value: f32) {
if alpha == 0.0 {
return;
}
self.0
.send(BrushThreadMessage::Pixel(BrushPixelMessage {
position,
alpha,
value,
}))
.unwrap_or_else(on_send_failure);
}
}
fn on_send_failure(error: SendError<BrushThreadMessage>) {
Log::err(format!("A brush painting message was not sent. {error:?}"));
}
pub type UndoChunkHandler = dyn FnMut(UndoData) + Send;
pub struct UndoData {
pub node: Handle<Node>,
pub chunks: Vec<ChunkData>,
pub target: BrushTarget,
}
#[derive(Default)]
pub struct BrushStroke {
brush: Brush,
textures: FxHashMap<Vector2<i32>, TextureResource>,
node: Handle<Node>,
undo_chunk_handler: Option<Box<UndoChunkHandler>>,
chunks: StrokeChunks,
undo_chunks: Vec<ChunkData>,
height_pixels: StrokeData<f32>,
mask_pixels: StrokeData<u8>,
}
#[derive(Debug, Default)]
pub struct StrokeData<V>(FxHashMap<Vector2<i32>, StrokeElement<V>>);
#[derive(Debug, Copy, Clone)]
pub struct StrokeElement<V> {
pub strength: f32,
pub original_value: V,
pub latest_value: V,
}
impl BrushStroke {
pub fn with_chunk_handler(undo_chunk_handler: Box<UndoChunkHandler>) -> Self {
Self {
undo_chunk_handler: Some(undo_chunk_handler),
..Default::default()
}
}
pub fn brush(&self) -> &Brush {
&self.brush
}
pub fn shape(&mut self) -> &mut BrushShape {
&mut self.brush.shape
}
pub fn mode(&mut self) -> &mut BrushMode {
&mut self.brush.mode
}
pub fn hardness(&mut self) -> &mut f32 {
&mut self.brush.hardness
}
pub fn alpha(&mut self) -> &mut f32 {
&mut self.brush.alpha
}
pub fn stamp(&mut self, position: Vector2<f32>, scale: Vector2<f32>, value: f32) {
let brush = self.brush.clone();
brush.stamp(position, scale, |position, alpha| {
self.draw_pixel(BrushPixelMessage {
position,
alpha,
value,
})
});
}
pub fn smear(
&mut self,
start: Vector2<f32>,
end: Vector2<f32>,
scale: Vector2<f32>,
value: f32,
) {
let brush = self.brush.clone();
brush.smear(start, end, scale, |position, alpha| {
self.draw_pixel(BrushPixelMessage {
position,
alpha,
value,
})
});
}
pub fn clear(&mut self) {
self.height_pixels.clear();
self.mask_pixels.clear();
self.chunks.clear();
}
pub fn data_pixel<V>(&self, position: Vector2<i32>) -> Option<V>
where
V: Clone,
{
let grid_pos = self.chunks.pixel_position_to_grid_position(position);
let origin = self.chunks.chunk_to_origin(grid_pos);
let p = position - origin;
let texture = self.textures.get(&grid_pos)?;
let index = self.chunks.pixel_index(p);
let data = texture.data_ref();
Some(data.data_of_type::<V>().unwrap()[index].clone())
}
pub fn accept_messages(&mut self, receiver: Receiver<BrushThreadMessage>) {
let mut message_buffer = PixelMessageBuffer::new(MESSAGE_BUFFER_SIZE);
loop {
while !message_buffer.is_full() {
if let Ok(message) = receiver.try_recv() {
self.handle_message(message, &mut message_buffer);
} else {
break;
}
}
if let Some(pixel) = message_buffer.pop() {
self.handle_pixel_message(pixel);
} else if self.chunks.count() > 0 {
self.flush();
} else {
if let Ok(message) = receiver.recv() {
self.handle_message(message, &mut message_buffer);
} else {
self.end_stroke();
return;
}
}
}
}
fn handle_message(
&mut self,
message: BrushThreadMessage,
message_buffer: &mut PixelMessageBuffer,
) {
match message {
BrushThreadMessage::StartStroke(brush, node, textures) => {
self.brush = brush;
self.node = node;
self.textures = textures.resources;
self.chunks.set_layout(textures.kind, textures.chunk_size);
}
BrushThreadMessage::EndStroke => {
while let Some(p) = message_buffer.pop() {
self.handle_pixel_message(p);
}
self.end_stroke();
}
BrushThreadMessage::Pixel(pixel) => {
message_buffer.push(pixel);
}
}
}
pub fn start_stroke(&mut self, brush: Brush, node: Handle<Node>, textures: TerrainTextureData) {
self.brush = brush;
self.node = node;
self.chunks.set_layout(textures.kind, textures.chunk_size);
self.textures = textures.resources;
}
pub fn end_stroke(&mut self) {
if let Some(handler) = &mut self.undo_chunk_handler {
self.chunks
.copy_texture_data(&self.textures, &mut self.undo_chunks);
handler(UndoData {
node: self.node,
chunks: std::mem::take(&mut self.undo_chunks),
target: self.brush.target,
});
}
self.apply();
self.clear();
}
pub fn draw_pixel(&mut self, pixel: BrushPixelMessage) {
let pixel = BrushPixelMessage {
alpha: 0.5 * (1.0 - (pixel.alpha * std::f32::consts::PI).cos()),
..pixel
};
let position = pixel.position;
match self.chunks.kind() {
TerrainTextureKind::Height => self.accept_pixel_height(pixel),
TerrainTextureKind::Mask => self.accept_pixel_mask(pixel),
}
self.chunks.write(position);
}
fn handle_pixel_message(&mut self, pixel: BrushPixelMessage) {
self.draw_pixel(pixel);
if self.chunks.count() >= PIXEL_BUFFER_SIZE {
self.flush();
}
}
fn smooth_height(
&self,
position: Vector2<i32>,
kernel_radius: u32,
original: f32,
alpha: f32,
) -> f32 {
let radius = kernel_radius as i32;
let diameter = kernel_radius * 2 + 1;
let area = (diameter * diameter - 1) as f32;
let mut total = 0.0;
for x in -radius..=radius {
for y in -radius..=radius {
if x == 0 && y == 0 {
continue;
}
let pos = position + Vector2::new(x, y);
let value = self
.height_pixels
.original_pixel_value(pos)
.copied()
.or_else(|| self.data_pixel(position))
.unwrap_or_default();
total += value;
}
}
let smoothed = total / area;
original * (1.0 - alpha) + smoothed * alpha
}
fn smooth_mask(
&self,
position: Vector2<i32>,
kernel_radius: u32,
original: u8,
alpha: f32,
) -> u8 {
let radius = kernel_radius as i32;
let diameter = kernel_radius as u64 * 2 + 1;
let area = diameter * diameter - 1;
let mut total: u64 = 0;
for x in -radius..=radius {
for y in -radius..=radius {
if x == 0 && y == 0 {
continue;
}
let pos = position + Vector2::new(x, y);
let value = self
.mask_pixels
.original_pixel_value(pos)
.copied()
.or_else(|| self.data_pixel(position))
.unwrap_or_default();
total += value as u64;
}
}
let smoothed = total / area;
(original as f32 * (1.0 - alpha) + smoothed as f32 * alpha).clamp(0.0, 255.0) as u8
}
fn accept_pixel_height(&mut self, pixel: BrushPixelMessage) {
let position = pixel.position;
let mut pixels = std::mem::take(&mut self.height_pixels);
let original = pixels.update_strength(position, pixel.alpha, || self.data_pixel(position));
self.height_pixels = pixels;
let Some(original) = original else {
return;
};
let alpha = self.brush.alpha * pixel.alpha;
let result: f32 = match self.brush.mode {
BrushMode::Raise { amount } => original + amount * alpha,
BrushMode::Flatten => original * (1.0 - alpha) + pixel.value * alpha,
BrushMode::Assign { value } => original * (1.0 - alpha) + value * alpha,
BrushMode::Smooth { kernel_radius } => {
self.smooth_height(position, kernel_radius, original, alpha)
}
};
self.height_pixels.set_latest(position, result);
}
fn accept_pixel_mask(&mut self, pixel: BrushPixelMessage) {
let position = pixel.position;
let mut pixels = std::mem::take(&mut self.mask_pixels);
let original = pixels.update_strength(position, pixel.alpha, || self.data_pixel(position));
self.mask_pixels = pixels;
let Some(original) = original else {
return;
};
let alpha = self.brush.alpha * pixel.alpha;
let result: u8 = match self.brush.mode {
BrushMode::Raise { amount } => mask_raise(original, amount * alpha),
BrushMode::Flatten => mask_lerp(original, pixel.value, alpha),
BrushMode::Assign { value } => mask_lerp(original, value, alpha),
BrushMode::Smooth { kernel_radius } => {
self.smooth_mask(position, kernel_radius, original, alpha)
}
};
self.mask_pixels.set_latest(position, result);
}
pub fn flush(&mut self) {
if self.undo_chunk_handler.is_some() {
self.chunks
.copy_texture_data(&self.textures, &mut self.undo_chunks);
}
self.apply();
self.chunks.clear();
}
fn apply(&self) {
match self.chunks.kind() {
TerrainTextureKind::Height => {
self.chunks.apply(&self.height_pixels, &self.textures);
}
TerrainTextureKind::Mask => {
self.chunks.apply(&self.mask_pixels, &self.textures);
}
}
}
}
impl<V> StrokeData<V> {
#[inline]
pub fn clear(&mut self) {
self.0.clear()
}
#[inline]
pub fn original_pixel_value(&self, position: Vector2<i32>) -> Option<&V> {
self.0.get(&position).map(|x| &x.original_value)
}
#[inline]
pub fn latest_pixel_value(&self, position: Vector2<i32>) -> Option<&V> {
self.0.get(&position).map(|x| &x.latest_value)
}
#[inline]
pub fn set_latest(&mut self, position: Vector2<i32>, value: V) {
if let Some(el) = self.0.get_mut(&position) {
el.latest_value = value;
} else {
panic!("Setting latest value of missing element");
}
}
#[inline]
pub fn update_strength<F>(
&mut self,
position: Vector2<i32>,
strength: f32,
pixel_value: F,
) -> Option<V>
where
V: Clone,
F: FnOnce() -> Option<V>,
{
if strength == 0.0 {
None
} else if let Some(element) = self.0.get_mut(&position) {
if element.strength < strength {
element.strength = strength;
Some(element.original_value.clone())
} else {
None
}
} else {
let value = pixel_value()?;
let element = StrokeElement {
strength,
latest_value: value.clone(),
original_value: value.clone(),
};
self.0.insert(position, element);
Some(value)
}
}
}
#[derive(Copy, Clone, Reflect, Debug)]
pub enum BrushShape {
Circle {
radius: f32,
},
Rectangle {
width: f32,
length: f32,
},
}
uuid_provider!(BrushShape = "a4dbfba0-077c-4658-9972-38384a8432f9");
impl Default for BrushShape {
fn default() -> Self {
BrushShape::Circle { radius: 1.0 }
}
}
impl BrushShape {
pub fn contains(&self, brush_center: Vector2<f32>, pixel_position: Vector2<f32>) -> bool {
match *self {
BrushShape::Circle { radius } => (brush_center - pixel_position).norm() < radius,
BrushShape::Rectangle { width, length } => Rect::new(
brush_center.x - width * 0.5,
brush_center.y - length * 0.5,
width,
length,
)
.contains(pixel_position),
}
}
}
#[derive(Clone, PartialEq, PartialOrd, Reflect, Debug)]
pub enum BrushMode {
Raise {
amount: f32,
},
Flatten,
Assign {
value: f32,
},
Smooth {
kernel_radius: u32,
},
}
uuid_provider!(BrushMode = "48ad4cac-05f3-485a-b2a3-66812713841f");
impl Default for BrushMode {
fn default() -> Self {
BrushMode::Raise { amount: 1.0 }
}
}
#[derive(Copy, Default, Clone, Reflect, Debug, PartialEq, Eq)]
pub enum BrushTarget {
#[default]
HeightMap,
LayerMask {
layer: usize,
},
HoleMask,
}
uuid_provider!(BrushTarget = "461c1be7-189e-44ee-b8fd-00b8fdbc668f");
#[derive(Clone, Reflect, Debug)]
pub struct Brush {
pub shape: BrushShape,
pub mode: BrushMode,
pub target: BrushTarget,
pub transform: Matrix2<f32>,
pub hardness: f32,
pub alpha: f32,
}
impl Default for Brush {
fn default() -> Self {
Self {
transform: Matrix2::identity(),
hardness: 0.0,
alpha: 1.0,
shape: Default::default(),
mode: Default::default(),
target: Default::default(),
}
}
}
fn within_size_limit(bounds: &Rect<i32>) -> bool {
let size = bounds.size;
let area = size.x * size.y;
let accepted = area <= BRUSH_PIXEL_SANITY_LIMIT;
if !accepted {
Log::warn(format!(
"Terrain brush operation dropped due to sanity limit: {area}"
))
}
accepted
}
impl Brush {
pub fn stamp<F>(&self, position: Vector2<f32>, scale: Vector2<f32>, mut draw_pixel: F)
where
F: FnMut(Vector2<i32>, f32),
{
let mut transform = self.transform;
let x_factor = scale.y / scale.x;
transform.m11 *= x_factor;
transform.m12 *= x_factor;
match self.shape {
BrushShape::Circle { radius } => {
let iter = StampPixels::new(
CircleRaster(radius / scale.y),
position,
self.hardness,
transform,
);
if !within_size_limit(&iter.bounds()) {
return;
}
for BrushPixel { position, strength } in iter {
draw_pixel(position, strength);
}
}
BrushShape::Rectangle { width, length } => {
let iter = StampPixels::new(
RectRaster(width * 0.5 / scale.y, length * 0.5 / scale.y),
position,
self.hardness,
transform,
);
if !within_size_limit(&iter.bounds()) {
return;
}
for BrushPixel { position, strength } in iter {
draw_pixel(position, strength);
}
}
}
}
pub fn smear<F>(
&self,
start: Vector2<f32>,
end: Vector2<f32>,
scale: Vector2<f32>,
mut draw_pixel: F,
) where
F: FnMut(Vector2<i32>, f32),
{
let mut transform = self.transform;
let x_factor = scale.y / scale.x;
transform.m11 *= x_factor;
transform.m12 *= x_factor;
match self.shape {
BrushShape::Circle { radius } => {
let iter = SmearPixels::new(
CircleRaster(radius / scale.y),
start,
end,
self.hardness,
transform,
);
if !within_size_limit(&iter.bounds()) {
return;
}
for BrushPixel { position, strength } in iter {
draw_pixel(position, strength);
}
}
BrushShape::Rectangle { width, length } => {
let iter = SmearPixels::new(
RectRaster(width * 0.5 / scale.y, length * 0.5 / scale.y),
start,
end,
self.hardness,
transform,
);
if !within_size_limit(&iter.bounds()) {
return;
}
for BrushPixel { position, strength } in iter {
draw_pixel(position, strength);
}
}
}
}
}
pub struct ChunkData {
pub grid_position: Vector2<i32>,
pub size: Vector2<u32>,
pub content: Box<[u8]>,
}
impl std::fmt::Debug for ChunkData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChunkData")
.field("grid_position", &self.grid_position)
.field("content", &format!("[..](len: {})", &self.content.len()))
.finish()
}
}
fn size_from_texture(texture: &ResourceDataRef<'_, Texture>) -> Vector2<u32> {
match texture.kind() {
crate::resource::texture::TextureKind::Rectangle { width, height } => {
Vector2::new(width, height)
}
_ => unreachable!("Terrain texture was not rectangle."),
}
}
impl ChunkData {
fn verify_texture_size(&self, texture: &ResourceDataRef<'_, Texture>) -> bool {
let size = size_from_texture(texture);
if size != self.size {
Log::err("Command swap failed due to texture size mismatch");
false
} else {
true
}
}
pub fn from_texture(grid_position: Vector2<i32>, texture: &TextureResource) -> Self {
let data_ref = texture.data_ref();
let size = size_from_texture(&data_ref);
let data = Box::<[u8]>::from(data_ref.data());
Self {
grid_position,
size,
content: data,
}
}
pub fn swap_height(&mut self, chunk: &mut Chunk) {
let mut data_ref = chunk.heightmap().data_ref();
if !self.verify_texture_size(&data_ref) {
return;
}
let mut modify = data_ref.modify();
for (a, b) in modify.data_mut().iter_mut().zip(self.content.iter_mut()) {
std::mem::swap(a, b);
}
}
pub fn swap_holes(&mut self, chunk: &mut Chunk) {
let Some(mut data_ref) = chunk.hole_mask.as_ref().map(|t| t.data_ref()) else {
return;
};
if !self.verify_texture_size(&data_ref) {
return;
}
let mut modify = data_ref.modify();
for (a, b) in modify.data_mut().iter_mut().zip(self.content.iter_mut()) {
std::mem::swap(a, b);
}
}
pub fn swap_layer_mask(&mut self, chunk: &mut Chunk, layer: usize) {
let mut data_ref = chunk.layer_masks[layer].data_ref();
if !self.verify_texture_size(&data_ref) {
return;
}
let mut modify = data_ref.modify();
for (a, b) in modify.data_mut().iter_mut().zip(self.content.iter_mut()) {
std::mem::swap(a, b);
}
}
pub fn swap_height_from_list(&mut self, chunks: &mut [Chunk]) {
for c in chunks {
if c.grid_position == self.grid_position {
self.swap_height(c);
break;
}
}
}
pub fn swap_holes_from_list(&mut self, chunks: &mut [Chunk]) {
for c in chunks {
if c.grid_position == self.grid_position {
self.swap_holes(c);
break;
}
}
}
pub fn swap_layer_mask_from_list(&mut self, chunks: &mut [Chunk], layer: usize) {
for c in chunks {
if c.grid_position == self.grid_position {
self.swap_layer_mask(c, layer);
break;
}
}
}
}