Browse Source

Implement instruction parser (untested)

master
NGnius 3 years ago
parent
commit
d630bb5827
7 changed files with 549 additions and 2 deletions
  1. +3
    -1
      Cargo.toml
  2. +55
    -0
      README.md
  3. +131
    -0
      src/instruction.rs
  4. +121
    -0
      src/instruction_parser.rs
  5. +29
    -1
      src/lib.rs
  6. +126
    -0
      src/text_parser.rs
  7. +84
    -0
      src/text_parser_bindings.rs

+ 3
- 1
Cargo.toml View File

@@ -9,7 +9,9 @@ edition = "2018"
[dependencies]
libc = "0.2"
clap = "2"
lazy_static = "1.4"
regex = "1"

[lib]
name = "filmscript"
crate-type = ["rlib", "cdylib"]
crate-type = ["rlib", "cdylib"]

+ 55
- 0
README.md View File

@@ -1,3 +1,58 @@
# FilmScript

The best Canadian camera technology since IMAX.

## Syntax

FilmScript uses a simple function syntax which you will be familiar with from most modern programming languages like C and Python.
The end of an operation is denoted by a new line `\n`.
Multiple operations can be performed simultaneously by placing them on the same line.
Lines are executed sequentially starting from the top of the file until the end.

Here's a generic example of the syntax:

```
operation1(operand1, operand2, operand3)
operation2(operand1, operand2)
operation3a(operand1) operation3b(operand1, operand2)
```

## Operations

FilmScript is a set of commands that can be used to describe a camera and it's movements in time.
Supported operations and their operands are outlined below.

Here's an example of a functioning FilmScript file using some of the supported operations:

```
#version 0.1.0
look(0, 0, 0), move(0, 0, 0)
track(20, 0, 20, 5), rotate(0, -5.5, 0, 5)
rotate(0, 5.5, 0, 2)
track(-20, 5, 0, 5)
```

### TRACK(DISTANCE_X, DISTANCE_Y, DISTANCE_Z, TIME=0)

Move the camera relative to the current position and rotation.
Use a negative value to move backwards.
This operation supports linear interpolation through the optional TIME operand (seconds).

### MOVE(X, Y, Z)

Move the camera to an absolute position.

### ROTATE(DEGREES_X, DEGREES_Y, DEGREES_Z, TIME=0)

Rotate the camera clockwise around the axes, relative to the current rotation.
Use a negative value to rotate counter-clockwise.
This operation supports linear interpolation through the optional TIME operand (seconds).

### LOOK(DEGREES_X, DEGREES_Y, DEGREES_Z)

Rotate the camera to an absolute rotation.

### MULTI(OPERATION1, OPERATION2, ...)

Perform multiple operations simultaneously.
This is implicitly used whenever more than one operation appears on a single line.

+ 131
- 0
src/instruction.rs View File

@@ -0,0 +1,131 @@

#[derive(Clone)]
pub(crate) struct Instruction {
pub instr: InstructionType,
pub start: f64,
pub progress: f64,
pub base: CameraData,
// impl in instruction_parser.rs
}

#[derive(Clone)]
pub(crate) enum InstructionType {
Track {
vector: Vector3,
time: f64,
},
Move {
vector: Vector3,
},
Rotate {
vector: Vector3,
time: f64,
},
Look {
vector: Vector3,
},
Multi {
instructions: Vec<InstructionType>,
},
}

impl InstructionType {
pub fn time(&self) -> f64 {
match self {
Self::Track {time, ..} => *time,
Self::Move {..} => 0.0,
Self::Rotate {time, ..} => *time,
Self::Look {..} => 0.0,
Self::Multi {instructions, ..} => {
let mut max: f64 = 0.0;
for i in instructions {
if i.time() > max {
max = i.time();
}
}
max
},
}
}
pub fn lerp(&self, start: f64, now: f64) -> CameraData {
let delta = now - start;
let mut portion = 1.0;
let time = self.time();
if time > 0.0 && time < delta {
portion = delta/self.time();
}
match self {
Self::Track {vector, ..} => CameraData {rotation: Vector3::default(), position: *vector * portion},
Self::Move {vector, ..} => CameraData {rotation: Vector3::default(), position: *vector},
Self::Rotate {vector, ..} => CameraData {rotation: *vector * portion, position: Vector3::default()},
Self::Look {vector, ..} => CameraData {rotation: *vector, position: Vector3::default()},
Self::Multi {instructions, ..} => {
let mut data = CameraData::default();
for i in instructions {
data += i.lerp(start, now);
}
data
},
}
}
}

