From: Josh Simmons Date: Sat, 30 Nov 2024 10:29:06 +0000 (+0100) Subject: shark: Move game stuff into its own module X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=a9a01dc36fd220458052d93d3917f668f9270a2a;p=josh%2Fnarcissus shark: Move game stuff into its own module --- diff --git a/title/shark/src/game.rs b/title/shark/src/game.rs new file mode 100644 index 0000000..e7be913 --- /dev/null +++ b/title/shark/src/game.rs @@ -0,0 +1,424 @@ +use std::f32::consts::SQRT_2; + +use narcissus_core::{box_assume_init, default, rand::Pcg64, zeroed_box, BitIter}; +use narcissus_maths::{clamp, perlin_noise3, sin_pi_f32, vec3, Deg, HalfTurn, Mat4, Point3, Vec3}; + +use crate::spring::simple_spring_damper_exact; + +const ARCHTYPE_PROJECTILE_MAX: usize = 65536; + +pub struct GameVariables { + game_speed: f32, + + camera_distance: f32, + camera_angle: Deg, + camera_damping: f32, + camera_deadzone: f32, + camera_shake_decay: f32, + camera_shake_max_offset: f32, + camera_shake_frequency: f32, + + player_speed: f32, + + weapon_cooldown: f32, + weapon_projectile_speed: f32, + weapon_projectile_lifetime: f32, +} + +pub static GAME_VARIABLES: GameVariables = GameVariables { + game_speed: 1.0, + + camera_distance: 45.0, + camera_angle: Deg::new(60.0), + camera_damping: 35.0, + camera_deadzone: 0.1, + camera_shake_decay: 2.0, + camera_shake_max_offset: 2.0, + camera_shake_frequency: 11.0, + + player_speed: 10.0, + + weapon_cooldown: 0.2, + weapon_projectile_speed: 20.0, + weapon_projectile_lifetime: 3.0, +}; + +#[derive(Clone, Copy, Debug)] +pub enum Action { + Left, + Right, + Up, + Down, + Damage, +} + +pub struct ActionEvent { + pub action: Action, + pub value: f32, +} + +pub struct Actions { + pub old_values: [f32; Self::MAX_ACTIONS], + pub new_values: [f32; Self::MAX_ACTIONS], +} + +impl Default for Actions { + fn default() -> Self { + Self { + old_values: [0.0; Self::MAX_ACTIONS], + new_values: [0.0; Self::MAX_ACTIONS], + } + } +} + +impl Actions { + const MAX_ACTIONS: usize = 256; + + fn is_active(&self, action: Action) -> bool { + self.new_values[action as usize] != 0.0 + } + + pub fn became_active_this_frame(&self, action: Action) -> bool { + self.new_values[action as usize] != 0.0 && self.old_values[action as usize] == 0.0 + } + + pub fn became_inactive_this_frame(&self, action: Action) -> bool { + self.new_values[action as usize] == 0.0 && self.old_values[action as usize] != 0.0 + } + + pub fn tick(&mut self, action_queue: &[ActionEvent]) { + self.old_values = self.new_values; + + for event in action_queue { + self.new_values[event.action as usize] = event.value; + } + } +} + +pub struct PlayerState { + pub position: Point3, + pub heading: Vec3, + pub weapon_cooldown: f32, +} + +impl PlayerState { + fn new() -> Self { + Self { + position: Point3::ZERO, + heading: vec3(SQRT_2, 0.0, -SQRT_2), + + weapon_cooldown: GAME_VARIABLES.weapon_cooldown, + } + } +} + +pub struct CameraState { + pub eye_offset: Vec3, + + pub shake: f32, + pub shake_offset: Vec3, + + pub position: Point3, + pub velocity: Vec3, +} + +impl Default for CameraState { + fn default() -> Self { + Self::new() + } +} + +impl CameraState { + pub fn new() -> Self { + let theta = HalfTurn::from(GAME_VARIABLES.camera_angle).as_f32(); + let hypotenuse = GAME_VARIABLES.camera_distance; + let height = sin_pi_f32(theta) * hypotenuse; + let base = (hypotenuse * hypotenuse - height * height).sqrt(); + + // Rotate camera + let one_on_sqrt2 = 1.0 / SQRT_2; + let eye_offset = vec3(-base * one_on_sqrt2, height, -base * one_on_sqrt2); + + Self { + eye_offset, + + shake: 0.0, + shake_offset: Vec3::ZERO, + + position: Point3::ZERO, + velocity: Vec3::ZERO, + } + } + + pub fn tick(&mut self, target: Point3, time: f32, delta_time: f32) { + if Point3::distance_sq(self.position, target) + > (GAME_VARIABLES.camera_deadzone * GAME_VARIABLES.camera_deadzone) + { + let (pos_x, vel_x) = simple_spring_damper_exact( + self.position.x, + self.velocity.x, + target.x, + GAME_VARIABLES.camera_damping, + delta_time, + ); + let (pos_z, vel_z) = simple_spring_damper_exact( + self.position.z, + self.velocity.z, + target.z, + GAME_VARIABLES.camera_damping, + delta_time, + ); + + self.position.z = pos_z; + self.position.x = pos_x; + self.velocity.x = vel_x; + self.velocity.z = vel_z; + } + + self.shake -= GAME_VARIABLES.camera_shake_decay * delta_time; + self.shake = clamp(self.shake, 0.0, 1.0); + + let t = time * GAME_VARIABLES.camera_shake_frequency; + let shake = GAME_VARIABLES.camera_shake_max_offset * self.shake * self.shake * self.shake; + + self.shake_offset.x = shake * perlin_noise3(0.0, t, 0.0); + self.shake_offset.z = shake * perlin_noise3(1.0, t, 0.0); + } + + pub fn camera_from_model(&self) -> Mat4 { + let position = self.position + self.shake_offset; + let eye = position + self.eye_offset; + Mat4::look_at(eye, position, Vec3::Y) + } +} + +#[derive(Clone, Copy, Default)] +#[repr(align(16))] +pub struct ArchetypeProjectileBlock { + pub position_x: [f32; Self::WIDTH], + pub position_z: [f32; Self::WIDTH], + pub velocity_x: [f32; Self::WIDTH], + pub velocity_z: [f32; Self::WIDTH], + pub lifetime: [f32; Self::WIDTH], +} + +impl ArchetypeProjectileBlock { + const WIDTH: usize = 8; +} + +#[derive(Default)] +pub struct ArchetypeProjectileChunk { + pub bitmap: [u8; Self::WIDTH], + pub blocks: [ArchetypeProjectileBlock; Self::WIDTH], +} + +impl ArchetypeProjectileChunk { + const WIDTH: usize = 8; + const LEN: usize = Self::WIDTH * ArchetypeProjectileBlock::WIDTH; +} + +pub struct ArchetypeProjectile { + pub bitmap_non_empty: [u64; ARCHTYPE_PROJECTILE_MAX / ArchetypeProjectileChunk::LEN / 64], + pub bitmap_non_full: [u64; ARCHTYPE_PROJECTILE_MAX / ArchetypeProjectileChunk::LEN / 64], + pub chunks: [ArchetypeProjectileChunk; ARCHTYPE_PROJECTILE_MAX / ArchetypeProjectileChunk::LEN], +} + +pub struct GameState { + pub rng: Pcg64, + + pub time: f32, + pub actions: Actions, + pub camera: CameraState, + pub player: PlayerState, + + pub archetype_projectile: Box, +} + +impl Default for GameState { + fn default() -> Self { + Self::new() + } +} + +impl GameState { + pub fn new() -> Self { + let mut archetype_projectile: Box = + unsafe { box_assume_init(zeroed_box()) }; + archetype_projectile.bitmap_non_full.fill(u64::MAX); + Self { + rng: Pcg64::new(), + time: 0.0, + actions: default(), + camera: CameraState::new(), + player: PlayerState::new(), + archetype_projectile, + } + } + + pub fn tick(&mut self, delta_time: f32, action_queue: &[ActionEvent]) { + let delta_time = delta_time * GAME_VARIABLES.game_speed; + self.time += delta_time; + + self.actions.tick(action_queue); + + if self.actions.became_active_this_frame(Action::Damage) { + self.camera.shake += 0.4; + } + + let movement_bitmap = self.actions.is_active(Action::Up) as usize + | (self.actions.is_active(Action::Down) as usize) << 1 + | (self.actions.is_active(Action::Left) as usize) << 2 + | (self.actions.is_active(Action::Right) as usize) << 3; + + // Pre-rotated values + const UP: Vec3 = vec3(SQRT_2, 0.0, SQRT_2); + const DOWN: Vec3 = vec3(-SQRT_2, 0.0, -SQRT_2); + const LEFT: Vec3 = vec3(SQRT_2, 0.0, -SQRT_2); + const RIGHT: Vec3 = vec3(-SQRT_2, 0.0, SQRT_2); + const UP_LEFT: Vec3 = vec3(1.0, 0.0, 0.0); + const UP_RIGHT: Vec3 = vec3(0.0, 0.0, 1.0); + const DOWN_LEFT: Vec3 = vec3(0.0, 0.0, -1.0); + const DOWN_RIGHT: Vec3 = vec3(-1.0, 0.0, 0.0); + + let movement = [ + // 0 0 0 0 + Vec3::ZERO, + // 0 0 0 1 + UP, + // 0 0 1 0 + DOWN, + // 0 0 1 1 + Vec3::ZERO, + // 0 1 0 0 + LEFT, + // 0 1 0 1 + UP_LEFT, + // 0 1 1 0 + DOWN_LEFT, + // 0 1 1 1 + LEFT, + // 1 0 0 0 + RIGHT, + // 1 0 0 1 + UP_RIGHT, + // 1 0 1 0 + DOWN_RIGHT, + // 1 0 1 1 + RIGHT, + // 1 1 0 0 + Vec3::ZERO, + // 1 1 0 1 + UP, + // 1 1 1 0 + DOWN, + // 1 1 1 1 + Vec3::ZERO, + ][movement_bitmap]; + + if movement != Vec3::ZERO { + self.player.heading = movement; + } + + let player_velocity = movement * GAME_VARIABLES.player_speed; + self.player.position += player_velocity * delta_time; + + self.camera + .tick(self.player.position, self.time, delta_time); + + self.player.weapon_cooldown -= delta_time; + + if self.player.weapon_cooldown <= 0.0 { + // fire! + for _ in 0..32 { + let [x, y] = self.rng.next_uniform_unit_circle_f32(); + let direction = vec3(x, 0.0, y); + let velocity = player_velocity + direction * GAME_VARIABLES.weapon_projectile_speed; + self.spawn_projectile( + self.player.position, + velocity, + GAME_VARIABLES.weapon_projectile_lifetime, + ); + } + + self.player.weapon_cooldown = GAME_VARIABLES.weapon_cooldown; + } + + for (bitmap_base, (bitmap_non_empty_word, bitmap_non_full_word)) in self + .archetype_projectile + .bitmap_non_empty + .iter_mut() + .zip(self.archetype_projectile.bitmap_non_full.iter_mut()) + .enumerate() + { + for i in BitIter::new(std::iter::once(*bitmap_non_empty_word)) { + let chunk_index = bitmap_base * 64 + i; + let chunk = &mut self.archetype_projectile.chunks[chunk_index]; + + for (bitmap, block) in chunk.bitmap.iter_mut().zip(chunk.blocks.iter_mut()) { + if *bitmap == 0 { + continue; + } + + let old_bitmap = *bitmap; + + for j in 0..8 { + if old_bitmap & (1 << j) == 0 { + continue; + } + + block.position_x[j] += block.velocity_x[j] * delta_time; + block.position_z[j] += block.velocity_z[j] * delta_time; + block.lifetime[j] -= delta_time; + let projectile_dead = block.lifetime[j] <= 0.0; + + *bitmap &= !((projectile_dead as u8) << j); + } + } + + let non_empty = chunk.bitmap.iter().any(|&x| x != 0); + let non_full = chunk.bitmap.iter().any(|&x| x != u8::MAX); + + *bitmap_non_empty_word = + (*bitmap_non_empty_word & !(1 << i)) | (non_empty as u64) << i; + *bitmap_non_full_word = + (*bitmap_non_full_word & !(1 << i)) | (non_full as u64) << i; + } + } + } + + pub fn spawn_projectile(&mut self, position: Point3, velocity: Vec3, lifetime: f32) { + let projectile = &mut self.archetype_projectile; + let bitmap_non_full = &mut projectile.bitmap_non_full; + let bitmap_non_empty = &mut projectile.bitmap_non_empty; + + let chunk_index = BitIter::new(bitmap_non_full.iter().copied()) + .next() + .unwrap(); + let chunk = &mut projectile.chunks[chunk_index]; + + let block_index = chunk + .bitmap + .iter() + .copied() + .position(|x| x != u8::MAX) + .unwrap(); + let block = &mut chunk.blocks[block_index]; + + let j = BitIter::new(std::iter::once(!chunk.bitmap[block_index])) + .next() + .unwrap(); + + block.position_x[j] = position.x; + block.position_z[j] = position.z; + block.velocity_x[j] = velocity.x; + block.velocity_z[j] = velocity.z; + block.lifetime[j] = lifetime; + + chunk.bitmap[block_index] |= 1 << j; + let block_non_full = chunk.bitmap[block_index] != !0; + bitmap_non_empty[chunk_index / 64] |= 1 << (chunk_index % 64); + bitmap_non_full[chunk_index / 64] = (bitmap_non_full[chunk_index / 64] + & !(1 << (chunk_index % 64))) + | (block_non_full as u64) << (chunk_index % 64); + } +} diff --git a/title/shark/src/main.rs b/title/shark/src/main.rs index a5e84c9..c09833e 100644 --- a/title/shark/src/main.rs +++ b/title/shark/src/main.rs @@ -3,6 +3,7 @@ use std::ops::Index; use std::path::Path; use std::time::{Duration, Instant}; +use game::{Action, ActionEvent, GameState}; use narcissus_core::{dds, Widen}; use shark_shaders::pipelines::{ @@ -17,7 +18,7 @@ use renderdoc_sys as rdoc; use fonts::{FontFamily, Fonts}; use helpers::load_obj; use narcissus_app::{create_app, Event, Key, WindowDesc}; -use narcissus_core::{box_assume_init, default, rand::Pcg64, zeroed_box, BitIter}; +use narcissus_core::{default, BitIter}; use narcissus_font::{FontCollection, GlyphCache, HorizontalMetrics}; use narcissus_gpu::{ create_device, Access, Bind, BufferImageCopy, BufferUsageFlags, ClearValue, CmdEncoder, @@ -28,423 +29,15 @@ use narcissus_gpu::{ SwapchainConfigurator, SwapchainImage, ThreadToken, TypedBind, Viewport, }; use narcissus_image as image; -use narcissus_maths::{ - clamp, perlin_noise3, sin_cos_pi_f32, sin_pi_f32, vec2, vec3, Affine3, Deg, HalfTurn, Mat3, - Mat4, Point3, Vec2, Vec3, -}; -use spring::simple_spring_damper_exact; +use narcissus_maths::{sin_cos_pi_f32, vec2, vec3, Affine3, HalfTurn, Mat3, Mat4, Vec2, Vec3}; mod fonts; +pub mod game; mod helpers; pub mod microshades; mod spring; -const SQRT_2: f32 = 0.70710677; const GLYPH_CACHE_SIZE: usize = 1024; -const ARCHTYPE_PROJECTILE_MAX: usize = 65536; - -struct GameVariables { - game_speed: f32, - - camera_distance: f32, - camera_angle: Deg, - camera_damping: f32, - camera_deadzone: f32, - camera_shake_decay: f32, - camera_shake_max_offset: f32, - camera_shake_frequency: f32, - - player_speed: f32, - - weapon_cooldown: f32, - weapon_projectile_speed: f32, - weapon_projectile_lifetime: f32, -} - -static GAME_VARIABLES: GameVariables = GameVariables { - game_speed: 1.0, - - camera_distance: 45.0, - camera_angle: Deg::new(60.0), - camera_damping: 35.0, - camera_deadzone: 0.1, - camera_shake_decay: 2.0, - camera_shake_max_offset: 2.0, - camera_shake_frequency: 11.0, - - player_speed: 10.0, - - weapon_cooldown: 0.2, - weapon_projectile_speed: 20.0, - weapon_projectile_lifetime: 3.0, -}; - -#[derive(Clone, Copy, Debug)] -pub enum Action { - Left, - Right, - Up, - Down, - Damage, -} - -pub struct ActionEvent { - action: Action, - value: f32, -} - -pub struct Actions { - old_values: [f32; Self::MAX_ACTIONS], - new_values: [f32; Self::MAX_ACTIONS], -} - -impl Actions { - const MAX_ACTIONS: usize = 256; - - fn new() -> Self { - Self { - old_values: [0.0; Self::MAX_ACTIONS], - new_values: [0.0; Self::MAX_ACTIONS], - } - } - - fn is_active(&self, action: Action) -> bool { - self.new_values[action as usize] != 0.0 - } - - pub fn became_active_this_frame(&self, action: Action) -> bool { - self.new_values[action as usize] != 0.0 && self.old_values[action as usize] == 0.0 - } - - pub fn became_inactive_this_frame(&self, action: Action) -> bool { - self.new_values[action as usize] == 0.0 && self.old_values[action as usize] != 0.0 - } - - pub fn tick(&mut self, action_queue: &[ActionEvent]) { - self.old_values = self.new_values; - - for event in action_queue { - self.new_values[event.action as usize] = event.value; - } - } -} - -struct PlayerState { - position: Point3, - heading: Vec3, - - weapon_cooldown: f32, -} - -impl PlayerState { - fn new() -> Self { - Self { - position: Point3::ZERO, - heading: vec3(SQRT_2, 0.0, -SQRT_2), - - weapon_cooldown: GAME_VARIABLES.weapon_cooldown, - } - } -} - -struct CameraState { - eye_offset: Vec3, - - shake: f32, - shake_offset: Vec3, - - position: Point3, - velocity: Vec3, -} - -impl CameraState { - fn new() -> Self { - let theta = HalfTurn::from(GAME_VARIABLES.camera_angle).as_f32(); - let hypotenuse = GAME_VARIABLES.camera_distance; - let height = sin_pi_f32(theta) * hypotenuse; - let base = (hypotenuse * hypotenuse - height * height).sqrt(); - - // Rotate camera - let one_on_sqrt2 = 1.0 / 2.0_f32.sqrt(); - let eye_offset = vec3(-base * one_on_sqrt2, height, -base * one_on_sqrt2); - - Self { - eye_offset, - - shake: 0.0, - shake_offset: Vec3::ZERO, - - position: Point3::ZERO, - velocity: Vec3::ZERO, - } - } - - fn tick(&mut self, target: Point3, time: f32, delta_time: f32) { - if Point3::distance_sq(self.position, target) - > (GAME_VARIABLES.camera_deadzone * GAME_VARIABLES.camera_deadzone) - { - let (pos_x, vel_x) = simple_spring_damper_exact( - self.position.x, - self.velocity.x, - target.x, - GAME_VARIABLES.camera_damping, - delta_time, - ); - let (pos_z, vel_z) = simple_spring_damper_exact( - self.position.z, - self.velocity.z, - target.z, - GAME_VARIABLES.camera_damping, - delta_time, - ); - - self.position.z = pos_z; - self.position.x = pos_x; - self.velocity.x = vel_x; - self.velocity.z = vel_z; - } - - self.shake -= GAME_VARIABLES.camera_shake_decay * delta_time; - self.shake = clamp(self.shake, 0.0, 1.0); - - let t = time * GAME_VARIABLES.camera_shake_frequency; - let shake = GAME_VARIABLES.camera_shake_max_offset * self.shake * self.shake * self.shake; - - self.shake_offset.x = shake * perlin_noise3(0.0, t, 0.0); - self.shake_offset.z = shake * perlin_noise3(1.0, t, 0.0); - } - - fn camera_from_model(&self) -> Mat4 { - let position = self.position + self.shake_offset; - let eye = position + self.eye_offset; - Mat4::look_at(eye, position, Vec3::Y) - } -} - -#[derive(Clone, Copy, Default)] -#[repr(align(16))] -struct ArchetypeProjectileBlock { - position_x: [f32; Self::WIDTH], - position_z: [f32; Self::WIDTH], - velocity_x: [f32; Self::WIDTH], - velocity_z: [f32; Self::WIDTH], - lifetime: [f32; Self::WIDTH], -} - -impl ArchetypeProjectileBlock { - const WIDTH: usize = 8; -} - -#[derive(Default)] -struct ArchetypeProjectileChunk { - bitmap: [u8; Self::WIDTH], - blocks: [ArchetypeProjectileBlock; Self::WIDTH], -} - -impl ArchetypeProjectileChunk { - const WIDTH: usize = 8; - const LEN: usize = Self::WIDTH * ArchetypeProjectileBlock::WIDTH; -} - -struct ArchetypeProjectile { - bitmap_non_empty: [u64; ARCHTYPE_PROJECTILE_MAX / ArchetypeProjectileChunk::LEN / 64], - bitmap_non_full: [u64; ARCHTYPE_PROJECTILE_MAX / ArchetypeProjectileChunk::LEN / 64], - chunks: [ArchetypeProjectileChunk; ARCHTYPE_PROJECTILE_MAX / ArchetypeProjectileChunk::LEN], -} - -struct GameState { - rng: Pcg64, - - time: f32, - actions: Actions, - camera: CameraState, - player: PlayerState, - - archetype_projectile: Box, -} - -impl GameState { - fn new() -> Self { - let mut archetype_projectile: Box = - unsafe { box_assume_init(zeroed_box()) }; - archetype_projectile.bitmap_non_full.fill(u64::MAX); - Self { - rng: Pcg64::new(), - time: 0.0, - actions: Actions::new(), - camera: CameraState::new(), - player: PlayerState::new(), - archetype_projectile, - } - } - - fn tick(&mut self, delta_time: f32, action_queue: &[ActionEvent]) { - let delta_time = delta_time * GAME_VARIABLES.game_speed; - self.time += delta_time; - - self.actions.tick(action_queue); - - if self.actions.became_active_this_frame(Action::Damage) { - self.camera.shake += 0.4; - } - - let movement_bitmap = self.actions.is_active(Action::Up) as usize - | (self.actions.is_active(Action::Down) as usize) << 1 - | (self.actions.is_active(Action::Left) as usize) << 2 - | (self.actions.is_active(Action::Right) as usize) << 3; - - // Pre-rotated values - const UP: Vec3 = vec3(SQRT_2, 0.0, SQRT_2); - const DOWN: Vec3 = vec3(-SQRT_2, 0.0, -SQRT_2); - const LEFT: Vec3 = vec3(SQRT_2, 0.0, -SQRT_2); - const RIGHT: Vec3 = vec3(-SQRT_2, 0.0, SQRT_2); - const UP_LEFT: Vec3 = vec3(1.0, 0.0, 0.0); - const UP_RIGHT: Vec3 = vec3(0.0, 0.0, 1.0); - const DOWN_LEFT: Vec3 = vec3(0.0, 0.0, -1.0); - const DOWN_RIGHT: Vec3 = vec3(-1.0, 0.0, 0.0); - - let movement = [ - // 0 0 0 0 - Vec3::ZERO, - // 0 0 0 1 - UP, - // 0 0 1 0 - DOWN, - // 0 0 1 1 - Vec3::ZERO, - // 0 1 0 0 - LEFT, - // 0 1 0 1 - UP_LEFT, - // 0 1 1 0 - DOWN_LEFT, - // 0 1 1 1 - LEFT, - // 1 0 0 0 - RIGHT, - // 1 0 0 1 - UP_RIGHT, - // 1 0 1 0 - DOWN_RIGHT, - // 1 0 1 1 - RIGHT, - // 1 1 0 0 - Vec3::ZERO, - // 1 1 0 1 - UP, - // 1 1 1 0 - DOWN, - // 1 1 1 1 - Vec3::ZERO, - ][movement_bitmap]; - - if movement != Vec3::ZERO { - self.player.heading = movement; - } - - let player_velocity = movement * GAME_VARIABLES.player_speed; - self.player.position += player_velocity * delta_time; - - self.camera - .tick(self.player.position, self.time, delta_time); - - self.player.weapon_cooldown -= delta_time; - - if self.player.weapon_cooldown <= 0.0 { - // fire! - for _ in 0..32 { - let [x, y] = self.rng.next_uniform_unit_circle_f32(); - let direction = vec3(x, 0.0, y); - let velocity = player_velocity + direction * GAME_VARIABLES.weapon_projectile_speed; - self.spawn_projectile( - self.player.position, - velocity, - GAME_VARIABLES.weapon_projectile_lifetime, - ); - } - - self.player.weapon_cooldown = GAME_VARIABLES.weapon_cooldown; - } - - for (bitmap_base, (bitmap_non_empty_word, bitmap_non_full_word)) in self - .archetype_projectile - .bitmap_non_empty - .iter_mut() - .zip(self.archetype_projectile.bitmap_non_full.iter_mut()) - .enumerate() - { - for i in BitIter::new(std::iter::once(*bitmap_non_empty_word)) { - let chunk_index = bitmap_base * 64 + i; - let chunk = &mut self.archetype_projectile.chunks[chunk_index]; - - for (bitmap, block) in chunk.bitmap.iter_mut().zip(chunk.blocks.iter_mut()) { - if *bitmap == 0 { - continue; - } - - let old_bitmap = *bitmap; - - for j in 0..8 { - if old_bitmap & (1 << j) == 0 { - continue; - } - - block.position_x[j] += block.velocity_x[j] * delta_time; - block.position_z[j] += block.velocity_z[j] * delta_time; - block.lifetime[j] -= delta_time; - let projectile_dead = block.lifetime[j] <= 0.0; - - *bitmap &= !((projectile_dead as u8) << j); - } - } - - let non_empty = chunk.bitmap.iter().any(|&x| x != 0); - let non_full = chunk.bitmap.iter().any(|&x| x != u8::MAX); - - *bitmap_non_empty_word = - (*bitmap_non_empty_word & !(1 << i)) | (non_empty as u64) << i; - *bitmap_non_full_word = - (*bitmap_non_full_word & !(1 << i)) | (non_full as u64) << i; - } - } - } - - fn spawn_projectile(&mut self, position: Point3, velocity: Vec3, lifetime: f32) { - let projectile = &mut self.archetype_projectile; - let bitmap_non_full = &mut projectile.bitmap_non_full; - let bitmap_non_empty = &mut projectile.bitmap_non_empty; - - let chunk_index = BitIter::new(bitmap_non_full.iter().copied()) - .next() - .unwrap(); - let chunk = &mut projectile.chunks[chunk_index]; - - let block_index = chunk - .bitmap - .iter() - .copied() - .position(|x| x != u8::MAX) - .unwrap(); - let block = &mut chunk.blocks[block_index]; - - let j = BitIter::new(std::iter::once(!chunk.bitmap[block_index])) - .next() - .unwrap(); - - block.position_x[j] = position.x; - block.position_z[j] = position.z; - block.velocity_x[j] = velocity.x; - block.velocity_z[j] = velocity.z; - block.lifetime[j] = lifetime; - - chunk.bitmap[block_index] |= 1 << j; - let block_non_full = chunk.bitmap[block_index] != !0; - bitmap_non_empty[chunk_index / 64] |= 1 << (chunk_index % 64); - bitmap_non_full[chunk_index / 64] = (bitmap_non_full[chunk_index / 64] - & !(1 << (chunk_index % 64))) - | (block_non_full as u64) << (chunk_index % 64); - } -} struct UiState<'a> { fonts: &'a Fonts<'a>,