First commit

This commit is contained in:
Daniel Ledda
2020-06-23 23:45:44 +02:00
commit 2381f43d38
28 changed files with 8385 additions and 0 deletions

7
.babelrc Normal file
View File

@@ -0,0 +1,7 @@
{
"presets": [
"@babel/typescript",
"@babel/env"
],
"plugins": ["@babel/proposal-class-properties"]
}

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/webgl.iml" filepath="$PROJECT_DIR$/.idea/webgl.iml" />
</modules>
</component>
</project>

8
.idea/webgl.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

100
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="dc704aa3-69bc-4c4e-bfa3-31f248741e3c" name="Default Changelist" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="package.json" />
<option value="HTML File" />
<option value="CSS File" />
<option value="GLSL Shader" />
<option value="TypeScript File" />
</list>
</option>
</component>
<component name="ProjectId" id="1dJrR6XMWeePLne62dJk97OPzOb" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="DefaultHtmlFileTemplate" value="HTML File" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="list.type.of.created.stylesheet" value="CSS" />
<property name="nodejs_package_manager_path" value="npm" />
<property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
<property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/build" />
<recent name="$PROJECT_DIR$/src" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$" />
</key>
</component>
<component name="RunManager">
<configuration name="index.html" type="JavascriptDebugType" temporary="true" nameIsGenerated="true" uri="http://localhost:63342/webgl/build/index.html" useBuiltInWebServerPort="true">
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="JavaScript Debug.index.html" />
</list>
</recent_temporary>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="dc704aa3-69bc-4c4e-bfa3-31f248741e3c" name="Default Changelist" comment="" />
<created>1592154025642</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1592154025642</updated>
<workItem from="1592154027024" duration="169000" />
<workItem from="1592154212967" duration="2262000" />
<workItem from="1592156543248" duration="1031000" />
<workItem from="1592157594053" duration="10162000" />
<workItem from="1592213404810" duration="33877000" />
<workItem from="1592384389528" duration="612000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="WindowStateProjectService">
<state x="208" y="764" width="654" height="390" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1592157547420">
<screen x="0" y="540" width="1080" height="1380" />
</state>
<state x="208" y="764" width="654" height="390" key="#com.intellij.fileTypes.FileTypeChooser/1080.513.1920.1080/0.540.1080.1380@0.540.1080.1380" timestamp="1592157547420" />
<state x="161" y="728" width="747" height="462" key="#com.intellij.ide.fileTemplates.ui.ConfigureTemplatesDialog" timestamp="1592157672793">
<screen x="0" y="540" width="1080" height="1380" />
</state>
<state x="161" y="728" width="747" height="462" key="#com.intellij.ide.fileTemplates.ui.ConfigureTemplatesDialog/1080.513.1920.1080/0.540.1080.1380@0.540.1080.1380" timestamp="1592157672793" />
<state x="0" y="721" key="SettingsEditor" timestamp="1592157574185">
<screen x="0" y="540" width="1080" height="1380" />
</state>
<state x="0" y="721" key="SettingsEditor/1080.513.1920.1080/0.540.1080.1380@0.540.1080.1380" timestamp="1592157574185" />
<state x="204" y="456" width="672" height="677" key="run.anything.popup" timestamp="1592164107251">
<screen x="0" y="540" width="1080" height="1380" />
</state>
<state x="204" y="456" width="672" height="677" key="run.anything.popup/1080.513.1920.1080/0.540.1080.1380@0.540.1080.1380" timestamp="1592164107251" />
<state x="992" y="237" width="672" height="678" key="search.everywhere.popup" timestamp="1592225020545">
<screen x="0" y="27" width="1920" height="1053" />
</state>
<state x="992" y="237" width="672" height="678" key="search.everywhere.popup/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1592225020545" />
</component>
</project>

14
build/index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Boilerplate Typescript Single Page App</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<div id="root">
<noscript>Enable JavaScript, you fuck!</noscript>
</div>
<script src="bundle.js"></script>
</body>
</html>

22
build/main.css Normal file
View File

@@ -0,0 +1,22 @@
body {
margin: 0;
}
#root {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
#main-stage {
border-color: black;
border-style: solid;
border-width: 2px;
box-shadow: 3px 3px 5px 0px rgba(0.0, 0.0, 0.0, 0.5);
}
div#main-stage {
font-family: sans-serif;
}

1
dist/bundle.js vendored Normal file

File diff suppressed because one or more lines are too long

