├── robots.txt ├── .gitignore ├── .prettierrc ├── _redirects ├── src ├── app │ ├── src │ │ ├── utils │ │ │ ├── lerp.ts │ │ │ ├── constants.ts │ │ │ ├── updateDebug.ts │ │ │ ├── debounce.ts │ │ │ ├── declarations.d.ts │ │ │ ├── globalState.ts │ │ │ ├── printMat4.ts │ │ │ ├── EventDispatcher.ts │ │ │ └── MouseMove.ts │ │ ├── shaders │ │ │ ├── post │ │ │ │ ├── vertex.glsl │ │ │ │ └── fragment.glsl │ │ │ ├── background │ │ │ │ ├── vertex.glsl │ │ │ │ └── fragment.glsl │ │ │ └── default │ │ │ │ ├── vertex.glsl │ │ │ │ └── fragment.glsl │ │ ├── lib │ │ │ ├── GeometriesManager.ts │ │ │ ├── Util.ts │ │ │ ├── parseOBJ.ts │ │ │ ├── Camera.ts │ │ │ ├── Mesh.ts │ │ │ ├── ShaderProgram.ts │ │ │ └── TexturesManager.ts │ │ ├── App.ts │ │ ├── Components │ │ │ └── Objects3D.ts │ │ └── Scene.ts │ └── index.ts ├── eleventy │ ├── index.md │ ├── public │ │ └── assets │ │ │ └── models │ │ │ ├── efa │ │ │ ├── efa.avif │ │ │ ├── efa.png │ │ │ ├── efa.webp │ │ │ └── efa.obj │ │ │ ├── f22 │ │ │ ├── f22.avif │ │ │ ├── f22.png │ │ │ ├── f22.webp │ │ │ └── f22.obj │ │ │ ├── crab │ │ │ └── crab.png │ │ │ ├── cube │ │ │ ├── cube.png │ │ │ └── cube.obj │ │ │ └── f117 │ │ │ ├── f117.png │ │ │ └── f117.obj │ └── _includes │ │ ├── layouts │ │ └── base.njk │ │ └── components │ │ └── footer.njk └── styles │ ├── utils │ ├── responsive.scss │ └── variables.scss │ ├── index.scss │ ├── pages │ └── landing.scss │ ├── components │ ├── containers.scss │ └── footer.scss │ └── base │ ├── global.scss │ └── reset.scss ├── tsconfig.json ├── .eleventy.js ├── README.md ├── rollup.config.js ├── scss.js └── package.json /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /src/app/src/utils/lerp.ts: -------------------------------------------------------------------------------- 1 | export const lerp = (p1: number, p2: number, t: number) => { 2 | return p1 + (p2 - p1) * t; 3 | }; 4 | -------------------------------------------------------------------------------- /src/app/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | const DEFAULT_FPS = 60; 2 | 3 | export const constants = { 4 | DT_FPS: 1000 / DEFAULT_FPS, 5 | }; 6 | -------------------------------------------------------------------------------- /src/eleventy/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 'layouts/base.njk' 3 | --- 4 | 5 |
6 | {% include "components/footer.njk" %} 7 |
8 | -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/efa/efa.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/efa/efa.avif -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/efa/efa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/efa/efa.png -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/efa/efa.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/efa/efa.webp -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/f22/f22.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/f22/f22.avif -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/f22/f22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/f22/f22.png -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/f22/f22.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/f22/f22.webp -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/crab/crab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/crab/crab.png -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/cube/cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/cube/cube.png -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/f117/f117.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalzalobny/webgl-3d-engine/HEAD/src/eleventy/public/assets/models/f117/f117.png -------------------------------------------------------------------------------- /src/styles/utils/responsive.scss: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 768px) { 2 | } 3 | 4 | @media only screen and (min-width: 1025px) { 5 | } 6 | 7 | @media only screen and (min-width: 1921px) { 8 | } 9 | -------------------------------------------------------------------------------- /src/app/src/utils/updateDebug.ts: -------------------------------------------------------------------------------- 1 | import { globalState } from "./globalState"; 2 | 3 | export const updateDebug = (text: string) => { 4 | if (!globalState.debugHolderEl) return; 5 | globalState.debugHolderEl.innerHTML = text; 6 | }; 7 | -------------------------------------------------------------------------------- /src/styles/utils/variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --easing-1: cubic-bezier(0.64, 0.02, 0.16, 0.97); 3 | 4 | --font-size-1: 12px; 5 | --font-size-2: 13px; 6 | 7 | --t-1: 0.45s; 8 | --t-2: 0.7s; 9 | 10 | --s-1: 10px; 11 | --s-2: 20px; 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "strict": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true 8 | }, 9 | "include": ["src/app/**/*"], 10 | "exclude": ["node_modules"] 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './utils/variables.scss'; 2 | @import './utils/responsive.scss'; 3 | 4 | @import './base/reset.scss'; 5 | @import './base/global.scss'; 6 | 7 | @import './pages/landing.scss'; 8 | 9 | @import './components/containers.scss'; 10 | @import './components/footer.scss'; 11 | -------------------------------------------------------------------------------- /src/app/src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | export const debounce = (func: Function, wait: number) => { 2 | let timeout: ReturnType | null = null; 3 | 4 | const debounced = (...args: any) => { 5 | timeout && clearTimeout(timeout); 6 | timeout = setTimeout(() => func(...args), wait); 7 | }; 8 | 9 | return debounced; 10 | }; 11 | -------------------------------------------------------------------------------- /src/app/src/utils/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.glsl" { 2 | const value: string; 3 | export default value; 4 | } 5 | 6 | declare module "*.glb" { 7 | const value: string; 8 | export default value; 9 | } 10 | 11 | declare module "*.mp4" { 12 | const src: string; 13 | export default src; 14 | } 15 | 16 | declare module "*.mp3" { 17 | const src: string; 18 | export default src; 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/pages/landing.scss: -------------------------------------------------------------------------------- 1 | .debug-holder { 2 | position: fixed; 3 | z-index: 100; 4 | top: 50px; 5 | right: 50px; 6 | color: rgb(0, 0, 0); 7 | background-color: rgb(206, 206, 206); 8 | border: 1px solid rgb(0, 0, 0); 9 | font-size: 13px; 10 | padding: 5px 10px; 11 | display: none; 12 | } 13 | 14 | #canvas { 15 | position: absolute; 16 | z-index: -1; 17 | top: 0; 18 | left: 0; 19 | width: 100%; 20 | height: 100%; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/src/utils/globalState.ts: -------------------------------------------------------------------------------- 1 | import { App } from '../App'; 2 | 3 | export const globalState = { 4 | stageSize: { 5 | value: [0, 0], 6 | }, 7 | pixelRatio: { 8 | value: 1, 9 | }, 10 | slowDownFactor: { 11 | value: 1, 12 | }, 13 | uTime: { 14 | value: 0, 15 | }, 16 | mouse2DTarget: { 17 | value: [0, 0], 18 | }, 19 | mouse2DCurrent: { 20 | value: [0, 0], 21 | }, 22 | app: null as App | null, 23 | debugHolderEl: null as HTMLDivElement | null, 24 | canvasEl: null as HTMLCanvasElement | null, 25 | }; 26 | -------------------------------------------------------------------------------- /src/styles/components/containers.scss: -------------------------------------------------------------------------------- 1 | .c-large { 2 | position: relative; 3 | max-width: 1920px; 4 | margin: 0 auto; 5 | padding: 0 var(--s-1); 6 | 7 | @media only screen and (min-width: 768px) { 8 | padding: 0 var(--s-2); 9 | } 10 | } 11 | 12 | .c-big { 13 | max-width: 1450px; 14 | margin: 0 auto; 15 | padding: 0 var(--s-1); 16 | } 17 | 18 | .c-medium { 19 | max-width: 955px; 20 | margin: 0 auto; 21 | padding: 0 var(--s-1); 22 | } 23 | 24 | .c-small { 25 | max-width: 480px; 26 | margin: 0 auto; 27 | padding: 0 var(--s-1); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/src/shaders/post/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 a_position; 4 | in vec3 a_normal; 5 | in vec2 a_uv; 6 | 7 | uniform mat4 u_projectionMatrix; 8 | uniform mat4 u_modelMatrix; 9 | uniform mat4 u_viewMatrix; 10 | 11 | out vec2 v_uv; 12 | 13 | void main() { 14 | vec3 pos = a_position; 15 | // Clip space (GL_Position) is from -1 to 1. 16 | // Your square is a 1x1, so it doesn't fill the complete space. 17 | // Multiplying it by 2 makes it fill the whole range from -1 to 1 18 | // ~ Daniel Velasquez 19 | pos.xy *= 2.0; 20 | 21 | gl_Position = vec4(pos, 1.0); 22 | 23 | v_uv = a_uv; 24 | } -------------------------------------------------------------------------------- /src/app/src/shaders/background/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 a_position; 4 | in vec3 a_normal; 5 | in vec2 a_uv; 6 | 7 | uniform mat4 u_projectionMatrix; 8 | uniform mat4 u_modelMatrix; 9 | uniform mat4 u_viewMatrix; 10 | 11 | out vec2 v_uv; 12 | 13 | void main() { 14 | vec3 pos = a_position; 15 | // Clip space (GL_Position) is from -1 to 1. 16 | // Your square is a 1x1, so it doesn't fill the complete space. 17 | // Multiplying it by 2 makes it fill the whole range from -1 to 1 18 | // ~ Daniel Velasquez 19 | pos.xy *= 2.0; 20 | 21 | gl_Position = vec4(pos, 1.0); 22 | 23 | v_uv = a_uv; 24 | } -------------------------------------------------------------------------------- /src/app/src/utils/printMat4.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from "gl-matrix"; 2 | 3 | export const printMat4 = (m: mat4) => { 4 | //Copy the matrix so we don't modify the original 5 | const mCopy = mat4.clone(m); 6 | 7 | // Example: 8 | // | 1 0 0 0 | 9 | // | 0 1 0 0 | 10 | // | 0 0 1 0 | 11 | // | 0 0 0 1 | 12 | 13 | const arr = Array.from(mCopy).map((n) => n.toFixed(2)); 14 | 15 | console.log( 16 | `| ${arr[0]} ${arr[4]} ${arr[8]} ${arr[12]} |\n` + 17 | `| ${arr[1]} ${arr[5]} ${arr[9]} ${arr[13]} |\n` + 18 | `| ${arr[2]} ${arr[6]} ${arr[10]} ${arr[14]} |\n` + 19 | `| ${arr[3]} ${arr[7]} ${arr[11]} ${arr[15]} |` 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | const htmlmin = require("html-minifier").minify; 2 | 3 | module.exports = function (eleventyConfig) { 4 | eleventyConfig.addPassthroughCopy("src/eleventy/public"); 5 | 6 | //Minify HTML 7 | eleventyConfig.addTransform("htmlmin", function (content, outputPath) { 8 | if (outputPath && outputPath.endsWith(".html")) { 9 | let minified = htmlmin(content, { 10 | useShortDoctype: true, 11 | removeComments: true, 12 | collapseWhitespace: true, 13 | }); 14 | return minified; 15 | } 16 | return content; 17 | }); 18 | 19 | return { 20 | dir: { 21 | input: "src/eleventy", 22 | output: "dist", 23 | }, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL2 3D Engine built from scratch 2 | 3 | The goal is to build a custom 3D engine without any libraries - just using WebGL2 API and what I have learnt while creating a [3D renderer in C](https://github.com/michalzalobny/3d-renderer-in-c). Guided by [WebGL2 Fundamentals](https://webgl2fundamentals.org/). 4 | 5 | I'm using here Right-Handed Coordinate System (positive Z axis points out of the screen). I'm also using column-major matrices layout - so I will be using post-multiplication -> `M * v` to transform a vector `v` by a matrix `M`. 6 | 7 | ## Lighthouse Performance 8 | 9 | - Application without any WebGL2 context scores 4x 100% on lighthouse, with 0ms of Total Blocking Time, 0.8s First and Largest Contentful Paint. Speed index is 0.8s. 10 | - After adding the WebGL2 context, the Total Blocking Time increases to 40ms. 11 | - After adding 4 textures to load into GPU and a model to parse, the Total Blocking Time increases to 60ms. The rest of the metrics are the same. 12 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const resolve = require("@rollup/plugin-node-resolve"); 2 | const swc = require("rollup-plugin-swc").default; 3 | const commonjs = require("@rollup/plugin-commonjs"); 4 | const glslify = require("rollup-plugin-glslify"); 5 | 6 | const isProduction = process.env.NODE_ENV === "production"; 7 | 8 | // Configuration for your main application 9 | const mainConfig = { 10 | input: "src/app/index.ts", 11 | output: { 12 | dir: "dist/js", 13 | format: "esm", 14 | sourcemap: !isProduction, 15 | }, 16 | plugins: [ 17 | resolve({ extensions: [".js", ".ts"] }), 18 | commonjs(), 19 | glslify({ 20 | include: ["**/*.vs", "**/*.fs", "**/*.vert", "**/*.frag", "**/*.glsl"], 21 | exclude: "node_modules/**", 22 | compress: true, 23 | }), 24 | swc({ 25 | jsc: { 26 | target: "es2020", 27 | parser: { 28 | syntax: "typescript", 29 | }, 30 | }, 31 | sourceMaps: !isProduction, 32 | minify: isProduction, 33 | }), 34 | ], 35 | }; 36 | 37 | module.exports = [mainConfig]; 38 | -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/cube/cube.obj: -------------------------------------------------------------------------------- 1 | 2 | # cube.obj 3 | # 4 | 5 | mtllib cube.mtl 6 | o cube 7 | 8 | v -1.000000 -1.000000 1.000000 9 | v 1.000000 -1.000000 1.000000 10 | v -1.000000 1.000000 1.000000 11 | v 1.000000 1.000000 1.000000 12 | v -1.000000 1.000000 -1.000000 13 | v 1.000000 1.000000 -1.000000 14 | v -1.000000 -1.000000 -1.000000 15 | v 1.000000 -1.000000 -1.000000 16 | 17 | vt 1.000000 0.000000 18 | vt 0.000000 0.000000 19 | vt 1.000000 1.000000 20 | vt 0.000000 1.000000 21 | 22 | vn 0.000000 0.000000 1.000000 23 | vn 0.000000 1.000000 0.000000 24 | vn 0.000000 0.000000 -1.000000 25 | vn 0.000000 -1.000000 0.000000 26 | vn 1.000000 0.000000 0.000000 27 | vn -1.000000 0.000000 0.000000 28 | 29 | g cube 30 | usemtl cube 31 | s 1 32 | f 1/1/1 2/2/1 3/3/1 33 | f 3/3/1 2/2/1 4/4/1 34 | s 2 35 | f 3/1/2 4/2/2 5/3/2 36 | f 5/3/2 4/2/2 6/4/2 37 | s 3 38 | f 5/4/3 6/3/3 7/2/3 39 | f 7/2/3 6/3/3 8/1/3 40 | s 4 41 | f 7/1/4 8/2/4 1/3/4 42 | f 1/3/4 8/2/4 2/4/4 43 | s 5 44 | f 2/1/5 8/2/5 4/3/5 45 | f 4/3/5 8/2/5 6/4/5 46 | s 6 47 | f 7/1/6 1/2/6 5/3/6 48 | f 5/3/6 1/2/6 3/4/6 49 | -------------------------------------------------------------------------------- /src/app/src/shaders/default/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 a_position; 4 | in vec3 a_normal; 5 | in vec2 a_uv; 6 | 7 | uniform mat4 u_projectionMatrix; 8 | uniform mat4 u_modelMatrix; 9 | uniform mat4 u_viewMatrix; 10 | 11 | out vec2 v_uv; 12 | out vec3 v_fragNormal; 13 | out vec3 v_fragPosition; 14 | 15 | void main() { 16 | vec4 worldPosition = u_modelMatrix * vec4(a_position, 1.0); 17 | vec4 viewPosition = u_viewMatrix * worldPosition; 18 | 19 | gl_Position = u_projectionMatrix * viewPosition; 20 | // Used only in Point rendering mode 21 | gl_PointSize = 8.0; 22 | gl_PointSize = gl_PointSize / gl_Position.w; 23 | 24 | // Transform normal to world space (using the inverse transpose for normals) 25 | vec4 normal = vec4(a_normal, 0.0); 26 | mat4 normalMatrix = transpose(inverse(u_modelMatrix)); 27 | normal = normalize(normalMatrix * normal); 28 | // Transform normal to view space 29 | normal = normalize(u_viewMatrix * normal); 30 | 31 | v_fragPosition = vec3(viewPosition); 32 | v_fragNormal = normal.xyz; 33 | v_uv = a_uv; 34 | } -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | import { globalState } from './src/utils/globalState'; 2 | import { App } from './src/App'; 3 | import { MouseMove } from './src/utils/MouseMove'; 4 | 5 | const onMouseMove = (e: any) => { 6 | const mouseX = (e.target as MouseMove).mouse.x; 7 | const mouseY = (e.target as MouseMove).mouse.y; 8 | 9 | const stageX = globalState.stageSize.value[0]; 10 | const stageY = globalState.stageSize.value[1]; 11 | 12 | globalState.mouse2DTarget.value = [(mouseX / stageX) * 2 - 1, -(mouseY / stageY) * 2 + 1]; 13 | }; 14 | 15 | document.addEventListener('DOMContentLoaded', async () => { 16 | globalState.debugHolderEl = document.querySelector('.debug-holder') as HTMLDivElement; 17 | 18 | globalState.canvasEl = document.getElementById('canvas') as HTMLCanvasElement; 19 | 20 | const mouseMove = MouseMove.getInstance(); 21 | 22 | mouseMove.addEventListener('mousemove', onMouseMove); 23 | 24 | // const { App } = await import("./App"); 25 | globalState.app = new App(); 26 | }); 27 | 28 | // Don't allow to zoom 29 | document.addEventListener( 30 | 'touchstart', 31 | function (event) { 32 | if (event.touches.length > 1) { 33 | event.preventDefault(); 34 | } 35 | }, 36 | { passive: false } 37 | ); 38 | 39 | let lastTouchEnd = 0; 40 | document.addEventListener( 41 | 'touchend', 42 | function (event) { 43 | const now = window.performance.now(); 44 | if (now - lastTouchEnd <= 300) { 45 | event.preventDefault(); 46 | } 47 | lastTouchEnd = now; 48 | }, 49 | false 50 | ); 51 | -------------------------------------------------------------------------------- /scss.js: -------------------------------------------------------------------------------- 1 | const sass = require("node-sass"); 2 | const postcss = require("postcss"); 3 | const autoprefixer = require("autoprefixer"); 4 | const CleanCSS = require("clean-css"); 5 | const fs = require("fs-extra"); 6 | const path = require("path"); 7 | 8 | const srcDir = path.join(__dirname, "src", "styles"); 9 | const outDir = path.join(__dirname, "dist", "css"); 10 | 11 | const isProduction = process.env.NODE_ENV === "production"; 12 | 13 | // Empty the output directory 14 | fs.emptyDirSync(outDir); 15 | 16 | fs.readdir(srcDir, (err, files) => { 17 | if (err) throw err; 18 | 19 | files.forEach((file) => { 20 | if (path.extname(file) === ".scss") { 21 | const srcFile = path.join(srcDir, file); 22 | const outFile = path.join(outDir, file.replace(".scss", ".css")); 23 | 24 | sass.render( 25 | { 26 | file: srcFile, 27 | outputStyle: isProduction ? "compressed" : "expanded", 28 | }, 29 | (err, result) => { 30 | if (err) throw err; 31 | 32 | postcss([autoprefixer]) 33 | .process(result.css, { from: srcFile, to: outFile }) 34 | .then((result) => { 35 | let outputCss = result.css; 36 | 37 | if (isProduction) { 38 | outputCss = new CleanCSS({}).minify(result.css).styles; 39 | } 40 | 41 | fs.writeFile(outFile, outputCss, (err) => { 42 | if (err) throw err; 43 | }); 44 | }); 45 | } 46 | ); 47 | } 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl-3d-engine", 3 | "version": "1.0.1", 4 | "description": "", 5 | "scripts": { 6 | "build": "npm run build:pre-eleventy && eleventy && cp _redirects dist/ && cp robots.txt dist/", 7 | "build:pre-eleventy": "npm run scss && rollup -c", 8 | "scss": "node scss.js", 9 | "watch:scss": "nodemon -e scss -x \"npm run scss\"", 10 | "rollup-compile-app": "rollup -c", 11 | "watch:rollup-compile-app": "rollup -cw", 12 | "dev": "npm run build:pre-eleventy && concurrently \"npm run watch:scss\" \"npm run watch:rollup-compile-app\" \"eleventy --serve\"" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "@11ty/eleventy": "2.0.1", 19 | "@rollup/plugin-commonjs": "25.0.4", 20 | "@rollup/plugin-node-resolve": "15.2.1", 21 | "@swc/core": "1.3.78", 22 | "@types/node": "20.4.2", 23 | "autoprefixer": "10.4.14", 24 | "chokidar-cli": "3.0.0", 25 | "clean-css": "5.3.2", 26 | "concurrently": "8.2.0", 27 | "dotenv": "16.3.1", 28 | "fs-extra": "11.1.1", 29 | "gl-matrix": "^3.4.3", 30 | "glob": "10.3.3", 31 | "glsl-noise": "0.0.0", 32 | "glslify": "7.1.1", 33 | "html-minifier": "4.0.0", 34 | "isomorphic-fetch": "3.0.0", 35 | "lil-gui": "0.17.0", 36 | "node-sass": "9.0.0", 37 | "nodemon": "3.0.1", 38 | "postcss": "8.4.26", 39 | "rollup": "3.28.1", 40 | "rollup-plugin-glslify": "1.3.1", 41 | "rollup-plugin-swc": "0.2.1" 42 | }, 43 | "devDependencies": { 44 | "typescript": "5.1.6" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/src/lib/GeometriesManager.ts: -------------------------------------------------------------------------------- 1 | import { parseOBJ, GeometryObject } from '../lib/parseOBJ'; 2 | 3 | interface AddGeometry { 4 | geometryUrl: string; 5 | geometryObject: GeometryObject; 6 | } 7 | 8 | export class GeometriesManager { 9 | private isReady = false; 10 | private loadedGeometries: Map = new Map(); 11 | 12 | constructor() {} 13 | 14 | public getGeometry(geometryUrl: string) { 15 | const geometryObject = this.loadedGeometries.get(geometryUrl); 16 | if (!this.isReady) return null; 17 | if (!geometryObject) { 18 | console.error(`Geometry not found. ${geometryUrl} `); 19 | return null; 20 | } 21 | return geometryObject; 22 | } 23 | 24 | private async loadGeometry(elUrl: string) { 25 | const response = await fetch(elUrl); 26 | const text = await response.text(); 27 | const objData = parseOBJ(text); 28 | 29 | this.addGeometry({ geometryUrl: elUrl, geometryObject: objData }); 30 | 31 | return Promise.resolve(); 32 | } 33 | 34 | public addGeometry({ geometryUrl, geometryObject }: AddGeometry) { 35 | if (this.loadedGeometries.has(geometryUrl)) { 36 | console.error(`Geometry already loaded. ${geometryUrl} `); 37 | return; 38 | } 39 | this.loadedGeometries.set(geometryUrl, geometryObject); 40 | } 41 | 42 | public async addObjectsToLoad(objsToLoad: string[]) { 43 | const promises = objsToLoad.map((geometryUrl) => { 44 | return this.loadGeometry(geometryUrl); 45 | }); 46 | return Promise.allSettled(promises).then(() => { 47 | this.isReady = true; 48 | Promise.resolve(); 49 | }); 50 | } 51 | 52 | public destroy() { 53 | this.loadedGeometries.clear(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/src/shaders/background/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | in vec2 v_uv; 6 | 7 | uniform float u_time; 8 | 9 | uniform vec2 u_resolution; 10 | 11 | #define PI 3.14159265359 12 | 13 | float N21 (vec2 p){ 14 | return fract(sin(p.x * 100.0 + p.y * 657.0) * 5647.0); 15 | } 16 | 17 | float SmoothNoise(vec2 uv){ 18 | vec2 lv = fract(uv); 19 | vec2 id = floor(uv); 20 | 21 | lv = lv * lv * (3.0 - 2.0 * lv); //3x^2 - 2x^3 22 | 23 | float bl = N21(id); 24 | float br = N21(id + vec2(1.0, 0.0)); 25 | float b = mix(bl, br, lv.x); 26 | 27 | float tl = N21(id + vec2(0.0, 1.0)); 28 | float tr = N21(id + vec2(1.0, 1.0)); 29 | float t = mix(tl, tr, lv.x); 30 | 31 | return mix(b, t, lv.y); 32 | } 33 | 34 | float SmoothNoise2(vec2 uv){ 35 | float c = SmoothNoise(uv * 4.0); 36 | c += SmoothNoise(uv * 8.0) * 0.5; 37 | c += SmoothNoise(uv * 16.0) * 0.25; 38 | c += SmoothNoise(uv * 32.0) * 0.125; 39 | c += SmoothNoise(uv * 65.0) * 0.0625; 40 | 41 | return c /= 1.9375; // (sum of all possible maximum values)so that it's always between 0 - 1 42 | } 43 | 44 | // we need to declare an output for the fragment shader 45 | out vec4 outColor; 46 | 47 | 48 | void main() { 49 | vec2 uv = v_uv; 50 | 51 | //fix uv aspect ratio 52 | float aspect = u_resolution.x / u_resolution.y; 53 | uv.x *= aspect * 0.5; 54 | 55 | float t = u_time * 0.1; 56 | 57 | vec3 color1 = vec3(0.12, 0.12, 0.12); 58 | vec3 color2 = vec3(0.7, 0.7, 0.7); 59 | vec3 colorMixed = mix(color1, color2, uv.y); 60 | 61 | float c = SmoothNoise2(vec2(uv.x + t * 1.3, uv.y + t *0.1)); 62 | float c2 = SmoothNoise2(vec2(uv.x + t * 8.0, uv.y)); 63 | c = mix(c, c2, 0.2); 64 | 65 | colorMixed = mix(colorMixed, vec3(c), 0.31); 66 | 67 | outColor = vec4(colorMixed, 1.0); 68 | } 69 | -------------------------------------------------------------------------------- /src/app/src/lib/Util.ts: -------------------------------------------------------------------------------- 1 | interface CreateAndInitBuffer { 2 | target: number; 3 | data: Float32Array | Uint16Array; 4 | gl: WebGL2RenderingContext; 5 | } 6 | 7 | export const createAndInitBuffer = (props: CreateAndInitBuffer) => { 8 | const { target, data, gl } = props; 9 | const buffer = gl.createBuffer(); 10 | gl.bindBuffer(target, buffer); 11 | gl.bufferData(target, data, gl.STATIC_DRAW); 12 | gl.bindBuffer(target, null); 13 | return buffer; 14 | }; 15 | 16 | interface SetupVertexAttribute { 17 | name: string; 18 | program: WebGLProgram | null; 19 | buffer: WebGLBuffer | null; 20 | size: number; 21 | gl: WebGL2RenderingContext; 22 | } 23 | 24 | export const setupVertexAttribute = (props: SetupVertexAttribute) => { 25 | const { name, program, buffer, size, gl } = props; 26 | if (!program) throw new Error("Could not create VAO, no program"); 27 | const location = gl.getAttribLocation(program, name); 28 | if (location === -1) { 29 | console.warn( 30 | `Could not find attribute location for ${name}. Either the attribute is not used in the vertex shader or the name is misspelled.` 31 | ); 32 | return null; 33 | } 34 | gl.enableVertexAttribArray(location); 35 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 36 | gl.vertexAttribPointer(location, size, gl.FLOAT, false, 0, 0); 37 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 38 | return location; 39 | }; 40 | 41 | interface UseTexture { 42 | gl: WebGL2RenderingContext | null; 43 | shaderProgram: WebGLProgram | null; 44 | uniformLocation: WebGLUniformLocation | undefined; 45 | textureIndex: number; 46 | texture: WebGLTexture; 47 | } 48 | 49 | export const useTexture = (props: UseTexture) => { 50 | const { gl, shaderProgram, texture, textureIndex, uniformLocation } = props; 51 | 52 | if (!gl || !shaderProgram) { 53 | throw new Error( 54 | "Cannot use texture. WebGL context or shader program is not available. " 55 | ); 56 | } 57 | 58 | gl.activeTexture(gl.TEXTURE0 + textureIndex); 59 | gl.bindTexture(gl.TEXTURE_2D, texture); 60 | 61 | if (!uniformLocation) return; 62 | 63 | gl.uniform1i(uniformLocation, textureIndex); 64 | }; 65 | -------------------------------------------------------------------------------- /src/styles/components/footer.scss: -------------------------------------------------------------------------------- 1 | .footerIcons { 2 | position: fixed; 3 | bottom: 15px; 4 | right: 15px; 5 | z-index: 10; 6 | 7 | @media only screen and (min-width: 768px) { 8 | bottom: 25px; 9 | right: 25px; 10 | } 11 | } 12 | 13 | .footerName { 14 | position: fixed; 15 | bottom: 15px; 16 | left: 15px; 17 | z-index: 10; 18 | 19 | @media only screen and (min-width: 768px) { 20 | bottom: 25px; 21 | left: 25px; 22 | } 23 | } 24 | 25 | .nameWrapper { 26 | font-size: 13px; 27 | color: rgba(255, 255, 255, 0.6); 28 | line-height: 1.5; 29 | } 30 | 31 | .socials { 32 | display: flex; 33 | align-items: center; 34 | flex-direction: column; 35 | 36 | @media only screen and (min-width: 768px) { 37 | flex-direction: row; 38 | } 39 | } 40 | 41 | .iconWrapper { 42 | width: 18px; 43 | 44 | &:not(:last-child) { 45 | margin-bottom: 10px; 46 | padding-bottom: 10px; 47 | border-bottom: 1px solid rgba(255, 255, 255, 0.2); 48 | } 49 | 50 | @media only screen and (min-width: 768px) { 51 | &:not(:last-child) { 52 | margin-bottom: 0; 53 | padding-bottom: 0; 54 | 55 | margin-right: 15px; 56 | padding-right: 15px; 57 | 58 | width: calc(18px + 15px); 59 | border-bottom: 0; 60 | border-right: 1px solid rgba(255, 255, 255, 0.2); 61 | } 62 | } 63 | 64 | svg { 65 | fill: rgb(255, 255, 255); 66 | width: 100%; 67 | opacity: 0.6; 68 | transition: opacity 0.35s; 69 | 70 | &:hover { 71 | opacity: 0.85; 72 | } 73 | } 74 | } 75 | 76 | .underline { 77 | cursor: pointer; 78 | position: relative; 79 | display: inline-block; 80 | color: rgba(255, 255, 255, 0.9); 81 | 82 | &:before { 83 | content: ''; 84 | position: absolute; 85 | top: 80%; 86 | width: 100%; 87 | height: 1px; 88 | background-color: currentColor; 89 | transform-origin: left; 90 | transform: scaleX(0); 91 | transition: transform var(--t-1) var(--easing-1); 92 | } 93 | 94 | &:hover { 95 | &:before { 96 | transform: scaleX(1); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/app/src/utils/EventDispatcher.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/mrdoob/eventdispatcher.js/ 3 | */ 4 | 5 | interface BaseEvent { 6 | type: string; 7 | target?: EventDispatcher | null; 8 | [key: string]: any; // Allows for additional properties 9 | } 10 | 11 | interface Listener { 12 | (event: any): void; 13 | } 14 | 15 | interface EventMap { 16 | [type: string]: Listener[]; 17 | } 18 | 19 | class EventDispatcher { 20 | _listeners?: EventMap; 21 | 22 | addEventListener(type: string, listener: Listener) { 23 | if (this._listeners === undefined) this._listeners = {}; 24 | 25 | const listeners = this._listeners; 26 | 27 | if (listeners[type] === undefined) { 28 | listeners[type] = []; 29 | } 30 | 31 | if (listeners[type].indexOf(listener) === -1) { 32 | listeners[type].push(listener); 33 | } 34 | } 35 | 36 | hasEventListener(type: string, listener: Listener): boolean { 37 | if (this._listeners === undefined) return false; 38 | 39 | const listeners = this._listeners; 40 | 41 | return ( 42 | listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1 43 | ); 44 | } 45 | 46 | removeEventListener(type: string, listener: Listener) { 47 | if (this._listeners === undefined) return; 48 | 49 | const listeners = this._listeners; 50 | const listenerArray = listeners[type]; 51 | 52 | if (listenerArray !== undefined) { 53 | const index = listenerArray.indexOf(listener); 54 | 55 | if (index !== -1) { 56 | listenerArray.splice(index, 1); 57 | } 58 | } 59 | } 60 | 61 | dispatchEvent(event: BaseEvent) { 62 | if (this._listeners === undefined) return; 63 | 64 | const listeners = this._listeners; 65 | const listenerArray = listeners[event.type]; 66 | 67 | if (listenerArray !== undefined) { 68 | event.target = this; 69 | 70 | // Make a copy, in case listeners are removed while iterating. 71 | const array = listenerArray.slice(0); 72 | 73 | for (let i = 0, l = array.length; i < l; i++) { 74 | array[i].call(this, event); 75 | } 76 | 77 | event.target = null; 78 | } 79 | } 80 | } 81 | 82 | export { EventDispatcher }; 83 | -------------------------------------------------------------------------------- /src/app/src/App.ts: -------------------------------------------------------------------------------- 1 | import { globalState } from './utils/globalState'; 2 | import { constants } from './utils/constants'; 3 | import { debounce } from './utils/debounce'; 4 | import { Scene } from './Scene'; 5 | 6 | export class App { 7 | private rafId: number | null = null; 8 | private isResumed = true; 9 | private lastFrameTime: number | null = null; 10 | private scene = new Scene(); 11 | 12 | constructor() { 13 | this.setPixelRatio(Math.min(window.devicePixelRatio, 2)); 14 | 15 | this.addListeners(); 16 | this.resumeAppFrame(); 17 | this.onResize(); 18 | } 19 | 20 | private onResizeDebounced = debounce(() => this.onResize(), 300); 21 | 22 | private onResize() { 23 | if (!globalState.canvasEl) return; 24 | const bounds = globalState.canvasEl.getBoundingClientRect(); 25 | const stageX = bounds.width; 26 | const stageY = bounds.height; 27 | globalState.stageSize.value = [stageX, stageY]; 28 | 29 | this.scene.onResize(); 30 | } 31 | 32 | private setPixelRatio(pixelRatio: number) { 33 | globalState.pixelRatio.value = pixelRatio; 34 | } 35 | 36 | private onVisibilityChange = () => { 37 | if (document.hidden) { 38 | this.stopAppFrame(); 39 | } else { 40 | this.resumeAppFrame(); 41 | } 42 | }; 43 | 44 | private addListeners() { 45 | window.addEventListener('resize', this.onResizeDebounced); 46 | window.addEventListener('visibilitychange', this.onVisibilityChange); 47 | } 48 | 49 | private resumeAppFrame() { 50 | this.isResumed = true; 51 | if (!this.rafId) { 52 | this.rafId = window.requestAnimationFrame(this.renderOnFrame); 53 | } 54 | } 55 | 56 | private renderOnFrame = (time: number) => { 57 | this.rafId = window.requestAnimationFrame(this.renderOnFrame); 58 | 59 | if (this.isResumed || !this.lastFrameTime) { 60 | this.lastFrameTime = window.performance.now(); 61 | this.isResumed = false; 62 | return; 63 | } 64 | 65 | const delta = time - this.lastFrameTime; 66 | const slowDownFactor = delta / constants.DT_FPS; 67 | globalState.slowDownFactor.value = slowDownFactor; 68 | globalState.uTime.value = time * 0.001; 69 | 70 | this.lastFrameTime = time; 71 | 72 | this.scene.update(); 73 | }; 74 | 75 | private stopAppFrame() { 76 | if (this.rafId) { 77 | window.cancelAnimationFrame(this.rafId); 78 | this.rafId = null; 79 | } 80 | } 81 | 82 | public destroy() { 83 | this.stopAppFrame(); 84 | window.removeEventListener('resize', this.onResizeDebounced); 85 | window.removeEventListener('visibilitychange', this.onVisibilityChange); 86 | this.scene.destroy(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/eleventy/_includes/layouts/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {# Meta Tags #} 5 | 6 | 7 | 8 | 9 | 10 | 11 | {# Title #} 12 | {{ title | default('WebGL 3D Engine') }} 13 | 14 | {# Favicon and Apple Touch Icons #} 15 | {# #} 16 | {# #} 17 | 18 | {# Canonical Tag #} 19 | {# #} 20 | 21 | {# Open Graph Tags #} 22 | 23 | 24 | 25 | 26 | 27 | {# Twitter Card Tags #} 28 | 29 | 30 | 31 | 32 | 33 | {# Stylesheet with Preload #} 34 | 35 | 36 | 37 | 38 | 39 | 40 | {# Main Content #} 41 |
42 |
43 |
44 | {{content | safe}} 45 |
46 |
47 |
48 | 49 |
debug
50 | 51 | 52 | {# Scripts #} 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/styles/base/global.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: inherit; 5 | margin: 0; 6 | padding: 0; 7 | -webkit-touch-callout: none !important; 8 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important; 9 | -webkit-tap-highlight-color: transparent !important; 10 | } 11 | 12 | //Disable on touch devices 13 | @media (hover: hover) { 14 | [data-js-focus-visible] :focus:not([data-focus-visible-added]) { 15 | outline: none; 16 | } 17 | } 18 | 19 | html { 20 | box-sizing: border-box; 21 | -moz-osx-font-smoothing: grayscale; 22 | -webkit-font-smoothing: antialiased; 23 | 24 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', sans-serif; 25 | } 26 | 27 | body { 28 | overflow-y: scroll; 29 | color: var(--foreground-rgb); 30 | background: var(--foreground-rgb); 31 | font-size: var(--font-size-1); 32 | line-height: 1.6; 33 | 34 | // &:before { 35 | // content: ""; 36 | // position: absolute; 37 | // top: 50%; 38 | // left: 0; 39 | // height: 1px; 40 | // width: 100%; 41 | // background-color: red; 42 | // z-index: 1; 43 | // } 44 | } 45 | 46 | html { 47 | background: var(--background-rgb); 48 | } 49 | 50 | a { 51 | color: inherit; 52 | pointer-events: auto; 53 | text-decoration: none; 54 | display: inline-block; 55 | } 56 | 57 | a[href^='tel'] { 58 | color: inherit; 59 | text-decoration: none; 60 | } 61 | 62 | button { 63 | background: none; 64 | border: none; 65 | border-radius: none; 66 | color: inherit; 67 | font: inherit; 68 | pointer-events: auto; 69 | } 70 | 71 | input, 72 | textarea { 73 | appearance: none; 74 | background: none; 75 | border: none; 76 | border-radius: 0; 77 | pointer-events: auto; 78 | } 79 | 80 | img, 81 | svg { 82 | vertical-align: center; 83 | } 84 | 85 | canvas { 86 | position: fixed; 87 | z-index: -1; 88 | top: 0; 89 | left: 0; 90 | width: 100%; 91 | height: 100%; 92 | pointer-events: none; 93 | user-select: none; 94 | } 95 | 96 | //Creative 97 | body { 98 | position: fixed; 99 | top: 0; 100 | left: 0; 101 | width: 100%; 102 | height: 99.98%; //99.95% 103 | overflow: hidden; 104 | overscroll-behavior: none; 105 | background-color: black; 106 | } 107 | 108 | html { 109 | position: fixed; 110 | top: 0; 111 | left: 0; 112 | width: 100%; 113 | height: 100%; 114 | overscroll-behavior: none; 115 | } 116 | 117 | img { 118 | user-select: none; 119 | pointer-events: none; 120 | } 121 | 122 | *, 123 | *:before, 124 | *:after { 125 | user-select: none; 126 | } 127 | 128 | .sr-only { 129 | position: absolute; 130 | width: 1px; 131 | height: 1px; 132 | padding: 0; 133 | margin: -1px; 134 | overflow: hidden; 135 | clip: rect(0, 0, 0, 0); 136 | border: 0; 137 | } 138 | -------------------------------------------------------------------------------- /src/app/src/shaders/default/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | in vec2 v_uv; 6 | in vec3 v_fragNormal; 7 | in vec3 v_fragPosition; 8 | 9 | uniform sampler2D u_image; 10 | uniform float u_time; 11 | uniform vec3 u_cameraPositionWorld; 12 | uniform mat4 u_viewMatrix; 13 | 14 | // we need to declare an output for the fragment shader 15 | out vec4 outColor; 16 | 17 | #define PI 3.14159265359 18 | 19 | float S(float a, float b, float t){ 20 | return clamp((t - a) / (b - a), 0.0, 1.0); 21 | } 22 | 23 | vec2 Rain(vec2 uv, float t){ 24 | t *= 40.0; 25 | vec2 a = vec2(3.0, 1.0);//aspect ratio 26 | vec2 st = uv * a; 27 | vec2 id = floor(st); 28 | st.y += t * 0.22; 29 | float n = fract(sin(id.x * 716.34) * 768.32); 30 | st.y += n; //offsets by random number in the given field 31 | uv.y +=n; 32 | id = floor(st); //recalculate id because of shifting 33 | st = fract(st) - 0.5; 34 | t += fract(sin(id.x * 76.34 + id.y * 1453.7) * 768.32) * 2.0 * PI; 35 | 36 | float y = -sin(t + sin(t + sin(t) * 0.5)) * 0.43; 37 | vec2 p1 = vec2(0.0, y); 38 | 39 | vec2 o1 = (st - p1)/a; 40 | float d = length(o1); 41 | float m1 = S(0.07, 0.0, d); 42 | 43 | vec2 o2 = (fract(uv * a.x * vec2(1.0, 2.0)) - 0.5) / vec2(1.0, 2.0); 44 | d = length(o2); 45 | 46 | float m2 = S(0.3 * (0.5 - st.y), 0.0, d) * S( -.1, .1, st.y - p1.y); 47 | 48 | // if(st.x> .48 || st.y > .49) m1 = 1.0; 49 | return vec2(m1 * o1 * 30.0 + m2 * o2 * 10.0); 50 | } 51 | 52 | void main() { 53 | vec2 uv = v_uv; 54 | uv.y = 1.0 - uv.y; 55 | 56 | // Rain effect 57 | float t = u_time * 0.1; 58 | vec2 rainDistort = Rain(vec2(uv.y, 1.0-uv.x) * 5.0, t) * 0.5; 59 | rainDistort += Rain(vec2(uv.y, 1.0-uv.x) * 7.0, t) * 0.5; 60 | uv += rainDistort; 61 | 62 | // Texture 63 | vec4 color = texture(u_image, uv); 64 | 65 | // Lighting 66 | vec3 lightPositionWorld = vec3(1.0, 3.0, 3.0); 67 | 68 | // Normal vector in view space 69 | vec3 normal = normalize(v_fragNormal); 70 | 71 | // Ambient lighting 72 | float ambient = 0.37; 73 | 74 | // Diffuse lighting 75 | vec3 lightPosition_view = (u_viewMatrix * vec4(lightPositionWorld, 1.0)).xyz; 76 | vec3 lightDirection = normalize(lightPosition_view - v_fragPosition); 77 | float diffuse = max(dot(normal, lightDirection), 0.0); 78 | 79 | // View direction in view space 80 | vec3 viewPosition = (u_viewMatrix * vec4(u_cameraPositionWorld, 1.0)).xyz; 81 | vec3 viewDirection = normalize(v_fragPosition - viewPosition); 82 | 83 | // Specular lighting 84 | vec3 reflectDirection = reflect(lightDirection, normal); 85 | float specularStrength = 0.5; 86 | float shininess = 32.0; 87 | float specular = pow(max(dot(viewDirection, reflectDirection), 0.0), shininess) * specularStrength; 88 | 89 | // Apply lighting 90 | vec3 lighting = vec3(ambient + diffuse + specular); 91 | color.rgb *= lighting; 92 | 93 | outColor = color; 94 | } 95 | -------------------------------------------------------------------------------- /src/app/src/lib/parseOBJ.ts: -------------------------------------------------------------------------------- 1 | export interface GeometryObject { 2 | vertices: number[]; 3 | normals: number[]; 4 | texcoords: number[]; 5 | } 6 | 7 | export const parseOBJ = (text: string): GeometryObject => { 8 | const vertices_lookup: number[][] = []; 9 | const normals_lookup: number[][] = []; 10 | const texcoords_lookup: number[][] = []; 11 | 12 | const finalVertices: number[][] = []; 13 | const finalNormals: number[][] = []; 14 | const finalTexcoords: number[][] = []; 15 | 16 | const lines = text.split('\n'); 17 | 18 | for (let lineNo = 0; lineNo < lines.length; ++lineNo) { 19 | // Remove whitespace from both sides of a string 20 | const line = lines[lineNo].trim(); 21 | 22 | // Skip comments and empty lines 23 | if (line === '' || line.startsWith('#')) { 24 | continue; 25 | } 26 | 27 | // Split a string into an array of substrings - after each space or tab 28 | const lineParts = line.split(/\s+/); 29 | const keyword = lineParts[0]; 30 | 31 | // Load vertices_lookup 32 | if (keyword === 'v') { 33 | const x = parseFloat(lineParts[1]); 34 | const y = parseFloat(lineParts[2]); 35 | const z = parseFloat(lineParts[3]); 36 | vertices_lookup.push([x, y, z]); 37 | } 38 | 39 | // Load normals_lookup 40 | if (keyword === 'vn') { 41 | const x = parseFloat(lineParts[1]); 42 | const y = parseFloat(lineParts[2]); 43 | const z = parseFloat(lineParts[3]); 44 | normals_lookup.push([x, y, z]); 45 | } 46 | 47 | // Load texture coordinates lookup 48 | if (keyword === 'vt') { 49 | const u = parseFloat(lineParts[1]); 50 | const v = parseFloat(lineParts[2]); 51 | texcoords_lookup.push([u, v]); 52 | } 53 | 54 | // Load face data. It's in the format of "f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3" 55 | if (keyword === 'f') { 56 | const faceVertices: number[][] = []; 57 | const faceNormals: number[][] = []; 58 | const faceTexcoords: number[][] = []; 59 | 60 | for (let i = 1; i < lineParts.length; ++i) { 61 | const facePart = lineParts[i].split('/'); 62 | 63 | const vertexIndex = parseInt(facePart[0]); 64 | const vertex = vertices_lookup[vertexIndex - 1]; 65 | faceVertices.push(vertex); 66 | 67 | const texcoordIndex = parseInt(facePart[1]); 68 | const texcoord = texcoords_lookup[texcoordIndex - 1]; 69 | faceTexcoords.push(texcoord); 70 | 71 | const normalIndex = parseInt(facePart[2]); 72 | const normal = normals_lookup[normalIndex - 1]; 73 | faceNormals.push(normal); 74 | } 75 | 76 | // Add vertices, normals and texture coordinates to the final arrays 77 | finalVertices.push(...faceVertices); 78 | finalNormals.push(...faceNormals); 79 | finalTexcoords.push(...faceTexcoords); 80 | } 81 | } 82 | 83 | return { 84 | vertices: finalVertices.flat().map((v) => v * 0.04), 85 | normals: finalNormals.flat(), 86 | texcoords: finalTexcoords.flat(), 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /src/app/src/shaders/post/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | in vec2 v_uv; 6 | 7 | uniform float u_time; 8 | uniform sampler2D u_image; 9 | uniform vec2 u_resolution; 10 | 11 | // we need to declare an output for the fragment shader 12 | out vec4 outColor; 13 | 14 | #define PI 3.14159265359 15 | 16 | float N21 (vec2 p){ 17 | return fract(sin(p.x * 100.0 + p.y * 657.0) * 5647.0); 18 | } 19 | 20 | float SmoothNoise(vec2 uv){ 21 | vec2 lv = fract(uv); 22 | vec2 id = floor(uv); 23 | 24 | lv = lv * lv * (3.0 - 2.0 * lv); //3x^2 - 2x^3 25 | 26 | float bl = N21(id); 27 | float br = N21(id + vec2(1.0, 0.0)); 28 | float b = mix(bl, br, lv.x); 29 | 30 | float tl = N21(id + vec2(0.0, 1.0)); 31 | float tr = N21(id + vec2(1.0, 1.0)); 32 | float t = mix(tl, tr, lv.x); 33 | 34 | return mix(b, t, lv.y); 35 | } 36 | 37 | float SmoothNoise2(vec2 uv){ 38 | float c = SmoothNoise(uv * 4.0); 39 | c += SmoothNoise(uv * 8.0) * 0.5; 40 | c += SmoothNoise(uv * 16.0) * 0.25; 41 | c += SmoothNoise(uv * 32.0) * 0.125; 42 | c += SmoothNoise(uv * 65.0) * 0.0625; 43 | 44 | return c /= 1.9375; // (sum of all possible maximum values)so that it's always between 0 - 1 45 | } 46 | 47 | float S(float a, float b, float t){ 48 | return clamp((t - a) / (b - a), 0.0, 1.0); 49 | } 50 | 51 | vec2 Rain(vec2 uv, float t){ 52 | t *= 40.0; 53 | vec2 a = vec2(3.0, 1.0);//aspect ratio 54 | vec2 st = uv * a; 55 | vec2 id = floor(st); 56 | st.y += t * 0.22; 57 | float n = fract(sin(id.x * 716.34) * 768.32); 58 | st.y += n; //offsets by random number in the given field 59 | uv.y +=n; 60 | id = floor(st); //recalculate id because of shifting 61 | st = fract(st) - 0.5; 62 | t += fract(sin(id.x * 76.34 + id.y * 1453.7) * 768.32) * 2.0 * PI; 63 | 64 | float y = -sin(t + sin(t + sin(t) * 0.5)) * 0.43; 65 | vec2 p1 = vec2(0.0, y); 66 | 67 | vec2 o1 = (st - p1)/a; 68 | float d = length(o1); 69 | float m1 = S(0.07, 0.0, d); 70 | 71 | vec2 o2 = (fract(uv * a.x * vec2(1.0, 2.0)) - 0.5) / vec2(1.0, 2.0); 72 | d = length(o2); 73 | 74 | float m2 = S(0.3 * (0.5 - st.y), 0.0, d) * S( -.1, .1, st.y - p1.y); 75 | 76 | // if(st.x> .48 || st.y > .49) m1 = 1.0; 77 | return vec2(m1 * o1 * 10.0 + m2 * o2 * 2.0); 78 | } 79 | 80 | void main() { 81 | vec2 uv = v_uv; 82 | 83 | //fix uv aspect ratio 84 | vec2 uv_aspect = uv; 85 | 86 | float aspect = u_resolution.x / u_resolution.y; 87 | uv_aspect.x *= aspect; 88 | 89 | // Rain effect 90 | float t = u_time * 0.1; 91 | vec2 rainDistort = Rain(uv_aspect * 15.0, t) * 0.1; 92 | rainDistort += Rain(uv_aspect * 9.0, t) * 0.4; 93 | uv += rainDistort *0.4; 94 | 95 | float c = SmoothNoise2(vec2(uv_aspect.x + t * 1.3, uv_aspect.y + t *0.1)); 96 | 97 | // Texture 98 | vec4 color = texture(u_image, uv); 99 | 100 | color = mix(color, vec4(c), 0.02); 101 | 102 | outColor = color; 103 | } 104 | -------------------------------------------------------------------------------- /src/styles/base/reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | ol, 53 | ul, 54 | li, 55 | fieldset, 56 | form, 57 | label, 58 | legend, 59 | table, 60 | caption, 61 | tbody, 62 | tfoot, 63 | thead, 64 | tr, 65 | th, 66 | td, 67 | article, 68 | aside, 69 | canvas, 70 | details, 71 | embed, 72 | figure, 73 | figcaption, 74 | footer, 75 | header, 76 | hgroup, 77 | menu, 78 | nav, 79 | output, 80 | ruby, 81 | section, 82 | summary, 83 | time, 84 | mark, 85 | audio, 86 | video { 87 | margin: 0; 88 | padding: 0; 89 | border: 0; 90 | font-size: 100%; 91 | font: inherit; 92 | vertical-align: baseline; 93 | } 94 | 95 | article, 96 | aside, 97 | details, 98 | figcaption, 99 | figure, 100 | footer, 101 | header, 102 | hgroup, 103 | menu, 104 | nav, 105 | section { 106 | display: block; 107 | } 108 | body { 109 | line-height: 1; 110 | } 111 | ol, 112 | ul, 113 | li { 114 | list-style: none; 115 | } 116 | blockquote, 117 | q { 118 | quotes: none; 119 | } 120 | blockquote:before, 121 | blockquote:after, 122 | q:before, 123 | q:after { 124 | content: ""; 125 | content: none; 126 | } 127 | table { 128 | border-collapse: collapse; 129 | border-spacing: 0; 130 | } 131 | 132 | // rverts css from reset.scss 133 | .revertCss { 134 | html, 135 | body, 136 | div, 137 | span, 138 | applet, 139 | object, 140 | iframe, 141 | h1, 142 | h2, 143 | h3, 144 | h4, 145 | h5, 146 | h6, 147 | p, 148 | blockquote, 149 | pre, 150 | a, 151 | abbr, 152 | acronym, 153 | address, 154 | big, 155 | cite, 156 | code, 157 | del, 158 | dfn, 159 | em, 160 | img, 161 | ins, 162 | kbd, 163 | q, 164 | s, 165 | samp, 166 | small, 167 | strike, 168 | strong, 169 | sub, 170 | sup, 171 | tt, 172 | var, 173 | b, 174 | u, 175 | i, 176 | center, 177 | dl, 178 | dt, 179 | dd, 180 | ol, 181 | ul, 182 | li, 183 | fieldset, 184 | form, 185 | label, 186 | legend, 187 | table, 188 | caption, 189 | tbody, 190 | tfoot, 191 | thead, 192 | tr, 193 | th, 194 | td, 195 | article, 196 | aside, 197 | canvas, 198 | details, 199 | embed, 200 | figure, 201 | figcaption, 202 | footer, 203 | header, 204 | hgroup, 205 | menu, 206 | nav, 207 | output, 208 | ruby, 209 | section, 210 | summary, 211 | time, 212 | mark, 213 | audio, 214 | video { 215 | margin: revert; 216 | padding: revert; 217 | border: revert; 218 | font-size: revert; 219 | font: revert; 220 | vertical-align: revert; 221 | } 222 | 223 | article, 224 | aside, 225 | details, 226 | figcaption, 227 | figure, 228 | footer, 229 | header, 230 | hgroup, 231 | menu, 232 | nav, 233 | section { 234 | display: revert; 235 | } 236 | body { 237 | line-height: revert; 238 | } 239 | ol, 240 | ul, 241 | li { 242 | list-style: revert; 243 | } 244 | blockquote, 245 | q { 246 | quotes: revert; 247 | } 248 | blockquote:before, 249 | blockquote:after, 250 | q:before, 251 | q:after { 252 | content: revert; 253 | content: revert; 254 | } 255 | table { 256 | border-collapse: revert; 257 | border-spacing: revert; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/app/src/utils/MouseMove.ts: -------------------------------------------------------------------------------- 1 | import { EventDispatcher } from "./EventDispatcher"; 2 | 3 | interface Mouse { 4 | x: number; 5 | y: number; 6 | } 7 | 8 | export class MouseMove extends EventDispatcher { 9 | _mouseLast: Mouse = { x: 0, y: 0 }; 10 | _isTouching = false; 11 | _clickStart: Mouse = { x: 0, y: 0 }; 12 | mouse: Mouse = { x: 0, y: 0 }; 13 | strength = 0; 14 | _isInit = false; 15 | 16 | static _instance: MouseMove | null; 17 | static _canCreate = false; 18 | static getInstance() { 19 | if (!MouseMove._instance) { 20 | MouseMove._canCreate = true; 21 | MouseMove._instance = new MouseMove(); 22 | MouseMove._canCreate = false; 23 | } 24 | 25 | return MouseMove._instance; 26 | } 27 | 28 | constructor() { 29 | super(); 30 | 31 | if (MouseMove._instance || !MouseMove._canCreate) { 32 | throw new Error("Use MouseMove.getInstance()"); 33 | } 34 | 35 | this._addEvents(); 36 | 37 | MouseMove._instance = this; 38 | } 39 | 40 | _onTouchDown = (event: TouchEvent | MouseEvent) => { 41 | this._isInit = true; 42 | this._isTouching = true; 43 | this._mouseLast.x = 44 | "touches" in event ? event.touches[0].clientX : event.clientX; 45 | this._mouseLast.y = 46 | "touches" in event ? event.touches[0].clientY : event.clientY; 47 | 48 | this.mouse.x = this._mouseLast.x; 49 | this.mouse.y = this._mouseLast.y; 50 | 51 | this._clickStart.x = this.mouse.x; 52 | this._clickStart.y = this.mouse.y; 53 | this.dispatchEvent({ type: "down" }); 54 | }; 55 | 56 | _onTouchMove = (event: TouchEvent | MouseEvent) => { 57 | this._isInit = true; 58 | const touchX = 59 | "touches" in event ? event.touches[0].clientX : event.clientX; 60 | const touchY = 61 | "touches" in event ? event.touches[0].clientY : event.clientY; 62 | 63 | const deltaX = touchX - this._mouseLast.x; 64 | const deltaY = touchY - this._mouseLast.y; 65 | 66 | this.strength = deltaX * deltaX + deltaY * deltaY; 67 | 68 | this._mouseLast.x = touchX; 69 | this._mouseLast.y = touchY; 70 | 71 | this.mouse.x += deltaX; 72 | this.mouse.y += deltaY; 73 | 74 | this.dispatchEvent({ type: "mousemove" }); 75 | this._mouseLast.x = this.mouse.x; 76 | this._mouseLast.y = this.mouse.y; 77 | }; 78 | 79 | _onTouchUp = () => { 80 | this._isTouching = false; 81 | this.dispatchEvent({ type: "up" }); 82 | }; 83 | 84 | _onMouseLeave = () => { 85 | this.dispatchEvent({ type: "left" }); 86 | }; 87 | 88 | _onClick = (e: any) => { 89 | // Dont react if the user clicked on a button or a link 90 | if ( 91 | e.target instanceof HTMLButtonElement || 92 | e.target instanceof HTMLAnchorElement 93 | ) { 94 | // console.warn("The clicked element is not a canvas"); 95 | return; 96 | } 97 | this._isInit = true; 98 | const clickBounds = 10; 99 | const xDiff = Math.abs(this._clickStart.x - this.mouse.x); 100 | const yDiff = Math.abs(this._clickStart.y - this.mouse.y); 101 | 102 | //Make sure that the user's click is held between certain boundaries 103 | if (xDiff <= clickBounds && yDiff <= clickBounds) { 104 | this.dispatchEvent({ type: "click" }); 105 | } 106 | }; 107 | 108 | _addEvents() { 109 | window.addEventListener("pointerdown", this._onTouchDown); 110 | 111 | window.addEventListener("mousemove", this._onTouchMove, { passive: true }); 112 | window.addEventListener("touchmove", this._onTouchMove, { passive: true }); 113 | 114 | window.addEventListener("pointerup", this._onTouchUp); 115 | 116 | window.addEventListener("click", this._onClick); 117 | window.addEventListener("mouseout", this._onMouseLeave); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/eleventy/_includes/components/footer.njk: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/app/src/lib/Camera.ts: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from 'gl-matrix'; 2 | 3 | interface MakeProjectionMatrix { 4 | fov: number; 5 | aspect_ratio: number; 6 | near: number; 7 | far: number; 8 | } 9 | 10 | interface MakeLookAtMatrix { 11 | eye?: vec3; 12 | target?: vec3; 13 | up?: vec3; 14 | } 15 | 16 | export class Camera { 17 | public perspectiveProjectionMatrix = mat4.create(); 18 | public orthoProjectionMatrix = mat4.create(); 19 | public viewMatrix = mat4.create(); 20 | 21 | public position = vec3.fromValues(0, 0, 0.36); 22 | private target = vec3.fromValues(0, 0, -1); 23 | private up = vec3.fromValues(0, 1, 0); 24 | 25 | constructor() { 26 | this.viewMatrix = this.makeLookAtMatrix({ 27 | eye: this.position, 28 | target: this.target, 29 | up: this.up, 30 | }); 31 | } 32 | 33 | private makePerspectiveProjMatrix(props: MakeProjectionMatrix) { 34 | const { fov, aspect_ratio, near, far } = props; 35 | 36 | const r = near * Math.tan(fov / 2.0) * aspect_ratio; // aspect_ratio = width / height 37 | const t = near * Math.tan(fov / 2.0); 38 | const f = far; 39 | 40 | const l = -r; 41 | const b = -t; 42 | const n = near; 43 | 44 | const out = mat4.create(); 45 | 46 | //Perspective projection: https://www.songho.ca/opengl/gl_projectionmatrix.html 47 | out[0] = (2 * n) / (r - l); // m[0][0] 48 | out[5] = (2 * n) / (t - b); // m[1][1] 49 | out[8] = (r + l) / (r - l); // m[2][0] 50 | out[9] = (t + b) / (t - b); // m[2][1] 51 | out[10] = -(f + n) / (f - n); // m[2][2] 52 | out[11] = -1.0; // m[2][3] 53 | out[14] = -(2 * f * n) / (f - n); // m[3][2] 54 | out[15] = 0.0; // m[3][3] 55 | 56 | return out; 57 | } 58 | 59 | private makeOrthoProjMatrix(props: MakeProjectionMatrix) { 60 | const { fov, aspect_ratio, near, far } = props; 61 | 62 | const r = near * Math.tan(fov / 2.0) * aspect_ratio; // aspect_ratio = width / height 63 | const t = near * Math.tan(fov / 2.0); 64 | const f = far; 65 | 66 | const l = -r; 67 | const b = -t; 68 | const n = near; 69 | 70 | const out = mat4.create(); 71 | 72 | //Orthographic projection: https://www.songho.ca/opengl/gl_projectionmatrix.html 73 | out[0] = 2 / (r - l); // m[0][0] 74 | out[5] = 2 / (t - b); // m[1][1] 75 | out[10] = -2 / (f - n); // m[2][2] 76 | out[12] = -(r + l) / (r - l); // m[0][3] 77 | out[13] = -(t + b) / (t - b); // m[1][3] 78 | out[14] = -(f + n) / (f - n); // m[2][3] 79 | out[15] = 1.0; // m[3][3] 80 | 81 | return out; 82 | } 83 | 84 | private makeLookAtMatrix(props: MakeLookAtMatrix) { 85 | const { eye = this.position, target = this.target, up = this.up } = props; 86 | 87 | // LookAt matrix: https://www.songho.ca/opengl/gl_camera.html 88 | const forward = vec3.create(); 89 | vec3.subtract(forward, eye, target); 90 | vec3.normalize(forward, forward); 91 | 92 | const left = vec3.create(); 93 | vec3.cross(left, up, forward); 94 | vec3.normalize(left, left); 95 | 96 | const newUp = vec3.create(); 97 | vec3.cross(newUp, forward, left); 98 | 99 | const out = mat4.create(); 100 | 101 | out[0] = left[0]; 102 | out[1] = newUp[0]; 103 | out[2] = forward[0]; 104 | out[3] = 0; 105 | 106 | out[4] = left[1]; 107 | out[5] = newUp[1]; 108 | out[6] = forward[1]; 109 | out[7] = 0; 110 | 111 | out[8] = left[2]; 112 | out[9] = newUp[2]; 113 | out[10] = forward[2]; 114 | out[11] = 0; 115 | 116 | out[12] = -left[0] * eye[0] - left[1] * eye[1] - left[2] * eye[2]; 117 | out[13] = -newUp[0] * eye[0] - newUp[1] * eye[1] - newUp[2] * eye[2]; 118 | out[14] = -forward[0] * eye[0] - forward[1] * eye[1] - forward[2] * eye[2]; 119 | out[15] = 1; 120 | 121 | return out; 122 | } 123 | 124 | public updateProjectionMatrix(props: MakeProjectionMatrix) { 125 | this.perspectiveProjectionMatrix = this.makePerspectiveProjMatrix(props); 126 | this.orthoProjectionMatrix = this.makeOrthoProjMatrix(props); 127 | } 128 | 129 | public updateViewMatrix({ eye, target, up }: MakeLookAtMatrix) { 130 | this.viewMatrix = this.makeLookAtMatrix({ eye, target, up }); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/app/src/lib/Mesh.ts: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from 'gl-matrix'; 2 | 3 | import { ShaderProgram } from './ShaderProgram'; 4 | import { createAndInitBuffer, setupVertexAttribute } from './Util'; 5 | import { Camera } from './Camera'; 6 | import { GeometryObject } from './parseOBJ'; 7 | 8 | interface Constructor { 9 | geometry: GeometryObject | null; 10 | shaderProgram: ShaderProgram; 11 | gl: WebGL2RenderingContext; 12 | } 13 | 14 | interface Render { 15 | camera: Camera; 16 | } 17 | 18 | export class Mesh { 19 | private gl: WebGL2RenderingContext; 20 | private shaderProgram: ShaderProgram; 21 | private vertices: number[]; 22 | private normals: number[]; 23 | private texcoords: number[]; 24 | private VAO: WebGLVertexArrayObject | null = null; 25 | 26 | private positionBuffer: WebGLBuffer | null = null; 27 | private normalBuffer: WebGLBuffer | null = null; 28 | private uvBuffer: WebGLBuffer | null = null; 29 | 30 | private modelMatrix = mat4.create(); 31 | 32 | public position = vec3.fromValues(0, 0, 0); 33 | public scale = vec3.fromValues(1, 1, 1); 34 | public rotation = vec3.fromValues(0, 0, 0); 35 | 36 | constructor(props: Constructor) { 37 | const { gl, shaderProgram, geometry } = props; 38 | 39 | if (!geometry) throw new Error('No geometry provided for the Mesh'); 40 | 41 | this.gl = gl; 42 | this.shaderProgram = shaderProgram; 43 | this.vertices = geometry.vertices; 44 | this.normals = geometry.normals; 45 | this.texcoords = geometry.texcoords; 46 | 47 | this.init(); 48 | } 49 | 50 | private init() { 51 | // Create VAO for buffer bindings 52 | this.VAO = this.gl.createVertexArray(); 53 | this.gl.bindVertexArray(this.VAO); 54 | 55 | // Position buffer 56 | this.positionBuffer = createAndInitBuffer({ 57 | gl: this.gl, 58 | target: this.gl.ARRAY_BUFFER, 59 | data: new Float32Array(this.vertices), 60 | }); 61 | 62 | setupVertexAttribute({ 63 | gl: this.gl, 64 | name: 'a_position', 65 | program: this.shaderProgram.program, 66 | buffer: this.positionBuffer, 67 | size: 3, 68 | }); 69 | 70 | // Normal buffer 71 | if (this.normals.length > 0) { 72 | this.normalBuffer = createAndInitBuffer({ 73 | gl: this.gl, 74 | target: this.gl.ARRAY_BUFFER, 75 | data: new Float32Array(this.normals), 76 | }); 77 | 78 | setupVertexAttribute({ 79 | gl: this.gl, 80 | name: 'a_normal', 81 | program: this.shaderProgram.program, 82 | buffer: this.normalBuffer, 83 | size: 3, 84 | }); 85 | } 86 | 87 | if (this.texcoords.length > 0) { 88 | // UV buffer 89 | this.uvBuffer = createAndInitBuffer({ 90 | gl: this.gl, 91 | target: this.gl.ARRAY_BUFFER, 92 | data: new Float32Array(this.texcoords), 93 | }); 94 | 95 | setupVertexAttribute({ 96 | gl: this.gl, 97 | name: 'a_uv', 98 | program: this.shaderProgram.program, 99 | buffer: this.uvBuffer, 100 | size: 2, 101 | }); 102 | } 103 | 104 | // Unbind VAO 105 | this.gl.bindVertexArray(null); 106 | } 107 | 108 | public render(props: Render) { 109 | const { camera } = props; 110 | 111 | this.shaderProgram.use(); 112 | this.gl.bindVertexArray(this.VAO); 113 | 114 | // Construct model matrix 115 | mat4.identity(this.modelMatrix); 116 | mat4.translate(this.modelMatrix, this.modelMatrix, this.position); 117 | mat4.scale(this.modelMatrix, this.modelMatrix, this.scale); 118 | mat4.rotateX(this.modelMatrix, this.modelMatrix, this.rotation[0]); 119 | mat4.rotateY(this.modelMatrix, this.modelMatrix, this.rotation[1]); 120 | mat4.rotateZ(this.modelMatrix, this.modelMatrix, this.rotation[2]); 121 | 122 | // Load model matrix, view matrix and projection matrix to shader 123 | this.shaderProgram.setUniformMatrix4fv('u_modelMatrix', this.modelMatrix); 124 | this.shaderProgram.setUniformMatrix4fv('u_viewMatrix', camera.viewMatrix); 125 | this.shaderProgram.setUniform3f('u_cameraPositionWorld', camera.position); 126 | this.shaderProgram.setUniformMatrix4fv('u_projectionMatrix', camera.perspectiveProjectionMatrix); 127 | 128 | this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertices.length / 3); 129 | 130 | // Unbind VAO 131 | this.gl.bindVertexArray(null); 132 | 133 | // Unbind textures - doing it here due to asynchronous nature of WebGL handling 134 | this.gl.bindTexture(this.gl.TEXTURE_2D, null); 135 | this.gl.activeTexture(this.gl.TEXTURE0); 136 | } 137 | 138 | public destroy() { 139 | this.gl.deleteBuffer(this.positionBuffer); 140 | this.gl.deleteBuffer(this.normalBuffer); 141 | this.gl.deleteBuffer(this.uvBuffer); 142 | this.gl.deleteVertexArray(this.VAO); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/app/src/Components/Objects3D.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from 'gl-matrix'; 2 | 3 | import { ShaderProgram } from '../lib/ShaderProgram'; 4 | import { Mesh } from '../lib/Mesh'; 5 | import { TexturesManager } from '../lib/TexturesManager'; 6 | import { globalState } from '../utils/globalState'; 7 | import { GeometriesManager } from '../lib/GeometriesManager'; 8 | import { Camera } from '../lib/Camera'; 9 | 10 | import fragmentShaderSource from '../shaders/default/fragment.glsl'; 11 | import vertexShaderSource from '../shaders/default/vertex.glsl'; 12 | 13 | import bgFragment from '../shaders/background/fragment.glsl'; 14 | import bgVertex from '../shaders/background/vertex.glsl'; 15 | 16 | interface Constructor { 17 | gl: WebGL2RenderingContext | null; 18 | texturesManager: TexturesManager; 19 | geometriesManager: GeometriesManager; 20 | camera: Camera; 21 | } 22 | 23 | export class Objects3D { 24 | private gl: WebGL2RenderingContext; 25 | 26 | private jet1: Mesh | null = null; 27 | private jet2: Mesh | null = null; 28 | private bgMesh: Mesh | null = null; 29 | 30 | private jetProgram1: ShaderProgram | null = null; 31 | private jetProgram2: ShaderProgram | null = null; 32 | private bgProgram: ShaderProgram | null = null; 33 | 34 | private texturesManager: TexturesManager; 35 | private geometriesManager: GeometriesManager; 36 | private camera: Camera; 37 | 38 | constructor(props: Constructor) { 39 | const { gl, texturesManager, geometriesManager, camera } = props; 40 | if (!gl) throw new Error('WebGL2 not supported, cannot create Objects3D'); 41 | 42 | this.gl = gl; 43 | this.texturesManager = texturesManager; 44 | this.geometriesManager = geometriesManager; 45 | this.camera = camera; 46 | 47 | this.init(); 48 | } 49 | 50 | private init() { 51 | this.jetProgram1 = new ShaderProgram({ 52 | gl: this.gl, 53 | fragmentCode: fragmentShaderSource, 54 | vertexCode: vertexShaderSource, 55 | texturesManager: this.texturesManager, 56 | texturesToUse: [ 57 | { 58 | textureSrc: '/public/assets/models/efa/efa.webp', 59 | uniformName: 'u_image', 60 | }, 61 | ], 62 | uniforms: { 63 | u_time: globalState.uTime, 64 | }, 65 | }); 66 | 67 | this.jetProgram2 = new ShaderProgram({ 68 | gl: this.gl, 69 | fragmentCode: fragmentShaderSource, 70 | vertexCode: vertexShaderSource, 71 | texturesManager: this.texturesManager, 72 | texturesToUse: [ 73 | { 74 | textureSrc: '/public/assets/models/f22/f22.webp', 75 | uniformName: 'u_image', 76 | }, 77 | ], 78 | uniforms: { 79 | u_time: globalState.uTime, 80 | }, 81 | }); 82 | 83 | this.jet1 = new Mesh({ 84 | gl: this.gl, 85 | shaderProgram: this.jetProgram1, 86 | geometry: this.geometriesManager.getGeometry('/public/assets/models/efa/efa.obj'), 87 | }); 88 | 89 | this.jet2 = new Mesh({ 90 | gl: this.gl, 91 | shaderProgram: this.jetProgram2, 92 | geometry: this.geometriesManager.getGeometry('/public/assets/models/f22/f22.obj'), 93 | }); 94 | 95 | this.bgProgram = new ShaderProgram({ 96 | gl: this.gl, 97 | fragmentCode: bgFragment, 98 | vertexCode: bgVertex, 99 | texturesManager: this.texturesManager, 100 | uniforms: { 101 | u_time: globalState.uTime, 102 | u_resolution: globalState.stageSize, 103 | }, 104 | }); 105 | 106 | this.bgMesh = new Mesh({ 107 | gl: this.gl, 108 | shaderProgram: this.bgProgram, 109 | geometry: this.geometriesManager.getGeometry('plane'), 110 | }); 111 | } 112 | 113 | public update() { 114 | const mouse2DCurrent = globalState.mouse2DCurrent.value; 115 | 116 | if (this.bgMesh) { 117 | // Disable depth test when rendering the background - it will be behind everything 118 | this.gl.disable(this.gl.DEPTH_TEST); 119 | this.bgMesh.render({ camera: this.camera }); 120 | this.gl.enable(this.gl.DEPTH_TEST); 121 | } 122 | 123 | if (this.jet1) { 124 | this.jet1.rotation[2] = -mouse2DCurrent[0] * 0.4; 125 | this.jet1.rotation[0] = mouse2DCurrent[1] * 3.2; 126 | 127 | const floatY = Math.sin(globalState.uTime.value * 1.2) * 0.035; 128 | 129 | this.jet1.position = vec3.fromValues( 130 | mouse2DCurrent[0] * -0.5, 131 | mouse2DCurrent[1] * -0.1 + floatY - 0.03, 132 | -mouse2DCurrent[1] * 0.25 - 0.02 133 | ); 134 | 135 | this.jet1.render({ camera: this.camera }); 136 | } 137 | 138 | if (this.jet2) { 139 | this.jet2.rotation[2] = mouse2DCurrent[0] * 0.8; 140 | this.jet2.rotation[0] = -mouse2DCurrent[1] * 1.2; 141 | 142 | const floatY = Math.sin(globalState.uTime.value * 1.6) * 0.02; 143 | 144 | this.jet2.position = vec3.fromValues( 145 | mouse2DCurrent[0] * 0.5, 146 | mouse2DCurrent[1] * 0.1 + floatY + 0.03, 147 | mouse2DCurrent[1] * 0.2 - 0.05 148 | ); 149 | 150 | this.jet2.render({ camera: this.camera }); 151 | } 152 | } 153 | 154 | public destroy() { 155 | if (this.jet1) this.jet1.destroy(); 156 | if (this.jet2) this.jet2.destroy(); 157 | if (this.bgMesh) this.bgMesh.destroy(); 158 | if (this.jetProgram1) this.jetProgram1.destroy(); 159 | if (this.jetProgram2) this.jetProgram2.destroy(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/app/src/Scene.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from 'gl-matrix'; 2 | 3 | import fragmentShaderPostSource from './shaders/post/fragment.glsl'; 4 | import vertexShaderPostSource from './shaders/post/vertex.glsl'; 5 | 6 | import { globalState } from './utils/globalState'; 7 | import { ShaderProgram } from './lib/ShaderProgram'; 8 | import { Mesh } from './lib/Mesh'; 9 | import { Camera } from './lib/Camera'; 10 | import { lerp } from './utils/lerp'; 11 | import { TexturesManager } from './lib/TexturesManager'; 12 | import { GeometriesManager } from './lib/GeometriesManager'; 13 | 14 | import { Objects3D } from './Components/Objects3D'; 15 | 16 | export class Scene { 17 | private gl: WebGL2RenderingContext | null = null; 18 | private camera = new Camera(); 19 | 20 | private texturesManager; 21 | private geometriesManager; 22 | 23 | private objects3D: Objects3D | null = null; 24 | 25 | private postProcessShaderProgram: ShaderProgram | null = null; 26 | private postProcessMesh: Mesh | null = null; 27 | 28 | constructor() { 29 | if (globalState.canvasEl) { 30 | this.gl = globalState.canvasEl.getContext('webgl2'); 31 | } 32 | if (!this.gl) throw new Error('WebGL2 not supported'); 33 | 34 | this.texturesManager = new TexturesManager({ gl: this.gl }); 35 | this.geometriesManager = new GeometriesManager(); 36 | 37 | this.init(); 38 | } 39 | 40 | private postProcess() { 41 | const { gl } = this; 42 | if (!gl) return; 43 | const { width, height } = gl.canvas; 44 | 45 | // Create a texture to render to 46 | this.texturesManager.createFrameBufferTexture(width, height, 'postProcessTexture'); 47 | 48 | this.postProcessShaderProgram = new ShaderProgram({ 49 | gl: this.gl, 50 | fragmentCode: fragmentShaderPostSource, 51 | vertexCode: vertexShaderPostSource, 52 | texturesManager: this.texturesManager, 53 | texturesToUse: [ 54 | { 55 | textureSrc: 'postProcessTexture', 56 | uniformName: 'u_image', 57 | }, 58 | ], 59 | uniforms: { 60 | u_time: globalState.uTime, 61 | u_resolution: globalState.stageSize, 62 | }, 63 | }); 64 | 65 | this.postProcessMesh = new Mesh({ 66 | gl, 67 | shaderProgram: this.postProcessShaderProgram, 68 | geometry: this.geometriesManager.getGeometry('plane'), 69 | }); 70 | } 71 | 72 | private async init() { 73 | if (!this.gl) return; 74 | 75 | await this.geometriesManager.addObjectsToLoad([ 76 | '/public/assets/models/f22/f22.obj', 77 | '/public/assets/models/efa/efa.obj', 78 | ]); 79 | 80 | // Plane made out of two triangles 81 | const planeVertices = [-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]; 82 | const planeTexcoords = [0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1]; 83 | 84 | this.geometriesManager.addGeometry({ 85 | geometryUrl: 'plane', 86 | geometryObject: { vertices: planeVertices, texcoords: planeTexcoords, normals: [] }, 87 | }); 88 | 89 | await this.texturesManager.addTexturesToLoad([ 90 | '/public/assets/models/f22/f22.webp', 91 | '/public/assets/models/efa/efa.webp', 92 | ]); 93 | 94 | this.objects3D = new Objects3D({ 95 | gl: this.gl, 96 | texturesManager: this.texturesManager, 97 | camera: this.camera, 98 | geometriesManager: this.geometriesManager, 99 | }); 100 | 101 | this.postProcess(); 102 | } 103 | 104 | private render() { 105 | const gl = this.gl; 106 | if (!gl) return; 107 | 108 | // Render to post process texture 109 | const textureObj = this.texturesManager.getTextureObj('postProcessTexture'); 110 | if (textureObj && textureObj.frameBuffer) { 111 | gl.bindFramebuffer(gl.FRAMEBUFFER, textureObj.frameBuffer); 112 | } 113 | 114 | // Clear the canvas and depth buffer 115 | gl.clearColor(0, 0, 0, 0); 116 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 117 | gl.enable(gl.DEPTH_TEST); 118 | 119 | // Lerp mouse position 120 | const mouse2DTarget = globalState.mouse2DTarget.value; 121 | const mouse2DCurrent = globalState.mouse2DCurrent.value; 122 | mouse2DCurrent[0] = lerp(mouse2DCurrent[0], mouse2DTarget[0], 0.053 * globalState.slowDownFactor.value); 123 | mouse2DCurrent[1] = lerp(mouse2DCurrent[1], mouse2DTarget[1], 0.053 * globalState.slowDownFactor.value); 124 | 125 | // Update camera 126 | this.camera.updateViewMatrix({ 127 | target: vec3.fromValues(mouse2DCurrent[0] * -0.25, mouse2DCurrent[1] * 0.05, -1), 128 | }); 129 | 130 | this.objects3D?.update(); 131 | 132 | // Post process 2nd part 133 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 134 | // Render plane where post process texture is applied 135 | this.postProcessShaderProgram?.use(); 136 | this.postProcessMesh?.render({ 137 | camera: this.camera, 138 | }); 139 | } 140 | 141 | public update() { 142 | this.render(); 143 | } 144 | 145 | public onResize() { 146 | let w = globalState.stageSize.value[0]; 147 | let h = globalState.stageSize.value[1]; 148 | 149 | // updateDebug(`Window size: ${w.toFixed(1)} X ${h.toFixed(1)}`); 150 | 151 | const ratio = globalState.pixelRatio.value; 152 | 153 | // Possibly need to Math.round() w and h here, but will leave for now 154 | w = w * ratio; 155 | h = h * ratio; 156 | 157 | const canvas = globalState.canvasEl; 158 | if (!canvas || !this.gl) return; 159 | 160 | if (canvas.width !== w && canvas.height !== h) { 161 | // Sets only the resolution of the canvas 162 | canvas.width = w; 163 | canvas.height = h; 164 | } 165 | 166 | this.gl.viewport(0, 0, w, h); 167 | 168 | this.camera.updateProjectionMatrix({ 169 | fov: Math.PI / 3, 170 | aspect_ratio: w / h, 171 | near: 0.1, 172 | far: 20, 173 | }); 174 | 175 | this.texturesManager.resize(); 176 | } 177 | 178 | public destroy() { 179 | this.geometriesManager?.destroy(); 180 | this.texturesManager?.destroy(); 181 | 182 | this.objects3D?.destroy(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/app/src/lib/ShaderProgram.ts: -------------------------------------------------------------------------------- 1 | import { useTexture } from './Util'; 2 | import { TexturesManager } from './TexturesManager'; 3 | 4 | interface TextureToUse { 5 | textureSrc: string; 6 | uniformName: string; 7 | } 8 | 9 | interface UniformValue { 10 | value: number | number[] | Float32Array; 11 | } 12 | type Uniforms = Record; 13 | 14 | interface Props { 15 | vertexCode: string; 16 | fragmentCode: string; 17 | gl: WebGL2RenderingContext | null; 18 | texturesToUse?: TextureToUse[]; 19 | uniforms?: Uniforms; 20 | texturesManager: TexturesManager; 21 | } 22 | 23 | export class ShaderProgram { 24 | private vertexCode: string; 25 | private fragmentCode: string; 26 | private gl: WebGL2RenderingContext; 27 | public program: WebGLProgram | null = null; 28 | private texturesManager; 29 | 30 | private uniformLocations = new Map(); 31 | 32 | public texturesToUse: TextureToUse[] = []; 33 | private uniforms: Uniforms = {}; 34 | 35 | constructor(props: Props) { 36 | const { vertexCode, fragmentCode, gl, texturesToUse, texturesManager, uniforms } = props; 37 | 38 | if (!gl) { 39 | throw new Error('No gl context provided to ShaderProgram constructor'); 40 | } 41 | this.gl = gl; 42 | 43 | this.vertexCode = vertexCode; 44 | this.fragmentCode = fragmentCode; 45 | 46 | this.texturesManager = texturesManager; 47 | 48 | if (texturesToUse) this.texturesToUse = texturesToUse; 49 | if (uniforms) this.uniforms = uniforms; 50 | 51 | this.init(this.gl); 52 | } 53 | 54 | private createShader(gl: WebGL2RenderingContext, type: number, source: string) { 55 | const shader = gl.createShader(type); 56 | if (!shader) throw new Error(`Shader not created for type ${type}`); 57 | gl.shaderSource(shader, source); 58 | gl.compileShader(shader); 59 | const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 60 | if (success) return shader; 61 | gl.deleteShader(shader); 62 | } 63 | 64 | private createProgram(gl: WebGL2RenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader) { 65 | const program = gl.createProgram(); 66 | if (!program) throw new Error('Program not created'); 67 | gl.attachShader(program, vertexShader); 68 | gl.attachShader(program, fragmentShader); 69 | gl.linkProgram(program); 70 | const success = gl.getProgramParameter(program, gl.LINK_STATUS); 71 | 72 | // Clean up 73 | gl.deleteShader(vertexShader); 74 | gl.deleteShader(fragmentShader); 75 | 76 | if (success) return program; 77 | gl.deleteProgram(program); 78 | } 79 | 80 | private init(gl: WebGL2RenderingContext) { 81 | const vertexShader = this.createShader(gl, gl.VERTEX_SHADER, this.vertexCode); 82 | if (!vertexShader) throw new Error('Could not create vertex shader'); 83 | 84 | const fragmentShader = this.createShader(gl, gl.FRAGMENT_SHADER, this.fragmentCode); 85 | if (!fragmentShader) throw new Error('Could not create fragment shader'); 86 | 87 | const program = this.createProgram(gl, vertexShader, fragmentShader); 88 | if (!program) throw new Error('Could not create program'); 89 | this.program = program; 90 | } 91 | 92 | private getUniformLocation(name: string) { 93 | if (!this.program) { 94 | throw new Error('Cannot get uniform location, program not initialized'); 95 | } 96 | 97 | // If uniform location is not cached, get it from the GPU 98 | if (!this.uniformLocations.has(name)) { 99 | const location = this.gl.getUniformLocation(this.program, name); 100 | if (!location) { 101 | return undefined; 102 | } 103 | this.uniformLocations.set(name, location); 104 | } 105 | const cachedLocation = this.uniformLocations.get(name); 106 | return cachedLocation; 107 | } 108 | 109 | public setUniform1f(name: string, value: number) { 110 | const location = this.getUniformLocation(name); 111 | if (!location) { 112 | return undefined; 113 | } 114 | this.gl.uniform1f(location, value); 115 | } 116 | 117 | public setUniform4f(name: string, value: [number, number, number, number]) { 118 | const location = this.getUniformLocation(name); 119 | if (!location) { 120 | return undefined; 121 | } 122 | this.gl.uniform4f(location, value[0], value[1], value[2], value[3]); 123 | } 124 | 125 | public setUniform2f(name: string, value: [number, number] | Float32Array) { 126 | const location = this.getUniformLocation(name); 127 | if (!location) { 128 | return undefined; 129 | } 130 | this.gl.uniform2f(location, value[0], value[1]); 131 | } 132 | 133 | public setUniform3f(name: string, value: [number, number, number] | Float32Array) { 134 | const location = this.getUniformLocation(name); 135 | if (!location) { 136 | return undefined; 137 | } 138 | this.gl.uniform3f(location, value[0], value[1], value[2]); 139 | } 140 | 141 | public setUniformMatrix4fv(name: string, value: Float32Array | number[]) { 142 | const location = this.getUniformLocation(name); 143 | if (!location) { 144 | return undefined; 145 | } 146 | this.gl.uniformMatrix4fv(location, false, value); 147 | } 148 | 149 | public use() { 150 | if (!this.program) { 151 | throw new Error('Cannot use program, program is not set'); 152 | } 153 | this.gl.useProgram(this.program); 154 | 155 | // Bind textures 156 | this.texturesToUse.forEach((el) => { 157 | const textureObj = this.texturesManager.getTextureObj(el.textureSrc); 158 | if (!textureObj) return; 159 | 160 | useTexture({ 161 | gl: this.gl, 162 | shaderProgram: this.program, 163 | uniformLocation: this.getUniformLocation(el.uniformName), 164 | texture: textureObj.texture, 165 | textureIndex: textureObj.textureIndex, 166 | }); 167 | }); 168 | 169 | // Set uniforms 170 | Object.entries(this.uniforms).forEach(([objKey, objValue]) => { 171 | const value = objValue.value; 172 | if (typeof value === 'number') { 173 | this.setUniform1f(objKey, value); 174 | } else if (value instanceof Array) { 175 | if (value.length === 4) { 176 | this.setUniform4f(objKey, [value[0], value[1], value[2], value[3]]); 177 | } 178 | if (value.length === 2) { 179 | this.setUniform2f(objKey, [value[0], value[1]]); 180 | } 181 | } else if (value instanceof Float32Array) { 182 | this.setUniformMatrix4fv(objKey, value); 183 | } 184 | }); 185 | } 186 | 187 | public destroy() { 188 | this.gl.deleteProgram(this.program); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/app/src/lib/TexturesManager.ts: -------------------------------------------------------------------------------- 1 | interface Constructor { 2 | gl: WebGL2RenderingContext | null; 3 | } 4 | 5 | interface TextureObject { 6 | width: number; 7 | height: number; 8 | textureIndex: number; 9 | frameBuffer?: WebGLFramebuffer; 10 | texture: WebGLTexture; 11 | depthBuffer?: WebGLRenderbuffer; 12 | } 13 | 14 | interface LoadTexture { 15 | gl: WebGL2RenderingContext | null; 16 | url: string; 17 | textureIndex: number; 18 | } 19 | 20 | export class TexturesManager { 21 | private gl: WebGL2RenderingContext | null = null; 22 | private isReady = false; 23 | private startedLoading = false; 24 | private loadedTextures: Map = new Map(); 25 | 26 | constructor(props: Constructor) { 27 | const { gl } = props; 28 | 29 | this.gl = gl; 30 | if (!this.gl) return; 31 | } 32 | 33 | public getTextureObj(textureUrl: string) { 34 | const textureObject = this.loadedTextures.get(textureUrl); 35 | if (!this.isReady) return null; 36 | if (!textureObject) { 37 | console.error(`Texture not found. ${textureUrl} `); 38 | return null; 39 | } 40 | return textureObject; 41 | } 42 | 43 | private loadImage(url: string): Promise { 44 | return new Promise((resolve, reject) => { 45 | const image = new Image(); 46 | image.crossOrigin = 'anonymous'; 47 | image.src = url; 48 | 49 | const onLoaded = () => { 50 | resolve(image); 51 | }; 52 | 53 | if (image.complete) { 54 | onLoaded(); 55 | } else { 56 | image.onload = onLoaded; 57 | image.onerror = function () { 58 | console.error('Failed to load texture image:', url); 59 | reject(); 60 | }; 61 | } 62 | }); 63 | } 64 | 65 | private async loadTexture(props: LoadTexture) { 66 | const { gl, url, textureIndex } = props; 67 | 68 | const image = await this.loadImage(url); 69 | 70 | if (!gl) { 71 | return console.error('WebGL context or shader program is not available.'); 72 | } 73 | 74 | // Create and bind the texture 75 | const texture = gl.createTexture(); 76 | if (!texture) { 77 | return console.error('Unable to create texture.'); 78 | } 79 | gl.bindTexture(gl.TEXTURE_2D, texture); 80 | 81 | const mode = gl.REPEAT; // or gl.CLAMP_TO_EDGE 82 | 83 | // Set the texture parameters 84 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, mode); 85 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, mode); 86 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 87 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 88 | 89 | // Upload the image to the texture 90 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 91 | 92 | // Generate mipmaps 93 | const isPowerOfTwo = (value: number) => { 94 | return (value & (value - 1)) == 0; 95 | }; 96 | if (isPowerOfTwo(image.width) && isPowerOfTwo(image.height)) { 97 | gl.generateMipmap(gl.TEXTURE_2D); 98 | } 99 | 100 | // Add the texture to the loaded textures 101 | this.loadedTextures.set(url, { 102 | texture, 103 | height: image.height, 104 | width: image.width, 105 | textureIndex, 106 | }); 107 | 108 | // Unbind the texture 109 | gl.bindTexture(gl.TEXTURE_2D, null); 110 | 111 | return this.loadedTextures.get(url); 112 | } 113 | 114 | public createFrameBufferTexture(width: number, height: number, name: string) { 115 | const gl = this.gl; 116 | if (!gl) return console.error('Cannot create frame buffer texture, WebGL context not available.'); 117 | 118 | const frameBuffer = gl.createFramebuffer(); 119 | if (!frameBuffer) return console.error('Cannot create frame buffer for frame buffer texture.'); 120 | 121 | gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer); 122 | 123 | // Create and set up the color texture 124 | const texture = gl.createTexture(); 125 | if (!texture) return console.error('Cannot create frame buffer texture.'); 126 | gl.bindTexture(gl.TEXTURE_2D, texture); 127 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 128 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 129 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 130 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 131 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 132 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 133 | 134 | // Create and set up the depth renderbuffer 135 | const depthBuffer = gl.createRenderbuffer(); 136 | if (!depthBuffer) return console.error('Cannot create depth renderbuffer.'); 137 | gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); 138 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); 139 | gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); 140 | 141 | // Check if the framebuffer is complete 142 | const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); 143 | if (status !== gl.FRAMEBUFFER_COMPLETE) { 144 | console.error('Framebuffer is not complete. Status:', status); 145 | 146 | // Remove everything in case of error 147 | gl.bindTexture(gl.TEXTURE_2D, null); 148 | gl.bindRenderbuffer(gl.RENDERBUFFER, null); 149 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 150 | 151 | texture && gl.deleteTexture(texture); 152 | depthBuffer && gl.deleteRenderbuffer(depthBuffer); 153 | frameBuffer && gl.deleteFramebuffer(frameBuffer); 154 | } 155 | 156 | // Add the texture and renderbuffer to the loaded textures 157 | this.loadedTextures.set(name, { 158 | width, 159 | height, 160 | textureIndex: 0, 161 | frameBuffer, 162 | texture, 163 | depthBuffer, 164 | }); 165 | 166 | // Unbind texture, renderbuffer, and framebuffer 167 | gl.bindTexture(gl.TEXTURE_2D, null); 168 | gl.bindRenderbuffer(gl.RENDERBUFFER, null); 169 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 170 | 171 | return this.loadedTextures.get(name); 172 | } 173 | 174 | private resizeFrameBufferTextures(width: number, height: number) { 175 | this.loadedTextures.forEach((textureObject) => { 176 | // Don't do anything if the texture does not have a frame buffer etc. 177 | if (!textureObject.frameBuffer || !textureObject.depthBuffer) return; 178 | 179 | const gl = this.gl; 180 | if (!gl) return console.error('Cannot resize frame buffer texture.'); 181 | 182 | gl.bindFramebuffer(gl.FRAMEBUFFER, textureObject.frameBuffer); 183 | 184 | // Resize the color texture 185 | gl.bindTexture(gl.TEXTURE_2D, textureObject.texture); 186 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); 187 | 188 | // Resize the depth renderbuffer 189 | gl.bindRenderbuffer(gl.RENDERBUFFER, textureObject.depthBuffer); 190 | gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); 191 | 192 | // Unbind texture, renderbuffer, and framebuffer 193 | gl.bindTexture(gl.TEXTURE_2D, null); 194 | gl.bindRenderbuffer(gl.RENDERBUFFER, null); 195 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 196 | }); 197 | } 198 | 199 | public async addTexturesToLoad(texturesToLoad: string[]) { 200 | if (!this.gl) { 201 | return console.error('WebGL context not available for TexturesManager.'); 202 | } 203 | 204 | if (this.startedLoading) { 205 | console.error('TexturesManager already started loading textures. Cannot add more textures.'); 206 | return; 207 | } 208 | 209 | this.startedLoading = true; 210 | 211 | const promises = texturesToLoad.map((textureUrl, key) => { 212 | return this.loadTexture({ 213 | gl: this.gl, 214 | url: textureUrl, 215 | textureIndex: key, 216 | }); 217 | }); 218 | 219 | return Promise.allSettled(promises).then(() => { 220 | this.isReady = true; 221 | Promise.resolve(); 222 | }); 223 | } 224 | 225 | public destroy() { 226 | this.loadedTextures.forEach((textureObject) => { 227 | if (textureObject.texture) { 228 | this.gl?.deleteTexture(textureObject.texture); 229 | } 230 | 231 | if (textureObject.frameBuffer) { 232 | this.gl?.deleteFramebuffer(textureObject.frameBuffer); 233 | } 234 | 235 | if (textureObject.depthBuffer) { 236 | this.gl?.deleteRenderbuffer(textureObject.depthBuffer); 237 | } 238 | }); 239 | this.loadedTextures.clear(); 240 | } 241 | 242 | public resize() { 243 | if (!this.gl) return; 244 | const width = this.gl.drawingBufferWidth || 0; 245 | const height = this.gl.drawingBufferHeight || 0; 246 | this.resizeFrameBufferTextures(width, height); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/f117/f117.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.79 (sub 0) OBJ File: 'fighters_0.blend' 2 | # www.blender.org 3 | mtllib f-117.mtl 4 | o F-117 5 | v 1.431311 -0.080746 -0.100098 6 | v 0.959029 -0.063769 -0.243942 7 | v 1.197672 -0.080746 -0.196029 8 | v 0.720659 -0.080746 -0.395345 9 | v 0.783476 0.073457 -0.183297 10 | v 0.547609 0.073457 -0.329773 11 | v 0.089260 -0.042764 -0.490725 12 | v 0.210262 0.118042 -0.349212 13 | v 0.063131 0.163098 -0.239188 14 | v -1.561768 -0.080746 -1.097042 15 | v -0.858154 -0.080746 -0.468403 16 | v -0.709112 -0.046530 -0.454687 17 | v -0.405576 0.091581 -0.327342 18 | v -0.996863 -0.080746 -0.432693 19 | v -1.433718 -0.080746 -1.217256 20 | v -1.271797 -0.080746 -1.217256 21 | v -0.924093 0.042619 -0.061599 22 | v -1.223704 -0.029585 -0.057042 23 | v -1.699272 0.295954 -0.300594 24 | v -2.006486 0.295954 -0.300594 25 | v 0.816673 0.229178 -0.049913 26 | v 0.800470 0.153175 -0.115014 27 | v 1.059639 0.139678 -0.044752 28 | v 0.899962 0.237001 -0.018498 29 | v 1.003989 0.102522 -0.101096 30 | v 0.876546 0.229086 -0.033912 31 | v 0.895185 0.231746 -0.025417 32 | v 1.017887 0.109624 -0.090248 33 | v 0.902870 0.242351 -0.006867 34 | v 1.065382 0.142992 -0.032976 35 | v 1.431311 -0.080746 0.100098 36 | v 0.959029 -0.063769 0.243942 37 | v 1.197672 -0.080746 0.196029 38 | v 0.720659 -0.080746 0.395345 39 | v 0.783476 0.073457 0.183297 40 | v 0.547609 0.073457 0.329773 41 | v 0.089260 -0.042764 0.490725 42 | v 0.210262 0.118042 0.349212 43 | v 0.063131 0.163098 0.239188 44 | v -1.561768 -0.080746 1.097042 45 | v -0.858154 -0.080746 0.468403 46 | v -0.709112 -0.046530 0.454687 47 | v -0.405576 0.091581 0.327342 48 | v -0.996863 -0.080746 0.432693 49 | v -1.433718 -0.080746 1.217256 50 | v -1.271797 -0.080746 1.217256 51 | v -0.924093 0.042619 0.061599 52 | v -1.223704 -0.029585 0.057042 53 | v -1.699272 0.295954 0.300594 54 | v -2.006486 0.295954 0.300594 55 | v 0.816673 0.229178 0.049913 56 | v 0.800470 0.153175 0.115014 57 | v 1.059639 0.139678 0.044752 58 | v 0.899962 0.237001 0.018498 59 | v 1.003989 0.102522 0.101096 60 | v 0.876546 0.229086 0.033912 61 | v 0.895185 0.231746 0.025417 62 | v 1.017887 0.109624 0.090248 63 | v 0.902870 0.242351 0.006867 64 | v 1.065382 0.142992 0.032976 65 | v 1.669928 -0.080746 0.000000 66 | v 1.210881 0.054104 0.000000 67 | v -1.500610 -0.080746 0.000000 68 | v -0.796050 0.091437 0.000000 69 | v 0.799601 -0.137449 0.000000 70 | v -1.220287 -0.012240 0.000000 71 | v 0.829096 0.287450 0.000000 72 | v 1.065449 0.142992 0.000000 73 | v 0.902884 0.242351 0.000000 74 | vt 0.077619 0.790742 75 | vt 0.148382 0.821431 76 | vt 0.004460 0.821431 77 | vt 0.222417 0.746640 78 | vt 0.149251 0.761330 79 | vt 0.295500 0.700221 80 | vt 0.011719 0.652344 81 | vt 0.085938 0.600830 82 | vt 0.085938 0.652344 83 | vt 0.236807 0.919792 84 | vt 0.231596 0.944233 85 | vt 0.169199 0.928703 86 | vt 0.309122 0.919792 87 | vt 0.449649 0.884160 88 | vt 0.412550 0.933462 89 | vt 0.497094 0.748098 90 | vt 0.348556 0.720325 91 | vt 0.451984 0.714365 92 | vt 0.457660 0.947276 93 | vt 0.226643 0.967465 94 | vt 0.601362 0.925349 95 | vt 0.640796 0.721070 96 | vt 0.489083 0.670978 97 | vt 0.906374 0.448228 98 | vt 0.956018 0.448228 99 | vt 0.779553 0.677822 100 | vt 0.733858 0.682027 101 | vt 0.995277 0.485085 102 | vt 0.077142 0.081292 103 | vt 0.003984 0.050603 104 | vt 0.270820 0.050603 105 | vt 0.976050 0.050603 106 | vt 0.821604 0.183264 107 | vt 0.779077 0.194212 108 | vt 0.295023 0.171813 109 | vt 0.148774 0.110704 110 | vt 0.782647 0.872515 111 | vt 0.694424 0.883005 112 | vt 0.740119 0.872515 113 | vt 0.905897 0.423806 114 | vt 0.955541 0.423806 115 | vt 0.760513 0.821431 116 | vt 0.799770 0.802545 117 | vt 0.890581 0.821431 118 | vt 0.262254 0.821431 119 | vt 0.822081 0.688770 120 | vt 0.463799 0.548321 121 | vt 0.369609 0.548321 122 | vt 0.610653 0.453831 123 | vt 0.701464 0.470650 124 | vt 0.037753 0.470652 125 | vt 0.129611 0.448514 126 | vt 0.275417 0.548323 127 | vt 0.976526 0.821431 128 | vt 0.891629 0.803942 129 | vt 0.214508 0.432829 130 | vt 0.524708 0.432827 131 | vt 0.109817 0.872515 132 | vt 0.152137 0.940095 133 | vt 0.038185 0.872515 134 | vt 0.164938 0.930881 135 | vt 0.222820 0.985401 136 | vt 0.202557 0.968322 137 | vt 0.029547 0.682016 138 | vt 0.079968 0.710244 139 | vt 0.078503 0.711855 140 | vt 0.208272 0.967505 141 | vt 0.201093 0.969934 142 | vt 0.101849 0.712312 143 | vt 0.125173 0.689040 144 | vt 0.120220 0.712272 145 | vt 0.017677 0.941196 146 | vt 0.007567 0.913944 147 | vt 0.038256 0.872600 148 | vt 0.011442 0.681877 149 | vt 0.013548 0.712340 150 | vt 0.011442 0.712340 151 | vt 0.007567 0.985486 152 | vt 0.007567 0.971659 153 | vt 0.009672 0.971659 154 | vt 0.011719 0.605469 155 | vt 0.021552 0.681877 156 | vt 0.007567 0.941196 157 | vt 0.062775 0.673510 158 | vt 0.042348 0.672802 159 | vt 0.994800 0.386949 160 | vt 0.276241 0.765234 161 | vt 0.182983 0.877720 162 | vt 0.256066 0.872515 163 | vt 0.077619 0.790742 164 | vt 0.222417 0.746640 165 | vt 0.295500 0.700221 166 | vt 0.149251 0.761330 167 | vt 0.011719 0.652344 168 | vt 0.085938 0.652344 169 | vt 0.085938 0.600830 170 | vt 0.236807 0.919792 171 | vt 0.169199 0.928703 172 | vt 0.231596 0.944233 173 | vt 0.309122 0.919792 174 | vt 0.412550 0.933462 175 | vt 0.449649 0.884160 176 | vt 0.497094 0.748098 177 | vt 0.451984 0.714365 178 | vt 0.348556 0.720325 179 | vt 0.457660 0.947276 180 | vt 0.226643 0.967465 181 | vt 0.601362 0.925349 182 | vt 0.640796 0.721070 183 | vt 0.489083 0.670978 184 | vt 0.956018 0.448228 185 | vt 0.906374 0.448228 186 | vt 0.779553 0.677822 187 | vt 0.995277 0.485085 188 | vt 0.733858 0.682027 189 | vt 0.077142 0.081292 190 | vt 0.821604 0.183264 191 | vt 0.779077 0.194212 192 | vt 0.295023 0.171813 193 | vt 0.148774 0.110704 194 | vt 0.782647 0.872515 195 | vt 0.740119 0.872515 196 | vt 0.694424 0.883005 197 | vt 0.905897 0.423806 198 | vt 0.955541 0.423806 199 | vt 0.799770 0.802545 200 | vt 0.822081 0.688770 201 | vt 0.463799 0.548321 202 | vt 0.369609 0.548321 203 | vt 0.701464 0.470650 204 | vt 0.037753 0.470652 205 | vt 0.275417 0.548323 206 | vt 0.129611 0.448514 207 | vt 0.891629 0.803942 208 | vt 0.109817 0.872515 209 | vt 0.038185 0.872515 210 | vt 0.152137 0.940095 211 | vt 0.164938 0.930881 212 | vt 0.202557 0.968322 213 | vt 0.029547 0.682016 214 | vt 0.078503 0.711855 215 | vt 0.079968 0.710244 216 | vt 0.208272 0.967505 217 | vt 0.201093 0.969934 218 | vt 0.101849 0.712312 219 | vt 0.120220 0.712272 220 | vt 0.125173 0.689040 221 | vt 0.017677 0.941196 222 | vt 0.038256 0.872600 223 | vt 0.013548 0.712340 224 | vt 0.009672 0.971659 225 | vt 0.011719 0.605469 226 | vt 0.021552 0.681877 227 | vt 0.062775 0.673510 228 | vt 0.042348 0.672802 229 | vt 0.994800 0.386949 230 | vt 0.276241 0.765234 231 | vt 0.182983 0.877720 232 | vt 0.256066 0.872515 233 | vn 0.2339 0.7964 -0.5577 234 | vn 0.1290 0.9423 -0.3088 235 | vn 0.3819 0.6900 -0.6149 236 | vn 0.2044 0.6113 -0.7645 237 | vn 0.1249 0.6008 -0.7896 238 | vn 0.1400 0.9678 -0.2092 239 | vn 0.1375 0.6273 -0.7666 240 | vn -0.0552 0.6827 -0.7286 241 | vn -0.0541 0.8966 -0.4396 242 | vn 0.0000 0.9986 -0.0522 243 | vn -0.2389 0.9335 0.2674 244 | vn 0.0642 -0.9861 -0.1532 245 | vn -0.0246 -0.9993 -0.0287 246 | vn -0.0163 -0.9979 -0.0632 247 | vn 0.0645 -0.9859 -0.1543 248 | vn -0.0223 0.9995 -0.0237 249 | vn 0.0638 -0.9858 -0.1555 250 | vn -0.1400 0.8276 -0.5436 251 | vn 0.0636 -0.9860 -0.1541 252 | vn -0.0436 -0.9985 0.0335 253 | vn -0.2288 0.9363 -0.2664 254 | vn -0.1187 0.9842 -0.1315 255 | vn -0.2268 0.9357 -0.2701 256 | vn -0.2254 0.9369 -0.2672 257 | vn 0.0000 0.6982 0.7159 258 | vn 0.0149 0.7099 0.7041 259 | vn 0.1055 -0.4923 -0.8640 260 | vn -0.2277 0.9369 -0.2651 261 | vn -0.2297 0.9362 -0.2662 262 | vn 0.0000 -0.5991 -0.8007 263 | vn -0.0355 -0.6521 -0.7573 264 | vn -0.1249 0.5110 0.8505 265 | vn 0.2862 0.6575 -0.6970 266 | vn 0.2053 0.6111 -0.7645 267 | vn 0.1984 0.6079 -0.7688 268 | vn 0.5215 0.8532 -0.0011 269 | vn 0.5215 0.8532 -0.0010 270 | vn 0.3413 0.7092 -0.6169 271 | vn 0.1989 0.6082 -0.7684 272 | vn 0.2045 0.6116 -0.7643 273 | vn -0.1224 0.9817 -0.1456 274 | vn 0.0000 -1.0000 0.0000 275 | vn 0.0000 -0.9989 -0.0465 276 | vn -0.1399 0.8197 -0.5555 277 | vn -0.0065 0.9992 -0.0387 278 | vn 0.0921 0.9704 -0.2233 279 | vn -0.0348 0.7177 -0.6955 280 | vn 0.1373 0.9655 -0.2211 281 | vn 0.1977 0.6075 -0.7694 282 | vn 0.1577 0.5310 -0.8326 283 | vn 0.2070 0.6047 -0.7691 284 | vn 0.2339 0.7964 0.5577 285 | vn 0.1290 0.9423 0.3088 286 | vn 0.3819 0.6900 0.6149 287 | vn 0.2044 0.6113 0.7645 288 | vn 0.1249 0.6008 0.7896 289 | vn 0.1400 0.9678 0.2092 290 | vn 0.1375 0.6273 0.7666 291 | vn -0.0552 0.6827 0.7286 292 | vn -0.0541 0.8966 0.4396 293 | vn 0.0000 0.9986 0.0522 294 | vn -0.2389 0.9335 -0.2674 295 | vn 0.0642 -0.9861 0.1532 296 | vn -0.0246 -0.9993 0.0287 297 | vn -0.0163 -0.9979 0.0632 298 | vn 0.0645 -0.9859 0.1543 299 | vn -0.0223 0.9995 0.0237 300 | vn 0.0638 -0.9858 0.1555 301 | vn -0.1400 0.8276 0.5436 302 | vn 0.0636 -0.9860 0.1541 303 | vn -0.0436 -0.9985 -0.0335 304 | vn -0.2288 0.9363 0.2664 305 | vn -0.1187 0.9842 0.1315 306 | vn -0.2268 0.9357 0.2701 307 | vn -0.2254 0.9369 0.2672 308 | vn 0.0000 0.6982 -0.7159 309 | vn 0.0149 0.7099 -0.7041 310 | vn 0.1055 -0.4923 0.8640 311 | vn -0.2277 0.9369 0.2651 312 | vn -0.2297 0.9362 0.2662 313 | vn 0.0000 -0.5991 0.8007 314 | vn -0.0355 -0.6521 0.7573 315 | vn -0.1249 0.5110 -0.8505 316 | vn 0.2862 0.6575 0.6970 317 | vn 0.2053 0.6111 0.7645 318 | vn 0.1984 0.6079 0.7688 319 | vn 0.5215 0.8532 0.0011 320 | vn 0.5215 0.8532 0.0010 321 | vn 0.3413 0.7092 0.6169 322 | vn 0.1989 0.6082 0.7684 323 | vn 0.2045 0.6116 0.7643 324 | vn -0.1224 0.9817 0.1456 325 | vn 0.0000 -0.9989 0.0465 326 | vn -0.1399 0.8197 0.5555 327 | vn -0.0065 0.9992 0.0387 328 | vn 0.0921 0.9704 0.2233 329 | vn -0.0348 0.7177 0.6955 330 | vn 0.1373 0.9655 0.2211 331 | vn 0.1977 0.6075 0.7694 332 | vn 0.1577 0.5310 0.8326 333 | vn 0.2070 0.6047 0.7691 334 | usemtl Material 335 | s off 336 | f 1/1/1 62/2/1 61/3/1 337 | f 2/4/2 3/5/2 4/6/2 338 | f 5/7/3 4/8/3 6/9/3 339 | f 5/10/4 22/11/4 25/12/4 340 | f 6/13/5 7/14/5 8/15/5 341 | f 9/16/6 6/17/6 8/18/6 342 | f 9/19/7 21/20/7 22/11/7 343 | f 7/14/8 13/21/8 8/15/8 344 | f 8/18/9 13/22/9 9/16/9 345 | f 7/23/10 16/24/10 15/25/10 346 | f 11/26/11 12/27/11 10/28/11 347 | f 1/29/12 61/30/12 65/31/12 348 | f 63/32/13 14/33/13 65/31/13 349 | f 14/33/14 11/34/14 65/31/14 350 | f 4/35/15 3/36/15 65/31/15 351 | f 12/27/16 15/25/16 10/28/16 352 | f 3/36/17 1/29/17 65/31/17 353 | f 14/37/18 12/38/18 11/39/18 354 | f 65/31/19 16/40/19 4/35/19 355 | f 11/34/20 15/41/20 65/31/20 356 | f 64/42/21 17/43/21 66/44/21 357 | f 9/16/22 64/42/22 67/45/22 358 | f 17/43/23 64/42/23 13/22/23 359 | f 17/43/24 13/22/24 14/46/24 360 | f 19/47/25 20/48/25 66/49/25 361 | f 66/49/26 17/50/26 19/47/26 362 | f 17/51/27 18/52/27 19/53/27 363 | f 14/46/28 63/54/28 18/55/28 364 | f 14/46/29 18/55/29 17/43/29 365 | f 19/53/30 18/52/30 20/48/30 366 | f 18/52/31 63/56/31 20/48/31 367 | f 20/48/32 63/57/32 66/49/32 368 | f 3/58/33 23/59/33 1/60/33 369 | f 28/61/33 67/62/33 27/63/33 370 | f 23/64/33 27/65/33 24/66/33 371 | f 21/20/34 67/62/34 26/67/34 372 | f 67/62/33 24/68/33 27/63/33 373 | f 23/59/33 67/62/33 1/60/33 374 | f 26/69/34 22/70/34 21/71/34 375 | f 25/12/35 67/62/35 3/58/35 376 | f 30/72/36 62/73/36 1/74/36 377 | f 68/75/37 29/76/37 69/77/37 378 | f 67/78/36 69/79/36 29/80/36 379 | f 30/72/36 67/78/36 29/80/36 380 | f 5/7/38 2/81/38 4/8/38 381 | f 30/72/36 1/74/36 67/78/36 382 | f 68/75/36 30/82/36 29/76/36 383 | f 30/72/36 68/83/36 62/73/36 384 | f 25/12/39 26/67/39 67/62/39 385 | f 26/69/40 25/84/40 22/70/40 386 | f 23/59/33 24/68/33 67/62/33 387 | f 23/64/33 28/85/33 27/65/33 388 | f 28/61/33 3/58/33 67/62/33 389 | f 3/58/33 28/61/33 23/59/33 390 | f 9/16/41 13/22/41 64/42/41 391 | f 11/34/42 10/86/42 15/41/42 392 | f 65/31/43 15/41/43 16/40/43 393 | f 14/37/44 13/21/44 12/38/44 394 | f 12/27/45 7/23/45 15/25/45 395 | f 7/23/46 4/6/46 16/24/46 396 | f 7/14/47 12/38/47 13/21/47 397 | f 9/19/7 67/62/7 21/20/7 398 | f 22/11/7 5/10/7 9/19/7 399 | f 9/16/48 5/87/48 6/17/48 400 | f 25/12/49 3/58/49 2/88/49 401 | f 6/13/50 4/89/50 7/14/50 402 | f 2/88/51 5/10/51 25/12/51 403 | f 31/90/52 61/3/52 62/2/52 404 | f 32/91/53 34/92/53 33/93/53 405 | f 35/94/54 36/95/54 34/96/54 406 | f 35/97/55 55/98/55 52/99/55 407 | f 36/100/56 38/101/56 37/102/56 408 | f 39/103/57 38/104/57 36/105/57 409 | f 39/106/58 52/99/58 51/107/58 410 | f 37/102/59 38/101/59 43/108/59 411 | f 38/104/60 39/103/60 43/109/60 412 | f 37/110/61 45/111/61 46/112/61 413 | f 41/113/62 40/114/62 42/115/62 414 | f 31/116/63 65/31/63 61/30/63 415 | f 63/32/64 65/31/64 44/117/64 416 | f 44/117/65 65/31/65 41/118/65 417 | f 34/119/66 65/31/66 33/120/66 418 | f 42/115/67 40/114/67 45/111/67 419 | f 33/120/68 65/31/68 31/116/68 420 | f 44/121/69 41/122/69 42/123/69 421 | f 65/31/70 34/119/70 46/124/70 422 | f 41/118/71 65/31/71 45/125/71 423 | f 64/42/72 66/44/72 47/126/72 424 | f 39/103/73 67/45/73 64/42/73 425 | f 47/126/74 43/109/74 64/42/74 426 | f 47/126/75 44/127/75 43/109/75 427 | f 49/128/76 66/49/76 50/129/76 428 | f 66/49/77 49/128/77 47/130/77 429 | f 47/131/78 49/132/78 48/133/78 430 | f 44/127/79 48/134/79 63/54/79 431 | f 44/127/80 47/126/80 48/134/80 432 | f 49/132/81 50/129/81 48/133/81 433 | f 48/133/82 50/129/82 63/56/82 434 | f 50/129/83 66/49/83 63/57/83 435 | f 33/135/84 31/136/84 53/137/84 436 | f 58/138/84 57/139/84 67/62/84 437 | f 53/140/84 54/141/84 57/142/84 438 | f 51/107/85 56/143/85 67/62/85 439 | f 67/62/84 57/139/84 54/144/84 440 | f 53/137/84 31/136/84 67/62/84 441 | f 56/145/85 51/146/85 52/147/85 442 | f 55/98/86 33/135/86 67/62/86 443 | f 60/148/87 31/149/87 62/73/87 444 | f 68/75/88 69/77/88 59/150/88 445 | f 67/78/87 59/151/87 69/79/87 446 | f 60/148/87 59/151/87 67/78/87 447 | f 35/94/89 34/96/89 32/152/89 448 | f 60/148/87 67/78/87 31/149/87 449 | f 68/75/87 59/150/87 60/153/87 450 | f 60/148/87 62/73/87 68/83/87 451 | f 55/98/90 67/62/90 56/143/90 452 | f 56/145/91 52/147/91 55/154/91 453 | f 53/137/84 67/62/84 54/144/84 454 | f 53/140/84 57/142/84 58/155/84 455 | f 58/138/84 67/62/84 33/135/84 456 | f 33/135/84 53/137/84 58/138/84 457 | f 39/103/92 64/42/92 43/109/92 458 | f 41/118/42 45/125/42 40/156/42 459 | f 65/31/93 46/124/93 45/125/93 460 | f 44/121/94 42/123/94 43/108/94 461 | f 42/115/95 45/111/95 37/110/95 462 | f 37/110/96 46/112/96 34/92/96 463 | f 37/102/97 43/108/97 42/123/97 464 | f 39/106/58 51/107/58 67/62/58 465 | f 52/99/58 39/106/58 35/97/58 466 | f 39/103/98 36/105/98 35/157/98 467 | f 55/98/99 32/158/99 33/135/99 468 | f 36/100/100 37/102/100 34/159/100 469 | f 32/158/101 55/98/101 35/97/101 470 | -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/f22/f22.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.79 (sub 0) OBJ File: 'fighters_0.blend' 2 | # www.blender.org 3 | mtllib f-22.mtl 4 | o F-22 5 | v 0.885739 0.001910 -0.380334 6 | v 0.322026 0.035470 -0.433102 7 | v -0.907053 0.017201 -0.352922 8 | v 1.047128 0.048117 -0.183264 9 | v 0.362277 0.114239 -0.142520 10 | v 1.125941 0.131116 -0.101088 11 | v 1.199510 0.242528 -0.056765 12 | v 1.529867 0.098373 -0.067674 13 | v 1.566378 0.011323 -0.146393 14 | v 1.762624 0.050007 -0.049581 15 | v 1.789931 -0.007335 -0.103633 16 | v -1.226703 0.083896 -0.231401 17 | v -1.226703 0.083896 -0.043034 18 | v -1.304689 0.083896 -0.142520 19 | v -0.560223 0.071221 -0.299735 20 | v 0.779234 -0.185506 -0.231401 21 | v -0.408812 -0.168000 -0.231401 22 | v -1.226703 -0.023036 -0.043034 23 | v -1.226703 -0.023036 -0.231464 24 | v -1.304556 -0.023036 -0.142520 25 | v -0.442164 -0.009523 -0.406435 26 | v -0.875984 0.035470 -1.161559 27 | v -0.727243 0.035470 -1.316844 28 | v -0.494141 0.035470 -1.316844 29 | v -1.376274 0.035470 -0.874979 30 | v -1.417826 0.035470 -0.043034 31 | v 1.016889 0.247035 -0.041089 32 | v -0.910201 0.071705 -0.260759 33 | v -0.910201 0.114239 -0.142520 34 | v -1.347072 0.053931 -0.291132 35 | v -0.949411 0.058716 -0.324121 36 | v -1.086356 0.618995 -0.582384 37 | v -0.945513 0.618995 -0.582384 38 | v -0.820765 0.618995 -0.582384 39 | v -1.074394 0.035470 -0.526090 40 | v -1.347072 0.017010 -0.291132 41 | v -1.420792 0.035470 -0.231401 42 | v -1.704863 0.035470 -0.554642 43 | v -1.607890 0.035470 -0.874979 44 | v 1.142508 0.156204 -0.091107 45 | v 0.821684 0.218761 -0.028011 46 | v 1.453805 0.131564 -0.065163 47 | v 0.835981 0.180467 -0.042103 48 | v 0.885739 0.001910 0.380334 49 | v 0.322026 0.035470 0.433102 50 | v -0.907053 0.017201 0.352922 51 | v 1.047128 0.048117 0.183264 52 | v 0.362277 0.114239 0.142520 53 | v 1.125941 0.131116 0.101088 54 | v 1.199510 0.242528 0.056765 55 | v 1.529867 0.098373 0.067674 56 | v 1.566378 0.011323 0.146393 57 | v 1.762624 0.050007 0.049581 58 | v 1.789931 -0.007335 0.103633 59 | v -1.226703 0.083896 0.231401 60 | v -1.226703 0.083896 0.043034 61 | v -1.304689 0.083896 0.142520 62 | v -0.560223 0.071221 0.299735 63 | v 0.779234 -0.185506 0.231401 64 | v -0.408812 -0.168000 0.231401 65 | v -1.226703 -0.023036 0.043034 66 | v -1.226703 -0.023036 0.231464 67 | v -1.304556 -0.023036 0.142520 68 | v -0.442164 -0.009523 0.406435 69 | v -0.875984 0.035470 1.161559 70 | v -0.727243 0.035470 1.316844 71 | v -0.494141 0.035470 1.316844 72 | v -1.376274 0.035470 0.874979 73 | v -1.417826 0.035470 0.043034 74 | v 1.016889 0.247035 0.041089 75 | v -0.910201 0.071705 0.260759 76 | v -0.910201 0.114239 0.142520 77 | v -1.347072 0.053931 0.291132 78 | v -0.949411 0.058716 0.324121 79 | v -1.086356 0.618995 0.582384 80 | v -0.945513 0.618995 0.582384 81 | v -0.820765 0.618995 0.582384 82 | v -1.074394 0.035470 0.526090 83 | v -1.347072 0.017010 0.291132 84 | v -1.420792 0.035470 0.231401 85 | v -1.704863 0.035470 0.554642 86 | v -1.607890 0.035470 0.874979 87 | v 1.142508 0.156204 0.091107 88 | v 0.821684 0.218761 0.028011 89 | v 1.453805 0.131564 0.065163 90 | v 0.835981 0.180467 0.042103 91 | v -0.910201 0.094558 0.000000 92 | v 0.403590 0.158204 0.000000 93 | v 1.236021 0.268974 0.000000 94 | v 1.568094 0.113851 0.000000 95 | v 1.791648 0.062518 0.000000 96 | v 2.014232 -0.030625 0.000000 97 | v 1.566378 -0.120168 0.000000 98 | v 1.789931 -0.082458 0.000000 99 | v 0.919486 -0.169810 0.000000 100 | v -0.408812 -0.167592 0.000000 101 | v -1.226703 0.078200 0.000000 102 | v -1.226703 -0.023036 0.000000 103 | v -1.442453 0.035470 0.000000 104 | v 1.016889 0.266178 0.000000 105 | v 1.487051 0.151710 0.000000 106 | v 0.817113 0.231006 0.000000 107 | vt 0.302940 0.699066 108 | vt 0.442081 0.762279 109 | vt 0.260042 0.751449 110 | vt 0.780315 0.800162 111 | vt 0.431099 0.800162 112 | vt 0.239093 0.773292 113 | vt 0.316239 0.628907 114 | vt 0.320312 0.648438 115 | vt 0.269531 0.648438 116 | vt 0.268080 0.668033 117 | vt 0.209833 0.668033 118 | vt 0.152344 0.648438 119 | vt 0.218750 0.648438 120 | vt 0.234686 0.622186 121 | vt 0.122021 0.761249 122 | vt 0.069858 0.786983 123 | vt 0.062599 0.772615 124 | vt 0.121565 0.800162 125 | vt 0.131726 0.782173 126 | vt 0.002978 0.800162 127 | vt 0.062143 0.800162 128 | vt 0.687288 0.720490 129 | vt 0.780315 0.762279 130 | vt 0.117476 0.888175 131 | vt 0.176898 0.858183 132 | vt 0.176898 0.893135 133 | vt 0.057855 0.881984 134 | vt 0.117476 0.868207 135 | vt 0.314919 0.902915 136 | vt 0.348847 0.844988 137 | vt 0.350939 0.448650 138 | vt 0.338144 0.510748 139 | vt 0.289431 0.452822 140 | vt 0.329981 0.132766 141 | vt 0.645773 0.071258 142 | vt 0.645773 0.132766 143 | vt 0.452780 0.685040 144 | vt 0.669723 0.450134 145 | vt 0.731683 0.450134 146 | vt 0.771220 0.491410 147 | vt 0.823959 0.660323 148 | vt 0.507656 0.899553 149 | vt 0.357817 0.890633 150 | vt 0.386127 0.840816 151 | vt 0.701919 0.845469 152 | vt 0.896439 0.722777 153 | vt 0.916034 0.738294 154 | vt 0.864444 0.738294 155 | vt 0.445843 0.497454 156 | vt 0.469468 0.469031 157 | vt 0.469468 0.497454 158 | vt 0.495912 0.497454 159 | vt 0.654638 0.182832 160 | vt 0.668454 0.424826 161 | vt 0.451511 0.189920 162 | vt 0.654638 0.179292 163 | vt 0.778209 0.165068 164 | vt 0.822690 0.211097 165 | vt 0.864444 0.469031 166 | vt 0.864444 0.497454 167 | vt 0.916034 0.484582 168 | vt 0.863174 0.082019 169 | vt 0.863174 0.071258 170 | vt 0.913977 0.082019 171 | vt 0.864444 0.800162 172 | vt 0.864444 0.789083 173 | vt 0.809306 0.497454 174 | vt 0.809306 0.469031 175 | vt 0.860108 0.484582 176 | vt 0.915246 0.789083 177 | vt 0.921792 0.800162 178 | vt 0.822690 0.214637 179 | vt 0.769951 0.383550 180 | vt 0.730414 0.424826 181 | vt 0.834355 0.894697 182 | vt 0.919320 0.884002 183 | vt 0.141653 0.276624 184 | vt 0.107458 0.425551 185 | vt 0.038204 0.279948 186 | vt 0.178054 0.425551 187 | vt 0.140617 0.425551 188 | vt 0.883868 0.109141 189 | vt 0.863174 0.132783 190 | vt 0.780315 0.730850 191 | vt 0.904201 0.567586 192 | vt 0.790737 0.714008 193 | vt 0.991542 0.652734 194 | vt 0.385137 0.280077 195 | vt 0.361364 0.425551 196 | vt 0.394523 0.425551 197 | vt 0.431960 0.425551 198 | vt 0.501261 0.275352 199 | vt 0.895170 0.148643 200 | vt 0.914765 0.132766 201 | vt 0.902932 0.303834 202 | vt 0.990273 0.218686 203 | vt 0.964497 0.303834 204 | vt 0.965766 0.567586 205 | vt 0.885173 0.762279 206 | vt 0.151941 0.583698 207 | vt 0.239090 0.583579 208 | vt 0.234686 0.590248 209 | vt 0.316239 0.596969 210 | vt 0.431096 0.590779 211 | vt 0.321182 0.614322 212 | vt 0.319967 0.606876 213 | vt 0.431099 0.614322 214 | vt 0.143107 0.601016 215 | vt 0.131726 0.583028 216 | vt 0.321182 0.668033 217 | vt 0.121565 0.601016 218 | vt 0.131723 0.574876 219 | vt 0.292110 0.279948 220 | vt 0.247354 0.275352 221 | vt 0.710784 0.887594 222 | vt 0.920523 0.071258 223 | vt 0.495912 0.469031 224 | vt 0.445826 0.469031 225 | vt 0.292701 0.071258 226 | vt 0.390527 0.498466 227 | vt 0.143107 0.668033 228 | vt 0.302940 0.699066 229 | vt 0.260042 0.751449 230 | vt 0.442081 0.762279 231 | vt 0.239093 0.773292 232 | vt 0.316239 0.628907 233 | vt 0.269531 0.648438 234 | vt 0.320312 0.648438 235 | vt 0.218750 0.648438 236 | vt 0.152344 0.648438 237 | vt 0.234686 0.622186 238 | vt 0.122021 0.761249 239 | vt 0.062599 0.772615 240 | vt 0.069858 0.786983 241 | vt 0.131726 0.782173 242 | vt 0.687288 0.720490 243 | vt 0.780315 0.762279 244 | vt 0.117476 0.888175 245 | vt 0.176898 0.893135 246 | vt 0.314919 0.902915 247 | vt 0.350939 0.448650 248 | vt 0.338144 0.510748 249 | vt 0.329981 0.132766 250 | vt 0.645773 0.132766 251 | vt 0.452780 0.685040 252 | vt 0.731683 0.450134 253 | vt 0.669723 0.450134 254 | vt 0.823959 0.660323 255 | vt 0.771220 0.491410 256 | vt 0.507656 0.899553 257 | vt 0.386127 0.840816 258 | vt 0.357817 0.890633 259 | vt 0.701919 0.845469 260 | vt 0.896439 0.722777 261 | vt 0.864444 0.738294 262 | vt 0.916034 0.738294 263 | vt 0.445843 0.497454 264 | vt 0.469468 0.497454 265 | vt 0.469468 0.469031 266 | vt 0.495912 0.497454 267 | vt 0.654638 0.182832 268 | vt 0.451511 0.189920 269 | vt 0.668454 0.424826 270 | vt 0.654638 0.179292 271 | vt 0.822690 0.211097 272 | vt 0.778209 0.165068 273 | vt 0.864444 0.469031 274 | vt 0.916034 0.484582 275 | vt 0.864444 0.497454 276 | vt 0.863174 0.082019 277 | vt 0.913977 0.082019 278 | vt 0.864444 0.789083 279 | vt 0.809306 0.497454 280 | vt 0.860108 0.484582 281 | vt 0.809306 0.469031 282 | vt 0.915246 0.789083 283 | vt 0.822690 0.214637 284 | vt 0.769951 0.383550 285 | vt 0.730414 0.424826 286 | vt 0.834355 0.894697 287 | vt 0.919320 0.884002 288 | vt 0.141653 0.276624 289 | vt 0.038204 0.279948 290 | vt 0.107458 0.425551 291 | vt 0.140617 0.425551 292 | vt 0.178054 0.425551 293 | vt 0.883868 0.109141 294 | vt 0.863174 0.132783 295 | vt 0.780315 0.730850 296 | vt 0.904201 0.567586 297 | vt 0.790737 0.714008 298 | vt 0.991542 0.652734 299 | vt 0.385137 0.280077 300 | vt 0.394523 0.425551 301 | vt 0.361364 0.425551 302 | vt 0.501261 0.275352 303 | vt 0.431960 0.425551 304 | vt 0.895170 0.148643 305 | vt 0.914765 0.132766 306 | vt 0.902932 0.303834 307 | vt 0.964497 0.303834 308 | vt 0.990273 0.218686 309 | vt 0.965766 0.567586 310 | vt 0.885173 0.762279 311 | vt 0.151941 0.583698 312 | vt 0.234686 0.590248 313 | vt 0.239090 0.583579 314 | vt 0.316239 0.596969 315 | vt 0.319967 0.606876 316 | vt 0.131726 0.583028 317 | vt 0.131723 0.574876 318 | vt 0.292110 0.279948 319 | vt 0.247354 0.275352 320 | vt 0.710784 0.887594 321 | vt 0.495912 0.469031 322 | vt 0.445826 0.469031 323 | vt 0.390527 0.498466 324 | vn 0.0752 0.9554 -0.2856 325 | vn -0.0464 0.9582 -0.2821 326 | vn -0.0672 0.7335 -0.6764 327 | vn -0.1079 0.3076 -0.9454 328 | vn -0.1576 0.8951 -0.4170 329 | vn 0.3158 0.7660 -0.5600 330 | vn 0.1032 0.3081 -0.9457 331 | vn 0.0996 0.6507 -0.7527 332 | vn 0.1879 0.7196 -0.6685 333 | vn 0.2166 0.9180 -0.3323 334 | vn 0.3493 0.7254 -0.5931 335 | vn 0.3509 0.8385 -0.4170 336 | vn 0.0000 0.9645 -0.2639 337 | vn 0.0656 -0.7424 -0.6668 338 | vn 0.1839 -0.7958 -0.5769 339 | vn 0.0506 -0.6600 -0.7496 340 | vn 0.6508 -0.6744 -0.3487 341 | vn -0.0147 -0.9999 0.0018 342 | vn -0.0004 0.9652 -0.2616 343 | vn 0.0000 0.9994 -0.0351 344 | vn -0.0610 0.9980 -0.0190 345 | vn 0.0346 -0.6338 -0.7728 346 | vn -0.0101 -0.6854 -0.7281 347 | vn -0.2421 0.9703 0.0011 348 | vn -0.7517 -0.0009 -0.6595 349 | vn -0.7870 -0.0010 0.6169 350 | vn 0.0569 -0.9970 -0.0525 351 | vn -0.0625 -0.9970 -0.0448 352 | vn 0.0001 -0.0006 1.0000 353 | vn -0.0313 0.9991 -0.0300 354 | vn -0.1740 -0.9847 0.0000 355 | vn -0.2927 -0.9562 0.0000 356 | vn -0.0512 0.9901 0.1310 357 | vn 0.0000 0.0000 -1.0000 358 | vn -0.1931 0.9749 -0.1105 359 | vn -0.0670 -0.9975 -0.0209 360 | vn -0.0388 -0.9986 -0.0372 361 | vn -0.1372 -0.7744 -0.6176 362 | vn 0.0702 -0.4308 -0.8997 363 | vn 0.0000 -0.4186 -0.9082 364 | vn -0.1725 -0.9734 -0.1510 365 | vn -0.1596 -0.9872 0.0017 366 | vn -0.1795 -0.9737 0.1405 367 | vn 0.0048 0.9410 -0.3385 368 | vn 0.0000 0.9906 0.1368 369 | vn 0.0387 0.9987 -0.0335 370 | vn -0.0258 0.9948 -0.0985 371 | vn -0.0061 0.8987 -0.4386 372 | vn -0.0204 0.9946 -0.1018 373 | vn -0.1436 0.9816 0.1262 374 | vn 0.0000 0.5067 0.8621 375 | vn -0.0803 0.4857 0.8704 376 | vn -0.2881 -0.9557 -0.0602 377 | vn 0.0387 -0.9987 -0.0335 378 | vn -0.0421 -0.9990 -0.0128 379 | vn -0.0126 -0.9956 -0.0929 380 | vn -0.0646 -0.8840 -0.4629 381 | vn -0.0000 0.9995 -0.0316 382 | vn 0.0000 -0.9995 -0.0316 383 | vn -0.0766 0.9953 0.0600 384 | vn -0.0765 0.9948 -0.0671 385 | vn -0.0519 0.9893 0.1366 386 | vn -0.1094 0.4290 -0.8967 387 | vn 0.3481 0.8307 -0.4344 388 | vn -0.0681 0.4073 -0.9107 389 | vn -0.1215 0.3445 -0.9309 390 | vn -0.0116 0.9064 -0.4223 391 | vn -0.0132 0.9096 -0.4153 392 | vn 0.3865 0.8274 -0.4075 393 | vn -0.1436 -0.9816 0.1262 394 | vn 0.0970 0.4924 0.8649 395 | vn -0.0421 0.9990 -0.0128 396 | vn -0.0694 -0.4316 -0.8994 397 | vn -0.1182 -0.7472 -0.6540 398 | vn -0.2436 0.9615 0.1273 399 | vn -0.2588 -0.9545 -0.1481 400 | vn 0.0000 -0.9988 -0.0494 401 | vn -0.7875 0.0000 0.6163 402 | vn -0.7525 0.0004 -0.6586 403 | vn 0.0200 -0.7392 -0.6731 404 | vn 0.0355 0.9988 -0.0328 405 | vn -0.0017 -0.9976 0.0687 406 | vn 0.6468 -0.6647 -0.3739 407 | vn -0.0053 -0.7439 -0.6682 408 | vn 0.1353 -0.8022 -0.5815 409 | vn -0.0364 0.9403 -0.3383 410 | vn 0.2094 0.9120 -0.3527 411 | vn 0.1990 0.7019 -0.6839 412 | vn 0.1150 0.6924 -0.7123 413 | vn 0.3792 0.8117 -0.4444 414 | vn 0.0728 0.9469 -0.3132 415 | vn 0.0825 0.9589 -0.2714 416 | vn 0.0752 0.9554 0.2856 417 | vn -0.0464 0.9582 0.2821 418 | vn -0.0672 0.7335 0.6764 419 | vn -0.1079 0.3076 0.9454 420 | vn -0.1576 0.8951 0.4170 421 | vn 0.3158 0.7660 0.5600 422 | vn 0.1032 0.3081 0.9457 423 | vn 0.0996 0.6507 0.7527 424 | vn 0.1879 0.7196 0.6685 425 | vn 0.2166 0.9180 0.3323 426 | vn 0.3493 0.7254 0.5931 427 | vn 0.3509 0.8385 0.4170 428 | vn 0.0000 0.9645 0.2639 429 | vn 0.0656 -0.7424 0.6668 430 | vn 0.1839 -0.7958 0.5769 431 | vn 0.0506 -0.6600 0.7496 432 | vn 0.6508 -0.6744 0.3487 433 | vn -0.0147 -0.9999 -0.0018 434 | vn -0.0004 0.9652 0.2616 435 | vn 0.0000 0.9994 0.0351 436 | vn -0.0610 0.9980 0.0190 437 | vn 0.0346 -0.6338 0.7728 438 | vn -0.0101 -0.6854 0.7281 439 | vn -0.2421 0.9703 -0.0011 440 | vn -0.7517 -0.0009 0.6595 441 | vn -0.7870 -0.0010 -0.6169 442 | vn 0.0569 -0.9970 0.0525 443 | vn -0.0625 -0.9970 0.0448 444 | vn 0.0001 -0.0006 -1.0000 445 | vn -0.0313 0.9991 0.0300 446 | vn -0.0512 0.9901 -0.1310 447 | vn 0.0000 0.0000 1.0000 448 | vn -0.1931 0.9749 0.1105 449 | vn -0.0670 -0.9975 0.0209 450 | vn -0.0388 -0.9986 0.0372 451 | vn -0.1372 -0.7744 0.6176 452 | vn 0.0702 -0.4308 0.8997 453 | vn 0.0000 -0.4186 0.9082 454 | vn -0.1725 -0.9734 0.1510 455 | vn -0.1596 -0.9872 -0.0017 456 | vn -0.1795 -0.9737 -0.1405 457 | vn 0.0048 0.9410 0.3385 458 | vn 0.0000 0.9906 -0.1368 459 | vn 0.0387 0.9987 0.0335 460 | vn -0.0258 0.9948 0.0985 461 | vn -0.0061 0.8987 0.4386 462 | vn -0.0204 0.9946 0.1018 463 | vn -0.1436 0.9816 -0.1262 464 | vn 0.0000 0.5067 -0.8621 465 | vn -0.0803 0.4857 -0.8704 466 | vn -0.2881 -0.9557 0.0602 467 | vn 0.0387 -0.9987 0.0335 468 | vn -0.0421 -0.9990 0.0128 469 | vn -0.0126 -0.9956 0.0929 470 | vn -0.0646 -0.8840 0.4629 471 | vn -0.0000 0.9995 0.0316 472 | vn 0.0000 -0.9995 0.0316 473 | vn -0.0766 0.9953 -0.0600 474 | vn -0.0765 0.9948 0.0671 475 | vn -0.0519 0.9893 -0.1366 476 | vn -0.1094 0.4290 0.8967 477 | vn 0.3481 0.8307 0.4344 478 | vn -0.0681 0.4073 0.9107 479 | vn -0.1215 0.3445 0.9309 480 | vn -0.0116 0.9064 0.4223 481 | vn -0.0132 0.9096 0.4153 482 | vn 0.3865 0.8274 0.4075 483 | vn -0.1436 -0.9816 -0.1262 484 | vn 0.0970 0.4924 -0.8649 485 | vn -0.0421 0.9990 0.0128 486 | vn -0.0694 -0.4316 0.8994 487 | vn -0.1182 -0.7472 0.6540 488 | vn -0.2436 0.9615 -0.1273 489 | vn -0.2588 -0.9545 0.1481 490 | vn 0.0000 -0.9988 0.0494 491 | vn -0.7875 0.0000 -0.6163 492 | vn -0.7525 0.0004 0.6586 493 | vn 0.0200 -0.7392 0.6731 494 | vn 0.0355 0.9988 0.0328 495 | vn -0.0017 -0.9976 -0.0687 496 | vn 0.6468 -0.6647 0.3739 497 | vn -0.0053 -0.7439 0.6682 498 | vn 0.1353 -0.8022 0.5815 499 | vn -0.0364 0.9403 0.3383 500 | vn 0.2094 0.9120 0.3527 501 | vn 0.1990 0.7019 0.6839 502 | vn 0.1150 0.6924 0.7123 503 | vn 0.3792 0.8117 0.4444 504 | vn 0.0728 0.9469 0.3132 505 | vn 0.0825 0.9589 0.2714 506 | usemtl Material.001 507 | s off 508 | f 1/1/1 5/2/1 4/3/1 509 | f 5/2/2 87/4/2 88/5/2 510 | f 4/3/3 88/5/3 6/6/3 511 | f 43/7/4 41/8/4 27/9/4 512 | f 41/8/5 100/10/5 27/9/5 513 | f 89/11/6 42/12/6 7/13/6 514 | f 7/13/7 42/12/7 40/14/7 515 | f 6/6/8 9/15/8 4/3/8 516 | f 9/15/9 10/16/9 11/17/9 517 | f 90/18/10 10/16/10 8/19/10 518 | f 11/17/11 10/16/11 92/20/11 519 | f 10/16/12 91/21/12 92/20/12 520 | f 15/22/13 29/23/13 5/2/13 521 | f 11/24/14 93/25/14 9/26/14 522 | f 11/24/15 92/27/15 94/28/15 523 | f 4/29/16 93/25/16 95/30/16 524 | f 16/31/17 4/32/17 95/33/17 525 | f 16/34/18 96/35/18 17/36/18 526 | f 5/2/19 2/37/19 15/22/19 527 | f 15/22/20 24/38/20 23/39/20 528 | f 15/22/21 22/40/21 35/41/21 529 | f 2/42/22 1/43/22 16/44/22 530 | f 17/45/23 2/42/23 16/44/23 531 | f 30/46/24 37/47/24 12/48/24 532 | f 12/49/25 20/50/25 14/51/25 533 | f 20/50/26 13/52/26 14/51/26 534 | f 21/53/27 24/54/27 2/55/27 535 | f 21/56/28 3/57/28 35/58/28 536 | f 19/59/29 12/60/29 37/61/29 537 | f 15/22/30 23/39/30 22/40/30 538 | f 18/62/31 96/35/31 98/63/31 539 | f 98/63/32 26/64/32 18/62/32 540 | f 97/65/33 87/4/33 13/66/33 541 | f 13/67/34 18/68/34 26/69/34 542 | f 97/65/35 26/70/35 99/71/35 543 | f 35/72/36 22/73/36 21/53/36 544 | f 22/73/37 23/74/37 21/53/37 545 | f 3/75/38 17/45/38 19/76/38 546 | f 31/77/39 34/78/39 15/79/39 547 | f 31/77/40 32/80/40 33/81/40 548 | f 20/82/41 19/83/41 17/36/41 549 | f 20/82/42 17/36/42 96/35/42 550 | f 18/62/43 20/82/43 96/35/43 551 | f 29/23/44 28/84/44 12/48/44 552 | f 87/4/45 5/2/45 29/23/45 553 | f 25/85/46 30/46/46 35/41/46 554 | f 31/86/47 15/22/47 35/41/47 555 | f 30/46/48 12/48/48 28/84/48 556 | f 30/46/49 31/86/49 35/41/49 557 | f 30/46/50 38/87/50 37/47/50 558 | f 28/88/51 34/89/51 33/90/51 559 | f 28/88/52 32/91/52 30/92/52 560 | f 36/93/53 19/83/53 37/94/53 561 | f 36/93/54 25/95/54 35/58/54 562 | f 36/93/55 38/96/55 39/97/55 563 | f 36/93/56 35/58/56 3/57/56 564 | f 3/57/57 19/83/57 36/93/57 565 | f 25/85/58 39/98/58 30/46/58 566 | f 36/93/59 39/97/59 25/95/59 567 | f 29/23/60 14/99/60 13/66/60 568 | f 14/99/61 29/23/61 12/48/61 569 | f 87/4/62 29/23/62 13/66/62 570 | f 42/100/7 6/101/7 40/102/7 571 | f 43/103/63 6/101/63 88/104/63 572 | f 102/105/5 41/106/5 88/107/5 573 | f 101/108/64 8/109/64 42/100/64 574 | f 27/9/65 7/13/65 40/14/65 575 | f 40/14/66 43/7/66 27/9/66 576 | f 41/8/5 102/110/5 100/10/5 577 | f 100/10/67 89/11/67 27/9/67 578 | f 89/11/68 7/13/68 27/9/68 579 | f 101/108/69 90/111/69 8/109/69 580 | f 43/103/63 40/102/63 6/101/63 581 | f 88/104/4 41/106/4 43/103/4 582 | f 42/100/7 8/112/7 6/101/7 583 | f 36/93/70 37/94/70 38/96/70 584 | f 28/88/51 33/90/51 32/91/51 585 | f 28/88/71 15/113/71 34/89/71 586 | f 30/46/72 39/98/72 38/87/72 587 | f 31/77/73 30/114/73 32/80/73 588 | f 31/77/40 33/81/40 34/78/40 589 | f 3/75/74 21/115/74 17/45/74 590 | f 97/65/75 13/66/75 26/70/75 591 | f 98/63/76 99/116/76 26/64/76 592 | f 21/53/77 23/74/77 24/54/77 593 | f 20/50/78 18/117/78 13/52/78 594 | f 12/49/79 19/118/79 20/50/79 595 | f 17/45/80 21/115/80 2/42/80 596 | f 15/22/81 2/37/81 24/38/81 597 | f 16/34/82 95/119/82 96/35/82 598 | f 16/31/83 1/120/83 4/32/83 599 | f 4/29/84 9/26/84 93/25/84 600 | f 11/24/85 94/28/85 93/25/85 601 | f 15/22/86 28/84/86 29/23/86 602 | f 90/18/87 91/21/87 10/16/87 603 | f 9/15/88 8/19/88 10/16/88 604 | f 6/6/89 8/19/89 9/15/89 605 | f 89/11/90 101/121/90 42/12/90 606 | f 4/3/91 5/2/91 88/5/91 607 | f 1/1/92 2/37/92 5/2/92 608 | f 44/122/93 47/123/93 48/124/93 609 | f 48/124/94 88/5/94 87/4/94 610 | f 47/123/95 49/125/95 88/5/95 611 | f 86/126/96 70/127/96 84/128/96 612 | f 84/128/97 70/127/97 100/10/97 613 | f 89/11/98 50/129/98 85/130/98 614 | f 50/129/99 83/131/99 85/130/99 615 | f 49/125/100 47/123/100 52/132/100 616 | f 52/132/101 54/133/101 53/134/101 617 | f 90/18/102 51/135/102 53/134/102 618 | f 54/133/103 92/20/103 53/134/103 619 | f 53/134/104 92/20/104 91/21/104 620 | f 58/136/105 48/124/105 72/137/105 621 | f 54/138/106 52/139/106 93/25/106 622 | f 54/138/107 94/28/107 92/27/107 623 | f 47/140/108 95/30/108 93/25/108 624 | f 59/141/109 95/33/109 47/142/109 625 | f 59/143/110 60/144/110 96/35/110 626 | f 48/124/111 58/136/111 45/145/111 627 | f 58/136/112 66/146/112 67/147/112 628 | f 58/136/113 78/148/113 65/149/113 629 | f 45/150/114 59/151/114 44/152/114 630 | f 60/153/115 59/151/115 45/150/115 631 | f 73/154/116 55/155/116 80/156/116 632 | f 55/157/117 57/158/117 63/159/117 633 | f 63/159/118 57/158/118 56/160/118 634 | f 64/161/119 45/162/119 67/163/119 635 | f 64/164/120 78/165/120 46/166/120 636 | f 62/167/121 80/168/121 55/169/121 637 | f 58/136/122 65/149/122 66/146/122 638 | f 61/170/31 98/63/31 96/35/31 639 | f 98/63/32 61/170/32 69/171/32 640 | f 97/65/123 56/172/123 87/4/123 641 | f 56/173/124 69/174/124 61/175/124 642 | f 97/65/125 99/71/125 69/176/125 643 | f 78/177/126 64/161/126 65/178/126 644 | f 65/178/127 64/161/127 66/179/127 645 | f 46/180/128 62/181/128 60/153/128 646 | f 74/182/129 58/183/129 77/184/129 647 | f 74/182/130 76/185/130 75/186/130 648 | f 63/187/131 60/144/131 62/188/131 649 | f 63/187/132 96/35/132 60/144/132 650 | f 61/170/133 96/35/133 63/187/133 651 | f 72/137/134 55/155/134 71/189/134 652 | f 87/4/135 72/137/135 48/124/135 653 | f 68/190/136 78/148/136 73/154/136 654 | f 74/191/137 78/148/137 58/136/137 655 | f 73/154/138 71/189/138 55/155/138 656 | f 73/154/139 78/148/139 74/191/139 657 | f 73/154/140 80/156/140 81/192/140 658 | f 71/193/141 76/194/141 77/195/141 659 | f 71/193/142 73/196/142 75/197/142 660 | f 79/198/143 80/199/143 62/188/143 661 | f 79/198/144 78/165/144 68/200/144 662 | f 79/198/145 82/201/145 81/202/145 663 | f 79/198/146 46/166/146 78/165/146 664 | f 46/166/147 79/198/147 62/188/147 665 | f 68/190/148 73/154/148 82/203/148 666 | f 79/198/149 68/200/149 82/201/149 667 | f 72/137/150 56/172/150 57/204/150 668 | f 57/204/151 55/155/151 72/137/151 669 | f 87/4/152 56/172/152 72/137/152 670 | f 85/205/99 83/206/99 49/207/99 671 | f 86/208/153 88/104/153 49/207/153 672 | f 102/105/97 88/107/97 84/209/97 673 | f 101/108/154 85/205/154 51/210/154 674 | f 70/127/155 83/131/155 50/129/155 675 | f 83/131/156 70/127/156 86/126/156 676 | f 84/128/97 100/10/97 102/110/97 677 | f 100/10/157 70/127/157 89/11/157 678 | f 89/11/158 70/127/158 50/129/158 679 | f 101/108/159 51/210/159 90/111/159 680 | f 86/208/153 49/207/153 83/206/153 681 | f 88/104/96 86/208/96 84/209/96 682 | f 85/205/99 49/207/99 51/211/99 683 | f 79/198/160 81/202/160 80/199/160 684 | f 71/193/141 75/197/141 76/194/141 685 | f 71/193/161 77/195/161 58/212/161 686 | f 73/154/162 81/192/162 82/203/162 687 | f 74/182/163 75/186/163 73/213/163 688 | f 74/182/130 77/184/130 76/185/130 689 | f 46/180/164 60/153/164 64/214/164 690 | f 97/65/165 69/176/165 56/172/165 691 | f 98/63/166 69/171/166 99/116/166 692 | f 64/161/167 67/163/167 66/179/167 693 | f 63/159/168 56/160/168 61/215/168 694 | f 55/157/169 63/159/169 62/216/169 695 | f 60/153/170 45/150/170 64/214/170 696 | f 58/136/171 67/147/171 45/145/171 697 | f 59/143/172 96/35/172 95/119/172 698 | f 59/141/173 47/142/173 44/217/173 699 | f 47/140/174 93/25/174 52/139/174 700 | f 54/138/175 93/25/175 94/28/175 701 | f 58/136/176 72/137/176 71/189/176 702 | f 90/18/177 53/134/177 91/21/177 703 | f 52/132/178 53/134/178 51/135/178 704 | f 49/125/179 52/132/179 51/135/179 705 | f 89/11/180 85/130/180 101/121/180 706 | f 47/123/181 88/5/181 48/124/181 707 | f 44/122/182 48/124/182 45/145/182 708 | -------------------------------------------------------------------------------- /src/eleventy/public/assets/models/efa/efa.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.79 (sub 0) OBJ File: 'fighters_0.blend' 2 | # www.blender.org 3 | mtllib ef-2000.mtl 4 | o EF-2000 5 | v -0.637137 -0.058235 -1.212867 6 | v 1.408317 0.129410 -0.096909 7 | v -0.470713 0.060266 -0.225620 8 | v 0.697766 0.065191 -0.184658 9 | v -1.227013 0.048801 -0.208787 10 | v 0.806605 0.177697 -0.113658 11 | v -0.499514 0.146273 -0.124903 12 | v -1.227013 0.107477 -0.111575 13 | v -1.010604 -0.053182 -0.223190 14 | v 1.430488 0.019222 -0.116861 15 | v -0.470713 -0.026554 -0.239685 16 | v 0.627798 -0.180045 -0.213781 17 | v -0.470713 -0.139104 -0.225865 18 | v -0.470713 -0.087806 -0.239685 19 | v -1.181829 0.179699 -0.024453 20 | v 0.635645 -0.060923 -0.202913 21 | v 1.478628 -0.073220 -0.093295 22 | v 0.878495 -0.072815 -0.195265 23 | v 0.878495 -0.186329 -0.207954 24 | v -1.227013 -0.127982 -0.112402 25 | v 1.064405 0.042134 -0.156845 26 | v 1.260287 0.049293 -0.136943 27 | v 1.260287 0.017903 -0.135526 28 | v 1.097918 -0.013106 -0.445226 29 | v 0.989527 -0.013106 -0.445226 30 | v -0.963396 -0.027673 -1.243429 31 | v -0.599211 -0.058235 -1.243429 32 | v -1.001322 -0.058235 -1.243429 33 | v -0.637137 -0.027673 -1.243429 34 | v -0.963396 -0.058235 -1.212867 35 | v -0.637137 -0.088797 -1.243429 36 | v -0.963396 -0.088797 -1.243429 37 | v -0.963396 -0.058235 -1.273991 38 | v -0.637137 -0.058235 -1.273991 39 | v -1.227013 -0.072252 -0.208787 40 | v -1.405837 0.038651 -0.188607 41 | v -1.405837 0.086090 -0.106440 42 | v -1.405837 -0.103667 -0.106440 43 | v -1.405837 -0.056228 -0.024272 44 | v -1.405837 -0.056228 -0.188607 45 | v -1.405837 0.038651 -0.024272 46 | v -1.227013 0.107477 -0.022448 47 | v -1.359631 0.107477 -0.022448 48 | v -1.358574 0.179699 -0.022448 49 | v -0.507611 0.150293 -0.050429 50 | v 1.157646 0.273375 -0.071246 51 | v 1.006628 0.306839 -0.072911 52 | v -0.637137 -0.058235 1.212867 53 | v 1.408317 0.129410 0.096909 54 | v -0.470713 0.060266 0.225620 55 | v 0.697766 0.065191 0.184658 56 | v -1.227013 0.048801 0.208787 57 | v 0.806605 0.177697 0.113658 58 | v -0.499514 0.146273 0.124903 59 | v -1.227013 0.107477 0.111575 60 | v -1.010604 -0.053182 0.223190 61 | v 1.430488 0.019222 0.116861 62 | v -0.470713 -0.026554 0.239685 63 | v 0.627798 -0.180045 0.213781 64 | v -0.470713 -0.139104 0.225865 65 | v -0.470713 -0.087806 0.239685 66 | v -1.181829 0.179699 0.024453 67 | v 0.635645 -0.060923 0.202913 68 | v 1.478628 -0.073220 0.093295 69 | v 0.878495 -0.072815 0.195265 70 | v 0.878495 -0.186329 0.207954 71 | v -1.227013 -0.127982 0.112402 72 | v 1.064405 0.042134 0.156845 73 | v 1.260287 0.049293 0.136943 74 | v 1.260287 0.017903 0.135526 75 | v 1.097918 -0.013106 0.445226 76 | v 0.989527 -0.013106 0.445226 77 | v -0.963396 -0.027673 1.243429 78 | v -0.599211 -0.058235 1.243429 79 | v -1.001322 -0.058235 1.243429 80 | v -0.637137 -0.027673 1.243429 81 | v -0.963396 -0.058235 1.212867 82 | v -0.637137 -0.088797 1.243429 83 | v -0.963396 -0.088797 1.243429 84 | v -0.963396 -0.058235 1.273991 85 | v -0.637137 -0.058235 1.273991 86 | v -1.227013 -0.072252 0.208787 87 | v -1.405837 0.038651 0.188607 88 | v -1.405837 0.086090 0.106440 89 | v -1.405837 -0.103667 0.106440 90 | v -1.405837 -0.056228 0.024272 91 | v -1.405837 -0.056228 0.188607 92 | v -1.405837 0.038651 0.024272 93 | v -1.227013 0.107477 0.022448 94 | v -1.359631 0.107477 0.022448 95 | v -1.358574 0.179699 0.022448 96 | v -0.507611 0.150293 0.050429 97 | v 1.157646 0.273375 0.071246 98 | v 1.006628 0.306839 0.072911 99 | v 1.995160 -0.027199 0.000000 100 | v -0.579312 0.185891 0.000000 101 | v -1.359309 0.179699 0.000000 102 | v 1.555716 -0.089928 0.000000 103 | v 0.877271 -0.072852 0.000000 104 | v 0.627798 -0.221217 0.000000 105 | v -0.470713 -0.191763 0.000000 106 | v -1.426984 0.810342 0.000000 107 | v -1.655949 0.816006 0.000000 108 | v -1.570942 0.836300 0.000000 109 | v 0.878495 -0.075334 0.000000 110 | v 0.878495 -0.226005 0.000000 111 | v -1.227013 -0.062495 0.000000 112 | v -1.227013 0.048801 0.000000 113 | v -1.227013 0.092461 0.000000 114 | v -1.359631 0.092461 0.000000 115 | v 0.385472 0.241012 0.000000 116 | v 1.475018 0.145701 0.000000 117 | v 1.179164 0.297095 0.000000 118 | v 1.000319 0.334393 0.000000 119 | vt 0.375195 0.740672 120 | vt 0.768365 0.737287 121 | vt 0.770803 0.759569 122 | vt 0.394253 0.496202 123 | vt 0.501980 0.535095 124 | vt 0.314999 0.535077 125 | vt 0.193993 0.535077 126 | vt 0.263030 0.557061 127 | vt 0.173898 0.557061 128 | vt 0.502144 0.535095 129 | vt 0.316910 0.557061 130 | vt 0.194067 0.745714 131 | vt 0.173988 0.774886 132 | vt 0.017414 0.774886 133 | vt 0.768380 0.885180 134 | vt 0.375210 0.894639 135 | vt 0.759710 0.859290 136 | vt 0.194082 0.880104 137 | vt 0.017429 0.832961 138 | vt 0.187408 0.846935 139 | vt 0.922229 0.825140 140 | vt 0.759710 0.799275 141 | vt 0.987373 0.819399 142 | vt 0.017402 0.089990 143 | vt 0.149684 0.089990 144 | vt 0.172889 0.122500 145 | vt 0.353910 0.089990 146 | vt 0.426645 0.151071 147 | vt 0.426658 0.729094 148 | vt 0.809793 0.425076 149 | vt 0.759695 0.718025 150 | vt 0.922214 0.722990 151 | vt 0.908004 0.425076 152 | vt 0.172917 0.819108 153 | vt 0.429007 0.071038 154 | vt 0.759683 0.006685 155 | vt 0.759683 0.074675 156 | vt 0.783089 0.963505 157 | vt 0.845687 0.930093 158 | vt 0.841868 0.964263 159 | vt 0.987373 0.855839 160 | vt 0.238642 0.855987 161 | vt 0.297607 0.853832 162 | vt 0.429007 0.006685 163 | vt 0.353542 0.069284 164 | vt 0.353542 0.006685 165 | vt 0.809780 0.387409 166 | vt 0.426645 0.083391 167 | vt 0.759683 0.094460 168 | vt 0.545142 0.304303 169 | vt 0.634437 0.495844 170 | vt 0.491717 0.304303 171 | vt 0.608848 0.501953 172 | vt 0.565514 0.494140 173 | vt 0.922201 0.089495 174 | vt 0.907991 0.387409 175 | vt 0.269539 0.535077 176 | vt 0.194082 0.535077 177 | vt 0.429035 0.786951 178 | vt 0.353570 0.819230 179 | vt 0.353570 0.785060 180 | vt 0.302464 0.801759 181 | vt 0.302096 0.742980 182 | vt 0.375199 0.740678 183 | vt 0.426673 0.822810 184 | vt 0.759710 0.833155 185 | vt 0.759710 0.814717 186 | vt 0.187380 0.125167 187 | vt 0.238614 0.130786 188 | vt 0.287491 0.224012 189 | vt 0.297579 0.137203 190 | vt 0.320119 0.224012 191 | vt 0.238627 0.733663 192 | vt 0.287504 0.640863 193 | vt 0.320132 0.640863 194 | vt 0.187393 0.739708 195 | vt 0.919420 0.415876 196 | vt 0.908004 0.415876 197 | vt 0.809780 0.396609 198 | vt 0.809780 0.405809 199 | vt 0.798363 0.396609 200 | vt 0.919407 0.396609 201 | vt 0.907991 0.396609 202 | vt 0.809793 0.415876 203 | vt 0.798376 0.415876 204 | vt 0.809793 0.406676 205 | vt 0.908004 0.406676 206 | vt 0.907991 0.405809 207 | vt 0.987345 0.040521 208 | vt 0.987345 0.006685 209 | vt 0.987345 0.069535 210 | vt 0.034834 0.555807 211 | vt 0.096727 0.590650 212 | vt 0.036535 0.598064 213 | vt 0.097571 0.525905 214 | vt 0.037680 0.517820 215 | vt 0.103915 0.493972 216 | vt 0.045488 0.477967 217 | vt 0.102283 0.619496 218 | vt 0.057775 0.676010 219 | vt 0.043652 0.634717 220 | vt 0.057675 0.442660 221 | vt 0.755353 0.951618 222 | vt 0.748595 0.969280 223 | vt 0.721766 0.969280 224 | vt 0.924062 0.923877 225 | vt 0.923747 0.950235 226 | vt 0.897195 0.965302 227 | vt 0.505318 0.275807 228 | vt 0.545142 0.282565 229 | vt 0.505318 0.282565 230 | vt 0.545142 0.304303 231 | vt 0.551897 0.304303 232 | vt 0.551897 0.278042 233 | vt 0.310347 0.306166 234 | vt 0.297592 0.727672 235 | vt 0.987358 0.741299 236 | vt 0.501965 0.774886 237 | vt 0.792386 0.774886 238 | vt 0.987358 0.768129 239 | vt 0.987358 0.774886 240 | vt 0.238642 0.846538 241 | vt 0.407973 0.860773 242 | vt 0.987373 0.873502 243 | vt 0.783089 0.918150 244 | vt 0.302096 0.801759 245 | vt 0.095237 0.556659 246 | vt 0.113514 0.652331 247 | vt 0.113514 0.466100 248 | vt 0.755353 0.964760 249 | vt 0.873640 0.950775 250 | vt 0.874330 0.921570 251 | vt 0.897155 0.908205 252 | vt 0.545139 0.275807 253 | vt 0.375195 0.740672 254 | vt 0.770803 0.759569 255 | vt 0.768365 0.737287 256 | vt 0.394253 0.496202 257 | vt 0.314999 0.535077 258 | vt 0.193993 0.535077 259 | vt 0.194067 0.745714 260 | vt 0.768380 0.885180 261 | vt 0.759710 0.859290 262 | vt 0.375210 0.894639 263 | vt 0.194082 0.880104 264 | vt 0.187408 0.846935 265 | vt 0.922229 0.825140 266 | vt 0.987373 0.819399 267 | vt 0.759710 0.799275 268 | vt 0.172889 0.122500 269 | vt 0.426645 0.151071 270 | vt 0.426658 0.729094 271 | vt 0.759695 0.718025 272 | vt 0.809793 0.425076 273 | vt 0.922214 0.722990 274 | vt 0.908004 0.425076 275 | vt 0.172917 0.819108 276 | vt 0.429007 0.071038 277 | vt 0.759683 0.074675 278 | vt 0.841868 0.964263 279 | vt 0.845687 0.930093 280 | vt 0.987373 0.855839 281 | vt 0.238642 0.855987 282 | vt 0.297607 0.853832 283 | vt 0.353542 0.069284 284 | vt 0.809780 0.387409 285 | vt 0.759683 0.094460 286 | vt 0.426645 0.083391 287 | vt 0.491717 0.304303 288 | vt 0.922201 0.089495 289 | vt 0.907991 0.387409 290 | vt 0.194082 0.535077 291 | vt 0.269539 0.535077 292 | vt 0.429035 0.786951 293 | vt 0.353570 0.785060 294 | vt 0.353570 0.819230 295 | vt 0.375199 0.740678 296 | vt 0.302096 0.742980 297 | vt 0.759710 0.833155 298 | vt 0.426673 0.822810 299 | vt 0.759710 0.814717 300 | vt 0.187380 0.125167 301 | vt 0.287491 0.224012 302 | vt 0.238614 0.130786 303 | vt 0.320119 0.224012 304 | vt 0.297579 0.137203 305 | vt 0.238627 0.733663 306 | vt 0.320132 0.640863 307 | vt 0.287504 0.640863 308 | vt 0.187393 0.739708 309 | vt 0.919420 0.415876 310 | vt 0.908004 0.415876 311 | vt 0.809780 0.396609 312 | vt 0.798363 0.396609 313 | vt 0.809780 0.405809 314 | vt 0.919407 0.396609 315 | vt 0.907991 0.396609 316 | vt 0.809793 0.415876 317 | vt 0.809793 0.406676 318 | vt 0.798376 0.415876 319 | vt 0.908004 0.406676 320 | vt 0.907991 0.405809 321 | vt 0.987345 0.040521 322 | vt 0.987345 0.069535 323 | vt 0.034834 0.555807 324 | vt 0.096727 0.590650 325 | vt 0.097571 0.525905 326 | vt 0.037680 0.517820 327 | vt 0.103915 0.493972 328 | vt 0.045488 0.477967 329 | vt 0.102283 0.619496 330 | vt 0.057775 0.676010 331 | vt 0.057675 0.442660 332 | vt 0.721766 0.969280 333 | vt 0.748595 0.969280 334 | vt 0.924062 0.923877 335 | vt 0.897195 0.965302 336 | vt 0.923747 0.950235 337 | vt 0.505318 0.282565 338 | vt 0.545142 0.282565 339 | vt 0.545142 0.304303 340 | vt 0.297592 0.727672 341 | vt 0.987358 0.741299 342 | vt 0.987358 0.774886 343 | vt 0.987358 0.768129 344 | vt 0.238642 0.846538 345 | vt 0.407973 0.860773 346 | vt 0.987373 0.873502 347 | vt 0.095237 0.556659 348 | vt 0.113514 0.652331 349 | vt 0.113514 0.466100 350 | vt 0.874330 0.921570 351 | vt 0.873640 0.950775 352 | vt 0.897155 0.908205 353 | vn -0.0235 0.9981 -0.0564 354 | vn -0.1532 0.5053 -0.8492 355 | vn 0.4134 0.8078 -0.4203 356 | vn -0.1401 0.9221 -0.3606 357 | vn 0.2952 0.8880 -0.3525 358 | vn -0.0126 0.7586 -0.6514 359 | vn 0.2149 0.2157 -0.9525 360 | vn -0.0432 -0.2415 -0.9694 361 | vn 0.1356 -0.9498 -0.2821 362 | vn -0.0082 -0.9988 -0.0489 363 | vn 0.0323 0.9988 -0.0380 364 | vn -0.0495 0.9987 -0.0075 365 | vn -0.0000 0.9995 -0.0325 366 | vn 0.1886 -0.1492 -0.9706 367 | vn -0.0338 -0.9733 -0.2269 368 | vn 1.0000 0.0000 0.0000 369 | vn -0.0260 0.7565 -0.6535 370 | vn 0.0845 0.3161 -0.9449 371 | vn -0.0188 -0.9821 -0.1874 372 | vn 0.0254 -0.9991 -0.0347 373 | vn -0.1362 -0.0635 -0.9886 374 | vn 0.0075 0.0417 -0.9991 375 | vn -0.0639 -0.9980 0.0020 376 | vn 0.0000 -0.9995 -0.0304 377 | vn 0.0473 0.2546 -0.9659 378 | vn 0.0259 0.1110 -0.9935 379 | vn 0.0489 0.9988 0.0005 380 | vn 0.0377 0.1598 -0.9864 381 | vn -0.0380 0.1598 -0.9864 382 | vn 0.0309 0.0888 -0.9956 383 | vn -0.0034 -0.9948 0.1014 384 | vn -0.1435 -0.9644 0.2220 385 | vn 0.0000 0.9801 -0.1984 386 | vn 0.1999 0.9345 -0.2944 387 | vn -0.4951 0.6144 0.6144 388 | vn 0.4951 -0.6144 -0.6144 389 | vn -0.4951 -0.6144 0.6144 390 | vn 0.4951 0.6144 -0.6144 391 | vn -0.4951 0.6144 -0.6144 392 | vn 0.0000 0.7071 -0.7071 393 | vn 0.4951 -0.6144 0.6144 394 | vn 0.0000 -0.7071 0.7071 395 | vn -0.4951 -0.6144 -0.6144 396 | vn 0.0000 -0.7071 -0.7071 397 | vn 0.4951 0.6144 0.6144 398 | vn 0.0000 0.7071 0.7071 399 | vn -0.0664 -0.0000 -0.9978 400 | vn -0.1461 -0.8548 0.4980 401 | vn -0.0875 -0.8624 -0.4986 402 | vn -0.1127 0.8794 0.4625 403 | vn -0.1063 0.8513 -0.5138 404 | vn -0.1121 -0.0000 -0.9937 405 | vn -0.0981 -0.8599 0.5010 406 | vn -0.1345 0.0000 0.9909 407 | vn -0.1329 -0.8580 -0.4961 408 | vn -1.0000 -0.0000 0.0000 409 | vn 0.0000 -0.8312 -0.5560 410 | vn 0.0000 1.0000 0.0000 411 | vn -0.0113 -0.0207 -0.9997 412 | vn -1.0000 0.0037 0.0025 413 | vn -0.0483 -0.9727 -0.2268 414 | vn 0.0399 0.0542 -0.9977 415 | vn 0.1504 0.2054 -0.9671 416 | vn -0.0172 0.9828 -0.1838 417 | vn 0.0000 -0.9950 0.0996 418 | vn -0.0542 0.9967 -0.0597 419 | vn -0.0448 0.7850 -0.6179 420 | vn 0.0410 -0.0534 -0.9977 421 | vn 0.0009 -0.2601 -0.9656 422 | vn 0.1085 -0.1922 -0.9754 423 | vn 0.0702 -0.2761 -0.9586 424 | vn 0.0817 0.1032 -0.9913 425 | vn 0.0960 0.4632 -0.8811 426 | vn 0.0576 0.4009 -0.9143 427 | vn 0.1272 -0.0875 -0.9880 428 | vn -0.0704 0.7650 -0.6402 429 | vn 0.0279 0.5141 -0.8573 430 | vn 0.1904 0.9128 -0.3614 431 | vn -0.0550 0.8548 -0.5160 432 | vn 0.0587 0.2165 -0.9745 433 | vn 0.4256 0.8122 -0.3990 434 | vn -0.0461 -0.2598 -0.9645 435 | vn -0.0249 -0.9874 -0.1563 436 | vn -0.0263 -0.9816 -0.1890 437 | vn -0.0202 -0.9818 -0.1891 438 | vn 0.0357 0.0885 -0.9954 439 | vn 0.8969 0.4422 0.0057 440 | vn 0.0342 0.1266 -0.9914 441 | vn -0.0235 0.0907 -0.9956 442 | vn -0.0889 0.8626 0.4980 443 | vn -0.1171 0.8601 -0.4966 444 | vn -0.1006 -0.8616 0.4975 445 | vn -0.1332 -0.8583 -0.4955 446 | vn 0.0000 0.0000 -1.0000 447 | vn -0.9994 0.0146 -0.0327 448 | vn -0.0594 0.9982 0.0000 449 | vn -0.0459 0.7908 -0.6104 450 | vn 0.2077 0.9208 -0.3300 451 | vn -0.0078 0.0326 -0.9994 452 | vn -0.0235 0.9981 0.0564 453 | vn -0.1532 0.5053 0.8492 454 | vn 0.4134 0.8078 0.4203 455 | vn -0.1401 0.9221 0.3606 456 | vn 0.2952 0.8880 0.3525 457 | vn -0.0126 0.7586 0.6514 458 | vn 0.2149 0.2157 0.9525 459 | vn -0.0432 -0.2415 0.9694 460 | vn 0.1356 -0.9498 0.2821 461 | vn -0.0082 -0.9988 0.0489 462 | vn 0.0323 0.9988 0.0380 463 | vn -0.0495 0.9987 0.0075 464 | vn -0.0000 0.9995 0.0325 465 | vn 0.1886 -0.1492 0.9706 466 | vn -0.0338 -0.9733 0.2269 467 | vn -0.0260 0.7565 0.6535 468 | vn 0.0845 0.3161 0.9449 469 | vn -0.0188 -0.9821 0.1874 470 | vn 0.0254 -0.9991 0.0347 471 | vn -0.1362 -0.0635 0.9886 472 | vn 0.0075 0.0417 0.9991 473 | vn -0.0639 -0.9980 -0.0020 474 | vn 0.0000 -0.9995 0.0304 475 | vn 0.0473 0.2546 0.9659 476 | vn 0.0259 0.1110 0.9935 477 | vn 0.0489 0.9988 -0.0005 478 | vn 0.0377 0.1598 0.9864 479 | vn -0.0380 0.1598 0.9864 480 | vn 0.0309 0.0888 0.9956 481 | vn -0.0034 -0.9948 -0.1014 482 | vn -0.1435 -0.9644 -0.2220 483 | vn 0.0000 0.9801 0.1984 484 | vn 0.1999 0.9345 0.2944 485 | vn -0.0664 -0.0000 0.9978 486 | vn -0.1461 -0.8548 -0.4980 487 | vn -0.0875 -0.8624 0.4986 488 | vn -0.1127 0.8794 -0.4625 489 | vn -0.1063 0.8513 0.5138 490 | vn -0.1121 -0.0000 0.9937 491 | vn -0.0981 -0.8599 -0.5010 492 | vn -0.1345 0.0000 -0.9909 493 | vn -0.1329 -0.8580 0.4961 494 | vn 0.0000 -0.8312 0.5560 495 | vn -0.0113 -0.0207 0.9997 496 | vn -1.0000 0.0037 -0.0025 497 | vn -0.0483 -0.9727 0.2268 498 | vn 0.0399 0.0542 0.9977 499 | vn 0.1504 0.2054 0.9671 500 | vn -0.0172 0.9828 0.1838 501 | vn 0.0000 -0.9950 -0.0996 502 | vn -0.0542 0.9967 0.0597 503 | vn -0.0448 0.7850 0.6179 504 | vn 0.0410 -0.0534 0.9977 505 | vn 0.0009 -0.2601 0.9656 506 | vn 0.1085 -0.1922 0.9754 507 | vn 0.0702 -0.2761 0.9586 508 | vn 0.0817 0.1032 0.9913 509 | vn 0.0960 0.4632 0.8811 510 | vn 0.0576 0.4009 0.9143 511 | vn 0.1272 -0.0875 0.9880 512 | vn -0.0704 0.7650 0.6402 513 | vn 0.0279 0.5141 0.8573 514 | vn 0.1904 0.9128 0.3614 515 | vn -0.0550 0.8548 0.5160 516 | vn 0.0587 0.2165 0.9745 517 | vn 0.4256 0.8122 0.3990 518 | vn -0.0461 -0.2598 0.9645 519 | vn -0.0249 -0.9874 0.1563 520 | vn -0.0263 -0.9816 0.1890 521 | vn -0.0202 -0.9818 0.1891 522 | vn 0.0357 0.0885 0.9954 523 | vn 0.8969 0.4422 -0.0057 524 | vn 0.0342 0.1266 0.9914 525 | vn -0.0235 0.0907 0.9956 526 | vn -0.0889 0.8626 -0.4980 527 | vn -0.1171 0.8601 0.4966 528 | vn -0.1006 -0.8616 -0.4975 529 | vn -0.1332 -0.8583 0.4955 530 | vn 0.0000 0.0000 1.0000 531 | vn -0.9994 0.0146 0.0327 532 | vn -0.0459 0.7908 0.6104 533 | vn 0.2077 0.9208 0.3300 534 | vn -0.0078 0.0326 0.9994 535 | usemtl Material.002 536 | s off 537 | f 6/1/1 7/2/1 45/3/1 538 | f 6/4/2 111/5/2 47/6/2 539 | f 2/7/3 113/8/3 112/9/3 540 | f 111/10/4 114/11/4 47/6/4 541 | f 2/12/5 112/13/5 95/14/5 542 | f 7/15/6 6/16/6 3/17/6 543 | f 2/18/7 95/19/7 10/20/7 544 | f 9/21/8 13/22/8 35/23/8 545 | f 95/24/9 98/25/9 17/26/9 546 | f 17/26/10 99/27/10 16/28/10 547 | f 16/29/11 1/30/11 11/31/11 548 | f 9/32/12 11/31/12 30/33/12 549 | f 11/31/13 1/30/13 30/33/13 550 | f 10/20/14 95/19/14 17/34/14 551 | f 12/35/15 101/36/15 13/37/15 552 | f 105/38/16 19/39/16 18/40/16 553 | f 5/41/17 7/15/17 3/17/17 554 | f 22/42/18 21/43/18 2/18/18 555 | f 100/44/19 19/45/19 106/46/19 556 | f 1/47/20 16/48/20 14/49/20 557 | f 97/50/21 103/51/21 15/52/21 558 | f 15/52/22 104/53/22 102/54/22 559 | f 9/55/23 30/56/23 14/49/23 560 | f 30/56/24 1/47/24 14/49/24 561 | f 6/4/25 46/57/25 2/58/25 562 | f 12/59/26 18/60/26 19/61/26 563 | f 99/62/27 18/63/27 16/64/27 564 | f 3/17/28 16/65/28 11/66/28 565 | f 9/21/29 3/17/29 11/66/29 566 | f 12/59/30 14/67/30 16/65/30 567 | f 10/68/31 23/69/31 24/70/31 568 | f 23/69/32 21/71/32 25/72/32 569 | f 22/73/33 24/74/33 25/75/33 570 | f 22/73/34 10/76/34 24/74/34 571 | f 28/77/35 30/33/35 26/78/35 572 | f 31/79/36 34/80/36 27/81/36 573 | f 28/82/37 32/83/37 30/56/37 574 | f 29/84/38 27/85/38 34/86/38 575 | f 26/78/39 33/87/39 28/77/39 576 | f 29/84/40 33/87/40 26/78/40 577 | f 27/81/41 1/47/41 31/79/41 578 | f 1/47/42 32/83/42 31/79/42 579 | f 32/83/43 28/82/43 33/88/43 580 | f 33/88/44 31/79/44 32/83/44 581 | f 27/85/45 29/84/45 1/30/45 582 | f 1/30/46 26/78/46 30/33/46 583 | f 35/23/47 5/41/47 9/21/47 584 | f 20/89/48 101/36/48 107/90/48 585 | f 20/89/49 35/91/49 13/37/49 586 | f 8/92/50 41/93/50 108/94/50 587 | f 36/95/51 8/92/51 5/96/51 588 | f 40/97/52 5/96/52 35/98/52 589 | f 39/99/53 20/100/53 107/101/53 590 | f 41/93/54 107/101/54 108/94/54 591 | f 20/102/55 40/97/55 35/98/55 592 | f 108/103/56 42/104/56 8/105/56 593 | f 39/106/56 41/107/56 37/108/56 594 | f 109/109/57 43/110/57 42/111/57 595 | f 15/52/58 44/112/58 97/50/58 596 | f 42/111/59 44/112/59 15/52/59 597 | f 97/113/60 43/110/60 110/114/60 598 | f 101/36/61 20/89/61 13/37/61 599 | f 96/115/62 15/52/62 102/54/62 600 | f 2/18/63 10/20/63 22/42/63 601 | f 21/116/64 22/73/64 25/75/64 602 | f 24/70/65 23/69/65 25/72/65 603 | f 8/117/66 45/3/66 7/2/66 604 | f 111/118/67 45/3/67 96/119/67 605 | f 96/119/68 42/120/68 15/121/68 606 | f 12/59/69 13/22/69 14/67/69 607 | f 23/122/70 10/20/70 17/34/70 608 | f 23/122/71 17/34/71 21/43/71 609 | f 16/65/72 4/123/72 21/43/72 610 | f 21/43/73 4/123/73 6/16/73 611 | f 6/16/74 2/18/74 21/43/74 612 | f 17/34/75 16/65/75 21/43/75 613 | f 96/119/76 45/3/76 42/120/76 614 | f 6/16/77 4/123/77 3/17/77 615 | f 46/57/78 114/11/78 113/8/78 616 | f 5/41/79 8/124/79 7/15/79 617 | f 6/4/80 47/6/80 46/57/80 618 | f 2/7/81 46/57/81 113/8/81 619 | f 9/21/82 14/67/82 13/22/82 620 | f 17/26/83 98/25/83 99/27/83 621 | f 12/35/84 100/44/84 101/36/84 622 | f 105/38/16 106/125/16 19/39/16 623 | f 100/44/85 12/35/85 19/45/85 624 | f 12/59/86 16/65/86 18/60/86 625 | f 99/62/87 105/126/87 18/63/87 626 | f 3/17/88 4/123/88 16/65/88 627 | f 9/21/89 5/41/89 3/17/89 628 | f 29/84/40 34/86/40 33/87/40 629 | f 1/47/42 30/56/42 32/83/42 630 | f 33/88/44 34/80/44 31/79/44 631 | f 1/30/46 29/84/46 26/78/46 632 | f 8/92/90 37/127/90 41/93/90 633 | f 36/95/91 37/127/91 8/92/91 634 | f 40/97/52 36/95/52 5/96/52 635 | f 39/99/92 38/128/92 20/100/92 636 | f 41/93/54 39/99/54 107/101/54 637 | f 20/102/93 38/129/93 40/97/93 638 | f 108/103/56 109/130/56 42/104/56 639 | f 37/108/56 36/131/56 40/132/56 640 | f 40/132/56 38/133/56 37/108/56 641 | f 38/133/56 39/106/56 37/108/56 642 | f 109/109/57 110/134/57 43/110/57 643 | f 42/111/94 43/110/94 44/112/94 644 | f 97/113/95 44/112/95 43/110/95 645 | f 8/117/96 42/120/96 45/3/96 646 | f 111/118/97 6/1/97 45/3/97 647 | f 46/57/98 47/6/98 114/11/98 648 | f 103/51/99 104/53/99 15/52/99 649 | f 53/135/100 92/136/100 54/137/100 650 | f 53/138/101 94/139/101 111/5/101 651 | f 49/140/102 112/9/102 113/8/102 652 | f 111/10/103 94/139/103 114/11/103 653 | f 49/141/104 95/14/104 112/13/104 654 | f 54/142/105 50/143/105 53/144/105 655 | f 49/145/106 57/146/106 95/19/106 656 | f 56/147/107 82/148/107 60/149/107 657 | f 95/24/108 64/150/108 98/25/108 658 | f 64/150/109 63/151/109 99/27/109 659 | f 63/152/110 58/153/110 48/154/110 660 | f 56/155/111 77/156/111 58/153/111 661 | f 58/153/112 77/156/112 48/154/112 662 | f 57/146/113 64/157/113 95/19/113 663 | f 59/158/114 60/159/114 101/36/114 664 | f 105/38/16 65/160/16 66/161/16 665 | f 52/162/115 50/143/115 54/142/115 666 | f 69/163/116 49/145/116 68/164/116 667 | f 100/44/117 106/46/117 66/165/117 668 | f 48/166/118 61/167/118 63/168/118 669 | f 97/50/119 62/169/119 103/51/119 670 | f 62/169/120 102/54/120 104/53/120 671 | f 56/170/121 61/167/121 77/171/121 672 | f 77/171/122 61/167/122 48/166/122 673 | f 53/138/123 49/172/123 93/173/123 674 | f 59/174/124 66/175/124 65/176/124 675 | f 99/62/125 63/177/125 65/178/125 676 | f 50/143/126 58/179/126 63/180/126 677 | f 56/147/127 58/179/127 50/143/127 678 | f 59/174/128 63/180/128 61/181/128 679 | f 57/182/129 71/183/129 70/184/129 680 | f 70/184/130 72/185/130 68/186/130 681 | f 69/187/131 72/188/131 71/189/131 682 | f 69/187/132 71/189/132 57/190/132 683 | f 75/191/39 73/192/39 77/156/39 684 | f 78/193/41 74/194/41 81/195/41 685 | f 75/196/43 77/171/43 79/197/43 686 | f 76/198/45 81/199/45 74/200/45 687 | f 73/192/35 75/191/35 80/201/35 688 | f 76/198/46 73/192/46 80/201/46 689 | f 74/194/36 78/193/36 48/166/36 690 | f 48/166/44 78/193/44 79/197/44 691 | f 79/197/37 80/202/37 75/196/37 692 | f 80/202/42 79/197/42 78/193/42 693 | f 74/200/38 48/154/38 76/198/38 694 | f 48/154/40 77/156/40 73/192/40 695 | f 82/148/133 56/147/133 52/162/133 696 | f 67/203/134 107/90/134 101/36/134 697 | f 67/203/135 60/159/135 82/204/135 698 | f 55/205/136 108/94/136 88/206/136 699 | f 83/207/137 52/208/137 55/205/137 700 | f 87/209/138 82/210/138 52/208/138 701 | f 86/211/139 107/101/139 67/212/139 702 | f 88/206/140 108/94/140 107/101/140 703 | f 67/213/141 82/210/141 87/209/141 704 | f 108/103/56 55/214/56 89/215/56 705 | f 86/216/56 84/217/56 88/218/56 706 | f 109/109/142 89/219/142 90/220/142 707 | f 62/169/58 97/50/58 91/221/58 708 | f 89/219/143 62/169/143 91/221/143 709 | f 97/113/144 110/114/144 90/220/144 710 | f 101/36/145 60/159/145 67/203/145 711 | f 96/115/146 102/54/146 62/169/146 712 | f 49/145/147 69/163/147 57/146/147 713 | f 68/222/148 72/188/148 69/187/148 714 | f 71/183/149 72/185/149 70/184/149 715 | f 55/223/150 54/137/150 92/136/150 716 | f 111/118/151 96/119/151 92/136/151 717 | f 96/119/152 62/224/152 89/225/152 718 | f 59/174/153 61/181/153 60/149/153 719 | f 70/226/154 64/157/154 57/146/154 720 | f 70/226/155 68/164/155 64/157/155 721 | f 63/180/156 68/164/156 51/227/156 722 | f 68/164/157 53/144/157 51/227/157 723 | f 53/144/158 68/164/158 49/145/158 724 | f 64/157/159 68/164/159 63/180/159 725 | f 96/119/160 89/225/160 92/136/160 726 | f 53/144/161 50/143/161 51/227/161 727 | f 93/173/162 113/8/162 114/11/162 728 | f 52/162/163 54/142/163 55/228/163 729 | f 53/138/164 93/173/164 94/139/164 730 | f 49/140/165 113/8/165 93/173/165 731 | f 56/147/166 60/149/166 61/181/166 732 | f 64/150/167 99/27/167 98/25/167 733 | f 59/158/168 101/36/168 100/44/168 734 | f 105/38/16 66/161/16 106/125/16 735 | f 100/44/169 66/165/169 59/158/169 736 | f 59/174/170 65/176/170 63/180/170 737 | f 99/62/171 65/178/171 105/126/171 738 | f 50/143/172 63/180/172 51/227/172 739 | f 56/147/173 50/143/173 52/162/173 740 | f 76/198/46 80/201/46 81/199/46 741 | f 48/166/44 79/197/44 77/171/44 742 | f 80/202/42 78/193/42 81/195/42 743 | f 48/154/40 73/192/40 76/198/40 744 | f 55/205/174 88/206/174 84/229/174 745 | f 83/207/175 55/205/175 84/229/175 746 | f 87/209/138 52/208/138 83/207/138 747 | f 86/211/176 67/212/176 85/230/176 748 | f 88/206/140 107/101/140 86/211/140 749 | f 67/213/177 87/209/177 85/231/177 750 | f 108/103/56 89/215/56 109/130/56 751 | f 84/217/56 87/232/56 83/233/56 752 | f 87/232/56 84/217/56 85/234/56 753 | f 85/234/56 84/217/56 86/216/56 754 | f 109/109/142 90/220/142 110/134/142 755 | f 89/219/178 91/221/178 90/220/178 756 | f 97/113/179 90/220/179 91/221/179 757 | f 55/223/96 92/136/96 89/225/96 758 | f 111/118/180 92/136/180 53/135/180 759 | f 93/173/181 114/11/181 94/139/181 760 | f 103/51/182 62/169/182 104/53/182 761 | --------------------------------------------------------------------------------