This commit is contained in:
ajuvercr 2019-09-17 20:19:04 +02:00
parent 1f18f3d16f
commit aa97ec8837
11 changed files with 1822 additions and 138 deletions

View file

@ -3,9 +3,12 @@
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
<link rel="stylesheet" type="text/css" href="static/res/style.css">
</head>
<body>
<canvas id="c" width=1700 height=900></canvas>
<div id="loader" class="loading">
<canvas id="c" width=1700 height=900></canvas>
</div>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<script src="./bootstrap.js"></script>

View file

@ -1,7 +1,7 @@
import { Game } from "planetwars";
import { Shader } from "./webgl/shader"
import { main } from './index.ts'
import { set_instance } from './index.ts'
const URL = window.location.origin+window.location.pathname;
const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
@ -11,5 +11,5 @@ const game_location = LOCATION + "static/game.json";
fetch(game_location)
.then((r) => r.text())
.then((response) => {
main(Game.new(response));
set_instance(Game.new(response));
}).catch(console.error);

View file

@ -1,15 +1,122 @@
import { Game } from "planetwars";
import { memory } from "planetwars/plantwars_bg";
import { Resizer, resizeCanvasToDisplaySize, FPSCounter } from "./webgl/util";
import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Uniform2f, ShaderFactory } from './webgl/shader';
import { Renderer } from "./webgl/renderer";
import { VertexBuffer, IndexBuffer } from "./webgl/buffer";
import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
import { callbackify } from "util";
const COUNTER = new FPSCounter();
const LOADER = document.getElementById("loader");
function set_loading(loading: boolean) {
if (loading) {
if (!LOADER.classList.contains("loading")) {
LOADER.classList.add("loading");
}
} else {
LOADER.classList.remove("loading");
}
}
const URL = window.location.origin+window.location.pathname;
const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
const CANVAS = <HTMLCanvasElement>document.getElementById("c");
const RESOLUTION = [CANVAS.width, CANVAS.height];
const GL = CANVAS.getContext("webgl");
resizeCanvasToDisplaySize(<HTMLCanvasElement>GL.canvas);
GL.viewport(0, 0, GL.canvas.width, GL.canvas.height);
GL.clearColor(0, 0, 0, 0);
GL.clear(GL.COLOR_BUFFER_BIT);
GL.enable(GL.BLEND);
GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
const positionBuffer = new VertexBuffer(GL, [
-1, -1,
-1, 1,
1, -1,
1, 1,
]);
const layout = new VertexBufferLayout();
layout.push(GL.FLOAT, 2, 4, "a_position");
const vao = new VertexArray();
vao.addBuffer(positionBuffer, layout);
const indexBuffer = new IndexBuffer(GL, [
0, 1, 2,
1, 2, 3,
]);
var SHADERFACOTRY: ShaderFactory;
ShaderFactory.create_factory(
LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl"
).then((e) => SHADERFACOTRY = e);
function create_array(ptr: number, size: number): Float64Array {
return new Float64Array(memory.buffer, ptr, size);
}
export function main(game: Game) {
class GameInstance {
resizer: Resizer;
game: Game;
shader: Shader;
renderer: Renderer;
constructor(game: Game) {
this.game = game;
this.shader = SHADERFACOTRY.create_shader(GL, {"MAX_CIRCLES": "50"});
this.resizer = new Resizer(CANVAS, [...create_array(game.get_viewbox(), 4)], true);
this.renderer = new Renderer();
this.renderer.addToDraw(indexBuffer, vao, this.shader);
}
render(time: number) {
this.shader.uniform(GL, "u_circle_count", new Uniform1i(3));
this.shader.uniform(GL, "u_time", new Uniform1f(time * 0.001));
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.shader.uniform(GL, "u_circles", new Uniform3fv([
0, 0, 3.5,
-2, -2, 2,
5, 2, 4,
]));
this.shader.uniform(GL, "u_color", new Uniform4f([1, 1, 0, 1]));
this.renderer.render(GL);
COUNTER.frame(time);
}
}
var game_instance: GameInstance;
export function set_instance(game: Game) {
game_instance = new GameInstance(game);
console.log(game.turn_count());
console.log(create_array(game.get_viewbox(), 4));
}
function step(time: number) {
if (game_instance) {
game_instance.render(time);
set_loading(false);
} else {
set_loading(true);
}
requestAnimationFrame(step);
}
set_loading(true);
requestAnimationFrame(step);

View file

@ -0,0 +1,57 @@
.loading {
position: relative;
display: inline-block;
}
.loading::before{
content: "";
position: absolute;
width: 100px;
height: 100px;
background-color: #1c5ba2;
border-radius: 100%;
z-index: 5;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
-webkit-animation: slide-top 0.5s ease-out infinite alternate ;
animation: slide-top 0.5s ease-out infinite alternate ;
}
#c {
position: relative;
}
/* ----------------------------------------------
* Generated by Animista on 2019-9-17 14:35:13
* Licensed under FreeBSD License.
* See http://animista.net/license for more info.
* w: http://animista.net, t: @cssanimista
* ---------------------------------------------- */
/**
* ----------------------------------------
* animation slide-top
* ----------------------------------------
*/
@-webkit-keyframes slide-top {
0% {
-webkit-transform: translate(-50%, 50%);
transform: translate(-50%, 50%);
}
100% {
-webkit-transform: translate(-50%, -150%);
transform: translate(-50%, -150%);
}
}
@keyframes slide-top {
0% {
-webkit-transform: translate(-50%, 50%);
transform: translate(-50%, 50%);
}
100% {
-webkit-transform: translate(-50%, -150%);
transform: translate(-50%, -150%);
}
}