#[derive(Clone, Copy)]
pub(crate) struct CameraData {
pub rotation: Vector3,
pub position: Vector3,
}

impl std::default::Default for CameraData {
fn default() -> Self {
Self {rotation: Vector3::default(), position: Vector3::default()}
}
}

impl std::ops::Add for CameraData {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {rotation: self.rotation + rhs.rotation, position: self.position + rhs.position}
}
}

impl std::ops::AddAssign for CameraData {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}

#[derive(Copy, Clone)]
pub(crate) struct Vector3 {
pub x: f64,
pub y: f64,
pub z: f64,
}

impl std::default::Default for Vector3 {
fn default() -> Self {
Self {x:0.0, y:0.0, z:0.0}
}
}

impl std::ops::Mul<f64> for Vector3 {
type Output = Self;

fn mul(self, rhs: f64) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}

impl std::ops::Add for Vector3 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z}
}
}

+ 121
- 0
src/instruction_parser.rs View File

@@ -0,0 +1,121 @@
use regex::{Regex, RegexBuilder, Captures};
use std::sync::RwLock;

use crate::instruction::{Instruction, InstructionType, Vector3, CameraData};

lazy_static!{
static ref REGEX_CACHE: RwLock<RegexPatterns> = RwLock::new(RegexPatterns{
single_instruction: RegexBuilder::new(r"(\w+)\s*\((\d+(?:\.\d+)?),?\s*(\d+(?:\.\d+)?),?\s*(\d+(?:\.\d+)?)(?:,?\s*(\d+(?:\.\d+)?))?\s*\)").case_insensitive(true).build().unwrap(),
//multi_instruction: RegexBuilder::new("").case_insensitive(true).build().unwrap(),
});
}

struct RegexPatterns {
single_instruction: Regex,
}


impl Instruction {
pub fn parse_line(line: &str) -> Result<Instruction, String> {
let cache_lock = REGEX_CACHE.read().unwrap();
if count(line, '(') > 1 {
// do multi parsing
let mut instructions = Vec::<InstructionType>::new();
for capture in cache_lock.single_instruction.captures_iter(line) {
let ins = Self::parse_single(&capture)?;
instructions.push(ins);
}
return Ok(Instruction {
instr: InstructionType::Multi{instructions},
start: 0.0,
progress: 0.0,
base: CameraData::default(),
})
} else {
// assume single instruction on line
if let Some(captures) = cache_lock.single_instruction.captures(line) {
let instr = Self::parse_single(&captures)?;
return Ok(Instruction {
instr,
start: 0.0,
progress: 0.0,
base: CameraData::default(),
})
} else {
return Err("Invalid line".to_string());
}
}
}
fn parse_single(c: &Captures) -> Result<InstructionType, String> {
let name = c.get(1).unwrap().as_str();
let param1 = c.get(2).unwrap().as_str().parse::<f64>().unwrap();
let param2 = c.get(3).unwrap().as_str().parse::<f64>().unwrap();
let param3 = c.get(4).unwrap().as_str().parse::<f64>().unwrap();
// optional 4th param
// let param4 = c.get(5).unwrap().as_str().parse::<f64>();
match name.to_uppercase().as_str() {
"TRACK" => {
if let Some(param4) = c.get(5) {
Ok(InstructionType::Track {
vector: Vector3{x: param1, y: param2, z: param3},
time: param4.as_str().parse::<f64>().unwrap(),
})
} else {
Ok(InstructionType::Track {
vector: Vector3{x: param1, y: param2, z: param3},
time: 0.0,
})
}
},
"MOVE" => Ok(InstructionType::Move {
vector: Vector3{x: param1, y: param2, z: param3},
}),
"ROTATE" => {
if let Some(param4) = c.get(5) {
Ok(InstructionType::Rotate {
vector: Vector3{x: param1, y: param2, z: param3},
time: param4.as_str().parse::<f64>().unwrap(),
})
} else {
Ok(InstructionType::Track {
vector: Vector3{x: param1, y: param2, z: param3},
time: 0.0,
})
}
},
"LOOK" => Ok(InstructionType::Look {
vector: Vector3{x: param1, y: param2, z: param3},
}),
_ => Err(format!("Invalid instruction {}", name))
}
}
pub fn start(&mut self, now: f64, base: CameraData) {
self.start = now;
self.base = base;
}
pub fn lerp(&mut self, now: f64) -> CameraData {
self.progress += now;
match self.instr {
InstructionType::Look {..} | InstructionType::Move {..} => self.instr.lerp(self.start, now),
InstructionType::Track {..} | InstructionType::Rotate {..} | InstructionType::Multi{..} => self.base + self.instr.lerp(self.start, now),
}
}
pub fn done(&self) -> bool {
!(self.progress < (self.start + self.instr.time()))
}
}