7393
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "webgl-test",
"version": "1.0.0",
"description": "",
"license": "ISC",
"author": "Daniel Ledda",
"scripts": {
"build-dev": "webpack --mode development && npm postbuild",
"build": "webpack --mode production",
"start": "webpack-dev-server --mode development",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.6",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.6",
"@babel/preset-typescript": "^7.9.0",
"@types/node": "^13.11.1",
"babel-loader": "^8.1.0",
"css-loader": "^3.5.3",
"file-loader": "^6.0.0",
"font-loader": "^0.1.2",
"ignore-loader": "^0.1.2",
"raw-loader": "^4.0.1",
"style-loader": "^1.2.1",
"ts-loader": "^7.0.3",
"tslint": "^6.1.1",
"tslint-react": "^4.2.0",
"typescript": "^3.8.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {}
}

175
src/App.ts Normal file
View File

@@ -0,0 +1,175 @@
import vs_src from "./vertexShader.glsl";
import fs_src from "./fragmentShader.glsl";
import Matrix4 from "./Matrix4";
import {perspMat} from "./utils";
import Triangle2D from "./Triangle2D";
import TrsMatrix from "./TrsMatrix";
import Point2D from "./Point2D";
import BasicColor from "./BasicColor";
import {ProgramLinkingError, ShaderCompilationError} from "./Errors";
export enum ExitCode {
ERROR,
QUIT,
CONTINUE,
WEBGL_ERROR
}
class WebGlApp {
private gl: WebGLRenderingContext;
private stage: HTMLCanvasElement;
private perspMat: Matrix4;
private currentProgram: WebGLProgram | null;
private positionAttributeLocation: number = 0;
private u_colorLoc: number = 0;
private u_trsMatrixLoc: number = 0;
private maxTris: number = 1000;
private tris: Triangle2D[] = [];
private sinceLastTri: number = 0;
private onShutdownCallback: (exitCode: ExitCode) => any = () => {};
constructor(gl: WebGLRenderingContext, canvas: HTMLCanvasElement) {
this.gl = gl;
this.stage = canvas;
this.currentProgram = this.createDefaultProgram();
this.perspMat = perspMat(90, this.stage.width / this.stage.height, 1.0, 2000.0);
this.setupGl();
}
private setupGl(): void {
this.gl.enable(this.gl.DEPTH_TEST);
this.gl.enable(this.gl.CULL_FACE);
}
onShutdown(callback: (exitCode: ExitCode) => any): void {
this.onShutdownCallback = callback;
}
private shutdown(exitCode: ExitCode) {
this.onShutdownCallback(exitCode);
}
private createDefaultProgram(): WebGLProgram {
const program = this.createProgram(
this.createShaderFromString(this.gl.VERTEX_SHADER, vs_src),
this.createShaderFromString(this.gl.FRAGMENT_SHADER, fs_src));
this.positionAttributeLocation = this.gl.getAttribLocation(program, "a_position");
this.u_colorLoc = this.gl.getUniformLocation(program, "u_color") as number;
this.u_trsMatrixLoc = this.gl.getUniformLocation(program, "u_trsMatrix") as number;
return program;
}
private createProgram(vertexShader: WebGLShader, fragmentShader: WebGLShader) {
const program: WebGLProgram = this.gl.createProgram() as WebGLProgram;
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);
const success = this.gl.getProgramParameter(program, this.gl.LINK_STATUS);
if (success) {
return program;
}
else {
const log = this.gl.getProgramInfoLog(program);
this.gl.deleteProgram(program);
throw new ProgramLinkingError(log ?? "");
}
}
private createShaderFromString(type: GLenum, source: string): WebGLShader {
const shader: WebGLShader = this.gl.createShader(type) as WebGLShader;
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
const success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (success) {
return shader;
}
else {
const log = this.gl.getShaderInfoLog(shader)?.toString();
this.gl.deleteShader(shader);
throw new ShaderCompilationError(log ?? "");
}
}
private renderTriangle(triangle: Triangle2D) {
this.gl.bufferData(this.gl.ARRAY_BUFFER, triangle.pointBuffer(), this.gl.STATIC_DRAW);
this.gl.uniform4fv(this.u_colorLoc, triangle.colorVec());
this.gl.uniformMatrix4fv(this.u_trsMatrixLoc, false,
triangle.getTrsMatrix().buffer());
this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
}
private initViewport(): void {
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
this.gl.clearColor(0, 0, 0, 0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
}
private useDefaultProgram(): void {
this.gl.useProgram(this.currentProgram);
const positionBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer);
this.gl.enableVertexAttribArray(this.positionAttributeLocation);
this.gl.vertexAttribPointer(
this.positionAttributeLocation, 3, this.gl.FLOAT, false, 0, 0);
}
private createRandomTriangle(): Triangle2D {
return new Triangle2D(
this.rand2DClipspacePoint(),
this.rand2DClipspacePoint(),
this.rand2DClipspacePoint(),
this.randTrsMat(),
this.randBasicColor()
)
}
private randTrsMat(): TrsMatrix {
return new TrsMatrix()
.translateBy(this.randClipspaceInt() / 2, this.randClipspaceInt() / 2, this.randClipspaceInt() / 2)
.rotateBy(this.randAngle(), this.randAngle(), this.randAngle())
}
private rand2DClipspacePoint(): Point2D {
return new Point2D(this.randClipspaceInt() / 2, this.randClipspaceInt() / 2);
}
private randBasicColor(): BasicColor {
return new BasicColor(Math.random(), Math.random(), Math.random());
}
private randAngle(): number {
return Math.random() * Math.PI / 10;
}
private randClipspaceInt(): number {
return Math.random() * 2 - 1;
}
private animateRandomTrisPopin(delta: number): void {
if (this.maxTris >= this.tris.length) {
this.sinceLastTri += delta;
if (this.sinceLastTri > 100) {
this.tris.push(this.createRandomTriangle());
this.sinceLastTri = 0;
}
}
for (const tri of this.tris) {
this.renderTriangle(tri);
}
}
private animate(then: number): void {
const now = Date.now();
const delta = now - then;
this.animateRandomTrisPopin(delta);
window.requestAnimationFrame(() => this.animate(now));
}
run() {
this.initViewport();
this.useDefaultProgram();
window.requestAnimationFrame(() => this.animate(Date.now()));
//this.shutdown(ExitCode.QUIT);
}
}
export default WebGlApp;

