--- a/rust/hedgewars-server/src/core.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hedgewars-server/src/core.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,5 +1,5 @@
-pub mod client;
-pub mod indexslab;
-pub mod room;
-pub mod server;
-pub mod types;
+pub mod client;
+pub mod indexslab;
+pub mod room;
+pub mod server;
+pub mod types;
--- a/rust/hedgewars-server/src/core/indexslab.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hedgewars-server/src/core/indexslab.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,71 +1,71 @@
-use std::{
- iter,
- mem::replace,
- ops::{Index, IndexMut},
-};
-
-pub struct IndexSlab<T> {
- data: Vec<Option<T>>,
-}
-
-impl<T> IndexSlab<T> {
- pub fn new() -> Self {
- Self { data: Vec::new() }
- }
-
- pub fn with_capacity(capacity: usize) -> Self {
- Self {
- data: Vec::with_capacity(capacity),
- }
- }
-
- pub fn insert(&mut self, index: usize, value: T) {
- if index >= self.data.len() {
- self.data.reserve(index - self.data.len() + 1);
- self.data.extend((self.data.len()..index).map(|_| None));
- self.data.push(Some(value))
- } else {
- self.data[index] = Some(value);
- }
- }
-
- pub fn contains(&self, index: usize) -> bool {
- self.data.get(index).and_then(|x| x.as_ref()).is_some()
- }
-
- pub fn remove(&mut self, index: usize) -> Option<T> {
- if let Some(x) = self.data.get_mut(index) {
- replace(x, None)
- } else {
- None
- }
- }
-
- pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
- self.data
- .iter()
- .enumerate()
- .filter_map(|(index, opt)| opt.as_ref().and_then(|x| Some((index, x))))
- }
-
- pub fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut T)> {
- self.data
- .iter_mut()
- .enumerate()
- .filter_map(|(index, opt)| opt.as_mut().and_then(|x| Some((index, x))))
- }
-}
-
-impl<T> Index<usize> for IndexSlab<T> {
- type Output = T;
-
- fn index(&self, index: usize) -> &Self::Output {
- self.data[index].as_ref().unwrap()
- }
-}
-
-impl<T> IndexMut<usize> for IndexSlab<T> {
- fn index_mut(&mut self, index: usize) -> &mut Self::Output {
- self.data[index].as_mut().unwrap()
- }
-}
+use std::{
+ iter,
+ mem::replace,
+ ops::{Index, IndexMut},
+};
+
+pub struct IndexSlab<T> {
+ data: Vec<Option<T>>,
+}
+
+impl<T> IndexSlab<T> {
+ pub fn new() -> Self {
+ Self { data: Vec::new() }
+ }
+
+ pub fn with_capacity(capacity: usize) -> Self {
+ Self {
+ data: Vec::with_capacity(capacity),
+ }
+ }
+
+ pub fn insert(&mut self, index: usize, value: T) {
+ if index >= self.data.len() {
+ self.data.reserve(index - self.data.len() + 1);
+ self.data.extend((self.data.len()..index).map(|_| None));
+ self.data.push(Some(value))
+ } else {
+ self.data[index] = Some(value);
+ }
+ }
+
+ pub fn contains(&self, index: usize) -> bool {
+ self.data.get(index).and_then(|x| x.as_ref()).is_some()
+ }
+
+ pub fn remove(&mut self, index: usize) -> Option<T> {
+ if let Some(x) = self.data.get_mut(index) {
+ replace(x, None)
+ } else {
+ None
+ }
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
+ self.data
+ .iter()
+ .enumerate()
+ .filter_map(|(index, opt)| opt.as_ref().and_then(|x| Some((index, x))))
+ }
+
+ pub fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut T)> {
+ self.data
+ .iter_mut()
+ .enumerate()
+ .filter_map(|(index, opt)| opt.as_mut().and_then(|x| Some((index, x))))
+ }
+}
+
+impl<T> Index<usize> for IndexSlab<T> {
+ type Output = T;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ self.data[index].as_ref().unwrap()
+ }
+}
+
+impl<T> IndexMut<usize> for IndexSlab<T> {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ self.data[index].as_mut().unwrap()
+ }
+}
--- a/rust/hedgewars-server/src/handlers/checker.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hedgewars-server/src/handlers/checker.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,13 +1,13 @@
-use log::*;
-use mio;
-
-use crate::{
- core::{server::HwServer, types::ClientId},
- protocol::messages::HwProtocolMessage,
-};
-
-pub fn handle(_server: &mut HwServer, _client_id: ClientId, message: HwProtocolMessage) {
- match message {
- _ => warn!("Unknown command"),
- }
-}
+use log::*;
+use mio;
+
+use crate::{
+ core::{server::HwServer, types::ClientId},
+ protocol::messages::HwProtocolMessage,
+};
+
+pub fn handle(_server: &mut HwServer, _client_id: ClientId, message: HwProtocolMessage) {
+ match message {
+ _ => warn!("Unknown command"),
+ }
+}
--- a/rust/hedgewars-server/src/server/io.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hedgewars-server/src/server/io.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,162 +1,162 @@
-use std::{
- fs::{File, OpenOptions},
- io::{Error, ErrorKind, Read, Result, Write},
- sync::mpsc,
- thread,
-};
-
-use crate::{
- handlers::{IoResult, IoTask},
- server::database::Database,
-};
-use log::*;
-use mio::{Evented, Poll, PollOpt};
-use mio_extras::channel;
-
-pub type RequestId = u32;
-
-pub struct IoThread {
- core_tx: mpsc::Sender<(RequestId, IoTask)>,
- core_rx: channel::Receiver<(RequestId, IoResult)>,
-}
-
-impl IoThread {
- pub fn new() -> Self {
- let (core_tx, io_rx) = mpsc::channel();
- let (io_tx, core_rx) = channel::channel();
-
- let mut db = Database::new();
- db.connect("localhost");
-
- thread::spawn(move || {
- while let Ok((request_id, task)) = io_rx.recv() {
- let response = match task {
- IoTask::CheckRegistered { nick } => match db.is_registered(&nick) {
- Ok(is_registered) => IoResult::AccountRegistered(is_registered),
- Err(e) => {
- warn!("Unable to check account's existence: {}", e);
- IoResult::AccountRegistered(false)
- }
- },
-
- IoTask::GetAccount {
- nick,
- protocol,
- password_hash,
- client_salt,
- server_salt,
- } => {
- match db.get_account(
- &nick,
- protocol,
- &password_hash,
- &client_salt,
- &server_salt,
- ) {
- Ok(account) => IoResult::Account(account),
- Err(e) => {
- warn!("Unable to get account data: {}", e);
- IoResult::Account(None)
- }
- }
- }
-
- IoTask::GetReplay { id } => {
- let result = match db.get_replay_name(id) {
- Ok(Some(filename)) => {
- let filename = format!(
- "checked/{}",
- if filename.starts_with("replays/") {
- &filename[8..]
- } else {
- &filename
- }
- );
- match load_file(&filename) {
- Ok(contents) => Some(unimplemented!()),
- Err(e) => {
- warn!(
- "Error while writing the room config file \"{}\": {}",
- filename, e
- );
- None
- }
- }
- }
- Ok(None) => None,
- Err(e) => {
- warn!("Unable to get replay name: {}", e);
- None
- }
- };
- IoResult::Replay(result)
- }
-
- IoTask::SaveRoom {
- room_id,
- filename,
- contents,
- } => {
- let result = match save_file(&filename, &contents) {
- Ok(()) => true,
- Err(e) => {
- warn!(
- "Error while writing the room config file \"{}\": {}",
- filename, e
- );
- false
- }
- };
- IoResult::SaveRoom(room_id, result)
- }
-
- IoTask::LoadRoom { room_id, filename } => {
- let result = match load_file(&filename) {
- Ok(contents) => Some(contents),
- Err(e) => {
- warn!(
- "Error while writing the room config file \"{}\": {}",
- filename, e
- );
- None
- }
- };
- IoResult::LoadRoom(room_id, result)
- }
- };
- io_tx.send((request_id, response));
- }
- });
-
- Self { core_rx, core_tx }
- }
-
- pub fn send(&self, request_id: RequestId, task: IoTask) {
- self.core_tx.send((request_id, task)).unwrap();
- }
-
- pub fn try_recv(&self) -> Option<(RequestId, IoResult)> {
- match self.core_rx.try_recv() {
- Ok(result) => Some(result),
- Err(mpsc::TryRecvError::Empty) => None,
- Err(mpsc::TryRecvError::Disconnected) => unreachable!(),
- }
- }
-
- pub fn register_rx(&self, poll: &mio::Poll, token: mio::Token) -> Result<()> {
- self.core_rx
- .register(poll, token, mio::Ready::readable(), PollOpt::edge())
- }
-}
-
-fn save_file(filename: &str, contents: &str) -> Result<()> {
- let mut writer = OpenOptions::new().create(true).write(true).open(filename)?;
- writer.write_all(contents.as_bytes())
-}
-
-fn load_file(filename: &str) -> Result<String> {
- let mut reader = File::open(filename)?;
- let mut result = String::new();
- reader.read_to_string(&mut result)?;
- Ok(result)
-}
+use std::{
+ fs::{File, OpenOptions},
+ io::{Error, ErrorKind, Read, Result, Write},
+ sync::mpsc,
+ thread,
+};
+
+use crate::{
+ handlers::{IoResult, IoTask},
+ server::database::Database,
+};
+use log::*;
+use mio::{Evented, Poll, PollOpt};
+use mio_extras::channel;
+
+pub type RequestId = u32;
+
+pub struct IoThread {
+ core_tx: mpsc::Sender<(RequestId, IoTask)>,
+ core_rx: channel::Receiver<(RequestId, IoResult)>,
+}
+
+impl IoThread {
+ pub fn new() -> Self {
+ let (core_tx, io_rx) = mpsc::channel();
+ let (io_tx, core_rx) = channel::channel();
+
+ let mut db = Database::new();
+ db.connect("localhost");
+
+ thread::spawn(move || {
+ while let Ok((request_id, task)) = io_rx.recv() {
+ let response = match task {
+ IoTask::CheckRegistered { nick } => match db.is_registered(&nick) {
+ Ok(is_registered) => IoResult::AccountRegistered(is_registered),
+ Err(e) => {
+ warn!("Unable to check account's existence: {}", e);
+ IoResult::AccountRegistered(false)
+ }
+ },
+
+ IoTask::GetAccount {
+ nick,
+ protocol,
+ password_hash,
+ client_salt,
+ server_salt,
+ } => {
+ match db.get_account(
+ &nick,
+ protocol,
+ &password_hash,
+ &client_salt,
+ &server_salt,
+ ) {
+ Ok(account) => IoResult::Account(account),
+ Err(e) => {
+ warn!("Unable to get account data: {}", e);
+ IoResult::Account(None)
+ }
+ }
+ }
+
+ IoTask::GetReplay { id } => {
+ let result = match db.get_replay_name(id) {
+ Ok(Some(filename)) => {
+ let filename = format!(
+ "checked/{}",
+ if filename.starts_with("replays/") {
+ &filename[8..]
+ } else {
+ &filename
+ }
+ );
+ match load_file(&filename) {
+ Ok(contents) => Some(unimplemented!()),
+ Err(e) => {
+ warn!(
+ "Error while writing the room config file \"{}\": {}",
+ filename, e
+ );
+ None
+ }
+ }
+ }
+ Ok(None) => None,
+ Err(e) => {
+ warn!("Unable to get replay name: {}", e);
+ None
+ }
+ };
+ IoResult::Replay(result)
+ }
+
+ IoTask::SaveRoom {
+ room_id,
+ filename,
+ contents,
+ } => {
+ let result = match save_file(&filename, &contents) {
+ Ok(()) => true,
+ Err(e) => {
+ warn!(
+ "Error while writing the room config file \"{}\": {}",
+ filename, e
+ );
+ false
+ }
+ };
+ IoResult::SaveRoom(room_id, result)
+ }
+
+ IoTask::LoadRoom { room_id, filename } => {
+ let result = match load_file(&filename) {
+ Ok(contents) => Some(contents),
+ Err(e) => {
+ warn!(
+ "Error while writing the room config file \"{}\": {}",
+ filename, e
+ );
+ None
+ }
+ };
+ IoResult::LoadRoom(room_id, result)
+ }
+ };
+ io_tx.send((request_id, response));
+ }
+ });
+
+ Self { core_rx, core_tx }
+ }
+
+ pub fn send(&self, request_id: RequestId, task: IoTask) {
+ self.core_tx.send((request_id, task)).unwrap();
+ }
+
+ pub fn try_recv(&self) -> Option<(RequestId, IoResult)> {
+ match self.core_rx.try_recv() {
+ Ok(result) => Some(result),
+ Err(mpsc::TryRecvError::Empty) => None,
+ Err(mpsc::TryRecvError::Disconnected) => unreachable!(),
+ }
+ }
+
+ pub fn register_rx(&self, poll: &mio::Poll, token: mio::Token) -> Result<()> {
+ self.core_rx
+ .register(poll, token, mio::Ready::readable(), PollOpt::edge())
+ }
+}
+
+fn save_file(filename: &str, contents: &str) -> Result<()> {
+ let mut writer = OpenOptions::new().create(true).write(true).open(filename)?;
+ writer.write_all(contents.as_bytes())
+}
+
+fn load_file(filename: &str) -> Result<String> {
+ let mut reader = File::open(filename)?;
+ let mut result = String::new();
+ reader.read_to_string(&mut result)?;
+ Ok(result)
+}
--- a/rust/hwphysics/src/collision.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hwphysics/src/collision.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,129 +1,129 @@
-use std::ops::RangeInclusive;
-
-use crate::{
- common::{GearData, GearDataProcessor, GearId},
- grid::Grid,
- physics::PhysicsData,
-};
-
-use fpnum::*;
-use integral_geometry::{GridIndex, Point, Size};
-use land2d::Land2D;
-
-pub fn fppoint_round(point: &FPPoint) -> Point {
- Point::new(point.x().round() as i32, point.y().round() as i32)
-}
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-pub struct CircleBounds {
- pub center: FPPoint,
- pub radius: FPNum,
-}
-
-impl CircleBounds {
- pub fn intersects(&self, other: &CircleBounds) -> bool {
- (other.center - self.center).is_in_range(self.radius + other.radius)
- }
-
- pub fn rows(&self) -> impl Iterator<Item = (usize, RangeInclusive<usize>)> {
- let radius = self.radius.abs_round() as usize;
- let center = Point::from_fppoint(&self.center);
- (center.y as usize - radius..=center.y as usize + radius)
- .map(move |row| (row, center.x as usize - radius..=center.x as usize + radius))
- }
-}
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-pub struct CollisionData {
- pub bounds: CircleBounds,
-}
-
-impl GearData for CollisionData {}
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-pub struct ContactData {
- pub elasticity: FPNum,
- pub friction: FPNum,
-}
-
-impl GearData for ContactData {}
-
-struct EnabledCollisionsCollection {
- gear_ids: Vec<GearId>,
- collisions: Vec<CollisionData>,
-}
-
-impl EnabledCollisionsCollection {
- fn new() -> Self {
- Self {
- gear_ids: Vec::new(),
- collisions: Vec::new(),
- }
- }
-
- fn push(&mut self, gear_id: GearId, collision: CollisionData) {
- self.gear_ids.push(gear_id);
- self.collisions.push(collision);
- }
-
- fn iter(&self) -> impl Iterator<Item = (GearId, &CollisionData)> {
- self.gear_ids.iter().cloned().zip(self.collisions.iter())
- }
-}
-
-pub struct CollisionProcessor {
- grid: Grid,
- enabled_collisions: EnabledCollisionsCollection,
-
- detected_collisions: DetectedCollisions,
-}
-
-pub struct DetectedCollisions {
- pub pairs: Vec<(GearId, GearId)>,
- pub positions: Vec<Point>,
-}
-
-impl DetectedCollisions {
- pub fn new(capacity: usize) -> Self {
- Self {
- pairs: Vec::with_capacity(capacity),
- positions: Vec::with_capacity(capacity),
- }
- }
-
- pub fn push(&mut self, contact_gear_id1: GearId, contact_gear_id2: GearId, position: &FPPoint) {
- self.pairs.push((contact_gear_id1, contact_gear_id2));
- self.positions.push(fppoint_round(&position));
- }
-}
-
-impl CollisionProcessor {
- pub fn new(size: Size) -> Self {
- Self {
- grid: Grid::new(size),
- enabled_collisions: EnabledCollisionsCollection::new(),
- detected_collisions: DetectedCollisions::new(0),
- }
- }
-
- pub fn process(&mut self, land: &Land2D<u32>, updates: &crate::physics::PositionUpdates) {
- self.grid.check_collisions(&mut self.detected_collisions);
-
- for (gear_id, collision) in self.enabled_collisions.iter() {
- if collision
- .bounds
- .rows()
- .any(|(y, r)| (&land[y][r]).iter().any(|v| *v != 0))
- {
- self.detected_collisions
- .push(gear_id, 0, &collision.bounds.center)
- }
- }
- }
-}
-
-impl GearDataProcessor<CollisionData> for CollisionProcessor {
- fn add(&mut self, gear_id: GearId, gear_data: CollisionData) {
- self.grid.insert_static(gear_id, &gear_data.bounds);
- }
-}
+use std::ops::RangeInclusive;
+
+use crate::{
+ common::{GearData, GearDataProcessor, GearId},
+ grid::Grid,
+ physics::PhysicsData,
+};
+
+use fpnum::*;
+use integral_geometry::{GridIndex, Point, Size};
+use land2d::Land2D;
+
+pub fn fppoint_round(point: &FPPoint) -> Point {
+ Point::new(point.x().round() as i32, point.y().round() as i32)
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct CircleBounds {
+ pub center: FPPoint,
+ pub radius: FPNum,
+}
+
+impl CircleBounds {
+ pub fn intersects(&self, other: &CircleBounds) -> bool {
+ (other.center - self.center).is_in_range(self.radius + other.radius)
+ }
+
+ pub fn rows(&self) -> impl Iterator<Item = (usize, RangeInclusive<usize>)> {
+ let radius = self.radius.abs_round() as usize;
+ let center = Point::from_fppoint(&self.center);
+ (center.y as usize - radius..=center.y as usize + radius)
+ .map(move |row| (row, center.x as usize - radius..=center.x as usize + radius))
+ }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct CollisionData {
+ pub bounds: CircleBounds,
+}
+
+impl GearData for CollisionData {}
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct ContactData {
+ pub elasticity: FPNum,
+ pub friction: FPNum,
+}
+
+impl GearData for ContactData {}
+
+struct EnabledCollisionsCollection {
+ gear_ids: Vec<GearId>,
+ collisions: Vec<CollisionData>,
+}
+
+impl EnabledCollisionsCollection {
+ fn new() -> Self {
+ Self {
+ gear_ids: Vec::new(),
+ collisions: Vec::new(),
+ }
+ }
+
+ fn push(&mut self, gear_id: GearId, collision: CollisionData) {
+ self.gear_ids.push(gear_id);
+ self.collisions.push(collision);
+ }
+
+ fn iter(&self) -> impl Iterator<Item = (GearId, &CollisionData)> {
+ self.gear_ids.iter().cloned().zip(self.collisions.iter())
+ }
+}
+
+pub struct CollisionProcessor {
+ grid: Grid,
+ enabled_collisions: EnabledCollisionsCollection,
+
+ detected_collisions: DetectedCollisions,
+}
+
+pub struct DetectedCollisions {
+ pub pairs: Vec<(GearId, GearId)>,
+ pub positions: Vec<Point>,
+}
+
+impl DetectedCollisions {
+ pub fn new(capacity: usize) -> Self {
+ Self {
+ pairs: Vec::with_capacity(capacity),
+ positions: Vec::with_capacity(capacity),
+ }
+ }
+
+ pub fn push(&mut self, contact_gear_id1: GearId, contact_gear_id2: GearId, position: &FPPoint) {
+ self.pairs.push((contact_gear_id1, contact_gear_id2));
+ self.positions.push(fppoint_round(&position));
+ }
+}
+
+impl CollisionProcessor {
+ pub fn new(size: Size) -> Self {
+ Self {
+ grid: Grid::new(size),
+ enabled_collisions: EnabledCollisionsCollection::new(),
+ detected_collisions: DetectedCollisions::new(0),
+ }
+ }
+
+ pub fn process(&mut self, land: &Land2D<u32>, updates: &crate::physics::PositionUpdates) {
+ self.grid.check_collisions(&mut self.detected_collisions);
+
+ for (gear_id, collision) in self.enabled_collisions.iter() {
+ if collision
+ .bounds
+ .rows()
+ .any(|(y, r)| (&land[y][r]).iter().any(|v| *v != 0))
+ {
+ self.detected_collisions
+ .push(gear_id, 0, &collision.bounds.center)
+ }
+ }
+ }
+}
+
+impl GearDataProcessor<CollisionData> for CollisionProcessor {
+ fn add(&mut self, gear_id: GearId, gear_data: CollisionData) {
+ self.grid.insert_static(gear_id, &gear_data.bounds);
+ }
+}
--- a/rust/hwphysics/src/common.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hwphysics/src/common.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,10 +1,10 @@
-pub type GearId = u16;
-pub trait GearData {}
-
-pub trait GearDataProcessor<T: GearData> {
- fn add(&mut self, gear_id: GearId, gear_data: T);
-}
-
-pub trait GearDataAggregator<T: GearData> {
- fn find_processor(&mut self) -> &mut GearDataProcessor<T>;
-}
+pub type GearId = u16;
+pub trait GearData {}
+
+pub trait GearDataProcessor<T: GearData> {
+ fn add(&mut self, gear_id: GearId, gear_data: T);
+}
+
+pub trait GearDataAggregator<T: GearData> {
+ fn find_processor(&mut self) -> &mut GearDataProcessor<T>;
+}
--- a/rust/hwphysics/src/grid.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hwphysics/src/grid.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,83 +1,83 @@
-use crate::{
- collision::{fppoint_round, CircleBounds, DetectedCollisions},
- common::GearId,
-};
-
-use fpnum::FPPoint;
-use integral_geometry::{GridIndex, Point, Size};
-
-struct GridBin {
- refs: Vec<GearId>,
- static_entries: Vec<CircleBounds>,
- dynamic_entries: Vec<CircleBounds>,
-}
-
-impl GridBin {
- fn new() -> Self {
- Self {
- refs: vec![],
- static_entries: vec![],
- dynamic_entries: vec![],
- }
- }
-}
-
-const GRID_BIN_SIZE: usize = 128;
-
-pub struct Grid {
- bins: Vec<GridBin>,
- space_size: Size,
- bins_count: Size,
- index: GridIndex,
-}
-
-impl Grid {
- pub fn new(size: Size) -> Self {
- assert!(size.is_power_of_two());
- let bins_count = Size::new(size.width / GRID_BIN_SIZE, size.height / GRID_BIN_SIZE);
-
- Self {
- bins: (0..bins_count.area()).map(|_| GridBin::new()).collect(),
- space_size: size,
- bins_count,
- index: Size::square(GRID_BIN_SIZE).to_grid_index(),
- }
- }
-
- fn bin_index(&self, position: &FPPoint) -> Point {
- self.index.map(fppoint_round(position))
- }
-
- fn lookup_bin(&mut self, position: &FPPoint) -> &mut GridBin {
- let index = self.bin_index(position);
- &mut self.bins[index.x as usize * self.bins_count.width + index.y as usize]
- }
-
- pub fn insert_static(&mut self, gear_id: GearId, bounds: &CircleBounds) {
- self.lookup_bin(&bounds.center).static_entries.push(*bounds)
- }
-
- pub fn insert_dynamic(&mut self, gear_id: GearId, bounds: &CircleBounds) {
- self.lookup_bin(&bounds.center)
- .dynamic_entries
- .push(*bounds)
- }
-
- pub fn check_collisions(&self, collisions: &mut DetectedCollisions) {
- for bin in &self.bins {
- for bounds in &bin.dynamic_entries {
- for other in &bin.dynamic_entries {
- if bounds.intersects(other) && bounds != other {
- collisions.push(0, 0, &bounds.center)
- }
- }
-
- for other in &bin.static_entries {
- if bounds.intersects(other) {
- collisions.push(0, 0, &bounds.center)
- }
- }
- }
- }
- }
-}
+use crate::{
+ collision::{fppoint_round, CircleBounds, DetectedCollisions},
+ common::GearId,
+};
+
+use fpnum::FPPoint;
+use integral_geometry::{GridIndex, Point, Size};
+
+struct GridBin {
+ refs: Vec<GearId>,
+ static_entries: Vec<CircleBounds>,
+ dynamic_entries: Vec<CircleBounds>,
+}
+
+impl GridBin {
+ fn new() -> Self {
+ Self {
+ refs: vec![],
+ static_entries: vec![],
+ dynamic_entries: vec![],
+ }
+ }
+}
+
+const GRID_BIN_SIZE: usize = 128;
+
+pub struct Grid {
+ bins: Vec<GridBin>,
+ space_size: Size,
+ bins_count: Size,
+ index: GridIndex,
+}
+
+impl Grid {
+ pub fn new(size: Size) -> Self {
+ assert!(size.is_power_of_two());
+ let bins_count = Size::new(size.width / GRID_BIN_SIZE, size.height / GRID_BIN_SIZE);
+
+ Self {
+ bins: (0..bins_count.area()).map(|_| GridBin::new()).collect(),
+ space_size: size,
+ bins_count,
+ index: Size::square(GRID_BIN_SIZE).to_grid_index(),
+ }
+ }
+
+ fn bin_index(&self, position: &FPPoint) -> Point {
+ self.index.map(fppoint_round(position))
+ }
+
+ fn lookup_bin(&mut self, position: &FPPoint) -> &mut GridBin {
+ let index = self.bin_index(position);
+ &mut self.bins[index.x as usize * self.bins_count.width + index.y as usize]
+ }
+
+ pub fn insert_static(&mut self, gear_id: GearId, bounds: &CircleBounds) {
+ self.lookup_bin(&bounds.center).static_entries.push(*bounds)
+ }
+
+ pub fn insert_dynamic(&mut self, gear_id: GearId, bounds: &CircleBounds) {
+ self.lookup_bin(&bounds.center)
+ .dynamic_entries
+ .push(*bounds)
+ }
+
+ pub fn check_collisions(&self, collisions: &mut DetectedCollisions) {
+ for bin in &self.bins {
+ for bounds in &bin.dynamic_entries {
+ for other in &bin.dynamic_entries {
+ if bounds.intersects(other) && bounds != other {
+ collisions.push(0, 0, &bounds.center)
+ }
+ }
+
+ for other in &bin.static_entries {
+ if bounds.intersects(other) {
+ collisions.push(0, 0, &bounds.center)
+ }
+ }
+ }
+ }
+ }
+}
--- a/rust/hwphysics/src/physics.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/hwphysics/src/physics.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,137 +1,137 @@
-use crate::common::{GearData, GearDataProcessor, GearId};
-use fpnum::*;
-use integral_geometry::{GridIndex, Point, Size};
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-pub struct PhysicsData {
- pub position: FPPoint,
- pub velocity: FPPoint,
-}
-
-impl GearData for PhysicsData {}
-
-impl PhysicsData {
- pub fn new(position: FPPoint, velocity: FPPoint) -> Self {
- Self { position, velocity }
- }
-}
-
-pub struct DynamicPhysicsCollection {
- gear_ids: Vec<GearId>,
- positions: Vec<FPPoint>,
- velocities: Vec<FPPoint>,
-}
-
-impl DynamicPhysicsCollection {
- fn new() -> Self {
- Self {
- gear_ids: Vec::new(),
- positions: Vec::new(),
- velocities: Vec::new(),
- }
- }
-
- fn len(&self) -> usize {
- self.gear_ids.len()
- }
-
- fn push(&mut self, id: GearId, physics: PhysicsData) {
- self.gear_ids.push(id);
- self.positions.push(physics.position);
- self.velocities.push(physics.velocity);
- }
-
- fn iter_pos_update(&mut self) -> impl Iterator<Item = (GearId, (&mut FPPoint, &FPPoint))> {
- self.gear_ids
- .iter()
- .cloned()
- .zip(self.positions.iter_mut().zip(self.velocities.iter()))
- }
-}
-
-pub struct StaticPhysicsCollection {
- gear_ids: Vec<GearId>,
- positions: Vec<FPPoint>,
-}
-
-impl StaticPhysicsCollection {
- fn new() -> Self {
- Self {
- gear_ids: Vec::new(),
- positions: Vec::new(),
- }
- }
-
- fn push(&mut self, gear_id: GearId, physics: PhysicsData) {
- self.gear_ids.push(gear_id);
- self.positions.push(physics.position);
- }
-}
-
-pub struct PhysicsProcessor {
- dynamic_physics: DynamicPhysicsCollection,
- static_physics: StaticPhysicsCollection,
-
- physics_cleanup: Vec<GearId>,
- position_updates: PositionUpdates,
-}
-
-pub struct PositionUpdates {
- pub gear_ids: Vec<GearId>,
- pub positions: Vec<FPPoint>,
-}
-
-impl PositionUpdates {
- pub fn new(capacity: usize) -> Self {
- Self {
- gear_ids: Vec::with_capacity(capacity),
- positions: Vec::with_capacity(capacity),
- }
- }
-
- pub fn push(&mut self, gear_id: GearId, position: &FPPoint) {
- self.gear_ids.push(gear_id);
- self.positions.push(*position);
- }
-}
-
-impl PhysicsProcessor {
- pub fn new() -> Self {
- PhysicsProcessor {
- dynamic_physics: DynamicPhysicsCollection::new(),
- static_physics: StaticPhysicsCollection::new(),
- physics_cleanup: Vec::new(),
- position_updates: PositionUpdates::new(0),
- }
- }
-
- pub fn process(&mut self, time_step: FPNum) -> &PositionUpdates {
- for (gear_id, (pos, vel)) in self.dynamic_physics.iter_pos_update() {
- *pos += *vel * time_step;
- if !vel.is_zero() {
- self.position_updates.push(gear_id, pos)
- } else {
- self.physics_cleanup.push(gear_id)
- }
- }
- &self.position_updates
- }
-
- pub fn push(&mut self, gear_id: GearId, physics_data: PhysicsData) {
- if physics_data.velocity.is_zero() {
- self.static_physics.push(gear_id, physics_data);
- } else {
- self.dynamic_physics.push(gear_id, physics_data);
- }
- }
-}
-
-impl GearDataProcessor<PhysicsData> for PhysicsProcessor {
- fn add(&mut self, gear_id: GearId, gear_data: PhysicsData) {
- if gear_data.velocity.is_zero() {
- self.static_physics.push(gear_id, gear_data);
- } else {
- self.dynamic_physics.push(gear_id, gear_data);
- }
- }
-}
+use crate::common::{GearData, GearDataProcessor, GearId};
+use fpnum::*;
+use integral_geometry::{GridIndex, Point, Size};
+
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub struct PhysicsData {
+ pub position: FPPoint,
+ pub velocity: FPPoint,
+}
+
+impl GearData for PhysicsData {}
+
+impl PhysicsData {
+ pub fn new(position: FPPoint, velocity: FPPoint) -> Self {
+ Self { position, velocity }
+ }
+}
+
+pub struct DynamicPhysicsCollection {
+ gear_ids: Vec<GearId>,
+ positions: Vec<FPPoint>,
+ velocities: Vec<FPPoint>,
+}
+
+impl DynamicPhysicsCollection {
+ fn new() -> Self {
+ Self {
+ gear_ids: Vec::new(),
+ positions: Vec::new(),
+ velocities: Vec::new(),
+ }
+ }
+
+ fn len(&self) -> usize {
+ self.gear_ids.len()
+ }
+
+ fn push(&mut self, id: GearId, physics: PhysicsData) {
+ self.gear_ids.push(id);
+ self.positions.push(physics.position);
+ self.velocities.push(physics.velocity);
+ }
+
+ fn iter_pos_update(&mut self) -> impl Iterator<Item = (GearId, (&mut FPPoint, &FPPoint))> {
+ self.gear_ids
+ .iter()
+ .cloned()
+ .zip(self.positions.iter_mut().zip(self.velocities.iter()))
+ }
+}
+
+pub struct StaticPhysicsCollection {
+ gear_ids: Vec<GearId>,
+ positions: Vec<FPPoint>,
+}
+
+impl StaticPhysicsCollection {
+ fn new() -> Self {
+ Self {
+ gear_ids: Vec::new(),
+ positions: Vec::new(),
+ }
+ }
+
+ fn push(&mut self, gear_id: GearId, physics: PhysicsData) {
+ self.gear_ids.push(gear_id);
+ self.positions.push(physics.position);
+ }
+}
+
+pub struct PhysicsProcessor {
+ dynamic_physics: DynamicPhysicsCollection,
+ static_physics: StaticPhysicsCollection,
+
+ physics_cleanup: Vec<GearId>,
+ position_updates: PositionUpdates,
+}
+
+pub struct PositionUpdates {
+ pub gear_ids: Vec<GearId>,
+ pub positions: Vec<FPPoint>,
+}
+
+impl PositionUpdates {
+ pub fn new(capacity: usize) -> Self {
+ Self {
+ gear_ids: Vec::with_capacity(capacity),
+ positions: Vec::with_capacity(capacity),
+ }
+ }
+
+ pub fn push(&mut self, gear_id: GearId, position: &FPPoint) {
+ self.gear_ids.push(gear_id);
+ self.positions.push(*position);
+ }
+}
+
+impl PhysicsProcessor {
+ pub fn new() -> Self {
+ PhysicsProcessor {
+ dynamic_physics: DynamicPhysicsCollection::new(),
+ static_physics: StaticPhysicsCollection::new(),
+ physics_cleanup: Vec::new(),
+ position_updates: PositionUpdates::new(0),
+ }
+ }
+
+ pub fn process(&mut self, time_step: FPNum) -> &PositionUpdates {
+ for (gear_id, (pos, vel)) in self.dynamic_physics.iter_pos_update() {
+ *pos += *vel * time_step;
+ if !vel.is_zero() {
+ self.position_updates.push(gear_id, pos)
+ } else {
+ self.physics_cleanup.push(gear_id)
+ }
+ }
+ &self.position_updates
+ }
+
+ pub fn push(&mut self, gear_id: GearId, physics_data: PhysicsData) {
+ if physics_data.velocity.is_zero() {
+ self.static_physics.push(gear_id, physics_data);
+ } else {
+ self.dynamic_physics.push(gear_id, physics_data);
+ }
+ }
+}
+
+impl GearDataProcessor<PhysicsData> for PhysicsProcessor {
+ fn add(&mut self, gear_id: GearId, gear_data: PhysicsData) {
+ if gear_data.velocity.is_zero() {
+ self.static_physics.push(gear_id, gear_data);
+ } else {
+ self.dynamic_physics.push(gear_id, gear_data);
+ }
+ }
+}
--- a/rust/lib-hedgewars-engine/src/render.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/lib-hedgewars-engine/src/render.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,9 +1,9 @@
-pub mod atlas;
-pub mod camera;
-mod gear;
-mod gl;
-mod map;
-
-use self::gl::*;
-pub use self::map::*;
-pub use gear::*;
+pub mod atlas;
+pub mod camera;
+mod gear;
+mod gl;
+mod map;
+
+use self::gl::*;
+pub use self::map::*;
+pub use gear::*;
--- a/rust/lib-hedgewars-engine/src/render/atlas.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/lib-hedgewars-engine/src/render/atlas.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,398 +1,398 @@
-use integral_geometry::{Rect, Size};
-use std::cmp::{max, min, Ordering};
-
-#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
-struct Fit {
- short_side: u32,
- long_side: u32,
-}
-
-impl Fit {
- fn new() -> Self {
- Self {
- short_side: u32::max_value(),
- long_side: u32::max_value(),
- }
- }
-
- fn measure(container: Size, size: Size) -> Option<Self> {
- if container.contains(size) {
- let x_leftover = container.width - size.width;
- let y_leftover = container.height - size.height;
- Some(Self {
- short_side: min(x_leftover, y_leftover) as u32,
- long_side: max(x_leftover, y_leftover) as u32,
- })
- } else {
- None
- }
- }
-}
-
-#[derive(PartialEq, Eq)]
-pub struct UsedSpace {
- used_area: usize,
- total_area: usize,
-}
-
-impl UsedSpace {
- const fn new(used_area: usize, total_area: usize) -> Self {
- Self {
- used_area,
- total_area,
- }
- }
-
- const fn used(&self) -> usize {
- self.used_area
- }
-
- const fn total(&self) -> usize {
- self.total_area
- }
-}
-
-impl std::fmt::Debug for UsedSpace {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
- write!(
- f,
- "{:.2}%",
- self.used() as f32 / self.total() as f32 / 100.0
- )?;
- Ok(())
- }
-}
-
-pub struct Atlas {
- size: Size,
- free_rects: Vec<Rect>,
- used_rects: Vec<Rect>,
- splits: Vec<Rect>,
-}
-
-impl Atlas {
- pub fn new(size: Size) -> Self {
- Self {
- size,
- free_rects: vec![Rect::at_origin(size)],
- used_rects: vec![],
- splits: vec![],
- }
- }
-
- pub fn size(&self) -> Size {
- self.size
- }
-
- pub fn used_space(&self) -> UsedSpace {
- let used = self.used_rects.iter().map(|r| r.size().area()).sum();
- UsedSpace::new(used, self.size.area())
- }
-
- fn find_position(&self, size: Size) -> Option<(Rect, Fit)> {
- let mut best_rect = Rect::EMPTY;
- let mut best_fit = Fit::new();
-
- for rect in &self.free_rects {
- if let Some(fit) = Fit::measure(rect.size(), size) {
- if fit < best_fit {
- best_fit = fit;
- best_rect = Rect::from_size(rect.top_left(), size);
- }
- }
-
- if let Some(fit) = Fit::measure(rect.size(), size.transpose()) {
- if fit < best_fit {
- best_fit = fit;
- best_rect = Rect::from_size(rect.top_left(), size.transpose());
- }
- }
- }
-
- if best_rect == Rect::EMPTY {
- None
- } else {
- Some((best_rect, best_fit))
- }
- }
-
- fn split_insert(&mut self, rect: Rect) {
- let mut splits = std::mem::replace(&mut self.splits, vec![]);
- let mut buffer = [Rect::EMPTY; 4];
-
- for i in (0..self.free_rects.len()).rev() {
- if let Some(count) = split_rect(self.free_rects[i], rect, &mut splits, &mut buffer) {
- self.free_rects.swap_remove(i as usize);
- splits.extend_from_slice(&buffer[0..count]);
- }
- }
-
- filter_swap_remove(&mut splits, |s| {
- self.free_rects.iter().any(|r| r.contains_rect(s))
- });
- self.free_rects.extend(splits.drain(..));
- std::mem::replace(&mut self.splits, splits);
-
- self.used_rects.push(rect);
- }
-
- pub fn insert(&mut self, size: Size) -> Option<Rect> {
- let (rect, _) = self.find_position(size)?;
- self.split_insert(rect);
- Some(rect)
- }
-
- pub fn insert_set<Iter>(&mut self, sizes: Iter) -> Vec<Rect>
- where
- Iter: Iterator<Item = Size>,
- {
- let mut sizes: Vec<_> = sizes.collect();
- let mut result = vec![];
-
- while let Some((index, (rect, _))) = sizes
- .iter()
- .enumerate()
- .filter_map(|(i, s)| self.find_position(*s).map(|res| (i, res)))
- .min_by_key(|(_, (_, fit))| fit.clone())
- {
- self.split_insert(rect);
-
- result.push(rect);
- sizes.swap_remove(index);
- }
- result
- }
-
- pub fn reset(&mut self) {
- self.free_rects.clear();
- self.used_rects.clear();
- self.free_rects.push(Rect::at_origin(self.size));
- }
-}
-
-pub struct AtlasCollection {
- texture_size: Size,
- atlases: Vec<Atlas>,
-}
-
-impl AtlasCollection {
- pub fn new(texture_size: Size) -> Self {
- Self {
- texture_size,
- atlases: vec![],
- }
- }
-
- fn repack(&mut self, size: Size) -> bool {
- for atlas in &mut self.atlases {
- let mut temp_atlas = Atlas::new(atlas.size());
- let sizes = atlas
- .used_rects
- .iter()
- .map(|r| r.size())
- .chain(std::iter::once(size));
- if !temp_atlas.insert_set(sizes).is_empty() {
- std::mem::swap(atlas, &mut temp_atlas);
- return true;
- }
- }
- false
- }
-
- pub fn insert_sprite(&mut self, size: Size) -> bool {
- if !self.texture_size.contains(size) {
- false
- } else {
- if let Some(rect) = self.atlases.iter_mut().find_map(|a| a.insert(size)) {
-
- } else if !self.repack(size) {
- let mut atlas = Atlas::new(self.texture_size);
- atlas.insert(size);
- self.atlases.push(atlas);
- }
- true
- }
- }
-}
-
-#[inline]
-fn filter_swap_remove<T, F>(vec: &mut Vec<T>, predicate: F)
-where
- F: Fn(&T) -> bool,
-{
- let mut i = 0;
- while i < vec.len() {
- if predicate(&vec[i]) {
- vec.swap_remove(i);
- } else {
- i += 1;
- }
- }
-}
-
-#[inline]
-fn prune_push(
- previous_splits: &mut Vec<Rect>,
- buffer: &mut [Rect; 4],
- buffer_size: &mut usize,
- rect: Rect,
-) {
- if !previous_splits.iter().any(|r| r.contains_rect(&rect)) {
- filter_swap_remove(previous_splits, |s| rect.contains_rect(s));
- buffer[*buffer_size] = rect;
- *buffer_size += 1;
- }
-}
-
-fn split_rect(
- free_rect: Rect,
- rect: Rect,
- previous_splits: &mut Vec<Rect>,
- buffer: &mut [Rect; 4],
-) -> Option<usize> {
- let mut buffer_size = 0usize;
- let split = free_rect.intersects(&rect);
- if split {
- if rect.left() > free_rect.left() {
- let trim = free_rect.right() - rect.left() + 1;
- prune_push(
- previous_splits,
- buffer,
- &mut buffer_size,
- free_rect.with_margins(0, -trim, 0, 0),
- );
- }
- if rect.right() < free_rect.right() {
- let trim = rect.right() - free_rect.left() + 1;
- prune_push(
- previous_splits,
- buffer,
- &mut buffer_size,
- free_rect.with_margins(-trim, 0, 0, 0),
- );
- }
- if rect.top() > free_rect.top() {
- let trim = free_rect.bottom() - rect.top() + 1;
- prune_push(
- previous_splits,
- buffer,
- &mut buffer_size,
- free_rect.with_margins(0, 0, 0, -trim),
- );;
- }
- if rect.bottom() < free_rect.bottom() {
- let trim = rect.bottom() - free_rect.top() + 1;
- prune_push(
- previous_splits,
- buffer,
- &mut buffer_size,
- free_rect.with_margins(0, 0, -trim, 0),
- );;
- }
- }
- if split {
- Some(buffer_size)
- } else {
- None
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::Atlas;
- use integral_geometry::{Rect, Size};
- use itertools::Itertools as _;
- use proptest::prelude::*;
-
- #[test]
- fn insert() {
- let atlas_size = Size::square(16);
- let mut atlas = Atlas::new(atlas_size);
-
- assert_eq!(None, atlas.insert(Size::square(20)));
-
- let rect_size = Size::new(11, 3);
- let rect = atlas.insert(rect_size).unwrap();
-
- assert_eq!(rect, Rect::at_origin(rect_size));
- assert_eq!(2, atlas.free_rects.len());
- }
-
- #[derive(Debug, Clone)]
- struct TestRect(Size);
- struct TestRectParameters(Size);
-
- impl Default for TestRectParameters {
- fn default() -> Self {
- Self(Size::square(64))
- }
- }
-
- impl Arbitrary for TestRect {
- type Parameters = TestRectParameters;
-
- fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
- (1..=args.0.width, 1..=args.0.height)
- .prop_map(|(w, h)| TestRect(Size::new(w, h)))
- .boxed()
- }
-
- type Strategy = BoxedStrategy<TestRect>;
- }
-
- trait HasSize {
- fn size(&self) -> Size;
- }
-
- impl HasSize for TestRect {
- fn size(&self) -> Size {
- self.0
- }
- }
-
- impl HasSize for Rect {
- fn size(&self) -> Size {
- self.size()
- }
- }
-
- fn sum_area<S: HasSize>(items: &[S]) -> usize {
- items.iter().map(|s| s.size().area()).sum()
- }
-
- proptest! {
- #[test]
- fn prop_insert(rects in Vec::<TestRect>::arbitrary()) {
- let container = Rect::at_origin(Size::square(2048));
- let mut atlas = Atlas::new(container.size());
- let inserted: Vec<_> = rects.iter().filter_map(|TestRect(size)| atlas.insert(*size)).collect();
-
- let mut inserted_pairs = inserted.iter().cartesian_product(inserted.iter());
-
- assert!(inserted.iter().all(|r| container.contains_rect(r)));
- assert!(inserted_pairs.all(|(r1, r2)| r1 == r2 || r1 != r2 && !r1.intersects(r2)));
-
- assert_eq!(inserted.len(), rects.len());
- assert_eq!(sum_area(&inserted), sum_area(&rects));
- }
- }
-
- proptest! {
- #[test]
- fn prop_insert_set(rects in Vec::<TestRect>::arbitrary()) {
- let container = Rect::at_origin(Size::square(2048));
- let mut atlas = Atlas::new(container.size());
- let mut set_atlas = Atlas::new(container.size());
-
- let inserted: Vec<_> = rects.iter().filter_map(|TestRect(size)| atlas.insert(*size)).collect();
- let set_inserted: Vec<_> = set_atlas.insert_set(rects.iter().map(|TestRect(size)| *size));
-
- let mut set_inserted_pairs = set_inserted.iter().cartesian_product(set_inserted.iter());
-
- assert!(set_inserted_pairs.all(|(r1, r2)| r1 == r2 || r1 != r2 && !r1.intersects(r2)));
- assert!(set_atlas.used_space().used() <= atlas.used_space().used());
-
- assert_eq!(sum_area(&set_inserted), sum_area(&inserted));
- }
- }
-}
+use integral_geometry::{Rect, Size};
+use std::cmp::{max, min, Ordering};
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
+struct Fit {
+ short_side: u32,
+ long_side: u32,
+}
+
+impl Fit {
+ fn new() -> Self {
+ Self {
+ short_side: u32::max_value(),
+ long_side: u32::max_value(),
+ }
+ }
+
+ fn measure(container: Size, size: Size) -> Option<Self> {
+ if container.contains(size) {
+ let x_leftover = container.width - size.width;
+ let y_leftover = container.height - size.height;
+ Some(Self {
+ short_side: min(x_leftover, y_leftover) as u32,
+ long_side: max(x_leftover, y_leftover) as u32,
+ })
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(PartialEq, Eq)]
+pub struct UsedSpace {
+ used_area: usize,
+ total_area: usize,
+}
+
+impl UsedSpace {
+ const fn new(used_area: usize, total_area: usize) -> Self {
+ Self {
+ used_area,
+ total_area,
+ }
+ }
+
+ const fn used(&self) -> usize {
+ self.used_area
+ }
+
+ const fn total(&self) -> usize {
+ self.total_area
+ }
+}
+
+impl std::fmt::Debug for UsedSpace {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ write!(
+ f,
+ "{:.2}%",
+ self.used() as f32 / self.total() as f32 / 100.0
+ )?;
+ Ok(())
+ }
+}
+
+pub struct Atlas {
+ size: Size,
+ free_rects: Vec<Rect>,
+ used_rects: Vec<Rect>,
+ splits: Vec<Rect>,
+}
+
+impl Atlas {
+ pub fn new(size: Size) -> Self {
+ Self {
+ size,
+ free_rects: vec![Rect::at_origin(size)],
+ used_rects: vec![],
+ splits: vec![],
+ }
+ }
+
+ pub fn size(&self) -> Size {
+ self.size
+ }
+
+ pub fn used_space(&self) -> UsedSpace {
+ let used = self.used_rects.iter().map(|r| r.size().area()).sum();
+ UsedSpace::new(used, self.size.area())
+ }
+
+ fn find_position(&self, size: Size) -> Option<(Rect, Fit)> {
+ let mut best_rect = Rect::EMPTY;
+ let mut best_fit = Fit::new();
+
+ for rect in &self.free_rects {
+ if let Some(fit) = Fit::measure(rect.size(), size) {
+ if fit < best_fit {
+ best_fit = fit;
+ best_rect = Rect::from_size(rect.top_left(), size);
+ }
+ }
+
+ if let Some(fit) = Fit::measure(rect.size(), size.transpose()) {
+ if fit < best_fit {
+ best_fit = fit;
+ best_rect = Rect::from_size(rect.top_left(), size.transpose());
+ }
+ }
+ }
+
+ if best_rect == Rect::EMPTY {
+ None
+ } else {
+ Some((best_rect, best_fit))
+ }
+ }
+
+ fn split_insert(&mut self, rect: Rect) {
+ let mut splits = std::mem::replace(&mut self.splits, vec![]);
+ let mut buffer = [Rect::EMPTY; 4];
+
+ for i in (0..self.free_rects.len()).rev() {
+ if let Some(count) = split_rect(self.free_rects[i], rect, &mut splits, &mut buffer) {
+ self.free_rects.swap_remove(i as usize);
+ splits.extend_from_slice(&buffer[0..count]);
+ }
+ }
+
+ filter_swap_remove(&mut splits, |s| {
+ self.free_rects.iter().any(|r| r.contains_rect(s))
+ });
+ self.free_rects.extend(splits.drain(..));
+ std::mem::replace(&mut self.splits, splits);
+
+ self.used_rects.push(rect);
+ }
+
+ pub fn insert(&mut self, size: Size) -> Option<Rect> {
+ let (rect, _) = self.find_position(size)?;
+ self.split_insert(rect);
+ Some(rect)
+ }
+
+ pub fn insert_set<Iter>(&mut self, sizes: Iter) -> Vec<Rect>
+ where
+ Iter: Iterator<Item = Size>,
+ {
+ let mut sizes: Vec<_> = sizes.collect();
+ let mut result = vec![];
+
+ while let Some((index, (rect, _))) = sizes
+ .iter()
+ .enumerate()
+ .filter_map(|(i, s)| self.find_position(*s).map(|res| (i, res)))
+ .min_by_key(|(_, (_, fit))| fit.clone())
+ {
+ self.split_insert(rect);
+
+ result.push(rect);
+ sizes.swap_remove(index);
+ }
+ result
+ }
+
+ pub fn reset(&mut self) {
+ self.free_rects.clear();
+ self.used_rects.clear();
+ self.free_rects.push(Rect::at_origin(self.size));
+ }
+}
+
+pub struct AtlasCollection {
+ texture_size: Size,
+ atlases: Vec<Atlas>,
+}
+
+impl AtlasCollection {
+ pub fn new(texture_size: Size) -> Self {
+ Self {
+ texture_size,
+ atlases: vec![],
+ }
+ }
+
+ fn repack(&mut self, size: Size) -> bool {
+ for atlas in &mut self.atlases {
+ let mut temp_atlas = Atlas::new(atlas.size());
+ let sizes = atlas
+ .used_rects
+ .iter()
+ .map(|r| r.size())
+ .chain(std::iter::once(size));
+ if !temp_atlas.insert_set(sizes).is_empty() {
+ std::mem::swap(atlas, &mut temp_atlas);
+ return true;
+ }
+ }
+ false
+ }
+
+ pub fn insert_sprite(&mut self, size: Size) -> bool {
+ if !self.texture_size.contains(size) {
+ false
+ } else {
+ if let Some(rect) = self.atlases.iter_mut().find_map(|a| a.insert(size)) {
+
+ } else if !self.repack(size) {
+ let mut atlas = Atlas::new(self.texture_size);
+ atlas.insert(size);
+ self.atlases.push(atlas);
+ }
+ true
+ }
+ }
+}
+
+#[inline]
+fn filter_swap_remove<T, F>(vec: &mut Vec<T>, predicate: F)
+where
+ F: Fn(&T) -> bool,
+{
+ let mut i = 0;
+ while i < vec.len() {
+ if predicate(&vec[i]) {
+ vec.swap_remove(i);
+ } else {
+ i += 1;
+ }
+ }
+}
+
+#[inline]
+fn prune_push(
+ previous_splits: &mut Vec<Rect>,
+ buffer: &mut [Rect; 4],
+ buffer_size: &mut usize,
+ rect: Rect,
+) {
+ if !previous_splits.iter().any(|r| r.contains_rect(&rect)) {
+ filter_swap_remove(previous_splits, |s| rect.contains_rect(s));
+ buffer[*buffer_size] = rect;
+ *buffer_size += 1;
+ }
+}
+
+fn split_rect(
+ free_rect: Rect,
+ rect: Rect,
+ previous_splits: &mut Vec<Rect>,
+ buffer: &mut [Rect; 4],
+) -> Option<usize> {
+ let mut buffer_size = 0usize;
+ let split = free_rect.intersects(&rect);
+ if split {
+ if rect.left() > free_rect.left() {
+ let trim = free_rect.right() - rect.left() + 1;
+ prune_push(
+ previous_splits,
+ buffer,
+ &mut buffer_size,
+ free_rect.with_margins(0, -trim, 0, 0),
+ );
+ }
+ if rect.right() < free_rect.right() {
+ let trim = rect.right() - free_rect.left() + 1;
+ prune_push(
+ previous_splits,
+ buffer,
+ &mut buffer_size,
+ free_rect.with_margins(-trim, 0, 0, 0),
+ );
+ }
+ if rect.top() > free_rect.top() {
+ let trim = free_rect.bottom() - rect.top() + 1;
+ prune_push(
+ previous_splits,
+ buffer,
+ &mut buffer_size,
+ free_rect.with_margins(0, 0, 0, -trim),
+ );;
+ }
+ if rect.bottom() < free_rect.bottom() {
+ let trim = rect.bottom() - free_rect.top() + 1;
+ prune_push(
+ previous_splits,
+ buffer,
+ &mut buffer_size,
+ free_rect.with_margins(0, 0, -trim, 0),
+ );;
+ }
+ }
+ if split {
+ Some(buffer_size)
+ } else {
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Atlas;
+ use integral_geometry::{Rect, Size};
+ use itertools::Itertools as _;
+ use proptest::prelude::*;
+
+ #[test]
+ fn insert() {
+ let atlas_size = Size::square(16);
+ let mut atlas = Atlas::new(atlas_size);
+
+ assert_eq!(None, atlas.insert(Size::square(20)));
+
+ let rect_size = Size::new(11, 3);
+ let rect = atlas.insert(rect_size).unwrap();
+
+ assert_eq!(rect, Rect::at_origin(rect_size));
+ assert_eq!(2, atlas.free_rects.len());
+ }
+
+ #[derive(Debug, Clone)]
+ struct TestRect(Size);
+ struct TestRectParameters(Size);
+
+ impl Default for TestRectParameters {
+ fn default() -> Self {
+ Self(Size::square(64))
+ }
+ }
+
+ impl Arbitrary for TestRect {
+ type Parameters = TestRectParameters;
+
+ fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+ (1..=args.0.width, 1..=args.0.height)
+ .prop_map(|(w, h)| TestRect(Size::new(w, h)))
+ .boxed()
+ }
+
+ type Strategy = BoxedStrategy<TestRect>;
+ }
+
+ trait HasSize {
+ fn size(&self) -> Size;
+ }
+
+ impl HasSize for TestRect {
+ fn size(&self) -> Size {
+ self.0
+ }
+ }
+
+ impl HasSize for Rect {
+ fn size(&self) -> Size {
+ self.size()
+ }
+ }
+
+ fn sum_area<S: HasSize>(items: &[S]) -> usize {
+ items.iter().map(|s| s.size().area()).sum()
+ }
+
+ proptest! {
+ #[test]
+ fn prop_insert(rects in Vec::<TestRect>::arbitrary()) {
+ let container = Rect::at_origin(Size::square(2048));
+ let mut atlas = Atlas::new(container.size());
+ let inserted: Vec<_> = rects.iter().filter_map(|TestRect(size)| atlas.insert(*size)).collect();
+
+ let mut inserted_pairs = inserted.iter().cartesian_product(inserted.iter());
+
+ assert!(inserted.iter().all(|r| container.contains_rect(r)));
+ assert!(inserted_pairs.all(|(r1, r2)| r1 == r2 || r1 != r2 && !r1.intersects(r2)));
+
+ assert_eq!(inserted.len(), rects.len());
+ assert_eq!(sum_area(&inserted), sum_area(&rects));
+ }
+ }
+
+ proptest! {
+ #[test]
+ fn prop_insert_set(rects in Vec::<TestRect>::arbitrary()) {
+ let container = Rect::at_origin(Size::square(2048));
+ let mut atlas = Atlas::new(container.size());
+ let mut set_atlas = Atlas::new(container.size());
+
+ let inserted: Vec<_> = rects.iter().filter_map(|TestRect(size)| atlas.insert(*size)).collect();
+ let set_inserted: Vec<_> = set_atlas.insert_set(rects.iter().map(|TestRect(size)| *size));
+
+ let mut set_inserted_pairs = set_inserted.iter().cartesian_product(set_inserted.iter());
+
+ assert!(set_inserted_pairs.all(|(r1, r2)| r1 == r2 || r1 != r2 && !r1.intersects(r2)));
+ assert!(set_atlas.used_space().used() <= atlas.used_space().used());
+
+ assert_eq!(sum_area(&set_inserted), sum_area(&inserted));
+ }
+ }
+}
--- a/rust/lib-hedgewars-engine/src/render/camera.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/lib-hedgewars-engine/src/render/camera.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,64 +1,64 @@
-use integral_geometry::{Point, Rect, Size};
-
-#[derive(Debug)]
-pub struct Camera {
- pub position: Point,
- pub zoom: f32,
- size: Size,
-}
-
-impl Camera {
- pub fn new() -> Self {
- Self::with_size(Size::new(1024, 768))
- }
-
- pub fn with_size(size: Size) -> Self {
- Self {
- position: Point::ZERO,
- zoom: 1.0,
- size,
- }
- }
-
- pub fn viewport(&self) -> Rect {
- #[inline]
- fn scale(value: usize, zoom: f32) -> i32 {
- (value as f32 / zoom / 2.0) as i32
- }
- let half_width = scale(self.size.width, self.zoom);
- let half_height = scale(self.size.height, self.zoom);
- Rect::from_box(
- self.position.x - half_width,
- self.position.x + half_width,
- self.position.y - half_height,
- self.position.y + half_height,
- )
- }
-
- pub fn projection(&self) -> [f32; 16] {
- let viewport = self.viewport();
- let left = viewport.left() as f32;
- let width = viewport.width() as f32;
- let height = viewport.height() as f32;
- let top = viewport.top() as f32;
-
- [
- 2f32 / width,
- 0f32,
- 0f32,
- 0f32,
- 0f32,
- 2f32 / -height,
- 0f32,
- 0f32,
- 0f32,
- 0f32,
- 0.5f32,
- 0f32,
- -(2.0 * left + width) / width,
- (2.0 * top + height) / height,
- 0.5f32,
- 1f32,
- ]
- }
-}
+use integral_geometry::{Point, Rect, Size};
+
+#[derive(Debug)]
+pub struct Camera {
+ pub position: Point,
+ pub zoom: f32,
+ size: Size,
+}
+
+impl Camera {
+ pub fn new() -> Self {
+ Self::with_size(Size::new(1024, 768))
+ }
+
+ pub fn with_size(size: Size) -> Self {
+ Self {
+ position: Point::ZERO,
+ zoom: 1.0,
+ size,
+ }
+ }
+
+ pub fn viewport(&self) -> Rect {
+ #[inline]
+ fn scale(value: usize, zoom: f32) -> i32 {
+ (value as f32 / zoom / 2.0) as i32
+ }
+ let half_width = scale(self.size.width, self.zoom);
+ let half_height = scale(self.size.height, self.zoom);
+ Rect::from_box(
+ self.position.x - half_width,
+ self.position.x + half_width,
+ self.position.y - half_height,
+ self.position.y + half_height,
+ )
+ }
+
+ pub fn projection(&self) -> [f32; 16] {
+ let viewport = self.viewport();
+ let left = viewport.left() as f32;
+ let width = viewport.width() as f32;
+ let height = viewport.height() as f32;
+ let top = viewport.top() as f32;
+
+ [
+ 2f32 / width,
+ 0f32,
+ 0f32,
+ 0f32,
+ 0f32,
+ 2f32 / -height,
+ 0f32,
+ 0f32,
+ 0f32,
+ 0f32,
+ 0.5f32,
+ 0f32,
+ -(2.0 * left + width) / width,
+ (2.0 * top + height) / height,
+ 0.5f32,
+ 1f32,
+ ]
+ }
+}
--- a/rust/lib-hedgewars-engine/src/render/gear.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/lib-hedgewars-engine/src/render/gear.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,15 +1,15 @@
-use super::atlas::AtlasCollection;
-use integral_geometry::Size;
-
-struct GearRenderer {
- atlas: AtlasCollection,
-}
-
-const ATLAS_SIZE: Size = Size::square(2048);
-
-impl GearRenderer {
- pub fn new() -> Self {
- let atlas = AtlasCollection::new(ATLAS_SIZE);
- Self { atlas }
- }
-}
+use super::atlas::AtlasCollection;
+use integral_geometry::Size;
+
+struct GearRenderer {
+ atlas: AtlasCollection,
+}
+
+const ATLAS_SIZE: Size = Size::square(2048);
+
+impl GearRenderer {
+ pub fn new() -> Self {
+ let atlas = AtlasCollection::new(ATLAS_SIZE);
+ Self { atlas }
+ }
+}
--- a/rust/mapgen/src/theme.rs Tue Jun 04 23:19:18 2019 +0300
+++ b/rust/mapgen/src/theme.rs Tue Jun 04 22:34:42 2019 +0200
@@ -1,194 +1,194 @@
-use png::{ColorType, Decoder, DecodingError};
-use std::{
- fs::{read_dir, File},
- io,
- io::BufReader,
- path::Path,
- slice::{from_raw_parts, from_raw_parts_mut},
-};
-
-use integral_geometry::Size;
-use vec2d::Vec2D;
-
-pub struct ThemeSprite {
- pixels: Vec2D<u32>,
-}
-
-impl ThemeSprite {
- #[inline]
- pub fn size(&self) -> Size {
- self.pixels.size()
- }
-
- #[inline]
- pub fn width(&self) -> usize {
- self.size().width
- }
-
- #[inline]
- pub fn height(&self) -> usize {
- self.size().height
- }
-
- #[inline]
- pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[u32]> {
- self.pixels.rows()
- }
-
- #[inline]
- pub fn get_row(&self, index: usize) -> &[u32] {
- &self.pixels[index]
- }
-
- #[inline]
- pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
- self.pixels[y][x]
- }
-
- pub fn to_transposed(&self) -> ThemeSprite {
- let size = self.size().transpose();
- let mut pixels = Vec2D::new(size, 0u32);
- for (y, row) in self.pixels.rows().enumerate() {
- for (x, v) in row.iter().enumerate() {
- pixels[x][y] = *v;
- }
- }
- ThemeSprite { pixels }
- }
-
- pub fn to_tiled(&self) -> TiledSprite {
- let size = self.size();
- assert!(size.is_power_of_two());
- let tile_width_shift = size.width.trailing_zeros() as usize + 2;
- let mut pixels = vec![0u32; size.area()];
-
- for (y, row) in self.pixels.rows().enumerate() {
- for (x, v) in row.iter().enumerate() {
- pixels[get_tiled_index(x, y, tile_width_shift)] = *v;
- }
- }
-
- TiledSprite {
- tile_width_shift,
- size,
- pixels,
- }
- }
-}
-
-#[inline]
-fn get_tiled_index(x: usize, y: usize, tile_width_shift: usize) -> usize {
- (((y >> 2) << tile_width_shift) + ((x >> 2) << 4)) + ((y & 0b11) << 2) + (x & 0b11)
-}
-
-pub struct TiledSprite {
- tile_width_shift: usize,
- size: Size,
- pixels: Vec<u32>,
-}
-
-impl TiledSprite {
- #[inline]
- pub fn size(&self) -> Size {
- self.size
- }
-
- #[inline]
- pub fn width(&self) -> usize {
- self.size().width
- }
-
- #[inline]
- pub fn height(&self) -> usize {
- self.size().height
- }
-
- #[inline]
- pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
- self.pixels[get_tiled_index(x, y, self.tile_width_shift)]
- }
-}
-
-pub struct Theme {
- land_texture: Option<ThemeSprite>,
- border_texture: Option<ThemeSprite>,
-}
-
-impl Theme {
- pub fn land_texture(&self) -> Option<&ThemeSprite> {
- self.land_texture.as_ref()
- }
-
- pub fn border_texture(&self) -> Option<&ThemeSprite> {
- self.border_texture.as_ref()
- }
-}
-
-#[derive(Debug)]
-pub enum ThemeLoadError {
- File(io::Error),
- Decoding(DecodingError),
- Format(String),
-}
-
-impl From<io::Error> for ThemeLoadError {
- fn from(e: io::Error) -> Self {
- ThemeLoadError::File(e)
- }
-}
-
-impl From<DecodingError> for ThemeLoadError {
- fn from(e: DecodingError) -> Self {
- ThemeLoadError::Decoding(e)
- }
-}
-
-impl Theme {
- pub fn new() -> Self {
- Theme {
- land_texture: None,
- border_texture: None,
- }
- }
-
- pub fn load(path: &Path) -> Result<Theme, ThemeLoadError> {
- let mut theme = Self::new();
-
- for entry in read_dir(path)? {
- let file = entry?;
- if file.file_name() == "LandTex.png" {
- theme.land_texture = Some(load_sprite(&file.path())?)
- } else if file.file_name() == "Border.png" {
- theme.border_texture = Some(load_sprite(&file.path())?)
- }
- }
-
- Ok(theme)
- }
-}
-
-fn load_sprite(path: &Path) -> Result<ThemeSprite, ThemeLoadError> {
- let decoder = Decoder::new(BufReader::new(File::open(path)?));
- let (info, mut reader) = decoder.read_info()?;
-
- if info.color_type != ColorType::RGBA {
- return Err(ThemeLoadError::Format(format!(
- "Unexpected format: {:?}",
- info.color_type
- )));
- }
- let size = Size::new(info.width as usize, info.height as usize);
-
- let mut pixels: Vec2D<u32> = Vec2D::new(size, 0);
- reader.next_frame(slice_u32_to_u8_mut(pixels.as_mut_slice()))?;
-
- Ok(ThemeSprite { pixels })
-}
-
-pub fn slice_u32_to_u8(slice_u32: &[u32]) -> &[u8] {
- unsafe { from_raw_parts::<u8>(slice_u32.as_ptr() as *const u8, slice_u32.len() * 4) }
-}
-
-pub fn slice_u32_to_u8_mut(slice_u32: &mut [u32]) -> &mut [u8] {
- unsafe { from_raw_parts_mut::<u8>(slice_u32.as_mut_ptr() as *mut u8, slice_u32.len() * 4) }
-}
+use png::{ColorType, Decoder, DecodingError};
+use std::{
+ fs::{read_dir, File},
+ io,
+ io::BufReader,
+ path::Path,
+ slice::{from_raw_parts, from_raw_parts_mut},
+};
+
+use integral_geometry::Size;
+use vec2d::Vec2D;
+
+pub struct ThemeSprite {
+ pixels: Vec2D<u32>,
+}
+
+impl ThemeSprite {
+ #[inline]
+ pub fn size(&self) -> Size {
+ self.pixels.size()
+ }
+
+ #[inline]
+ pub fn width(&self) -> usize {
+ self.size().width
+ }
+
+ #[inline]
+ pub fn height(&self) -> usize {
+ self.size().height
+ }
+
+ #[inline]
+ pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[u32]> {
+ self.pixels.rows()
+ }
+
+ #[inline]
+ pub fn get_row(&self, index: usize) -> &[u32] {
+ &self.pixels[index]
+ }
+
+ #[inline]
+ pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
+ self.pixels[y][x]
+ }
+
+ pub fn to_transposed(&self) -> ThemeSprite {
+ let size = self.size().transpose();
+ let mut pixels = Vec2D::new(size, 0u32);
+ for (y, row) in self.pixels.rows().enumerate() {
+ for (x, v) in row.iter().enumerate() {
+ pixels[x][y] = *v;
+ }
+ }
+ ThemeSprite { pixels }
+ }
+
+ pub fn to_tiled(&self) -> TiledSprite {
+ let size = self.size();
+ assert!(size.is_power_of_two());
+ let tile_width_shift = size.width.trailing_zeros() as usize + 2;
+ let mut pixels = vec![0u32; size.area()];
+
+ for (y, row) in self.pixels.rows().enumerate() {
+ for (x, v) in row.iter().enumerate() {
+ pixels[get_tiled_index(x, y, tile_width_shift)] = *v;
+ }
+ }
+
+ TiledSprite {
+ tile_width_shift,
+ size,
+ pixels,
+ }
+ }
+}
+
+#[inline]
+fn get_tiled_index(x: usize, y: usize, tile_width_shift: usize) -> usize {
+ (((y >> 2) << tile_width_shift) + ((x >> 2) << 4)) + ((y & 0b11) << 2) + (x & 0b11)
+}
+
+pub struct TiledSprite {
+ tile_width_shift: usize,
+ size: Size,
+ pixels: Vec<u32>,
+}
+
+impl TiledSprite {
+ #[inline]
+ pub fn size(&self) -> Size {
+ self.size
+ }
+
+ #[inline]
+ pub fn width(&self) -> usize {
+ self.size().width
+ }
+
+ #[inline]
+ pub fn height(&self) -> usize {
+ self.size().height
+ }
+
+ #[inline]
+ pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
+ self.pixels[get_tiled_index(x, y, self.tile_width_shift)]
+ }
+}
+
+pub struct Theme {
+ land_texture: Option<ThemeSprite>,
+ border_texture: Option<ThemeSprite>,
+}
+
+impl Theme {
+ pub fn land_texture(&self) -> Option<&ThemeSprite> {
+ self.land_texture.as_ref()
+ }
+
+ pub fn border_texture(&self) -> Option<&ThemeSprite> {
+ self.border_texture.as_ref()
+ }
+}
+
+#[derive(Debug)]
+pub enum ThemeLoadError {
+ File(io::Error),
+ Decoding(DecodingError),
+ Format(String),
+}
+
+impl From<io::Error> for ThemeLoadError {
+ fn from(e: io::Error) -> Self {
+ ThemeLoadError::File(e)
+ }
+}
+
+impl From<DecodingError> for ThemeLoadError {
+ fn from(e: DecodingError) -> Self {
+ ThemeLoadError::Decoding(e)
+ }
+}
+
+impl Theme {
+ pub fn new() -> Self {
+ Theme {
+ land_texture: None,
+ border_texture: None,
+ }
+ }
+
+ pub fn load(path: &Path) -> Result<Theme, ThemeLoadError> {
+ let mut theme = Self::new();
+
+ for entry in read_dir(path)? {
+ let file = entry?;
+ if file.file_name() == "LandTex.png" {
+ theme.land_texture = Some(load_sprite(&file.path())?)
+ } else if file.file_name() == "Border.png" {
+ theme.border_texture = Some(load_sprite(&file.path())?)
+ }
+ }
+
+ Ok(theme)
+ }
+}
+
+fn load_sprite(path: &Path) -> Result<ThemeSprite, ThemeLoadError> {
+ let decoder = Decoder::new(BufReader::new(File::open(path)?));
+ let (info, mut reader) = decoder.read_info()?;
+
+ if info.color_type != ColorType::RGBA {
+ return Err(ThemeLoadError::Format(format!(
+ "Unexpected format: {:?}",
+ info.color_type
+ )));
+ }
+ let size = Size::new(info.width as usize, info.height as usize);
+
+ let mut pixels: Vec2D<u32> = Vec2D::new(size, 0);
+ reader.next_frame(slice_u32_to_u8_mut(pixels.as_mut_slice()))?;
+
+ Ok(ThemeSprite { pixels })
+}
+
+pub fn slice_u32_to_u8(slice_u32: &[u32]) -> &[u8] {
+ unsafe { from_raw_parts::<u8>(slice_u32.as_ptr() as *const u8, slice_u32.len() * 4) }
+}
+
+pub fn slice_u32_to_u8_mut(slice_u32: &mut [u32]) -> &mut [u8] {
+ unsafe { from_raw_parts_mut::<u8>(slice_u32.as_mut_ptr() as *mut u8, slice_u32.len() * 4) }
+}