View file

@ -0,0 +1,27 @@
#ifdef GL_ES
precision mediump float;
#endif
uniform int u_circle_count;
uniform float u_time;
uniform vec2 u_mouse;
uniform vec4 u_viewbox; // [x, y, width, height]
uniform vec2 u_resolution;
uniform vec3 u_circles[$MAX_CIRCLES];
uniform vec4 u_color;
varying vec2 v_pos;
void main() {
vec2 uv = v_pos;
float alpha = 0.0;
for (int i = 0; i < $MAX_CIRCLES; i++ ){
if (i >= u_circle_count) { break; }
float d = distance(uv.xy, u_circles[i].xy);
alpha = max(1.0 - d/u_circles[i].z, alpha);
}
gl_FragColor = u_color;
gl_FragColor.w *= alpha;
}

View file

@ -0,0 +1,22 @@
#ifdef GL_ES
precision mediump float;
#endif
attribute vec2 a_position;
uniform vec4 u_viewbox; // [x, y, width, height]
uniform vec2 u_resolution;
varying vec2 v_pos;
void main() {
vec2 uv = ( a_position.xy + 1.0 ) * 0.5;
uv *= u_viewbox.zw;
uv += u_viewbox.xy;
v_pos = uv.xy;
gl_Position = vec4(a_position.xy, 0.0, 1.0);
}

115
frontend/www/webgl/index.ts Normal file
View file

