From 47cdea0c8a5d10a2440ca6270a975d560aa3642d Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Thu, 1 Feb 2024 00:04:07 -0800
Subject: [PATCH] data structure rewrite

---
 Cargo.lock                 |  46 ++++++++
 Cargo.toml                 |   1 +
 src/bvh.rs                 |  46 ++++----
 src/gameplay_attributes.rs |  18 +--
 src/gameplay_modes.rs      | 233 +++++++++++++++++++++++++++++++++----
 src/gameplay_style.rs      | 121 +++++++++++--------
 src/integer.rs             |   9 +-
 src/lib.rs                 |   2 +
 src/map.rs                 |  13 +++
 src/model.rs               | 117 +++++++++++++++----
 src/updatable.rs           |  56 +++++++++
 11 files changed, 535 insertions(+), 127 deletions(-)
 create mode 100644 src/map.rs
 create mode 100644 src/updatable.rs

diff --git a/Cargo.lock b/Cargo.lock
index 5be6aee..c407d0e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,9 +8,55 @@ version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
 
+[[package]]
+name = "id"
+version = "0.1.0"
+source = "git+https://git.itzana.me/Quaternions/id?rev=1f710976cc786c8853dab73d6e1cee53158deeb0#1f710976cc786c8853dab73d6e1cee53158deeb0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
 [[package]]
 name = "strafesnet_common"
 version = "0.1.0"
 dependencies = [
  "glam",
+ "id",
 ]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
diff --git a/Cargo.toml b/Cargo.toml
index a3628d1..d671ba1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,3 +7,4 @@ edition = "2021"
 
 [dependencies]
 glam = "0.25.0"
+id = { git = "https://git.itzana.me/Quaternions/id", rev = "1f710976cc786c8853dab73d6e1cee53158deeb0" }
diff --git a/src/bvh.rs b/src/bvh.rs
index f4c9bc6..5235790 100644
--- a/src/bvh.rs
+++ b/src/bvh.rs
@@ -9,23 +9,24 @@ use crate::aabb::Aabb;
 //start with bisection into octrees because a bad bvh is still 1000x better than no bvh
 //sort the centerpoints on each axis (3 lists)
 //bv is put into octant based on whether it is upper or lower in each list
