388 lines
14 KiB
Rust
388 lines
14 KiB
Rust
use scene;
|
|
use tile;
|
|
use view;
|
|
use vr;
|
|
use world as model;
|
|
use world::HasMap;
|
|
|
|
extern crate memmap;
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::marker::PhantomData;
|
|
use std::time::SystemTime;
|
|
|
|
use gfx::{self, tex};
|
|
use gfx::traits::FactoryExt;
|
|
use na::{self, ToHomogeneous};
|
|
use num_traits::identities::One;
|
|
use piston::input::{Button, Input, Key};
|
|
|
|
const PI: f32 = ::std::f32::consts::PI;
|
|
const TWO_PI_CIRC: f32 = 2.0 * PI / 256.0;
|
|
|
|
const R1: f32 = 256.0;
|
|
const R2: f32 = 64.0;
|
|
const R3: f32 = 128.0;
|
|
|
|
//const SKY_COLOR: [f32; 4] = [0.15, 0.15, 0.75, 1.0];
|
|
const SKY_COLOR: [f32; 4] = [0.005, 0.005, 0.01, 1.0];
|
|
|
|
gfx_defines! {
|
|
vertex Vertex {
|
|
pos: [f32; 3] = "a_pos",
|
|
uv: [f32; 2] = "a_uv",
|
|
tileidx: u32 = "a_tileidx",
|
|
}
|
|
|
|
constant Constants {
|
|
anim: [u32; 4] = "anim",
|
|
r1: f32 = "R1",
|
|
r2: f32 = "R2",
|
|
r3: f32 = "R3",
|
|
haze: f32 = "haze",
|
|
hazecolor: [f32; 4] = "hazecolor",
|
|
}
|
|
|
|
constant Locals {
|
|
millis: u32 = "millis",
|
|
treadmill_x: f32 = "treadmill_x",
|
|
treadmill_y: f32 = "treadmill_y",
|
|
}
|
|
|
|
pipeline pipe {
|
|
vbuf: gfx::VertexBuffer<Vertex> = (),
|
|
trans: gfx::ConstantBuffer<::view::Trans> = "b_trans",
|
|
constants: gfx::ConstantBuffer<Constants> = "b_constants",
|
|
locals: gfx::ConstantBuffer<Locals> = "b_locals",
|
|
atlas: gfx::TextureSampler<[f32; 4]> = "t_tiles",
|
|
pixcolor: gfx::RenderTarget<::view::ColorFormat> = "pixcolor",
|
|
depth: gfx::DepthTarget<::view::DepthFormat> = gfx::preset::depth::LESS_EQUAL_WRITE,
|
|
}
|
|
}
|
|
|
|
|
|
fn get_model(world: &model::World) -> (Vec<Vertex>, Vec<u32>) {
|
|
let mut verticies = Vec::new();
|
|
let mut indicies = Vec::new();
|
|
let mut v = 0;
|
|
|
|
for (r, row) in world.map().rows().enumerate() {
|
|
for (c, tile) in row.into_iter().enumerate() {
|
|
let tileidx = tile.val as u32;
|
|
let alt = match tileidx {
|
|
5 => 0.1,
|
|
6 => 0.8,
|
|
7 => 0.2,
|
|
8 => 1.5,
|
|
9 => 1.0,
|
|
10 | 11 | 12 => 1.0,
|
|
_ => 0.0,
|
|
};
|
|
let (rf, cf) = (r as f32, c as f32);
|
|
if alt == 0.0 {
|
|
verticies.extend_from_slice(
|
|
&[Vertex { pos: [ cf + 0., rf + 1., 0. ], uv: [0., 0.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 1., rf + 1., 0. ], uv: [1., 0.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 1., rf + 0., 0. ], uv: [1., 1.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 0., rf + 0., 0. ], uv: [0., 1.], tileidx: tileidx },]);
|
|
indicies.extend_from_slice(
|
|
&[ v + 0, v + 1, v + 2,
|
|
v + 2, v + 3, v + 0 ]);
|
|
v += 4;
|
|
} else {
|
|
verticies.extend_from_slice(
|
|
&[Vertex { pos: [ cf + 0., rf + 1., 0. ], uv: [0., 0.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 1., rf + 1., 0. ], uv: [1., 0.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 1., rf + 0., 0. ], uv: [1., 1.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 0., rf + 0., 0. ], uv: [0., 1.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 0., rf, 0. ], uv: [0., 0.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 1., rf, 0. ], uv: [1., 0.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 1., rf, alt ], uv: [1., 1.], tileidx: tileidx },
|
|
Vertex { pos: [ cf + 0., rf, alt ], uv: [0., 1.], tileidx: tileidx },]);
|
|
indicies.extend_from_slice(
|
|
&[ v + 0, v + 1, v + 2,
|
|
v + 2, v + 3, v + 0,
|
|
v + 4, v + 5, v + 6,
|
|
v + 6, v + 7, v + 4 ]);
|
|
v += 8;
|
|
}
|
|
}
|
|
}
|
|
(verticies, indicies)
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
enum TrackMode {
|
|
Touch,
|
|
Press
|
|
}
|
|
|
|
pub struct WorldScene<D: gfx::Device,
|
|
F: gfx::Factory<D::Resources>> {
|
|
pso: gfx::PipelineState<D::Resources, pipe::Meta>,
|
|
camera: na::Matrix4<f32>,
|
|
constants: Constants,
|
|
constants_buffer: gfx::handle::Buffer<D::Resources, Constants>,
|
|
constants_dirty: bool,
|
|
locals: gfx::handle::Buffer<D::Resources, Locals>,
|
|
atlas: gfx::handle::ShaderResourceView<D::Resources,
|
|
<view::ColorFormat as gfx::format::Formatted>::View>,
|
|
sampler: gfx::handle::Sampler<D::Resources>,
|
|
f: PhantomData<F>,
|
|
|
|
vbuf: gfx::handle::Buffer<D::Resources, Vertex>,
|
|
slice: gfx::Slice<D::Resources>,
|
|
|
|
start_time: SystemTime,
|
|
treadmills: (f32, f32),
|
|
pads: BTreeMap<u32, (TrackMode, Option<vr::VRControllerState_t>)>,
|
|
|
|
_worldmap: model::World,
|
|
lat: u8,
|
|
lng: u8,
|
|
}
|
|
|
|
impl<D: gfx::Device, F: gfx::Factory<D::Resources>> WorldScene<D, F> {
|
|
pub fn new(device: &mut D,
|
|
factory: &mut F,
|
|
aux_command: &mut <D as gfx::Device>::CommandBuffer) -> WorldScene<D, F> {
|
|
let worldmap = get_data_model();
|
|
let (model, model_idx) = get_model(&worldmap);
|
|
let (vertex_buffer, slice) =
|
|
factory.create_vertex_buffer_with_slice(&model, &model_idx[..]);
|
|
|
|
WorldScene {
|
|
pso: factory.create_pipeline_simple(VERTEX_SHADER_SRC,
|
|
FRAGMENT_SHADER_SRC,
|
|
pipe::new())
|
|
.expect("create pipeline"),
|
|
camera: na::Matrix4::one(),
|
|
constants: Constants { anim: ANIMDATA,
|
|
r1: R1, r2: R2, r3: R3,
|
|
haze: 1.0/2.0f32.sqrt(), hazecolor: SKY_COLOR },
|
|
constants_buffer: factory.create_constant_buffer(1),
|
|
constants_dirty: true,
|
|
locals: factory.create_constant_buffer(1),
|
|
atlas: tile::get_tiles::<_, _, view::ColorFormat>(device, factory, aux_command),
|
|
sampler: factory.create_sampler(tex::SamplerInfo::new(tex::FilterMethod::Jrd,
|
|
tex::WrapMode::Tile)),
|
|
f: PhantomData,
|
|
|
|
vbuf: vertex_buffer,
|
|
slice: slice,
|
|
start_time: SystemTime::now(),
|
|
treadmills: (0.0, 0.0),
|
|
pads: BTreeMap::new(),
|
|
|
|
_worldmap: worldmap,
|
|
lat: 144,
|
|
lng: 90,
|
|
}
|
|
}
|
|
|
|
fn toroid((x, y): (f32, f32), r1: f32, r2: f32, r3: f32) -> na::Vector3<f32>
|
|
{
|
|
let x: f32 = TWO_PI_CIRC * x as f32;
|
|
let y: f32 = TWO_PI_CIRC * y as f32;
|
|
na::Vector3::<f32>::new(r3 * x.sin(), // use r3 instead of r2 for "deflated" torus
|
|
(r1 + r2 * x.cos()) * y.cos(),
|
|
(r1 + r2 * x.cos()) * y.sin())
|
|
}
|
|
}
|
|
|
|
const ANIMDATA: [u32; 4] =
|
|
[1 << 0 | 1 << 1 | 1 << 2,
|
|
0,
|
|
1 << (68 % 32) | 1 << (69 % 32) | 1 << (70 % 32) | 1 << (71 % 32) | 1 << (76 % 32),
|
|
0];
|
|
|
|
impl<D: gfx::Device,
|
|
F: gfx::Factory<D::Resources>> scene::Scene<D, F> for WorldScene<D, F> {
|
|
|
|
fn event(&mut self, event: scene::Event) {
|
|
use scene::Event::*;
|
|
use vr::Event::*;
|
|
match event {
|
|
// treadmill / camera movement registration
|
|
Vr(Touch { dev_idx, .. }) => {
|
|
self.pads.insert(dev_idx, (TrackMode::Touch, None));
|
|
},
|
|
Vr(Press { dev_idx, .. }) => {
|
|
self.pads.insert(dev_idx, (TrackMode::Press, None));
|
|
},
|
|
Vr(Unpress { dev_idx, .. }) => {
|
|
self.pads.insert(dev_idx, (TrackMode::Touch, None));
|
|
},
|
|
Vr(Untouch { dev_idx, .. }) => {
|
|
self.pads.remove(&dev_idx);
|
|
},
|
|
|
|
// treadmill / camera reset
|
|
Piston(Input::Press(Button::Keyboard(Key::Backspace))) => {
|
|
self.treadmills = (0.0, 0.0);
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::D0))) => {
|
|
self.camera = na::Matrix4::one();
|
|
},
|
|
|
|
// player movement
|
|
Piston(Input::Press(Button::Keyboard(Key::Up))) => {
|
|
self.lat = self.lat.wrapping_sub(1);
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::Down))) => {
|
|
self.lat = self.lat.wrapping_add(1);
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::Left))) => {
|
|
self.lng = self.lng.wrapping_sub(1);
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::Right))) => {
|
|
self.lng = self.lng.wrapping_add(1);
|
|
},
|
|
|
|
// scale adjustment
|
|
Piston(Input::Press(Button::Keyboard(Key::Q))) => {
|
|
self.constants = Constants { r1: R1 / 2.0, r2: R2 / 2.0, r3: R3 / 2.0, ..self.constants };
|
|
self.constants_dirty = true;
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::D1))) => {
|
|
self.constants = Constants { r1: R1, r2: R2, r3: R3, ..self.constants };
|
|
self.constants_dirty = true;
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::D2))) => {
|
|
self.constants = Constants { r1: R1 * 2.0, r2: R2 * 2.0, r3: R3 * 2.0, ..self.constants };
|
|
self.constants_dirty = true;
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::D3))) => {
|
|
self.constants = Constants { r1: R1 * 4.0, r2: R2 * 4.0, r3: R3 * 4.0, ..self.constants };
|
|
self.constants_dirty = true;
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::D4))) => {
|
|
self.constants = Constants { r1: R1 * 16.0, r2: R2 * 16.0, r3: R3 * 16.0, ..self.constants };
|
|
self.constants_dirty = true;
|
|
},
|
|
|
|
Piston(Input::Press(Button::Keyboard(Key::H))) => {
|
|
self.constants = Constants { haze: self.constants.haze * 2.0f32.sqrt().sqrt(), ..self.constants };
|
|
println!("haze: {}", self.constants.haze);
|
|
self.constants_dirty = true;
|
|
},
|
|
Piston(Input::Press(Button::Keyboard(Key::N))) => {
|
|
self.constants = Constants { haze: self.constants.haze / 2.0f32.sqrt().sqrt(), ..self.constants };
|
|
println!("haze: {}", self.constants.haze);
|
|
self.constants_dirty = true;
|
|
},
|
|
_ => ()
|
|
}
|
|
}
|
|
|
|
fn update(&mut self,
|
|
vr: &mut Option<vr::VR>,
|
|
encoder: &mut gfx::Encoder<D::Resources, D::CommandBuffer>) {
|
|
const NANOS_PER_MILLI: u32 = 1_000_000;
|
|
const MILLIS_PER_SEC: u64 = 1_000;
|
|
let elapsed = self.start_time.elapsed().expect("scene timer");
|
|
let millis = elapsed.subsec_nanos() / NANOS_PER_MILLI + (elapsed.as_secs() * MILLIS_PER_SEC) as u32;
|
|
|
|
for (pad, track) in self.pads.iter_mut() {
|
|
let mode = track.0;
|
|
if let Some(state) = vr.as_ref().and_then(|vr| vr.get_controller_state(*pad)) {
|
|
if let Some(old_state) = track.1 {
|
|
match mode {
|
|
TrackMode::Touch => {
|
|
const THRESHOLD: f32 = 0.005;
|
|
const SCALE: f32 = 32.0;
|
|
let xdiff = state.rAxis[0].x - old_state.rAxis[0].x;
|
|
let ydiff = state.rAxis[0].y - old_state.rAxis[0].y;
|
|
if xdiff.abs() > THRESHOLD { self.treadmills.0 += SCALE * xdiff; }
|
|
if ydiff.abs() > THRESHOLD { self.treadmills.1 += SCALE * ydiff; }
|
|
},
|
|
TrackMode::Press => {
|
|
let rot = na::Vector3::new(0.0, 0.0, 0.0);
|
|
let speed = R2 * 0.005;
|
|
if state.rAxis[0].x > 0.5 {
|
|
self.camera = na::Similarity3::new(na::Vector3::new(-speed, 0.0, 0.0),
|
|
rot, 1.0).to_homogeneous() * self.camera;
|
|
} if state.rAxis[0].x < -0.5 {
|
|
self.camera = na::Similarity3::new(na::Vector3::new( speed, 0.0, 0.0),
|
|
rot, 1.0).to_homogeneous() * self.camera;
|
|
} if state.rAxis[0].y > 0.5 {
|
|
self.camera = na::Similarity3::new(na::Vector3::new( 0.0, -speed, 0.0),
|
|
rot, 1.0).to_homogeneous() * self.camera;
|
|
} if state.rAxis[0].y < -0.5 {
|
|
self.camera = na::Similarity3::new(na::Vector3::new( 0.0, speed, 0.0),
|
|
rot, 1.0).to_homogeneous() * self.camera;
|
|
}
|
|
},
|
|
}
|
|
|
|
if state.unPacketNum == old_state.unPacketNum {
|
|
continue;
|
|
}
|
|
}
|
|
*track = (mode, Some(state));
|
|
}
|
|
}
|
|
|
|
if self.constants_dirty {
|
|
self.constants_dirty = false;
|
|
encoder.update_constant_buffer(&self.constants_buffer, &self.constants);
|
|
}
|
|
|
|
encoder.update_constant_buffer(&self.locals, &Locals { millis: millis,
|
|
treadmill_x: self.treadmills.0,
|
|
treadmill_y: self.treadmills.1 });
|
|
}
|
|
|
|
fn render(&self,
|
|
_factory: &mut F,
|
|
encoder: &mut gfx::Encoder<D::Resources, D::CommandBuffer>,
|
|
trans: &gfx::handle::Buffer<D::Resources, view::Trans>,
|
|
target: &gfx::handle::RenderTargetView<D::Resources, view::ColorFormat>,
|
|
depth: &gfx::handle::DepthStencilView<D::Resources, view::DepthFormat>) {
|
|
|
|
encoder.clear(&target, SKY_COLOR);
|
|
encoder.clear_depth(&depth, 1.0);
|
|
let pipe = pipe::Data {
|
|
vbuf: self.vbuf.clone(),
|
|
trans: trans.clone(),
|
|
constants: self.constants_buffer.clone(),
|
|
locals: self.locals.clone(),
|
|
atlas: (self.atlas.clone(), self.sampler.clone()),
|
|
pixcolor: target.clone(),
|
|
depth: depth.clone(),
|
|
};
|
|
encoder.draw(&self.slice, &self.pso, &pipe);
|
|
}
|
|
|
|
fn origin(&self) -> na::Matrix4<f32> {
|
|
let (r1, r2, r3) = (self.constants.r1, self.constants.r2, self.constants.r3);
|
|
let (y, x) = (self.lat as f32 + 0.5, self.lng as f32 + 0.5); // center of tile
|
|
let eye = Self::toroid((x, y), r1, r2, r3);
|
|
let looktgt = Self::toroid((x, y - 1.0), r1, r2, r3); // look ahead = north
|
|
let normal = Self::toroid((x, y), 0.0, r2, r2) * na::Vector3::new(r2 / r3, 1.0, 1.0);
|
|
self.camera * na::Isometry3::look_at_rh(eye.as_point(),
|
|
looktgt.as_point(),
|
|
&normal,
|
|
).to_homogeneous()
|
|
}
|
|
}
|
|
|
|
fn get_data_model() -> model::World {
|
|
use self::memmap::{Mmap, Protection};
|
|
use std::mem::transmute;
|
|
|
|
fn mmap_to_rows<'a, M: model::HasMap>(mmap: &memmap::Mmap) -> &'a M
|
|
where M: Copy + 'a
|
|
{
|
|
assert_eq!(::std::mem::size_of::<M>(), mmap.len());
|
|
unsafe { transmute::<*const u8, &M>(mmap.ptr()) }
|
|
}
|
|
|
|
let filename = "data/WORLD.MAP";
|
|
let file_mmap = Mmap::open_path(filename, Protection::Read).unwrap();
|
|
mmap_to_rows::<model::World>(&file_mmap).clone()
|
|
}
|
|
|
|
const VERTEX_SHADER_SRC: &'static [u8] = include_bytes!("shader/torus_vertex.glsl");
|
|
const FRAGMENT_SHADER_SRC: &'static [u8] = include_bytes!("shader/tile_frag.glsl");
|