Move a few things around

- common files to a new 'wmc-common' package in common/
- skycraft now puts all serving files into static/
This commit is contained in:
2025-02-12 19:06:34 +01:00
parent 9bb162e7d5
commit 33733c6fe4
16 changed files with 31 additions and 19 deletions

131
common/geometry.js Normal file
View File

@@ -0,0 +1,131 @@
import { memoize } from "./memoize";
function _makeTextureFace(texture) {
const textureWidth = 16;
const texturesPerAtlas = 16;
const textMul = 1 / texturesPerAtlas;
const halfPixel = 1 / texturesPerAtlas / textureWidth / 2;
const textOff = texture.map(x => x * textMul + halfPixel);
const a = textMul / textureWidth * (textureWidth - 1);
const textuv = [
[1, 1],
[0, 1],
[0, 0],
[1, 1],
[0, 0],
[1, 0],
];
const textures = textuv.map(uv => uv.map((x, j) => a * x + textOff[j]));
return textures;
}
const makeTextureFace = memoize(_makeTextureFace);
function makePZFace(texture) {
return {
vertices: [[0.5, 0.5, 0], [-0.5, 0.5, 0], [-0.5, -0.5, 0], [0.5, 0.5, 0], [-0.5, -0.5, 0], [0.5, -0.5, 0]],
normals: Array(6).fill([0.0, 0.0, 1.0]),
textures: makeTextureFace(texture),
};
}
function makeNZFace(texture) {
return {
vertices: [[-0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, -0.5, 0.0]],
normals: Array(6).fill([0.0, 0.0, -1.0]),
textures: makeTextureFace(texture),
};
}
function makePXFace(texture) {
return {
vertices: [[0, 0.5, -0.5], [0, 0.5, 0.5], [0, -0.5, 0.5], [0, 0.5, -0.5], [0, -0.5, 0.5], [0, -0.5, -0.5]],
normals: Array(6).fill([1.0, 0.0, 0.0]),
textures: makeTextureFace(texture),
};
}
function makeNXFace(texture) {
return {
vertices: [[0, 0.5, 0.5], [0, 0.5, -0.5], [0, -0.5, -0.5], [0, 0.5, 0.5], [0, -0.5, -0.5], [0, -0.5, 0.5]],
normals: Array(6).fill([-1.0, 0.0, 0.0]),
textures: makeTextureFace(texture),
};
}
function makePYFace(texture) {
return {
vertices: [[0.5, 0, -0.5], [-0.5, 0, -0.5], [-0.5, 0, 0.5], [0.5, 0, -0.5], [-0.5, 0, 0.5], [0.5, 0, 0.5]],
normals: Array(6).fill([0.0, 1.0, 0.0]),
textures: makeTextureFace(texture),
};
}
function makeNYFace(texture) {
return {
vertices: [[0.5, 0, 0.5], [-0.5, 0, 0.5], [-0.5, 0, -0.5], [0.5, 0, 0.5], [-0.5, 0, -0.5], [0.5, 0, -0.5]],
normals: Array(6).fill([0.0, -1.0, 0.0]),
textures: makeTextureFace(texture),
};
}
function translateFace(face, x, y, z) {
return {
normals: face.normals,
textures: face.textures,
vertices: face.vertices.map(([vx, vy, vz]) => [vx + x, vy + y, vz + z]),
};
}
export function makeFace(which, texture, centerPos) {
switch(which) {
case '-x': return translateFace(makeNXFace(texture), ...centerPos);
case '+x': return translateFace(makePXFace(texture), ...centerPos);
case '-y': return translateFace(makeNYFace(texture), ...centerPos);
case '+y': return translateFace(makePYFace(texture), ...centerPos);
case '-z': return translateFace(makeNZFace(texture), ...centerPos);
case '+z': return translateFace(makePZFace(texture), ...centerPos);
}
throw Error('unknown face');
}
/** Packs all those faces into one big buffer. */
export function makeBufferFromFaces(gl, faces) {
const numVertices = faces.map(f => f.vertices).reduce((count, vertices) => count + vertices.length, 0);
// 3 * float32 + 3 * byte (padded to 4) + 2 * short
// see https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer#examples
const vertexSize = 3 * 4 + 4 + 4;
const glBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
const buffer = new Uint8Array(numVertices * vertexSize);
const dv = new DataView(buffer.buffer);
let offset = 0;
faces.forEach(face => {
for (let i = 0; i < face.vertices.length; i++) {
const position = face.vertices[i];
dv.setFloat32(offset + 0, position[0], true);
dv.setFloat32(offset + 4, position[1], true);
dv.setFloat32(offset + 8, position[2], true);
offset += 12;
const normal = face.normals[i];
dv.setInt8(offset + 0, normal[0] * 0x7f);
dv.setInt8(offset + 1, normal[1] * 0x7f);
dv.setInt8(offset + 2, normal[2] * 0x7f);
offset += 4;
const texture = face.textures[i];
dv.setUint16(offset + 0, texture[0] * 0xffff, true);
dv.setUint16(offset + 2, texture[1] * 0xffff, true);
offset += 4;
}
});
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
return {
glBuffer,
numVertices,
delete: () => gl.deleteBuffer(glBuffer),
};
}

