412 lines
16 KiB
Rust
412 lines
16 KiB
Rust
use crate::backend::render::{RenderBackend, ShapeHandle};
|
|
use crate::display_object::{DisplayObject, DisplayObjectBase};
|
|
use crate::player::{RenderContext, UpdateContext};
|
|
use crate::prelude::*;
|
|
use std::collections::HashMap;
|
|
use swf::Twips;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct MorphShape<'gc> {
|
|
base: DisplayObjectBase<'gc>,
|
|
|
|
start: swf::MorphShape,
|
|
|
|
end: swf::MorphShape,
|
|
|
|
frames: HashMap<u16, ShapeHandle>,
|
|
|
|
ratio: u16,
|
|
}
|
|
|
|
impl<'gc> MorphShape<'gc> {
|
|
pub fn from_swf_tag(swf_tag: &swf::DefineMorphShape, renderer: &mut dyn RenderBackend) -> Self {
|
|
// Convert the MorphShape into a normal Shape.
|
|
// TODO(Herschel): impl From in swf crate?
|
|
let mut morph_shape = Self {
|
|
start: swf_tag.start.clone(),
|
|
end: swf_tag.end.clone(),
|
|
base: Default::default(),
|
|
frames: HashMap::new(),
|
|
ratio: 0,
|
|
};
|
|
|
|
morph_shape.register_ratio(renderer, 0);
|
|
morph_shape.register_ratio(renderer, 65535);
|
|
|
|
morph_shape
|
|
}
|
|
|
|
pub fn register_ratio(&mut self, renderer: &mut dyn RenderBackend, ratio: u16) {
|
|
if self.frames.contains_key(&ratio) {
|
|
// Already registered.
|
|
return;
|
|
}
|
|
|
|
// Interpolate MorphShapes into a Shape.
|
|
use swf::{FillStyle, Gradient, LineStyle, ShapeRecord, ShapeStyles};
|
|
// Start shape is ratio 65535, end shape is ratio 0.
|
|
let b = f32::from(ratio) / 65535.0;
|
|
let a = 1.0 - b;
|
|
let fill_styles: Vec<FillStyle> = self
|
|
.start
|
|
.fill_styles
|
|
.iter()
|
|
.zip(self.end.fill_styles.iter())
|
|
.map(|(start, end)| match (start, end) {
|
|
(FillStyle::Color(start), FillStyle::Color(end)) => FillStyle::Color(Color {
|
|
r: (a * f32::from(start.r) + b * f32::from(end.r)) as u8,
|
|
g: (a * f32::from(start.g) + b * f32::from(end.g)) as u8,
|
|
b: (a * f32::from(start.b) + b * f32::from(end.b)) as u8,
|
|
a: (a * f32::from(start.a) + b * f32::from(end.a)) as u8,
|
|
}),
|
|
(FillStyle::LinearGradient(start), FillStyle::LinearGradient(end)) => {
|
|
let records: Vec<swf::GradientRecord> = start
|
|
.records
|
|
.iter()
|
|
.zip(end.records.iter())
|
|
.map(|(start, end)| swf::GradientRecord {
|
|
ratio: (f32::from(start.ratio) * a + f32::from(end.ratio) * b) as u8,
|
|
color: Color {
|
|
r: (a * f32::from(start.color.r) + b * f32::from(end.color.r))
|
|
as u8,
|
|
g: (a * f32::from(start.color.g) + b * f32::from(end.color.g))
|
|
as u8,
|
|
b: (a * f32::from(start.color.b) + b * f32::from(end.color.b))
|
|
as u8,
|
|
a: (a * f32::from(start.color.a) + b * f32::from(end.color.a))
|
|
as u8,
|
|
},
|
|
})
|
|
.collect();
|
|
|
|
FillStyle::LinearGradient(Gradient {
|
|
matrix: start.matrix.clone(),
|
|
spread: start.spread,
|
|
interpolation: start.interpolation,
|
|
records,
|
|
})
|
|
}
|
|
_ => {
|
|
log::info!("Unhandled morph shape combination: {:?} {:?}", start, end);
|
|
start.clone()
|
|
}
|
|
})
|
|
.collect();
|
|
let line_styles: Vec<LineStyle> = self
|
|
.start
|
|
.line_styles
|
|
.iter()
|
|
.zip(self.end.line_styles.iter())
|
|
.map(|(start, end)| LineStyle {
|
|
width: Twips::new(
|
|
((start.width.get() as f32) * a + (end.width.get() as f32) * b) as i32,
|
|
),
|
|
color: Color {
|
|
r: (a * f32::from(start.color.r) + b * f32::from(end.color.r)) as u8,
|
|
g: (a * f32::from(start.color.g) + b * f32::from(end.color.g)) as u8,
|
|
b: (a * f32::from(start.color.b) + b * f32::from(end.color.b)) as u8,
|
|
a: (a * f32::from(start.color.a) + b * f32::from(end.color.a)) as u8,
|
|
},
|
|
start_cap: start.start_cap,
|
|
end_cap: start.end_cap,
|
|
join_style: start.join_style,
|
|
fill_style: None,
|
|
allow_scale_x: start.allow_scale_x,
|
|
allow_scale_y: start.allow_scale_y,
|
|
is_pixel_hinted: start.is_pixel_hinted,
|
|
allow_close: start.allow_close,
|
|
})
|
|
.collect();
|
|
|
|
let mut shape = Vec::with_capacity(self.start.shape.len());
|
|
let mut start_iter = self.start.shape.iter();
|
|
let mut end_iter = self.end.shape.iter();
|
|
let mut start = start_iter.next();
|
|
let mut end = end_iter.next();
|
|
let mut start_x = Twips::new(0);
|
|
let mut start_y = Twips::new(0);
|
|
let mut end_x = Twips::new(0);
|
|
let mut end_y = Twips::new(0);
|
|
// TODO: Feels like this could be cleaned up a bit.
|
|
// We step through both the start records and end records, interpolating edges pairwise.
|
|
// Fill style/line style changes should only appear in the start records.
|
|
// However, StyleChangeRecord move_to can appear it both start and end records,
|
|
// and not necessarily in matching pairs; therefore, we have to keep track of the pen position
|
|
// in case one side is missing a move_to; it will implicitly use the last pen position.
|
|
while let (Some(s), Some(e)) = (start, end) {
|
|
match (s, e) {
|
|
(ShapeRecord::StyleChange(start_change), ShapeRecord::StyleChange(end_change)) => {
|
|
let mut style_change = start_change.clone();
|
|
if let Some((s_x, s_y)) = start_change.move_to {
|
|
if let Some((e_x, e_y)) = end_change.move_to {
|
|
start_x = s_x;
|
|
start_y = s_y;
|
|
end_x = e_x;
|
|
end_y = e_y;
|
|
style_change.move_to = Some((
|
|
Twips::new(
|
|
(start_x.get() as f32 * a + end_x.get() as f32 * b) as i32,
|
|
),
|
|
Twips::new(
|
|
(start_y.get() as f32 * a + end_y.get() as f32 * b) as i32,
|
|
),
|
|
));
|
|
} else {
|
|
panic!("Expected move_to for morph shape")
|
|
}
|
|
}
|
|
shape.push(ShapeRecord::StyleChange(style_change));
|
|
start = start_iter.next();
|
|
end = end_iter.next();
|
|
}
|
|
(ShapeRecord::StyleChange(start_change), _) => {
|
|
let mut style_change = start_change.clone();
|
|
if let Some((s_x, s_y)) = start_change.move_to {
|
|
start_x = s_x;
|
|
start_y = s_y;
|
|
style_change.move_to = Some((
|
|
Twips::new((start_x.get() as f32 * a + end_x.get() as f32 * b) as i32),
|
|
Twips::new((start_y.get() as f32 * a + end_y.get() as f32 * b) as i32),
|
|
));
|
|
}
|
|
shape.push(ShapeRecord::StyleChange(style_change));
|
|
Self::update_pos(&mut start_x, &mut start_y, s);
|
|
start = start_iter.next();
|
|
}
|
|
(_, ShapeRecord::StyleChange(end_change)) => {
|
|
let mut style_change = end_change.clone();
|
|
if let Some((e_x, e_y)) = end_change.move_to {
|
|
end_x = e_x;
|
|
end_y = e_y;
|
|
style_change.move_to = Some((
|
|
Twips::new((start_x.get() as f32 * a + end_x.get() as f32 * b) as i32),
|
|
Twips::new((start_y.get() as f32 * a + end_y.get() as f32 * b) as i32),
|
|
));
|
|
}
|
|
shape.push(ShapeRecord::StyleChange(style_change));
|
|
Self::update_pos(&mut end_x, &mut end_y, s);
|
|
end = end_iter.next();
|
|
continue;
|
|
}
|
|
_ => {
|
|
shape.push(Self::interpolate_edges(s, e, a));
|
|
Self::update_pos(&mut start_x, &mut start_y, s);
|
|
Self::update_pos(&mut end_x, &mut end_y, e);
|
|
start = start_iter.next();
|
|
end = end_iter.next();
|
|
}
|
|
}
|
|
}
|
|
|
|
let styles = ShapeStyles {
|
|
fill_styles,
|
|
line_styles,
|
|
};
|
|
|
|
let bounds = crate::shape_utils::calculate_shape_bounds(&shape[..]);
|
|
let shape = swf::Shape {
|
|
version: 4,
|
|
id: 0,
|
|
shape_bounds: bounds.clone(),
|
|
edge_bounds: bounds,
|
|
has_fill_winding_rule: false,
|
|
has_non_scaling_strokes: false,
|
|
has_scaling_strokes: true,
|
|
styles,
|
|
shape,
|
|
};
|
|
|
|
let shape_handle = renderer.register_shape(&shape);
|
|
self.frames.insert(ratio, shape_handle);
|
|
}
|
|
|
|
fn update_pos(x: &mut Twips, y: &mut Twips, record: &swf::ShapeRecord) {
|
|
use swf::ShapeRecord;
|
|
match record {
|
|
ShapeRecord::StraightEdge { delta_x, delta_y } => {
|
|
*x += *delta_x;
|
|
*y += *delta_y;
|
|
}
|
|
ShapeRecord::CurvedEdge {
|
|
control_delta_x,
|
|
control_delta_y,
|
|
anchor_delta_x,
|
|
anchor_delta_y,
|
|
} => {
|
|
*x += *control_delta_x + *anchor_delta_x;
|
|
*y += *control_delta_y + *anchor_delta_y;
|
|
}
|
|
ShapeRecord::StyleChange(ref style_change) => {
|
|
if let Some((move_x, move_y)) = style_change.move_to {
|
|
*x = move_x;
|
|
*y = move_y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn interpolate_edges(
|
|
start: &swf::ShapeRecord,
|
|
end: &swf::ShapeRecord,
|
|
a: f32,
|
|
) -> swf::ShapeRecord {
|
|
use swf::ShapeRecord;
|
|
let b = 1.0 - a;
|
|
match (start, end) {
|
|
(
|
|
ShapeRecord::StraightEdge {
|
|
delta_x: start_dx,
|
|
delta_y: start_dy,
|
|
},
|
|
ShapeRecord::StraightEdge {
|
|
delta_x: end_dx,
|
|
delta_y: end_dy,
|
|
},
|
|
) => ShapeRecord::StraightEdge {
|
|
delta_x: Twips::new((start_dx.get() as f32 * a + end_dx.get() as f32 * b) as i32),
|
|
delta_y: Twips::new((start_dy.get() as f32 * a + end_dy.get() as f32 * b) as i32),
|
|
},
|
|
|
|
(
|
|
ShapeRecord::CurvedEdge {
|
|
control_delta_x: start_cdx,
|
|
control_delta_y: start_cdy,
|
|
anchor_delta_x: start_adx,
|
|
anchor_delta_y: start_ady,
|
|
},
|
|
ShapeRecord::CurvedEdge {
|
|
control_delta_x: end_cdx,
|
|
control_delta_y: end_cdy,
|
|
anchor_delta_x: end_adx,
|
|
anchor_delta_y: end_ady,
|
|
},
|
|
) => ShapeRecord::CurvedEdge {
|
|
control_delta_x: Twips::new(
|
|
(start_cdx.get() as f32 * a + end_cdx.get() as f32 * b) as i32,
|
|
),
|
|
control_delta_y: Twips::new(
|
|
(start_cdy.get() as f32 * a + end_cdy.get() as f32 * b) as i32,
|
|
),
|
|
anchor_delta_x: Twips::new(
|
|
(start_adx.get() as f32 * a + end_adx.get() as f32 * b) as i32,
|
|
),
|
|
anchor_delta_y: Twips::new(
|
|
(start_ady.get() as f32 * a + end_ady.get() as f32 * b) as i32,
|
|
),
|
|
},
|
|
|
|
(
|
|
ShapeRecord::StraightEdge {
|
|
delta_x: start_dx,
|
|
delta_y: start_dy,
|
|
},
|
|
ShapeRecord::CurvedEdge {
|
|
control_delta_x: end_cdx,
|
|
control_delta_y: end_cdy,
|
|
anchor_delta_x: end_adx,
|
|
anchor_delta_y: end_ady,
|
|
},
|
|
) => {
|
|
let start_cdx = *start_dx / 2;
|
|
let start_cdy = *start_dy / 2;
|
|
let start_adx = start_cdx;
|
|
let start_ady = start_cdy;
|
|
ShapeRecord::CurvedEdge {
|
|
control_delta_x: Twips::new(
|
|
(start_cdx.get() as f32 * a + end_cdx.get() as f32 * b) as i32,
|
|
),
|
|
control_delta_y: Twips::new(
|
|
(start_cdy.get() as f32 * a + end_cdy.get() as f32 * b) as i32,
|
|
),
|
|
anchor_delta_x: Twips::new(
|
|
(start_adx.get() as f32 * a + end_adx.get() as f32 * b) as i32,
|
|
),
|
|
anchor_delta_y: Twips::new(
|
|
(start_ady.get() as f32 * a + end_ady.get() as f32 * b) as i32,
|
|
),
|
|
}
|
|
}
|
|
|
|
(
|
|
ShapeRecord::CurvedEdge {
|
|
control_delta_x: start_cdx,
|
|
control_delta_y: start_cdy,
|
|
anchor_delta_x: start_adx,
|
|
anchor_delta_y: start_ady,
|
|
},
|
|
ShapeRecord::StraightEdge {
|
|
delta_x: end_dx,
|
|
delta_y: end_dy,
|
|
},
|
|
) => {
|
|
let end_cdx = *end_dx / 2;
|
|
let end_cdy = *end_dy / 2;
|
|
let end_adx = end_cdx;
|
|
let end_ady = end_cdy;
|
|
ShapeRecord::CurvedEdge {
|
|
control_delta_x: Twips::new(
|
|
(start_cdx.get() as f32 * a + end_cdx.get() as f32 * b) as i32,
|
|
),
|
|
control_delta_y: Twips::new(
|
|
(start_cdy.get() as f32 * a + end_cdy.get() as f32 * b) as i32,
|
|
),
|
|
anchor_delta_x: Twips::new(
|
|
(start_adx.get() as f32 * a + end_adx.get() as f32 * b) as i32,
|
|
),
|
|
anchor_delta_y: Twips::new(
|
|
(start_ady.get() as f32 * a + end_ady.get() as f32 * b) as i32,
|
|
),
|
|
}
|
|
}
|
|
_ => unreachable!("{:?} {:?}", start, end),
|
|
}
|
|
}
|
|
|
|
pub fn ratio(&self) -> u16 {
|
|
self.ratio
|
|
}
|
|
|
|
pub fn set_ratio(&mut self, ratio: u16) {
|
|
self.ratio = ratio;
|
|
}
|
|
}
|
|
|
|
impl<'gc> DisplayObject<'gc> for MorphShape<'gc> {
|
|
impl_display_object!(base);
|
|
|
|
fn as_morph_shape(&self) -> Option<&Self> {
|
|
Some(self)
|
|
}
|
|
|
|
fn as_morph_shape_mut(&mut self) -> Option<&mut Self> {
|
|
Some(self)
|
|
}
|
|
|
|
fn run_frame(&mut self, context: &mut UpdateContext) {
|
|
if !self.frames.contains_key(&self.ratio) {
|
|
self.register_ratio(context.renderer, self.ratio);
|
|
}
|
|
}
|
|
|
|
fn render(&self, context: &mut RenderContext) {
|
|
context.transform_stack.push(self.transform());
|
|
|
|
if let Some(shape) = self.frames.get(&self.ratio) {
|
|
context
|
|
.renderer
|
|
.render_shape(*shape, context.transform_stack.transform());
|
|
} else {
|
|
warn!("Missing ratio for morph shape");
|
|
}
|
|
|
|
context.transform_stack.pop();
|
|
}
|
|
}
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for MorphShape<'gc> {
|
|
#[inline]
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
self.base.trace(cc);
|
|
}
|
|
}
|