// string helper functions
fn count(target: &str, chr: char) -> usize {
let mut times: usize = 0;
for c in target.chars() {
if chr == c {
times+=1;
}
}
times
}

+ 29
- 1
src/lib.rs View File

@@ -1,9 +1,16 @@
#[macro_use]
extern crate lazy_static;

use std::ffi::CString;
use std::os::raw::c_char;

mod version;
mod text_parser;
mod text_parser_bindings;
mod instruction;
mod instruction_parser;

pub unsafe fn allocate_cstring(input: &str) -> *mut c_char {
pub(crate) unsafe fn allocate_cstring(input: &str) -> *mut c_char {
let input_c = CString::new(input).expect("Rust &str -> CString conversion failed");
let space = libc::malloc(libc::strlen(input_c.as_ptr()) + 1) as *mut c_char;
libc::strcpy(space, input_c.as_ptr());
@@ -12,8 +19,29 @@ pub unsafe fn allocate_cstring(input: &str) -> *mut c_char {

#[cfg(test)]
mod tests {
use std::io::Read;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
fn vec_read_test() {
let vec: Vec<u8> = vec![1, 2, 3, 4, 5];
let file_res = crate::text_parser::FilmscriptFile::from_vec(&vec);
assert!(file_res.is_ok());
let mut f = file_res.unwrap();
let buf: &mut [u8] = &mut [0; 1];
assert!(f.read(buf).is_ok());
println!("Buffer contains value: {}", buf[0]);
assert!(f.read(buf).is_ok());
println!("Buffer contains value: {}", buf[0]);
assert!(f.read(buf).is_ok());
println!("Buffer contains value: {}", buf[0]);
assert!(f.read(buf).is_ok());
println!("Buffer contains value: {}", buf[0]);
assert!(f.read(buf).is_ok());
println!("Buffer contains value: {}", buf[0]);
}
}

+ 126
- 0
src/text_parser.rs View File

@@ -0,0 +1,126 @@
use std::fs::File;
use std::io::{BufReader, BufRead, Read};
use std::path::{Path, PathBuf};
use std::sync::{RwLock};

use crate::instruction::{Instruction, CameraData};

lazy_static! {
pub(crate) static ref FILE_HANDLES: RwLock<Vec<FilmscriptFile>> = RwLock::new(Vec::new());
}

pub struct FilmscriptFile {
path: Option<PathBuf>,
buffer: Box<dyn BufRead + Sync + Send>,
index: usize,
instructions: Vec<Instruction>,
done: bool,
done_msg: String,
}

impl FilmscriptFile {
pub fn from_path<'a>(path: &'a Path) -> std::io::Result<FilmscriptFile> {
let new_reader = File::open(path)?;
std::io::Result::Ok(FilmscriptFile {
path: Some(PathBuf::from(path)),
//reader: new_reader,
buffer: Box::new(BufReader::new(new_reader)),
index: 0,
instructions: Vec::new(),
done: false,
done_msg: String::new(),
})
}
pub fn from_static_slice(buf: &'static[u8]) -> std::io::Result<FilmscriptFile> {
std::io::Result::Ok(FilmscriptFile {
path: None,
buffer: Box::new(buf),
index: 0,
instructions: Vec::new(),
done: false,
done_msg: String::new(),
})
}
pub fn from_slice(buf: &[u8]) -> std::io::Result<FilmscriptFile> {
Self::from_vec(&Vec::from(buf))
}
pub fn from_vec(buf: &Vec<u8>) -> std::io::Result<FilmscriptFile> {
let new_reader = VectorReader{vec: buf.clone()};
std::io::Result::Ok(FilmscriptFile {
path: None,
buffer: Box::new(BufReader::new(new_reader)),
index: 0,
instructions: Vec::new(),
done: false,
done_msg: String::new(),
})
}
pub fn filepath(&self) -> Option<PathBuf> {
self.path.clone()
}
pub fn is_done(&self) -> bool {
self.done
}
pub fn done_msg(&self) -> String {
self.done_msg.clone()
}
fn next_instruction(&mut self) -> Result<Instruction, String> {
let mut buf = String::new();
if let Ok(len) = self.buffer.read_line(&mut buf) {
let instr = Instruction::parse_line(&buf[..len])?;
self.instructions.push(instr.clone());
return Ok(instr);
}
Err("End of file".to_string())
}
pub(crate) fn lerp(&mut self, now: f64) -> CameraData {
if self.index >= self.instructions.len() {
match self.next_instruction() {
Ok(_) => {
if self.index == 0 {
// do initial init
self.instructions[0].start(now, CameraData::default());
} else {
let end = self.instructions[self.index-1].start + self.instructions[self.index-1].instr.time();
let base = self.instructions[self.index-1].instr.lerp(0.0, self.instructions[self.index-1].instr.time());
self.instructions[self.index].start(end, base);
}
},
Err(msg) => {
self.done = true;
self.done_msg = msg;
},
}
}
let curr_instr = &mut self.instructions[self.index];
let cam_data = curr_instr.lerp(now);
if curr_instr.done() {
self.index+=1;
}
cam_data
}
}

impl Read for FilmscriptFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.buffer.read(buf)
}
}