49
common/gl.js Normal file
View File

@@ -0,0 +1,49 @@
function makeShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw Error(`Shader compiler error: ${gl.getShaderInfoLog(shader)}`);
}
return shader;
}
export function makeProgram(gl, vertexShader, fragmentShader) {
const vshader = makeShader(gl, gl.VERTEX_SHADER, vertexShader);
const fshader = makeShader(gl, gl.FRAGMENT_SHADER, fragmentShader);
const shaderProg = gl.createProgram();
gl.attachShader(shaderProg, vshader);
gl.attachShader(shaderProg, fshader);
gl.linkProgram(shaderProg);
if (!gl.getProgramParameter(shaderProg, gl.LINK_STATUS)) {
throw Error(`Shader linker error: ${gl.getProgramInfoLog(shaderProg)}`);
}
return shaderProg;
}
function getImage(url) {
return new Promise(resolve => {
const image = new Image();
image.onload = () => resolve(image);
image.src = url;
});
}
export async function loadTexture(gl, url) {
const image = await getImage(url);
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
return texture;
}

12
common/memoize.js Normal file
View File

@@ -0,0 +1,12 @@
export function memoize(f) {
const memo = {};
function g(...args) {
if (!(args in memo)) {
memo[args] = f(...args);
}
return memo[args];
}
return g;
}

8
common/package.json Normal file
View File

@@ -0,0 +1,8 @@
{
"name": "wmc-common",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"esbuild": "^0.14.2"
}
}

139
common/se3.js Normal file
View File

@@ -0,0 +1,139 @@
// all column-major, *sigh*
export function identity() {
return [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
];
}
export function rotx(rad) {
const s = Math.sin(rad);
const c = Math.cos(rad);
return [
1.0, 0.0, 0.0, 0.0,
0.0, c, s, 0.0,
0.0, -s, c, 0.0,
0.0, 0.0, 0.0, 1.0,
];
}
export function roty(rad) {
const s = Math.sin(rad);
const c = Math.cos(rad);
return [
c, 0.0, -s, 0.0,
0.0, 1.0, 0.0, 0.0,
s, 0.0, c, 0.0,
0.0, 0.0, 0.0, 1.0,
];
}
export function rotz(rad) {
const s = Math.sin(rad);
const c = Math.cos(rad);
return [
c, s, 0.0, 0.0,
-s, c, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
];
}
export function apply(mat, vect) {
const indices = [0, 1, 2, 3];
const sum = v => v.reduce((a, b) => a + b);
return [
sum(indices.map(i => mat[4*i + 0] * vect[i])),
sum(indices.map(i => mat[4*i + 1] * vect[i])),
sum(indices.map(i => mat[4*i + 2] * vect[i])),
sum(indices.map(i => mat[4*i + 3] * vect[i])),
];
}
export function rotxyz(x, y, z) {
return [rotx(x), roty(y), rotz(z)].reduce(product);
}
export function rotationOnly(m) {
return [
m[0], m[1], m[2], 0.0,
m[4], m[5], m[6], 0.0,
m[8], m[9], m[10], 0.0,
0, 0, 0, 1,
];
}
export function translation(x, y, z) {
return [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1.0,
];
}
export function orientationOnly(m) {
return [
m[0], m[1], m[2], 0,
m[4], m[5], m[6], 0,
m[8], m[9], m[10], 0,
0, 0, 0, 1,
];
}
export function inverse(m) {
const t = apply(m, [0, 0, 0, 1]);
const r = orientationOnly(m);
const newR = [
m[0], m[4], m[8], 0,
m[1], m[5], m[9], 0,
m[2], m[6], m[10], 0,
0, 0, 0, 1,
];
const newT = apply(newR, t);
const out = newR;
out[12] = -newT[0];
out[13] = -newT[1];
out[14] = -newT[2];
return out;
}
export function product(a, b) {
const c = (i, j) => (
a[4 * 0 + i] * b[4 * j + 0] +
a[4 * 1 + i] * b[4 * j + 1] +
a[4 * 2 + i] * b[4 * j + 2] +
a[4 * 3 + i] * b[4 * j + 3]
);
return [
c(0, 0), c(1, 0), c(2, 0), c(3, 0),
c(0, 1), c(1, 1), c(2, 1), c(3, 1),
c(0, 2), c(1, 2), c(2, 2), c(3, 2),
c(0, 3), c(1, 3), c(2, 3), c(3, 3),
];
}
export function ortho(aspectRatio) {
const out = identity();
out[0] = 1 / aspectRatio;
return out;
}
export function perspective(fov, aspectRatio, near, far) {
const f = 1 / Math.tan(fov / 2);
const rangeInv = 1 / (near - far);
return [
f / aspectRatio, 0.0, 0.0, 0.0,
0.0, f, 0.0, 0.0,
0.0, 0.0, (near + far) * rangeInv, -1,
0.0, 0.0, near * far * rangeInv * 2, 0,
];
};