From adc8759e0ecf250dbf72c5df197d39a33da429ae Mon Sep 17 00:00:00 2001 From: ajuvercr Date: Fri, 20 Sep 2019 17:48:00 +0200 Subject: [PATCH] Flying straight --- frontend/src/lib.rs | 161 ++++++++++++++++++- frontend/src/types.rs | 20 +++ frontend/www/index.ts | 71 ++++++-- frontend/www/static/res/assets/ship.svg | 66 ++++++++ frontend/www/static/shaders/vert/simple.glsl | 10 +- frontend/www/webgl/renderer.ts | 149 ++++++++++------- frontend/www/webgl/shader.ts | 4 +- 7 files changed, 404 insertions(+), 77 deletions(-) create mode 100644 frontend/www/static/res/assets/ship.svg diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index cfefae7..4d29623 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -4,11 +4,12 @@ extern crate serde_derive; extern crate serde_json; extern crate octoon_math; -use octoon_math::{Mat3, Vec3}; +use octoon_math::{Mat3, Vec3, One}; mod utils; mod types; +use std::collections::HashMap; use wasm_bindgen::prelude::*; // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global @@ -17,16 +18,128 @@ use wasm_bindgen::prelude::*; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +#[derive(Debug, Clone)] +pub struct Circle { + r: f32, + x: f32, + y: f32, + a1: f32, + a2: f32, + distance: usize, +} + +impl Circle { + pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self { + let dx = p1.x - p2.x; + let dy = p1.y - p2.y; + let dr = (dx * dx + dy * dy).sqrt(); + let r = dr * 1.05; + let distance = (dx * dx + dy * dy).sqrt().ceil() as usize; + + let d = p1.x * p2.y - p2.x * p1.y; + + let x = (d * dy + dy.signum() * dx * (r * r * dr * dr - d * d).sqrt()) / (dr * dr); + let y = (-d * dx + dy.abs() * (r * r * dr * dr - d * d).sqrt()) / (dr * dr); + + let a1 = (y - p1.y).atan2(x - p1.x); + let a2 = (y - p2.y).atan2(x - p2.x); + + Self { + r, x, y, a1, a2, distance + } + } + + pub fn get_for_remaining(&self, remaining: usize) -> (Mat3, Mat3) { + ( + self.get_remaining(remaining), + self.get_remaining((remaining + 1).min(self.distance - 1)), + ) + } + + fn get_remaining(&self, remaining: usize) -> Mat3 { + let alpha = self.a1 + (1.0 - (remaining as f32 / self.distance as f32)) * (self.a2 - self.a1); + let c = alpha.cos(); + let s = alpha.sin(); + + Mat3::new( + c, -s, 0.0, + s, c, 0.0, + 0.0, 0.0, 1.0 + ) * Mat3::new( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + self.x, self.y, 1.0, + ) * Mat3::new( + c, -s, 0.0, + s, c, 0.0, + 0.0, 0.0, 1.0 + ) + } +} + +struct Line { + x1: f32, + y1: f32, + x2: f32, + y2: f32, + a: f32, + d: usize, +} +impl Line { + pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self { + let dx = p1.x - p2.x; + let dy = p1.y - p2.y; + let a = dy.atan2(dx); + // let a = (dy / dx).atan(); + let d = (dx * dx + dy * dy).sqrt().ceil() as usize + 1; + + Self { + x1: p1.x, + x2: p2.x, + y1: p1.y, + y2: p2.y, + d, a, + } + } + + pub fn get_for_remaining(&self, remaining: usize) -> (Mat3, Mat3) { + ( + self.get_remaining(remaining), + self.get_remaining((remaining + 1).min(self.d - 1)), + ) + } + + fn get_remaining(&self, remaining: usize) -> Mat3 { + let x = (self.x1 * remaining as f32 + (self.d - remaining) as f32 * self.x2) / self.d as f32; + // let x = self.x1 + (remaining as f32 / self.d as f32) * (self.x2 - self.x1); + let y = (self.y1 * remaining as f32 + (self.d - remaining) as f32 * self.y2) / self.d as f32; + + let c = self.a.cos(); + let s = self.a.sin(); + // let y = self.y1 + (remaining as f32 / self.d as f32) * (self.y2 - self.y1); + Mat3::new( + 0.3, 0.0, 0.0, + 0.0, 0.3, 0.0, + x, y, 0.3, + ) * Mat3::rotate_z(self.a) + } +} + + #[wasm_bindgen] pub struct Game { states: Vec, turn: usize, + planet_map: HashMap<(String, String), Line>, + /* put extra shit here */ - planets: Vec>, view_box: Vec, - ship_locations: Vec>, + planets: Vec>, + + ship_locations: Vec<[f32;9]>, + ship_colours: Vec>, current_planet_colours: Vec>, } @@ -36,16 +149,32 @@ impl Game { utils::set_panic_hook(); // First line is fucked but we just filter out things that cannot parse - let states: Vec = file.split("\n").filter_map(|line| + let mut states: Vec = file.split("\n").filter_map(|line| serde_json::from_str(line).ok() ).collect(); + states.push(types::State { + planets: states.last().unwrap().planets.clone(), + expeditions: Vec::new() + }); + + let mut planet_map = HashMap::new(); + + // Iterator? + for p1 in states[0].planets.iter() { + for p2 in states[0].planets.iter() { + planet_map.insert((p1.name.clone(), p2.name.clone()), Line::new(&p1, &p2)); + } + } Self { planets: utils::get_planets(&states[0].planets, 2.0), view_box: utils::caclulate_viewbox(&states[0].planets), + + planet_map, turn: 0, states, ship_locations: Vec::new(), + ship_colours: Vec::new(), current_planet_colours: Vec::new(), } } @@ -95,7 +224,17 @@ impl Game { } fn update_ship_locations(&mut self) { + let mut new_vec = Vec::new(); + for ship in self.states[self.turn].expeditions.iter() { + let (o1, o2) = self.planet_map.get(&(ship.origin.clone(), ship.destination.clone())).unwrap().get_for_remaining(ship.turns_remaining as usize); + new_vec.push(o1.to_array()); + new_vec.push(o2.to_array()); + } + self.ship_locations = new_vec; + self.ship_colours = self.states[self.turn].expeditions.iter().map(|s| { + utils::COLORS[s.owner as usize % utils::COLORS.len()].into() + }).collect(); } // pub fn add_location(&mut self, x: f32, y: f32, angle: f32) { @@ -104,13 +243,21 @@ impl Game { // ); // } - pub fn location_count(&self) -> usize { - self.ship_locations.len() + pub fn get_max_ships(&self) -> usize { + self.states.iter().map(|s| s.expeditions.len()).max().unwrap() } - pub fn get_ship_locations(&self) -> *const Mat3 { + pub fn get_ship_count(&self) -> usize { + self.states[self.turn].expeditions.len() + } + + pub fn get_ship_locations(&self) -> *const [f32;9] { self.ship_locations.as_ptr() } + + pub fn get_ship_colours(&self) -> *const Vec3 { + self.ship_colours.as_ptr() + } } diff --git a/frontend/src/types.rs b/frontend/src/types.rs index e1a4594..2d7d8c0 100644 --- a/frontend/src/types.rs +++ b/frontend/src/types.rs @@ -17,6 +17,26 @@ pub struct Planet { pub name: String, } +use std::hash::{Hash, Hasher}; +use std::mem; + +impl Hash for Planet { + fn hash(&self, state: &mut H) { + unsafe { + let x: u32 = mem::transmute_copy(&self.x); + let y: u32 = mem::transmute_copy(&self.y); + state.write_u32(x); + state.write_u32(y); + } + } +} + +impl PartialEq for Planet { + fn eq(&self, other: &Self) -> bool { + (self.x - other.x).abs() < 0.0001 && (self.y - other.y).abs() < 0.0001 + } +} +impl Eq for Planet {} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct State { diff --git a/frontend/www/index.ts b/frontend/www/index.ts index 58ef527..f843a24 100644 --- a/frontend/www/index.ts +++ b/frontend/www/index.ts @@ -50,6 +50,8 @@ ShaderFactory.create_factory( LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl" ).then((e) => SHADERFACOTRY = e); + + class GameInstance { resizer: Resizer; game: Game; @@ -60,7 +62,9 @@ class GameInstance { last_time = 0; frame = -1; - constructor(game: Game, meshes: Mesh[]) { + ship_indices: number[]; + + constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh) { this.game = game; this.planet_count = this.game.get_planet_count(); this.shader = SHADERFACOTRY.create_shader(GL, {"MAX_CIRCLES": ''+this.planet_count}); @@ -93,18 +97,36 @@ class GameInstance { this.shader, { "u_trans": transform, + "u_trans_next": transform, } - ) + ); } - // this.game.update_turn(this.frame); + this.ship_indices = []; + const ship_ibo = new IndexBuffer(GL, ship_mesh.cells); + const ship_positions = new VertexBuffer(GL, ship_mesh.positions); + const ship_layout = new VertexBufferLayout(); + ship_layout.push(GL.FLOAT, 3, 4, "a_position"); + const ship_vao = new VertexArray(); + ship_vao.addBuffer(ship_positions, ship_layout); - console.log(f32v(this.game.get_planet_colors(), 3 * this.game.get_planet_count())); - // console.log(this.resizer.get_viewbox()); + for (let i = 0; i < this.game.get_max_ships(); i++) { + this.ship_indices.push( + this.renderer.addToDraw( + ship_ibo, + ship_vao, + this.shader, + {} + ) + ); + } } render(time: number) { - if (time > this.last_time + 1000) { + if (time > this.last_time + 500) { + const transparant = new Uniform3f(0, 0, 0); + const transform = new UniformMatrix3fv([0, 0, 0, 0, 0, 0, 0, 0, 0, ]); + this.last_time = time; this.frame ++; this.game.update_turn(this.frame); @@ -117,13 +139,40 @@ class GameInstance { this.renderer.updateUniform(i, (us) => us["u_color_next"] = u2); } + const ships = f32v(this.game.get_ship_locations(), this.game.get_ship_count() * 9 * 2); + const ship_colours = f32v(this.game.get_ship_colours(), this.game.get_ship_count() * 3); + + for (let i=0; i < this.game.get_max_ships(); i++) { + const index = this.ship_indices[i]; + if (i < this.game.get_ship_count()) { + + this.renderer.enableRendershit(index); + + const u = new Uniform3f(ship_colours[i*3], ship_colours[i*3 + 1], ship_colours[i*3 + 2]); + // const t1 = new UniformMatrix3fv(new Float32Array(ships, i * 18, 9)); + // const t2 = new UniformMatrix3fv(new Float32Array(ships, i * 18 + 9, 9)); + + const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 9)); + const t2 = new UniformMatrix3fv(ships.slice(i * 18 + 9, i * 18 + 18)); + + this.renderer.updateUniform(index, (us) => { + us["u_color"] = u; + us["u_color_next"] = u; + us["u_trans"] = t1; + us["u_trans_next"] = t2; + }); + } else { + this.renderer.disableRenderShift(index); + } + } } + GL.bindFramebuffer(GL.FRAMEBUFFER, null); GL.viewport(0, 0, GL.canvas.width, GL.canvas.height); GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT); - this.shader.uniform(GL, "u_step_interval", new Uniform1f(1000)); - this.shader.uniform(GL, "u_time", new Uniform1f(time)); + this.shader.uniform(GL, "u_step_interval", new Uniform1f(500)); + this.shader.uniform(GL, "u_time", new Uniform1f((time - this.last_time) / 500)); this.shader.uniform(GL, "u_mouse", new Uniform2f(this.resizer.get_mouse_pos())); this.shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox())); this.shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); @@ -138,18 +187,18 @@ var game_instance: GameInstance; export async function set_instance(game: Game) { const meshes = await Promise.all( - ["earth.svg", "mars.svg", "venus.svg"].map( + ["ship.svg", "earth.svg", "mars.svg", "venus.svg"].map( (name) => "static/res/assets/" + name ).map(url_to_mesh) ); - game_instance = new GameInstance(game, meshes); + game_instance = new GameInstance(game, meshes.slice(1), meshes[0]); } function step(time: number) { if (game_instance) { - game_instance.render(time); set_loading(false); + game_instance.render(time); } else { set_loading(true); } diff --git a/frontend/www/static/res/assets/ship.svg b/frontend/www/static/res/assets/ship.svg new file mode 100644 index 0000000..6e4d6f2 --- /dev/null +++ b/frontend/www/static/res/assets/ship.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/frontend/www/static/shaders/vert/simple.glsl b/frontend/www/static/shaders/vert/simple.glsl index e35e68b..38cc502 100644 --- a/frontend/www/static/shaders/vert/simple.glsl +++ b/frontend/www/static/shaders/vert/simple.glsl @@ -4,16 +4,24 @@ precision mediump float; attribute vec2 a_position; + +uniform float u_step_interval; +uniform float u_time; + uniform vec4 u_viewbox; // [x, y, width, height] uniform vec2 u_resolution; uniform mat3 u_trans; +uniform mat3 u_trans_next; varying vec2 v_pos; void main() { vec3 pos = vec3(a_position, 1.0); - pos = u_trans * pos; + // float part = fract(u_time / u_step_interval); + mat3 trans = (u_trans_next * (1.0 - u_time)) + (u_trans * u_time); + + pos = trans * pos; vec2 uv = pos.xy; diff --git a/frontend/www/webgl/renderer.ts b/frontend/www/webgl/renderer.ts index 26ef696..38fc65d 100644 --- a/frontend/www/webgl/renderer.ts +++ b/frontend/www/webgl/renderer.ts @@ -9,41 +9,109 @@ export interface Renderable { render(gl: WebGLRenderingContext): void; } -export class Renderer { - renderables: Renderable[]; - - indexBuffers: IndexBuffer[]; - vertexArrays: VertexArray[]; - shaders: Shader[]; +class RenderShit implements Renderable { + ibo: IndexBuffer; + va: VertexArray; + shader: Shader; textures: Texture[]; - uniforms: Dictionary[]; + uniforms: Dictionary; + + enabled: boolean; + + constructor( + ibo: IndexBuffer, + va: VertexArray, + shader: Shader, + textures: Texture[], + uniforms: Dictionary, + ) { + this.enabled = true; + this.ibo = ibo; + this.va = va; + this.shader = shader; + this.textures = textures; + this.uniforms = uniforms; + } + + render(gl: WebGLRenderingContext): void { + if (!this.enabled) { + return; + } + + const indexBuffer = this.ibo; + const vertexArray = this.va; + const uniforms = this.uniforms; + + const shader = this.shader; + const textures = this.textures; + let texLocation = 0; + + for (let texture of textures) { + + shader.uniform(gl, texture.name, new Uniform1i(texLocation)); + texture.bind(gl, texLocation); + + texLocation ++; + // if (texLocation > maxTextures) { + // console.error("Using too many textures, this is not supported yet\nUndefined behaviour!"); + // } + } + + if (vertexArray && shader && uniforms) { + for(let key in uniforms) { + shader.uniform(gl, key, uniforms[key]); + } + + vertexArray.bind(gl, shader); + + if (indexBuffer) { + indexBuffer.bind(gl); + gl.drawElements(gl.TRIANGLES, indexBuffer.getCount(), gl.UNSIGNED_SHORT, 0); + } else { + console.error("IndexBuffer is required to render, for now"); + } + } + + } + +} + +export class Renderer { + renderables: RenderShit[]; constructor() { - this.indexBuffers = []; - this.vertexArrays = []; - this.shaders = []; - this.textures = []; - this.uniforms = []; + this.renderables = []; } updateUniform(i: number, f: (uniforms: Dictionary) => void) { - f(this.uniforms[i]); + f(this.renderables[i].uniforms); } - addRenderable(item: Renderable) { - this.renderables.push(item); + disableRenderShift(i: number) { + this.renderables[i].enabled = false; } - addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, uniforms: Dictionary,texture?: Texture): number { - this.indexBuffers.push(indexBuffer); - this.vertexArrays.push(vertexArray); - this.shaders.push(shader); - this.textures.push(texture); + enableRendershit(i: number) { + this.renderables[i].enabled = true; + } - this.uniforms.push(uniforms); + // addRenderable(item: Renderable) { + // this.renderables.push(item); + // } + addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, uniforms?: Dictionary, texture?: Texture[]): number { - return this.indexBuffers.length - 1; + this.renderables.push( + new RenderShit( + indexBuffer, + vertexArray, + shader, + texture || [], + uniforms || {}, + ) + ); + + return this.renderables.length - 1; } render(gl: WebGLRenderingContext, frameBuffer?: WebGLFramebuffer, width?: number, height?: number) { @@ -52,41 +120,10 @@ export class Renderer { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); const maxTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS); - let texLocation = 0; - for(let i = 0; i < this.indexBuffers.length; i ++) { - const indexBuffer = this.indexBuffers[i]; - const vertexArray = this.vertexArrays[i]; - const uniforms = this.uniforms[i]; - - const shader = this.shaders[i]; - const texture = this.textures[i]; - - if (texture) { - - shader.uniform(gl, texture.name, new Uniform1i(texLocation)); - texture.bind(gl, texLocation); - - texLocation ++; - if (texLocation > maxTextures) { - console.error("Using too many textures, this is not supported yet\nUndefined behaviour!"); - } - } - - if (vertexArray && shader && uniforms) { - for(let key in uniforms) { - shader.uniform(gl, key, uniforms[key]); - } - - vertexArray.bind(gl, shader); - - if (indexBuffer) { - indexBuffer.bind(gl); - gl.drawElements(gl.TRIANGLES, indexBuffer.getCount(), gl.UNSIGNED_SHORT, 0); - } else { - console.error("IndexBuffer is required to render, for now"); - } - } + for (let r of this.renderables) { + r.render(gl); } + } } diff --git a/frontend/www/webgl/shader.ts b/frontend/www/webgl/shader.ts index 3f867e8..3b49421 100644 --- a/frontend/www/webgl/shader.ts +++ b/frontend/www/webgl/shader.ts @@ -306,8 +306,8 @@ export class Uniform4f implements Uniform { } export class UniformMatrix3fv implements Uniform { - data: number[]; - constructor(data: number[]) { + data: number[] | Float32Array; + constructor(data: number[] | Float32Array) { this.data = data; }