Move icon smoothing system to its own file

This commit is contained in:
Tad Hardesty 2019-11-16 22:12:47 -08:00
parent 84859e0689
commit c8950c0ebc
3 changed files with 336 additions and 334 deletions

View file

@ -1,14 +1,14 @@
use std::sync::RwLock;
use std::collections::HashSet;
use ndarray::{self, Axis};
use ndarray::Axis;
use dm::objtree::*;
use dm::objtree::subpath as subtype;
use dm::constants::Constant;
use dmm::{Map, Grid, Prefab};
use dmi::Image;
use render_passes::RenderPass;
use render_passes::{RenderPass, icon_smoothing};
use icon_cache::IconCache;
const TILE_SIZE: u32 = 32;
@ -190,7 +190,7 @@ pub fn generate(ctx: Context, icon_cache: &IconCache) -> Result<Image, ()> {
// smoothing time
let loc = atom.loc;
sprites.extend(underlays.drain(..).map(|o| (loc, o)));
handle_smooth(&mut sprites, ctx, atom, !0);
icon_smoothing::handle_smooth(&mut sprites, ctx, atom, !0);
sprites.extend(overlays.drain(..).map(|o| (loc, o)));
}
}
@ -318,7 +318,7 @@ pub fn get_atom_list<'a>(
#[derive(Debug, Clone)]
pub struct Atom<'a> {
type_: &'a Type,
pub(crate) type_: &'a Type,
prefab: Option<&'a Vars>,
pub sprite: Sprite<'a>,
pub loc: (u32, u32),
@ -647,333 +647,3 @@ pub fn color_of<'s, T: GetVar<'s> + ?Sized>(objtree: &'s ObjectTree, atom: &T) -
_ => [255, 255, 255, alpha],
}
}
// ----------------------------------------------------------------------------
// Icon smoothing subsystem
// (1 << N) where N is the usual value
const N_NORTH: i32 = 2;
const N_SOUTH: i32 = 4;
const N_EAST: i32 = 16;
const N_WEST: i32 = 256;
const N_NORTHEAST: i32 = 32;
const N_NORTHWEST: i32 = 512;
const N_SOUTHEAST: i32 = 64;
const N_SOUTHWEST: i32 = 1024;
const SMOOTH_TRUE: i32 = 1; // smooth with exact specified types or just itself
const SMOOTH_MORE: i32 = 2; // smooth with all subtypes thereof
const SMOOTH_DIAGONAL: i32 = 4; // smooth diagonally
const SMOOTH_BORDER: i32 = 8; // smooth with the borders of the map
fn handle_smooth<'a>(output: &mut Vec<((u32, u32), Sprite<'a>)>, ctx: Context<'a>, atom: Atom<'a>, mask: i32) {
let smooth_flags = mask & atom.get_var("smooth", ctx.objtree).to_int().unwrap_or(0);
if smooth_flags & (SMOOTH_TRUE | SMOOTH_MORE) != 0 {
let adjacencies = calculate_adjacencies(ctx, &atom, smooth_flags);
if smooth_flags & SMOOTH_DIAGONAL != 0 {
diagonal_smooth(output, ctx, &atom, adjacencies);
} else {
cardinal_smooth(output, ctx, &atom, adjacencies);
}
} else {
output.push((atom.loc, atom.sprite));
}
}
fn calculate_adjacencies(ctx: Context, atom: &Atom, flags: i32) -> i32 {
use dmi::*;
// TODO: anchored check
let mut adjacencies = 0;
let check_one = |direction, flag| {
if find_type_in_direction(ctx, atom, direction, flags) {
flag
} else {
0
}
};
for &dir in &[SOUTH, NORTH, EAST, WEST] {
adjacencies |= check_one(dir, 1 << dir);
}
if adjacencies & N_NORTH != 0 {
if adjacencies & N_WEST != 0 {
adjacencies |= check_one(NORTHWEST, N_NORTHWEST);
}
if adjacencies & N_EAST != 0 {
adjacencies |= check_one(NORTHEAST, N_NORTHEAST);
}
}
if adjacencies & N_SOUTH != 0 {
if adjacencies & N_WEST != 0 {
adjacencies |= check_one(SOUTHWEST, N_SOUTHWEST);
}
if adjacencies & N_EAST != 0 {
adjacencies |= check_one(SOUTHEAST, N_SOUTHEAST);
}
}
adjacencies
}
fn find_type_in_direction<'a>(ctx: Context, source: &Atom, direction: i32, flags: i32) -> bool {
use std::ptr::eq;
let (dx, dy) = offset(direction);
let new_loc = (source.loc.0 as i32 + dx, source.loc.1 as i32 + dy);
let (dim_y, dim_x) = ctx.grid.dim();
if new_loc.0 < 0 || new_loc.1 < 0 || new_loc.0 >= dim_x as i32 || new_loc.1 >= dim_y as i32 {
return flags & SMOOTH_BORDER != 0;
}
let new_loc = (new_loc.0 as u32, new_loc.1 as u32);
// TODO: make this not call get_atom_list way too many times
let atom_list = get_atom_list(
ctx.objtree,
&ctx.map.dictionary[&ctx.grid[ndarray::Dim([new_loc.1 as usize, new_loc.0 as usize])]],
new_loc,
ctx.render_passes,
None,
);
match source.get_var("canSmoothWith", ctx.objtree) {
&Constant::List(ref elements) => if flags & SMOOTH_MORE != 0 {
// smooth with canSmoothWith + subtypes
for atom in atom_list {
let mut path = &atom.type_.path[..];
while !path.is_empty() {
if smoothlist_contains(elements, path) {
return true;
}
path = &path[..path.rfind("/").unwrap()];
}
}
} else {
// smooth only with exact types in canSmoothWith
for atom in atom_list {
if smoothlist_contains(elements, &atom.type_.path) {
return true;
}
}
},
_ => {
// smooth only with the same type
for atom in atom_list {
if eq(atom.type_, source.type_) {
return true;
}
}
},
}
false
}
fn smoothlist_contains(list: &[(Constant, Option<Constant>)], desired: &str) -> bool {
for &(ref key, _) in list {
// TODO: be more specific than to_string
if key.to_string() == desired {
return true;
}
}
false
}
fn cardinal_smooth<'a>(output: &mut Vec<((u32, u32), Sprite<'a>)>, ctx: Context<'a>, source: &Atom<'a>, adjacencies: i32) {
for &(what, f1, n1, f2, n2, f3) in &[
("1", N_NORTH, "n", N_WEST, "w", N_NORTHWEST),
("2", N_NORTH, "n", N_EAST, "e", N_NORTHEAST),
("3", N_SOUTH, "s", N_WEST, "w", N_SOUTHWEST),
("4", N_SOUTH, "s", N_EAST, "e", N_SOUTHEAST),
] {
let name = if (adjacencies & f1 != 0) && (adjacencies & f2 != 0) {
if (adjacencies & f3) != 0 {
format!("{}-f", what)
} else {
format!("{}-{}{}", what, n1, n2)
}
} else if adjacencies & f1 != 0 {
format!("{}-{}", what, n1)
} else if adjacencies & f2 != 0 {
format!("{}-{}", what, n2)
} else {
format!("{}-i", what)
};
let mut sprite = Sprite {
icon_state: ctx.bump.alloc(name),
.. source.sprite
};
if let Some(icon) = source.get_var("smooth_icon", ctx.objtree).as_path_str() {
sprite.icon = icon;
}
output.push((source.loc, sprite));
}
}
fn diagonal_smooth<'a>(output: &mut Vec<((u32, u32), Sprite<'a>)>, ctx: Context<'a>, source: &Atom<'a>, adjacencies: i32) {
let presets = if adjacencies == N_NORTH | N_WEST {
["d-se", "d-se-0"]
} else if adjacencies == N_NORTH | N_EAST {
["d-sw", "d-sw-0"]
} else if adjacencies == N_SOUTH | N_WEST {
["d-ne", "d-ne-0"]
} else if adjacencies == N_SOUTH | N_EAST {
["d-nw", "d-nw-0"]
} else if adjacencies == N_NORTH | N_WEST | N_NORTHWEST {
["d-se", "d-se-1"]
} else if adjacencies == N_NORTH | N_EAST | N_NORTHEAST {
["d-sw", "d-sw-1"]
} else if adjacencies == N_SOUTH | N_WEST | N_SOUTHWEST {
["d-ne", "d-ne-1"]
} else if adjacencies == N_SOUTH | N_EAST | N_SOUTHEAST {
["d-nw", "d-nw-1"]
} else {
return cardinal_smooth(output, ctx, source, adjacencies);
};
// turf underneath
if subtype(&source.type_.path, "/turf/closed/wall/") {
// BYOND memes
if source
.get_var("fixed_underlay", ctx.objtree)
.index(&Constant::string("space"))
.is_some()
{
output.push((
source.loc,
Sprite::from_vars(ctx.objtree, &ctx.objtree.expect("/turf/open/space/basic"))
));
} else {
let dir = flip(reverse_ndir(adjacencies));
let mut needs_plating = true;
// check direct, then 45deg left, then 45deg right
'dirs: for &each in &[dir, left_45(dir), right_45(dir)] {
let (dx, dy) = offset(each);
let new_loc = (source.loc.0 as i32 + dx, source.loc.1 as i32 + dy);
let (dim_y, dim_x) = ctx.grid.dim();
if !(new_loc.0 < 0 || new_loc.1 < 0 || new_loc.0 >= dim_x as i32 || new_loc.1 >= dim_y as i32) {
let new_loc = (new_loc.0 as u32, new_loc.1 as u32);
// TODO: make this not call get_atom_list way too many times
let atom_list = get_atom_list(
ctx.objtree,
&ctx.map.dictionary[&ctx.grid[ndarray::Dim([new_loc.1 as usize, new_loc.0 as usize])]],
new_loc,
ctx.render_passes,
None,
);
for atom in atom_list {
if subtype(&atom.type_.path, "/turf/open/") {
output.push((source.loc, Sprite::from_vars(ctx.objtree, &atom)));
needs_plating = false;
break 'dirs;
}
}
}
}
if needs_plating {
output.push((
source.loc,
Sprite::from_vars(ctx.objtree, &ctx.objtree.expect("/turf/open/floor/plating"))
));
}
}
}
// the diagonal overlay
for &each in presets.iter() {
let mut copy = Sprite {
icon_state: each,
.. source.sprite
};
if let Some(icon) = source.get_var("smooth_icon", ctx.objtree).as_path_str() {
copy.icon = icon;
}
output.push((source.loc, copy));
}
}
fn offset(direction: i32) -> (i32, i32) {
use dmi::*;
match direction {
0 => (0, 0),
SOUTH => (0, 1),
NORTH => (0, -1),
EAST => (1, 0),
WEST => (-1, 0),
SOUTHEAST => (1, 1),
SOUTHWEST => (-1, 1),
NORTHEAST => (1, -1),
NORTHWEST => (-1, -1),
_ => panic!(),
}
}
fn flip(direction: i32) -> i32 {
use dmi::*;
match direction {
0 => 0,
SOUTH => NORTH,
NORTH => SOUTH,
EAST => WEST,
WEST => EAST,
SOUTHEAST => NORTHWEST,
SOUTHWEST => NORTHEAST,
NORTHEAST => SOUTHWEST,
NORTHWEST => SOUTHEAST,
_ => panic!(),
}
}
fn reverse_ndir(ndir: i32) -> i32 {
use dmi::*;
const NW1: i32 = N_NORTH | N_WEST;
const NW2: i32 = NW1 | N_NORTHWEST;
const NE1: i32 = N_NORTH | N_EAST;
const NE2: i32 = NE1 | N_NORTHEAST;
const SW1: i32 = N_SOUTH | N_WEST;
const SW2: i32 = SW1 | N_SOUTHWEST;
const SE1: i32 = N_SOUTH | N_EAST;
const SE2: i32 = SE1 | N_SOUTHEAST;
match ndir {
N_NORTH => NORTH,
N_SOUTH => SOUTH,
N_WEST => WEST,
N_EAST => EAST,
N_SOUTHEAST | SE1 | SE2 => SOUTHEAST,
N_SOUTHWEST | SW1 | SW2 => SOUTHWEST,
N_NORTHEAST | NE1 | NE2 => NORTHEAST,
N_NORTHWEST | NW1 | NW2 => NORTHWEST,
_ => panic!(),
}
}
fn left_45(dir: i32) -> i32 {
use dmi::*;
match dir {
NORTH => NORTHWEST,
NORTHEAST => NORTH,
EAST => NORTHEAST,
SOUTHEAST => EAST,
SOUTH => SOUTHEAST,
SOUTHWEST => SOUTH,
WEST => SOUTHWEST,
NORTHWEST => WEST,
e => e,
}
}
fn right_45(dir: i32) -> i32 {
use dmi::*;
match dir {
NORTH => NORTHEAST,
NORTHEAST => EAST,
EAST => SOUTHEAST,
SOUTHEAST => SOUTH,
SOUTH => SOUTHWEST,
SOUTHWEST => WEST,
WEST => NORTHWEST,
NORTHWEST => NORTH,
e => e,
}
}