46
src/BasicColor.ts Normal file
View File

@@ -0,0 +1,46 @@
class BasicColor implements Vector, Color {
private rep: Float32Array;
constructor(r: number, g: number, b: number, a?: number) {
this.rep = new Float32Array([r, g, b, a ?? 1.0]);
}
static fromVec(vec: Float32Array): Color {
if (vec.length === 4) {
return new BasicColor(vec[0], vec[1], vec[2], vec[3]);
}
else if (vec.length === 3) {
return new BasicColor(vec[0], vec[1], vec[2]);
}
else {
throw new VectorSizeError("3 to 4", vec.length);
}
}
red(): number {
return this.rep[0];
}
green(): number {
return this.rep[1];
}
blue(): number {
return this.rep[2];
}
alpha(): number {
return this.rep[3];
}
setRed(r: number): void {
this.rep[0] = r;
}
setGreen(g: number): void {
this.rep[1] = g;
}
setBlue(b: number): void {
this.rep[2] = b;
}
setAlpha(a: number): void {
this.rep[3] = a;
}
vec(): Float32Array {
return this.rep;
}
}
export default BasicColor;

20
src/Errors.ts Normal file
View File

@@ -0,0 +1,20 @@
export class ShaderCompilationError extends Error {
constructor(message?: string) {
super(message);
this.name = "ShaderCompilationError";
}
}
export class ProgramLinkingError extends Error {
constructor(message?: string) {
super(message);
this.name = "ProgramLinkingError";
}
}
export class VectorSizeError extends Error {
constructor(expected: number | string, received: number) {
super("Expected vector of at least size " + expected + ", but got one of size " + received + "!");
this.name = "VectorSizeError";
}
}

54
src/Matrix4.ts Normal file
View File