struct VectorReader {
vec: Vec<u8>
}

impl Read for VectorReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.vec.as_slice().read(buf)
}
}

+ 84
- 0
src/text_parser_bindings.rs View File

@@ -0,0 +1,84 @@
//use libc;
use std::os::raw::c_char;
use std::ffi::CStr;
use std::convert::From;
use std::time::Instant;

use crate::text_parser::{FilmscriptFile, FILE_HANDLES};
use crate::allocate_cstring;
use crate::instruction::{CameraData, Vector3};

#[repr(C)]
pub struct CameraDataC {
pub rotation: Vector3C,
pub position: Vector3C,
}

impl From<CameraData> for CameraDataC {
fn from(c: CameraData) -> Self {
Self {
rotation: Vector3C::from(c.rotation),
position: Vector3C::from(c.position),
}
}
}

#[repr(C)]
pub struct Vector3C {
pub x: f64,
pub y: f64,
pub z: f64,
}

impl From<Vector3> for Vector3C {
fn from(v: Vector3) -> Self {
Self {
x: v.x,
y: v.y,
z: v.z,
}
}
}

#[no_mangle]
pub unsafe extern "C" fn filmscript_open(filepath: *const c_char) -> i64 {
let c_str = CStr::from_ptr(filepath);
if let Ok(path) = c_str.to_str() {
if let Ok(fsf) = FilmscriptFile::from_path(std::path::Path::new(path)) {
FILE_HANDLES.write().unwrap().push(fsf);
return FILE_HANDLES.read().unwrap().len() as i64;
} else {
println!("Failed to open '{}' in filmscript_open(*char)", path);
}
} else {
println!("Invalid *char parameter in filmscript_open(*char)");
}
return -1;
}

#[no_mangle]
pub unsafe extern "C" fn filmscript_path(handle: i64) -> *const c_char {
if let Some(path) = FILE_HANDLES.read().unwrap()[(handle-1) as usize].filepath().clone() {
if let Some(s_path) = path.to_str() {
return allocate_cstring(s_path);
}
}
allocate_cstring("")
}

#[no_mangle]
pub unsafe extern "C" fn filmscript_is_done(handle: i64) -> bool {
FILE_HANDLES.read().unwrap()[(handle-1) as usize].is_done()
}

#[no_mangle]
pub unsafe extern "C" fn filmscript_done_message(handle: i64) -> *const c_char {
allocate_cstring(&FILE_HANDLES.read().unwrap()[(handle-1) as usize].done_msg())
}

#[no_mangle]
pub unsafe extern "C" fn filmscript_poll(handle: i64) -> CameraDataC {
let now = Instant::now().elapsed().as_secs_f64();
let cdata = FILE_HANDLES.write().unwrap()[(handle-1) as usize].lerp(now);
CameraDataC::from(cdata)
}

Loading…
Cancel
Save