-enum BvhNodeContent{
-	Branch(Vec<BvhNode>),
-	Leaf(usize),
+
+enum BvhNodeContent<T>{
+	Branch(Vec<BvhNode<T>>),
+	Leaf(T),
 }
-impl Default for BvhNodeContent{
+impl<T> Default for BvhNodeContent<T>{
 	fn default()->Self{
 		Self::Branch(Vec::new())
 	}
 }
 #[derive(Default)]
-pub struct BvhNode{
-	content:BvhNodeContent,
+pub struct BvhNode<T>{
+	content:BvhNodeContent<T>,
 	aabb:Aabb,
 }
 
-impl BvhNode{
-	pub fn the_tester<F:FnMut(usize)>(&self,aabb:&Aabb,f:&mut F){
+impl<T:Copy+Eq+std::hash::Hash> BvhNode<T>{
+	pub fn the_tester<F:FnMut(T)>(&self,aabb:&Aabb,f:&mut F){
 		match &self.content{
 			&BvhNodeContent::Leaf(model)=>f(model),
 			BvhNodeContent::Branch(children)=>for child in children{
@@ -41,13 +42,13 @@ impl BvhNode{
 	}
 }
 
-pub fn generate_bvh(boxen:Vec<Aabb>)->BvhNode{
-	generate_bvh_node(boxen.into_iter().enumerate().collect())
+pub fn generate_bvh<T:Copy+Eq+std::hash::Hash>(boxen:Vec<(T,Aabb)>)->BvhNode<T>{
+	generate_bvh_node(boxen,false)
 }
 
-fn generate_bvh_node(boxen:Vec<(usize,Aabb)>)->BvhNode{
+fn generate_bvh_node<T:Copy+Eq+std::hash::Hash>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
 	let n=boxen.len();
-	if n<20{
+	if force||n<20{
 		let mut aabb=Aabb::default();
 		let nodes=boxen.into_iter().map(|b|{
 			aabb.join(&b.1);
@@ -110,14 +111,19 @@ fn generate_bvh_node(boxen:Vec<(usize,Aabb)>)->BvhNode{
 			list_list[list_id].push((i,aabb));
 		}
 		let mut aabb=Aabb::default();
-		let children=list_list.into_iter().map(|b|{
-			let node=generate_bvh_node(b);
-			aabb.join(&node.aabb);
-			node
-		}).collect();
-		BvhNode{
-			content:BvhNodeContent::Branch(children),
-			aabb,
+		if list_list.len()==1{
+			generate_bvh_node(list_list.remove(0),true)
+		}else{
+			BvhNode{
+				content:BvhNodeContent::Branch(
+					list_list.into_iter().map(|b|{
+						let node=generate_bvh_node(b,false);
+						aabb.join(&node.aabb);
+						node
+					}).collect()
+				),
+				aabb,
+			}
 		}
 	}
 }
diff --git a/src/gameplay_attributes.rs b/src/gameplay_attributes.rs
index 6756e45..1e919f9 100644
--- a/src/gameplay_attributes.rs
+++ b/src/gameplay_attributes.rs
@@ -1,3 +1,4 @@
+use crate::model;
 use crate::integer::{Time,Planar64,Planar64Vec3};
 
 //you have this effect while in contact
@@ -8,8 +9,9 @@ pub struct ContactingLadder{
 #[derive(Clone,Hash,Eq,PartialEq)]
 pub enum ContactingBehaviour{
 	Surf,
-	Cling,//usable as a zipline, or other weird and wonderful things
 	Ladder(ContactingLadder),
+	NoJump,
+	Cling,//usable as a zipline, or other weird and wonderful things
 	Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32
 }
 //you have this effect while intersecting
@@ -54,7 +56,7 @@ pub enum SetTrajectory{
 	Velocity(Planar64Vec3),//SetVelocity
 }
 impl SetTrajectory{
-	fn is_velocity(&self)->bool{
+	pub const fn is_absolute(&self)->bool{
 		match self{
 			SetTrajectory::AirTime(_)
 			|SetTrajectory::Height(_)
@@ -76,7 +78,7 @@ pub struct Wormhole{
 	//destination does not need to be another wormhole
 	//this defines a one way portal to a destination model transform
 	//two of these can create a two way wormhole
-	pub destination_model_id:u32,
+	pub destination_model:model::ModelId,
 	//(position,angles)*=origin.transform.inverse()*destination.transform
 }
 //attributes listed in order of handling
@@ -88,14 +90,14 @@ pub struct GeneralAttributes{
 	pub accelerator:Option<Accelerator>,
 }
 impl GeneralAttributes{
-	pub fn any(&self)->bool{
+	pub const fn any(&self)->bool{
 		self.booster.is_some()
 		||self.trajectory.is_some()
 		||self.wormhole.is_some()
 		||self.accelerator.is_some()
 	}
 	pub fn is_wrcp(&self)->bool{
-		self.trajectory.as_ref().map_or(false,|t|t.is_velocity())
+		self.trajectory.as_ref().map_or(false,|t|t.is_absolute())
 		/*
 		&&match &self.teleport_behaviour{
 			Some(TeleportBehaviour::StageElement(
@@ -117,7 +119,7 @@ pub struct ContactingAttributes{
 	pub contact_behaviour:Option<ContactingBehaviour>,
 }
 impl ContactingAttributes{
-	pub fn any(&self)->bool{
+	pub const fn any(&self)->bool{
 		self.contact_behaviour.is_some()
 	}
 }
@@ -126,11 +128,13 @@ pub struct IntersectingAttributes{
 	pub water:Option<IntersectingWater>,
 }
 impl IntersectingAttributes{
-	pub fn any(&self)->bool{
+	pub const fn any(&self)->bool{
 		self.water.is_some()
 	}
 }
+#[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)]
 pub struct CollisionAttributesId(u32);
+#[derive(Clone,Hash,Eq,PartialEq)]
 pub enum CollisionAttributes{
 	Decoration,//visual only
 	Contact{//track whether you are contacting the object
diff --git a/src/gameplay_modes.rs b/src/gameplay_modes.rs
index a06fbb2..7b4ebb9 100644
--- a/src/gameplay_modes.rs
+++ b/src/gameplay_modes.rs
@@ -1,14 +1,38 @@
 use std::collections::{HashSet,HashMap};
 use crate::model::ModelId;
 use crate::gameplay_style;
+use crate::updatable::Updatable;
 
+#[derive(Clone)]
 pub struct StageElement{
-	stage:StageId,//which stage spawn to send to
+	stage_id:StageId,//which stage spawn to send to
 	force:bool,//allow setting to lower spawn id i.e. 7->3
 	behaviour:StageElementBehaviour
 }
+impl StageElement{
+	#[inline]
+	pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour)->Self{
+		Self{
+			stage_id,
+			force,
+			behaviour,
+		}
+	}
+	#[inline]
+	pub const fn stage_id(&self)->StageId{
+		self.stage_id
+	}
+	#[inline]
+	pub const fn force(&self)->bool{
+		self.force
+	}
+	#[inline]
+	pub const fn behaviour(&self)->StageElementBehaviour{
+		self.behaviour
+	}
+}
 
-#[derive(Clone,Hash,Eq,PartialEq)]
+#[derive(Clone,Copy,Hash,Eq,PartialEq)]
 pub enum StageElementBehaviour{
 	SpawnAt,//must be standing on top to get effect. except cancollide false
 	Trigger,
@@ -20,51 +44,150 @@ pub enum StageElementBehaviour{
 	Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both.
 }
 
+#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
+pub struct CheckpointId(u32);
+impl CheckpointId{
+	pub const FIRST:Self=Self(0);
+}
+#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
 pub struct StageId(u32);
+impl StageId{
+	pub const FIRST:Self=Self(0);
+}
+#[derive(Clone)]
 pub struct Stage{
 	spawn:ModelId,
-	//other behaviour models of this stage can have
-	ordered_checkpoints:Vec<ModelId>,
+	//open world support lol
+	ordered_checkpoints_count:u32,
+	unordered_checkpoints_count:u32,
+	//currently loaded checkpoint models
+	ordered_checkpoints:HashMap<CheckpointId,ModelId>,
 	unordered_checkpoints:HashSet<ModelId>,
 }
-
-#[derive(Clone,Hash,Eq,PartialEq)]
-pub enum ZoneBehaviour{
-	Finish,
-	Anitcheat,
+impl Stage{
+	pub fn new(spawn:ModelId)->Self{
+		Self{
+			spawn,
+			ordered_checkpoints_count:0,
+			unordered_checkpoints_count:0,
+			ordered_checkpoints:HashMap::new(),
+			unordered_checkpoints:HashSet::new(),
+		}
+	}
+	#[inline]
+	pub const fn spawn(&self)->ModelId{
+		self.spawn
+	}
+	#[inline]
+	pub const fn is_empty(&self)->bool{
+		self.is_complete(0,0)
+	}
+	#[inline]
+	pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{
+		self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count
+	}
+	#[inline]
+	pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{
+		self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint)
+	}
+	#[inline]
+	pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{
+		self.unordered_checkpoints.contains(&model_id)
+	}
 }
+#[derive(Default)]
+pub struct StageUpdate{
+	//other behaviour models of this stage can have
+	ordered_checkpoints:HashMap<CheckpointId,ModelId>,
+	unordered_checkpoints:HashSet<ModelId>,
+}
+impl Updatable<StageUpdate> for Stage{
+	fn update(&mut self,update:StageUpdate){
+		self.ordered_checkpoints.extend(update.ordered_checkpoints);
+		self.unordered_checkpoints.extend(update.unordered_checkpoints);
+	}
+}
+
+#[derive(Clone,Copy,Hash,Eq,PartialEq)]
+pub enum Zone{
+	Start,
+	Finish,
+	Anticheat,
+}
+#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
 pub struct ModeId(u32);
+impl ModeId{
+	pub const MAIN:Self=Self(0);
+	pub const BONUS:Self=Self(1);
+}
+#[derive(Clone)]
 pub struct Mode{
 	style:gameplay_style::StyleModifiers,
-	start:ModelId,
-	zones:HashMap<ModelId,ZoneBehaviour>,
-	stages:Vec<Stage>,
+	start:ModelId,//when you press reset you go here
+	zones:HashMap<ModelId,Zone>,
+	stages:Vec<Stage>,//when you load the map you go to stages[0].spawn
 	//mutually exlusive stage element behaviour
 	elements:HashMap<ModelId,StageElement>,
 	jump_limit:HashMap<ModelId,u32>,
 }
 impl Mode{
+	pub fn new(style:gameplay_style::StyleModifiers,start:ModelId)->Self{
+		Self{
+			style,
+			start,
+			zones:HashMap::new(),
+			stages:Vec::new(),
+			elements:HashMap::new(),
+			jump_limit:HashMap::new(),
+		}
+	}
+	pub const fn get_start(&self)->ModelId{
+		self.start
+	}
+	pub const fn get_style(&self)->&gameplay_style::StyleModifiers{
+		&self.style
+	}
+	pub fn push_stage(&mut self,stage:Stage){
+		self.stages.push(stage)
+	}
+	pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
+		self.stages.get_mut(stage.0 as usize)
+	}
 	pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
 		self.stages.get(stage.0 as usize).map(|s|s.spawn)
 	}
+	pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
+		self.zones.get(&model_id)
+	}
+	pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
+		self.stages.get(stage_id.0 as usize)
+	}
+	pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
+		self.elements.get(&model_id)
+	}
+	pub fn get_jump_limit(&self,model_id:ModelId)->Option<u32>{
+		self.jump_limit.get(&model_id).copied()
+	}
+	//TODO: put this in the SNF
 	pub fn denormalize_data(&mut self){
 		//expand and index normalized data
+		self.zones.insert(self.start,Zone::Start);
 		for (stage_id,stage) in self.stages.iter().enumerate(){
 			self.elements.insert(stage.spawn,StageElement{
-				stage:StageId(stage_id as u32),
+				stage_id:StageId(stage_id as u32),
 				force:false,
 				behaviour:StageElementBehaviour::SpawnAt,
 			});
-			for &model_id in &stage.ordered_checkpoints{
-				self.elements.insert(model_id,StageElement{
-					stage:StageId(stage_id as u32),
+			for (_,&model) in &stage.ordered_checkpoints{
+				self.elements.insert(model,StageElement{
+					stage_id:StageId(stage_id as u32),
 					force:false,
 					behaviour:StageElementBehaviour::Checkpoint,
 				});
 			}
-			for &model_id in &stage.unordered_checkpoints{
-				self.elements.insert(model_id,StageElement{
-					stage:StageId(stage_id as u32),
+			for &model in &stage.unordered_checkpoints{
+				self.elements.insert(model,StageElement{
+					stage_id:StageId(stage_id as u32),
 					force:false,
 					behaviour:StageElementBehaviour::Checkpoint,
 				});
@@ -72,19 +195,81 @@ impl Mode{
 		}
 	}
 }
-
+//this would be nice as a macro
 #[derive(Default)]
+pub struct ModeUpdate{
+	zones:HashMap<ModelId,Zone>,
+	stages:HashMap<StageId,StageUpdate>,
+	//mutually exlusive stage element behaviour
+	elements:HashMap<ModelId,StageElement>,
+	jump_limit:HashMap<ModelId,u32>,
+}
+impl Updatable<ModeUpdate> for Mode{
+	fn update(&mut self,update:ModeUpdate){
+		self.zones.extend(update.zones);
+		for (stage,stage_update) in update.stages{
+			if let Some(stage)=self.stages.get_mut(stage.0 as usize){
+				stage.update(stage_update);
+			}
+		}
+		self.elements.extend(update.elements);
+		self.jump_limit.extend(update.jump_limit);
+	}
+}
+impl ModeUpdate{
+	pub fn zone(model_id:ModelId,zone:Zone)->Self{
+		let mut mu=Self::default();
+		mu.zones.insert(model_id,zone);
+		mu
+	}
+	pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
+		let mut mu=Self::default();
+		mu.stages.insert(stage_id,stage_update);
+		mu
+	}
+	pub fn element(model_id:ModelId,element:StageElement)->Self{
+		let mut mu=Self::default();
+		mu.elements.insert(model_id,element);
+		mu
+	}
+	pub fn jump_limit(model_id:ModelId,jump_limit:u32)->Self{
+		let mut mu=Self::default();
+		mu.jump_limit.insert(model_id,jump_limit);
+		mu
+	}
+	pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
+		for (_,stage_element) in self.elements.iter_mut(){
+			stage_element.stage_id=f(stage_element.stage_id);
+		}
+	}
+}
+
+#[derive(Default,Clone)]
 pub struct Modes{
 	modes:Vec<Mode>,
 }
 impl Modes{
-	pub fn clear(&mut self){
-		self.modes.clear();
+	pub fn new(modes:Vec<Mode>)->Self{
+		Self{
+			modes,
+		}
+	}
+	pub fn push_mode(&mut self,mode:Mode){
+		self.modes.push(mode)
 	}
 	pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
 		self.modes.get(mode.0 as usize)
 	}
-	pub fn insert(&mut self,mode:Mode){
-		self.modes.push(mode);
+}
+pub struct ModesUpdate{
+	modes:HashMap<ModeId,ModeUpdate>,
+}
+impl Updatable<ModesUpdate> for Modes{
+	fn update(&mut self,update:ModesUpdate){
+		for (mode,mode_update) in update.modes{
+			if let Some(mode)=self.modes.get_mut(mode.0 as usize){
+				mode.update(mode_update);
+			}
+		}
 	}
 }
\ No newline at end of file
diff --git a/src/gameplay_style.rs b/src/gameplay_style.rs
index 318fb10..4a6e179 100644
--- a/src/gameplay_style.rs
+++ b/src/gameplay_style.rs
@@ -1,28 +1,29 @@
-const VALVE_SCALE:i64=16;
+const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
 
 use crate::integer::{Time,Ratio64,Planar64,Planar64Vec3};
 
+#[derive(Clone)]
 pub struct StyleModifiers{
-	controls_used:u32,//controls which are allowed to pass into gameplay
-	controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style)
-	strafe:Option<StrafeSettings>,
-	jump_impulse:JumpImpulse,
-	jump_calculation:JumpCalculation,
-	static_friction:Planar64,
-	kinetic_friction:Planar64,
-	walk_speed:Planar64,
-	walk_accel:Planar64,
-	ladder_speed:Planar64,
-	ladder_accel:Planar64,
-	ladder_dot:Planar64,
-	swim_speed:Planar64,
-	mass:Planar64,
-	mv:Planar64,
-	surf_slope:Option<Planar64>,
-	rocket_force:Option<Planar64>,
-	gravity:Planar64Vec3,
-	hitbox:Hitbox,
-	camera_offset:Planar64Vec3,
+	pub controls_used:u32,//controls which are allowed to pass into gameplay
+	pub controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style)
+	pub strafe:Option<StrafeSettings>,
+	pub jump_impulse:JumpImpulse,
+	pub jump_calculation:JumpCalculation,
+	pub static_friction:Planar64,
+	pub kinetic_friction:Planar64,
+	pub walk_speed:Planar64,
+	pub walk_accel:Planar64,
+	pub ladder_speed:Planar64,
+	pub ladder_accel:Planar64,
+	pub ladder_dot:Planar64,
+	pub swim_speed:Planar64,
+	pub mass:Planar64,
+	pub mv:Planar64,
+	pub surf_slope:Option<Planar64>,
+	pub rocket_force:Option<Planar64>,
+	pub gravity:Planar64Vec3,
+	pub hitbox:Hitbox,
+	pub camera_offset:Planar64Vec3,
 }
 impl std::default::Default for StyleModifiers{
 	fn default()->Self{
@@ -30,18 +31,18 @@ impl std::default::Default for StyleModifiers{
 	}
 }
 impl StyleModifiers{
-	const CONTROL_MOVEFORWARD:u32=0b00000001;
-	const CONTROL_MOVEBACK:u32=0b00000010;
-	const CONTROL_MOVERIGHT:u32=0b00000100;
-	const CONTROL_MOVELEFT:u32=0b00001000;
-	const CONTROL_MOVEUP:u32=0b00010000;
-	const CONTROL_MOVEDOWN:u32=0b00100000;
-	const CONTROL_JUMP:u32=0b01000000;
-	const CONTROL_ZOOM:u32=0b10000000;
+	pub const CONTROL_MOVEFORWARD:u32=0b00000001;
+	pub const CONTROL_MOVEBACK:u32=0b00000010;
+	pub const CONTROL_MOVERIGHT:u32=0b00000100;
+	pub const CONTROL_MOVELEFT:u32=0b00001000;
+	pub const CONTROL_MOVEUP:u32=0b00010000;
+	pub const CONTROL_MOVEDOWN:u32=0b00100000;
+	pub const CONTROL_JUMP:u32=0b01000000;
+	pub const CONTROL_ZOOM:u32=0b10000000;
 
-	const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X;
-	const UP_DIR:Planar64Vec3=Planar64Vec3::Y;
-	const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z;
+	pub const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X;
+	pub const UP_DIR:Planar64Vec3=Planar64Vec3::Y;
+	pub const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z;
 
 	fn neo()->Self{
 		Self{
@@ -72,7 +73,7 @@ impl StyleModifiers{
 		}
 	}
 
-	fn roblox_bhop()->Self{
+	pub fn roblox_bhop()->Self{
 		Self{
 			controls_used:!0,
 			controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
@@ -138,13 +139,13 @@ impl StyleModifiers{
 				air_accel_limit:Some(Planar64::raw(150<<28)*100),
 				tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
 			}),
-			jump_impulse:JumpImpulse::FromHeight(Planar64::int(52)/VALVE_SCALE),
+			jump_impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE),
 			jump_calculation:JumpCalculation::Linear,
-			gravity:Planar64Vec3::int(0,-800,0)/VALVE_SCALE,
+			gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE,
 			static_friction:Planar64::int(2),//?
 			kinetic_friction:Planar64::int(3),//?
 			mass:Planar64::int(1),
-			mv:Planar64::raw(30)/VALVE_SCALE,
+			mv:Planar64::raw(30)*VALVE_SCALE,
 			rocket_force:None,
 			walk_speed:Planar64::int(18),//?
 			walk_accel:Planar64::int(90),//?
@@ -154,7 +155,7 @@ impl StyleModifiers{
 			swim_speed:Planar64::int(12),//?
 			surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75
 			hitbox:Hitbox::source(),
-			camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)/VALVE_SCALE,
+			camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE,
 		}
 	}
 	fn source_surf()->Self{
@@ -163,16 +164,16 @@ impl StyleModifiers{
 			controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
 			strafe:Some(StrafeSettings{
 				enable:EnableStrafe::Always,
-				air_accel_limit:Some(Planar64::int(150)*66/VALVE_SCALE),
+				air_accel_limit:Some(Planar64::int(150)*66*VALVE_SCALE),
 				tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(),
 			}),
-			jump_impulse:JumpImpulse::FromHeight(Planar64::int(52)/VALVE_SCALE),
+			jump_impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE),
 			jump_calculation:JumpCalculation::Linear,
-			gravity:Planar64Vec3::int(0,-800,0)/VALVE_SCALE,
+			gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE,
 			static_friction:Planar64::int(2),//?
 			kinetic_friction:Planar64::int(3),//?
 			mass:Planar64::int(1),
-			mv:Planar64::int(30)/VALVE_SCALE,
+			mv:Planar64::int(30)*VALVE_SCALE,
 			rocket_force:None,
 			walk_speed:Planar64::int(18),//?
 			walk_accel:Planar64::int(90),//?
@@ -182,7 +183,7 @@ impl StyleModifiers{
 			swim_speed:Planar64::int(12),//?
 			surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75
 			hitbox:Hitbox::source(),
-			camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)/VALVE_SCALE,
+			camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE,
 		}
 	}
 	fn roblox_rocket()->Self{
@@ -211,13 +212,15 @@ impl StyleModifiers{
 	}
 }
 
-enum JumpCalculation{
+#[derive(Clone)]
+pub enum JumpCalculation{
 	Capped,//roblox
 	Energy,//new
 	Linear,//source
 }
 
-enum JumpImpulse{
+#[derive(Clone)]
+pub enum JumpImpulse{
 	FromTime(Time),//jump time is invariant across mass and gravity changes
 	FromHeight(Planar64),//jump height is invariant across mass and gravity changes
 	FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes
@@ -228,20 +231,35 @@ enum JumpImpulse{
 //Energy means it adds energy
 //Linear means it linearly adds on
 
-enum EnableStrafe{
+#[derive(Clone)]
+pub enum EnableStrafe{
 	Always,
 	MaskAny(u32),//hsw, shsw
 	MaskAll(u32),
 	//Function(Box<dyn Fn(u32)->bool>),
 }
 
-struct StrafeSettings{
+#[derive(Clone)]
+pub struct StrafeSettings{
 	enable:EnableStrafe,
 	air_accel_limit:Option<Planar64>,
 	tick_rate:Ratio64,
 }
+impl StrafeSettings{
+	pub fn next_tick(&self,time:Time)->Time{
+		Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
+	}
+	pub fn mask(&self,controls:u32)->bool{
+		match self.enable{
+			EnableStrafe::Always=>true,
+			EnableStrafe::MaskAny(mask)=>mask&controls!=0,
+			EnableStrafe::MaskAll(mask)=>mask&controls==mask,
+		}
+	}
+}
 
-enum HitboxMesh{
+#[derive(Clone)]
+pub enum HitboxMesh{
 	Box,//source
 	Cylinder,//roblox
 	//Sphere,//roblox old physics
@@ -250,9 +268,10 @@ enum HitboxMesh{
 	//DualCone,
 }
 
-struct Hitbox{
-	halfsize:Planar64Vec3,
-	mesh:HitboxMesh,
+#[derive(Clone)]
+pub struct Hitbox{
+	pub halfsize:Planar64Vec3,
+	pub mesh:HitboxMesh,
 }
 impl Hitbox{
 	fn roblox()->Self{
@@ -263,7 +282,7 @@ impl Hitbox{
 	}
 	fn source()->Self{
 		Self{
-			halfsize:Planar64Vec3::raw(33,73,33)/2/VALVE_SCALE,
+			halfsize:Planar64Vec3::raw(33,73,33)/2*VALVE_SCALE,
 			mesh:HitboxMesh::Box,
 		}
 	}
diff --git a/src/integer.rs b/src/integer.rs
index b04376a..844cf4e 100644
--- a/src/integer.rs
+++ b/src/integer.rs
@@ -294,9 +294,10 @@ impl std::ops::Mul<i64> for Ratio64Vec2{
 pub struct Angle32(i32);
 impl Angle32{
 	pub const FRAC_PI_2:Self=Self(1<<30);
+	pub const NEG_FRAC_PI_2:Self=Self(-1<<30);
 	pub const PI:Self=Self(-1<<31);
 	#[inline]
-	pub fn wrap_from_i64(theta:i64)->Self{
+	pub const fn wrap_from_i64(theta:i64)->Self{
 		//take lower bits
 		//note: this was checked on compiler explorer and compiles to 1 instruction!
 		Self(i32::from_ne_bytes(((theta&((1<<32)-1)) as u32).to_ne_bytes()))
@@ -308,7 +309,7 @@ impl Angle32{
 		Self(theta.clamp(i32::MIN as i64,i32::MAX as i64) as i32)
 	}
 	#[inline]
-	pub fn get(&self)->i32{
+	pub const fn get(&self)->i32{
 		self.0
 	}
 	/// Clamps the value towards the midpoint of the range.
@@ -411,7 +412,7 @@ impl TryFrom<[f32;3]> for Unit32Vec3{
 */
 
 ///[-1.0,1.0] = [-2^32,2^32]
-#[derive(Clone,Copy,Hash,Eq,Ord,PartialEq,PartialOrd)]
+#[derive(Clone,Copy,Debug,Hash,Eq,Ord,PartialEq,PartialOrd)]
 pub struct Planar64(i64);
 impl Planar64{
 	pub const ZERO:Self=Self(0);
@@ -579,7 +580,7 @@ impl std::ops::Div<Planar64> for Planar64{
 
 
 ///[-1.0,1.0] = [-2^32,2^32]
-#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
+#[derive(Clone,Copy,Debug,Default,Hash,Eq,PartialEq)]
 pub struct Planar64Vec3(glam::I64Vec3);
 impl Planar64Vec3{
 	pub const ZERO:Self=Planar64Vec3(glam::I64Vec3::ZERO);
diff --git a/src/lib.rs b/src/lib.rs
index 99b060b..b567481 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,8 +1,10 @@
 pub mod bvh;
+pub mod map;
 pub mod aabb;
 pub mod model;
 pub mod zeroes;
 pub mod integer;
+pub mod updatable;
 pub mod instruction;
 pub mod gameplay_modes;
 pub mod gameplay_style;
diff --git a/src/map.rs b/src/map.rs
new file mode 100644
index 0000000..d567b8f
--- /dev/null
+++ b/src/map.rs
@@ -0,0 +1,13 @@
+use crate::model;
+use crate::gameplay_modes;
+use crate::gameplay_attributes;
+//this is a temporary struct to try to get the code running again
+//TODO: use snf::map::Region to update the data in physics and graphics instead of this
+pub struct CompleteMap{
+	pub modes:gameplay_modes::Modes,
+	pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
+	pub meshes:Vec<model::Mesh>,
+	pub models:Vec<model::Model>,
+	//RenderPattern
+	pub render_configs:Vec<model::RenderConfig>,
+}
diff --git a/src/model.rs b/src/model.rs
index cbf7370..30d5361 100644
--- a/src/model.rs
+++ b/src/model.rs
@@ -3,51 +3,126 @@ use crate::gameplay_attributes;
 
 pub type TextureCoordinate=glam::Vec2;
 pub type Color4=glam::Vec4;
+#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
+pub struct PositionId(u32);
+#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
+pub struct TextureCoordinateId(u32);
+#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
+pub struct NormalId(u32);
+#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
+pub struct ColorId(u32);
 #[derive(Clone,Hash,PartialEq,Eq)]
 pub struct IndexedVertex{
-	pub pos:u32,
-	pub tex:u32,
-	pub normal:u32,
-	pub color:u32,
+	pub pos:PositionId,
+	pub tex:TextureCoordinateId,
+	pub normal:NormalId,
+	pub color:ColorId,
 }
+#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
 pub struct VertexId(u32);
-pub struct IndexedVertexList{
-	pub vertices:Vec<VertexId>,
+pub type IndexedVertexList=Vec<VertexId>;
+pub trait PolygonIter{
+	fn polys(&self)->impl Iterator<Item=&[VertexId]>;
 }
-pub struct GroupId(u32);
-pub enum IndexedGroup{
-	PolygonList(Vec<IndexedVertexList>),
-	//TriangleStrip(Vec<IndexedVertexList>),
+pub trait MapVertexId{
+	fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self;
+}
+pub struct PolygonList(Vec<IndexedVertexList>);
+impl PolygonList{
+	pub const fn new(list:Vec<IndexedVertexList>)->Self{
+		Self(list)
+	}
+	pub fn extend<T:IntoIterator<Item=IndexedVertexList>>(&mut self,iter:T){
+		self.0.extend(iter);
+	}
+}
+impl PolygonIter for PolygonList{
+	fn polys(&self)->impl Iterator<Item=&[VertexId]>{
+		self.0.iter().map(|poly|poly.as_slice())
+	}
+}
+impl MapVertexId for PolygonList{
+	fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
+		Self(self.0.into_iter().map(|ivl|ivl.into_iter().map(&f).collect()).collect())
+	}
+}
+// pub struct TriangleStrip(IndexedVertexList);
+// impl PolygonIter for TriangleStrip{
+// 	fn polys(&self)->impl Iterator<Item=&[VertexId]>{
+// 		self.0.vertices.windows(3).enumerate().map(|(i,s)|if i&0!=0{return s.iter().rev()}else{return s.iter()})
+// 	}
+// }
+#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
+pub struct PolygonGroupId(u32);
+pub enum PolygonGroup{
+	PolygonList(PolygonList),
+	//TriangleStrip(TriangleStrip),
+}
+impl PolygonIter for PolygonGroup{
+	fn polys(&self)->impl Iterator<Item=&[VertexId]>{
+		match self{
+			PolygonGroup::PolygonList(list)=>list.polys(),
+			//PolygonGroup::TriangleStrip(strip)=>strip.polys(),
+		}
+	}
+}
+impl MapVertexId for PolygonGroup{
+	fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
+		match self{
+			PolygonGroup::PolygonList(polys)=>Self::PolygonList(polys.map_vertex_id(f)),
+		}
+	}
+}
+/// Ah yes, a group of things to render at the same time
+#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
+pub struct TextureId(u32);
+#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
+pub struct RenderConfigId(u32);
+#[derive(Default)]
+pub struct RenderConfig{
+	pub texture:Option<TextureId>,
+}
+impl RenderConfig{
+	pub const fn texture(texture:TextureId)->Self{
+		Self{
+			texture:Some(texture),
+		}
+	}
 }
-pub struct RenderId(u32);
 pub struct IndexedGraphicsGroup{
 	//Render pattern material/texture/shader/flat color
-	pub render:RenderId,
-	pub groups:Vec<GroupId>,
+	pub render:RenderConfigId,
+	pub groups:Vec<PolygonGroupId>,
 }
+#[derive(Default)]
 pub struct IndexedPhysicsGroup{
 	//the polygons in this group are guaranteed to make a closed convex shape
-	pub groups:Vec<GroupId>,
+	pub groups:Vec<PolygonGroupId>,
 }
 //This is a superset of PhysicsModel and GraphicsModel
-pub struct IndexedModel{
+#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
+pub struct MeshId(u32);
+pub struct Mesh{
 	pub unique_pos:Vec<Planar64Vec3>,//Unit32Vec3
 	pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
 	pub unique_tex:Vec<TextureCoordinate>,
 	pub unique_color:Vec<Color4>,
 	pub unique_vertices:Vec<IndexedVertex>,
-	//groups are constant texture AND convexity slices
-	pub groups:Vec<IndexedGroup>,
+	//polygon groups are constant texture AND convexity slices
+	//note that this may need to be changed to be a list of individual faces
+	//for submeshes to work since face ids need to be consistent across submeshes
+	//so face == polygon_groups[face_id]
+	pub polygon_groups:Vec<PolygonGroup>,
 	//graphics indexed (by texture)
-	pub graphics_sets:Vec<IndexedGraphicsGroup>,
+	pub graphics_groups:Vec<IndexedGraphicsGroup>,
 	//physics indexed (by convexity)
-	pub physics_sets:Vec<IndexedPhysicsGroup>,
+	pub physics_groups:Vec<IndexedPhysicsGroup>,
 }
 
-#[derive(Clone,Copy,Hash,Eq,PartialEq)]
+#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
 pub struct ModelId(u32);
 pub struct Model{
-	pub model:ModelId,
+	pub mesh:MeshId,
 	pub attributes:gameplay_attributes::CollisionAttributesId,
 	pub color:Color4,//transparency is in here
 	pub transform:Planar64Affine3,
diff --git a/src/updatable.rs b/src/updatable.rs
new file mode 100644
index 0000000..13b6dd2
--- /dev/null
+++ b/src/updatable.rs
@@ -0,0 +1,56 @@
+pub trait Updatable<Updater>{
+	fn update(&mut self,update:Updater);
+}
+#[derive(Clone,Copy,Hash,Eq,PartialEq)]
+struct InnerId(u32);
+#[derive(Clone)]
+struct Inner{
+	id:InnerId,
+	enabled:bool,
+}
+#[derive(Clone,Copy,Hash,Eq,PartialEq)]
+struct OuterId(u32);
+struct Outer{
+	id:OuterId,
+	inners:std::collections::HashMap<InnerId,Inner>,
+}
+
+enum Update<I,U>{
+	Insert(I),
+	Update(U),
+	Remove
+}
+
+struct InnerUpdate{
+	//#[updatable(Update)]
+	enabled:Option<bool>,
+}
+struct OuterUpdate{
+	//#[updatable(Insert,Update,Remove)]
+	inners:std::collections::HashMap<InnerId,Update<Inner,InnerUpdate>>,
+	//#[updatable(Update)]
+	//inners:std::collections::HashMap<InnerId,InnerUpdate>,
+}
+impl Updatable<InnerUpdate> for Inner{
+	fn update(&mut self,update:InnerUpdate){
+		if let Some(enabled)=update.enabled{
+			self.enabled=enabled;
+		}
+	}
+}
+impl Updatable<OuterUpdate> for Outer{
+	fn update(&mut self,update:OuterUpdate){
+		for (id,up) in update.inners{
+			match up{
+				Update::Insert(new_inner)=>self.inners.insert(id,new_inner),
+				Update::Update(inner_update)=>self.inners.get_mut(&id).map(|inner|{
+					let old=inner.clone();
+					inner.update(inner_update);
+					old
+				}),
+				Update::Remove=>self.inners.remove(&id),
+			};
+		}
+	}
+}
+//*/
\ No newline at end of file