@@ -0,0 +1,54 @@
import {identityMatrix} from "./utils";
class Matrix4 {
protected readonly matrix: Float32Array;
constructor(matrix: Float32Array | ArrayLike<number>);
constructor(...args: any[]) {
if (args[0].length === 16) {
if (!(args[0] instanceof Float32Array)) {
args[0] = new Float32Array(args[0]);
}
this.matrix = args[0];
}
else {
throw new VectorSizeError(16, args[0].length);
}
}
times(other: Matrix4) {
const result = new Matrix4(Array<number>(16));
const a = this.matrix;
const b = other.matrix;
const c = result.matrix;
c[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
c[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
c[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
c[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];
c[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
c[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
c[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
c[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];
c[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
c[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
c[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
c[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];
c[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
c[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
c[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
c[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];
return result;
}
buffer(): Float32Array {
return this.matrix.slice();
}
set(m: number, n: number, val: number): void {
this.matrix[m * 4 + n] = val;
}
setRel(m: number, n: number, val: number): void {
this.matrix[m * 4 + n] += val;
}
static Identity(): Matrix4 {
return new Matrix4(identityMatrix(4));
}
}
export default Matrix4;

37
src/Point2D.ts Normal file
View File

@@ -0,0 +1,37 @@
import PointVec from "./PointVec";
class Point2D extends PointVec {
constructor(vec: Float32Array | ArrayLike<number>);
constructor(x: number, y: number);
constructor(first: any, second?: any) {
if (typeof first === "number") {
super(first, second, 0.0);
}
else if (first.length !== 2) {
throw new VectorSizeError(2, first.length);
}
else {
super([...first, 0]);
}
}
xOrd(): number {
return this.rep[0];
}
yOrd(): number {
return this.rep[1];
}
setX(x: number): void {
this.rep[0] = x;
}
setY(y: number): void {
this.rep[1] = y;
}
vec(): Float32Array {
return new Float32Array([...this.rep]);
}
static Zeros(): Point2D {
return new Point2D(0.0, 0.0);
}
}
export default Point2D;

35
src/Point3D.ts Normal file
View File

@@ -0,0 +1,35 @@
import PointVec from "./PointVec";
class Point3D extends PointVec {
constructor(vec: Float32Array | ArrayLike<number>);
constructor(x: number, y: number, z: number);
constructor(first: any, second?: any, third?: any) {
super(first, second, third);
}
xOrd(): number {
return this.rep[0];
}
yOrd(): number {
return this.rep[1];
}
zOrd(): number {
return this.rep[2];
}
setX(x: number): void {
this.rep[0] = x;
}
setY(y: number): void {
this.rep[1] = y;
}
setZ(z: number): void {
this.rep[2] = z;
}
vec(): Float32Array {
return this.rep.slice();
}
static Zeros(): Point3D {
return new Point3D(0.0, 0.0, 0.0);
}
}
export default Point3D;

24
src/PointVec.ts Normal file
View File

@@ -0,0 +1,24 @@
abstract class PointVec implements Vector {
protected rep: Float32Array;
protected constructor(vec: Float32Array | ArrayLike<number>);
protected constructor(x: number, y: number, z: number);
protected constructor(first: number | Float32Array | ArrayLike<number>, second?: number, third?: number) {
if (typeof first === "number") {
if (typeof second !== "number" || typeof third !== "number"){
throw new TypeError("Incorrect constructor call for Point3D");
}
else {
this.rep = new Float32Array(3);
this.rep[0] = first;
this.rep[1] = <number>second;
this.rep[2] = <number>third;
}
}
else {
this.rep = new Float32Array(first[2]);
}
}
abstract vec(): Float32Array;
}
export default PointVec;

105
src/Triangle2D.ts Normal file
View File

@@ -0,0 +1,105 @@
import TrsMatrix from "./TrsMatrix";
import Point2D from "./Point2D";
class Triangle2D implements Transformable{
private geometryRep: Float32Array;
private color: Color;
private readonly trsMatrix: TrsMatrix;
constructor(vec: Float32Array, trsMatrix?: TrsMatrix, color?: Color);
constructor(p1: Point2D, p2: Point2D, p3: Point2D, trsMatrix?: TrsMatrix, color?: Color);
constructor(...args: any[]) {
if (args[0] instanceof Float32Array) {
if (args[0].length !== 6 && args[0].length !== 8) {
throw new VectorSizeError("6 or 9", args[0].length)
}
else if (args[0].length === 6) {
this.geometryRep = new Float32Array([
...args[0].slice(0, 2), 0.0,
...args[0].slice(2, 4), 0.0,
...args[0].slice(4, 6), 0.0
]);
}
else {
this.geometryRep = args[0];
}
this.trsMatrix = args[1] ?? new TrsMatrix();
this.color = args[2] ?? new BasicColor(0, 0, 0, 1.0);
}
else {
this.geometryRep = new Float32Array([...args[0].vec(), ...args[1].vec(), ...args[2].vec()]);
this.trsMatrix = args[3] ?? new TrsMatrix();
this.color = args[4] ?? new BasicColor(0, 0, 0, 1.0);
}
}
colorVec(): Float32Array {
return this.color.vec();
}
setColor(color: Color) {
this.color = color;
}
getColor(): Color {
return this.color;
}
getTrsMatrix(): TrsMatrix {
return this.trsMatrix;
}
private mapPointTo(point: Point2D | ArrayLike<number>, x: number) {
if (point instanceof Point2D) {
this.geometryRep[x] = point.xOrd();
this.geometryRep[x + 1] = point.yOrd();
}
else {
this.geometryRep[x] = point[0];
this.geometryRep[x + 1] = point[1];
}
}
updatePoints(p1: Point2D | ArrayLike<number> | null,
p2: Point2D | ArrayLike<number> | null,
p3: Point2D | ArrayLike<number> | null) {
if (p1) {
this.mapPointTo(p1, 0);
}
if (p2) {
this.mapPointTo(p2, 3)
}
if (p3) {
this.mapPointTo(p3, 6);
}
}
updatePointsFromVec(vec: Float32Array) {
if (vec.length === 6) {
this.geometryRep = vec;
}
else {
throw new VectorSizeError(6, vec.length);
}
}
pointBuffer(): Float32Array {
return this.geometryRep;
}
translateBy(tX: number, tY: number, tZ: number): Triangle2D {
this.trsMatrix.translateBy(tX, tY, tZ);
return this;
}
rotateBy(degX: number, degY: number, degZ: number): Triangle2D {
this.trsMatrix.rotateBy(degX, degY, degZ);
return this;
}
scaleBy(sX: number, sY: number, sZ: number): Triangle2D {
this.trsMatrix.scaleBy(sX, sY, sZ);
return this;
}
}
export default Triangle2D;

71
src/TrsMatrix.ts Normal file
View File

@@ -0,0 +1,71 @@
import Matrix4 from "./Matrix4";
class TrsMatrix implements Transformable {
protected trs: Matrix4 = Matrix4.Identity();
constructor();
constructor(translation: Matrix4, rotX: Matrix4, rotY: Matrix4, rotZ: Matrix4, scale: Matrix4);
constructor(mat: TrsMatrix);
constructor(...args: any[]) {
if (args[0]) {
if (args[0] instanceof TrsMatrix) {
this.trs = new Matrix4(args[0].buffer());
} else {
this.trs = args[0].times(args[1]).times(args[2]).times(args[3]).times(args[4])
}
}
}
toMatrix4(): Matrix4 {
return this.trs;
}
buffer(): Float32Array {
return this.trs.buffer();
}
translateBy(tX: number, tY: number, tZ: number): TrsMatrix {
this.trs = this.trs.times(new Matrix4([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tX, tY, tZ, 1,
]));
return this;
}
rotateBy(degX: number, degY: number, degZ: number): TrsMatrix {
const sx = Math.sin(degX);
const cx = Math.cos(degX);
const sy = Math.sin(degY);
const cy = Math.cos(degY);
const sz = Math.sin(degZ);
const cz = Math.cos(degZ);
this.trs = this.trs
.times(new Matrix4([
1, 0, 0, 0,
0, cx, sx, 0,
0, -sx, cx, 0,
0, 0, 0, 1,
]))
.times(new Matrix4([
cy, 0, -sy, 0,
0, 1, 0, 0,
sy, 0, cy, 0,
0, 0, 0, 1,
]))
.times(new Matrix4([
cz, sz, 0, 0,
-sz, cz, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
return this;
}
scaleBy(sX: number, sY: number, sZ: number): TrsMatrix {
this.trs = this.trs.times(new Matrix4([
sX, 0, 0, 0,
0, sY, 0, 0,
0, 0, sZ, 0,
0, 0, 0, 1,
]));
return this;
}
}
export default TrsMatrix;

9
src/fragmentShader.glsl Normal file
View File

@@ -0,0 +1,9 @@
precision mediump float;
uniform vec4 u_color;
varying float v_depth;
void main() {
gl_FragColor = vec4(exp(v_depth), exp(v_depth), exp(v_depth), 1.0);
}

47
src/main.ts Normal file
View File

@@ -0,0 +1,47 @@
import App, {ExitCode as AppExitCode} from "./App";
const STAGE_ELEMENT_ID: string = "main-stage";
const WIDTH: number = 1024;
const HEIGHT: number = 720;
function bootstrap() {
const stage = setupStage(WIDTH, HEIGHT);
const gl = stage.getContext("webgl");
if (gl) {
const app = new App(gl, stage);
app.onShutdown((exitCode) => shutdown(stage, exitCode));
app.run();
}
else {
replaceStageWithMessage(stage,
"Unable to initialize WebGL. Your browser or machine may not support it.");
}
}
function shutdown(stage: HTMLCanvasElement, exitCode: AppExitCode): void {
if (exitCode === AppExitCode.QUIT) {
replaceStageWithMessage(stage, "We're done here.");
}
else if (exitCode === AppExitCode.ERROR) {
replaceStageWithMessage(stage, "Something unexpected happened and forced the program to shut down.");
}
}
function replaceStageWithMessage(stage: HTMLCanvasElement, message: string): void {
const div = document.createElement("div");
div.id = stage.id;
div.innerText = message;
stage.replaceWith(div);
}
function setupStage(width: number, height: number): HTMLCanvasElement {
const root: HTMLCanvasElement = document.querySelector("#root") as HTMLCanvasElement;
const stage = document.createElement("canvas");
stage.id = STAGE_ELEMENT_ID;
stage.width = width;
stage.height = height;
root.appendChild(stage);
return stage;
}
window.onload = bootstrap;

21
src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
declare module '*.glsl' {
const value: string;
export = value;
}
declare interface Vector {
vec(): Float32Array;
}
declare interface Transformable {
translateBy(tX: number, tY: number, tZ: number): any;
rotateBy(degX: number, degY: number, degZ: number): any;
scaleBy(sX: number, sY: number, sZ: number): any;
}
declare interface Color extends Vector {
red(): number;
green(): number;
blue(): number;
alpha(): number;
}

21
src/utils.ts Normal file
View File

@@ -0,0 +1,21 @@
import Matrix4 from "./Matrix4";
export function identityMatrix(size: number): Float32Array {
const mat = Array<number>(size*size).fill(0);
return new Float32Array(
mat.map((_, index) =>
index % 5 === 0 ? 1 : 0
));
}
export function perspMat(fovDeg: number, aspect: number, near: number, far: number): Matrix4 {
const f = Math.tan(Math.PI * 0.5 - 0.5 * fovDeg);
const rangeInv = 1.0 / (near - far);
return new Matrix4([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (near + far) * rangeInv, -1,
0, 0, near * far * rangeInv * 2, 0
]);
}

12
src/vertexShader.glsl Normal file
View File

@@ -0,0 +1,12 @@
attribute vec4 a_position;
uniform mat4 u_trsMatrix;
varying float v_depth;
void main() {
vec4 pos = u_trsMatrix * a_position;
v_depth = pos.z;
gl_Position = pos;
}

23
tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"outDir": "./build/",
"noImplicitAny": true,
"module": "esnext",
"target": "es5",
"jsx": "react",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"downlevelIteration": true
},
"lib": [
"dom",
"dom.iterable",
"esnext"
]
}

32
tslint.json Normal file
View File

@@ -0,0 +1,32 @@
{
"defaultSeverity": "error",
"extends": [
"tslint-react"
],
"jsRules": {
},
"rules": {
"jsx-no-multiline-js": false,
"member-access": false,
"prefer-for-of": true,
"prefer-const": true,
"prefer-readonly": true,
"typedef": [
true,
"call-signature",
"property-declaration"
],
"ordered-imports": false,
"quotemark": false,
"no-console": false,
"jsx-no-lambda": false
},
"rulesDirectory": [
],
"linterOptions": {
"exclude": [
"build/**/*.js",
"node_modules/**/*.ts"
]
}
}

56
webpack.config.js Normal file
View File

@@ -0,0 +1,56 @@
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: ["./src/main.ts"],
mode: "production",
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /(node_modules|bower_components|\.d\.ts$)/,
use: [
"babel-loader",
],
},
{
test: /\.d\.ts$/,
loader: 'ignore-loader'
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i,
use: [
{
loader: 'file-loader',
},
],
},
{
test: /\.glsl$/i,
use: [
{
loader: 'raw-loader',
},
],
}
]
},
resolve: { extensions: [".tsx", ".ts", ".js", "*"] },
output: {
path: path.resolve(__dirname, "dist/"),
publicPath: "/",
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "build/"),
contentBasePublicPath: "/",
port: 3000,
publicPath: "http://localhost:3000/",
hotOnly: true
},
plugins: [new webpack.HotModuleReplacementPlugin()]
};