2024-01-31 04:25:07 +00:00
use std ::collections ::HashMap ;
2024-01-31 01:41:16 +00:00
use crate ::primitives ;
2024-01-31 04:25:07 +00:00
use strafesnet_common ::map ;
use strafesnet_common ::model ;
use strafesnet_common ::gameplay_modes ;
2024-02-02 03:43:02 +00:00
use strafesnet_common ::gameplay_style ;
2024-01-31 04:25:07 +00:00
use strafesnet_common ::gameplay_attributes as attr ;
2024-01-31 01:41:16 +00:00
use strafesnet_common ::integer ::{ Planar64 , Planar64Vec3 , Planar64Mat3 , Planar64Affine3 } ;
2024-02-05 04:57:25 +00:00
use strafesnet_common ::model ::RenderConfig ;
2024-02-08 06:32:52 +00:00
use strafesnet_common ::model ::RenderConfigId ;
2024-02-02 03:43:02 +00:00
use strafesnet_common ::updatable ::Updatable ;
2024-01-31 01:41:16 +00:00
fn class_is_a ( class : & str , superclass : & str ) -> bool {
if class = = superclass {
return true
}
let class_descriptor = rbx_reflection_database ::get ( ) . classes . get ( class ) ;
if let Some ( descriptor ) = & class_descriptor {
if let Some ( class_super ) = & descriptor . superclass {
return class_is_a ( & class_super , superclass )
}
}
return false
}
fn recursive_collect_superclass ( objects : & mut std ::vec ::Vec < rbx_dom_weak ::types ::Ref > , dom : & rbx_dom_weak ::WeakDom , instance : & rbx_dom_weak ::Instance , superclass : & str ) {
let mut stack = vec! [ instance ] ;
while let Some ( item ) = stack . pop ( ) {
for & referent in item . children ( ) {
if let Some ( c ) = dom . get_by_ref ( referent ) {
if class_is_a ( c . class . as_str ( ) , superclass ) {
objects . push ( c . referent ( ) ) ; //copy ref
}
stack . push ( c ) ;
}
}
}
}
fn planar64_affine3_from_roblox ( cf :& rbx_dom_weak ::types ::CFrame , size :& rbx_dom_weak ::types ::Vector3 ) ->Planar64Affine3 {
Planar64Affine3 ::new (
Planar64Mat3 ::from_cols (
Planar64Vec3 ::try_from ( [ cf . orientation . x . x , cf . orientation . y . x , cf . orientation . z . x ] ) . unwrap ( )
* Planar64 ::try_from ( size . x / 2.0 ) . unwrap ( ) ,
Planar64Vec3 ::try_from ( [ cf . orientation . x . y , cf . orientation . y . y , cf . orientation . z . y ] ) . unwrap ( )
* Planar64 ::try_from ( size . y / 2.0 ) . unwrap ( ) ,
Planar64Vec3 ::try_from ( [ cf . orientation . x . z , cf . orientation . y . z , cf . orientation . z . z ] ) . unwrap ( )
* Planar64 ::try_from ( size . z / 2.0 ) . unwrap ( ) ,
) ,
Planar64Vec3 ::try_from ( [ cf . position . x , cf . position . y , cf . position . z ] ) . unwrap ( )
)
}
2024-02-02 03:43:02 +00:00
struct ModeBuilder {
mode :gameplay_modes ::Mode ,
final_stage_id_from_builder_stage_id :HashMap < gameplay_modes ::StageId , gameplay_modes ::StageId > ,
}
2024-01-31 04:25:07 +00:00
#[ derive(Default) ]
struct ModesBuilder {
modes :HashMap < gameplay_modes ::ModeId , gameplay_modes ::Mode > ,
2024-02-02 03:43:02 +00:00
stages :HashMap < gameplay_modes ::ModeId , HashMap < gameplay_modes ::StageId , gameplay_modes ::Stage > > ,
2024-01-31 04:25:07 +00:00
mode_updates :Vec < ( gameplay_modes ::ModeId , gameplay_modes ::ModeUpdate ) > ,
2024-02-02 03:43:02 +00:00
stage_updates :Vec < ( gameplay_modes ::ModeId , gameplay_modes ::StageId , gameplay_modes ::StageUpdate ) > ,
2024-01-31 04:25:07 +00:00
}
impl ModesBuilder {
2024-02-02 03:43:02 +00:00
fn build ( mut self ) ->gameplay_modes ::Modes {
//collect modes and stages into contiguous arrays
let mut unique_modes :Vec < ( gameplay_modes ::ModeId , gameplay_modes ::Mode ) >
= self . modes . into_iter ( ) . collect ( ) ;
unique_modes . sort_by ( | a , b | a . 0. cmp ( & b . 0 ) ) ;
let ( mut modes , final_mode_id_from_builder_mode_id ) :( Vec < ModeBuilder > , HashMap < gameplay_modes ::ModeId , gameplay_modes ::ModeId > )
= unique_modes . into_iter ( ) . enumerate ( )
. map ( | ( final_mode_id , ( builder_mode_id , mut mode ) ) | {
(
ModeBuilder {
final_stage_id_from_builder_stage_id :self . stages . remove ( & builder_mode_id ) . map_or_else ( | | HashMap ::new ( ) , | stages | {
let mut unique_stages :Vec < ( gameplay_modes ::StageId , gameplay_modes ::Stage ) >
= stages . into_iter ( ) . collect ( ) ;
unique_stages . sort_by ( | a , b | a . 0. cmp ( & b . 0 ) ) ;
unique_stages . into_iter ( ) . enumerate ( )
. map ( | ( final_stage_id , ( builder_stage_id , stage ) ) | {
mode . push_stage ( stage ) ;
2024-02-07 06:54:13 +00:00
( builder_stage_id , gameplay_modes ::StageId ::new ( final_stage_id as u32 ) )
2024-02-02 03:43:02 +00:00
} ) . collect ( )
} ) ,
mode ,
} ,
(
builder_mode_id ,
2024-02-07 06:54:13 +00:00
gameplay_modes ::ModeId ::new ( final_mode_id as u32 )
2024-02-02 03:43:02 +00:00
)
)
} ) . unzip ( ) ;
//TODO: failure messages or errors or something
//push stage updates
for ( builder_mode_id , builder_stage_id , stage_update ) in self . stage_updates {
if let Some ( final_mode_id ) = final_mode_id_from_builder_mode_id . get ( & builder_mode_id ) {
if let Some ( mode ) = modes . get_mut ( final_mode_id . get ( ) as usize ) {
if let Some ( & final_stage_id ) = mode . final_stage_id_from_builder_stage_id . get ( & builder_stage_id ) {
if let Some ( stage ) = mode . mode . get_stage_mut ( final_stage_id ) {
stage . update ( stage_update ) ;
}
}
}
}
}
//push mode updates
2024-02-13 05:33:55 +00:00
for ( builder_mode_id , mut mode_update ) in self . mode_updates {
2024-02-02 03:43:02 +00:00
if let Some ( final_mode_id ) = final_mode_id_from_builder_mode_id . get ( & builder_mode_id ) {
if let Some ( mode ) = modes . get_mut ( final_mode_id . get ( ) as usize ) {
2024-02-13 05:33:55 +00:00
//map stage id on stage elements
mode_update . map_stage_element_ids ( | stage_id |
//walk down one stage id at a time until a stage is found
//TODO use better logic like BTreeMap::upper_bound instead of walking
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
// .value().copied().unwrap_or(gameplay_modes::StageId::FIRST)
( 0 ..= stage_id . get ( ) ) . rev ( ) . find_map ( | builder_stage_id |
//map the stage element to that stage
mode . final_stage_id_from_builder_stage_id . get ( & gameplay_modes ::StageId ::new ( builder_stage_id ) ) . copied ( )
) . unwrap_or ( gameplay_modes ::StageId ::FIRST )
) ;
2024-02-02 03:43:02 +00:00
mode . mode . update ( mode_update ) ;
}
}
}
gameplay_modes ::Modes ::new ( modes . into_iter ( ) . map ( | mode_builder | mode_builder . mode ) . collect ( ) )
}
2024-01-31 04:25:07 +00:00
fn insert_mode ( & mut self , mode_id :gameplay_modes ::ModeId , mode :gameplay_modes ::Mode ) {
assert! ( self . modes . insert ( mode_id , mode ) . is_none ( ) , " Cannot replace existing mode " ) ;
}
2024-02-02 03:43:02 +00:00
fn insert_stage ( & mut self , mode_id :gameplay_modes ::ModeId , stage_id :gameplay_modes ::StageId , stage :gameplay_modes ::Stage ) {
assert! ( self . stages . entry ( mode_id ) . or_insert ( HashMap ::new ( ) ) . insert ( stage_id , stage ) . is_none ( ) , " Cannot replace existing stage " ) ;
}
2024-01-31 04:25:07 +00:00
fn push_mode_update ( & mut self , mode_id :gameplay_modes ::ModeId , mode_update :gameplay_modes ::ModeUpdate ) {
self . mode_updates . push ( ( mode_id , mode_update ) ) ;
}
2024-02-02 03:43:02 +00:00
fn push_stage_update ( & mut self , mode_id :gameplay_modes ::ModeId , stage_id :gameplay_modes ::StageId , stage_update :gameplay_modes ::StageUpdate ) {
self . stage_updates . push ( ( mode_id , stage_id , stage_update ) ) ;
}
2024-01-31 04:25:07 +00:00
}
2024-02-13 04:43:09 +00:00
fn get_attributes ( object :& rbx_dom_weak ::Instance , can_collide :bool , velocity :Planar64Vec3 , model_id :model ::ModelId , modes_builder :& mut ModesBuilder , wormhole_in_model_to_id :& mut HashMap < model ::ModelId , u32 > , wormhole_id_to_out_model :& mut HashMap < u32 , model ::ModelId > ) ->attr ::CollisionAttributes {
2024-02-02 03:43:02 +00:00
let name = object . name . as_str ( ) ;
2024-01-31 04:25:07 +00:00
let mut general = attr ::GeneralAttributes ::default ( ) ;
let mut intersecting = attr ::IntersectingAttributes ::default ( ) ;
let mut contacting = attr ::ContactingAttributes ::default ( ) ;
2024-01-31 01:41:16 +00:00
let mut force_can_collide = can_collide ;
2024-02-13 04:43:09 +00:00
let mut force_intersecting = false ;
2024-01-31 01:41:16 +00:00
match name {
" Water " = > {
force_can_collide = false ;
//TODO: read stupid CustomPhysicalProperties
2024-01-31 04:25:07 +00:00
intersecting . water = Some ( attr ::IntersectingWater { density :Planar64 ::ONE , viscosity :Planar64 ::ONE / 10 , velocity } ) ;
2024-01-31 01:41:16 +00:00
} ,
" Accelerator " = > {
//although the new game supports collidable accelerators, this is a roblox compatability map loader
force_can_collide = false ;
2024-01-31 04:25:07 +00:00
general . accelerator = Some ( attr ::Accelerator { acceleration :velocity } ) ;
2024-01-31 01:41:16 +00:00
} ,
2024-01-31 04:25:07 +00:00
// "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(attr::StageElement{
2024-01-31 01:41:16 +00:00
// mode_id:0,
// stage_id:0,
// force:false,
// behaviour:model::StageElementBehaviour::Unordered
// })),
2024-01-31 04:25:07 +00:00
" SetVelocity " = > general . trajectory = Some ( attr ::SetTrajectory ::Velocity ( velocity ) ) ,
2024-02-13 04:43:09 +00:00
" MapStart " = > {
force_can_collide = false ;
force_intersecting = true ;
modes_builder . insert_mode (
gameplay_modes ::ModeId ::MAIN ,
gameplay_modes ::Mode ::new (
gameplay_style ::StyleModifiers ::roblox_bhop ( ) ,
model_id
)
) ;
} ,
2024-01-31 04:25:07 +00:00
" MapFinish " = > {
force_can_collide = false ;
2024-02-13 04:43:09 +00:00
force_intersecting = true ;
2024-01-31 04:25:07 +00:00
modes_builder . push_mode_update (
gameplay_modes ::ModeId ::MAIN ,
gameplay_modes ::ModeUpdate ::zone (
model_id ,
gameplay_modes ::Zone ::Finish ,
) ,
) ;
} ,
" MapAnticheat " = > {
force_can_collide = false ;
2024-02-13 04:43:09 +00:00
force_intersecting = true ;
2024-01-31 04:25:07 +00:00
modes_builder . push_mode_update (
gameplay_modes ::ModeId ::MAIN ,
gameplay_modes ::ModeUpdate ::zone (
model_id ,
gameplay_modes ::Zone ::Anticheat ,
) ,
) ;
} ,
" Platform " = > {
modes_builder . push_mode_update (
gameplay_modes ::ModeId ::MAIN ,
gameplay_modes ::ModeUpdate ::element (
model_id ,
2024-02-13 05:42:35 +00:00
gameplay_modes ::StageElement ::new ( gameplay_modes ::StageId ::FIRST , false , gameplay_modes ::StageElementBehaviour ::Platform ) , //roblox does not know which stage the platform belongs to
2024-01-31 04:25:07 +00:00
) ,
) ;
} ,
2024-01-31 01:41:16 +00:00
other = > {
2024-02-13 05:37:54 +00:00
let regman = lazy_regex ::regex! ( r "^(BonusStart|WormholeOut)(\d+)$" ) ;
2024-02-13 04:43:09 +00:00
if let Some ( captures ) = regman . captures ( other ) {
match & captures [ 1 ] {
" BonusStart " = > {
force_can_collide = false ;
force_intersecting = true ;
modes_builder . insert_mode (
gameplay_modes ::ModeId ::new ( captures [ 2 ] . parse ::< u32 > ( ) . unwrap ( ) ) ,
gameplay_modes ::Mode ::new (
gameplay_style ::StyleModifiers ::roblox_bhop ( ) ,
model_id
)
) ;
} ,
" WormholeOut " = > {
2024-02-13 06:07:02 +00:00
//the PhysicsModelId has to exist for it to be teleported to!
force_intersecting = true ;
2024-02-13 04:43:09 +00:00
//this object is not special in strafe client, but the roblox mapping needs to be converted to model id
assert! ( wormhole_id_to_out_model . insert ( captures [ 2 ] . parse ::< u32 > ( ) . unwrap ( ) , model_id ) . is_none ( ) , " Cannot have multiple WormholeOut with same id " ) ;
} ,
_ = > ( ) ,
}
} else if let Some ( captures ) = lazy_regex ::regex! ( r "^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$" )
2024-01-31 01:41:16 +00:00
. captures ( other ) {
2024-02-13 04:43:09 +00:00
force_intersecting = true ;
2024-02-13 05:42:35 +00:00
let stage_id = gameplay_modes ::StageId ::new ( captures [ 3 ] . parse ::< u32 > ( ) . unwrap ( ) ) ;
2024-02-13 05:37:54 +00:00
let stage_element = gameplay_modes ::StageElement ::new (
//stage_id:
2024-02-13 05:42:35 +00:00
stage_id ,
2024-02-13 05:37:54 +00:00
//force:
match captures . get ( 1 ) {
Some ( m ) = > m . as_str ( ) = = " Force " ,
None = > false ,
} ,
//behaviour:
match & captures [ 2 ] {
" Spawn " = > {
modes_builder . insert_stage (
gameplay_modes ::ModeId ::MAIN ,
2024-02-13 05:42:35 +00:00
stage_id ,
2024-02-13 05:37:54 +00:00
gameplay_modes ::Stage ::new ( model_id ) ,
) ;
gameplay_modes ::StageElementBehaviour ::SpawnAt
} ,
" SpawnAt " = > gameplay_modes ::StageElementBehaviour ::SpawnAt ,
//cancollide false so you don't hit the side
//NOT a decoration
" Trigger " = > { force_can_collide = false ; gameplay_modes ::StageElementBehaviour ::Trigger } ,
" Teleport " = > { force_can_collide = false ; gameplay_modes ::StageElementBehaviour ::Teleport } ,
" Platform " = > gameplay_modes ::StageElementBehaviour ::Platform ,
_ = > panic! ( " regex1[2] messed up bad " ) ,
} ,
) ;
2024-01-31 04:25:07 +00:00
modes_builder . push_mode_update (
gameplay_modes ::ModeId ::MAIN ,
gameplay_modes ::ModeUpdate ::element (
model_id ,
2024-02-13 05:37:54 +00:00
stage_element ,
2024-01-31 04:25:07 +00:00
) ,
) ;
2024-02-02 03:43:02 +00:00
} else if let Some ( captures ) = lazy_regex ::regex! ( r "^(Jump|WormholeIn)(\d+)$" )
2024-01-31 01:41:16 +00:00
. captures ( other ) {
2024-02-02 03:43:02 +00:00
match & captures [ 1 ] {
" Jump " = > modes_builder . push_mode_update (
gameplay_modes ::ModeId ::MAIN ,
gameplay_modes ::ModeUpdate ::jump_limit (
model_id ,
//jump_limit:
captures [ 2 ] . parse ::< u32 > ( ) . unwrap ( )
) ,
2024-01-31 04:25:07 +00:00
) ,
2024-02-02 03:43:02 +00:00
" WormholeIn " = > {
force_can_collide = false ;
2024-02-13 04:43:09 +00:00
force_intersecting = true ;
2024-02-02 03:43:02 +00:00
assert! ( wormhole_in_model_to_id . insert ( model_id , captures [ 2 ] . parse ::< u32 > ( ) . unwrap ( ) ) . is_none ( ) , " Impossible " ) ;
} ,
2024-02-05 04:57:25 +00:00
_ = > panic! ( " regex2[1] messed up bad " ) ,
2024-02-02 03:43:02 +00:00
}
2024-01-31 01:41:16 +00:00
} else if let Some ( captures ) = lazy_regex ::regex! ( r "^Bonus(Finish|Anticheat)(\d+)$" )
. captures ( other ) {
force_can_collide = false ;
2024-02-13 04:43:09 +00:00
force_intersecting = true ;
2024-01-31 04:25:07 +00:00
modes_builder . push_mode_update (
2024-02-07 06:54:13 +00:00
gameplay_modes ::ModeId ::new ( captures [ 2 ] . parse ::< u32 > ( ) . unwrap ( ) ) ,
2024-01-31 04:25:07 +00:00
gameplay_modes ::ModeUpdate ::zone (
model_id ,
//zone:
match & captures [ 1 ] {
" Finish " = > gameplay_modes ::Zone ::Finish ,
" Anticheat " = > gameplay_modes ::Zone ::Anticheat ,
2024-02-05 04:57:25 +00:00
_ = > panic! ( " regex3[1] messed up bad " ) ,
2024-01-31 04:25:07 +00:00
} ,
) ,
) ;
2024-01-31 01:41:16 +00:00
}
2024-02-02 03:43:02 +00:00
// else if let Some(captures)=lazy_regex::regex!(r"^Stage(\d+)OrderedCheckpoint(\d+)$")
2024-01-31 01:41:16 +00:00
// .captures(other){
// match &captures[1]{
2024-02-02 03:43:02 +00:00
// "OrderedCheckpoint"=>modes_builder.push_stage_update(
// gameplay_modes::ModeId::MAIN,
2024-02-07 06:54:13 +00:00
// gameplay_modes::StageId::new(0),
2024-02-02 03:43:02 +00:00
// gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
// ),
2024-01-31 01:41:16 +00:00
// _=>panic!("regex3[1] messed up bad"),
// }
// }
}
}
//need some way to skip this
if velocity ! = Planar64Vec3 ::ZERO {
2024-01-31 04:25:07 +00:00
general . booster = Some ( attr ::Booster ::Velocity ( velocity ) ) ;
2024-01-31 01:41:16 +00:00
}
match force_can_collide {
true = > {
match name {
2024-01-31 04:25:07 +00:00
" Bounce " = > contacting . contact_behaviour = Some ( attr ::ContactingBehaviour ::Elastic ( u32 ::MAX ) ) ,
" Surf " = > contacting . contact_behaviour = Some ( attr ::ContactingBehaviour ::Surf ) ,
" Ladder " = > contacting . contact_behaviour = Some ( attr ::ContactingBehaviour ::Ladder ( attr ::ContactingLadder { sticky :true } ) ) ,
2024-01-31 01:41:16 +00:00
_ = > ( ) ,
}
2024-01-31 04:25:07 +00:00
attr ::CollisionAttributes ::Contact { contacting , general }
2024-01-31 01:41:16 +00:00
} ,
false = > if force_intersecting
| | general . any ( )
| | intersecting . any ( )
{
2024-01-31 04:25:07 +00:00
attr ::CollisionAttributes ::Intersect { intersecting , general }
2024-01-31 01:41:16 +00:00
} else {
2024-01-31 04:25:07 +00:00
attr ::CollisionAttributes ::Decoration
2024-01-31 01:41:16 +00:00
} ,
}
}
struct RobloxAssetId ( u64 ) ;
struct RobloxAssetIdParseErr ;
2024-02-05 04:57:25 +00:00
impl std ::str ::FromStr for RobloxAssetId {
2024-01-31 01:41:16 +00:00
type Err = RobloxAssetIdParseErr ;
2024-02-05 04:57:25 +00:00
fn from_str ( s :& str ) ->Result < Self , Self ::Err > {
2024-01-31 01:41:16 +00:00
let regman = lazy_regex ::regex! ( r "(\d+)$" ) ;
if let Some ( captures ) = regman . captures ( s ) {
if captures . len ( ) = = 2 { //captures[0] is all captures concatenated, and then each individual capture
2024-02-05 04:57:25 +00:00
if let Ok ( id ) = captures [ 0 ] . parse ::< u64 > ( ) {
2024-01-31 01:41:16 +00:00
return Ok ( Self ( id ) ) ;
}
}
}
Err ( RobloxAssetIdParseErr )
}
}
#[ derive(Clone,Copy,PartialEq) ]
struct RobloxTextureTransform {
offset_u :f32 ,
offset_v :f32 ,
scale_u :f32 ,
scale_v :f32 ,
}
impl std ::cmp ::Eq for RobloxTextureTransform { } //????
impl std ::default ::Default for RobloxTextureTransform {
2024-02-05 04:57:25 +00:00
fn default ( ) ->Self {
2024-01-31 01:41:16 +00:00
Self { offset_u :0.0 , offset_v :0.0 , scale_u :1.0 , scale_v :1.0 }
}
}
2024-02-05 04:57:25 +00:00
impl std ::hash ::Hash for RobloxTextureTransform {
fn hash < H :std ::hash ::Hasher > ( & self , state :& mut H ) {
2024-01-31 01:41:16 +00:00
self . offset_u . to_ne_bytes ( ) . hash ( state ) ;
self . offset_v . to_ne_bytes ( ) . hash ( state ) ;
self . scale_u . to_ne_bytes ( ) . hash ( state ) ;
self . scale_v . to_ne_bytes ( ) . hash ( state ) ;
}
}
#[ derive(Clone,PartialEq) ]
struct RobloxFaceTextureDescription {
2024-02-08 06:32:52 +00:00
render :RenderConfigId ,
2024-01-31 01:41:16 +00:00
color :glam ::Vec4 ,
transform :RobloxTextureTransform ,
}
impl std ::cmp ::Eq for RobloxFaceTextureDescription { } //????
2024-02-05 04:57:25 +00:00
impl std ::hash ::Hash for RobloxFaceTextureDescription {
fn hash < H :std ::hash ::Hasher > ( & self , state :& mut H ) {
self . render . hash ( state ) ;
2024-01-31 01:41:16 +00:00
self . transform . hash ( state ) ;
2024-02-05 04:57:25 +00:00
for & el in self . color . as_ref ( ) . iter ( ) {
2024-01-31 01:41:16 +00:00
el . to_ne_bytes ( ) . hash ( state ) ;
}
}
}
impl RobloxFaceTextureDescription {
fn to_face_description ( & self ) ->primitives ::FaceDescription {
primitives ::FaceDescription {
2024-02-05 04:57:25 +00:00
render :self . render ,
2024-01-31 01:41:16 +00:00
transform :glam ::Affine2 ::from_translation (
glam ::vec2 ( self . transform . offset_u , self . transform . offset_v )
)
* glam ::Affine2 ::from_scale (
glam ::vec2 ( self . transform . scale_u , self . transform . scale_v )
) ,
color :self . color ,
}
}
}
type RobloxPartDescription = [ Option < RobloxFaceTextureDescription > ; 6 ] ;
type RobloxWedgeDescription = [ Option < RobloxFaceTextureDescription > ; 5 ] ;
type RobloxCornerWedgeDescription = [ Option < RobloxFaceTextureDescription > ; 5 ] ;
#[ derive(Clone,Eq,Hash,PartialEq) ]
enum RobloxBasePartDescription {
Sphere ( RobloxPartDescription ) ,
Part ( RobloxPartDescription ) ,
Cylinder ( RobloxPartDescription ) ,
Wedge ( RobloxWedgeDescription ) ,
CornerWedge ( RobloxCornerWedgeDescription ) ,
}
2024-02-02 03:43:02 +00:00
struct ModelOwnedAttributes {
2024-02-08 06:32:52 +00:00
mesh :model ::MeshId ,
2024-02-02 03:43:02 +00:00
attributes :attr ::CollisionAttributes ,
color :model ::Color4 , //transparency is in here
transform :Planar64Affine3 ,
}
2024-02-08 06:32:52 +00:00
pub fn convert ( dom :rbx_dom_weak ::WeakDom ) ->map ::CompleteMap {
2024-02-02 03:43:02 +00:00
let mut modes_builder = ModesBuilder ::default ( ) ;
2024-01-31 01:41:16 +00:00
2024-02-02 03:43:02 +00:00
let mut models1 = Vec ::new ( ) ;
2024-02-08 06:32:52 +00:00
let mut meshes = Vec ::new ( ) ;
2024-02-05 04:57:25 +00:00
let mut indexed_model_id_from_description = HashMap ::new ( ) ;
2024-02-02 03:43:02 +00:00
let mut unique_attributes = Vec ::new ( ) ;
let mut attributes_id_from_attributes = HashMap ::new ( ) ;
let mut wormhole_in_model_to_id = HashMap ::new ( ) ;
let mut wormhole_id_to_out_model = HashMap ::new ( ) ;
2024-01-31 01:41:16 +00:00
2024-02-05 04:57:25 +00:00
//TODO: some sort of thing like RobloxResources that describes where to get each resource
//this would be another dependency built for downloading resources to keep this one clean
let mut unique_render_groups = vec! [ RenderConfig ::default ( ) ] ;
2024-02-08 06:32:52 +00:00
let mut render_id_from_asset_id = HashMap ::< u64 , RenderConfigId > ::new ( ) ;
let textureless_render_group = RenderConfigId ::new ( 0 ) ;
2024-01-31 01:41:16 +00:00
let mut object_refs = Vec ::new ( ) ;
let mut temp_objects = Vec ::new ( ) ;
recursive_collect_superclass ( & mut object_refs , & dom , dom . root ( ) , " BasePart " ) ;
for object_ref in object_refs {
if let Some ( object ) = dom . get_by_ref ( object_ref ) {
if let (
Some ( rbx_dom_weak ::types ::Variant ::CFrame ( cf ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Vector3 ( size ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Vector3 ( velocity ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Float32 ( transparency ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Color3uint8 ( color3 ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Bool ( can_collide ) ) ,
) = (
object . properties . get ( " CFrame " ) ,
object . properties . get ( " Size " ) ,
object . properties . get ( " Velocity " ) ,
object . properties . get ( " Transparency " ) ,
object . properties . get ( " Color " ) ,
object . properties . get ( " CanCollide " ) ,
)
{
let model_transform = planar64_affine3_from_roblox ( cf , size ) ;
if model_transform . matrix3 . determinant ( ) = = Planar64 ::ZERO {
let mut parent_ref = object . parent ( ) ;
let mut full_path = object . name . clone ( ) ;
while let Some ( parent ) = dom . get_by_ref ( parent_ref ) {
full_path = format! ( " {} . {} " , parent . name , full_path ) ;
parent_ref = parent . parent ( ) ;
}
println! ( " Zero determinant CFrame at location {} " , full_path ) ;
println! ( " matrix3: {} " , model_transform . matrix3 ) ;
continue ;
}
2024-02-02 03:43:02 +00:00
//at this point a new model is going to be generated for sure.
2024-02-07 06:54:13 +00:00
let model_id = model ::ModelId ::new ( models1 . len ( ) as u32 ) ;
2024-02-02 03:43:02 +00:00
2024-01-31 01:41:16 +00:00
//TODO: also detect "CylinderMesh" etc here
let shape = match & object . class [ .. ] {
" Part " = > {
if let Some ( rbx_dom_weak ::types ::Variant ::Enum ( shape ) ) = object . properties . get ( " Shape " ) {
match shape . to_u32 ( ) {
0 = > primitives ::Primitives ::Sphere ,
1 = > primitives ::Primitives ::Cube ,
2 = > primitives ::Primitives ::Cylinder ,
3 = > primitives ::Primitives ::Wedge ,
4 = > primitives ::Primitives ::CornerWedge ,
_ = > panic! ( " Funky roblox PartType= {} ; " , shape . to_u32 ( ) ) ,
}
} else {
panic! ( " Part has no Shape! " ) ;
}
} ,
" TrussPart " = > primitives ::Primitives ::Cube ,
" WedgePart " = > primitives ::Primitives ::Wedge ,
" CornerWedgePart " = > primitives ::Primitives ::CornerWedge ,
_ = > {
println! ( " Unsupported BasePart ClassName= {} ; defaulting to cube " , object . class ) ;
primitives ::Primitives ::Cube
}
} ;
//use the biggest one and cut it down later...
let mut part_texture_description :RobloxPartDescription = [ None , None , None , None , None , None ] ;
temp_objects . clear ( ) ;
recursive_collect_superclass ( & mut temp_objects , & dom , object , " Decal " ) ;
for & decal_ref in & temp_objects {
if let Some ( decal ) = dom . get_by_ref ( decal_ref ) {
if let (
Some ( rbx_dom_weak ::types ::Variant ::Content ( content ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Enum ( normalid ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Color3 ( decal_color3 ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Float32 ( decal_transparency ) ) ,
) = (
decal . properties . get ( " Texture " ) ,
decal . properties . get ( " Face " ) ,
decal . properties . get ( " Color3 " ) ,
decal . properties . get ( " Transparency " ) ,
) {
if let Ok ( asset_id ) = content . clone ( ) . into_string ( ) . parse ::< RobloxAssetId > ( ) {
2024-02-05 04:57:25 +00:00
let render_id = textureless_render_group ;
2024-02-13 04:42:54 +00:00
/* TODO: textures
2024-02-05 04:57:25 +00:00
let render_id = if let Some ( & render_id ) = render_id_from_asset_id . get ( & asset_id . 0 ) {
render_id
2024-01-31 01:41:16 +00:00
} else {
2024-02-08 06:32:52 +00:00
let render_id = RenderConfigId ::new ( unique_render_groups . len ( ) as u32 ) ;
2024-02-05 04:57:25 +00:00
render_id_from_asset_id . insert ( asset_id . 0 , render_id ) ;
unique_render_groups . push ( RenderConfig ::texture ( asset_id . 0 ) ) ;
render_id
2024-01-31 01:41:16 +00:00
} ;
2024-02-05 04:57:25 +00:00
* /
2024-01-31 01:41:16 +00:00
let normal_id = normalid . to_u32 ( ) ;
if normal_id < 6 {
let ( roblox_texture_color , roblox_texture_transform ) = if decal . class = = " Texture " {
//generate tranform
if let (
Some ( rbx_dom_weak ::types ::Variant ::Float32 ( ox ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Float32 ( oy ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Float32 ( sx ) ) ,
Some ( rbx_dom_weak ::types ::Variant ::Float32 ( sy ) ) ,
) = (
decal . properties . get ( " OffsetStudsU " ) ,
decal . properties . get ( " OffsetStudsV " ) ,
decal . properties . get ( " StudsPerTileU " ) ,
decal . properties . get ( " StudsPerTileV " ) ,
)
{
let ( size_u , size_v ) = match normal_id {
0 = > ( size . z , size . y ) , //right
1 = > ( size . x , size . z ) , //top
2 = > ( size . x , size . y ) , //back
3 = > ( size . z , size . y ) , //left
4 = > ( size . x , size . z ) , //bottom
5 = > ( size . x , size . y ) , //front
_ = > panic! ( " unreachable " ) ,
} ;
(
glam ::vec4 ( decal_color3 . r , decal_color3 . g , decal_color3 . b , 1.0 - * decal_transparency ) ,
RobloxTextureTransform {
offset_u :* ox / ( * sx ) , offset_v :* oy / ( * sy ) ,
scale_u :size_u / ( * sx ) , scale_v :size_v / ( * sy ) ,
}
)
} else {
( glam ::Vec4 ::ONE , RobloxTextureTransform ::default ( ) )
}
} else {
( glam ::Vec4 ::ONE , RobloxTextureTransform ::default ( ) )
} ;
part_texture_description [ normal_id as usize ] = Some ( RobloxFaceTextureDescription {
2024-02-05 04:57:25 +00:00
render :render_id ,
2024-01-31 01:41:16 +00:00
color :roblox_texture_color ,
transform :roblox_texture_transform ,
} ) ;
} else {
println! ( " NormalId= {} unsupported for shape= {:?} " , normal_id , shape ) ;
}
}
}
}
}
//obscure rust syntax "slice pattern"
let [
f0 , //Cube::Right
f1 , //Cube::Top
f2 , //Cube::Back
f3 , //Cube::Left
f4 , //Cube::Bottom
f5 , //Cube::Front
] = part_texture_description ;
let basepart_texture_description = match shape {
primitives ::Primitives ::Sphere = > RobloxBasePartDescription ::Sphere ( [ f0 , f1 , f2 , f3 , f4 , f5 ] ) ,
primitives ::Primitives ::Cube = > RobloxBasePartDescription ::Part ( [ f0 , f1 , f2 , f3 , f4 , f5 ] ) ,
primitives ::Primitives ::Cylinder = > RobloxBasePartDescription ::Cylinder ( [ f0 , f1 , f2 , f3 , f4 , f5 ] ) ,
//use front face texture first and use top face texture as a fallback
primitives ::Primitives ::Wedge = > RobloxBasePartDescription ::Wedge ( [
f0 , //Cube::Right->Wedge::Right
if f5 . is_some ( ) { f5 } else { f1 } , //Cube::Front|Cube::Top->Wedge::TopFront
f2 , //Cube::Back->Wedge::Back
f3 , //Cube::Left->Wedge::Left
f4 , //Cube::Bottom->Wedge::Bottom
] ) ,
//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
primitives ::Primitives ::CornerWedge = > RobloxBasePartDescription ::CornerWedge ( [
f0 , //Cube::Right->CornerWedge::Right
if f2 . is_some ( ) { f2 } else { f1 . clone ( ) } , //Cube::Back|Cube::Top->CornerWedge::TopBack
if f3 . is_some ( ) { f3 } else { f1 } , //Cube::Left|Cube::Top->CornerWedge::TopLeft
f4 , //Cube::Bottom->CornerWedge::Bottom
f5 , //Cube::Front->CornerWedge::Front
] ) ,
} ;
//make new model if unit cube has not been created before
2024-02-02 03:43:02 +00:00
let indexed_model_id = if let Some ( & indexed_model_id ) = indexed_model_id_from_description . get ( & basepart_texture_description ) {
2024-01-31 01:41:16 +00:00
//push to existing texture model
2024-02-02 03:43:02 +00:00
indexed_model_id
2024-01-31 01:41:16 +00:00
} else {
2024-02-08 06:32:52 +00:00
let indexed_model_id = model ::MeshId ::new ( meshes . len ( ) as u32 ) ;
2024-02-02 03:43:02 +00:00
indexed_model_id_from_description . insert ( basepart_texture_description . clone ( ) , indexed_model_id ) ; //borrow checker going crazy
2024-02-08 06:32:52 +00:00
meshes . push ( match basepart_texture_description {
2024-01-31 01:41:16 +00:00
RobloxBasePartDescription ::Sphere ( part_texture_description )
| RobloxBasePartDescription ::Cylinder ( part_texture_description )
| RobloxBasePartDescription ::Part ( part_texture_description ) = > {
let mut cube_face_description = primitives ::CubeFaceDescription ::default ( ) ;
for ( face_id , roblox_face_description ) in part_texture_description . iter ( ) . enumerate ( ) {
cube_face_description . insert (
match face_id {
0 = > primitives ::CubeFace ::Right ,
1 = > primitives ::CubeFace ::Top ,
2 = > primitives ::CubeFace ::Back ,
3 = > primitives ::CubeFace ::Left ,
4 = > primitives ::CubeFace ::Bottom ,
5 = > primitives ::CubeFace ::Front ,
_ = > panic! ( " unreachable " ) ,
} ,
match roblox_face_description {
Some ( roblox_texture_transform ) = > roblox_texture_transform . to_face_description ( ) ,
2024-02-05 04:57:25 +00:00
None = > primitives ::FaceDescription ::new_with_render_id ( textureless_render_group ) ,
2024-01-31 01:41:16 +00:00
} ) ;
}
primitives ::generate_partial_unit_cube ( cube_face_description )
} ,
RobloxBasePartDescription ::Wedge ( wedge_texture_description ) = > {
let mut wedge_face_description = primitives ::WedgeFaceDescription ::default ( ) ;
for ( face_id , roblox_face_description ) in wedge_texture_description . iter ( ) . enumerate ( ) {
wedge_face_description . insert (
match face_id {
0 = > primitives ::WedgeFace ::Right ,
1 = > primitives ::WedgeFace ::TopFront ,
2 = > primitives ::WedgeFace ::Back ,
3 = > primitives ::WedgeFace ::Left ,
4 = > primitives ::WedgeFace ::Bottom ,
_ = > panic! ( " unreachable " ) ,
} ,
match roblox_face_description {
Some ( roblox_texture_transform ) = > roblox_texture_transform . to_face_description ( ) ,
2024-02-05 04:57:25 +00:00
None = > primitives ::FaceDescription ::new_with_render_id ( textureless_render_group ) ,
2024-01-31 01:41:16 +00:00
} ) ;
}
primitives ::generate_partial_unit_wedge ( wedge_face_description )
} ,
RobloxBasePartDescription ::CornerWedge ( cornerwedge_texture_description ) = > {
let mut cornerwedge_face_description = primitives ::CornerWedgeFaceDescription ::default ( ) ;
for ( face_id , roblox_face_description ) in cornerwedge_texture_description . iter ( ) . enumerate ( ) {
cornerwedge_face_description . insert (
match face_id {
0 = > primitives ::CornerWedgeFace ::Right ,
1 = > primitives ::CornerWedgeFace ::TopBack ,
2 = > primitives ::CornerWedgeFace ::TopLeft ,
3 = > primitives ::CornerWedgeFace ::Bottom ,
4 = > primitives ::CornerWedgeFace ::Front ,
_ = > panic! ( " unreachable " ) ,
} ,
match roblox_face_description {
Some ( roblox_texture_transform ) = > roblox_texture_transform . to_face_description ( ) ,
2024-02-05 04:57:25 +00:00
None = > primitives ::FaceDescription ::new_with_render_id ( textureless_render_group ) ,
2024-01-31 01:41:16 +00:00
} ) ;
}
primitives ::generate_partial_unit_cornerwedge ( cornerwedge_face_description )
} ,
} ) ;
2024-02-02 03:43:02 +00:00
indexed_model_id
2024-01-31 01:41:16 +00:00
} ;
2024-02-02 03:43:02 +00:00
let attributes = get_attributes (
& object ,
* can_collide ,
Planar64Vec3 ::try_from ( [ velocity . x , velocity . y , velocity . z ] ) . unwrap ( ) ,
model_id ,
& mut modes_builder ,
& mut wormhole_in_model_to_id ,
2024-02-13 04:43:09 +00:00
& mut wormhole_id_to_out_model ,
2024-02-02 03:43:02 +00:00
) ;
models1 . push ( ModelOwnedAttributes {
2024-02-08 06:32:52 +00:00
mesh :indexed_model_id ,
2024-01-31 01:41:16 +00:00
transform :model_transform ,
color :glam ::vec4 ( color3 . r as f32 / 255 f32 , color3 . g as f32 / 255 f32 , color3 . b as f32 / 255 f32 , 1.0 - * transparency ) ,
2024-02-02 03:43:02 +00:00
attributes ,
2024-01-31 01:41:16 +00:00
} ) ;
}
}
}
2024-02-02 03:43:02 +00:00
let models = models1 . into_iter ( ) . enumerate ( ) . map ( | ( model_id , mut model1 ) | {
2024-02-07 06:54:13 +00:00
let model_id = model ::ModelId ::new ( model_id as u32 ) ;
2024-02-02 03:43:02 +00:00
//update attributes with wormhole id
//TODO: errors/prints
if let Some ( wormhole_id ) = wormhole_in_model_to_id . get ( & model_id ) {
if let Some ( & wormhole_out_model_id ) = wormhole_id_to_out_model . get ( wormhole_id ) {
match & mut model1 . attributes {
attr ::CollisionAttributes ::Contact { contacting :_ , general }
| attr ::CollisionAttributes ::Intersect { intersecting :_ , general }
= > general . wormhole = Some ( attr ::Wormhole { destination_model :wormhole_out_model_id } ) ,
attr ::CollisionAttributes ::Decoration = > println! ( " Not a wormhole " ) ,
}
}
}
//index the attributes
let attributes_id = if let Some ( & attributes_id ) = attributes_id_from_attributes . get ( & model1 . attributes ) {
attributes_id
} else {
2024-02-07 06:54:13 +00:00
let attributes_id = attr ::CollisionAttributesId ::new ( unique_attributes . len ( ) as u32 ) ;
2024-02-05 04:57:25 +00:00
attributes_id_from_attributes . insert ( model1 . attributes . clone ( ) , attributes_id ) ;
2024-02-02 03:43:02 +00:00
unique_attributes . push ( model1 . attributes ) ;
attributes_id
} ;
2024-02-08 06:32:52 +00:00
model ::Model {
mesh :model1 . mesh ,
2024-02-02 03:43:02 +00:00
transform :model1 . transform ,
color :model1 . color ,
attributes :attributes_id ,
2024-02-08 06:32:52 +00:00
}
2024-02-02 03:43:02 +00:00
} ) . collect ( ) ;
2024-02-08 06:32:52 +00:00
map ::CompleteMap {
textures :Vec ::new ( ) ,
render_configs :vec ! [ RenderConfig ::default ( ) ] , //asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(),
meshes ,
2024-02-05 06:42:07 +00:00
models ,
2024-02-02 03:43:02 +00:00
modes :modes_builder . build ( ) ,
attributes :unique_attributes ,
2024-01-31 01:41:16 +00:00
}
}