View file

@ -0,0 +1,331 @@
//! Port of icon smoothing subsystem.
use dm::constants::Constant;
use minimap::{Sprite, Context, Atom, GetVar, get_atom_list};
// (1 << N) where N is the usual value
const N_NORTH: i32 = 2;
const N_SOUTH: i32 = 4;
const N_EAST: i32 = 16;
const N_WEST: i32 = 256;
const N_NORTHEAST: i32 = 32;
const N_NORTHWEST: i32 = 512;
const N_SOUTHEAST: i32 = 64;
const N_SOUTHWEST: i32 = 1024;
const SMOOTH_TRUE: i32 = 1; // smooth with exact specified types or just itself
const SMOOTH_MORE: i32 = 2; // smooth with all subtypes thereof
const SMOOTH_DIAGONAL: i32 = 4; // smooth diagonally
const SMOOTH_BORDER: i32 = 8; // smooth with the borders of the map
pub fn handle_smooth<'a>(output: &mut Vec<((u32, u32), Sprite<'a>)>, ctx: Context<'a>, atom: Atom<'a>, mask: i32) {
let smooth_flags = mask & atom.get_var("smooth", ctx.objtree).to_int().unwrap_or(0);
if smooth_flags & (SMOOTH_TRUE | SMOOTH_MORE) != 0 {
let adjacencies = calculate_adjacencies(ctx, &atom, smooth_flags);
if smooth_flags & SMOOTH_DIAGONAL != 0 {
diagonal_smooth(output, ctx, &atom, adjacencies);
} else {
cardinal_smooth(output, ctx, &atom, adjacencies);
}
} else {
output.push((atom.loc, atom.sprite));
}
}
fn calculate_adjacencies(ctx: Context, atom: &Atom, flags: i32) -> i32 {
use dmi::*;
// TODO: anchored check
let mut adjacencies = 0;
let check_one = |direction, flag| {
if find_type_in_direction(ctx, atom, direction, flags) {
flag
} else {
0
}
};
for &dir in &[SOUTH, NORTH, EAST, WEST] {
adjacencies |= check_one(dir, 1 << dir);
}
if adjacencies & N_NORTH != 0 {
if adjacencies & N_WEST != 0 {
adjacencies |= check_one(NORTHWEST, N_NORTHWEST);
}
if adjacencies & N_EAST != 0 {
adjacencies |= check_one(NORTHEAST, N_NORTHEAST);
}
}
if adjacencies & N_SOUTH != 0 {
if adjacencies & N_WEST != 0 {
adjacencies |= check_one(SOUTHWEST, N_SOUTHWEST);
}
if adjacencies & N_EAST != 0 {
adjacencies |= check_one(SOUTHEAST, N_SOUTHEAST);
}
}
adjacencies
}
fn find_type_in_direction<'a>(ctx: Context, source: &Atom, direction: i32, flags: i32) -> bool {
use std::ptr::eq;
let (dx, dy) = offset(direction);
let new_loc = (source.loc.0 as i32 + dx, source.loc.1 as i32 + dy);
let (dim_y, dim_x) = ctx.grid.dim();
if new_loc.0 < 0 || new_loc.1 < 0 || new_loc.0 >= dim_x as i32 || new_loc.1 >= dim_y as i32 {
return flags & SMOOTH_BORDER != 0;
}
let new_loc = (new_loc.0 as u32, new_loc.1 as u32);
// TODO: make this not call get_atom_list way too many times
let atom_list = get_atom_list(
ctx.objtree,
&ctx.map.dictionary[&ctx.grid[ndarray::Dim([new_loc.1 as usize, new_loc.0 as usize])]],
new_loc,
ctx.render_passes,
None,
);
match source.get_var("canSmoothWith", ctx.objtree) {
&Constant::List(ref elements) => if flags & SMOOTH_MORE != 0 {
// smooth with canSmoothWith + subtypes
for atom in atom_list {
let mut path = &atom.type_.path[..];
while !path.is_empty() {
if smoothlist_contains(elements, path) {
return true;
}
path = &path[..path.rfind("/").unwrap()];
}
}
} else {
// smooth only with exact types in canSmoothWith
for atom in atom_list {
if smoothlist_contains(elements, &atom.type_.path) {
return true;
}
}
},
_ => {
// smooth only with the same type
for atom in atom_list {
if eq(atom.type_, source.type_) {
return true;
}
}
},
}
false
}
fn smoothlist_contains(list: &[(Constant, Option<Constant>)], desired: &str) -> bool {
for &(ref key, _) in list {
// TODO: be more specific than to_string
if key.to_string() == desired {
return true;
}
}
false
}
fn cardinal_smooth<'a>(output: &mut Vec<((u32, u32), Sprite<'a>)>, ctx: Context<'a>, source: &Atom<'a>, adjacencies: i32) {
for &(what, f1, n1, f2, n2, f3) in &[
("1", N_NORTH, "n", N_WEST, "w", N_NORTHWEST),
("2", N_NORTH, "n", N_EAST, "e", N_NORTHEAST),
("3", N_SOUTH, "s", N_WEST, "w", N_SOUTHWEST),
("4", N_SOUTH, "s", N_EAST, "e", N_SOUTHEAST),
] {
let name = if (adjacencies & f1 != 0) && (adjacencies & f2 != 0) {
if (adjacencies & f3) != 0 {
format!("{}-f", what)
} else {
format!("{}-{}{}", what, n1, n2)
}
} else if adjacencies & f1 != 0 {
format!("{}-{}", what, n1)
} else if adjacencies & f2 != 0 {
format!("{}-{}", what, n2)
} else {
format!("{}-i", what)
};
let mut sprite = Sprite {
icon_state: ctx.bump.alloc(name),
.. source.sprite
};
if let Some(icon) = source.get_var("smooth_icon", ctx.objtree).as_path_str() {
sprite.icon = icon;
}
output.push((source.loc, sprite));
}
}
fn diagonal_smooth<'a>(output: &mut Vec<((u32, u32), Sprite<'a>)>, ctx: Context<'a>, source: &Atom<'a>, adjacencies: i32) {
let presets = if adjacencies == N_NORTH | N_WEST {
["d-se", "d-se-0"]
} else if adjacencies == N_NORTH | N_EAST {
["d-sw", "d-sw-0"]
} else if adjacencies == N_SOUTH | N_WEST {
["d-ne", "d-ne-0"]
} else if adjacencies == N_SOUTH | N_EAST {
["d-nw", "d-nw-0"]
} else if adjacencies == N_NORTH | N_WEST | N_NORTHWEST {
["d-se", "d-se-1"]
} else if adjacencies == N_NORTH | N_EAST | N_NORTHEAST {
["d-sw", "d-sw-1"]
} else if adjacencies == N_SOUTH | N_WEST | N_SOUTHWEST {
["d-ne", "d-ne-1"]
} else if adjacencies == N_SOUTH | N_EAST | N_SOUTHEAST {
["d-nw", "d-nw-1"]
} else {
return cardinal_smooth(output, ctx, source, adjacencies);
};
// turf underneath
if dm::objtree::subpath(&source.type_.path, "/turf/closed/wall/") {
// BYOND memes
if source
.get_var("fixed_underlay", ctx.objtree)
.index(&Constant::string("space"))
.is_some()
{
output.push((
source.loc,
Sprite::from_vars(ctx.objtree, &ctx.objtree.expect("/turf/open/space/basic"))
));
} else {
let dir = flip(reverse_ndir(adjacencies));
let mut needs_plating = true;
// check direct, then 45deg left, then 45deg right
'dirs: for &each in &[dir, left_45(dir), right_45(dir)] {
let (dx, dy) = offset(each);
let new_loc = (source.loc.0 as i32 + dx, source.loc.1 as i32 + dy);
let (dim_y, dim_x) = ctx.grid.dim();
if !(new_loc.0 < 0 || new_loc.1 < 0 || new_loc.0 >= dim_x as i32 || new_loc.1 >= dim_y as i32) {
let new_loc = (new_loc.0 as u32, new_loc.1 as u32);
// TODO: make this not call get_atom_list way too many times
let atom_list = get_atom_list(
ctx.objtree,
&ctx.map.dictionary[&ctx.grid[ndarray::Dim([new_loc.1 as usize, new_loc.0 as usize])]],
new_loc,
ctx.render_passes,
None,
);
for atom in atom_list {
if dm::objtree::subpath(&atom.type_.path, "/turf/open/") {
output.push((source.loc, Sprite::from_vars(ctx.objtree, &atom)));
needs_plating = false;
break 'dirs;
}
}
}
}
if needs_plating {
output.push((
source.loc,
Sprite::from_vars(ctx.objtree, &ctx.objtree.expect("/turf/open/floor/plating"))
));
}
}
}
// the diagonal overlay
for &each in presets.iter() {
let mut copy = Sprite {
icon_state: each,
.. source.sprite
};
if let Some(icon) = source.get_var("smooth_icon", ctx.objtree).as_path_str() {
copy.icon = icon;
}
output.push((source.loc, copy));
}
}
fn offset(direction: i32) -> (i32, i32) {
use dmi::*;
match direction {
0 => (0, 0),
SOUTH => (0, 1),
NORTH => (0, -1),
EAST => (1, 0),
WEST => (-1, 0),
SOUTHEAST => (1, 1),
SOUTHWEST => (-1, 1),
NORTHEAST => (1, -1),
NORTHWEST => (-1, -1),
_ => panic!(),
}
}
fn flip(direction: i32) -> i32 {
use dmi::*;
match direction {
0 => 0,
SOUTH => NORTH,
NORTH => SOUTH,
EAST => WEST,
WEST => EAST,
SOUTHEAST => NORTHWEST,
SOUTHWEST => NORTHEAST,
NORTHEAST => SOUTHWEST,
NORTHWEST => SOUTHEAST,
_ => panic!(),
}
}
fn reverse_ndir(ndir: i32) -> i32 {
use dmi::*;
const NW1: i32 = N_NORTH | N_WEST;
const NW2: i32 = NW1 | N_NORTHWEST;
const NE1: i32 = N_NORTH | N_EAST;
const NE2: i32 = NE1 | N_NORTHEAST;
const SW1: i32 = N_SOUTH | N_WEST;
const SW2: i32 = SW1 | N_SOUTHWEST;
const SE1: i32 = N_SOUTH | N_EAST;
const SE2: i32 = SE1 | N_SOUTHEAST;
match ndir {
N_NORTH => NORTH,
N_SOUTH => SOUTH,
N_WEST => WEST,
N_EAST => EAST,
N_SOUTHEAST | SE1 | SE2 => SOUTHEAST,
N_SOUTHWEST | SW1 | SW2 => SOUTHWEST,
N_NORTHEAST | NE1 | NE2 => NORTHEAST,
N_NORTHWEST | NW1 | NW2 => NORTHWEST,
_ => panic!(),
}
}
fn left_45(dir: i32) -> i32 {
use dmi::*;
match dir {
NORTH => NORTHWEST,
NORTHEAST => NORTH,
EAST => NORTHEAST,
SOUTHEAST => EAST,
SOUTH => SOUTHEAST,
SOUTHWEST => SOUTH,
WEST => SOUTHWEST,
NORTHWEST => WEST,
e => e,
}
}
fn right_45(dir: i32) -> i32 {
use dmi::*;
match dir {
NORTH => NORTHEAST,
NORTHEAST => EAST,
EAST => SOUTHEAST,
SOUTHEAST => SOUTH,
SOUTH => SOUTHWEST,
SOUTHWEST => WEST,
WEST => NORTHWEST,
NORTHWEST => NORTH,
e => e,
}
}

View file

@ -5,6 +5,7 @@ use minimap::{Atom, GetVar, Sprite, Layer};
pub mod transit_tube;
pub mod random;
pub mod structures;
pub mod icon_smoothing;
/// A map rendering pass.
///