Beatsaber Rust Utilities: A Beatsaber V3 parsing library.
beatsaber beatmap
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Moved distribution logic to `DistributionType`.

+254 -47
+210 -1
src/difficulty/lightshow.rs
··· 5 5 pub mod rotation; 6 6 pub mod translation; 7 7 8 + use crate::difficulty::lightshow::easing::Easing; 9 + use crate::difficulty::lightshow::filter::Filter; 8 10 use crate::loose_enum; 9 11 10 12 loose_enum! { ··· 27 29 Wave = 1, 28 30 Step = 2, 29 31 } 32 + } 33 + 34 + impl DistributionType { 35 + fn compute_offset( 36 + &self, 37 + light_id: i32, 38 + group_size: i32, 39 + filter: &Filter, 40 + dist_value: f32, 41 + last_data_offset: Option<f32>, 42 + easing: Option<Easing>, 43 + ) -> f32 { 44 + let filtered_size = filter.count_filtered(group_size) as f32; 45 + let filtered_id = filter.get_relative_index(light_id, group_size) as f32; 46 + 47 + match self { 48 + DistributionType::Wave => { 49 + let mut modified_value = dist_value; 50 + if let Some(offset) = last_data_offset { 51 + modified_value -= offset; 52 + } 53 + 54 + let mut fraction = filtered_id / filtered_size; 55 + if let Some(easing) = easing { 56 + fraction = easing.ease(fraction); 57 + } 58 + 59 + fraction * modified_value.max(0.0) 60 + } 61 + DistributionType::Step => dist_value * filtered_id, 62 + DistributionType::Unknown(_) => 0.0, 63 + } 64 + } 65 + } 66 + 67 + #[macro_export] 68 + macro_rules! impl_get_beat_offset { 69 + ($ident:ident) => { 70 + impl $ident { 71 + pub fn get_beat_offset(&self, light_id: i32, group_size: i32) -> f32 { 72 + self.beat_dist_type.compute_offset( 73 + light_id, 74 + group_size, 75 + &self.filter, 76 + self.beat_dist_value, 77 + self.data.last().map(|data| data.beat_offset), 78 + None, 79 + ) 80 + } 81 + } 82 + }; 30 83 } 31 84 32 85 loose_enum! { 33 86 /// Controls how the value is changed from the previous event. 34 87 /// - Transition: The value will blend from the previous event's value, using the 35 - /// [easing](easing::Easing) value. 88 + /// [easing](Easing) value. 36 89 /// - Extend: The event's value will be ignored, replaced with the values from the previous event. 37 90 /// 38 91 /// More info [here](https://bsmg.wiki/mapping/map-format/lightshow.html#light-rotation-events-type). ··· 53 106 Z = 2, 54 107 } 55 108 } 109 + 110 + // More readable concrete tests are available in the `color` module. 111 + #[cfg(test)] 112 + mod tests { 113 + use super::*; 114 + use crate::difficulty::lightshow::filter::FilterType; 115 + use crate::utils::LooseBool; 116 + 117 + #[test] 118 + fn wave() { 119 + for i in 0..12 { 120 + assert_eq!( 121 + DistributionType::Wave.compute_offset(i, 12, &Filter::default(), 12.0, None, None), 122 + i as f32 123 + ); 124 + } 125 + } 126 + 127 + #[test] 128 + fn step() { 129 + for i in 0..12 { 130 + assert_eq!( 131 + DistributionType::Step.compute_offset(i, 12, &Filter::default(), 1.0, None, None), 132 + i as f32 133 + ); 134 + } 135 + } 136 + 137 + #[test] 138 + fn wave_with_division_filter() { 139 + for i in 0..6 { 140 + assert_eq!( 141 + DistributionType::Wave.compute_offset( 142 + i + 6, 143 + 12, 144 + &Filter { 145 + filter_type: FilterType::Division, 146 + parameter1: 2, 147 + parameter2: 1, 148 + ..Default::default() 149 + }, 150 + 6.0, 151 + None, 152 + None 153 + ), 154 + i as f32 155 + ); 156 + } 157 + } 158 + 159 + #[test] 160 + fn step_with_division_filter() { 161 + for i in 0..6 { 162 + assert_eq!( 163 + DistributionType::Step.compute_offset( 164 + i + 6, 165 + 12, 166 + &Filter { 167 + filter_type: FilterType::Division, 168 + parameter1: 2, 169 + parameter2: 1, 170 + ..Default::default() 171 + }, 172 + 1.0, 173 + None, 174 + None 175 + ), 176 + i as f32 177 + ); 178 + } 179 + } 180 + 181 + #[test] 182 + fn wave_with_step_filter() { 183 + for i in 0..6 { 184 + assert_eq!( 185 + DistributionType::Wave.compute_offset( 186 + i * 2, 187 + 12, 188 + &Filter { 189 + filter_type: FilterType::StepAndOffset, 190 + parameter1: 0, 191 + parameter2: 2, 192 + ..Default::default() 193 + }, 194 + 6.0, 195 + None, 196 + None 197 + ), 198 + i as f32 199 + ); 200 + } 201 + } 202 + 203 + #[test] 204 + fn step_with_step_filter() { 205 + for i in 0..6 { 206 + assert_eq!( 207 + DistributionType::Step.compute_offset( 208 + i * 2, 209 + 12, 210 + &Filter { 211 + filter_type: FilterType::StepAndOffset, 212 + parameter1: 0, 213 + parameter2: 2, 214 + ..Default::default() 215 + }, 216 + 1.0, 217 + None, 218 + None 219 + ), 220 + i as f32 221 + ); 222 + } 223 + } 224 + 225 + #[test] 226 + fn wave_with_reverse_filter() { 227 + for i in 0..12 { 228 + assert_eq!( 229 + DistributionType::Wave.compute_offset( 230 + i, 231 + 12, 232 + &Filter { 233 + reverse: LooseBool::True, 234 + ..Default::default() 235 + }, 236 + 12.0, 237 + None, 238 + None 239 + ), 240 + 12.0 - i as f32 241 + ); 242 + } 243 + } 244 + 245 + #[test] 246 + fn step_with_reverse_filter() { 247 + for i in 0..12 { 248 + assert_eq!( 249 + DistributionType::Step.compute_offset( 250 + i, 251 + 12, 252 + &Filter { 253 + reverse: LooseBool::True, 254 + ..Default::default() 255 + }, 256 + 1.0, 257 + None, 258 + None 259 + ), 260 + 12.0 - i as f32 261 + ); 262 + } 263 + } 264 + }
+11 -42
src/difficulty/lightshow/color.rs
··· 2 2 use crate::difficulty::lightshow::easing::Easing; 3 3 use crate::difficulty::lightshow::filter::Filter; 4 4 use crate::utils::LooseBool; 5 - use crate::{impl_timed, loose_enum}; 5 + use crate::{impl_get_beat_offset, impl_timed, loose_enum}; 6 6 use serde::{Deserialize, Serialize}; 7 7 8 8 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] ··· 38 38 pub data: Vec<ColorEventData>, 39 39 } 40 40 41 + impl_get_beat_offset!(ColorEventGroup); 42 + 41 43 impl ColorEventGroup { 42 - pub fn get_beat_offset(&self, light_id: i32, group_size: i32) -> f32 { 43 - let filtered_size = self.filter.count_filtered(group_size) as f32; 44 - let filtered_id = self.filter.get_relative_index(light_id, group_size) as f32; 45 - 46 - match self.beat_dist_type { 47 - DistributionType::Wave => { 48 - let mut modified_value = self.beat_dist_value; 49 - 50 - if let Some(data) = self.data.last() { 51 - modified_value -= data.beat_offset; 52 - } 53 - 54 - let fraction = filtered_id / filtered_size; 55 - fraction * modified_value.max(0.0) 56 - } 57 - DistributionType::Step => self.beat_dist_value * filtered_id, 58 - DistributionType::Unknown(_) => 0.0, 59 - } 60 - } 61 - 62 44 pub fn get_brightness_offset(&self, light_id: i32, group_size: i32) -> f32 { 63 - let filtered_size = self.filter.count_filtered(group_size) as f32; 64 - let filtered_id = self.filter.get_relative_index(light_id, group_size) as f32; 65 - 66 - match self.bright_dist_type { 67 - DistributionType::Wave => { 68 - let mut modified_value = self.bright_dist_value; 69 - 70 - if let Some(data) = self.data.last() { 71 - modified_value -= data.beat_offset; 72 - } 73 - 74 - let fraction = match self.bright_dist_easing { 75 - Some(easing) => easing.ease(filtered_id / filtered_size), 76 - None => filtered_id / filtered_size, 77 - }; 78 - 79 - fraction * modified_value.max(0.0) 80 - } 81 - DistributionType::Step => self.bright_dist_value * filtered_id, 82 - DistributionType::Unknown(_) => 0.0, 83 - } 45 + self.bright_dist_type.compute_offset( 46 + light_id, 47 + group_size, 48 + &self.filter, 49 + self.bright_dist_value, 50 + self.data.last().map(|data| data.beat_offset), 51 + self.bright_dist_easing, 52 + ) 84 53 } 85 54 } 86 55
-1
src/difficulty/lightshow/filter.rs
··· 253 253 ..Default::default() 254 254 }; 255 255 256 - println!("{outer}"); 257 256 assert!((0..outer).all(|i| !filter.is_in_filter(i, 12))); 258 257 assert!((outer..12).all(|i| filter.is_in_filter(i, 12))); 259 258 assert_eq!(filter.count_filtered(12), 12 - outer);
+16 -1
src/difficulty/lightshow/rotation.rs
··· 2 2 use crate::difficulty::lightshow::filter::Filter; 3 3 use crate::difficulty::lightshow::{Axis, DistributionType, TransitionType}; 4 4 use crate::utils::LooseBool; 5 - use crate::{impl_timed, loose_enum}; 5 + use crate::{impl_get_beat_offset, impl_timed, loose_enum}; 6 6 use serde::{Deserialize, Serialize}; 7 7 8 8 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] ··· 40 40 pub invert_axis: LooseBool, 41 41 #[serde(rename = "l")] 42 42 pub data: Vec<RotationEventData>, 43 + } 44 + 45 + impl_get_beat_offset!(RotationEventGroup); 46 + 47 + impl RotationEventGroup { 48 + pub fn get_rotation_offset(&self, light_id: i32, group_size: i32) -> f32 { 49 + self.rotation_dist_type.compute_offset( 50 + light_id, 51 + group_size, 52 + &self.filter, 53 + self.rotation_dist_value, 54 + self.data.last().map(|data| data.beat_offset), 55 + self.rotation_dist_easing, 56 + ) 57 + } 43 58 } 44 59 45 60 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+16 -1
src/difficulty/lightshow/translation.rs
··· 1 1 use crate::difficulty::lightshow::easing::Easing; 2 2 use crate::difficulty::lightshow::filter::Filter; 3 3 use crate::difficulty::lightshow::{Axis, DistributionType, TransitionType}; 4 - use crate::impl_timed; 5 4 use crate::utils::LooseBool; 5 + use crate::{impl_get_beat_offset, impl_timed}; 6 6 use serde::{Deserialize, Serialize}; 7 7 8 8 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] ··· 39 39 pub invert_axis: LooseBool, 40 40 #[serde(rename = "l")] 41 41 pub data: Vec<TranslationEventData>, 42 + } 43 + 44 + impl_get_beat_offset!(TranslationEventGroup); 45 + 46 + impl TranslationEventGroup { 47 + pub fn get_translation_offset(&self, light_id: i32, group_size: i32) -> f32 { 48 + self.translation_dist_type.compute_offset( 49 + light_id, 50 + group_size, 51 + &self.filter, 52 + self.translation_dist_value, 53 + self.data.last().map(|data| data.beat_offset), 54 + Some(self.translation_dist_easing), 55 + ) 56 + } 42 57 } 43 58 44 59 #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
+1 -1
src/utils.rs
··· 58 58 impl From<$name> for String { 59 59 fn from(value: $name) -> Self { 60 60 match value { 61 - $( $name::$variant => $value, )+ 61 + $( $name::$variant => $value.to_string(), )+ 62 62 $name::Unknown(val) => val, 63 63 } 64 64 }