commit 36f01c67169e8cb77866920674601211f4ae91ca Author: schmelczerandras Date: Wed Sep 16 16:03:01 2020 +0200 Copy files diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..2cb7d2a --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +**/*.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..c259ab0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "root": true, + "env": { + "browser": true, + "es2020": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + "prettier/@typescript-eslint" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module" + }, + "plugins": ["unused-imports", "@typescript-eslint", "prettier"], + "rules": { + "prettier/prettier": "error", + "no-unused-vars": "off", + "unused-imports/no-unused-imports-ts": "error", + "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ea544b2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.psd filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a9d836 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist +node_modules +package-lock.json +.vscode diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..5aae580 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "printWidth": 90, + "tabWidth": 2, + "singleQuote": true, + "endOfLine": "lf" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3141753 --- /dev/null +++ b/package.json @@ -0,0 +1,65 @@ +{ + "name": "decla.red", + "version": "0.0.0", + "description": "![logo](media/declared.png)", + "private": true, + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development", + "lint": "npx eslint --fix \"src/**/*.ts\" && npx prettier --write \"src/**/*.ts\"", + "build": "webpack && find dist -type f -not -name '*.html' | xargs rm" + }, + "keywords": [], + "author": "AndrĂ¡s Schmelczer", + "postcss": { + "plugins": { + "autoprefixer": {} + } + }, + "browserslist": [ + "defaults" + ], + "sideEffects": [ + "*.scss" + ], + "optimization": { + "usedExports": true + }, + "devDependencies": { + "@types/gl-matrix": "^2.4.5", + "@typescript-eslint/eslint-plugin": "^3.10.1", + "@typescript-eslint/parser": "^3.10.1", + "autoprefixer": "^9.8.6", + "clean-webpack-plugin": "^3.0.0", + "css-loader": "^3.5.2", + "cssnano": "^4.1.10", + "eslint": "^7.9.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-unused-imports": "^0.1.3", + "gl-matrix": "^3.3.0", + "html-webpack-inline-source-plugin": "0.0.10", + "html-webpack-plugin": "^3.2.0", + "image-webpack-loader": "^6.0.0", + "mini-css-extract-plugin": "^0.9.0", + "optimize-css-assets-webpack-plugin": "^5.0.4", + "postcss-loader": "^3.0.0", + "prettier": "^2.1.2", + "raw-loader": "^4.0.1", + "resolve-url-loader": "^3.1.1", + "responsive-loader": "^1.2.0", + "sass": "^1.26.3", + "sass-loader": "^9.0.3", + "sdf-2d": "^0.0.0", + "sharp": "^0.25.4", + "style-loader": "^1.1.4", + "svg-url-loader": "^6.0.0", + "terser-webpack-plugin": "^2.3.8", + "ts-loader": "^8.0.3", + "typescript": "^3.9.7", + "webpack": "^4.44.1", + "webpack-cli": "^3.3.11", + "webpack-dev-server": "^3.10.3" + } +} diff --git a/src/helper/clamp.ts b/src/helper/clamp.ts new file mode 100644 index 0000000..45da555 --- /dev/null +++ b/src/helper/clamp.ts @@ -0,0 +1,4 @@ +export const clamp = (value: number, min: number, max: number): number => + Math.min(max, Math.max(min, value)); + +export const clamp01 = (value: number): number => Math.min(1, Math.max(0, value)); diff --git a/src/helper/delta-time-calculator.ts b/src/helper/delta-time-calculator.ts new file mode 100644 index 0000000..fb7686d --- /dev/null +++ b/src/helper/delta-time-calculator.ts @@ -0,0 +1,23 @@ +export class DeltaTimeCalculator { + private previousTime: DOMHighResTimeStamp | null = null; + + constructor() { + document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); + } + + public getNextDeltaTime(currentTime: DOMHighResTimeStamp): DOMHighResTimeStamp { + if (this.previousTime === null) { + this.previousTime = currentTime; + } + + const delta = currentTime - this.previousTime; + this.previousTime = currentTime; + return delta; + } + + private handleVisibilityChange() { + if (!document.hidden) { + this.previousTime = null; + } + } +} diff --git a/src/helper/last.ts b/src/helper/last.ts new file mode 100644 index 0000000..5c319b0 --- /dev/null +++ b/src/helper/last.ts @@ -0,0 +1,3 @@ +export function last(a: Array): T | null { + return a.length > 0 ? a[a.length - 1] : null; +} diff --git a/src/helper/mix.ts b/src/helper/mix.ts new file mode 100644 index 0000000..16a76ed --- /dev/null +++ b/src/helper/mix.ts @@ -0,0 +1 @@ +export const mix = (from: number, to: number, q: number) => from + (to - from) * q; diff --git a/src/helper/random.ts b/src/helper/random.ts new file mode 100644 index 0000000..6a59c67 --- /dev/null +++ b/src/helper/random.ts @@ -0,0 +1,18 @@ +// source: +// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript +// Mulberry32 + +export abstract class Random { + private static _seed = 42; + + public static set seed(value: number) { + Random._seed = value; + } + + public static getRandom(): number { + let t = (Random._seed += 0x6d2b79f5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + } +} diff --git a/src/helper/to-percent.ts b/src/helper/to-percent.ts new file mode 100644 index 0000000..0047591 --- /dev/null +++ b/src/helper/to-percent.ts @@ -0,0 +1 @@ +export const toPercent = (value: number) => `${Math.round(value * 100)}%`; diff --git a/src/helper/wait.ts b/src/helper/wait.ts new file mode 100644 index 0000000..73dd2c2 --- /dev/null +++ b/src/helper/wait.ts @@ -0,0 +1,3 @@ +export const wait = (ms: number): Promise => { + return new Promise((resolve, _) => setTimeout(resolve, ms)); +}; diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..ad4e325 --- /dev/null +++ b/src/index.html @@ -0,0 +1,14 @@ + + + + + Test + + + +
+ + + diff --git a/src/index.scss b/src/index.scss new file mode 100644 index 0000000..5105afb --- /dev/null +++ b/src/index.scss @@ -0,0 +1,35 @@ +html, +body, +canvas#main { + height: 100%; + width: 100%; +} + +body { + position: relative; + margin: 0; + + #overlay { + font-family: Helvetica, Arial, sans-serif; + font-size: 0.6em; + margin: 0.75em 1em; + position: absolute; + right: 0; + + $outline-width: 0.5px; + text-shadow: -$outline-width -$outline-width 0 #000, + $outline-width -$outline-width 0 #000, -$outline-width $outline-width 0 #000, + $outline-width $outline-width 0 #000; + + color: white; + white-space: pre; + + @media (max-width: 800px) { + font-size: 0.55em; + } + } + + canvas#main { + background-color: hotpink; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..aaeb6db --- /dev/null +++ b/src/index.ts @@ -0,0 +1,76 @@ +import { glMatrix, vec2, vec3 } from 'gl-matrix'; +import { compile, Renderer } from 'sdf-2d'; +import { DeltaTimeCalculator } from './helper/delta-time-calculator'; +import './index.scss'; +import { RainScene } from './scenes/rain-scene'; +import { Scene } from './scenes/scene'; + +glMatrix.setMatrixArrayType(Array); + +const deltaTimeCalculator = new DeltaTimeCalculator(); +const canvas = document.querySelector('canvas') as HTMLCanvasElement; +const overlay = document.querySelector('#overlay') as HTMLDivElement; + +const drawFrame = ( + renderer: Renderer, + scene: Scene, + deltaTime: DOMHighResTimeStamp, + currentTime: DOMHighResTimeStamp +) => { + const { width, height } = canvas.getBoundingClientRect(); + renderer.setViewArea(vec2.fromValues(0, height), vec2.fromValues(width, height)); + renderer.autoscaleQuality(deltaTime); + + overlay.innerText = JSON.stringify( + renderer.insights, + (_, v) => (v.toFixed ? Number(v.toFixed(2)) : v), + ' ' + ); + + scene.animate(currentTime, { width, height }); + + scene.drawables.forEach((d) => renderer.addDrawable(d)); + renderer.renderDrawables(); +}; + +const handleScene = async (scene: Scene) => { + const renderer = compile( + canvas, + scene.descriptors, + [vec3.fromValues(0.4, 0.35, 0.6), vec3.fromValues(0, 1, 0), vec3.fromValues(1, 1, 0)], + { tileMultiplier: 8, enableStopwatch: false } + ); + + let triggerIsOver: () => void; + const isOver = new Promise((resolve) => (triggerIsOver = resolve)); + + let timeSinceStart = 0; + + const handleFrame = (currentTime: DOMHighResTimeStamp) => { + const deltaTime = deltaTimeCalculator.getNextDeltaTime(currentTime); + + drawFrame(renderer, scene, deltaTime, currentTime); + + if ((timeSinceStart += deltaTime) > 200000 * 1000) { + triggerIsOver(); + } else { + requestAnimationFrame(handleFrame); + } + }; + + requestAnimationFrame(handleFrame); + await isOver; + + renderer.destroy(); +}; + +const scenes = [new RainScene()]; + +const main = async () => { + let i = 0; + for (;;) { + await handleScene(scenes[i++ % scenes.length]); + } +}; + +main(); diff --git a/src/scenes/rain-scene.ts b/src/scenes/rain-scene.ts new file mode 100644 index 0000000..616abd0 --- /dev/null +++ b/src/scenes/rain-scene.ts @@ -0,0 +1,62 @@ +import { vec2 } from 'gl-matrix'; +import { CircleLight, Drawable, DrawableDescriptor, Flashlight, Tunnel } from 'sdf-2d'; +import { Random } from '../helper/random'; +import { Scene } from './scene'; + +export class RainScene implements Scene { + public droplets: Array = []; + + public constructor() { + for (let i = 0; i < 100; i++) { + this.droplets.push(new Droplet()); + } + } + + public get descriptors(): Array { + return [Tunnel.descriptor, CircleLight.descriptor, Flashlight.descriptor]; + } + + public animate( + currentTime: number, + viewAreaSize: { width: number; height: number } + ): void { + this.droplets.forEach((d) => d.animate(currentTime, viewAreaSize)); + } + + public get drawables(): Array { + return this.droplets.map((d) => d.drawable); + } +} + +class Droplet { + public readonly drawable: Tunnel; + + private speed = Random.getRandom() * 0.2 + 0.1; + private position = vec2.fromValues(Random.getRandom(), Random.getRandom()); + private length = Random.getRandom() * 30 + 8; + + constructor() { + const size = Random.getRandom() * 10 + 5; + + this.drawable = new Tunnel( + vec2.create(), + vec2.create(), + size + Random.getRandom() * 5 + 5, + size + ); + } + + public animate( + currentTime: number, + { width, height }: { width: number; height: number } + ) { + const heightOffset = 300; + vec2.set( + this.drawable.from, + this.position[0] * width, + height - ((this.speed * currentTime) % (height + heightOffset)) + ); + + vec2.add(this.drawable.to, this.drawable.from, vec2.fromValues(0, this.length)); + } +} diff --git a/src/scenes/scene.ts b/src/scenes/scene.ts new file mode 100644 index 0000000..6c5d2d5 --- /dev/null +++ b/src/scenes/scene.ts @@ -0,0 +1,10 @@ +import { Drawable, DrawableDescriptor } from 'sdf-2d'; + +export interface Scene { + readonly descriptors: Array; + animate( + currentTime: DOMHighResTimeStamp, + viewAreaSize: { width: number; height: number } + ): void; + readonly drawables: Array; +} diff --git a/static/declared.png b/static/declared.png new file mode 100644 index 0000000..897278d Binary files /dev/null and b/static/declared.png differ diff --git a/static/declared.psd b/static/declared.psd new file mode 100644 index 0000000..e2f814d --- /dev/null +++ b/static/declared.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03ed783e268bc05d40997239f6118767c5d5d8b15d23fa7b329321c0a1529c46 +size 437314 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..13fb34c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "sourceMap": true, + "noImplicitAny": false, + "target": "es5", + "downlevelIteration": true, + "allowJs": true, + "experimentalDecorators": true, + "moduleResolution": "Node", + "module": "commonjs", + "lib": ["es2015", "dom"] + } +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..f91ecb7 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,127 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const TerserJSPlugin = require('terser-webpack-plugin'); +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); +const Sass = require('sass'); + +const isProduction = process.env.NODE_ENV == 'production'; +const isDevelopment = !isProduction; + +module.exports = { + watchOptions: { + ignored: /node_modules/, + }, + devtool: 'inline-source-map', + devServer: { + host: '0.0.0.0', + disableHostCheck: true, + }, + optimization: { + minimize: true, + minimizer: [ + new TerserJSPlugin({ + sourceMap: isDevelopment, + cache: true, + test: /\.ts$/i, + terserOptions: { + ecma: 5, + warnings: true, + parse: {}, + compress: { defaults: true }, + mangle: true, + module: false, + output: null, + toplevel: true, + nameCache: null, + ie8: false, + keep_classnames: false, + keep_fnames: false, + safari10: false, + }, + }), + new OptimizeCSSAssetsPlugin({}), + ], + }, + plugins: [ + new CleanWebpackPlugin(), + new HtmlWebpackPlugin({ + xhtml: true, + template: './src/index.html', + minify: { + collapseWhitespace: true, + removeComments: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + useShortDoctype: true, + }, + inlineSource: '.(js|css)$', + }), + new HtmlWebpackInlineSourcePlugin(), + new MiniCssExtractPlugin({ + filename: '[name].[contenthash].css', + chunkFilename: '[id].[contenthash].css', + }), + ], + entry: { + index: './src/index.ts', + }, + module: { + rules: [ + { + test: /\.(glsl)$/, + use: { + loader: 'raw-loader', + }, + }, + { + test: /\.ico$/i, + use: { + loader: 'file-loader', + query: { + outputPath: '/', + name: '[name].[ext]', + }, + }, + }, + { + test: /\.scss$/i, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + 'postcss-loader', + { + loader: 'resolve-url-loader', + options: { + keepQuery: true, + }, + }, + { + loader: 'sass-loader', + options: { + sourceMap: true, + implementation: Sass, + }, + }, + ], + }, + { + test: /\.ts$/, + use: { + loader: 'ts-loader', + }, + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.ts', '.js', '.glsl'], + }, + output: { + filename: '[name].[contenthash].js', + path: path.resolve(__dirname, 'dist'), + }, +};