2023-04-29 15:16:35 -07:00
|
|
|
import {
|
|
|
|
createWindow,
|
|
|
|
getProcAddress,
|
|
|
|
gl,
|
|
|
|
} from "./deps.ts";
|
|
|
|
export {mainloop} from "./deps.ts";
|
2023-05-10 00:06:08 -07:00
|
|
|
import { COLOR, palette } from "./colors.ts";
|
2023-04-29 15:16:35 -07:00
|
|
|
|
2023-05-05 16:02:23 -07:00
|
|
|
export const gameWindow = createWindow({
|
2023-04-29 15:16:35 -07:00
|
|
|
title: "Faux",
|
2023-05-13 16:24:14 -07:00
|
|
|
width: 512,
|
|
|
|
height: 512,
|
|
|
|
resizable: true,
|
2023-04-29 15:16:35 -07:00
|
|
|
glVersion: [3, 2],
|
|
|
|
gles: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
gl.load(getProcAddress);
|
|
|
|
|
|
|
|
function loadShader(type: number, src: string) {
|
|
|
|
const shader = gl.CreateShader(type);
|
|
|
|
gl.ShaderSource(
|
|
|
|
shader,
|
|
|
|
1,
|
|
|
|
new Uint8Array(
|
|
|
|
new BigUint64Array([
|
|
|
|
BigInt(
|
|
|
|
Deno.UnsafePointer.value(
|
|
|
|
Deno.UnsafePointer.of(new TextEncoder().encode(src)),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
]).buffer,
|
|
|
|
),
|
|
|
|
new Int32Array([src.length]),
|
|
|
|
);
|
|
|
|
gl.CompileShader(shader);
|
|
|
|
const status = new Int32Array(1);
|
|
|
|
gl.GetShaderiv(shader, gl.COMPILE_STATUS, status);
|
|
|
|
if (status[0] === gl.FALSE) {
|
|
|
|
const logLength = new Int32Array(1);
|
|
|
|
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, logLength);
|
|
|
|
const log = new Uint8Array(logLength[0]);
|
|
|
|
gl.GetShaderInfoLog(shader, logLength[0], logLength, log);
|
|
|
|
console.log(new TextDecoder().decode(log));
|
|
|
|
gl.DeleteShader(shader);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return shader;
|
|
|
|
}
|
|
|
|
|
|
|
|
const vShaderSrc = `
|
|
|
|
attribute vec4 vPosition;
|
|
|
|
attribute vec4 vCol;
|
|
|
|
varying vec4 color;
|
|
|
|
void main() {
|
|
|
|
gl_Position = vPosition;
|
|
|
|
color = vCol;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const fShaderSrc = `
|
|
|
|
precision mediump float;
|
|
|
|
varying vec4 color;
|
|
|
|
void main() {
|
|
|
|
gl_FragColor = color;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const vShader = loadShader(gl.VERTEX_SHADER, vShaderSrc);
|
|
|
|
const fShader = loadShader(gl.FRAGMENT_SHADER, fShaderSrc);
|
|
|
|
|
|
|
|
const program = gl.CreateProgram();
|
|
|
|
gl.AttachShader(program, vShader);
|
|
|
|
gl.AttachShader(program, fShader);
|
|
|
|
|
|
|
|
gl.BindAttribLocation(program, 0, new TextEncoder().encode("vPosition\0"));
|
|
|
|
gl.BindAttribLocation(program, 1, new TextEncoder().encode("vCol\0"));
|
|
|
|
|
|
|
|
gl.LinkProgram(program);
|
|
|
|
|
|
|
|
const status = new Int32Array(1);
|
|
|
|
gl.GetProgramiv(program, gl.LINK_STATUS, status);
|
|
|
|
if (status[0] === gl.FALSE) {
|
|
|
|
const logLength = new Int32Array(1);
|
|
|
|
gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, logLength);
|
|
|
|
const log = new Uint8Array(logLength[0]);
|
|
|
|
gl.GetProgramInfoLog(program, logLength[0], logLength, log);
|
|
|
|
console.log(new TextDecoder().decode(log));
|
|
|
|
gl.DeleteProgram(program);
|
|
|
|
Deno.exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
gl.ClearColor(0.0, 0.0, 0.0, 1.0);
|
|
|
|
|
|
|
|
const pixelsPerRow = 128;
|
|
|
|
|
|
|
|
const top = 1;
|
|
|
|
const left = -1;
|
|
|
|
const cell = 2/pixelsPerRow;
|
|
|
|
|
2023-05-13 16:24:14 -07:00
|
|
|
const getHalfAsInt = (n: number) => Number(parseInt(Math.floor(n/2).toString()));
|
|
|
|
|
|
|
|
addEventListener("resize", (event) => {
|
|
|
|
const {width, height} = event;
|
|
|
|
const min = Math.min(width, height);
|
|
|
|
gl.Viewport(getHalfAsInt(width-min), getHalfAsInt(height-min), min, min);
|
|
|
|
});
|
|
|
|
|
2023-04-29 15:16:35 -07:00
|
|
|
const px = (x: number, y: number) => {
|
|
|
|
// deno-fmt-ignore
|
|
|
|
return [
|
|
|
|
left + x*cell, top - y*cell, 0,
|
|
|
|
left + (x+1)*cell, top - y*cell, 0,
|
|
|
|
left + x*cell, top - (y+1)*cell, 0,
|
|
|
|
left + (x+1)*cell, top - y*cell, 0,
|
|
|
|
left + x*cell, top - (y+1)*cell, 0,
|
|
|
|
left + (x+1)*cell, top - (y+1)*cell, 0,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
const paletteX6 = palette.map(rgba => [...rgba, ...rgba, ...rgba, ...rgba, ...rgba, ...rgba]);
|
|
|
|
|
|
|
|
const c = (n: number) => {
|
|
|
|
return paletteX6[n];
|
|
|
|
}
|
|
|
|
|
|
|
|
const allPixelTriangles = new Float32Array(
|
|
|
|
Array(pixelsPerRow*pixelsPerRow).fill(null).flatMap((_, i) => px(i%pixelsPerRow,Math.floor(i/pixelsPerRow)))
|
|
|
|
)
|
|
|
|
|
|
|
|
const allPixelColors = new Float32Array(
|
|
|
|
Array(pixelsPerRow*pixelsPerRow).fill(null).flatMap(() => c(1))
|
|
|
|
)
|
|
|
|
|
2023-05-10 00:06:08 -07:00
|
|
|
export const cameraPos = {
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
export const setPixelColorRaw = (x: number, y: number, color: number) => {
|
2023-05-01 11:12:08 -07:00
|
|
|
if (x < 0 || y < 0 || x > 127 || y > 127) {
|
|
|
|
return;
|
|
|
|
}
|
2023-04-29 20:07:06 -07:00
|
|
|
if (color !== 0) {
|
|
|
|
const col = c(color);
|
|
|
|
allPixelColors.set(col, 4*6*(128*y+x));
|
|
|
|
}
|
2023-04-29 15:16:35 -07:00
|
|
|
}
|
|
|
|
|
2023-05-10 00:06:08 -07:00
|
|
|
export const setPixelColor = (x: number, y: number, color: number) => {
|
2023-05-10 19:23:36 -07:00
|
|
|
return setPixelColorRaw(Math.floor(x - cameraPos.x), Math.floor(y - cameraPos.y), color);
|
2023-05-10 00:06:08 -07:00
|
|
|
}
|
|
|
|
|
2023-04-29 15:16:35 -07:00
|
|
|
export const setPixelsInRect = (x: number, y: number, w: number, pixels: Array<number>) => {
|
|
|
|
for (let i = 0; i < pixels.length; i++) {
|
|
|
|
setPixelColor(x+i%w, y+Math.floor(i/w), pixels[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:06:08 -07:00
|
|
|
export const setPixelsInRectRaw = (x: number, y: number, w: number, pixels: Array<number>) => {
|
|
|
|
for (let i = 0; i < pixels.length; i++) {
|
|
|
|
setPixelColorRaw(x+i%w, y+Math.floor(i/w), pixels[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-04 20:14:48 -07:00
|
|
|
export const fillRect = (x: number, y: number, w: number, h: number, color: number) => {
|
|
|
|
setPixelsInRect(x, y, w, Array(w*h).fill(color));
|
|
|
|
}
|
|
|
|
|
2023-05-10 19:23:36 -07:00
|
|
|
export const fillCircle = (x: number, y: number, r: number, color: number) => {
|
|
|
|
const left = Math.floor(x-r-1);
|
|
|
|
const top = Math.floor(y-r-1);
|
|
|
|
for (let i = left; i <= Math.ceil(x+r+1); i ++) {
|
|
|
|
for (let j = top; j <= Math.ceil(y+r+1); j ++) {
|
|
|
|
if (Math.sqrt((x-i)**2 + (y-j)**2) <= r+0.5) {
|
|
|
|
setPixelColor(i, j, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const outlineCircle = (x: number, y: number, r: number, color: number) => {
|
|
|
|
const left = Math.floor(x-r-1);
|
|
|
|
const top = Math.floor(y-r-1);
|
|
|
|
const inR = (d: number) => d <= r+0.5 && d > r-0.5;
|
|
|
|
for (let i = left; i <= Math.ceil(x+r+1); i ++) {
|
|
|
|
for (let j = top; j <= Math.ceil(y+r+1); j ++) {
|
|
|
|
const d = Math.sqrt((x-i)**2 + (y-j)**2);
|
|
|
|
if (inR(d)) {
|
|
|
|
const dh = Math.sqrt((x-(i+Math.sign(i-x)))**2 + (y-j)**2);
|
|
|
|
const dv = Math.sqrt((x-i)**2 + (y-(j+Math.sign(j-y)))**2);
|
|
|
|
const h = Math.abs(x-i) > Math.abs(y-j);
|
|
|
|
if (!inR(h ? dh : dv)) {
|
|
|
|
setPixelColor(i, j, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 19:58:01 -07:00
|
|
|
export const fillEllipse = (x0: number, y0: number, x1: number, y1: number, color: number) => {
|
|
|
|
const x = 0.5*(x0 + x1);
|
|
|
|
const y = 0.5*(y0 + y1);
|
|
|
|
const rx = Math.abs(x0-x1)/2;
|
|
|
|
const ry = Math.abs(y0-y1)/2;
|
|
|
|
const left = Math.floor(x-rx-1);
|
|
|
|
const top = Math.floor(y-ry-1);
|
|
|
|
for (let i = left; i <= Math.ceil(x+rx+1); i ++) {
|
|
|
|
for (let j = top; j <= Math.ceil(y+ry+1); j ++) {
|
|
|
|
if (Math.sqrt(((x-i)/rx)**2 + ((y-j)/ry)**2) <= 1+(1/(rx+ry))) {
|
|
|
|
setPixelColor(i, j, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const outlineEllipse = (x0: number, y0: number, x1: number, y1: number, color: number) => {
|
|
|
|
const x = 0.5*(x0 + x1);
|
|
|
|
const y = 0.5*(y0 + y1);
|
|
|
|
const rx = Math.abs(x0-x1)/2;
|
|
|
|
const ry = Math.abs(y0-y1)/2;
|
|
|
|
const left = Math.floor(x-rx-1);
|
|
|
|
const top = Math.floor(y-ry-1);
|
|
|
|
const inR = (d: number) => d <= 1+1/(rx+ry);
|
|
|
|
for (let i = left; i <= Math.ceil(x+rx+1); i ++) {
|
|
|
|
for (let j = top; j <= Math.ceil(y+ry+1); j ++) {
|
|
|
|
const d = Math.sqrt(((x-i)/rx)**2 + ((y-j)/ry)**2);
|
|
|
|
if (inR(d)) {
|
|
|
|
const dh = Math.sqrt(((x-(i+Math.sign(i-x)))/rx)**2 + ((y-j)/ry)**2);
|
|
|
|
const dv = Math.sqrt(((x-i)/rx)**2 + ((y-(j+Math.sign(j-y)))/ry)**2);
|
|
|
|
if (!inR(dh) || !inR(dv)) {
|
|
|
|
setPixelColor(i, j, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 00:06:08 -07:00
|
|
|
export const fillRectRaw = (x: number, y: number, w: number, h: number, color: number) => {
|
|
|
|
setPixelsInRectRaw(x, y, w, Array(w*h).fill(color));
|
|
|
|
}
|
|
|
|
|
|
|
|
export const clearScreen = (color?: number) => {
|
|
|
|
fillRectRaw(0, 0, 128, 128, color ?? COLOR.BLACK);
|
2023-04-29 15:16:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export const frame = () => {
|
|
|
|
gl.Clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
gl.UseProgram(program);
|
|
|
|
gl.VertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 0, allPixelTriangles);
|
|
|
|
gl.VertexAttribPointer(1, 4, gl.FLOAT, gl.FALSE, 0, allPixelColors);
|
|
|
|
gl.EnableVertexAttribArray(0);
|
|
|
|
gl.EnableVertexAttribArray(1);
|
|
|
|
gl.DrawArrays(gl.TRIANGLES, 0, 6*pixelsPerRow*pixelsPerRow);
|
2023-05-05 16:02:23 -07:00
|
|
|
gameWindow.swapBuffers();
|
2023-04-29 15:16:35 -07:00
|
|
|
}
|