use crate::{
render::{
buffer::Buffer,
cell::{cellsym_block, decode_pua},
sprite::Layer,
style::{Color, Modifier},
symbol_map::{get_layered_symbol_map, Tile},
AdapterBase,
},
util::{ARect, PointF32, PointI32, PointU16, Rand},
LOGO_FRAME,
};
use std::sync::OnceLock;
use super::logo_data::LOGO_PIX_DATA;
static LOGO_CELLS: OnceLock<Vec<(u8, u8, u8, u8)>> = OnceLock::new();
fn get_logo_cells() -> &'static Vec<(u8, u8, u8, u8)> {
LOGO_CELLS.get_or_init(|| {
let mut cells = Vec::new();
let mut lines = LOGO_PIX_DATA.lines();
let mut found_header = false;
for line in lines.by_ref() {
if line.starts_with("width=") {
found_header = true;
break;
}
}
if !found_header {
return cells;
}
for line in lines {
if line.trim().is_empty() {
continue;
}
for cell_str in line.split_whitespace() {
let parts: Vec<&str> = cell_str.split(',').collect();
if parts.len() >= 3 {
if let (Ok(sym), Ok(fg), Ok(tex)) = (
parts[0].parse::<u8>(),
parts[1].parse::<u8>(),
parts[2].parse::<u8>(),
) {
let bg = if parts.len() >= 4 {
parts[3].parse::<u8>().unwrap_or(0)
} else {
0
};
cells.push((sym, fg, tex, bg));
}
}
}
}
cells
})
}
#[cfg(graphics_mode)]
#[derive(Clone, Debug, Default)]
pub enum RtSize {
#[default]
FollowWindow,
Fixed(u32, u32),
}
#[cfg(graphics_mode)]
#[derive(Clone, Debug)]
pub struct RtConfig {
pub size: RtSize,
}
#[cfg(graphics_mode)]
impl Default for RtConfig {
fn default() -> Self {
Self {
size: RtSize::FollowWindow,
}
}
}
#[cfg(graphics_mode)]
#[derive(Clone, Copy, Debug, Default)]
pub enum BlendMode {
#[default]
Normal,
Add,
Multiply,
Screen,
}
#[cfg(graphics_mode)]
#[derive(Clone, Debug)]
pub struct RtComposite {
pub rt: usize,
pub viewport: Option<ARect>,
pub content_size: Option<(u32, u32)>,
pub blend: BlendMode,
pub alpha: u8,
pub transform: Option<UnifiedTransform>,
}
#[cfg(graphics_mode)]
impl RtComposite {
pub fn fullscreen(rt: usize) -> Self {
Self {
rt,
viewport: None,
content_size: None,
blend: BlendMode::Normal,
alpha: 255,
transform: None,
}
}
pub fn with_viewport(rt: usize, viewport: ARect) -> Self {
let content_size = Some((viewport.w, viewport.h));
Self {
rt,
viewport: Some(viewport),
content_size,
blend: BlendMode::Normal,
alpha: 255,
transform: None,
}
}
pub fn blend(mut self, blend: BlendMode) -> Self {
self.blend = blend;
self
}
pub fn alpha(mut self, alpha: u8) -> Self {
self.alpha = alpha;
self
}
pub fn transform(mut self, transform: UnifiedTransform) -> Self {
self.transform = Some(transform);
self
}
pub fn offset(mut self, dx: i32, dy: i32) -> Self {
if let Some(ref mut vp) = self.viewport {
vp.x += dx;
vp.y += dy;
}
self
}
pub fn x(mut self, x: i32) -> Self {
if let Some(ref mut vp) = self.viewport {
vp.x = x;
}
self
}
pub fn y(mut self, y: i32) -> Self {
if let Some(ref mut vp) = self.viewport {
vp.y = y;
}
self
}
pub fn scale(mut self, scale_x: f32, scale_y: f32) -> Self {
if let Some(ref mut vp) = self.viewport {
let old_w = vp.w;
let old_h = vp.h;
vp.w = (vp.w as f32 * scale_x) as u32;
vp.h = (vp.h as f32 * scale_y) as u32;
vp.x += (old_w as i32 - vp.w as i32) / 2;
vp.y += (old_h as i32 - vp.h as i32) / 2;
}
self
}
pub fn scale_uniform(self, scale: f32) -> Self {
self.scale(scale, scale)
}
pub fn rotate(mut self, degrees: f32) -> Self {
let mut transform = self.transform.unwrap_or_default();
transform.rotate(degrees.to_radians());
self.transform = Some(transform);
self
}
pub fn translate(mut self, dx: f32, dy: f32) -> Self {
let mut transform = self.transform.unwrap_or_default();
transform.translate(dx, dy);
self.transform = Some(transform);
self
}
pub fn width(mut self, w: u32) -> Self {
if let Some(ref mut vp) = self.viewport {
vp.w = w;
}
self
}
pub fn height(mut self, h: u32) -> Self {
if let Some(ref mut vp) = self.viewport {
vp.h = h;
}
self
}
pub fn size(mut self, w: u32, h: u32) -> Self {
if let Some(ref mut vp) = self.viewport {
vp.w = w;
vp.h = h;
}
self
}
pub fn cells_to_pixel_size(
cell_w: u16,
cell_h: u16,
sym_w: f32,
sym_h: f32,
rx: f32,
ry: f32,
) -> (u32, u32) {
let pw = (cell_w as f32 * sym_w / rx) as u32;
let ph = (cell_h as f32 * sym_h / ry) as u32;
(pw, ph)
}
pub fn centered(rt: usize, vp_w: u32, vp_h: u32, canvas_w: u32, canvas_h: u32) -> Self {
let x = ((canvas_w.saturating_sub(vp_w)) / 2) as i32;
let y = ((canvas_h.saturating_sub(vp_h)) / 2) as i32;
Self {
rt,
viewport: Some(ARect { x, y, w: vp_w, h: vp_h }),
content_size: Some((vp_w, vp_h)),
blend: BlendMode::Normal,
alpha: 255,
transform: None,
}
}
pub fn at_position(rt: usize, x: i32, y: i32, w: u32, h: u32) -> Self {
Self {
rt,
viewport: Some(ARect { x, y, w, h }),
content_size: Some((w, h)),
blend: BlendMode::Normal,
alpha: 255,
transform: None,
}
}
pub fn centered_cells(
rt: usize,
cell_w: u16,
cell_h: u16,
sym_w: f32,
sym_h: f32,
rx: f32,
ry: f32,
canvas_w: u32,
canvas_h: u32,
) -> Self {
let (vp_w, vp_h) = Self::cells_to_pixel_size(cell_w, cell_h, sym_w, sym_h, rx, ry);
Self::centered(rt, vp_w, vp_h, canvas_w, canvas_h)
}
}
static LETTERBOXING_OVERRIDE: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub fn is_letterboxing_enabled() -> bool {
crate::get_game_config().fullscreen_fit
|| LETTERBOXING_OVERRIDE.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn set_letterboxing_override(enabled: bool) {
LETTERBOXING_OVERRIDE.store(enabled, std::sync::atomic::Ordering::Relaxed);
}
pub const PIXEL_SYMBOL_SIZE: f32 = 16.0;
pub static PIXEL_SYM_WIDTH: OnceLock<f32> = OnceLock::new();
pub static PIXEL_SYM_HEIGHT: OnceLock<f32> = OnceLock::new();
pub static PIXEL_RATIO_X: OnceLock<f32> = OnceLock::new();
pub static PIXEL_RATIO_Y: OnceLock<f32> = OnceLock::new();
pub fn get_ratio_x() -> f32 {
*PIXEL_RATIO_X.get().unwrap_or(&1.0)
}
pub fn get_ratio_y() -> f32 {
*PIXEL_RATIO_Y.get().unwrap_or(&1.0)
}
pub fn init_sym_width(width: u32) -> f32 {
const TEXTURE_GRID_SIZE: f32 = 256.0;
width as f32 / TEXTURE_GRID_SIZE
}
pub fn init_sym_height(height: u32) -> f32 {
const TEXTURE_GRID_SIZE: f32 = 256.0;
height as f32 / TEXTURE_GRID_SIZE
}
pub const PIXEL_LOGO_WIDTH: usize = 180;
pub const PIXEL_LOGO_HEIGHT: usize = 60;
pub const PIXEL_LOGO_SCALE: f32 = 0.2;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UnifiedColor {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl UnifiedColor {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
pub fn white() -> Self {
Self::new(1.0, 1.0, 1.0, 1.0)
}
pub fn black() -> Self {
Self::new(0.0, 0.0, 0.0, 1.0)
}
pub fn to_array(&self) -> [f32; 4] {
[self.r, self.g, self.b, self.a]
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UnifiedTransform {
pub m00: f32,
pub m01: f32,
pub m10: f32,
pub m11: f32,
pub m20: f32,
pub m21: f32,
}
impl UnifiedTransform {
pub fn new() -> Self {
Self {
m00: 1.0,
m01: 0.0,
m10: 0.0,
m11: 1.0,
m20: 0.0,
m21: 0.0,
}
}
pub fn new_with_values(m00: f32, m01: f32, m10: f32, m11: f32, m20: f32, m21: f32) -> Self {
Self {
m00,
m01,
m10,
m11,
m20,
m21,
}
}
pub fn scale(&mut self, x: f32, y: f32) {
self.m00 *= x;
self.m10 *= y;
self.m01 *= x;
self.m11 *= y;
}
pub fn translate(&mut self, x: f32, y: f32) {
self.m20 += self.m00 * x + self.m10 * y;
self.m21 += self.m01 * x + self.m11 * y;
}
pub fn rotate(&mut self, angle: f32) {
let cos_a = angle.cos();
let sin_a = angle.sin();
let m00 = self.m00;
let m01 = self.m01;
let m10 = self.m10;
let m11 = self.m11;
self.m00 = m00 * cos_a - m10 * sin_a;
self.m10 = m00 * sin_a + m10 * cos_a;
self.m01 = m01 * cos_a - m11 * sin_a;
self.m11 = m01 * sin_a + m11 * cos_a;
}
pub fn skew_x(&mut self, shear_x: f32) {
self.m01 -= self.m00 * shear_x;
self.m11 -= self.m10 * shear_x;
}
pub fn identity(&mut self) {
self.m00 = 1.0;
self.m01 = 0.0;
self.m10 = 0.0;
self.m11 = 1.0;
self.m20 = 0.0;
self.m21 = 0.0;
}
pub fn set(&mut self, other: &UnifiedTransform) {
*self = *other;
}
pub fn copy(&self) -> Self {
*self
}
pub fn compose(&self, other: &UnifiedTransform) -> Self {
Self {
m00: self.m00 * other.m00 + self.m10 * other.m01,
m01: self.m01 * other.m00 + self.m11 * other.m01,
m10: self.m00 * other.m10 + self.m10 * other.m11,
m11: self.m01 * other.m10 + self.m11 * other.m11,
m20: self.m00 * other.m20 + self.m10 * other.m21 + self.m20,
m21: self.m01 * other.m20 + self.m11 * other.m21 + self.m21,
}
}
pub fn multiply(&mut self, other: &UnifiedTransform) {
let new_m00 = self.m00 * other.m00 + self.m01 * other.m10;
let new_m01 = self.m00 * other.m01 + self.m01 * other.m11;
let new_m10 = self.m10 * other.m00 + self.m11 * other.m10;
let new_m11 = self.m10 * other.m01 + self.m11 * other.m11;
let new_m20 = self.m20 * other.m00 + self.m21 * other.m10 + other.m20;
let new_m21 = self.m20 * other.m01 + self.m21 * other.m11 + other.m21;
self.m00 = new_m00;
self.m01 = new_m01;
self.m10 = new_m10;
self.m11 = new_m11;
self.m20 = new_m20;
self.m21 = new_m21;
}
pub fn to_matrix4(&self) -> [[f32; 4]; 4] {
[
[self.m00, self.m01, 0.0, 0.0],
[self.m10, self.m11, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[self.m20, self.m21, 0.0, 1.0],
]
}
}
impl Default for UnifiedTransform {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub struct RenderCell {
pub fcolor: (f32, f32, f32, f32),
pub bcolor: Option<(f32, f32, f32, f32)>,
pub tile: crate::render::symbol_map::Tile,
pub x: f32,
pub y: f32,
pub w: u32,
pub h: u32,
pub angle: f32,
pub cx: f32,
pub cy: f32,
pub modifier: u16,
}
pub struct Graph {
pub pixel_w: u32,
pub pixel_h: u32,
pub ratio_x: f32,
pub ratio_y: f32,
pub use_tui_height: bool,
pub rflag: bool,
pub rbuf: Vec<RenderCell>,
}
impl Default for Graph {
fn default() -> Self {
Self {
pixel_w: 0,
pixel_h: 0,
ratio_x: 1.0,
ratio_y: 1.0,
use_tui_height: false,
rflag: true,
rbuf: Vec::new(),
}
}
}
impl Graph {
pub fn new() -> Self {
Self::default()
}
pub fn set_ratiox(&mut self, rx: f32) {
let rx = if crate::get_game_config().fullscreen { 1.0 } else { rx };
self.ratio_x = rx;
let _ = PIXEL_RATIO_X.set(rx);
}
pub fn set_ratioy(&mut self, ry: f32) {
let ry = if crate::get_game_config().fullscreen { 1.0 } else { ry };
self.ratio_y = ry;
let _ = PIXEL_RATIO_Y.set(ry);
}
pub fn set_use_tui_height(&mut self, use_tui: bool) {
self.use_tui_height = use_tui;
}
pub fn set_pixel_size(&mut self, cell_w: u16, cell_h: u16) {
let sym_width = PIXEL_SYM_WIDTH.get().expect("lazylock init");
let sym_height = PIXEL_SYM_HEIGHT.get().expect("lazylock init");
self.pixel_w = (cell_w as f32 * sym_width / self.ratio_x) as u32;
let height_multiplier = if self.use_tui_height { 2.0 } else { 1.0 };
self.pixel_h = (cell_h as f32 * sym_height * height_multiplier / self.ratio_y) as u32;
}
pub fn cell_width(&self) -> f32 {
PIXEL_SYM_WIDTH.get().expect("lazylock init") / self.ratio_x
}
pub fn cell_height(&self) -> f32 {
PIXEL_SYM_HEIGHT.get().expect("lazylock init") / self.ratio_y
}
}
pub fn push_render_buffer(
rbuf: &mut Vec<RenderCell>,
fc: &(u8, u8, u8, u8),
bgc: &Option<(u8, u8, u8, u8)>,
tile: crate::render::symbol_map::Tile,
s: ARect,
angle: f64,
ccp: &PointI32,
modifier: u16,
) {
let mut wc = RenderCell {
fcolor: (
fc.0 as f32 / 255.0,
fc.1 as f32 / 255.0,
fc.2 as f32 / 255.0,
fc.3 as f32 / 255.0,
),
modifier,
..Default::default()
};
if let Some(bc) = bgc {
wc.bcolor = Some((
bc.0 as f32 / 255.0,
bc.1 as f32 / 255.0,
bc.2 as f32 / 255.0,
bc.3 as f32 / 255.0,
));
} else {
wc.bcolor = None;
}
wc.tile = tile;
wc.x = s.x as f32 + s.w as f32;
wc.y = s.y as f32 + s.h as f32;
wc.w = s.w;
wc.h = s.h;
if angle == 0.0 {
wc.angle = angle as f32;
} else {
let mut aa = (1.0 - angle / 180.0) * std::f64::consts::PI;
let pi2 = std::f64::consts::PI * 2.0;
while aa < 0.0 {
aa += pi2;
}
while aa > pi2 {
aa -= pi2;
}
wc.angle = aa as f32;
}
wc.cx = ccp.x as f32;
wc.cy = ccp.y as f32;
rbuf.push(wc);
}
pub fn render_helper(
cell_w: u16,
r: PointF32,
i: usize,
p: PointU16,
cell_h: u8,
) -> ARect {
render_helper_with_scale(cell_w, r, i, p, 1.0, 1.0, 1.0, None, cell_h)
}
pub fn render_helper_with_scale(
cell_w: u16,
r: PointF32,
i: usize,
p: PointU16,
scale_x: f32,
scale_y: f32,
sprite_scale_y: f32,
cumulative_x: Option<f32>,
cell_h: u8,
) -> ARect {
let w = *PIXEL_SYM_WIDTH.get().expect("lazylock init") as i32;
let h = (*PIXEL_SYM_HEIGHT.get().expect("lazylock init") as i32) * cell_h as i32;
let dsty = i as u16 / cell_w;
let (scaled_x, scaled_y, scaled_w, scaled_h) = if let Some(cum_x) = cumulative_x {
let row_h = h as f32 / r.y * sprite_scale_y;
let cell_fh = h as f32 / r.y * scale_y;
let y_offset = (row_h - cell_fh) / 2.0;
let base_y = dsty as f32 * row_h;
let this_y_f = base_y + y_offset;
let next_y_f = (dsty as f32 + 1.0) * row_h + y_offset;
let w_val = (w as f32 / r.x * scale_x).round() as u32;
let h_val = if y_offset.abs() < 0.01 {
(next_y_f.round() - this_y_f.round()) as u32
} else {
(h as f32 / r.y * scale_y).round() as u32
};
(cum_x.round(), this_y_f.round(), w_val, h_val)
} else {
let dstx = i as u16 % cell_w;
let cell_f_w = w as f32 / r.x * scale_x;
let cell_f_h = h as f32 / r.y * scale_y;
let this_x = (dstx as f32 * cell_f_w).round();
let this_y = (dsty as f32 * cell_f_h).round();
let next_x = ((dstx as f32 + 1.0) * cell_f_w).round();
let next_y = ((dsty as f32 + 1.0) * cell_f_h).round();
(this_x, this_y, (next_x - this_x) as u32, (next_y - this_y) as u32)
};
ARect {
x: scaled_x as i32 + p.x as i32,
y: scaled_y as i32 + p.y as i32,
w: scaled_w,
h: scaled_h,
}
}
pub fn render_buffer_to_cells<F>(
buf: &Buffer,
rx: f32,
ry: f32,
use_tui: bool,
alpha: u8,
scale_x: f32,
scale_y: f32,
angle: f64,
mut f: F,
) where
F: FnMut(&(u8, u8, u8, u8), &Option<(u8, u8, u8, u8)>, ARect, Tile, f64, PointI32, u16),
{
let px = buf.area.x;
let py = buf.area.y;
let pw = buf.area.width;
let ph = buf.area.height;
let w = *PIXEL_SYM_WIDTH.get().expect("lazylock init");
let h = *PIXEL_SYM_HEIGHT.get().expect("lazylock init");
let base_cell_w = w / rx;
let base_cell_h = h / ry;
let mut row_pixel_widths: Vec<f32> = vec![0.0; ph as usize];
{
let mut skip = false;
for (i, cell) in buf.content.iter().enumerate() {
if skip {
skip = false;
continue;
}
let row = i / pw as usize;
let tile = cell.get_tile();
let has_fixed_slot = cell.modifier.contains(Modifier::FIXED_SLOT);
let cell_sx = scale_x * cell.scale_x;
let effective_slot_scale = if has_fixed_slot {
scale_x
} else if cell.scale_x >= 1.0 {
cell_sx
} else {
scale_x
};
let slot_w = base_cell_w * effective_slot_scale;
if tile.cell_w >= 2 {
row_pixel_widths[row] += slot_w * 2.0;
if (i + 1) % pw as usize != 0 {
skip = true;
}
} else {
row_pixel_widths[row] += slot_w;
}
}
}
let mut cumulative_x: f32 = 0.0;
let mut last_row: u16 = 0;
let mut skip_next = false;
for (i, cell) in buf.content.iter().enumerate() {
let row = i as u16 / pw;
if row != last_row {
cumulative_x = 0.0;
last_row = row;
}
if skip_next {
skip_next = false;
continue;
}
let cell_sx = scale_x * cell.scale_x;
let cell_sy = scale_y * cell.scale_y;
let mut tile = cell.get_tile();
let fg = cell.fg;
let bg = cell.bg;
let modifier = cell.modifier.bits();
if use_tui {
if let Some(ch) = cell.symbol.chars().next() {
if let Some((block, idx)) = decode_pua(ch) {
if block < 160 {
if let Some(map) = get_layered_symbol_map() {
let ascii_str = (idx as char).to_string();
tile = *map.resolve(&ascii_str);
}
}
}
}
}
let has_fixed_slot = cell.modifier.contains(Modifier::FIXED_SLOT);
let effective_slot_scale = if has_fixed_slot {
scale_x
} else if cell.scale_x >= 1.0 {
cell_sx
} else {
scale_x
};
let slot_w = base_cell_w * effective_slot_scale;
let rendered_w = base_cell_w * cell_sx;
let x_center_offset = (slot_w - rendered_w) / 2.0;
let mut s2 = render_helper_with_scale(
pw,
PointF32 { x: rx, y: ry },
i,
PointU16 { x: px, y: py },
cell_sx,
cell_sy,
scale_y,
Some(cumulative_x + x_center_offset),
tile.cell_h,
);
let mut grid_advance = slot_w;
if tile.cell_w >= 2 {
s2.w *= 2;
grid_advance = slot_w * 2.0;
if (i + 1) % pw as usize != 0 {
skip_next = true;
}
}
if x_center_offset.abs() < 0.01 {
let this_x_f = cumulative_x + x_center_offset;
let next_x_f = this_x_f + grid_advance;
let corrected_w = next_x_f.round() as i32 - this_x_f.round() as i32;
if corrected_w > 0 {
s2.w = corrected_w as u32;
}
}
cumulative_x += grid_advance;
let row = i / pw as usize;
let row_center_x = row_pixel_widths[row] / 2.0;
let cell_left_x = cumulative_x - grid_advance;
let offset_x = row_center_x - cell_left_x;
let row_h = base_cell_h * scale_y;
let buffer_center_y = ph as f32 * row_h / 2.0;
let cell_top_y = row as f32 * row_h;
let offset_y = buffer_center_y - cell_top_y;
let ccp = PointI32 {
x: offset_x as i32,
y: offset_y as i32,
};
let fc = if use_tui && tile.is_emoji {
(255, 255, 255, alpha)
} else {
let mut rgba = fg.get_rgba();
rgba.3 = alpha;
rgba
};
let bc = if bg != Color::Reset {
let mut brgba = bg.get_rgba();
brgba.3 = alpha;
Some(brgba)
} else {
None
};
f(&fc, &bc, s2, tile, angle, ccp, modifier);
}
}
pub fn render_layers<F>(layers: &mut Layer, rx: f32, ry: f32, mut f: F)
where
F: FnMut(&(u8, u8, u8, u8), &Option<(u8, u8, u8, u8)>, ARect, Tile, f64, PointI32, u16),
{
layers.update_render_index();
for si in &layers.render_index.clone() {
let s = &layers.sprites[si.0];
if s.is_hidden() {
continue;
}
render_buffer_to_cells(
&s.content,
rx,
ry,
s.use_tui, s.alpha,
s.scale_x,
s.scale_y,
s.angle,
|fc, bc, s2, tile, angle, ccp, modifier| {
f(fc, bc, s2, tile, angle, ccp, modifier);
},
);
}
}
pub fn render_main_buffer<F>(
buf: &Buffer,
_width: u16, rx: f32,
ry: f32,
use_tui: bool,
mut f: F,
) where
F: FnMut(&(u8, u8, u8, u8), &Option<(u8, u8, u8, u8)>, ARect, Tile, u16),
{
render_buffer_to_cells(
buf,
rx,
ry,
use_tui,
255, 1.0, 1.0, 0.0, |fc, bc, s2, tile, _angle, _ccp, modifier| {
f(fc, bc, s2, tile, modifier);
},
);
}
pub fn render_logo<F>(srx: f32, sry: f32, spw: u32, sph: u32, _rd: &mut Rand, stage: u32, mut f: F)
where
F: FnMut(&(u8, u8, u8, u8), ARect, Tile),
{
let logo_cells = get_logo_cells();
let rx = srx * 1.0;
let ry = sry * 1.0;
let scale = PIXEL_LOGO_SCALE;
let symw = PIXEL_SYM_WIDTH.get().expect("lazylock init") / rx * scale;
let symh = PIXEL_SYM_HEIGHT.get().expect("lazylock init") / ry * scale;
let logo_pixel_w = PIXEL_LOGO_WIDTH as f32 * symw;
let logo_pixel_h = PIXEL_LOGO_HEIGHT as f32 * symh;
let base_x = (spw as f32 / 2.0 - logo_pixel_w / 2.0) as u16;
let base_y = (sph as f32 / 2.0 - logo_pixel_h / 2.0) as u16;
for y in 0usize..PIXEL_LOGO_HEIGHT {
for x in 0usize..PIXEL_LOGO_WIDTH {
let sci = y * PIXEL_LOGO_WIDTH + x;
if sci >= logo_cells.len() {
continue;
}
let (sym, fg, tex, _bg) = logo_cells[sci];
let logo_tile = get_layered_symbol_map()
.map(|m| *m.resolve(&cellsym_block(tex, sym)))
.unwrap_or_default();
let mut s2 = render_helper_with_scale(
PIXEL_LOGO_WIDTH as u16,
PointF32 { x: rx, y: ry },
sci,
PointU16 {
x: base_x,
y: base_y,
},
scale, scale, scale, None, 1, );
let fc = Color::Indexed(fg).get_rgba();
let sg = LOGO_FRAME as u8 / 3;
let symw_px = PIXEL_SYM_WIDTH.get().expect("lazylock init") / rx * scale;
const JITTER_HOLD_FRAMES: u32 = 40;
let held_stage = stage / JITTER_HOLD_FRAMES;
let row_group = (y / 5) as u32;
let row_seed = row_group.wrapping_mul(7919).wrapping_add(held_stage.wrapping_mul(31)) % 65536;
let row_rand = (row_seed.wrapping_mul(1103515245).wrapping_add(12345) >> 16) & 0x7fff;
let r: u8;
let g: u8;
let b: u8;
let a: u8;
let rand_x: i32;
let mut rand_y: i32 = 0;
if stage <= sg as u32 {
let stage_progress = stage as f32 / sg as f32;
let chaos = 1.0 - stage_progress;
let max_row_offset = (symw_px * 12.0 * chaos) as i32;
let row_offset = if max_row_offset > 0 {
(row_rand as i32 % (max_row_offset * 2 + 1)) - max_row_offset
} else {
0
};
rand_x = row_offset;
r = fc.0;
g = fc.1;
b = fc.2;
a = (stage_progress * 255.0) as u8;
} else if stage <= sg as u32 * 2 {
let stage_progress = (stage as f32 - sg as f32) / sg as f32;
let residual = 1.0 - stage_progress;
let residual_offset = (symw_px * 10.0 * residual) as i32;
let row_offset = if residual_offset > 0 {
(row_rand as i32 % (residual_offset * 2 + 1)) - residual_offset
} else {
0
};
rand_x = row_offset;
r = fc.0;
g = fc.1;
b = fc.2;
a = 255;
} else {
let p = ((stage - sg as u32 * 2) as f32 / sg as f32).clamp(0.0, 1.0);
let ep = p * p * p;
let h = {
let mut v = (sci as u32).wrapping_mul(0x9e3779b9);
v ^= v >> 16;
v = v.wrapping_mul(0x7feb352d);
v ^= v >> 15;
v
};
let angle = (h & 0xffff) as f32 / 65536.0 * std::f32::consts::TAU;
let dist = ((h >> 16) & 0xffff) as f32 / 65536.0 * 0.5 + 0.5;
let cx_logo = logo_pixel_w / 2.0;
let cy_logo = logo_pixel_h / 2.0;
let cell_lx = x as f32 * symw_px;
let cell_ly = y as f32 * symh;
let dir_x = cell_lx - cx_logo;
let dir_y = cell_ly - cy_logo;
let fly_range = logo_pixel_w * 1.2;
let out_x = dir_x * ep * 3.0 + angle.cos() * fly_range * dist * ep;
let out_y = dir_y * ep * 3.0 + angle.sin() * fly_range * dist * ep;
rand_x = out_x as i32;
rand_y = out_y as i32;
let nx = (x as f32 / PIXEL_LOGO_WIDTH as f32 - 0.5) * 2.0;
let ny = (y as f32 / PIXEL_LOGO_HEIGHT as f32 - 0.5) * 2.0;
let dist_c = (nx * nx + ny * ny).sqrt().min(1.0); let fade_p = (ep + dist_c * p * 2.0).clamp(0.0, 1.0);
let visible = (h % 3) == 0;
let hr = (h.wrapping_mul(0x45d9f3b) >> 8) as u8;
let hg = (h.wrapping_mul(0x119de1f3) >> 8) as u8;
let hb = (h.wrapping_mul(0x16a6b885) >> 8) as u8;
let brightness = if visible { (1.0 - fade_p).max(0.0) } else { 0.0 };
r = (hr as f32 * brightness) as u8;
g = (hg as f32 * brightness) as u8;
b = (hb as f32 * brightness) as u8;
a = if visible { ((1.0 - fade_p) * 255.0) as u8 } else { 0 };
}
s2.x += rand_x;
s2.y += rand_y;
f(&(r, g, b, a), s2, logo_tile);
}
}
}
pub fn generate_render_buffer(
cb: &Buffer,
_pb: &Buffer,
ps: &mut Vec<Layer>,
stage: u32,
base: &mut AdapterBase,
) -> Vec<RenderCell> {
let mut rbuf = vec![];
let width = cb.area.width;
let pz = PointI32 { x: 0, y: 0 };
if stage <= LOGO_FRAME {
render_logo(
base.gr.ratio_x,
base.gr.ratio_y,
base.gr.pixel_w,
base.gr.pixel_h,
&mut base.rd,
stage,
|fc, s2, tile| {
push_render_buffer(&mut rbuf, fc, &None, tile, s2, 0.0, &pz, 0);
},
);
return rbuf;
}
let rx = base.gr.ratio_x;
let ry = base.gr.ratio_y;
if stage > LOGO_FRAME {
for item in ps {
if !item.is_hidden {
render_layers(item, rx, ry, |fc, bc, s2, tile, angle, ccp, modifier| {
push_render_buffer(&mut rbuf, fc, bc, tile, s2, angle, &ccp, modifier);
});
}
}
let mut rfunc = |fc: &(u8, u8, u8, u8),
bc: &Option<(u8, u8, u8, u8)>,
s2: ARect,
tile: Tile,
modifier: u16| {
push_render_buffer(&mut rbuf, fc, bc, tile, s2, 0.0, &pz, modifier);
};
render_main_buffer(cb, width, rx, ry, true, &mut rfunc);
}
rbuf
}