use crate::graphics::data::mamodel::{Model, ModelPart};
#[derive(Clone, Copy, Debug)]
pub struct Vector {
pub x: f32,
pub y: f32,
}
#[derive(Clone, Copy, Debug)]
pub struct WorldTransform {
pub matrix: [f32; 9],
pub opacity: f32,
pub z_order: i32,
pub sprite_index: usize,
pub pivot: Vector,
pub hidden: bool,
pub glow: i32,
pub part_index: usize,
}
#[derive(Clone, Copy, Debug)]
struct LocalState {
x: f64,
y: f64,
scale_x: f64,
scale_y: f64,
angle: f64,
opacity: f64,
flip_x: f64,
flip_y: f64,
}
#[derive(Clone, Copy, Debug)]
struct GlobalState {
scale_x: f64,
scale_y: f64,
angle: f64,
flip_x: f64,
flip_y: f64,
opacity: f64,
}
impl Default for GlobalState {
fn default() -> Self {
Self {
scale_x: 1.0,
scale_y: 1.0,
angle: 0.0,
flip_x: 1.0,
flip_y: 1.0,
opacity: 1.0,
}
}
}
pub fn solve_hierarchy(parts: &[ModelPart], model: &Model) -> Vec<WorldTransform> {
let mut results = Vec::with_capacity(parts.len());
for (part_index, _) in parts.iter().enumerate() {
results.push(solve_single_part(part_index, parts, model));
}
results.sort_by(|part_a, part_b| {
part_a.z_order.cmp(&part_b.z_order)
.then(part_a.part_index.cmp(&part_b.part_index))
});
results
}
fn solve_single_part(target_index: usize, parts: &[ModelPart], model: &Model) -> WorldTransform {
let target_part = &parts[target_index];
let mut chain = Vec::new();
let mut current_index = target_index;
let mut safety_counter = 0;
loop {
chain.push(current_index);
let next_parent = parts[current_index].parent_id;
if next_parent == -1 { break; }
if next_parent as usize == current_index { break; }
current_index = next_parent as usize;
if current_index >= parts.len() { break; }
safety_counter += 1;
if safety_counter > 100 { break; }
}
let mut global_states = Vec::with_capacity(chain.len());
let mut current_global = GlobalState::default();
for &part_index in chain.iter().rev() {
let local_state = get_local_state(&parts[part_index], model);
let new_flip_x = local_state.flip_x * current_global.flip_x;
let new_flip_y = local_state.flip_y * current_global.flip_y;
let new_scale_x = local_state.scale_x * current_global.scale_x;
let new_scale_y = local_state.scale_y * current_global.scale_y;
let new_angle = local_state.angle * new_flip_x * new_flip_y + current_global.angle;
let new_opacity = local_state.opacity * current_global.opacity;
current_global = GlobalState {
scale_x: new_scale_x,
scale_y: new_scale_y,
angle: new_angle,
flip_x: new_flip_x,
flip_y: new_flip_y,
opacity: new_opacity,
};
global_states.push(current_global);
}
struct VectorStep {
position: [f64; 2],
matrix_scale: [f64; 2],
matrix_rotation: [f64; 4],
}
let mut vector_steps = Vec::with_capacity(chain.len());
if chain.len() > 1 {
for step_index in 0..chain.len() - 1 {
let child_index = chain[chain.len() - 1 - (step_index + 1)];
let parent_index = chain[chain.len() - 1 - step_index];
let child_local_state = get_local_state(&parts[child_index], model);
let parent_local_state = get_local_state(&parts[parent_index], model);
let parent_global_flip_x = global_states[step_index].flip_x;
let parent_global_flip_y = global_states[step_index].flip_y;
let position = [child_local_state.x, -child_local_state.y];
let scale_x_computed = parent_local_state.scale_x * parent_local_state.flip_x;
let scale_y_computed = parent_local_state.scale_y * parent_local_state.flip_y;
let angle_radians = parent_local_state.angle.to_radians() * parent_global_flip_x * parent_global_flip_y;
let cosine_value = angle_radians.cos();
let sine_value = angle_radians.sin();
let rotation_matrix = [cosine_value, sine_value, -sine_value, cosine_value];
vector_steps.push(VectorStep {
position,
matrix_scale: [scale_x_computed, scale_y_computed],
matrix_rotation: rotation_matrix,
});
}
}
let step_count = vector_steps.len();
for apply_index in 0..step_count {
let current_scale = vector_steps[apply_index].matrix_scale;
for modify_index in apply_index..step_count {
vector_steps[modify_index].position[0] *= current_scale[0];
vector_steps[modify_index].position[1] *= current_scale[1];
}
}
let root_index = chain.last().copied().unwrap_or(target_index);
let root_state = get_local_state(&parts[root_index], model);
let root_part = &parts[root_index];
let mut final_position = [
(root_state.x + root_part.pivot_x as f64) * root_state.scale_x * root_state.flip_x,
-(root_state.y + root_part.pivot_y as f64) * root_state.scale_y * root_state.flip_y
];
for apply_index in 0..step_count {
let current_rotation_matrix = vector_steps[apply_index].matrix_rotation;
for modify_index in apply_index..step_count {
let x = vector_steps[modify_index].position[0];
let y = vector_steps[modify_index].position[1];
let new_x = x * current_rotation_matrix[0] + y * current_rotation_matrix[1];
let new_y = x * current_rotation_matrix[2] + y * current_rotation_matrix[3];
vector_steps[modify_index].position = [new_x, new_y];
}
final_position[0] += vector_steps[apply_index].position[0];
final_position[1] += vector_steps[apply_index].position[1];
}
let target_global = global_states.last().copied().unwrap_or(current_global);
let final_scale_x = target_global.scale_x * target_global.flip_x;
let final_scale_y = target_global.scale_y * target_global.flip_y;
let angle_radians = target_global.angle.to_radians();
let cosine_final = angle_radians.cos();
let sine_final = angle_radians.sin();
let matrix = [
(final_scale_x * cosine_final) as f32, (final_scale_x * sine_final) as f32, 0.0,
(-final_scale_y * sine_final) as f32, (final_scale_y * cosine_final) as f32, 0.0,
final_position[0] as f32, -final_position[1] as f32, 1.0
];
WorldTransform {
matrix,
opacity: target_global.opacity as f32,
z_order: target_part.drawing_layer,
sprite_index: target_part.sprite_index as usize,
pivot: Vector { x: target_part.pivot_x, y: target_part.pivot_y },
hidden: target_part.unit_id == -1 || target_part.sprite_index == -1 || target_global.opacity < 0.001,
glow: target_part.glow_mode,
part_index: target_index,
}
}
fn get_local_state(part: &ModelPart, model: &Model) -> LocalState {
let scale_unit = if model.scale_unit == 0.0 { 1000.0 } else { model.scale_unit as f64 };
let angle_unit = if model.angle_unit == 0.0 { 1000.0 } else { model.angle_unit as f64 };
let alpha_unit = if model.alpha_unit == 0.0 { 1000.0 } else { model.alpha_unit as f64 };
LocalState {
x: part.position_x as f64,
y: part.position_y as f64,
scale_x: part.scale_x as f64 / scale_unit,
scale_y: part.scale_y as f64 / scale_unit,
angle: (part.rotation as f64) * 360.0 / angle_unit,
opacity: part.alpha as f64 / alpha_unit,
flip_x: if part.flip_x { -1.0 } else { 1.0 },
flip_y: if part.flip_y { -1.0 } else { 1.0 },
}
}