667 lines
16 KiB
TypeScript
667 lines
16 KiB
TypeScript
|
import { Game } from "planetwars-rs";
|
||
|
// import { memory } from "planetwars-rs/planetwars_rs_bg";
|
||
|
// const memory = planetwars_bg.memory;
|
||
|
import type { Dictionary } from './webgl/util';
|
||
|
import type { BBox } from "./voronoi/voronoi-core";
|
||
|
|
||
|
import {
|
||
|
Resizer,
|
||
|
resizeCanvasToDisplaySize,
|
||
|
FPSCounter,
|
||
|
url_to_mesh,
|
||
|
Mesh,
|
||
|
} from "./webgl/util";
|
||
|
import {
|
||
|
Shader,
|
||
|
Uniform4f,
|
||
|
Uniform3fv,
|
||
|
Uniform1f,
|
||
|
Uniform2f,
|
||
|
ShaderFactory,
|
||
|
Uniform3f,
|
||
|
UniformMatrix3fv,
|
||
|
UniformBool,
|
||
|
} from "./webgl/shader";
|
||
|
import { Renderer } from "./webgl/renderer";
|
||
|
import { VertexBuffer, IndexBuffer } from "./webgl/buffer";
|
||
|
import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
|
||
|
import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text";
|
||
|
import { VoronoiBuilder } from "./voronoi/voronoi";
|
||
|
|
||
|
// svg-mesh requires global to exist
|
||
|
(window as any).global = window;
|
||
|
|
||
|
|
||
|
|
||
|
function to_bbox(box: number[]): BBox {
|
||
|
return {
|
||
|
xl: box[0],
|
||
|
xr: box[0] + box[2],
|
||
|
yt: box[1],
|
||
|
yb: box[1] + box[3],
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// function f32v(ptr: number, size: number): Float32Array {
|
||
|
// return new Float32Array(memory.buffer, ptr, size);
|
||
|
// }
|
||
|
|
||
|
// function i32v(ptr: number, size: number): Int32Array {
|
||
|
// return new Int32Array(memory.buffer, ptr, size);
|
||
|
// }
|
||
|
|
||
|
export function set_game_name(name: string) {
|
||
|
ELEMENTS["name"].innerHTML = name;
|
||
|
}
|
||
|
|
||
|
export function set_loading(loading: boolean) {
|
||
|
if (loading) {
|
||
|
if (!ELEMENTS["main"].classList.contains("loading")) {
|
||
|
ELEMENTS["main"].classList.add("loading");
|
||
|
}
|
||
|
} else {
|
||
|
ELEMENTS["main"].classList.remove("loading");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const ELEMENTS: any = {};
|
||
|
var CANVAS: any;
|
||
|
var RESOLUTION: any;
|
||
|
var GL: any;
|
||
|
var ms_per_frame: any;
|
||
|
|
||
|
const LAYERS = {
|
||
|
vor: -1, // Background
|
||
|
planet: 1,
|
||
|
planet_label: 2,
|
||
|
ship: 3,
|
||
|
ship_label: 4,
|
||
|
};
|
||
|
|
||
|
const COUNTER = new FPSCounter();
|
||
|
|
||
|
|
||
|
|
||
|
export function init() {
|
||
|
[
|
||
|
"name",
|
||
|
"turnCounter",
|
||
|
"main",
|
||
|
"turnSlider",
|
||
|
"fileselect",
|
||
|
"speed",
|
||
|
"canvas",
|
||
|
].forEach((n) => (ELEMENTS[n] = document.getElementById(n)));
|
||
|
|
||
|
CANVAS = ELEMENTS["canvas"];
|
||
|
RESOLUTION = [CANVAS.width, CANVAS.height];
|
||
|
|
||
|
ms_per_frame = parseInt(ELEMENTS["speed"].value);
|
||
|
|
||
|
GL = CANVAS.getContext("webgl");
|
||
|
|
||
|
GL.clearColor(0, 0, 0, 1);
|
||
|
GL.clear(GL.COLOR_BUFFER_BIT);
|
||
|
|
||
|
GL.enable(GL.BLEND);
|
||
|
GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
|
||
|
|
||
|
window.addEventListener(
|
||
|
"resize",
|
||
|
function () {
|
||
|
resizeCanvasToDisplaySize(CANVAS);
|
||
|
|
||
|
if (game_instance) {
|
||
|
game_instance.on_resize();
|
||
|
}
|
||
|
},
|
||
|
{ capture: false, passive: true }
|
||
|
);
|
||
|
|
||
|
ELEMENTS["turnSlider"].oninput = function () {
|
||
|
if (game_instance) {
|
||
|
game_instance.updateTurn(parseInt(ELEMENTS["turnSlider"].value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ELEMENTS["speed"].onchange = function () {
|
||
|
ms_per_frame = parseInt(ELEMENTS["speed"].value);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export class GameInstance {
|
||
|
resizer: Resizer;
|
||
|
game: Game;
|
||
|
|
||
|
shader: Shader;
|
||
|
vor_shader: Shader;
|
||
|
image_shader: Shader;
|
||
|
|
||
|
text_factory: LabelFactory;
|
||
|
planet_labels: Label[];
|
||
|
ship_labels: Label[];
|
||
|
|
||
|
ship_ibo: IndexBuffer;
|
||
|
ship_vao: VertexArray;
|
||
|
// TODO: find a better way
|
||
|
max_num_ships: number;
|
||
|
|
||
|
renderer: Renderer;
|
||
|
planet_count: number;
|
||
|
|
||
|
vor_builder: VoronoiBuilder;
|
||
|
|
||
|
vor_counter = 3;
|
||
|
use_vor = true;
|
||
|
playing = true;
|
||
|
time_stopped_delta = 0;
|
||
|
last_time = 0;
|
||
|
frame = -1;
|
||
|
|
||
|
turn_count = 0;
|
||
|
|
||
|
constructor(
|
||
|
game: Game,
|
||
|
meshes: Mesh[],
|
||
|
ship_mesh: Mesh,
|
||
|
shaders: Dictionary<ShaderFactory>
|
||
|
) {
|
||
|
this.game = game;
|
||
|
const planets = game.get_planets();
|
||
|
this.planet_count = planets.length;
|
||
|
|
||
|
this.shader = shaders["normal"].create_shader(GL, {
|
||
|
MAX_CIRCLES: "" + planets.length,
|
||
|
});
|
||
|
this.image_shader = shaders["image"].create_shader(GL);
|
||
|
this.vor_shader = shaders["vor"].create_shader(GL, {
|
||
|
PLANETS: "" + planets.length,
|
||
|
});
|
||
|
|
||
|
this.text_factory = defaultLabelFactory(GL, this.image_shader);
|
||
|
this.planet_labels = [];
|
||
|
this.ship_labels = [];
|
||
|
|
||
|
this.resizer = new Resizer(CANVAS, [...game.get_viewbox()], true);
|
||
|
this.renderer = new Renderer();
|
||
|
this.game.update_turn(0);
|
||
|
|
||
|
// Setup key handling
|
||
|
document.addEventListener("keydown", this.handleKey.bind(this));
|
||
|
|
||
|
// List of [(x, y, r)] for all planets
|
||
|
this._create_voronoi(planets);
|
||
|
this._create_planets(planets, meshes);
|
||
|
|
||
|
// create_shipes
|
||
|
this.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");
|
||
|
this.ship_vao = new VertexArray();
|
||
|
this.ship_vao.addBuffer(ship_positions, ship_layout);
|
||
|
this.max_num_ships = 0;
|
||
|
|
||
|
// Set slider correctly
|
||
|
this.turn_count = game.turn_count();
|
||
|
ELEMENTS["turnSlider"].max = this.turn_count - 1 + "";
|
||
|
}
|
||
|
|
||
|
push_state(state: string) {
|
||
|
this.game.push_state(state);
|
||
|
|
||
|
if (this.frame == this.turn_count - 1) {
|
||
|
this.playing = true;
|
||
|
}
|
||
|
|
||
|
// Set slider correctly
|
||
|
this.turn_count = this.game.turn_count();
|
||
|
this.updateTurnCounters();
|
||
|
}
|
||
|
|
||
|
_create_voronoi(planets: Float32Array) {
|
||
|
const planet_points = [];
|
||
|
for (let i = 0; i < planets.length; i += 3) {
|
||
|
planet_points.push({ x: -planets[i], y: -planets[i + 1] });
|
||
|
}
|
||
|
|
||
|
const bbox = to_bbox(this.resizer.get_viewbox());
|
||
|
|
||
|
this.vor_builder = new VoronoiBuilder(
|
||
|
GL,
|
||
|
this.vor_shader,
|
||
|
planet_points,
|
||
|
bbox
|
||
|
);
|
||
|
this.renderer.addRenderable(this.vor_builder.getRenderable(), LAYERS.vor);
|
||
|
}
|
||
|
|
||
|
_create_planets(planets: Float32Array, meshes: Mesh[]) {
|
||
|
for (let i = 0; i < this.planet_count; i++) {
|
||
|
{
|
||
|
const transform = new UniformMatrix3fv([
|
||
|
1,
|
||
|
0,
|
||
|
0,
|
||
|
0,
|
||
|
1,
|
||
|
0,
|
||
|
-planets[i * 3],
|
||
|
-planets[i * 3 + 1],
|
||
|
1,
|
||
|
]);
|
||
|
|
||
|
const indexBuffer = new IndexBuffer(
|
||
|
GL,
|
||
|
meshes[i % meshes.length].cells
|
||
|
);
|
||
|
const positionBuffer = new VertexBuffer(
|
||
|
GL,
|
||
|
meshes[i % meshes.length].positions
|
||
|
);
|
||
|
|
||
|
const layout = new VertexBufferLayout();
|
||
|
layout.push(GL.FLOAT, 3, 4, "a_position");
|
||
|
const vao = new VertexArray();
|
||
|
vao.addBuffer(positionBuffer, layout);
|
||
|
|
||
|
this.renderer.addToDraw(
|
||
|
indexBuffer,
|
||
|
vao,
|
||
|
this.shader,
|
||
|
{
|
||
|
u_trans: transform,
|
||
|
u_trans_next: transform,
|
||
|
},
|
||
|
[],
|
||
|
LAYERS.planet
|
||
|
);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
const transform = new UniformMatrix3fv([
|
||
|
1,
|
||
|
0,
|
||
|
0,
|
||
|
0,
|
||
|
1,
|
||
|
0,
|
||
|
-planets[i * 3],
|
||
|
-planets[i * 3 + 1] - 1.2,
|
||
|
1,
|
||
|
]);
|
||
|
|
||
|
const label = this.text_factory.build(GL, transform);
|
||
|
this.planet_labels.push(label);
|
||
|
this.renderer.addRenderable(label.getRenderable(), LAYERS.planet_label);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
on_resize() {
|
||
|
this.resizer = new Resizer(CANVAS, [...this.game.get_viewbox()], true);
|
||
|
const bbox = to_bbox(this.resizer.get_viewbox());
|
||
|
this.vor_builder.resize(GL, bbox);
|
||
|
}
|
||
|
|
||
|
_update_state() {
|
||
|
this._update_planets();
|
||
|
this._update_ships();
|
||
|
}
|
||
|
|
||
|
_update_planets() {
|
||
|
const colours = this.game.get_planet_colors();
|
||
|
const planet_ships = this.game.get_planet_ships();
|
||
|
|
||
|
this.vor_shader.uniform(GL, "u_planet_colours", new Uniform3fv(colours));
|
||
|
|
||
|
for (let i = 0; i < this.planet_count; i++) {
|
||
|
const u = new Uniform3f(
|
||
|
colours[i * 6],
|
||
|
colours[i * 6 + 1],
|
||
|
colours[i * 6 + 2]
|
||
|
);
|
||
|
this.renderer.updateUniform(
|
||
|
i,
|
||
|
(us) => (us["u_color"] = u),
|
||
|
LAYERS.planet
|
||
|
);
|
||
|
const u2 = new Uniform3f(
|
||
|
colours[i * 6 + 3],
|
||
|
colours[i * 6 + 4],
|
||
|
colours[i * 6 + 5]
|
||
|
);
|
||
|
this.renderer.updateUniform(
|
||
|
i,
|
||
|
(us) => (us["u_color_next"] = u2),
|
||
|
LAYERS.planet
|
||
|
);
|
||
|
|
||
|
this.planet_labels[i].setText(
|
||
|
GL,
|
||
|
"*" + planet_ships[i],
|
||
|
Align.Middle,
|
||
|
Align.Begin
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_update_ships() {
|
||
|
const ships = this.game.get_ship_locations();
|
||
|
const labels = this.game.get_ship_label_locations();
|
||
|
const ship_counts = this.game.get_ship_counts();
|
||
|
const ship_colours = this.game.get_ship_colours();
|
||
|
|
||
|
for (let i = this.max_num_ships; i < ship_counts.length; i++) {
|
||
|
this.renderer.addToDraw(
|
||
|
this.ship_ibo,
|
||
|
this.ship_vao,
|
||
|
this.shader,
|
||
|
{},
|
||
|
[],
|
||
|
LAYERS.ship
|
||
|
);
|
||
|
|
||
|
const label = this.text_factory.build(GL);
|
||
|
this.ship_labels.push(label);
|
||
|
this.renderer.addRenderable(label.getRenderable(), LAYERS.ship_label);
|
||
|
}
|
||
|
if (ship_counts.length > this.max_num_ships)
|
||
|
this.max_num_ships = ship_counts.length;
|
||
|
|
||
|
// TODO: actually remove obsolete ships
|
||
|
for (let i = 0; i < this.max_num_ships; i++) {
|
||
|
if (i < ship_counts.length) {
|
||
|
this.ship_labels[i].setText(
|
||
|
GL,
|
||
|
"" + ship_counts[i],
|
||
|
Align.Middle,
|
||
|
Align.Middle
|
||
|
);
|
||
|
|
||
|
this.renderer.enableRenderable(i, LAYERS.ship);
|
||
|
this.renderer.enableRenderable(i, LAYERS.ship_label);
|
||
|
|
||
|
const u = new Uniform3f(
|
||
|
ship_colours[i * 3],
|
||
|
ship_colours[i * 3 + 1],
|
||
|
ship_colours[i * 3 + 2]
|
||
|
);
|
||
|
|
||
|
const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 9));
|
||
|
const t2 = new UniformMatrix3fv(ships.slice(i * 18 + 9, i * 18 + 18));
|
||
|
|
||
|
const tl1 = new UniformMatrix3fv(labels.slice(i * 18, i * 18 + 9));
|
||
|
const tl2 = new UniformMatrix3fv(labels.slice(i * 18 + 9, i * 18 + 18));
|
||
|
|
||
|
this.renderer.updateUniform(
|
||
|
i,
|
||
|
(us) => {
|
||
|
us["u_color"] = u;
|
||
|
us["u_color_next"] = u;
|
||
|
us["u_trans"] = t1;
|
||
|
us["u_trans_next"] = t2;
|
||
|
},
|
||
|
LAYERS.ship
|
||
|
);
|
||
|
|
||
|
this.renderer.updateUniform(
|
||
|
i,
|
||
|
(us) => {
|
||
|
us["u_trans"] = tl1;
|
||
|
us["u_trans_next"] = tl2;
|
||
|
},
|
||
|
LAYERS.ship_label
|
||
|
);
|
||
|
} else {
|
||
|
this.renderer.disableRenderable(i, LAYERS.ship);
|
||
|
this.renderer.disableRenderable(i, LAYERS.ship_label);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
render(time: number) {
|
||
|
COUNTER.frame(time);
|
||
|
|
||
|
if (COUNTER.delta(time) < 30) {
|
||
|
this.vor_counter = Math.min(3, this.vor_counter + 1);
|
||
|
} else {
|
||
|
this.vor_counter = Math.max(-3, this.vor_counter - 1);
|
||
|
}
|
||
|
|
||
|
if (this.vor_counter < -2) {
|
||
|
this.use_vor = false;
|
||
|
}
|
||
|
|
||
|
// If not playing, still reder with different viewbox, so people can still pan etc.
|
||
|
if (!this.playing) {
|
||
|
this.last_time = time;
|
||
|
|
||
|
this.shader.uniform(
|
||
|
GL,
|
||
|
"u_viewbox",
|
||
|
new Uniform4f(this.resizer.get_viewbox())
|
||
|
);
|
||
|
this.vor_shader.uniform(
|
||
|
GL,
|
||
|
"u_viewbox",
|
||
|
new Uniform4f(this.resizer.get_viewbox())
|
||
|
);
|
||
|
this.image_shader.uniform(
|
||
|
GL,
|
||
|
"u_viewbox",
|
||
|
new Uniform4f(this.resizer.get_viewbox())
|
||
|
);
|
||
|
|
||
|
this.renderer.render(GL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check if turn is still correct
|
||
|
if (time > this.last_time + ms_per_frame) {
|
||
|
this.last_time = time;
|
||
|
this.updateTurn(this.frame + 1);
|
||
|
if (this.frame == this.turn_count - 1) {
|
||
|
this.playing = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Do GL things
|
||
|
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.vor_shader.uniform(
|
||
|
GL,
|
||
|
"u_time",
|
||
|
new Uniform1f((time - this.last_time) / ms_per_frame)
|
||
|
);
|
||
|
this.vor_shader.uniform(
|
||
|
GL,
|
||
|
"u_viewbox",
|
||
|
new Uniform4f(this.resizer.get_viewbox())
|
||
|
);
|
||
|
this.vor_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
|
||
|
this.vor_shader.uniform(GL, "u_vor", new UniformBool(this.use_vor));
|
||
|
|
||
|
this.shader.uniform(
|
||
|
GL,
|
||
|
"u_time",
|
||
|
new Uniform1f((time - this.last_time) / ms_per_frame)
|
||
|
);
|
||
|
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));
|
||
|
|
||
|
this.image_shader.uniform(
|
||
|
GL,
|
||
|
"u_time",
|
||
|
new Uniform1f((time - this.last_time) / ms_per_frame)
|
||
|
);
|
||
|
this.image_shader.uniform(
|
||
|
GL,
|
||
|
"u_mouse",
|
||
|
new Uniform2f(this.resizer.get_mouse_pos())
|
||
|
);
|
||
|
this.image_shader.uniform(
|
||
|
GL,
|
||
|
"u_viewbox",
|
||
|
new Uniform4f(this.resizer.get_viewbox())
|
||
|
);
|
||
|
this.image_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
|
||
|
|
||
|
// Render
|
||
|
this.renderer.render(GL);
|
||
|
|
||
|
COUNTER.frame_end();
|
||
|
}
|
||
|
|
||
|
updateTurn(turn: number) {
|
||
|
this.frame = Math.max(0, turn);
|
||
|
const new_frame = this.game.update_turn(this.frame);
|
||
|
if (new_frame < this.frame) {
|
||
|
this.frame = new_frame;
|
||
|
this.playing = false;
|
||
|
} else {
|
||
|
this._update_state();
|
||
|
this.playing = true;
|
||
|
}
|
||
|
|
||
|
this.updateTurnCounters();
|
||
|
}
|
||
|
|
||
|
updateTurnCounters() {
|
||
|
ELEMENTS["turnCounter"].innerHTML =
|
||
|
this.frame + " / " + (this.turn_count - 1);
|
||
|
ELEMENTS["turnSlider"].value = this.frame + "";
|
||
|
ELEMENTS["turnSlider"].max = this.turn_count - 1 + "";
|
||
|
}
|
||
|
|
||
|
handleKey(event: KeyboardEvent) {
|
||
|
// Space
|
||
|
if (event.keyCode == 32) {
|
||
|
if (this.playing) {
|
||
|
this.playing = false;
|
||
|
} else {
|
||
|
this.playing = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Arrow left
|
||
|
if (event.keyCode == 37) {
|
||
|
// This feels more natural than -1 what it should be, I think
|
||
|
this.updateTurn(this.frame - 2);
|
||
|
}
|
||
|
|
||
|
// Arrow right
|
||
|
if (event.keyCode == 39) {
|
||
|
this.updateTurn(this.frame + 1);
|
||
|
}
|
||
|
|
||
|
// d key
|
||
|
if (event.keyCode == 68) {
|
||
|
ELEMENTS["speed"].value = ms_per_frame + 10 + "";
|
||
|
ELEMENTS["speed"].onchange(undefined);
|
||
|
}
|
||
|
|
||
|
// a key
|
||
|
if (event.keyCode == 65) {
|
||
|
ELEMENTS["speed"].value = Math.max(ms_per_frame - 10, 0) + "";
|
||
|
ELEMENTS["speed"].onchange(undefined);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var game_instance: GameInstance;
|
||
|
var meshes: Mesh[];
|
||
|
var shaders: Dictionary<ShaderFactory>;
|
||
|
|
||
|
export async function set_instance(source: string): Promise<GameInstance> {
|
||
|
if (!meshes || !shaders) {
|
||
|
const mesh_promises = ["ship.svg", "earth.svg", "mars.svg", "venus.svg"]
|
||
|
.map((name) => "/static/res/assets/" + name)
|
||
|
.map(url_to_mesh);
|
||
|
|
||
|
const shader_promies = [
|
||
|
(async () =>
|
||
|
<[string, ShaderFactory]>[
|
||
|
"normal",
|
||
|
await ShaderFactory.create_factory(
|
||
|
"/static/shaders/frag/simple.glsl",
|
||
|
"/static/shaders/vert/simple.glsl"
|
||
|
),
|
||
|
])(),
|
||
|
(async () =>
|
||
|
<[string, ShaderFactory]>[
|
||
|
"vor",
|
||
|
await ShaderFactory.create_factory(
|
||
|
"/static/shaders/frag/vor.glsl",
|
||
|
"/static/shaders/vert/vor.glsl"
|
||
|
),
|
||
|
])(),
|
||
|
(async () =>
|
||
|
<[string, ShaderFactory]>[
|
||
|
"image",
|
||
|
await ShaderFactory.create_factory(
|
||
|
"/static/shaders/frag/image.glsl",
|
||
|
"/static/shaders/vert/simple.glsl"
|
||
|
),
|
||
|
])(),
|
||
|
];
|
||
|
let shaders_array: [string, ShaderFactory][];
|
||
|
[meshes, shaders_array] = await Promise.all([
|
||
|
Promise.all(mesh_promises),
|
||
|
Promise.all(shader_promies),
|
||
|
]);
|
||
|
|
||
|
shaders = {};
|
||
|
shaders_array.forEach(([name, fac]) => (shaders[name] = fac));
|
||
|
}
|
||
|
|
||
|
resizeCanvasToDisplaySize(CANVAS);
|
||
|
|
||
|
game_instance = new GameInstance(
|
||
|
Game.new(source),
|
||
|
meshes.slice(1),
|
||
|
meshes[0],
|
||
|
shaders
|
||
|
);
|
||
|
|
||
|
set_loading(false);
|
||
|
start();
|
||
|
return game_instance;
|
||
|
}
|
||
|
|
||
|
var _animating = false;
|
||
|
|
||
|
export function start() {
|
||
|
if (_animating) {
|
||
|
// already running
|
||
|
return;
|
||
|
}
|
||
|
_animating = true;
|
||
|
requestAnimationFrame(step);
|
||
|
}
|
||
|
|
||
|
export function stop() {
|
||
|
_animating = false;
|
||
|
}
|
||
|
|
||
|
function step(time: number) {
|
||
|
if (game_instance) {
|
||
|
game_instance.render(time);
|
||
|
}
|
||
|
|
||
|
if (_animating) {
|
||
|
requestAnimationFrame(step);
|
||
|
}
|
||
|
}
|