This commit is contained in:
Mike Welsh 2019-04-26 18:55:06 -07:00
parent 3a2b11c511
commit ba6843cf55
16 changed files with 314 additions and 225 deletions

View File

@ -6,6 +6,12 @@ edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
name = "fluster"
path = "src/lib.rs"
[[bin]]
name = "fluster"
path = "src/lib.rs"
[features]
default = ["console_error_panic_hook", "console_log"]
@ -15,7 +21,14 @@ bacon_rajan_cc = "0.2"
log = "0.4"
url = "1.7.2"
svg = "0.5.12"
swf = { path = "../swf-rs", version = "*" }
swf = { git = "https://github.com/Herschel/swf-rs", version = "*" }
# Desktop dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
structopt = "0.2.15"
glium = "0.24"
glutin = "0.20"
winit = "0.19.1"
# Wasm32 dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
@ -32,4 +45,7 @@ version = "0.3.19"
features = ["CanvasRenderingContext2d", "HtmlCanvasElement", "Performance", "HtmlImageElement", "Window"]
[dev-dependencies]
wasm-bindgen-test = "0.2"
wasm-bindgen-test = "0.2"
[patch.'https://github.com/Herschel/swf-rs']
swf = { path = "../swf-rs" }

11
src/backend.rs Normal file
View File

@ -0,0 +1,11 @@
pub mod render;
pub mod ui;
use render::RenderBackend;
use ui::UiBackend;
pub fn build() -> Result<(Box<UiBackend>, Box<RenderBackend>), Box<std::error::Error>> {
let mut ui = ui::glutin::GlutinBackend::new(500, 500)?;
let renderer = render::glium::GliumRenderBackend::new(&mut ui)?;
Ok((Box::new(ui), Box::new(renderer)))
}

7
src/backend/render.rs Normal file
View File

@ -0,0 +1,7 @@
pub mod glium;
use swf::Shape;
pub trait RenderBackend {
//fn register_shape(shape: &Shape) {}
}

View File

@ -0,0 +1,17 @@
use super::RenderBackend;
use crate::backend::ui::glutin::GlutinBackend;
use glium::Display;
use glutin::Context;
pub struct GliumRenderBackend {
display: Display,
}
impl GliumRenderBackend {
pub fn new(ui: &mut GlutinBackend) -> Result<GliumRenderBackend, Box<std::error::Error>> {
let display = Display::from_gl_window(ui.take_context())?;
Ok(GliumRenderBackend { display })
}
}
impl RenderBackend for GliumRenderBackend {}

5
src/backend/ui.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod glutin;
pub trait UiBackend {
fn poll_events(&mut self) -> bool;
}

38
src/backend/ui/glutin.rs Normal file
View File

@ -0,0 +1,38 @@
use super::UiBackend;
use glutin::{ContextBuilder, Event, EventsLoop, WindowBuilder, WindowEvent, WindowedContext};
pub struct GlutinBackend {
context: Option<WindowedContext>,
events_loop: EventsLoop,
}
impl GlutinBackend {
pub fn new(width: u32, height: u32) -> Result<GlutinBackend, Box<std::error::Error>> {
let events_loop = EventsLoop::new();
let window_builder = WindowBuilder::new().with_dimensions((width, height).into());
let context = ContextBuilder::new().build_windowed(window_builder, &events_loop)?;
Ok(GlutinBackend {
context: Some(context),
events_loop,
})
}
pub fn take_context(&mut self) -> WindowedContext {
self.context.take().unwrap()
}
}
impl UiBackend for GlutinBackend {
fn poll_events(&mut self) -> bool {
let mut request_close = false;
self.events_loop.poll_events(|event| match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => request_close = true,
_ => (),
});
!request_close
}
}

View File