@ -0,0 +1,115 @@
import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Uniform2f, ShaderFactory } from './shader';
import { resizeCanvasToDisplaySize, FPSCounter, onload2promise, Resizer } from "./util";
import { VertexBuffer, IndexBuffer } from './buffer';
import { VertexArray, VertexBufferLayout } from './vertexBufferLayout';
import { Renderer } from './renderer';
import { Texture } from './texture';
async function main() {
const URL = window.location.origin+window.location.pathname;
const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
// Get A WebGL context
var canvas = <HTMLCanvasElement>document.getElementById("c");
const resolution = [canvas.width, canvas.height];
const resizer = new Resizer(canvas, [0, 0, 900, 900], true);
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
const renderer = new Renderer();
const factory = await ShaderFactory.create_factory(LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl");
const program = factory.create_shader(gl, {"MAX_CIRCLES": "50"});
var positions = [
-1, -1, 0, 1,
-1, 1, 0, 0,
1, -1, 1, 1,
1, 1, 1, 0,
];
var positionBuffer = new VertexBuffer(gl, positions);
var layout = new VertexBufferLayout();
layout.push(gl.FLOAT, 2, 4, "a_position");
layout.push(gl.FLOAT, 2, 4, "a_tex");
const vao = new VertexArray();
vao.addBuffer(positionBuffer, layout);
resizeCanvasToDisplaySize(<HTMLCanvasElement>gl.canvas);
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
program.bind(gl);
vao.bind(gl, program);
var indices = [
0, 1, 2,
1, 2, 3,
];
var indexBuffer = new IndexBuffer(gl, indices);
indexBuffer.bind(gl);
renderer.addToDraw(indexBuffer, vao, program);
var blue = 1.0;
var inc = 0.05;
const counter = new FPSCounter();
const step = function (time: number) {
blue += inc;
// if (blue > 1.0 || blue < 0.0) {
// inc = -1 * inc;
// blue += inc;
// }
program.uniform(gl, "u_circle_count", new Uniform1i(3));
program.uniform(gl, "u_time", new Uniform1f(time * 0.001));
program.uniform(gl, "u_mouse", new Uniform2f(resizer.get_mouse_pos()));
program.uniform(gl, "u_viewbox", new Uniform4f(resizer.get_viewbox()));
program.uniform(gl, "u_resolution", new Uniform2f(resolution));
program.uniform(gl, "u_circles", new Uniform3fv([
450, 450, 100,
200, 200, 200,
900, 0, 300,
]));
program.uniform(gl, "u_color", new Uniform4f([1, blue, 0, 1]));
renderer.render(gl);
counter.frame(time);
requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
main();
const loader = document.getElementById("loader");
setInterval(() => {
if (loader.classList.contains("loading")) {
loader.classList.remove("loading")
} else {
loader.classList.add("loading");
}
}, 2000);

View file

@ -1,7 +1,7 @@
import { Dictionary } from './util';
function error(msg: string) {
console.log(msg);
console.error(msg);
}
const defaultShaderType = [
@ -9,6 +9,7 @@ const defaultShaderType = [
"FRAGMENT_SHADER"
];
/// Create Shader from Source string
function loadShader(
gl: WebGLRenderingContext,
shaderSource: string,
@ -41,6 +42,7 @@ function loadShader(
return shader;
}
/// Actually Create Program with Shader's
function createProgram(
gl: WebGLRenderingContext,
shaders: WebGLShader[],
@ -76,39 +78,45 @@ function createProgram(
return program;
}
function createShaderFromScript(
gl: WebGLRenderingContext,
scriptId: string,
context: Dictionary<any>,
opt_shaderType: number,
opt_errorCallback: any,
): WebGLShader {
var shaderSource = "";
var shaderType;
var shaderScript = document.getElementById(scriptId) as HTMLScriptElement;
if (!shaderScript) {
console.log("*** Error: unknown script element" + scriptId);
}
shaderSource = shaderScript.text;
export class ShaderFactory {
frag_source: string;
vert_source: string;
for (let key in context) {
console.log("substitute " + key);
shaderSource = shaderSource.replace(new RegExp("\\$" + key, 'g'), context[key]);
static async create_factory(frag_url: string, vert_url: string): Promise<ShaderFactory> {
const sources = await Promise.all([
fetch(frag_url).then((r) => r.text()),
fetch(vert_url).then((r) => r.text()),
]);
return new ShaderFactory(sources[0], sources[1]);
}
if (!opt_shaderType) {
if (shaderScript.type === "x-shader/x-vertex") {
shaderType = 35633;
} else if (shaderScript.type === "x-shader/x-fragment") {
shaderType = 35632;
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
console.log("*** Error: unknown shader type");
constructor(frag_source: string, vert_source: string ) {
this.frag_source = frag_source;
this.vert_source = vert_source;
}
create_shader(
gl: WebGLRenderingContext,
context?: Dictionary<string>,
opt_attribs?: string[],
opt_locations?: number[],
opt_errorCallback?: any,
): Shader {
let vert = this.vert_source.slice();
let frag = this.frag_source.slice();
for (let key in context) {
vert = vert.replace(new RegExp("\\$" + key, 'g'), context[key]);
frag = frag.replace(new RegExp("\\$" + key, 'g'), context[key]);
}
}
return loadShader(
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
opt_errorCallback);
const shaders = [
loadShader(gl, vert, gl.VERTEX_SHADER, opt_errorCallback),
loadShader(gl, frag, gl.FRAGMENT_SHADER, opt_errorCallback),
];
return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
}
}
export class Shader {
@ -116,22 +124,6 @@ export class Shader {
uniformCache: Dictionary<WebGLUniformLocation>;
attribCache: Dictionary<number>;
static createProgramFromScripts(
gl: WebGLRenderingContext,
shaderScriptIds: string[],
context = {},
opt_attribs?: string[],
opt_locations?: number[],
opt_errorCallback?: any,
): Shader {
var shaders = [];
for (var ii = 0; ii < shaderScriptIds.length; ++ii) {
shaders.push(createShaderFromScript(
gl, shaderScriptIds[ii], context, (gl as any)[defaultShaderType[ii % 2]] as number, opt_errorCallback));
}
return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
}
static async createProgramFromUrls(
gl: WebGLRenderingContext,
vert_url: string,
@ -198,6 +190,10 @@ export class Shader {
uniform.setUniform(gl, location);
}
clear(gl: WebGLRenderingContext) {
gl.deleteProgram(this.shader);
}
}
export interface Uniform {
@ -226,6 +222,22 @@ export class Uniform3fv implements Uniform {
}
}
export class Uniform3f implements Uniform {
x: number;
y: number;
z: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
gl.uniform3f(location, this.x ,this.y, this.z);
}
}
export class Uniform1iv implements Uniform {
data: number[];
constructor(data: number[]) {
@ -265,9 +277,9 @@ export class Uniform2f implements Uniform {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
constructor(xy: number[]) {
this.x = xy[0];
this.y = xy[1];
}
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
@ -281,25 +293,14 @@ export class Uniform4f implements Uniform {
v2: number;
v3: number;
constructor(vec: number[]) {
this.v0 = vec[0];
this.v1 = vec[1];
this.v2 = vec[2];
this.v3 = vec[3];
constructor(xyzw: number[]) {
this.v0 = xyzw[0];
this.v1 = xyzw[1];
this.v2 = xyzw[2];
this.v3 = xyzw[3];
}
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
gl.uniform4f(location, this.v0, this.v1, this.v2, this.v3);
}
}
export class UniformMatrix3fv implements Uniform {
data: number[];
constructor(data: number[]) {
this.data = data;
}
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
gl.uniformMatrix3fv(location, false, this.data);
}
}

View file

@ -39,7 +39,7 @@ export class FPSCounter {
frame(now: number) {
this.count += 1;
if (now - this.last > 1) {
if (now - this.last > 1000) {
this.last = now;
console.log(this.count + " fps");
this.count = 0;
@ -47,83 +47,122 @@ export class FPSCounter {
}
}
export class M3 {
_data: any;
export class Resizer {
hoovering: boolean;
dragging: boolean;
constructor(data: any) {
this._data = data;
}
mouse_pos: number[];
last_drag: number[];
static ident(): M3 {
return new M3([
1, 0, 0,
0, 1, 0,
0, 0, 1
]);
}
viewbox: number[];
orig_viewbox: number[];
multiply(other: M3): M3 {
const a = this._data;
const b = other._data;
el_width: number;
var a00 = a[0 * 3 + 0];
var a01 = a[0 * 3 + 1];
var a02 = a[0 * 3 + 2];
var a10 = a[1 * 3 + 0];
var a11 = a[1 * 3 + 1];
var a12 = a[1 * 3 + 2];
var a20 = a[2 * 3 + 0];
var a21 = a[2 * 3 + 1];
var a22 = a[2 * 3 + 2];
var b00 = b[0 * 3 + 0];
var b01 = b[0 * 3 + 1];
var b02 = b[0 * 3 + 2];
var b10 = b[1 * 3 + 0];
var b11 = b[1 * 3 + 1];
var b12 = b[1 * 3 + 2];
var b20 = b[2 * 3 + 0];
var b21 = b[2 * 3 + 1];
var b22 = b[2 * 3 + 2];
scaleX = 1;
scaleY = 1;
return new M3([
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22,
]);
}
constructor(el: HTMLCanvasElement, viewbox: number[], keep_aspect_ratio=false) {
console.log("viewbox:" + viewbox);
this.hoovering = false;
this.dragging = false;
translation(x: number, y: number): M3 {
const out = [...this._data];
out[6] += x;
out[7] += y;
return new M3(out);
}
this.mouse_pos = [0, 0];
this.last_drag = [0, 0];
rotate(rad: number): M3 {
var c = Math.cos(rad);
var s = Math.sin(rad);
this.viewbox = [...viewbox];
const out = new M3([...this._data]);
if (keep_aspect_ratio) {
const or_width = this.viewbox[2];
const or_height = this.viewbox[3];
const scaleX = el.height / el.width;
if (scaleX < 1) {
this.scaleX= 1 / scaleX;
return out.multiply(new M3([
c, -s, 0,
s, c, 0,
0, 0, 1
]));
}
this.viewbox[2] *= this.scaleX;
} else {
this.scaleY = scaleX;
this.viewbox[3] *= scaleX;
}
scale(s_x: number, s_y = s_x, s_z = 1): M3 {
const out = new M3([...this._data]);
return out.multiply(new M3([
s_x, 0, 0,
0, s_y, 0,
0, 0, s_z,
]));
}
this.viewbox[0] -= (this.viewbox[2] - or_width) / 2;
this.viewbox[1] -= (this.viewbox[3] - or_height) / 2;
}
this.orig_viewbox = [...this.viewbox];
this.el_width = el.width;
el.addEventListener("mouseenter", this.mouseenter.bind(this), { capture: false, passive: true});
el.addEventListener("mouseleave", this.mouseleave.bind(this), { capture: false, passive: true});
el.addEventListener("mousemove", this.mousemove.bind(this), { capture: false, passive: true});
el.addEventListener("mousedown", this.mousedown.bind(this), { capture: false, passive: true});
el.addEventListener("mouseup", this.mouseup.bind(this), { capture: false, passive: true});
window.addEventListener('wheel', this.wheel.bind(this), { capture: false, passive: true});
}
clip_viewbox() {
this.viewbox[0] = Math.max(this.viewbox[0], this.orig_viewbox[0]);
this.viewbox[1] = Math.max(this.viewbox[1], this.orig_viewbox[1]);
this.viewbox[0] = Math.min(this.viewbox[0] + this.viewbox[2], this.orig_viewbox[0] + this.orig_viewbox[2]) - this.viewbox[2];
this.viewbox[1] = Math.min(this.viewbox[1] + this.viewbox[3], this.orig_viewbox[1] + this.orig_viewbox[3]) - this.viewbox[3];
}
mouseenter() {
this.hoovering = true;
}
mouseleave() {
this.hoovering = false;
this.dragging = false;
}
mousemove(e: MouseEvent) {
this.mouse_pos = [e.offsetX, this.el_width - e.offsetY];
if (this.dragging) {
const scale = this.viewbox[3] / this.orig_viewbox[3];
this.viewbox[0] += (this.last_drag[0] - this.mouse_pos[0]) * scale;
this.viewbox[1] += (this.last_drag[1] - this.mouse_pos[1]) * scale;
this.last_drag = [...this.mouse_pos];
this.clip_viewbox();
}
}
mousedown() {
this.dragging = true;
this.last_drag = [...this.mouse_pos];
}
mouseup() {
this.dragging = false;
}
wheel(e: WheelEvent) {
if (this.hoovering) {
const dx = e.deltaY * this.scaleX;
this.viewbox[2] += dx;
this.viewbox[0] -= dx / 2;
this.viewbox[2] = Math.min(this.viewbox[2], this.orig_viewbox[2]);
const dy = e.deltaY * this.scaleY;
this.viewbox[3] += dy;
this.viewbox[1] -= dy / 2;
this.viewbox[3] = Math.min(this.viewbox[3], this.orig_viewbox[3]);
this.clip_viewbox();
}
}
get_viewbox(): number[] {
return this.viewbox;
}
get_mouse_pos(): number[] {
return this.mouse_pos;
}
}

20
frontend/www/webgl/webgl-utils.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the module template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
// /*~ If this module is a UMD module that exposes a global variable 'myLib' when
// *~ loaded outside a module loader environment, declare that global here.
// *~ Otherwise, delete this declaration.
// */
// export as namespace myLib;
/*~ If this module has methods, declare them as functions like so.
*/
export function createProgramFromScripts(gl: WebGLRenderingContext, shaderScriptIds: string[], opt_attribs?: string[], opt_locations?: number[], opt_errorCallback?: any): any;
export function resizeCanvasToDisplaySize(canvas: HTMLCanvasElement, multiplier?: number): boolean;

File diff suppressed because it is too large Load diff