diff --git a/lib/rbx_loader/src/rbx.rs b/lib/rbx_loader/src/rbx.rs
index 0b69776..72d0891 100644
--- a/lib/rbx_loader/src/rbx.rs
+++ b/lib/rbx_loader/src/rbx.rs
@@ -1,6 +1,7 @@
 use std::collections::HashMap;
 use crate::loader::MeshIndex;
 use crate::primitives;
+use strafesnet_common::aabb::Aabb;
 use strafesnet_common::map;
 use strafesnet_common::model;
 use strafesnet_common::gameplay_modes;
@@ -769,7 +770,55 @@ pub fn convert<'a>(
 }
 struct MeshWithAabb{
 	mesh:model::Mesh,
-	aabb:strafesnet_common::aabb::Aabb,
+	aabb:Aabb,
+}
+fn acquire_mesh_id_from_render_config_id<'a>(
+	primitive_meshes:&mut Vec<model::Mesh>,
+	mesh_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RenderConfigId,model::MeshId>>,
+	loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
+	old_mesh_id:model::MeshId,
+	render:RenderConfigId,
+)->Option<(model::MeshId,&'a Aabb)>{
+	//ignore meshes that fail to load completely for now
+	loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
+		*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
+		.entry(render).or_insert_with(||{
+			let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
+			let mut mesh_clone=mesh_with_aabb.mesh.clone();
+			//set the render group lool
+			if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){
+				graphics_group.render=render;
+			}
+			primitive_meshes.push(mesh_clone);
+			mesh_id
+		}),
+		&mesh_with_aabb.aabb,
+	))
+}
+fn acquire_union_id_from_render_config_id<'a>(
+	primitive_meshes:&mut Vec<model::Mesh>,
+	union_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RobloxPartDescription,model::MeshId>>,
+	loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
+	old_union_id:model::MeshId,
+	part_texture_description:RobloxPartDescription,
+)->Option<(model::MeshId,&'a Aabb)>{
+	//ignore uniones that fail to load completely for now
+	loaded_meshes.get(&old_union_id).map(|union_with_aabb|(
+		*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new())
+		.entry(part_texture_description.clone()).or_insert_with(||{
+			let union_id=model::MeshId::new(primitive_meshes.len() as u32);
+			let mut union_clone=union_with_aabb.mesh.clone();
+			//set the render groups
+			for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description){
+				if let Some(face_texture_description)=maybe_face_texture_description{
+					graphics_group.render=face_texture_description.render;
+				}
+			}
+			primitive_meshes.push(union_clone);
+			union_id
+		}),
+		&union_with_aabb.aabb,
+	))
 }
 pub struct PartialMap1<'a>{
 	primitive_meshes:Vec<model::Mesh>,
@@ -804,30 +853,21 @@ impl PartialMap1<'_>{
 			})
 		}).collect();
 
+		// SAFETY: I have no idea what I'm doing and this is definitely unsound in some subtle way
+		// I just want to chain iterators together man
+		let aint_no_way=core::cell::UnsafeCell::new(&mut self.primitive_meshes);
+
 		let mut mesh_id_from_render_config_id=HashMap::new();
-		//ignore meshes that fail to load completely for now
-		let mut acquire_mesh_id_from_render_config_id=|old_mesh_id,render|{
-			loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
-				*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
-				.entry(render).or_insert_with(||{
-					let mesh_id=model::MeshId::new(self.primitive_meshes.len() as u32);
-					let mut mesh_clone=mesh_with_aabb.mesh.clone();
-					//set the render group lool
-					if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){
-						graphics_group.render=render;
-					}
-					self.primitive_meshes.push(mesh_clone);
-					mesh_id
-				}),
-				&mesh_with_aabb.aabb,
-			))
-		};
+		let mut union_id_from_render_config_id=HashMap::new();
 		//now that the meshes are loaded, these models can be generated
 		let models_owned_attributes:Vec<ModelOwnedAttributes>=
 		self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
 			//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
 			//insert into primitive_meshes
 			let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
+				unsafe{*aint_no_way.get()},
+				&mut mesh_id_from_render_config_id,
+				&loaded_meshes,
 				deferred_model_deferred_attributes.model.mesh,
 				deferred_model_deferred_attributes.render
 			)?;
@@ -845,7 +885,32 @@ impl PartialMap1<'_>{
 					deferred_model_deferred_attributes.model.transform.translation
 				),
 			})
-		}).chain(self.primitive_models_deferred_attributes.into_iter())
+		}).chain(self.deferred_unions_deferred_attributes.into_iter().flat_map(|deferred_union_deferred_attributes|{
+			//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
+			//insert into primitive_meshes
+			let (mesh,aabb)=acquire_union_id_from_render_config_id(
+				unsafe{*aint_no_way.get()},
+				&mut union_id_from_render_config_id,
+				&loaded_meshes,
+				deferred_union_deferred_attributes.model.mesh,
+				deferred_union_deferred_attributes.render
+			)?;
+			let size=aabb.size();
+			Some(ModelDeferredAttributes{
+				mesh,
+				deferred_attributes:deferred_union_deferred_attributes.model.deferred_attributes,
+				color:deferred_union_deferred_attributes.model.color,
+				transform:Planar64Affine3::new(
+					Planar64Mat3::from_cols([
+						(deferred_union_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().fix_1(),
+						(deferred_union_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().fix_1(),
+						(deferred_union_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().fix_1()
+					]),
+					deferred_union_deferred_attributes.model.transform.translation
+				),
+			})
+		}))
+		.chain(self.primitive_models_deferred_attributes.into_iter())
 		.enumerate().map(|(model_id,model_deferred_attributes)|{
 			let model_id=model::ModelId::new(model_id as u32);
 			ModelOwnedAttributes{