@ -1,8 +1,6 @@
use web_sys::HtmlImageElement;
pub enum Character {
Graphic {
image: HtmlImageElement,
//image: HtmlImageElement,
x_min: f32,
y_min: f32,
},

View File

@ -26,6 +26,7 @@ impl From<swf::ColorTransform> for ColorTransform {
}
impl ColorTransform {
#[allow(clippy::float_cmp)]
pub fn is_identity(&self) -> bool {
self.r_mult == 1.0
&& self.g_mult == 1.0

View File

@ -1,7 +1,7 @@
use crate::ColorTransform;
use crate::Matrix;
use crate::{graphic::Graphic, MovieClip, Stage};
use crate::{RenderContext, UpdateContext};
use crate::color_transform::ColorTransform;
use crate::matrix::Matrix;
use crate::player::{RenderContext, UpdateContext};
use crate::{graphic::Graphic, movie_clip::MovieClip, stage::Stage};
use bacon_rajan_cc::{Trace, Tracer};
pub trait DisplayObject {

View File

@ -1,10 +1,9 @@
use crate::color_transform::ColorTransform;
use crate::display_object::DisplayObject;
use crate::library::Library;
use crate::Matrix;
use crate::{RenderContext, UpdateContext};
use crate::matrix::Matrix;
use crate::player::{RenderContext, UpdateContext};
use bacon_rajan_cc::{Trace, Tracer};
use log::{info, trace, warn};
#[cfg(target_arch = "wasm32")]
use web_sys::HtmlImageElement;
pub struct Graphic {
@ -12,13 +11,11 @@ pub struct Graphic {
color_transform: ColorTransform,
x_min: f32,
y_min: f32,
image: HtmlImageElement,
}
impl Graphic {
pub fn new(image: HtmlImageElement, x_min: f32, y_min: f32) -> Graphic {
pub fn new(x_min: f32, y_min: f32) -> Graphic {
Graphic {
image,
color_transform: Default::default(),
x_min,
y_min,
@ -39,32 +36,32 @@ impl DisplayObject for Graphic {
let world_matrix = context.matrix_stack.matrix();
let color_transform = context.color_transform_stack.color_transform();
if !color_transform.is_identity() {
context
.context_2d
.set_global_alpha(color_transform.a_mult.into());
}
// if !color_transform.is_identity() {
// context
// .context_2d
// .set_global_alpha(color_transform.a_mult.into());
// }
context
.context_2d
.set_transform(
world_matrix.a.into(),
world_matrix.b.into(),
world_matrix.c.into(),
world_matrix.d.into(),
world_matrix.tx.into(),
world_matrix.ty.into(),
)
.unwrap();
// context
// .context_2d
// .set_transform(
// world_matrix.a.into(),
// world_matrix.b.into(),
// world_matrix.c.into(),
// world_matrix.d.into(),
// world_matrix.tx.into(),
// world_matrix.ty.into(),
// )
// .unwrap();
if !color_transform.is_identity() {
context.context_2d.set_global_alpha(1.0);
}
// if !color_transform.is_identity() {
// context.context_2d.set_global_alpha(1.0);
// }
context
.context_2d
.draw_image_with_html_image_element(&self.image, self.x_min.into(), self.y_min.into())
.expect("Couldn't render image");
// context
// .context_2d
// .draw_image_with_html_image_element(&self.image, self.x_min.into(), self.y_min.into())
// .expect("Couldn't render image");
context.matrix_stack.pop();
context.color_transform_stack.push(&self.color_transform);

View File

@ -1,3 +1,4 @@
mod backend;
mod character;
mod color_transform;
mod display_object;
@ -5,151 +6,28 @@ mod graphic;
mod library;
mod matrix;
mod movie_clip;
mod player;
mod shape_utils;
mod stage;
use self::color_transform::{ColorTransform, ColorTransformStack};
use self::display_object::DisplayObject;
use self::library::Library;
use self::matrix::{Matrix, MatrixStack};
use self::movie_clip::MovieClip;
use self::stage::Stage;
use bacon_rajan_cc::Cc;
use js_sys::{ArrayBuffer, Uint8Array};
use log::{info, trace, warn};
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::Cursor;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlImageElement};
pub use player::Player;
type CharacterId = swf::CharacterId;
#[cfg(not(target_arch = "wasm32"))]
#[allow(dead_code)]
fn main() {
use std::path::PathBuf;
use structopt::StructOpt;
#[wasm_bindgen]
pub struct Player {
tag_stream: swf::read::Reader<Cursor<Vec<u8>>>,
canvas: HtmlCanvasElement,
render_context: RenderContext,
library: Library,
stage: Cc<RefCell<Stage>>,
frame_rate: f64,
frame_accumulator: f64,
cur_timestamp: f64,
}
#[wasm_bindgen]
impl Player {
pub fn new(data: ArrayBuffer, canvas: HtmlCanvasElement) -> Player {
console_error_panic_hook::set_once();
console_log::init_with_level(log::Level::Trace).expect("error initializing log");
let data = Uint8Array::new(data.as_ref());
let mut swf_data = vec![0; data.byte_length() as usize];
data.copy_to(&mut swf_data[..]);
let (swf, tag_stream) = swf::read::read_swf_header_decompressed(&swf_data[..]).unwrap();
info!("{}x{}", swf.stage_size.x_max, swf.stage_size.y_max);
canvas.set_width(swf.stage_size.x_max as u32);
canvas.set_height(swf.stage_size.y_max as u32);
let context: CanvasRenderingContext2d = canvas
.get_context("2d")
.expect("Expected canvas")
.expect("Expected canvas")
.dyn_into()
.expect("Expected CanvasRenderingContext2d");
Player {
tag_stream,
canvas,
render_context: RenderContext {
context_2d: context,
matrix_stack: MatrixStack::new(),
color_transform_stack: ColorTransformStack::new(),
},
library: Library::new(),
stage: Stage::new(swf.num_frames),
frame_rate: swf.frame_rate.into(),
frame_accumulator: 0.0,
cur_timestamp: web_sys::window()
.expect("Expected window")
.performance()
.expect("Expected performance")
.now(),
}
#[derive(StructOpt, Debug)]
#[structopt(name = "basic")]
struct Opt {
#[structopt(name = "FILE", parse(from_os_str))]
input_path: PathBuf,
}
pub fn tick(&mut self, timestamp: f64) {
let dt = timestamp - self.cur_timestamp;
self.cur_timestamp = timestamp;
let opt = Opt::from_args();
self.frame_accumulator += dt;
let frame_time = 1000.0 / self.frame_rate;
let needs_render = self.frame_accumulator >= frame_time;
while self.frame_accumulator >= frame_time {
self.frame_accumulator -= frame_time;
self.run_frame();
}
if needs_render {
self.render();
}
}
}
impl Player {
fn run_frame(&mut self) {
let mut update_context = UpdateContext {
tag_stream: &mut self.tag_stream,
position_stack: vec![],
library: &mut self.library,
};
let mut stage = self.stage.borrow_mut();
stage.run_frame(&mut update_context);
stage.update_frame_number();
}
fn render(&mut self) {
let stage = self.stage.borrow_mut();
let background_color = stage.background_color();
let css_color = format!(
"rgb({}, {}, {})",
background_color.r, background_color.g, background_color.b
);
self.render_context.context_2d.reset_transform().unwrap();
self.render_context
.context_2d
.set_fill_style(&format!("{}", css_color).into());
let width: f64 = self.canvas.width().into();
let height: f64 = self.canvas.height().into();
self.render_context
.context_2d
.fill_rect(0.0, 0.0, width, height);
stage.render(&mut self.render_context);
}
}
pub struct UpdateContext<'a> {
tag_stream: &'a mut swf::read::Reader<Cursor<Vec<u8>>>,
position_stack: Vec<u64>,
library: &'a mut Library,
}
pub struct RenderContext {
context_2d: CanvasRenderingContext2d,
matrix_stack: MatrixStack,
color_transform_stack: ColorTransformStack,
let swf_data = std::fs::read(opt.input_path).unwrap();
let mut player = Player::new(swf_data).unwrap();
player.play();
}

View File

@ -31,13 +31,12 @@ impl Library {
) -> Result<DisplayObjectNode, Box<std::error::Error>> {
match self.characters.get(&id) {
Some(Character::Graphic {
image,
//image,
x_min,
y_min,
}) => Ok(DisplayObjectNode::Graphic(Graphic::new(
image.clone(),
*x_min,
*y_min,
//image.clone(),
*x_min, *y_min,
))),
Some(Character::MovieClip {
tag_stream_start,

View File

@ -1,13 +1,11 @@
use crate::color_transform::ColorTransform;
use crate::display_object::{DisplayObject, DisplayObjectNode};
use crate::matrix::Matrix;
use crate::Library;
use crate::{RenderContext, UpdateContext};
use crate::player::{RenderContext, UpdateContext};
use bacon_rajan_cc::{Cc, Trace, Tracer};
use log::{info, trace, warn};
use log::info;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::Cursor;
type Depth = i16;
type FrameNumber = u16;
@ -65,7 +63,7 @@ impl MovieClip {
PlaceObjectAction::Place(id) => {
// TODO(Herschel): Behavior when character doesn't exist/isn't a DisplayObject?
let character =
if let Ok(mut character) = context.library.instantiate_display_object(id) {
if let Ok(character) = context.library.instantiate_display_object(id) {
Cc::new(RefCell::new(character))
} else {
return;
@ -84,7 +82,7 @@ impl MovieClip {
}
PlaceObjectAction::Replace(id) => {
let character =
if let Ok(mut character) = context.library.instantiate_display_object(id) {
if let Ok(character) = context.library.instantiate_display_object(id) {
Cc::new(RefCell::new(character))
} else {
return;
@ -122,10 +120,7 @@ impl DisplayObject for MovieClip {
Tag::PlaceObject(place_object) => {
MovieClip::run_place_object(&mut self.children, &*place_object, context)
}
Tag::RemoveObject {
depth,
character_id,
} => {
Tag::RemoveObject { depth, .. } => {
// TODO(Herschel): How does the character ID work for RemoveObject?
self.children.remove(&depth);
info!("REMOVE {} {}", depth, self.children.len());

151
src/player.rs Normal file
View File

@ -0,0 +1,151 @@
use crate::backend::{render::RenderBackend, ui::UiBackend};
use crate::color_transform::ColorTransformStack;
use crate::display_object::DisplayObject;
use crate::library::Library;
use crate::matrix::MatrixStack;
use crate::stage::Stage;
use bacon_rajan_cc::Cc;
use log::info;
use std::cell::RefCell;
use std::io::Cursor;
#[cfg(target_arch = "wasm32")]
use js_sys::{ArrayBuffer, Uint8Array};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlImageElement};
type CharacterId = swf::CharacterId;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
pub struct Player {
tag_stream: swf::read::Reader<Cursor<Vec<u8>>>,
render_context: RenderContext,
ui: Box<UiBackend>,
renderer: Box<RenderBackend>,
library: Library,
stage: Cc<RefCell<Stage>>,
frame_rate: f64,
frame_accumulator: f64,
//cur_timestamp: f64,
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
impl Player {
pub fn new(swf_data: Vec<u8>) -> Result<Player, Box<std::error::Error>> {
let (swf, tag_stream) = swf::read::read_swf_header_decompressed(&swf_data[..]).unwrap();
info!("{}x{}", swf.stage_size.x_max, swf.stage_size.y_max);
let (ui, renderer) = crate::backend::build()?;
Ok(Player {
tag_stream,
ui,
renderer,
render_context: RenderContext {
matrix_stack: MatrixStack::new(),
color_transform_stack: ColorTransformStack::new(),
},
library: Library::new(),
stage: Stage::new(swf.num_frames),
frame_rate: swf.frame_rate.into(),
frame_accumulator: 0.0,
//cur_timestamp: std::time::Instant::now(),
})
}
pub fn play(&mut self) {
use std::time::{Duration, Instant};
let mut time = Instant::now();
while self.ui.poll_events() {
let new_time = Instant::now();
let dt = new_time.duration_since(time).as_millis();
time = new_time;
self.tick(dt as f64);
std::thread::sleep(Duration::from_millis(1000 / 60));
}
}
pub fn tick(&mut self, timestamp: f64) {
// let dt = timestamp - self.cur_timestamp;
// self.cur_timestamp = timestamp;
// self.frame_accumulator += dt;
// let frame_time = 1000.0 / self.frame_rate;
// let needs_render = self.frame_accumulator >= frame_time;
// while self.frame_accumulator >= frame_time {
// self.frame_accumulator -= frame_time;
// self.run_frame();
// }
// if needs_render {
// self.render();
// }
}
}
impl Player {
fn run_frame(&mut self) {
let mut update_context = UpdateContext {
tag_stream: &mut self.tag_stream,
position_stack: vec![],
library: &mut self.library,
};
let mut stage = self.stage.borrow_mut();
stage.run_frame(&mut update_context);
stage.update_frame_number();
}
fn render(&mut self) {
let stage = self.stage.borrow_mut();
/*
let background_color = stage.background_color();
let css_color = format!(
"rgb({}, {}, {})",
background_color.r, background_color.g, background_color.b
);
self.render_context.context_2d.reset_transform().unwrap();
self.render_context
.context_2d
.set_fill_style(&format!("{}", css_color).into());
let width: f64 = self.canvas.width().into();
let height: f64 = self.canvas.height().into();
self.render_context
.context_2d
.fill_rect(0.0, 0.0, width, height);
*/
stage.render(&mut self.render_context);
}
}
pub struct UpdateContext<'a> {
pub tag_stream: &'a mut swf::read::Reader<Cursor<Vec<u8>>>,
pub position_stack: Vec<u64>,
pub library: &'a mut Library,
}
pub struct RenderContext {
//pub context_2d: CanvasRenderingContext2d,
pub matrix_stack: MatrixStack,
pub color_transform_stack: ColorTransformStack,
}

View File

@ -1,5 +1,4 @@
use crate::Matrix;
use log::{info, trace, warn};
use crate::matrix::Matrix;
use std::collections::{HashMap, VecDeque};
use svg::node::element::{
path::Data, Definitions, Image, LinearGradient, Path as SvgPath, Pattern, RadialGradient, Stop,
@ -179,12 +178,7 @@ pub fn swf_shape_to_svg(shape: &Shape) -> String {
num_defs += 1;
fill_id
}
FillStyle::Bitmap {
id,
matrix,
is_smoothed,
is_repeating,
} => {
FillStyle::Bitmap { .. } => {
let svg_image = Image::new(); // TODO: .set("xlink:href", "");
let svg_pattern = Pattern::new()
@ -283,7 +277,6 @@ fn swf_shape_to_paths(shape: &Shape) -> (Vec<Path>, Vec<Path>) {
let mut fill_style_0 = 0;
let mut fill_style_1 = 0;
let mut line_style = 0;
let mut i = 0;
let mut fill_styles = &shape.styles.fill_styles;
let mut line_styles = &shape.styles.line_styles;
for record in &shape.shape {

View File

@ -3,15 +3,12 @@ use crate::color_transform::ColorTransform;
use crate::display_object::{DisplayObject, DisplayObjectNode};
use crate::matrix::Matrix;
use crate::movie_clip::MovieClip;
use crate::Library;
use crate::{RenderContext, UpdateContext};
use crate::player::{RenderContext, UpdateContext};
use bacon_rajan_cc::{Cc, Trace, Tracer};
use log::{info, trace, warn};
use log::info;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::Cursor;
use swf::Color;
use web_sys::HtmlImageElement;
type Depth = i16;
type FrameNumber = u16;
@ -78,26 +75,16 @@ impl DisplayObject for Stage {
Tag::ShowFrame => break,
Tag::DefineSceneAndFrameLabelData {
scenes,
frame_labels,
} => (), // TODO(Herschel)
Tag::DefineSceneAndFrameLabelData { .. } => (), // TODO(Herschel)
Tag::DefineShape(shape) => {
if !context.library.contains_character(shape.id) {
let svg = crate::shape_utils::swf_shape_to_svg(&shape);
use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
let url_encoded_svg = format!(
"data:image/svg+xml,{}",
utf8_percent_encode(&svg, DEFAULT_ENCODE_SET)
);
let mut image = HtmlImageElement::new().unwrap();
image.set_src(&url_encoded_svg);
//let mut image = HtmlImageElement::new().unwrap();
//image.set_src(&url_encoded_svg);
context.library.register_character(
shape.id,
Character::Graphic {
image,
//image,
x_min: shape.shape_bounds.x_min,
y_min: shape.shape_bounds.y_min,
},
@ -122,14 +109,10 @@ impl DisplayObject for Stage {
}
}
Tag::ShowFrame => break,
Tag::PlaceObject(place_object) => {
MovieClip::run_place_object(&mut self.children, &*place_object, context)
}
Tag::RemoveObject {
depth,
character_id,
} => {
Tag::RemoveObject { depth, .. } => {
// TODO(Herschel): How does the character ID work for RemoveObject?
self.children.remove(&depth).is_some();
info!("REMOVE {} {}", depth, self.children.len());