mirror of
https://github.com/FMS-Cat/condition.git
synced 2025-08-11 16:34:10 +02:00
init??
This commit is contained in:
96
.eslintrc.js
Normal file
96
.eslintrc.js
Normal file
@@ -0,0 +1,96 @@
|
||||
module.exports = {
|
||||
"root": true,
|
||||
|
||||
"plugins": [
|
||||
"sort-imports-es6-autofix",
|
||||
"@typescript-eslint",
|
||||
],
|
||||
|
||||
"env": {
|
||||
"es6": true,
|
||||
},
|
||||
|
||||
"parser": "@typescript-eslint/parser",
|
||||
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2017
|
||||
},
|
||||
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
|
||||
"globals": {
|
||||
"process": true // good ol' process.env
|
||||
},
|
||||
|
||||
"rules": {
|
||||
// basics
|
||||
"@typescript-eslint/indent": [ "error", 2, { // indentation should be 2 spaces
|
||||
"flatTernaryExpressions": true, // ternary should be performed in flat
|
||||
"MemberExpression": 1 // member chain should be performed with an indent
|
||||
} ], // it forces 2 spaces indentation
|
||||
"linebreak-style": [ "error", "unix" ], // fuck you, CRLF
|
||||
"quotes": [ "error", "single" ], // quotes must be single
|
||||
"eqeqeq": [ "error", "smart" ], // fuck you, `==`
|
||||
"max-len": [ "error", { // don't be too long, code
|
||||
"code": 100,
|
||||
"ignoreComments": true, // comments are okay
|
||||
"ignoreStrings": true, // strings are okay
|
||||
"ignoreTemplateLiterals": true, // templates are also okay
|
||||
"ignoreRegExpLiterals": true, // regexs are also okay too
|
||||
} ],
|
||||
"sort-imports-es6-autofix/sort-imports-es6": [ "error" ], // imports have to be ordered
|
||||
"eol-last": [ "error", "always" ], // eof newline is cool
|
||||
|
||||
// variables
|
||||
"@typescript-eslint/no-unused-vars": [ "warn", { "argsIgnorePattern": "^_" } ], // draw yellow line under unused vars
|
||||
// "no-undef": [ "warn" ], // draws yellow line under undefined vars // it doesn't work on typescript sometimes
|
||||
"no-var": [ "error" ], // fuck you, var
|
||||
"prefer-const": [ "error" ], // const is better than let
|
||||
|
||||
// omittables
|
||||
"semi": [ "error", "always" ], // semicolon is required
|
||||
"curly": [ "error" ], // it kills `if (foo) bar++;`
|
||||
"arrow-parens": [ "error", "always" ], // it kills `arg => { func(); }`
|
||||
|
||||
// force spacing (I prefer super sparse code!)
|
||||
"array-bracket-spacing": [ "error", "always" ], // it kills `[val1, val2]`
|
||||
"arrow-spacing": [ "error", { "before": true, "after": true } ], // it kills `( arg )=>{ func(); }`
|
||||
"block-spacing": [ "error", "always" ], // it kills `if ( cond ) {func();}`
|
||||
"comma-spacing": [ "error", { "before": false, "after": true } ], // it kills `func( arg1,arg2 )`
|
||||
"computed-property-spacing": [ "error", "always" ], // it kills `arr[i]`
|
||||
"key-spacing": [ "error", { "beforeColon": false, "afterColon": true } ], // it kills `{ key:val }`
|
||||
"keyword-spacing": [ "error", { "before": true, "after": true } ], // it kills `}else{`
|
||||
"object-curly-spacing": [ "error", "always" ], // it kills `{key: val}`
|
||||
"semi-spacing": [ "error", { "before": false, "after": true } ], // it kills `func1();func2();`
|
||||
"space-before-blocks": [ "error", "always" ], // it kills `if (cond){`
|
||||
"space-in-parens": [ "error", "always" ], // it kills `func (arg)`
|
||||
"space-infix-ops": [ "error" ], // it kills val1+val2
|
||||
"space-unary-ops": [ "error", { "words": true, "nonwords": false, "overrides": { "++": true, "--": true } } ], // it kills `val++`
|
||||
"spaced-comment": [ "error", "always" ], // it kills `//this is comment`
|
||||
|
||||
// ban spacing
|
||||
"func-call-spacing": [ "error", "never" ], // no-trailing-spaces. yea.
|
||||
"no-trailing-spaces": [ "error" ], // no-trailing-spaces. yea.
|
||||
"no-whitespace-before-property": [ "error" ], // it kills `obj .key`
|
||||
"space-before-function-paren": [ "error", { "anonymous": "never", "named": "never", "asyncArrow": "always" } ], // it kills `func ()`
|
||||
|
||||
// others
|
||||
"no-eval": [ "off" ], // we need to go the evil way
|
||||
"no-implied-eval": [ "warn" ], // ok don't
|
||||
"no-console": [ "error", { allow: [ "info", "warn", "error" ] } ], // don't forget to remove `console.log` !
|
||||
|
||||
// typescript-specifics
|
||||
"@typescript-eslint/no-explicit-any": [ "off" ], // yea
|
||||
"@typescript-eslint/explicit-module-boundary-types": [ "off" ], // We are using explicit any on purpose because it's explicit, be permissive
|
||||
"@typescript-eslint/no-inferrable-types": [ "off" ], // it's ok
|
||||
"@typescript-eslint/no-non-null-assertion": [ "off" ], // bang is sometimes required
|
||||
"@typescript-eslint/no-empty-interface": [ "off" ], // we need to perform mixins
|
||||
"@typescript-eslint/explicit-function-return-type": [ "error", { "allowExpressions": true } ], // return type is required
|
||||
"@typescript-eslint/explicit-member-accessibility": [ "error" ], // `public` / `private` for members and methods are required
|
||||
}
|
||||
};
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/dist/
|
||||
/node_modules/
|
||||
*.log
|
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# <placeholder>
|
||||
|
||||
## Build
|
||||
|
||||
- You will need [`shader_minifier.exe`](https://github.com/laurentlb/Shader_Minifier) in your PATH
|
||||
|
||||
```sh
|
||||
yarn
|
||||
yarn build
|
||||
```
|
17
loaders/automaton-json-loader.js
Normal file
17
loaders/automaton-json-loader.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { getOptions } = require( 'loader-utils' );
|
||||
const { AutomatonWithGUI } = require( '@fms-cat/automaton-with-gui' );
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
*/
|
||||
module.exports = function( content ) {
|
||||
const options = getOptions( this );
|
||||
|
||||
let data = JSON.parse( content );
|
||||
|
||||
if ( options.minimize ) {
|
||||
data = AutomatonWithGUI.minimizeData( data, { ...options } );
|
||||
}
|
||||
|
||||
return `module.exports = ${ JSON.stringify( data ) }`;
|
||||
};
|
107
loaders/shader-minifier-loader.js
Normal file
107
loaders/shader-minifier-loader.js
Normal file
@@ -0,0 +1,107 @@
|
||||
const { getOptions } = require( 'loader-utils' );
|
||||
const cp = require( 'child_process' );
|
||||
const tempy = require( 'tempy' );
|
||||
const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
const { promisify } = require( 'util' );
|
||||
|
||||
const exec = promisify( cp.exec );
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildMinifierOptionsString( options ) {
|
||||
let str = '';
|
||||
|
||||
if ( options.hlsl ) {
|
||||
str += '--hlsl ';
|
||||
}
|
||||
|
||||
str += '--format none ';
|
||||
|
||||
if ( typeof options.fieldNames === 'string' ) {
|
||||
str += `--field-names ${ options.fieldNames } `;
|
||||
}
|
||||
|
||||
if ( options.preserveExternals ) {
|
||||
str += '--preserve-externals ';
|
||||
}
|
||||
|
||||
if ( options.preserveAllGlobals ) {
|
||||
str += '--preserve-all-globals ';
|
||||
}
|
||||
|
||||
if ( options.noRenaming ) {
|
||||
str += '--no-renaming ';
|
||||
}
|
||||
|
||||
if ( Array.isArray( options.noRenamingList ) ) {
|
||||
str += `--no-renaming-list ${ options.noRenamingList.join( ',' ) } `;
|
||||
}
|
||||
|
||||
if ( options.noSequence ) {
|
||||
str += '--no-sequence ';
|
||||
}
|
||||
|
||||
if ( options.smoothstep ) {
|
||||
str += '--smoothstep ';
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
* @returns string
|
||||
*/
|
||||
function sanitizeContent( content ) {
|
||||
return content.split( '\n' ).map( ( line ) => {
|
||||
let lineSanitized = line;
|
||||
|
||||
// precision
|
||||
// https://github.com/laurentlb/Shader_Minifier/issues/8
|
||||
if ( /^\s*precision/m.exec( lineSanitized ) ) {
|
||||
lineSanitized = `//[\n${ lineSanitized }\n//]`
|
||||
}
|
||||
|
||||
return lineSanitized;
|
||||
} ).join( '\n' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
*/
|
||||
module.exports = async function( content ) {
|
||||
const callback = this.async();
|
||||
const options = getOptions( this );
|
||||
|
||||
const minifierOptions = buildMinifierOptionsString( options );
|
||||
|
||||
const name = path.basename( this.resourcePath );
|
||||
|
||||
const contentSanitized = sanitizeContent( content );
|
||||
|
||||
const minified = await tempy.file.task( async ( pathOriginal ) => {
|
||||
await fs.promises.writeFile( pathOriginal, contentSanitized, { encoding: 'utf8' } );
|
||||
|
||||
return await tempy.file.task( async ( pathMinified ) => {
|
||||
const command = `shader_minifier.exe ${ pathOriginal } ${ minifierOptions }-o ${ pathMinified }`;
|
||||
await exec( command ).catch( ( error ) => {
|
||||
if ( error.stdout ) {
|
||||
this.emitError( Error( error.stdout ) );
|
||||
}
|
||||
|
||||
throw error;
|
||||
} );
|
||||
|
||||
return await fs.promises.readFile( pathMinified, { encoding: 'utf8' } );
|
||||
}, { name } );
|
||||
}, { name } ).catch( ( error ) => {
|
||||
callback( error );
|
||||
} );
|
||||
|
||||
if ( minified ) {
|
||||
callback( null, minified );
|
||||
}
|
||||
};
|
32
package.json
Normal file
32
package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "revision-2021",
|
||||
"version": "0.0.1",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "webpack-cli serve --mode development",
|
||||
"build": "webpack --mode production && ruby pnginator.rb ./dist/bundle.js ./dist/out.png.html"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fms-cat/automaton": "^4.1.0",
|
||||
"@fms-cat/automaton-fxs": "^4.1.0",
|
||||
"@fms-cat/automaton-with-gui": "^4.1.1",
|
||||
"@fms-cat/experimental": "^0.5.0",
|
||||
"@fms-cat/glcat-ts": "^0.14.0",
|
||||
"@types/webpack-env": "^1.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
||||
"@typescript-eslint/parser": "^4.17.0",
|
||||
"eslint": "^7.21.0",
|
||||
"glslify-loader": "^2.0.0",
|
||||
"html-webpack-plugin": "^5.3.0",
|
||||
"loader-utils": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"schema-utils": "^3.0.0",
|
||||
"tempy": "^1.0.0",
|
||||
"ts-loader": "^8.0.17",
|
||||
"typescript": "^4.2.3",
|
||||
"webpack": "^5.24.4",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
}
|
||||
}
|
106
pnginator.rb
Normal file
106
pnginator.rb
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env ruby -w
|
||||
|
||||
# pnginator.rb: pack a .js file into a PNG image with an HTML payload;
|
||||
# when saved with an .html extension and opened in a browser, the HTML extracts and executes
|
||||
# the javascript.
|
||||
|
||||
# Usage: ruby pnginator.rb input.js output.png.html
|
||||
|
||||
# By Gasman <http://matt.west.co.tt/>
|
||||
# from an original idea by Daeken: http://daeken.com/superpacking-js-demos
|
||||
|
||||
|
||||
MAX_WIDTH = 4096
|
||||
USE_PNGOUT = true
|
||||
|
||||
require 'zlib'
|
||||
require 'tempfile'
|
||||
|
||||
input_filename, output_filename = ARGV
|
||||
|
||||
f = File.open(input_filename, 'rb')
|
||||
js = f.read
|
||||
f.close
|
||||
|
||||
if js.length < MAX_WIDTH
|
||||
# js fits onto one pixel line
|
||||
js += "\x00"
|
||||
scanlines = [js]
|
||||
width = js.length
|
||||
height = 1
|
||||
|
||||
# Daeken's single-pixel-row bootstrap (requires js string to be reversed)
|
||||
# (edit by Gasman: change eval to (1,eval) to force global evaluation and avoid massive slowdown)
|
||||
# html = "<canvas id=q><img onload=for(p=q.width=#{width},(c=q.getContext('2d')).drawImage(this,0,e='');p;)e+=String.fromCharCode(c.getImageData(--p,0,1,1).data[0]);(1,eval)(e) src=#>"
|
||||
|
||||
# p01's single-pixel-row bootstrap (requires an 0x00 end marker on the js string)
|
||||
# (edit by Gasman: move drawImage out of getImageData params (it returns undef, which is invalid) and change eval to (1,eval) to force global evaluation)
|
||||
html = "<canvas id=c><img onload=with(c.getContext('2d'))for(p=e='';drawImage(this,p--,0),t=getImageData(0,0,1,1).data[0];)e+=String.fromCharCode(t);(1,eval)(e) src=#>"
|
||||
else
|
||||
js = "\x00" + js
|
||||
width = MAX_WIDTH
|
||||
# split js into scanlines of 'width' pixels; pad the last one with whitespace
|
||||
scanlines = js.scan(/.{1,#{width}}/m).collect{|line| line.ljust(width, "\x00")}
|
||||
height = scanlines.length
|
||||
|
||||
# p01's multiple-pixel-row bootstrap (requires a dummy first byte on the js string)
|
||||
# (edit by Gasman: set explicit canvas width to support widths above 300; move drawImage out of getImageData params; change eval to (1,eval) to force global evaluation)
|
||||
html = "<canvas id=c><img onload=for(w=c.width=#{width},a=c.getContext('2d'),a.drawImage(this,p=0,0),e='',d=a.getImageData(0,0,w,#{height}).data;t=d[p+=4];)e+=String.fromCharCode(t);(1,eval)(e) src=#>"
|
||||
end
|
||||
|
||||
# prepend each scanline with 0x00 to indicate 'no filtering', then concat into one string
|
||||
image_data = scanlines.collect{|line| "\x00" + line}.join
|
||||
idat_chunk = Zlib::Deflate.deflate(image_data, 9) # 9 = maximum compression
|
||||
|
||||
def png_chunk(signature, data)
|
||||
[data.length, signature, data, Zlib::crc32(signature + data)].pack("NA4A*N")
|
||||
end
|
||||
|
||||
if USE_PNGOUT
|
||||
# Create a valid (no format hacks) .png file to pass to pngout
|
||||
f = Tempfile.open(['pnginator', '.png'])
|
||||
|
||||
begin
|
||||
f.write("\x89PNG\x0d\x0a\x1a\x0a") # PNG file header
|
||||
f.write(png_chunk("IHDR", [width, height, 8, 0, 0, 0, 0].pack("NNccccc")))
|
||||
f.write(png_chunk("IDAT", idat_chunk))
|
||||
f.write(png_chunk("IEND", ''))
|
||||
f.close
|
||||
|
||||
system("pngout", f.path, "-c0", "-y")
|
||||
|
||||
# read file back and extract the IDAT chunk
|
||||
f.open
|
||||
f.read(8)
|
||||
while !f.eof?
|
||||
length, signature = f.read(8).unpack("NA4")
|
||||
data = f.read(length)
|
||||
crc = f.read(4)
|
||||
|
||||
if signature == "IDAT"
|
||||
idat_chunk = data
|
||||
break
|
||||
end
|
||||
end
|
||||
ensure
|
||||
f.close
|
||||
f.unlink
|
||||
end
|
||||
end
|
||||
|
||||
File.open(output_filename, 'wb') do |f|
|
||||
f.write("\x89PNG\x0d\x0a\x1a\x0a") # PNG file header
|
||||
|
||||
f.write(png_chunk("IHDR", [width, height, 8, 0, 0, 0, 0].pack("NNccccc")))
|
||||
|
||||
# a custom chunk containing the HTML payload; stated chunk length is 4 less than the actual length,
|
||||
# leaving the final 4 bytes to take the place of the checksum
|
||||
f.write([html.length - 4, "jawh", html].pack("NA4A*"))
|
||||
|
||||
# can safely omit the checksum of the IDAT chunk
|
||||
# f.write([idat_chunk.length, "IDAT", idat_chunk, Zlib::crc32("IDAT" + idat_chunk)].pack("NA4A*N"))
|
||||
f.write([idat_chunk.length, "IDAT", idat_chunk].pack("NA4A*"))
|
||||
|
||||
# can safely omit the IEND chunk
|
||||
# f.write([0, "IEND", "", Zlib::crc32("IEND")].pack("NA4A*N"))
|
||||
end
|
124
shader-minifier-tips.md
Normal file
124
shader-minifier-tips.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# shader-minifier-tips.md
|
||||
|
||||
[shader_minifier](https://github.com/laurentlb/Shader_Minifier) is good. The behavior is kinda [jej](https://www.youtube.com/watch?v=Wjhhs6GcUx0) though.
|
||||
This tips should cover all the tips you have to care when you are using shader_minifier.
|
||||
|
||||
## Precisions
|
||||
|
||||
It will be handled automatically in shader-minifier-loader.
|
||||
|
||||
It doesn't accept `precision <precision> <type>;` for some reasons.
|
||||
There are a workaround that put `//[` and `//]` around the line (shoutouts to [davidar](https://github.com/laurentlb/Shader_Minifier/issues/8#issuecomment-401582088)):
|
||||
|
||||
```glsl
|
||||
//[
|
||||
precision highp float;
|
||||
//]
|
||||
```
|
||||
|
||||
### Error Examples
|
||||
|
||||
```
|
||||
Parse error: Error in <path>: Ln: 5 Col: 17
|
||||
precision highp float;
|
||||
^
|
||||
Expecting: '('
|
||||
```
|
||||
|
||||
### Issues
|
||||
|
||||
- https://github.com/laurentlb/Shader_Minifier/issues/8
|
||||
|
||||
(issue)
|
||||
|
||||
## Preprocessor Branches
|
||||
|
||||
Preprocessor branching often triggers issues on minified output. Use with cares.
|
||||
|
||||
The branch below should be accepted without problems:
|
||||
|
||||
```glsl
|
||||
vec3 func() {
|
||||
vec3 ret;
|
||||
#ifdef CONDITION
|
||||
ret = vec3( 1.0 );
|
||||
#else
|
||||
ret = vec3( 0.0 );
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see these issues below.
|
||||
|
||||
### Error Examples
|
||||
|
||||
```
|
||||
ERROR: 0:9: 'r' : undeclared identifier
|
||||
ERROR: 0:9: 'constructor' : not enough data provided for construction
|
||||
```
|
||||
|
||||
```
|
||||
ERROR: 0:30: 'm' : variable expected
|
||||
ERROR: 0:30: 'return' : function return is not matching type:
|
||||
```
|
||||
|
||||
```
|
||||
ERROR: 0:25: 'ifdef' : unexpected end of file found in conditional block
|
||||
```
|
||||
|
||||
### Issues
|
||||
|
||||
- https://github.com/laurentlb/Shader_Minifier/issues/27
|
||||
- https://github.com/laurentlb/Shader_Minifier/issues/28
|
||||
|
||||
## Overloading
|
||||
|
||||
I don't think overloading is not working properly for most of cases.
|
||||
|
||||
### Error Examples
|
||||
|
||||
```
|
||||
ERROR: 0:11: 't' : no matching overloaded function found
|
||||
ERROR: 0:11: '=' : dimension mismatch
|
||||
ERROR: 0:11: 'assign' : cannot convert from 'highp 3-component vector of float' to 'highp float'
|
||||
```
|
||||
|
||||
## Variable Declarations
|
||||
|
||||
Variable declarations with same types (especially, uniforms) should be done at once.
|
||||
|
||||
```glsl
|
||||
// uh
|
||||
uniform float uHoge;
|
||||
uniform float uFuga;
|
||||
uniform vec3 uFoo;
|
||||
uniform float uBar;
|
||||
// -> uniform float uHoge,uFuga;uniform vec3 uFoo;uniform float uBar;
|
||||
|
||||
// better
|
||||
uniform float uHoge;
|
||||
uniform float uFuga;
|
||||
uniform float uBar;
|
||||
uniform vec3 uFoo;
|
||||
// -> uniform float uHoge,uFuga,uBar;uniform vec3 uFoo;
|
||||
```
|
||||
|
||||
## Preprocessor Constants
|
||||
|
||||
Defining constants should be performed using `const` rather than `#define`.
|
||||
|
||||
```glsl
|
||||
// no
|
||||
const float PI = 3.14159265;
|
||||
const float TAU = 6.283185307;
|
||||
// -> const float PI = 3.14159265;
|
||||
// const float TAU = 6.283185307;
|
||||
|
||||
// preferable
|
||||
const float PI = 3.1415926535;
|
||||
const float TAU = 6.283185307;
|
||||
// -> const float e=3.14159,s=6.28319;
|
||||
```
|
||||
|
||||
You might want to also avoid macros with arguments for the same reason.
|
186
src/Music.ts
Normal file
186
src/Music.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { GLCat, GLCatBuffer, GLCatProgram, GLCatTransformFeedback } from '@fms-cat/glcat-ts';
|
||||
import { MUSIC_BPM, MUSIC_BUFFER_LENGTH } from './config';
|
||||
import { Pool } from './utils/Pool';
|
||||
import musicVert from './shaders/music.vert';
|
||||
|
||||
const discardFrag = '#version 300 es\nvoid main(){discard;}';
|
||||
|
||||
export class Music {
|
||||
public isPlaying: boolean;
|
||||
public time: number;
|
||||
public deltaTime: number;
|
||||
public audio: AudioContext;
|
||||
public glCat: GLCat<WebGL2RenderingContext>;
|
||||
|
||||
private __program: GLCatProgram<WebGL2RenderingContext>;
|
||||
private __bufferOff: GLCatBuffer<WebGL2RenderingContext>;
|
||||
private __bufferTransformFeedbacks: [
|
||||
GLCatBuffer<WebGL2RenderingContext>,
|
||||
GLCatBuffer<WebGL2RenderingContext>
|
||||
];
|
||||
private __transformFeedback: GLCatTransformFeedback<WebGL2RenderingContext>;
|
||||
private __prevAudioTime: number;
|
||||
private __bufferPool: Pool<AudioBuffer>;
|
||||
private __prevBufferSource: AudioBufferSourceNode | null = null;
|
||||
|
||||
constructor( glCat: GLCat<WebGL2RenderingContext>, audio: AudioContext ) {
|
||||
this.glCat = glCat;
|
||||
const { gl } = glCat;
|
||||
|
||||
this.audio = audio;
|
||||
|
||||
// == yoinked from wavenerd-deck ===============================================================
|
||||
const bufferOffArray = new Array( MUSIC_BUFFER_LENGTH )
|
||||
.fill( 0 )
|
||||
.map( ( _, i ) => i );
|
||||
this.__bufferOff = glCat.createBuffer();
|
||||
this.__bufferOff.setVertexbuffer( new Float32Array( bufferOffArray ) );
|
||||
|
||||
this.__bufferTransformFeedbacks = [
|
||||
glCat.createBuffer(),
|
||||
glCat.createBuffer(),
|
||||
];
|
||||
this.__transformFeedback = glCat.createTransformFeedback();
|
||||
|
||||
this.__bufferTransformFeedbacks[ 0 ].setVertexbuffer(
|
||||
MUSIC_BUFFER_LENGTH * 4,
|
||||
gl.DYNAMIC_COPY
|
||||
);
|
||||
|
||||
this.__bufferTransformFeedbacks[ 1 ].setVertexbuffer(
|
||||
MUSIC_BUFFER_LENGTH * 4,
|
||||
gl.DYNAMIC_COPY
|
||||
);
|
||||
|
||||
this.__transformFeedback.bindBuffer( 0, this.__bufferTransformFeedbacks[ 0 ] );
|
||||
this.__transformFeedback.bindBuffer( 1, this.__bufferTransformFeedbacks[ 1 ] );
|
||||
|
||||
// == program ==================================================================================
|
||||
this.__program = glCat.lazyProgram(
|
||||
musicVert,
|
||||
discardFrag,
|
||||
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
|
||||
);
|
||||
|
||||
// == audio ====================================================================================
|
||||
this.__prevAudioTime = 0;
|
||||
|
||||
this.__bufferPool = new Pool( [
|
||||
audio.createBuffer( 2, MUSIC_BUFFER_LENGTH, audio.sampleRate ),
|
||||
audio.createBuffer( 2, MUSIC_BUFFER_LENGTH, audio.sampleRate ),
|
||||
] );
|
||||
|
||||
// == I think these are gonna be required ======================================================
|
||||
this.isPlaying = false;
|
||||
this.time = 0.0;
|
||||
this.deltaTime = 0.0;
|
||||
|
||||
// == hot hot hot hot hot ======================================================================
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept(
|
||||
[
|
||||
'./shaders/music.vert',
|
||||
],
|
||||
async () => {
|
||||
const program = await this.glCat.lazyProgramAsync(
|
||||
musicVert,
|
||||
discardFrag,
|
||||
{ transformFeedbackVaryings: [ 'outL', 'outR' ] },
|
||||
).catch( ( error ) => {
|
||||
console.error( error );
|
||||
return null;
|
||||
} );
|
||||
|
||||
if ( program ) {
|
||||
this.__program.dispose( true );
|
||||
this.__program = program;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public update(): void {
|
||||
const { audio, isPlaying } = this;
|
||||
|
||||
const genTime = audio.currentTime;
|
||||
this.deltaTime = genTime - this.__prevAudioTime;
|
||||
|
||||
if ( isPlaying ) {
|
||||
this.time += this.deltaTime;
|
||||
|
||||
const buffer = this.__bufferPool.next();
|
||||
|
||||
if ( this.__program ) {
|
||||
this.__prepareBuffer( buffer );
|
||||
}
|
||||
|
||||
const bufferSource = audio.createBufferSource();
|
||||
bufferSource.buffer = buffer;
|
||||
bufferSource.connect( audio.destination );
|
||||
|
||||
this.__prevBufferSource?.stop( audio.currentTime );
|
||||
bufferSource.start( audio.currentTime, audio.currentTime - genTime );
|
||||
this.__prevBufferSource = bufferSource;
|
||||
} else {
|
||||
this.__prevBufferSource?.stop( audio.currentTime );
|
||||
this.__prevBufferSource = null;
|
||||
}
|
||||
|
||||
this.__prevAudioTime = genTime;
|
||||
}
|
||||
|
||||
private __prepareBuffer( buffer: AudioBuffer ): void {
|
||||
const { glCat, time } = this;
|
||||
const { gl } = this.glCat;
|
||||
const program = this.__program;
|
||||
|
||||
const beatLength = 60.0 / MUSIC_BPM;
|
||||
const barLength = 240.0 / MUSIC_BPM;
|
||||
const sixteenBarLength = 3840.0 / MUSIC_BPM;
|
||||
|
||||
program.attribute( 'off', this.__bufferOff, 1 );
|
||||
program.uniform1f( 'bpm', MUSIC_BPM );
|
||||
program.uniform1f( '_deltaSample', 1.0 / this.audio.sampleRate );
|
||||
program.uniform4f(
|
||||
'timeLength',
|
||||
beatLength,
|
||||
barLength,
|
||||
sixteenBarLength,
|
||||
1E16
|
||||
);
|
||||
program.uniform4f(
|
||||
'_timeHead',
|
||||
time % beatLength,
|
||||
time % barLength,
|
||||
time % sixteenBarLength,
|
||||
time
|
||||
);
|
||||
|
||||
glCat.useProgram( program, () => {
|
||||
glCat.bindTransformFeedback( this.__transformFeedback, () => {
|
||||
gl.enable( gl.RASTERIZER_DISCARD );
|
||||
gl.beginTransformFeedback( gl.POINTS );
|
||||
gl.drawArrays( gl.POINTS, 0, MUSIC_BUFFER_LENGTH );
|
||||
gl.endTransformFeedback();
|
||||
gl.disable( gl.RASTERIZER_DISCARD );
|
||||
} );
|
||||
} );
|
||||
|
||||
gl.flush();
|
||||
|
||||
[ 0, 1 ].map( ( i ) => {
|
||||
glCat.bindVertexBuffer( this.__bufferTransformFeedbacks[ i ], () => {
|
||||
gl.getBufferSubData(
|
||||
gl.ARRAY_BUFFER,
|
||||
0,
|
||||
buffer.getChannelData( i ),
|
||||
0,
|
||||
MUSIC_BUFFER_LENGTH
|
||||
);
|
||||
} );
|
||||
} );
|
||||
}
|
||||
};
|
1
src/automaton-fxs/automatonFxs.ts
Normal file
1
src/automaton-fxs/automatonFxs.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { hermitePatch } from '@fms-cat/automaton-fxs';
|
1
src/automaton.json
Normal file
1
src/automaton.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"4.1.1","resolution":100,"curves":[{"nodes":[[0,1,0,0,0.16140350877192983],[1,0,-0.47719298245614045]]}],"channels":[["Glitch/amp",{"items":[{"length":1,"curve":0}]}]],"labels":{"zero":0},"guiSettings":{"snapTimeActive":true,"snapTimeInterval":0.1,"snapValueActive":true,"snapValueInterval":0.1,"snapBeatActive":false,"bpm":140,"beatOffset":0,"useBeatInGUI":false,"minimizedPrecisionTime":3,"minimizedPrecisionValue":3}}
|
6
src/automaton.json.d.ts
vendored
Normal file
6
src/automaton.json.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { SerializedAutomatonWithGUI } from '@fms-cat/automaton-with-gui';
|
||||
|
||||
declare module './automaton.json' {
|
||||
const data: SerializedAutomatonWithGUI;
|
||||
export default data;
|
||||
}
|
7
src/config-hot.ts
Normal file
7
src/config-hot.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const
|
||||
RTINSPECTOR_CAPTURE_NAME: string | null = null,
|
||||
// RTINSPECTOR_CAPTURE_NAME: string | null = 'Bloom/swap1',
|
||||
RTINSPECTOR_CAPTURE_INDEX = 0,
|
||||
COMPONENT_UPDATE_BREAKPOINT: string | null = null,
|
||||
// COMPONENT_UPDATE_BREAKPOINT: string | null = 'Bloom/quadDup3',
|
||||
COMPONENT_DRAW_BREAKPOINT: string | null = 'Rings/meshRender';
|
7
src/config.ts
Normal file
7
src/config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const
|
||||
RANDOM_RESOLUTION = [ 64, 64 ],
|
||||
STATIC_RANDOM_RESOLUTION = [ 2048, 2048 ],
|
||||
AO_RESOLUTION_RATIO = 1.0,
|
||||
RESOLUTION = [ 1280, 720 ],
|
||||
MUSIC_BPM = 140,
|
||||
MUSIC_BUFFER_LENGTH = 16384;
|
104
src/entities/Bloom.ts
Normal file
104
src/entities/Bloom.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { Material } from '../heck/Material';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { RenderTarget } from '../heck/RenderTarget';
|
||||
import { Swap } from '@fms-cat/experimental';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import bloomPreFrag from '../shaders/bloom-pre.frag';
|
||||
import returnFrag from '../shaders/return.frag';
|
||||
import bloomBlurFrag from '../shaders/bloom-blur.frag';
|
||||
import bloomPostFrag from '../shaders/bloom-post.frag';
|
||||
|
||||
export interface BloomOptions {
|
||||
input: GLCatTexture<WebGL2RenderingContext>;
|
||||
target: RenderTarget;
|
||||
}
|
||||
|
||||
export class Bloom {
|
||||
public entity: Entity;
|
||||
|
||||
public constructor( options: BloomOptions ) {
|
||||
this.entity = new Entity();
|
||||
|
||||
const swap = new Swap(
|
||||
new BufferRenderTarget( {
|
||||
width: options.target.width,
|
||||
height: options.target.height,
|
||||
name: process.env.DEV && 'Bloom/swap0',
|
||||
} ),
|
||||
new BufferRenderTarget( {
|
||||
width: options.target.width,
|
||||
height: options.target.height,
|
||||
name: process.env.DEV && 'Bloom/swap1',
|
||||
} ),
|
||||
);
|
||||
|
||||
// -- pre ----------------------------------------------------------------------------------------
|
||||
const materialBloomPre = new Material(
|
||||
quadVert,
|
||||
bloomPreFrag
|
||||
);
|
||||
materialBloomPre.addUniformTexture( 'sampler0', options.input );
|
||||
|
||||
this.entity.components.push( new Quad( {
|
||||
target: swap.o,
|
||||
material: materialBloomPre,
|
||||
range: [ -1.0, -1.0, 0.0, 0.0 ],
|
||||
name: process.env.DEV && 'Bloom/quadPre',
|
||||
} ) );
|
||||
|
||||
swap.swap();
|
||||
|
||||
// -- dup ----------------------------------------------------------------------------------------
|
||||
for ( let i = 0; i < 6; i ++ ) {
|
||||
const material = new Material(
|
||||
quadVert,
|
||||
returnFrag
|
||||
);
|
||||
material.addUniformTexture( 'sampler0', swap.i.texture );
|
||||
|
||||
this.entity.components.push( new Quad( {
|
||||
target: swap.o,
|
||||
material,
|
||||
range: i === 0 ? [ -1.0, -1.0, 1.0, 1.0 ] : [ 0.0, 0.0, 1.0, 1.0 ],
|
||||
name: process.env.DEV && `Bloom/quadDup${ i }`,
|
||||
} ) );
|
||||
|
||||
swap.swap();
|
||||
}
|
||||
|
||||
// -- blur ---------------------------------------------------------------------------------------
|
||||
for ( let i = 0; i < 2; i ++ ) {
|
||||
const material = new Material(
|
||||
quadVert,
|
||||
bloomBlurFrag
|
||||
);
|
||||
material.addUniform( 'isVert', '1i', i );
|
||||
material.addUniformTexture( 'sampler0', swap.i.texture );
|
||||
|
||||
this.entity.components.push( new Quad( {
|
||||
target: swap.o,
|
||||
material,
|
||||
name: process.env.DEV && `Bloom/quadBlur${ i }`,
|
||||
} ) );
|
||||
|
||||
swap.swap();
|
||||
}
|
||||
|
||||
// -- post ---------------------------------------------------------------------------------------
|
||||
const materialBloomPost = new Material(
|
||||
quadVert,
|
||||
bloomPostFrag
|
||||
);
|
||||
materialBloomPost.addUniformTexture( 'samplerDry', options.input );
|
||||
materialBloomPost.addUniformTexture( 'samplerWet', swap.i.texture );
|
||||
|
||||
this.entity.components.push( new Quad( {
|
||||
target: options.target,
|
||||
material: materialBloomPost,
|
||||
name: process.env.DEV && 'Bloom/quadPost',
|
||||
} ) );
|
||||
}
|
||||
}
|
211
src/entities/CameraEntity.ts
Normal file
211
src/entities/CameraEntity.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { Lambda } from '../heck/components/Lambda';
|
||||
import { LightEntity } from './LightEntity';
|
||||
import { Material } from '../heck/Material';
|
||||
import { PerspectiveCamera } from '../heck/components/PerspectiveCamera';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { RenderTarget } from '../heck/RenderTarget';
|
||||
import { AO_RESOLUTION_RATIO } from '../config';
|
||||
import aoFrag from '../shaders/ao.frag';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import shadingFrag from '../shaders/shading.frag';
|
||||
|
||||
export interface CameraEntityOptions {
|
||||
root: Entity;
|
||||
target: RenderTarget;
|
||||
lights: LightEntity[];
|
||||
textureRandom: GLCatTexture<WebGL2RenderingContext>;
|
||||
}
|
||||
|
||||
export class CameraEntity {
|
||||
private __root: Entity;
|
||||
|
||||
public get root(): Entity {
|
||||
return this.__root;
|
||||
}
|
||||
|
||||
private __camera: PerspectiveCamera;
|
||||
|
||||
public get camera(): PerspectiveCamera {
|
||||
return this.__camera;
|
||||
}
|
||||
|
||||
private __entity: Entity;
|
||||
|
||||
public get entity(): Entity {
|
||||
return this.__entity;
|
||||
}
|
||||
|
||||
public constructor( options: CameraEntityOptions ) {
|
||||
this.__root = options.root;
|
||||
|
||||
this.__entity = new Entity();
|
||||
|
||||
const cameraTarget = new BufferRenderTarget( {
|
||||
width: options.target.width,
|
||||
height: options.target.height,
|
||||
numBuffers: 4,
|
||||
name: 'CameraEntity/cameraTarget',
|
||||
} );
|
||||
this.__camera = new PerspectiveCamera( {
|
||||
scene: this.__root,
|
||||
renderTarget: cameraTarget,
|
||||
near: 0.1,
|
||||
far: 20.0,
|
||||
name: 'CameraEntity/camera',
|
||||
} );
|
||||
this.__entity.components.push( this.__camera );
|
||||
|
||||
const aoTarget = new BufferRenderTarget( {
|
||||
width: AO_RESOLUTION_RATIO * options.target.width,
|
||||
height: AO_RESOLUTION_RATIO * options.target.height,
|
||||
name: 'CameraEntity/aoTarget',
|
||||
} );
|
||||
|
||||
const aoMaterial = new Material(
|
||||
quadVert,
|
||||
aoFrag,
|
||||
);
|
||||
|
||||
this.__entity.components.push( new Lambda( {
|
||||
onUpdate: () => {
|
||||
const cameraView = this.__entity.transform.matrix.inverse!;
|
||||
|
||||
aoMaterial.addUniformVector(
|
||||
'cameraPV',
|
||||
'Matrix4fv',
|
||||
this.camera.projectionMatrix.multiply(
|
||||
cameraView
|
||||
).elements
|
||||
);
|
||||
},
|
||||
visible: false,
|
||||
name: process.env.DEV && 'CameraEntity/ao/setCameraUniforms',
|
||||
} ) );
|
||||
|
||||
for ( let i = 0; i < 2; i ++ ) { // it doesn't need 2 and 3
|
||||
aoMaterial.addUniformTexture(
|
||||
'sampler' + i,
|
||||
cameraTarget.getTexture( DISPLAY.gl.COLOR_ATTACHMENT0 + i )
|
||||
);
|
||||
}
|
||||
|
||||
aoMaterial.addUniformTexture( 'samplerRandom', options.textureRandom );
|
||||
|
||||
const aoQuad = new Quad( {
|
||||
material: aoMaterial,
|
||||
target: aoTarget,
|
||||
name: process.env.DEV && 'CameraEntity/ao/quad',
|
||||
} );
|
||||
this.__entity.components.push( aoQuad );
|
||||
|
||||
const shadingMaterials = options.lights.map( ( light, iLight ) => {
|
||||
const shadingMaterial = new Material(
|
||||
quadVert,
|
||||
shadingFrag,
|
||||
{
|
||||
defines: {
|
||||
IS_FIRST_LIGHT: iLight === 0 ? 'true' : undefined
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.__entity.components.push( new Lambda( {
|
||||
onUpdate: () => {
|
||||
const cameraView = this.__entity.transform.matrix.inverse!;
|
||||
|
||||
shadingMaterial.addUniformVector(
|
||||
'cameraView',
|
||||
'Matrix4fv',
|
||||
cameraView.elements
|
||||
);
|
||||
|
||||
shadingMaterial.addUniformVector(
|
||||
'cameraPV',
|
||||
'Matrix4fv',
|
||||
this.camera.projectionMatrix.multiply(
|
||||
cameraView
|
||||
).elements
|
||||
);
|
||||
|
||||
shadingMaterial.addUniform(
|
||||
'lightNearFar',
|
||||
'2f',
|
||||
light.camera.near,
|
||||
light.camera.far
|
||||
);
|
||||
|
||||
shadingMaterial.addUniform(
|
||||
'cameraNearFar',
|
||||
'2f',
|
||||
this.camera.near,
|
||||
this.camera.far
|
||||
);
|
||||
|
||||
shadingMaterial.addUniform(
|
||||
'cameraPos',
|
||||
'3f',
|
||||
...this.__entity.transform.position.elements
|
||||
);
|
||||
|
||||
shadingMaterial.addUniform(
|
||||
'lightPos',
|
||||
'3f',
|
||||
...light.entity.transform.position.elements
|
||||
);
|
||||
|
||||
shadingMaterial.addUniform(
|
||||
'lightColor',
|
||||
'3f',
|
||||
...light.color
|
||||
);
|
||||
|
||||
shadingMaterial.addUniformVector(
|
||||
'lightPV',
|
||||
'Matrix4fv',
|
||||
light.camera.projectionMatrix.multiply(
|
||||
light.entity.transform.matrix.inverse!
|
||||
).elements
|
||||
);
|
||||
},
|
||||
visible: false,
|
||||
name: process.env.DEV && 'CameraEntity/shading/setCameraUniforms',
|
||||
} ) );
|
||||
|
||||
for ( let i = 0; i < 4; i ++ ) {
|
||||
shadingMaterial.addUniformTexture(
|
||||
'sampler' + i,
|
||||
cameraTarget.getTexture( DISPLAY.gl.COLOR_ATTACHMENT0 + i )
|
||||
);
|
||||
}
|
||||
|
||||
shadingMaterial.blend = [ DISPLAY.gl.ONE, DISPLAY.gl.ONE ];
|
||||
shadingMaterial.addUniformTexture( 'samplerAo', aoTarget.texture );
|
||||
shadingMaterial.addUniformTexture( 'samplerShadow', light.shadowMap.texture );
|
||||
shadingMaterial.addUniformTexture( 'samplerRandom', options.textureRandom );
|
||||
|
||||
const shadingQuad = new Quad( {
|
||||
material: shadingMaterial,
|
||||
target: options.target,
|
||||
name: process.env.DEV && 'CameraEntity/shading/quad',
|
||||
} );
|
||||
shadingQuad.clear = iLight === 0 ? [] : false;
|
||||
this.__entity.components.push( shadingQuad );
|
||||
|
||||
return shadingMaterial;
|
||||
} );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/shading.frag', () => {
|
||||
shadingMaterials.forEach( ( material ) => {
|
||||
material.replaceShader( quadVert, shadingFrag );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
100
src/entities/GPUParticles.ts
Normal file
100
src/entities/GPUParticles.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { BufferRenderTarget, BufferRenderTargetOptions } from '../heck/BufferRenderTarget';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { Geometry } from '../heck/Geometry';
|
||||
import { Lambda } from '../heck/components/Lambda';
|
||||
import { Material } from '../heck/Material';
|
||||
import { Mesh } from '../heck/components/Mesh';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { Swap } from '@fms-cat/experimental';
|
||||
|
||||
export interface GPUParticlesOptions {
|
||||
materialCompute: Material;
|
||||
geometryRender: Geometry;
|
||||
materialRender: Material;
|
||||
computeWidth: number;
|
||||
computeHeight: number;
|
||||
computeNumBuffers: number;
|
||||
namePrefix?: string;
|
||||
}
|
||||
|
||||
export class GPUParticles {
|
||||
private __entity: Entity;
|
||||
|
||||
public get entity(): Entity {
|
||||
return this.__entity;
|
||||
}
|
||||
|
||||
private __swapCompute: Swap<BufferRenderTarget>;
|
||||
|
||||
private __quadCompute: Quad;
|
||||
|
||||
private __meshRender: Mesh;
|
||||
|
||||
public get meshRender(): Mesh {
|
||||
return this.__meshRender;
|
||||
}
|
||||
|
||||
public get materialCompute(): Material {
|
||||
return this.__quadCompute.material;
|
||||
}
|
||||
|
||||
public get materialRender(): Material {
|
||||
return this.__meshRender.material;
|
||||
}
|
||||
|
||||
public constructor( options: GPUParticlesOptions ) {
|
||||
this.__entity = new Entity();
|
||||
|
||||
const brtOptions: BufferRenderTargetOptions = {
|
||||
width: options.computeWidth,
|
||||
height: options.computeHeight,
|
||||
numBuffers: options.computeNumBuffers,
|
||||
};
|
||||
|
||||
this.__swapCompute = new Swap(
|
||||
new BufferRenderTarget( {
|
||||
...brtOptions,
|
||||
name: process.env.DEV && `${ options.namePrefix }/swap0`,
|
||||
} ),
|
||||
new BufferRenderTarget( {
|
||||
...brtOptions,
|
||||
name: process.env.DEV && `${ options.namePrefix }/swap1`,
|
||||
} ),
|
||||
);
|
||||
|
||||
// -- swapper ----------------------------------------------------------------------------------
|
||||
this.__entity.components.push( new Lambda( {
|
||||
onUpdate: () => {
|
||||
this.__swapCompute.swap();
|
||||
|
||||
this.materialCompute.addUniformTexture( 'samplerCompute', this.__swapCompute.i.texture );
|
||||
this.__quadCompute.target = this.__swapCompute.o;
|
||||
this.materialRender.addUniformTexture( 'samplerCompute', this.__swapCompute.o.texture );
|
||||
},
|
||||
visible: false,
|
||||
name: process.env.DEV && `${ options.namePrefix }/swapper`,
|
||||
} ) );
|
||||
|
||||
// -- compute ----------------------------------------------------------------------------------
|
||||
this.__quadCompute = new Quad( {
|
||||
target: this.__swapCompute.o,
|
||||
material: options.materialCompute,
|
||||
name: process.env.DEV && `${ options.namePrefix }/quadCompute`,
|
||||
} );
|
||||
this.__entity.components.push( this.__quadCompute );
|
||||
|
||||
// -- render -----------------------------------------------------------------------------------
|
||||
this.__meshRender = new Mesh( {
|
||||
geometry: options.geometryRender,
|
||||
material: options.materialRender,
|
||||
name: process.env.DEV && `${ options.namePrefix }/meshRender`,
|
||||
} );
|
||||
options.materialRender.addUniform(
|
||||
'resolutionCompute',
|
||||
'2f',
|
||||
options.computeWidth,
|
||||
options.computeHeight
|
||||
);
|
||||
this.__entity.components.push( this.__meshRender );
|
||||
}
|
||||
}
|
47
src/entities/Glitch.ts
Normal file
47
src/entities/Glitch.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { Material } from '../heck/Material';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { RenderTarget } from '../heck/RenderTarget';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import glitchFrag from '../shaders/glitch.frag';
|
||||
import { Automaton } from '@fms-cat/automaton';
|
||||
|
||||
export interface GlitchOptions {
|
||||
input: GLCatTexture<WebGL2RenderingContext>;
|
||||
target: RenderTarget;
|
||||
automaton: Automaton;
|
||||
}
|
||||
|
||||
export class Glitch {
|
||||
public amp = 0.0;
|
||||
|
||||
public entity: Entity;
|
||||
public material: Material;
|
||||
|
||||
public constructor( options: GlitchOptions ) {
|
||||
this.entity = new Entity();
|
||||
|
||||
// -- quad -------------------------------------------------------------------------------------
|
||||
this.material = new Material( quadVert, glitchFrag );
|
||||
this.material.addUniformTexture( 'sampler0', options.input );
|
||||
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/glitch.frag', () => {
|
||||
this.material.replaceShader( quadVert, glitchFrag );
|
||||
} );
|
||||
}
|
||||
|
||||
const quad = new Quad( {
|
||||
target: options.target,
|
||||
material: this.material,
|
||||
name: process.env.DEV && 'Glitch/quad',
|
||||
} );
|
||||
this.entity.components.push( quad );
|
||||
|
||||
// -- update uniform ---------------------------------------------------------------------------
|
||||
options.automaton.auto( 'Glitch/amp', ( { value } ) => {
|
||||
this.material.addUniform( 'amp', '1f', value );
|
||||
} );
|
||||
}
|
||||
}
|
137
src/entities/LightEntity.ts
Normal file
137
src/entities/LightEntity.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { Lambda } from '../heck/components/Lambda';
|
||||
import { Material } from '../heck/Material';
|
||||
import { PerspectiveCamera } from '../heck/components/PerspectiveCamera';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { Swap } from '@fms-cat/experimental';
|
||||
import posToDepthFrag from '../shaders/pos-to-depth.frag';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import shadowBlurFrag from '../shaders/shadow-blur.frag';
|
||||
|
||||
export interface LightEntityOptions {
|
||||
root: Entity;
|
||||
shadowMapFov?: number;
|
||||
shadowMapNear?: number;
|
||||
shadowMapFar?: number;
|
||||
shadowMapWidth?: number;
|
||||
shadowMapHeight?: number;
|
||||
namePrefix?: string;
|
||||
}
|
||||
|
||||
export class LightEntity {
|
||||
public color: [ number, number, number ] = [ 1.0, 1.0, 1.0 ];
|
||||
|
||||
private __root: Entity;
|
||||
|
||||
public get root(): Entity {
|
||||
return this.__root;
|
||||
}
|
||||
|
||||
private __shadowMapCamera: PerspectiveCamera;
|
||||
|
||||
public get camera(): PerspectiveCamera {
|
||||
return this.__shadowMapCamera;
|
||||
}
|
||||
|
||||
private __shadowMap: BufferRenderTarget;
|
||||
|
||||
public get shadowMap(): BufferRenderTarget {
|
||||
return this.__shadowMap;
|
||||
}
|
||||
|
||||
private __entity: Entity;
|
||||
|
||||
public get entity(): Entity {
|
||||
return this.__entity;
|
||||
}
|
||||
|
||||
public constructor( options: LightEntityOptions ) {
|
||||
this.__root = options.root;
|
||||
|
||||
this.__entity = new Entity();
|
||||
|
||||
const swapOptions = {
|
||||
width: options.shadowMapWidth || 1024,
|
||||
height: options.shadowMapHeight || 1024
|
||||
};
|
||||
|
||||
const swap = new Swap(
|
||||
new BufferRenderTarget( {
|
||||
...swapOptions,
|
||||
name: process.env.DEV && `${ options.namePrefix }/swap0`,
|
||||
} ),
|
||||
new BufferRenderTarget( {
|
||||
...swapOptions,
|
||||
name: process.env.DEV && `${ options.namePrefix }/swap1`,
|
||||
} )
|
||||
);
|
||||
|
||||
// -- camera -----------------------------------------------------------------------------------
|
||||
const fov = options.shadowMapFov || 45.0;
|
||||
const near = options.shadowMapNear || 0.1;
|
||||
const far = options.shadowMapFar || 100.0;
|
||||
|
||||
this.__shadowMapCamera = new PerspectiveCamera( {
|
||||
fov,
|
||||
near,
|
||||
far,
|
||||
renderTarget: swap.o,
|
||||
scene: this.__root,
|
||||
name: process.env.DEV && `${ options.namePrefix }/shadowMapCamera`,
|
||||
} );
|
||||
this.__shadowMapCamera.clear = [ 1.0, 1.0, 1.0, 1.0 ];
|
||||
this.__entity.components.push( this.__shadowMapCamera );
|
||||
|
||||
this.__shadowMap = new BufferRenderTarget( {
|
||||
width: options.shadowMapWidth || 2048,
|
||||
height: options.shadowMapHeight || 2048,
|
||||
name: process.env.DEV && `${ options.namePrefix }/shadowMap`,
|
||||
} );
|
||||
|
||||
swap.swap();
|
||||
|
||||
// -- convert ----------------------------------------------------------------------------------
|
||||
const materialConvert = new Material(
|
||||
quadVert,
|
||||
posToDepthFrag
|
||||
);
|
||||
|
||||
materialConvert.addUniformTexture( 'sampler0', swap.i.texture );
|
||||
|
||||
this.__entity.components.push( new Lambda( {
|
||||
onUpdate: () => {
|
||||
materialConvert.addUniform( 'cameraPos', '3f', ...this.entity.transform.position.elements );
|
||||
materialConvert.addUniform( 'cameraNearFar', '2f', this.camera.near, this.camera.far );
|
||||
},
|
||||
visible: false,
|
||||
name: process.env.DEV && `${ options.namePrefix }/setCameraUniforms`,
|
||||
} ) );
|
||||
|
||||
this.__entity.components.push( new Quad( {
|
||||
target: swap.o,
|
||||
material: materialConvert,
|
||||
name: process.env.DEV && `${ options.namePrefix }/quadConvertPosToDepth`,
|
||||
} ) );
|
||||
|
||||
swap.swap();
|
||||
|
||||
// -- blur ---------------------------------------------------------------------------------------
|
||||
for ( let i = 0; i < 2; i ++ ) {
|
||||
const material = new Material(
|
||||
quadVert,
|
||||
shadowBlurFrag
|
||||
);
|
||||
material.addUniform( 'isVert', '1i', i );
|
||||
material.addUniformTexture( 'sampler0', swap.i.texture );
|
||||
|
||||
this.__entity.components.push( new Quad( {
|
||||
target: i === 0 ? swap.o : this.__shadowMap,
|
||||
material,
|
||||
name: process.env.DEV && `${ options.namePrefix }/quadShadowBlur${ i }`,
|
||||
} ) );
|
||||
|
||||
swap.swap();
|
||||
}
|
||||
}
|
||||
}
|
42
src/entities/Post.ts
Normal file
42
src/entities/Post.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { Material } from '../heck/Material';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { RenderTarget } from '../heck/RenderTarget';
|
||||
import postFrag from '../shaders/post.frag';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
|
||||
export interface PostOptions {
|
||||
input: GLCatTexture<WebGL2RenderingContext>;
|
||||
target: RenderTarget;
|
||||
}
|
||||
|
||||
export class Post {
|
||||
public entity: Entity;
|
||||
|
||||
public constructor( options: PostOptions ) {
|
||||
this.entity = new Entity();
|
||||
this.entity.visible = false;
|
||||
|
||||
// -- post -------------------------------------------------------------------------------------
|
||||
const material = new Material(
|
||||
quadVert,
|
||||
postFrag,
|
||||
);
|
||||
material.addUniformTexture( 'sampler0', options.input );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/post.frag', () => {
|
||||
material.replaceShader( quadVert, postFrag );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
this.entity.components.push( new Quad( {
|
||||
target: options.target,
|
||||
material,
|
||||
name: process.env.DEV && 'Post/quad',
|
||||
} ) );
|
||||
}
|
||||
}
|
63
src/entities/RTInspector.ts
Normal file
63
src/entities/RTInspector.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { Material } from '../heck/Material';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { RenderTarget } from '../heck/RenderTarget';
|
||||
import returnFrag from '../shaders/return.frag';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import { BufferRenderTarget } from '../heck/BufferRenderTarget';
|
||||
import { RTINSPECTOR_CAPTURE_INDEX, RTINSPECTOR_CAPTURE_NAME } from '../config-hot';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
|
||||
export interface RTInspectorOptions {
|
||||
target: RenderTarget;
|
||||
}
|
||||
|
||||
export class RTInspector {
|
||||
public entity: Entity;
|
||||
public material: Material;
|
||||
|
||||
public constructor( options: RTInspectorOptions ) {
|
||||
this.entity = new Entity();
|
||||
|
||||
this.material = new Material(
|
||||
quadVert,
|
||||
returnFrag,
|
||||
);
|
||||
|
||||
this.entity.components.push( new Quad( {
|
||||
target: options.target,
|
||||
material: this.material,
|
||||
name: process.env.DEV && 'RTInspector/quad',
|
||||
ignoreBreakpoints: true,
|
||||
} ) );
|
||||
|
||||
this.__updateTarget();
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../config-hot', () => {
|
||||
this.__updateTarget();
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private __updateTarget(): void {
|
||||
if ( RTINSPECTOR_CAPTURE_NAME != null ) {
|
||||
const target = BufferRenderTarget.nameMap.get( RTINSPECTOR_CAPTURE_NAME ?? '' ) ?? null;
|
||||
const attachment = DISPLAY.gl.COLOR_ATTACHMENT0 + ( RTINSPECTOR_CAPTURE_INDEX ?? 0 );
|
||||
const texture = target?.getTexture( attachment );
|
||||
|
||||
if ( texture ) {
|
||||
this.material.addUniformTexture( 'sampler0', texture );
|
||||
this.entity.active = true;
|
||||
return;
|
||||
} else {
|
||||
console.warn( `RTInspector: Cannot retrieve a render target texture, RTINSPECTOR_CAPTURE_NAME: ${ RTINSPECTOR_CAPTURE_NAME }, RTINSPECTOR_CAPTURE_INDEX: ${ RTINSPECTOR_CAPTURE_INDEX }` );
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to not render it
|
||||
this.entity.active = false;
|
||||
}
|
||||
}
|
102
src/entities/Raymarcher.ts
Normal file
102
src/entities/Raymarcher.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { Mesh, MeshCull } from '../heck/components/Mesh';
|
||||
import { TRIANGLE_STRIP_QUAD, Vector3 } from '@fms-cat/experimental';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { Geometry } from '../heck/Geometry';
|
||||
import { Material } from '../heck/Material';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import raymarcherFrag from '../shaders/raymarcher.frag';
|
||||
import { Lambda } from '../heck/components/Lambda';
|
||||
|
||||
export class Raymarcher {
|
||||
private __mesh: Mesh;
|
||||
private __geometry: Geometry;
|
||||
|
||||
private __material: Material;
|
||||
|
||||
public get material(): Material {
|
||||
return this.__material;
|
||||
}
|
||||
|
||||
private __entity: Entity;
|
||||
|
||||
public get entity(): Entity {
|
||||
return this.__entity;
|
||||
}
|
||||
|
||||
public constructor( options: {
|
||||
textureRandom: GLCatTexture<WebGL2RenderingContext>;
|
||||
textureRandomStatic: GLCatTexture<WebGL2RenderingContext>;
|
||||
} ) {
|
||||
this.__entity = new Entity();
|
||||
this.__entity.transform.position = new Vector3( [ 0.0, 0.0, 0.3 ] );
|
||||
this.__entity.transform.scale = new Vector3( [ 16.0, 9.0, 1.0 ] ).scale( 0.15 );
|
||||
|
||||
this.__geometry = this.__createGeoemtry();
|
||||
this.__material = this.__createMaterial();
|
||||
|
||||
this.__material.addUniform( 'range', '4f', -1.0, -1.0, 1.0, 1.0 );
|
||||
|
||||
this.__material.addUniformTexture( 'samplerRandom', options.textureRandom );
|
||||
this.__material.addUniformTexture( 'samplerRandomStatic', options.textureRandomStatic );
|
||||
|
||||
this.__entity.components.push( new Lambda( {
|
||||
onDraw: ( event ) => {
|
||||
this.__material.addUniform(
|
||||
'cameraNearFar',
|
||||
'2f',
|
||||
event.camera.near,
|
||||
event.camera.far
|
||||
);
|
||||
|
||||
this.__material.addUniformVector(
|
||||
'inversePV',
|
||||
'Matrix4fv',
|
||||
event.projectionMatrix.multiply( event.viewMatrix ).inverse!.elements
|
||||
);
|
||||
},
|
||||
active: false,
|
||||
name: process.env.DEV && 'Raymarcher/setCameraUniforms',
|
||||
} ) );
|
||||
|
||||
this.__mesh = new Mesh( {
|
||||
geometry: this.__geometry,
|
||||
material: this.__material,
|
||||
name: process.env.DEV && 'Raymarcher/mesh',
|
||||
} );
|
||||
this.__mesh.cull = MeshCull.None;
|
||||
this.__entity.components.push( this.__mesh );
|
||||
}
|
||||
|
||||
protected __createGeoemtry(): Geometry {
|
||||
const geometry = new Geometry();
|
||||
|
||||
const bufferPos = DISPLAY.glCat.createBuffer();
|
||||
bufferPos.setVertexbuffer( new Float32Array( TRIANGLE_STRIP_QUAD ) );
|
||||
geometry.addAttribute( 'p', {
|
||||
buffer: bufferPos,
|
||||
size: 2,
|
||||
type: DISPLAY.gl.FLOAT
|
||||
} );
|
||||
|
||||
geometry.count = 4;
|
||||
geometry.mode = DISPLAY.gl.TRIANGLE_STRIP;
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
protected __createMaterial(): Material {
|
||||
const material = new Material( quadVert, raymarcherFrag );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/raymarcher.frag', () => {
|
||||
material.replaceShader( quadVert, raymarcherFrag );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
32
src/entities/Return.ts
Normal file
32
src/entities/Return.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { Material } from '../heck/Material';
|
||||
import { Quad } from '../heck/components/Quad';
|
||||
import { RenderTarget } from '../heck/RenderTarget';
|
||||
import returnFrag from '../shaders/return.frag';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
|
||||
export interface PostOptions {
|
||||
input: GLCatTexture<WebGL2RenderingContext>;
|
||||
target: RenderTarget;
|
||||
}
|
||||
|
||||
export class Return {
|
||||
public entity: Entity;
|
||||
|
||||
public constructor( options: PostOptions ) {
|
||||
this.entity = new Entity();
|
||||
|
||||
// -- post -------------------------------------------------------------------------------------
|
||||
const material = new Material(
|
||||
quadVert,
|
||||
returnFrag,
|
||||
);
|
||||
material.addUniformTexture( 'sampler0', options.input );
|
||||
|
||||
this.entity.components.push( new Quad( {
|
||||
target: options.target,
|
||||
material
|
||||
} ) );
|
||||
}
|
||||
}
|
90
src/entities/Rings.ts
Normal file
90
src/entities/Rings.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Quaternion, Vector3 } from '@fms-cat/experimental';
|
||||
import { genTorus } from '../geometries/genTorus';
|
||||
import { Mesh } from '../heck/components/Mesh';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { Geometry } from '../heck/Geometry';
|
||||
import { InstancedGeometry } from '../heck/InstancedGeometry';
|
||||
import { Material } from '../heck/Material';
|
||||
import ringsVert from '../shaders/rings.vert';
|
||||
import ringsFrag from '../shaders/rings.frag';
|
||||
|
||||
const PRIMCOUNT = 32;
|
||||
|
||||
export class Rings {
|
||||
public mesh: Mesh;
|
||||
public geometry: Geometry;
|
||||
public material: Material;
|
||||
public entity: Entity;
|
||||
|
||||
public constructor() {
|
||||
this.entity = new Entity();
|
||||
|
||||
this.entity.transform.rotation = Quaternion.fromAxisAngle(
|
||||
new Vector3( [ 1.0, 0.0, 0.0 ] ),
|
||||
0.4,
|
||||
).multiply( Quaternion.fromAxisAngle(
|
||||
new Vector3( [ 0.0, 0.0, 1.0 ] ),
|
||||
0.4,
|
||||
) );
|
||||
|
||||
this.geometry = this.__createGeometry();
|
||||
this.material = this.__createMaterial();
|
||||
|
||||
this.mesh = new Mesh( {
|
||||
geometry: this.geometry,
|
||||
material: this.material,
|
||||
name: process.env.DEV && 'Rings/mesh',
|
||||
} );
|
||||
this.entity.components.push( this.mesh );
|
||||
}
|
||||
|
||||
private __createGeometry(): Geometry {
|
||||
const torus = genTorus( { segmentsRadial: 256 } );
|
||||
|
||||
const geometry = new InstancedGeometry();
|
||||
|
||||
geometry.addAttribute( 'position', torus.position );
|
||||
geometry.addAttribute( 'normal', torus.normal );
|
||||
geometry.setIndex( torus.index );
|
||||
|
||||
const arrayInstanceId = new Array( PRIMCOUNT ).fill( 0 ).map( ( _, i ) => i );
|
||||
const bufferInstanceId = DISPLAY.glCat.createBuffer();
|
||||
bufferInstanceId.setVertexbuffer( new Float32Array( arrayInstanceId ) );
|
||||
|
||||
geometry.addAttribute( 'instanceId', {
|
||||
buffer: bufferInstanceId,
|
||||
size: 1,
|
||||
divisor: 1,
|
||||
type: DISPLAY.gl.FLOAT
|
||||
} );
|
||||
|
||||
geometry.count = torus.count;
|
||||
geometry.primcount = PRIMCOUNT;
|
||||
geometry.mode = torus.mode;
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
private __createMaterial(): Material {
|
||||
const material = new Material( ringsVert, ringsFrag );
|
||||
|
||||
material.addUniform( 'inflate', '1f', 0.01 );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept(
|
||||
[
|
||||
'../shaders/rings.vert',
|
||||
'../shaders/rings.frag',
|
||||
],
|
||||
() => {
|
||||
material.replaceShader( ringsVert, ringsFrag );
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
152
src/entities/SphereParticles.ts
Normal file
152
src/entities/SphereParticles.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { GPUParticles } from './GPUParticles';
|
||||
import { Geometry } from '../heck/Geometry';
|
||||
import { InstancedGeometry } from '../heck/InstancedGeometry';
|
||||
import { Material } from '../heck/Material';
|
||||
import { genOctahedron } from '../geometries/genOctahedron';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import sphereParticleComputeFrag from '../shaders/sphere-particles-compute.frag';
|
||||
import sphereParticleRenderFrag from '../shaders/sphere-particles-render.frag';
|
||||
import sphereParticleRenderVert from '../shaders/sphere-particles-render.vert';
|
||||
|
||||
export interface SphereParticlesOptions {
|
||||
particlesSqrt: number;
|
||||
textureRandom: GLCatTexture<WebGL2RenderingContext>;
|
||||
textureRandomStatic: GLCatTexture<WebGL2RenderingContext>;
|
||||
}
|
||||
|
||||
export class SphereParticles {
|
||||
private static __ppp = 2;
|
||||
|
||||
public get entity(): Entity {
|
||||
return this.__gpuParticles.entity;
|
||||
}
|
||||
|
||||
private __gpuParticles: GPUParticles;
|
||||
|
||||
public get materialCompute(): Material {
|
||||
return this.__gpuParticles.materialCompute;
|
||||
}
|
||||
|
||||
public get materialRender(): Material {
|
||||
return this.__gpuParticles.materialRender;
|
||||
}
|
||||
|
||||
public constructor( options: SphereParticlesOptions ) {
|
||||
this.__gpuParticles = new GPUParticles( {
|
||||
materialCompute: this.__createMaterialCompute( options ),
|
||||
geometryRender: this.__createGeometryRender( options ),
|
||||
materialRender: this.__createMaterialRender( options ),
|
||||
computeWidth: SphereParticles.__ppp * options.particlesSqrt,
|
||||
computeHeight: options.particlesSqrt,
|
||||
computeNumBuffers: 1,
|
||||
namePrefix: process.env.DEV && 'SphereParticles',
|
||||
} );
|
||||
}
|
||||
|
||||
private __createMaterialCompute( options: SphereParticlesOptions ): Material {
|
||||
const { particlesSqrt } = options;
|
||||
const particles = particlesSqrt * particlesSqrt;
|
||||
|
||||
const material = new Material( quadVert, sphereParticleComputeFrag );
|
||||
material.addUniform( 'particlesSqrt', '1f', particlesSqrt );
|
||||
material.addUniform( 'particles', '1f', particles );
|
||||
material.addUniform( 'ppp', '1f', SphereParticles.__ppp );
|
||||
material.addUniformTexture( 'samplerRandom', options.textureRandom );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/sphere-particles-compute.frag', () => {
|
||||
material.replaceShader( quadVert, sphereParticleComputeFrag );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
private __createGeometryRender( options: SphereParticlesOptions ): Geometry {
|
||||
const { particlesSqrt } = options;
|
||||
const particles = particlesSqrt * particlesSqrt;
|
||||
|
||||
const octahedron = genOctahedron( { radius: 1.0, div: 1 } );
|
||||
|
||||
const geometry = new InstancedGeometry();
|
||||
|
||||
geometry.addAttribute( 'position', octahedron.position );
|
||||
geometry.addAttribute( 'normal', octahedron.normal );
|
||||
geometry.setIndex( octahedron.index );
|
||||
|
||||
const bufferComputeUV = DISPLAY.glCat.createBuffer();
|
||||
bufferComputeUV.setVertexbuffer( ( () => {
|
||||
const ret = new Float32Array( particles * 2 );
|
||||
for ( let iy = 0; iy < particlesSqrt; iy ++ ) {
|
||||
for ( let ix = 0; ix < particlesSqrt; ix ++ ) {
|
||||
const i = ix + iy * particlesSqrt;
|
||||
const s = ( SphereParticles.__ppp * ix + 0.5 )
|
||||
/ ( SphereParticles.__ppp * particlesSqrt );
|
||||
const t = ( iy + 0.5 )
|
||||
/ ( particlesSqrt );
|
||||
ret[ i * 2 + 0 ] = s;
|
||||
ret[ i * 2 + 1 ] = t;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} )() );
|
||||
|
||||
geometry.addAttribute( 'computeUV', {
|
||||
buffer: bufferComputeUV,
|
||||
size: 2,
|
||||
divisor: 1,
|
||||
type: DISPLAY.gl.FLOAT
|
||||
} );
|
||||
|
||||
geometry.count = octahedron.count;
|
||||
geometry.mode = octahedron.mode;
|
||||
geometry.primcount = options.particlesSqrt * options.particlesSqrt;
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
private __createMaterialRender( options: SphereParticlesOptions ): Material {
|
||||
const material = new Material(
|
||||
sphereParticleRenderVert,
|
||||
sphereParticleRenderFrag,
|
||||
{
|
||||
defines: {
|
||||
'USE_CLIP': 'true',
|
||||
'USE_VERTEX_COLOR': 'true'
|
||||
},
|
||||
},
|
||||
);
|
||||
material.addUniform( 'colorVar', '1f', 0.1 );
|
||||
material.addUniform( 'ppp', '1f', SphereParticles.__ppp );
|
||||
material.addUniformTexture( 'samplerRandomStatic', options.textureRandomStatic );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/sphere-particles-render.vert', () => {
|
||||
material.replaceShader(
|
||||
sphereParticleRenderVert,
|
||||
sphereParticleRenderFrag,
|
||||
);
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/sphere-particles-render.frag', () => {
|
||||
material.replaceShader(
|
||||
sphereParticleRenderVert,
|
||||
sphereParticleRenderFrag,
|
||||
);
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
187
src/entities/Trails.ts
Normal file
187
src/entities/Trails.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
import { Entity } from '../heck/Entity';
|
||||
import { GPUParticles } from './GPUParticles';
|
||||
import { Geometry } from '../heck/Geometry';
|
||||
import { InstancedGeometry } from '../heck/InstancedGeometry';
|
||||
import { Material } from '../heck/Material';
|
||||
import quadVert from '../shaders/quad.vert';
|
||||
import trailsComputeFrag from '../shaders/trails-compute.frag';
|
||||
import trailsRenderFrag from '../shaders/trails-render.frag';
|
||||
import trailsRenderVert from '../shaders/trails-render.vert';
|
||||
|
||||
export interface TrailsOptions {
|
||||
trails: number;
|
||||
trailLength: number;
|
||||
textureRandom: GLCatTexture<WebGL2RenderingContext>;
|
||||
textureRandomStatic: GLCatTexture<WebGL2RenderingContext>;
|
||||
}
|
||||
|
||||
export class Trails {
|
||||
private static __ppp = 2;
|
||||
|
||||
public get entity(): Entity {
|
||||
return this.__gpuParticles.entity;
|
||||
}
|
||||
|
||||
private __gpuParticles: GPUParticles;
|
||||
|
||||
public get materialCompute(): Material {
|
||||
return this.__gpuParticles.materialCompute;
|
||||
}
|
||||
|
||||
public get materialRender(): Material {
|
||||
return this.__gpuParticles.materialRender;
|
||||
}
|
||||
|
||||
public constructor( options: TrailsOptions ) {
|
||||
this.__gpuParticles = new GPUParticles( {
|
||||
materialCompute: this.__createMaterialCompute( options ),
|
||||
geometryRender: this.__createGeometryRender( options ),
|
||||
materialRender: this.__createMaterialRender( options ),
|
||||
computeWidth: Trails.__ppp * options.trailLength,
|
||||
computeHeight: options.trails,
|
||||
computeNumBuffers: 1,
|
||||
namePrefix: process.env.DEV && 'Trails',
|
||||
} );
|
||||
}
|
||||
|
||||
private __createMaterialCompute( options: TrailsOptions ): Material {
|
||||
const material = new Material( quadVert, trailsComputeFrag );
|
||||
material.addUniform( 'trails', '1f', options.trails );
|
||||
material.addUniform( 'trailLength', '1f', options.trailLength );
|
||||
material.addUniform( 'ppp', '1f', Trails.__ppp );
|
||||
material.addUniformTexture( 'samplerRandom', options.textureRandom );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/trails-compute.frag', () => {
|
||||
material.replaceShader( quadVert, trailsComputeFrag );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
private __createGeometryRender( options: TrailsOptions ): Geometry {
|
||||
const geometry = new InstancedGeometry();
|
||||
|
||||
const bufferComputeU = DISPLAY.glCat.createBuffer();
|
||||
bufferComputeU.setVertexbuffer( ( () => {
|
||||
const ret = new Float32Array( options.trailLength * 3 );
|
||||
for ( let i = 0; i < options.trailLength; i ++ ) {
|
||||
const u = ( Trails.__ppp * i + 0.5 ) / ( Trails.__ppp * options.trailLength );
|
||||
ret[ i * 3 + 0 ] = u;
|
||||
ret[ i * 3 + 1 ] = u;
|
||||
ret[ i * 3 + 2 ] = u;
|
||||
}
|
||||
return ret;
|
||||
} )() );
|
||||
|
||||
geometry.addAttribute( 'computeU', {
|
||||
buffer: bufferComputeU,
|
||||
size: 1,
|
||||
type: DISPLAY.gl.FLOAT
|
||||
} );
|
||||
|
||||
const bufferComputeV = DISPLAY.glCat.createBuffer();
|
||||
bufferComputeV.setVertexbuffer( ( () => {
|
||||
const ret = new Float32Array( options.trails );
|
||||
for ( let i = 0; i < options.trails; i ++ ) {
|
||||
ret[ i ] = ( i + 0.5 ) / options.trails;
|
||||
}
|
||||
return ret;
|
||||
} )() );
|
||||
|
||||
geometry.addAttribute( 'computeV', {
|
||||
buffer: bufferComputeV,
|
||||
size: 1,
|
||||
divisor: 1,
|
||||
type: DISPLAY.gl.FLOAT
|
||||
} );
|
||||
|
||||
const bufferTriIndex = DISPLAY.glCat.createBuffer();
|
||||
bufferTriIndex.setVertexbuffer( ( () => {
|
||||
const ret = new Float32Array( 3 * options.trailLength );
|
||||
for ( let i = 0; i < options.trailLength; i ++ ) {
|
||||
ret[ i * 3 + 0 ] = 0;
|
||||
ret[ i * 3 + 1 ] = 1;
|
||||
ret[ i * 3 + 2 ] = 2;
|
||||
}
|
||||
return ret;
|
||||
} )() );
|
||||
|
||||
geometry.addAttribute( 'triIndex', {
|
||||
buffer: bufferTriIndex,
|
||||
size: 1,
|
||||
type: DISPLAY.gl.FLOAT
|
||||
} );
|
||||
|
||||
const indexBuffer = DISPLAY.glCat.createBuffer();
|
||||
indexBuffer.setIndexbuffer( ( () => {
|
||||
const ret = new Uint16Array( ( options.trailLength - 1 ) * 18 );
|
||||
for ( let i = 0; i < options.trailLength - 1; i ++ ) {
|
||||
for ( let j = 0; j < 3; j ++ ) {
|
||||
const jn = ( j + 1 ) % 3;
|
||||
ret[ i * 18 + j * 6 + 0 ] = i * 3 + j;
|
||||
ret[ i * 18 + j * 6 + 1 ] = i * 3 + 3 + j;
|
||||
ret[ i * 18 + j * 6 + 2 ] = i * 3 + 3 + jn;
|
||||
ret[ i * 18 + j * 6 + 3 ] = i * 3 + j;
|
||||
ret[ i * 18 + j * 6 + 4 ] = i * 3 + 3 + jn;
|
||||
ret[ i * 18 + j * 6 + 5 ] = i * 3 + jn;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} )() );
|
||||
|
||||
geometry.setIndex( {
|
||||
buffer: indexBuffer,
|
||||
type: DISPLAY.gl.UNSIGNED_SHORT
|
||||
} );
|
||||
|
||||
geometry.count = ( options.trailLength - 1 ) * 18;
|
||||
geometry.primcount = options.trails;
|
||||
geometry.mode = DISPLAY.gl.TRIANGLES;
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
private __createMaterialRender( options: TrailsOptions ): Material {
|
||||
const material = new Material(
|
||||
trailsRenderVert,
|
||||
trailsRenderFrag,
|
||||
{
|
||||
defines: {
|
||||
'USE_CLIP': 'true',
|
||||
'USE_VERTEX_COLOR': 'true'
|
||||
}
|
||||
},
|
||||
);
|
||||
material.addUniform( 'colorVar', '1f', 0.1 );
|
||||
material.addUniform( 'ppp', '1f', Trails.__ppp );
|
||||
material.addUniformTexture( 'samplerRandomStatic', options.textureRandomStatic );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/trails-render.vert', () => {
|
||||
material.replaceShader(
|
||||
trailsRenderVert,
|
||||
trailsRenderFrag,
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../shaders/trails-render.frag', () => {
|
||||
material.replaceShader(
|
||||
trailsRenderVert,
|
||||
trailsRenderFrag,
|
||||
);
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
4
src/frag.d.ts
vendored
Normal file
4
src/frag.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module '*.frag' {
|
||||
const code: string;
|
||||
export default code;
|
||||
}
|
148
src/geometries/genOctahedron.ts
Normal file
148
src/geometries/genOctahedron.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { GeometryAttribute, GeometryIndex } from '../heck/Geometry';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
|
||||
interface ResultGenOctahedron {
|
||||
position: GeometryAttribute;
|
||||
normal: GeometryAttribute;
|
||||
index: GeometryIndex;
|
||||
count: number;
|
||||
mode: GLenum;
|
||||
}
|
||||
|
||||
export function genOctahedron( options: {
|
||||
radius?: number;
|
||||
div?: number;
|
||||
} = {} ): ResultGenOctahedron {
|
||||
const radius = options.radius ?? 1.0;
|
||||
const div = options.div ?? 1;
|
||||
|
||||
const pos = [];
|
||||
const nor = [];
|
||||
const ind = [];
|
||||
|
||||
for ( let ii = 0; ii < 2; ii ++ ) { // side
|
||||
for ( let ip = 0; ip < 4; ip ++ ) { // plane
|
||||
for ( let iy = 0; iy < div + 1; iy ++ ) {
|
||||
for ( let ix = 0; ix < iy + 1; ix ++ ) {
|
||||
const lat0 = ( ii * 2.0 + iy / ( div + 1 ) ) * Math.PI / 2.0;
|
||||
const lat1 = ( ii * 2.0 + ( iy + 1 ) / ( div + 1 ) ) * Math.PI / 2.0;
|
||||
|
||||
const lon0 = ( ii * 2.0 - 1.0 ) * ( ( ix - 1 ) / Math.max( 1, iy ) + ip ) * Math.PI / 2.0;
|
||||
const lon1 = ( ii * 2.0 - 1.0 ) * ( ix / ( iy + 1 ) + ip ) * Math.PI / 2.0;
|
||||
const lon2 = ( ii * 2.0 - 1.0 ) * ( ix / Math.max( 1, iy ) + ip ) * Math.PI / 2.0;
|
||||
const lon3 = ( ii * 2.0 - 1.0 ) * ( ( ix + 1 ) / ( iy + 1 ) + ip ) * Math.PI / 2.0;
|
||||
|
||||
if ( ix !== 0 ) {
|
||||
ind.push(
|
||||
pos.length / 3,
|
||||
pos.length / 3 + 1,
|
||||
pos.length / 3 + 2
|
||||
);
|
||||
|
||||
const x1 = radius * Math.sin( lat0 ) * Math.cos( lon0 );
|
||||
const y1 = radius * Math.cos( lat0 );
|
||||
const z1 = radius * Math.sin( lat0 ) * Math.sin( lon0 );
|
||||
|
||||
const x2 = radius * Math.sin( lat1 ) * Math.cos( lon1 );
|
||||
const y2 = radius * Math.cos( lat1 );
|
||||
const z2 = radius * Math.sin( lat1 ) * Math.sin( lon1 );
|
||||
|
||||
const x3 = radius * Math.sin( lat0 ) * Math.cos( lon2 );
|
||||
const y3 = radius * Math.cos( lat0 );
|
||||
const z3 = radius * Math.sin( lat0 ) * Math.sin( lon2 );
|
||||
|
||||
pos.push(
|
||||
x1, y1, z1,
|
||||
x2, y2, z2,
|
||||
x3, y3, z3
|
||||
);
|
||||
|
||||
{
|
||||
const x = x1 + x2 + x3;
|
||||
const y = y1 + y2 + y3;
|
||||
const z = z1 + z2 + z3;
|
||||
const l = Math.sqrt( x * x + y * y + z * z );
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
nor.push(
|
||||
x / l,
|
||||
y / l,
|
||||
z / l
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ind.push(
|
||||
pos.length / 3,
|
||||
pos.length / 3 + 1,
|
||||
pos.length / 3 + 2
|
||||
);
|
||||
|
||||
const x1 = radius * Math.sin( lat0 ) * Math.cos( lon2 );
|
||||
const y1 = radius * Math.cos( lat0 );
|
||||
const z1 = radius * Math.sin( lat0 ) * Math.sin( lon2 );
|
||||
|
||||
const x2 = radius * Math.sin( lat1 ) * Math.cos( lon1 );
|
||||
const y2 = radius * Math.cos( lat1 );
|
||||
const z2 = radius * Math.sin( lat1 ) * Math.sin( lon1 );
|
||||
|
||||
const x3 = radius * Math.sin( lat1 ) * Math.cos( lon3 );
|
||||
const y3 = radius * Math.cos( lat1 );
|
||||
const z3 = radius * Math.sin( lat1 ) * Math.sin( lon3 );
|
||||
|
||||
pos.push(
|
||||
x1, y1, z1,
|
||||
x2, y2, z2,
|
||||
x3, y3, z3
|
||||
);
|
||||
|
||||
{
|
||||
const x = x1 + x2 + x3;
|
||||
const y = y1 + y2 + y3;
|
||||
const z = z1 + z2 + z3;
|
||||
const l = Math.sqrt( x * x + y * y + z * z );
|
||||
|
||||
for ( let i = 0; i < 3; i ++ ) {
|
||||
nor.push(
|
||||
x / l,
|
||||
y / l,
|
||||
z / l
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const position: GeometryAttribute = {
|
||||
buffer: DISPLAY.glCat.createBuffer(),
|
||||
type: DISPLAY.gl.FLOAT,
|
||||
size: 3
|
||||
};
|
||||
position.buffer.setVertexbuffer( new Float32Array( pos ) );
|
||||
|
||||
const normal: GeometryAttribute = {
|
||||
buffer: DISPLAY.glCat.createBuffer(),
|
||||
type: DISPLAY.gl.FLOAT,
|
||||
size: 3
|
||||
};
|
||||
normal.buffer.setVertexbuffer( new Float32Array( nor ) );
|
||||
|
||||
const index: GeometryIndex = {
|
||||
buffer: DISPLAY.glCat.createBuffer(),
|
||||
type: DISPLAY.gl.UNSIGNED_SHORT
|
||||
};
|
||||
index.buffer.setIndexbuffer( new Uint16Array( ind ) );
|
||||
|
||||
return {
|
||||
position,
|
||||
normal,
|
||||
index,
|
||||
count: ind.length,
|
||||
mode: DISPLAY.gl.TRIANGLES
|
||||
};
|
||||
}
|
87
src/geometries/genTorus.ts
Normal file
87
src/geometries/genTorus.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { GeometryAttribute, GeometryIndex } from '../heck/Geometry';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
|
||||
interface ResultGenTorus {
|
||||
position: GeometryAttribute;
|
||||
normal: GeometryAttribute;
|
||||
index: GeometryIndex;
|
||||
count: number;
|
||||
mode: GLenum;
|
||||
}
|
||||
|
||||
export function genTorus( options?: {
|
||||
radiusRadial?: number;
|
||||
radiusTubular?: number;
|
||||
segmentsRadial?: number;
|
||||
segmentsTubular?: number;
|
||||
} ): ResultGenTorus {
|
||||
const radiusRadial = options?.radiusRadial ?? 1;
|
||||
const radiusTubular = options?.radiusTubular ?? 0; // WHOA
|
||||
const segmentsRadial = options?.segmentsRadial ?? 64;
|
||||
const segmentsTubular = options?.segmentsTubular ?? 4;
|
||||
|
||||
const pos = [];
|
||||
const nor = [];
|
||||
const ind = [];
|
||||
|
||||
for ( let iRad = 0; iRad < segmentsRadial; iRad ++ ) {
|
||||
const iRadn = ( iRad + 1 ) % segmentsRadial;
|
||||
const tRad = 2.0 * Math.PI * iRad / segmentsRadial;
|
||||
const xRad = Math.cos( tRad );
|
||||
const yRad = Math.sin( tRad );
|
||||
|
||||
for ( let iTub = 0; iTub < segmentsTubular; iTub ++ ) {
|
||||
const iTubn = ( iTub + 1 ) % segmentsTubular;
|
||||
const tTub = 2.0 * Math.PI * iTub / segmentsTubular;
|
||||
const xTub = Math.cos( tTub );
|
||||
const yTub = Math.sin( tTub );
|
||||
|
||||
const nx = xRad * xTub;
|
||||
const ny = yTub;
|
||||
const nz = yRad * xTub;
|
||||
|
||||
pos.push(
|
||||
radiusRadial * xRad + radiusTubular * nx,
|
||||
0.0 + radiusTubular * ny,
|
||||
radiusRadial * yRad + radiusTubular * nz
|
||||
);
|
||||
nor.push( nx, ny, nz );
|
||||
ind.push(
|
||||
segmentsTubular * iRad + iTub,
|
||||
segmentsTubular * iRad + iTubn,
|
||||
segmentsTubular * iRadn + iTubn,
|
||||
segmentsTubular * iRad + iTub,
|
||||
segmentsTubular * iRadn + iTubn,
|
||||
segmentsTubular * iRadn + iTub,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const position: GeometryAttribute = {
|
||||
buffer: DISPLAY.glCat.createBuffer(),
|
||||
type: DISPLAY.gl.FLOAT,
|
||||
size: 3
|
||||
};
|
||||
position.buffer.setVertexbuffer( new Float32Array( pos ) );
|
||||
|
||||
const normal: GeometryAttribute = {
|
||||
buffer: DISPLAY.glCat.createBuffer(),
|
||||
type: DISPLAY.gl.FLOAT,
|
||||
size: 3
|
||||
};
|
||||
normal.buffer.setVertexbuffer( new Float32Array( nor ) );
|
||||
|
||||
const index: GeometryIndex = {
|
||||
buffer: DISPLAY.glCat.createBuffer(),
|
||||
type: DISPLAY.gl.UNSIGNED_SHORT
|
||||
};
|
||||
index.buffer.setIndexbuffer( new Uint16Array( ind ) );
|
||||
|
||||
return {
|
||||
position,
|
||||
normal,
|
||||
index,
|
||||
count: ind.length,
|
||||
mode: DISPLAY.gl.TRIANGLES
|
||||
};
|
||||
}
|
88
src/heck/BufferRenderTarget.ts
Normal file
88
src/heck/BufferRenderTarget.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { GLCatFramebuffer, GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { DISPLAY } from './DISPLAY';
|
||||
import { RenderTarget } from './RenderTarget';
|
||||
|
||||
export interface BufferRenderTargetOptions {
|
||||
width: number;
|
||||
height: number;
|
||||
numBuffers?: number;
|
||||
isFloat?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class BufferRenderTarget extends RenderTarget {
|
||||
public static nameMap = new Map<string, BufferRenderTarget>();
|
||||
|
||||
private readonly __framebuffer: GLCatFramebuffer<WebGL2RenderingContext>;
|
||||
|
||||
public get framebuffer(): GLCatFramebuffer<WebGL2RenderingContext> {
|
||||
return this.__framebuffer;
|
||||
}
|
||||
|
||||
private __width: number;
|
||||
|
||||
public get width(): number {
|
||||
return this.__width;
|
||||
}
|
||||
|
||||
private __height: number;
|
||||
|
||||
public get height(): number {
|
||||
return this.__height;
|
||||
}
|
||||
|
||||
private __numBuffers: number;
|
||||
|
||||
public get numBuffers(): number {
|
||||
return this.__numBuffers;
|
||||
}
|
||||
|
||||
public constructor( options: BufferRenderTargetOptions ) {
|
||||
super();
|
||||
|
||||
this.__framebuffer = DISPLAY.glCat.lazyDrawbuffers(
|
||||
options.width,
|
||||
options.height,
|
||||
options.numBuffers || 1,
|
||||
{
|
||||
isFloat: options.isFloat || true
|
||||
}
|
||||
);
|
||||
|
||||
this.__width = options.width;
|
||||
this.__height = options.height;
|
||||
this.__numBuffers = options.numBuffers || 1;
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( options.name ) {
|
||||
if ( BufferRenderTarget.nameMap.has( options.name ) ) {
|
||||
console.warn( `Duplicated BufferRenderTarget name, ${ options.name }` );
|
||||
return;
|
||||
}
|
||||
|
||||
BufferRenderTarget.nameMap.set( options.name, this );
|
||||
} else {
|
||||
console.warn( 'BufferRenderTarget created without name' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get texture(): GLCatTexture<WebGL2RenderingContext> {
|
||||
return this.__framebuffer.texture!;
|
||||
}
|
||||
|
||||
public getTexture( attachment: number ): GLCatTexture<WebGL2RenderingContext> | null {
|
||||
return this.__framebuffer.getTexture( attachment );
|
||||
}
|
||||
|
||||
public bind(): void {
|
||||
const { gl, glCat } = DISPLAY;
|
||||
gl.bindFramebuffer( gl.FRAMEBUFFER, this.__framebuffer.raw );
|
||||
glCat.drawBuffers( this.__numBuffers );
|
||||
gl.viewport( 0, 0, this.width, this.height );
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.__framebuffer.dispose();
|
||||
}
|
||||
}
|
19
src/heck/CanvasRenderTarget.ts
Normal file
19
src/heck/CanvasRenderTarget.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DISPLAY } from './DISPLAY';
|
||||
import { RenderTarget } from './RenderTarget';
|
||||
|
||||
export class CanvasRenderTarget extends RenderTarget {
|
||||
public get width(): number {
|
||||
return DISPLAY.canvas.width;
|
||||
}
|
||||
|
||||
public get height(): number {
|
||||
return DISPLAY.canvas.height;
|
||||
}
|
||||
|
||||
public bind(): void {
|
||||
const { gl, glCat } = DISPLAY;
|
||||
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
|
||||
glCat.drawBuffers( [ gl.BACK ] );
|
||||
gl.viewport( 0, 0, this.width, this.height );
|
||||
}
|
||||
}
|
17
src/heck/DISPLAY.ts
Normal file
17
src/heck/DISPLAY.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { RESOLUTION } from '../config';
|
||||
import { GLCat } from '@fms-cat/glcat-ts';
|
||||
|
||||
const canvas = document.createElement( 'canvas' );
|
||||
canvas.width = RESOLUTION[ 0 ];
|
||||
canvas.height = RESOLUTION[ 1 ];
|
||||
|
||||
const gl = canvas.getContext( 'webgl2' )!;
|
||||
gl.lineWidth( 1 );
|
||||
|
||||
const glCat = new GLCat( gl );
|
||||
|
||||
export const DISPLAY = {
|
||||
canvas,
|
||||
gl,
|
||||
glCat
|
||||
};
|
43
src/heck/Dog.ts
Normal file
43
src/heck/Dog.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Entity } from './Entity';
|
||||
import { Transform } from './Transform';
|
||||
import { Music } from '../Music';
|
||||
import { Component } from './components/Component';
|
||||
|
||||
let ha = 0;
|
||||
|
||||
/**
|
||||
* And what a WONDERFUL Dog they are!!
|
||||
*/
|
||||
export class Dog {
|
||||
public root: Entity;
|
||||
public music: Music;
|
||||
public active: boolean;
|
||||
|
||||
private __frameCount: number = 0;
|
||||
|
||||
public constructor( music: Music ) {
|
||||
this.root = new Entity();
|
||||
this.music = music;
|
||||
this.active = true;
|
||||
|
||||
const update = (): void => {
|
||||
if ( this.active ) {
|
||||
this.music.update();
|
||||
this.root.update( {
|
||||
frameCount: this.__frameCount ++,
|
||||
time: this.music.time,
|
||||
deltaTime: this.music.deltaTime,
|
||||
globalTransform: new Transform(),
|
||||
parent: null
|
||||
} );
|
||||
}
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
Component.resetUpdateBreakpoint();
|
||||
}
|
||||
|
||||
requestAnimationFrame( update );
|
||||
};
|
||||
update();
|
||||
}
|
||||
}
|
90
src/heck/Entity.ts
Normal file
90
src/heck/Entity.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Camera } from './components/Camera';
|
||||
import { Component } from './components/Component';
|
||||
import { Matrix4 } from '@fms-cat/experimental';
|
||||
import { RenderTarget } from './RenderTarget';
|
||||
import { Transform } from './Transform';
|
||||
|
||||
export interface EntityUpdateEvent {
|
||||
frameCount: number;
|
||||
time: number;
|
||||
deltaTime: number;
|
||||
globalTransform: Transform;
|
||||
parent: Entity | null;
|
||||
}
|
||||
|
||||
export interface EntityDrawEvent {
|
||||
frameCount: number;
|
||||
time: number;
|
||||
renderTarget: RenderTarget;
|
||||
globalTransform: Transform;
|
||||
viewMatrix: Matrix4;
|
||||
projectionMatrix: Matrix4;
|
||||
camera: Camera;
|
||||
}
|
||||
|
||||
export class Entity {
|
||||
public readonly transform = new Transform();
|
||||
|
||||
public active = true;
|
||||
public visible = true;
|
||||
|
||||
public children: Entity[] = [];
|
||||
public components: Component[] = [];
|
||||
|
||||
public update( event: EntityUpdateEvent ): void {
|
||||
if ( !this.active ) { return; }
|
||||
|
||||
const globalTransform = event.globalTransform.multiply( this.transform );
|
||||
|
||||
this.components.forEach( ( component ) => {
|
||||
component.update( {
|
||||
frameCount: event.frameCount,
|
||||
time: event.time,
|
||||
deltaTime: event.deltaTime,
|
||||
globalTransform,
|
||||
entity: this
|
||||
} );
|
||||
} );
|
||||
|
||||
this.children.forEach( ( child ) => {
|
||||
child.update( {
|
||||
frameCount: event.frameCount,
|
||||
time: event.time,
|
||||
deltaTime: event.deltaTime,
|
||||
globalTransform,
|
||||
parent: this
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
public draw( event: EntityDrawEvent ): void {
|
||||
if ( !this.visible ) { return; }
|
||||
|
||||
const globalTransform = event.globalTransform.multiply( this.transform );
|
||||
|
||||
this.components.forEach( ( component ) => {
|
||||
component.draw( {
|
||||
frameCount: event.frameCount,
|
||||
time: event.time,
|
||||
renderTarget: event.renderTarget,
|
||||
globalTransform,
|
||||
camera: event.camera,
|
||||
viewMatrix: event.viewMatrix,
|
||||
projectionMatrix: event.projectionMatrix,
|
||||
entity: this
|
||||
} );
|
||||
} );
|
||||
|
||||
this.children.forEach( ( child ) => {
|
||||
child.draw( {
|
||||
frameCount: event.frameCount,
|
||||
time: event.time,
|
||||
renderTarget: event.renderTarget,
|
||||
globalTransform,
|
||||
viewMatrix: event.viewMatrix,
|
||||
projectionMatrix: event.projectionMatrix,
|
||||
camera: event.camera
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
118
src/heck/Geometry.ts
Normal file
118
src/heck/Geometry.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { GLCatBuffer, GLCatTransformFeedback } from '@fms-cat/glcat-ts';
|
||||
import { DISPLAY } from './DISPLAY';
|
||||
import { Material } from './Material';
|
||||
|
||||
export interface GeometryAttribute {
|
||||
buffer: GLCatBuffer<WebGL2RenderingContext>;
|
||||
size: number;
|
||||
divisor?: number;
|
||||
type: GLenum;
|
||||
stride?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface GeometryIndex {
|
||||
buffer: GLCatBuffer<WebGL2RenderingContext>;
|
||||
type: GLenum;
|
||||
}
|
||||
|
||||
export class Geometry {
|
||||
public static typeSizeMap = {
|
||||
[ DISPLAY.gl.BYTE ]: 1,
|
||||
[ DISPLAY.gl.UNSIGNED_BYTE ]: 1,
|
||||
[ DISPLAY.gl.SHORT ]: 2,
|
||||
[ DISPLAY.gl.UNSIGNED_SHORT ]: 2,
|
||||
[ DISPLAY.gl.INT ]: 4,
|
||||
[ DISPLAY.gl.UNSIGNED_INT ]: 4,
|
||||
[ DISPLAY.gl.HALF_FLOAT ]: 2,
|
||||
[ DISPLAY.gl.FLOAT ]: 4,
|
||||
};
|
||||
|
||||
public transformFeedback?: GLCatTransformFeedback<WebGL2RenderingContext> | null;
|
||||
|
||||
protected __attributes: {
|
||||
[ name: string ]: GeometryAttribute;
|
||||
} = {};
|
||||
protected __index: GeometryIndex | null = null;
|
||||
|
||||
public mode: GLenum = /* GL_TRIANGLES */ 4;
|
||||
public first = 0;
|
||||
public count = 0;
|
||||
public primcount: number | null = null;
|
||||
|
||||
public addAttribute( name: string, attribute: GeometryAttribute ): void {
|
||||
this.__attributes[ name ] = attribute;
|
||||
}
|
||||
|
||||
public removeAttribute( name: string, alsoDisposeBuffer = true ): void {
|
||||
if ( alsoDisposeBuffer ) {
|
||||
this.__attributes[ name ].buffer.dispose();
|
||||
}
|
||||
|
||||
delete this.__attributes[ name ];
|
||||
}
|
||||
|
||||
public setIndex( index: GeometryIndex | null ): void {
|
||||
this.__index = index;
|
||||
}
|
||||
|
||||
public assignBuffers( material: Material ): void {
|
||||
const program = material.program;
|
||||
|
||||
Object.entries( this.__attributes ).forEach( ( [ name, attr ] ) => {
|
||||
program.attribute(
|
||||
name,
|
||||
attr.buffer,
|
||||
attr.size,
|
||||
attr.divisor,
|
||||
attr.type,
|
||||
attr.stride,
|
||||
attr.offset
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
public draw(): void {
|
||||
const { gl, glCat } = DISPLAY;
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( this.count === 0 ) {
|
||||
console.warn( 'You attempt to draw a geometry that count is 0' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( this.transformFeedback ) {
|
||||
glCat.bindTransformFeedback( this.transformFeedback, () => {
|
||||
gl.beginTransformFeedback( this.mode );
|
||||
this.drawElementsOrArrays();
|
||||
gl.endTransformFeedback();
|
||||
} );
|
||||
} else {
|
||||
this.drawElementsOrArrays();
|
||||
}
|
||||
}
|
||||
|
||||
public drawElementsOrArrays(): void {
|
||||
const { gl, glCat } = DISPLAY;
|
||||
|
||||
if ( this.__index ) {
|
||||
glCat.bindIndexBuffer( this.__index.buffer, () => {
|
||||
gl.drawElements(
|
||||
this.mode,
|
||||
this.count,
|
||||
this.__index!.type,
|
||||
this.first * Geometry.typeSizeMap[ this.__index!.type ]
|
||||
);
|
||||
} );
|
||||
} else {
|
||||
gl.drawArrays( this.mode, this.first, this.count );
|
||||
}
|
||||
}
|
||||
|
||||
public disposeBuffers(): void {
|
||||
Object.values( this.__attributes ).forEach( ( attr ) => {
|
||||
attr.buffer.dispose();
|
||||
} );
|
||||
}
|
||||
}
|
36
src/heck/InstancedGeometry.ts
Normal file
36
src/heck/InstancedGeometry.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { DISPLAY } from './DISPLAY';
|
||||
import { Geometry } from './Geometry';
|
||||
|
||||
export class InstancedGeometry extends Geometry {
|
||||
public primcount: number = 0;
|
||||
|
||||
public draw(): void {
|
||||
const { gl } = DISPLAY;
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( this.count === 0 ) {
|
||||
console.warn( 'You attempt to draw an instanced geometry that count is 0' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.primcount === 0 ) {
|
||||
console.warn( 'You attempt to draw an instanced geometry that primcount is 0' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( this.__index ) {
|
||||
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, this.__index.buffer.raw );
|
||||
gl.drawElementsInstanced(
|
||||
this.mode,
|
||||
this.count,
|
||||
this.__index.type,
|
||||
this.first * InstancedGeometry.typeSizeMap[ this.__index.type ],
|
||||
this.primcount
|
||||
);
|
||||
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
|
||||
} else {
|
||||
gl.drawArraysInstanced( this.mode, this.first, this.count, this.primcount );
|
||||
}
|
||||
}
|
||||
}
|
161
src/heck/Material.ts
Normal file
161
src/heck/Material.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { GLCatProgram, GLCatProgramLinkOptions, GLCatProgramUniformType, GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { DISPLAY } from './DISPLAY';
|
||||
import { SHADERPOOL } from './ShaderPool';
|
||||
|
||||
export class Material {
|
||||
protected __linkOptions: GLCatProgramLinkOptions;
|
||||
|
||||
protected __defines: {
|
||||
[ name: string ]: ( string | undefined );
|
||||
};
|
||||
|
||||
protected __uniforms: {
|
||||
[ name: string ]: {
|
||||
type: GLCatProgramUniformType;
|
||||
value: number[];
|
||||
};
|
||||
} = {};
|
||||
|
||||
protected __uniformVectors: {
|
||||
[ name: string ]: {
|
||||
type: GLCatProgramUniformType;
|
||||
value: Float32List | Int32List;
|
||||
};
|
||||
} = {};
|
||||
|
||||
protected __uniformTextures: {
|
||||
[ name: string ]: {
|
||||
texture: GLCatTexture<WebGL2RenderingContext> | null;
|
||||
};
|
||||
} = {};
|
||||
|
||||
private __vert: string;
|
||||
|
||||
public get vert(): string {
|
||||
return this.__vert;
|
||||
}
|
||||
|
||||
public get vertWithDefines(): string {
|
||||
return this.__withDefines( this.vert );
|
||||
}
|
||||
|
||||
private __frag: string;
|
||||
|
||||
public get frag(): string {
|
||||
return this.__frag;
|
||||
}
|
||||
|
||||
public get fragWithDefines(): string {
|
||||
return this.__withDefines( this.frag );
|
||||
}
|
||||
|
||||
public get program(): GLCatProgram<WebGL2RenderingContext> {
|
||||
return SHADERPOOL.getProgram(
|
||||
this,
|
||||
this.vertWithDefines,
|
||||
this.fragWithDefines,
|
||||
);
|
||||
}
|
||||
|
||||
public blend: [ GLenum, GLenum ] = [ DISPLAY.gl.ONE, DISPLAY.gl.ZERO ];
|
||||
|
||||
public constructor(
|
||||
vert: string,
|
||||
frag: string,
|
||||
options?: {
|
||||
defines?: { [ key: string ]: ( string | undefined ) },
|
||||
linkOptions?: GLCatProgramLinkOptions,
|
||||
},
|
||||
) {
|
||||
this.__vert = vert;
|
||||
this.__frag = frag;
|
||||
this.__linkOptions = options?.linkOptions ?? {};
|
||||
this.__defines = options?.defines ?? {};
|
||||
}
|
||||
|
||||
public addUniform( name: string, type: GLCatProgramUniformType, ...value: number[] ): void {
|
||||
this.__uniforms[ name ] = { type, value };
|
||||
}
|
||||
|
||||
public addUniformVector(
|
||||
name: string,
|
||||
type: GLCatProgramUniformType,
|
||||
value: Float32List | Int32List
|
||||
): void {
|
||||
this.__uniformVectors[ name ] = { type, value };
|
||||
}
|
||||
|
||||
public addUniformTexture( name: string, texture: GLCatTexture<WebGL2RenderingContext> | null ): void {
|
||||
this.__uniformTextures[ name ] = { texture };
|
||||
}
|
||||
|
||||
public setUniforms(): void {
|
||||
const program = this.program;
|
||||
|
||||
Object.entries( this.__uniforms ).forEach( ( [ name, { type, value } ] ) => {
|
||||
program.uniform( name, type, ...value );
|
||||
} );
|
||||
|
||||
Object.entries( this.__uniformVectors ).forEach( ( [ name, { type, value } ] ) => {
|
||||
program.uniformVector( name, type, value );
|
||||
} );
|
||||
|
||||
Object.entries( this.__uniformTextures ).forEach( ( [ name, { texture } ] ) => {
|
||||
program.uniformTexture( name, texture );
|
||||
} );
|
||||
}
|
||||
|
||||
public setBlendMode(): void {
|
||||
const { gl } = DISPLAY;
|
||||
|
||||
gl.blendFunc( ...this.blend );
|
||||
}
|
||||
|
||||
public async replaceShader(
|
||||
vert: string,
|
||||
frag: string,
|
||||
options?: {
|
||||
defines?: { [ key: string ]: ( string | undefined ) },
|
||||
linkOptions?: GLCatProgramLinkOptions,
|
||||
},
|
||||
): Promise<void> {
|
||||
if ( options?.defines ) {
|
||||
this.__defines = { ...options.defines };
|
||||
}
|
||||
|
||||
const program = await SHADERPOOL.getProgramAsync(
|
||||
this,
|
||||
this.__withDefines( vert ),
|
||||
this.__withDefines( frag ),
|
||||
options?.linkOptions,
|
||||
).catch( ( e ) => {
|
||||
console.error( e );
|
||||
} );
|
||||
|
||||
if ( program ) {
|
||||
const prevVert = this.vertWithDefines;
|
||||
const prevFrag = this.fragWithDefines;
|
||||
|
||||
this.__vert = vert;
|
||||
this.__frag = frag;
|
||||
|
||||
SHADERPOOL.discardProgram( this, prevVert, prevFrag );
|
||||
}
|
||||
}
|
||||
|
||||
protected __withDefines( code: string ): string {
|
||||
if ( !code.startsWith( '#version' ) ) {
|
||||
return code;
|
||||
}
|
||||
|
||||
const lines = code.split( '\n' );
|
||||
|
||||
Object.entries( this.__defines ).map( ( [ key, value ] ) => {
|
||||
if ( value != null ) {
|
||||
lines.splice( 1, 0, `#define ${key} ${value}` );
|
||||
}
|
||||
} );
|
||||
|
||||
return lines.join( '\n' );
|
||||
}
|
||||
}
|
7
src/heck/RenderTarget.ts
Normal file
7
src/heck/RenderTarget.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export abstract class RenderTarget {
|
||||
public abstract get width(): number;
|
||||
|
||||
public abstract get height(): number;
|
||||
|
||||
public abstract bind(): void;
|
||||
}
|
115
src/heck/ShaderPool.ts
Normal file
115
src/heck/ShaderPool.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { DISPLAY } from './DISPLAY';
|
||||
import { GLCatProgram, GLCatProgramLinkOptions } from '@fms-cat/glcat-ts';
|
||||
import { Material } from './Material';
|
||||
|
||||
export class ShaderPool<TUser> {
|
||||
private __programMap: Map<string, GLCatProgram<WebGL2RenderingContext>> = new Map();
|
||||
|
||||
private __ongoingPromises: Map<string, Promise<GLCatProgram<WebGL2RenderingContext>>> = new Map();
|
||||
|
||||
private __programUsersMap: Map<GLCatProgram<WebGL2RenderingContext>, Set<TUser>> = new Map();
|
||||
|
||||
public getProgram(
|
||||
user: TUser,
|
||||
vert: string,
|
||||
frag: string,
|
||||
options?: GLCatProgramLinkOptions,
|
||||
): GLCatProgram<WebGL2RenderingContext> {
|
||||
let program = this.__programMap.get( vert + frag );
|
||||
if ( !program ) {
|
||||
if ( process.env.DEV ) {
|
||||
try {
|
||||
program = DISPLAY.glCat.lazyProgram( vert, frag, options );
|
||||
} catch ( e ) {
|
||||
console.error( user );
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
program = DISPLAY.glCat.lazyProgram( vert, frag, options );
|
||||
}
|
||||
|
||||
this.__programMap.set( vert + frag, program );
|
||||
}
|
||||
|
||||
this.__setUser( user, program );
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
public async getProgramAsync(
|
||||
user: TUser,
|
||||
vert: string,
|
||||
frag: string,
|
||||
options?: GLCatProgramLinkOptions
|
||||
): Promise<GLCatProgram<WebGL2RenderingContext>> {
|
||||
let program = this.__programMap.get( vert + frag );
|
||||
if ( !program ) {
|
||||
let promise = this.__ongoingPromises.get( vert + frag );
|
||||
if ( !promise ) {
|
||||
if ( process.env.DEV ) {
|
||||
promise = DISPLAY.glCat.lazyProgramAsync( vert, frag, options ).catch( ( e ) => {
|
||||
console.error( user );
|
||||
throw e;
|
||||
} );
|
||||
} else {
|
||||
promise = DISPLAY.glCat.lazyProgramAsync( vert, frag, options );
|
||||
}
|
||||
|
||||
promise.then( ( program ) => {
|
||||
this.__programMap.set( vert + frag, program );
|
||||
this.__ongoingPromises.delete( vert + frag );
|
||||
} );
|
||||
this.__ongoingPromises.set( vert + frag, promise );
|
||||
}
|
||||
|
||||
program = await promise;
|
||||
}
|
||||
|
||||
this.__setUser( user, program );
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
public discardProgram(
|
||||
user: TUser,
|
||||
vert: string,
|
||||
frag: string,
|
||||
): void {
|
||||
const program = this.__programMap.get( vert + frag )!;
|
||||
|
||||
this.__deleteUser( user, program );
|
||||
|
||||
if ( this.__countUsers( program ) === 0 ) {
|
||||
program.dispose( true );
|
||||
this.__programMap.delete( vert + frag );
|
||||
}
|
||||
}
|
||||
|
||||
private __setUser( user: TUser, program: GLCatProgram<WebGL2RenderingContext> ): void {
|
||||
let users = this.__programUsersMap.get( program );
|
||||
if ( !users ) {
|
||||
users = new Set();
|
||||
this.__programUsersMap.set( program, users );
|
||||
}
|
||||
|
||||
if ( !users.has( user ) ) {
|
||||
users.add( user );
|
||||
}
|
||||
}
|
||||
|
||||
private __deleteUser( user: TUser, program: GLCatProgram<WebGL2RenderingContext> ): void {
|
||||
const users = this.__programUsersMap.get( program )!;
|
||||
|
||||
if ( !users.has( user ) ) {
|
||||
throw new Error( 'Attempt to delete an user of the program but the specified user is not an owner' );
|
||||
}
|
||||
users.delete( user );
|
||||
}
|
||||
|
||||
private __countUsers( program: GLCatProgram<WebGL2RenderingContext> ): number {
|
||||
const users = this.__programUsersMap.get( program )!;
|
||||
return users.size;
|
||||
}
|
||||
}
|
||||
|
||||
export const SHADERPOOL = new ShaderPool<Material>();
|
64
src/heck/Transform.ts
Normal file
64
src/heck/Transform.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Matrix4, Quaternion, Vector3 } from '@fms-cat/experimental';
|
||||
|
||||
export class Transform {
|
||||
protected __position: Vector3 = Vector3.zero;
|
||||
|
||||
public get position(): Vector3 {
|
||||
return this.__position;
|
||||
}
|
||||
|
||||
public set position( vector: Vector3 ) {
|
||||
this.__position = vector;
|
||||
|
||||
this.__matrix = Matrix4.compose( this.__position, this.__rotation, this.__scale );
|
||||
}
|
||||
|
||||
protected __rotation: Quaternion = Quaternion.identity;
|
||||
|
||||
public get rotation(): Quaternion {
|
||||
return this.__rotation;
|
||||
}
|
||||
|
||||
public set rotation( quaternion: Quaternion ) {
|
||||
this.__rotation = quaternion;
|
||||
|
||||
this.__matrix = Matrix4.compose( this.__position, this.__rotation, this.__scale );
|
||||
}
|
||||
|
||||
protected __scale: Vector3 = Vector3.one;
|
||||
|
||||
public get scale(): Vector3 {
|
||||
return this.__scale;
|
||||
}
|
||||
|
||||
public set scale( vector: Vector3 ) {
|
||||
this.__scale = vector;
|
||||
|
||||
this.__matrix = Matrix4.compose( this.__position, this.__rotation, this.__scale );
|
||||
}
|
||||
|
||||
protected __matrix: Matrix4 = Matrix4.identity;
|
||||
|
||||
public get matrix(): Matrix4 {
|
||||
return this.__matrix;
|
||||
}
|
||||
|
||||
public set matrix( matrix: Matrix4 ) {
|
||||
this.__matrix = matrix;
|
||||
|
||||
const decomposed = this.__matrix.decompose();
|
||||
this.__position = decomposed.position;
|
||||
this.__rotation = decomposed.rotation;
|
||||
this.__scale = decomposed.scale;
|
||||
}
|
||||
|
||||
public lookAt( position: Vector3, target?: Vector3, up?: Vector3, roll?: number ): void {
|
||||
this.matrix = Matrix4.lookAt( position, target, up, roll );
|
||||
}
|
||||
|
||||
public multiply( transform: Transform ): Transform {
|
||||
const result = new Transform();
|
||||
result.matrix = this.matrix.multiply( transform.matrix );
|
||||
return result;
|
||||
}
|
||||
}
|
76
src/heck/components/Camera.ts
Normal file
76
src/heck/components/Camera.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Component, ComponentOptions, ComponentUpdateEvent } from './Component';
|
||||
import { DISPLAY } from '../DISPLAY';
|
||||
import { Entity } from '../Entity';
|
||||
import { Matrix4 } from '@fms-cat/experimental';
|
||||
import { RenderTarget } from '../RenderTarget';
|
||||
import { Transform } from '../Transform';
|
||||
|
||||
export interface CameraOptions extends ComponentOptions {
|
||||
renderTarget?: RenderTarget;
|
||||
projectionMatrix: Matrix4;
|
||||
scene?: Entity;
|
||||
clear?: Array<number | undefined> | false;
|
||||
}
|
||||
|
||||
export abstract class Camera extends Component {
|
||||
protected __projectionMatrix: Matrix4;
|
||||
|
||||
public get projectionMatrix(): Matrix4 {
|
||||
return this.__projectionMatrix;
|
||||
}
|
||||
|
||||
public renderTarget?: RenderTarget;
|
||||
|
||||
public scene?: Entity;
|
||||
|
||||
public clear: Array<number | undefined> | false = [];
|
||||
|
||||
public abstract get near(): number;
|
||||
|
||||
public abstract get far(): number;
|
||||
|
||||
public constructor( options: CameraOptions ) {
|
||||
super( options );
|
||||
|
||||
this.visible = false;
|
||||
|
||||
this.renderTarget = options.renderTarget;
|
||||
this.scene = options.scene;
|
||||
this.__projectionMatrix = options.projectionMatrix;
|
||||
if ( options.clear !== undefined ) { this.clear = options.clear; }
|
||||
}
|
||||
|
||||
protected __updateImpl( event: ComponentUpdateEvent ): void {
|
||||
const { renderTarget, scene } = this;
|
||||
|
||||
if ( !renderTarget ) {
|
||||
throw new Error( process.env.DEV && 'You must assign a renderTarget to the Camera' );
|
||||
}
|
||||
|
||||
if ( !scene ) {
|
||||
throw new Error( process.env.DEV && 'You must assign a scene to the Camera' );
|
||||
}
|
||||
|
||||
const viewMatrix = event.globalTransform.matrix.inverse!;
|
||||
|
||||
renderTarget.bind();
|
||||
|
||||
if ( this.clear ) {
|
||||
DISPLAY.glCat.clear( ...this.clear );
|
||||
}
|
||||
|
||||
scene.draw( {
|
||||
frameCount: event.frameCount,
|
||||
time: event.time,
|
||||
renderTarget: renderTarget,
|
||||
globalTransform: new Transform(),
|
||||
viewMatrix,
|
||||
projectionMatrix: this.__projectionMatrix,
|
||||
camera: this
|
||||
} );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
Component.resetDrawBreakpoint();
|
||||
}
|
||||
}
|
||||
}
|
189
src/heck/components/Component.ts
Normal file
189
src/heck/components/Component.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { Camera } from './Camera';
|
||||
import { Entity } from '../Entity';
|
||||
import { Matrix4 } from '@fms-cat/experimental';
|
||||
import { RenderTarget } from '../RenderTarget';
|
||||
import { Transform } from '../Transform';
|
||||
import { COMPONENT_DRAW_BREAKPOINT, COMPONENT_UPDATE_BREAKPOINT } from '../../config-hot';
|
||||
|
||||
export interface ComponentUpdateEvent {
|
||||
frameCount: number;
|
||||
time: number;
|
||||
deltaTime: number;
|
||||
globalTransform: Transform;
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
export interface ComponentDrawEvent {
|
||||
frameCount: number;
|
||||
time: number;
|
||||
camera: Camera;
|
||||
renderTarget: RenderTarget;
|
||||
globalTransform: Transform;
|
||||
viewMatrix: Matrix4;
|
||||
projectionMatrix: Matrix4;
|
||||
entity: Entity;
|
||||
}
|
||||
|
||||
export interface ComponentOptions {
|
||||
active?: boolean;
|
||||
visible?: boolean;
|
||||
name?: string;
|
||||
ignoreBreakpoints?: boolean;
|
||||
}
|
||||
|
||||
export class Component {
|
||||
public static nameMap = new Map<string, Component>();
|
||||
private static __updateHaveReachedBreakpoint = false;
|
||||
private static __drawHaveReachedBreakpoint = false;
|
||||
|
||||
public static resetUpdateBreakpoint(): void {
|
||||
if ( process.env.DEV ) {
|
||||
if ( window.divComponentsUpdate ) {
|
||||
if ( window.divComponentsUpdate.innerHTML !== window.strComponentsUpdate ) {
|
||||
window.divComponentsUpdate.innerHTML = window.strComponentsUpdate ?? '';
|
||||
}
|
||||
}
|
||||
window.strComponentsUpdate = '== update ==';
|
||||
|
||||
this.__updateHaveReachedBreakpoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static resetDrawBreakpoint(): void {
|
||||
if ( process.env.DEV ) {
|
||||
if ( window.divComponentsDraw ) {
|
||||
if ( window.divComponentsDraw.innerHTML !== window.strComponentsDraw ) {
|
||||
window.divComponentsDraw.innerHTML = window.strComponentsDraw ?? '';
|
||||
}
|
||||
}
|
||||
window.strComponentsDraw = '== draw ==';
|
||||
|
||||
this.__drawHaveReachedBreakpoint = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected __lastUpdateFrame = 0;
|
||||
|
||||
public active: boolean;
|
||||
public visible: boolean;
|
||||
|
||||
private __name?: string;
|
||||
public get name(): string | undefined {
|
||||
return this.__name;
|
||||
}
|
||||
public set name( name: string | undefined ) {
|
||||
if ( process.env.DEV ) {
|
||||
// remove previous one from the nameMap
|
||||
if ( this.__name != null ) {
|
||||
Component.nameMap.delete( this.__name );
|
||||
}
|
||||
|
||||
this.__name = name;
|
||||
|
||||
// set the current one to the nameMap
|
||||
if ( name != null ) {
|
||||
if ( Component.nameMap.has( name ) ) {
|
||||
console.warn( `Duplicated Component name, ${ name }` );
|
||||
return;
|
||||
}
|
||||
|
||||
Component.nameMap.set( name, this );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ignoreBreakpoints?: boolean;
|
||||
|
||||
public constructor( options?: ComponentOptions ) {
|
||||
this.active = options?.active ?? true;
|
||||
this.visible = options?.visible ?? true;
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
this.name = options?.name;
|
||||
this.ignoreBreakpoints = options?.ignoreBreakpoints;
|
||||
|
||||
if ( !this.name ) {
|
||||
console.warn( 'Component created without name' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public update( event: ComponentUpdateEvent ): void {
|
||||
if ( !this.active ) { return; }
|
||||
if ( this.__lastUpdateFrame === event.frameCount ) { return; }
|
||||
this.__lastUpdateFrame = event.frameCount;
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( Component.__updateHaveReachedBreakpoint && !this.ignoreBreakpoints ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( window.strComponentsUpdate != null ) {
|
||||
window.strComponentsUpdate += `\n${ this.name }`;
|
||||
}
|
||||
}
|
||||
|
||||
this.__updateImpl( event );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( COMPONENT_UPDATE_BREAKPOINT != null && COMPONENT_UPDATE_BREAKPOINT === this.name ) {
|
||||
Component.__updateHaveReachedBreakpoint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected __updateImpl( event: ComponentUpdateEvent ): void { // eslint-disable-line
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public draw( event: ComponentDrawEvent ): void {
|
||||
if ( !this.visible ) { return; }
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( window.strComponentsDraw != null ) {
|
||||
window.strComponentsDraw += `\n${ this.name }`;
|
||||
}
|
||||
|
||||
if ( Component.__drawHaveReachedBreakpoint && !this.ignoreBreakpoints ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.__drawImpl( event );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
if ( COMPONENT_DRAW_BREAKPOINT != null && COMPONENT_DRAW_BREAKPOINT === this.name ) {
|
||||
Component.__drawHaveReachedBreakpoint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected __drawImpl( event: ComponentDrawEvent ): void { // eslint-disable-line
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
const checkBreakpointNames = () => {
|
||||
if (
|
||||
COMPONENT_UPDATE_BREAKPOINT != null &&
|
||||
Component.nameMap.get( COMPONENT_UPDATE_BREAKPOINT ?? '' ) == null
|
||||
) {
|
||||
console.warn( `Component: Cannot retrieve a component, COMPONENT_UPDATE_BREAKPOINT: ${ COMPONENT_UPDATE_BREAKPOINT }` );
|
||||
}
|
||||
|
||||
if (
|
||||
COMPONENT_DRAW_BREAKPOINT != null &&
|
||||
Component.nameMap.get( COMPONENT_DRAW_BREAKPOINT ?? '' ) == null
|
||||
) {
|
||||
console.warn( `Component: Cannot retrieve a component, COMPONENT_DRAW_BREAKPOINT: ${ COMPONENT_DRAW_BREAKPOINT }` );
|
||||
}
|
||||
};
|
||||
checkBreakpointNames();
|
||||
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( '../../config-hot', () => {
|
||||
checkBreakpointNames();
|
||||
} );
|
||||
}
|
||||
}
|
26
src/heck/components/Lambda.ts
Normal file
26
src/heck/components/Lambda.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Component, ComponentDrawEvent, ComponentOptions, ComponentUpdateEvent } from './Component';
|
||||
|
||||
export interface LambdaOptions extends ComponentOptions {
|
||||
onUpdate?: ( event: ComponentUpdateEvent ) => void;
|
||||
onDraw?: ( event: ComponentDrawEvent ) => void;
|
||||
}
|
||||
|
||||
export class Lambda extends Component {
|
||||
public onUpdate?: ( event: ComponentUpdateEvent ) => void;
|
||||
public onDraw?: ( event: ComponentDrawEvent ) => void;
|
||||
|
||||
public constructor( options?: LambdaOptions ) {
|
||||
super( options );
|
||||
|
||||
this.onUpdate = options?.onUpdate;
|
||||
this.onDraw = options?.onDraw;
|
||||
}
|
||||
|
||||
protected __updateImpl( event: ComponentUpdateEvent ): void {
|
||||
this.onUpdate && this.onUpdate( event );
|
||||
}
|
||||
|
||||
protected __drawImpl( event: ComponentDrawEvent ): void {
|
||||
this.onDraw && this.onDraw( event );
|
||||
}
|
||||
}
|
18
src/heck/components/LogTransform.ts
Normal file
18
src/heck/components/LogTransform.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Component, ComponentOptions, ComponentUpdateEvent } from './Component';
|
||||
|
||||
export class LogTransform extends Component {
|
||||
public constructor( options?: ComponentOptions ) {
|
||||
super( options );
|
||||
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
protected __updateImpl( event: ComponentUpdateEvent ): void {
|
||||
console.info( `
|
||||
Position: ${ event.globalTransform.position }
|
||||
Rotation: ${ event.globalTransform.rotation }
|
||||
Scale: ${ event.globalTransform.scale }
|
||||
Matrix: ${ event.globalTransform.matrix }
|
||||
` );
|
||||
}
|
||||
}
|
70
src/heck/components/Mesh.ts
Normal file
70
src/heck/components/Mesh.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Component, ComponentDrawEvent, ComponentOptions } from './Component';
|
||||
import { DISPLAY } from '../DISPLAY';
|
||||
import { Geometry } from '../Geometry';
|
||||
import { Material } from '../Material';
|
||||
|
||||
export enum MeshCull {
|
||||
None,
|
||||
Front,
|
||||
Back,
|
||||
Both
|
||||
}
|
||||
|
||||
const meshCullMap = {
|
||||
[ MeshCull.Front ]: /* GL_FRONT */ 1028,
|
||||
[ MeshCull.Back ]: /* GL_BACK */ 1029,
|
||||
[ MeshCull.Both ]: /* GL_FRONT_AND_BACK */ 1032
|
||||
};
|
||||
|
||||
export interface MeshOptions extends ComponentOptions {
|
||||
geometry: Geometry;
|
||||
material: Material;
|
||||
}
|
||||
|
||||
export class Mesh extends Component {
|
||||
public geometry: Geometry;
|
||||
public material: Material;
|
||||
|
||||
public cull: MeshCull = MeshCull.Back;
|
||||
|
||||
public constructor( options: MeshOptions ) {
|
||||
super( options );
|
||||
|
||||
this.active = false;
|
||||
|
||||
this.geometry = options.geometry;
|
||||
this.material = options.material;
|
||||
}
|
||||
|
||||
protected __drawImpl( event: ComponentDrawEvent ): void {
|
||||
const glCat = DISPLAY.glCat;
|
||||
const gl = glCat.renderingContext;
|
||||
|
||||
const program = this.material.program;
|
||||
|
||||
glCat.useProgram( program );
|
||||
this.material.setBlendMode();
|
||||
|
||||
if ( this.cull === MeshCull.None ) {
|
||||
gl.disable( gl.CULL_FACE );
|
||||
} else {
|
||||
gl.enable( gl.CULL_FACE );
|
||||
gl.cullFace( meshCullMap[ this.cull ] );
|
||||
}
|
||||
|
||||
this.geometry.assignBuffers( this.material );
|
||||
|
||||
this.material.setUniforms();
|
||||
|
||||
program.uniform1f( 'time', event.time );
|
||||
program.uniform1f( 'frameCount', event.frameCount );
|
||||
program.uniform2f( 'resolution', event.renderTarget.width, event.renderTarget.height );
|
||||
|
||||
program.uniformMatrix4fv( 'normalMatrix', event.globalTransform.matrix.inverse!.transpose.elements );
|
||||
program.uniformMatrix4fv( 'modelMatrix', event.globalTransform.matrix.elements );
|
||||
program.uniformMatrix4fv( 'viewMatrix', event.viewMatrix.elements );
|
||||
program.uniformMatrix4fv( 'projectionMatrix', event.projectionMatrix.elements );
|
||||
|
||||
this.geometry.draw();
|
||||
}
|
||||
}
|
47
src/heck/components/PerspectiveCamera.ts
Normal file
47
src/heck/components/PerspectiveCamera.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Camera } from './Camera';
|
||||
import { Entity } from '../Entity';
|
||||
import { Matrix4 } from '@fms-cat/experimental';
|
||||
import { RenderTarget } from '../RenderTarget';
|
||||
import { ComponentOptions } from './Component';
|
||||
|
||||
export interface PerspectiveCameraOptions extends ComponentOptions {
|
||||
renderTarget?: RenderTarget;
|
||||
near?: number;
|
||||
far?: number;
|
||||
fov?: number;
|
||||
scene?: Entity;
|
||||
clear?: Array<number | undefined> | false;
|
||||
}
|
||||
|
||||
export class PerspectiveCamera extends Camera {
|
||||
private __near: number;
|
||||
|
||||
public get near(): number {
|
||||
return this.__near;
|
||||
}
|
||||
|
||||
private __far: number;
|
||||
|
||||
public get far(): number {
|
||||
return this.__far;
|
||||
}
|
||||
|
||||
public constructor( options: PerspectiveCameraOptions ) {
|
||||
const projectionMatrix = Matrix4.perspective(
|
||||
options.fov || 45.0,
|
||||
options.near || 0.01,
|
||||
options.far || 100.0,
|
||||
);
|
||||
|
||||
super( {
|
||||
...options,
|
||||
projectionMatrix,
|
||||
renderTarget: options.renderTarget,
|
||||
scene: options.scene,
|
||||
clear: options.clear
|
||||
} );
|
||||
|
||||
this.__near = options.near || 0.01;
|
||||
this.__far = options.far || 100.0;
|
||||
}
|
||||
}
|
74
src/heck/components/Quad.ts
Normal file
74
src/heck/components/Quad.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Component, ComponentOptions, ComponentUpdateEvent } from './Component';
|
||||
import { DISPLAY } from '../DISPLAY';
|
||||
import { Geometry } from '../Geometry';
|
||||
import { Material } from '../Material';
|
||||
import { RenderTarget } from '../RenderTarget';
|
||||
import { TRIANGLE_STRIP_QUAD } from '@fms-cat/experimental';
|
||||
|
||||
const quadBuffer = DISPLAY.glCat.createBuffer();
|
||||
quadBuffer.setVertexbuffer( new Float32Array( TRIANGLE_STRIP_QUAD ) );
|
||||
|
||||
const quadGeometry = new Geometry();
|
||||
quadGeometry.addAttribute( 'p', {
|
||||
buffer: quadBuffer,
|
||||
size: 2,
|
||||
type: /* GL_FLOAT */ 5126
|
||||
} );
|
||||
|
||||
quadGeometry.count = 4;
|
||||
quadGeometry.mode = /* GL_TRIANGLE_STRIP */ 5;
|
||||
|
||||
export interface QuadOptions extends ComponentOptions {
|
||||
material: Material;
|
||||
target: RenderTarget;
|
||||
range?: [ number, number, number, number ];
|
||||
clear?: Array<number | undefined> | false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a fullscreen quad.
|
||||
*/
|
||||
export class Quad extends Component {
|
||||
public material: Material;
|
||||
public target: RenderTarget;
|
||||
public range: [ number, number, number, number ] = [ -1.0, -1.0, 1.0, 1.0 ];
|
||||
public clear: Array<number | undefined> | false = false;
|
||||
|
||||
public constructor( options: QuadOptions ) {
|
||||
super( options );
|
||||
|
||||
this.visible = false;
|
||||
|
||||
this.material = options.material;
|
||||
this.target = options.target;
|
||||
if ( options.range !== undefined ) { this.range = options.range; }
|
||||
if ( options.clear !== undefined ) { this.clear = options.clear; }
|
||||
}
|
||||
|
||||
protected __updateImpl( event: ComponentUpdateEvent ): void {
|
||||
const { glCat } = DISPLAY;
|
||||
|
||||
glCat.useProgram( this.material.program );
|
||||
|
||||
this.target.bind();
|
||||
this.material.setBlendMode();
|
||||
|
||||
if ( this.clear ) {
|
||||
glCat.clear( ...this.clear );
|
||||
}
|
||||
|
||||
quadGeometry.assignBuffers( this.material );
|
||||
|
||||
this.material.setUniforms();
|
||||
|
||||
const program = this.material.program;
|
||||
|
||||
program.uniform1f( 'time', event.time );
|
||||
program.uniform1f( 'deltaTime', event.deltaTime );
|
||||
program.uniform1f( 'frameCount', event.frameCount );
|
||||
program.uniform2f( 'resolution', this.target.width, this.target.height );
|
||||
program.uniform4f( 'range', ...this.range );
|
||||
|
||||
quadGeometry.draw();
|
||||
}
|
||||
}
|
354
src/main.ts
Normal file
354
src/main.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
import { Automaton } from '@fms-cat/automaton';
|
||||
import { AutomatonWithGUI } from '@fms-cat/automaton-with-gui';
|
||||
import * as automatonFxs from './automaton-fxs/automatonFxs';
|
||||
import { Music } from './Music';
|
||||
import automatonData from './automaton.json';
|
||||
import { DISPLAY } from './heck/DISPLAY';
|
||||
import { Dog } from './heck/Dog';
|
||||
import { CanvasRenderTarget } from './heck/CanvasRenderTarget';
|
||||
import { Lambda } from './heck/components/Lambda';
|
||||
import { RandomTexture } from './utils/RandomTexture';
|
||||
import { RANDOM_RESOLUTION, STATIC_RANDOM_RESOLUTION } from './config';
|
||||
import { SphereParticles } from './entities/SphereParticles';
|
||||
import { Swap, Vector3 } from '@fms-cat/experimental';
|
||||
import { BufferRenderTarget } from './heck/BufferRenderTarget';
|
||||
import { CameraEntity } from './entities/CameraEntity';
|
||||
import { LightEntity } from './entities/LightEntity';
|
||||
import { Bloom } from './entities/Bloom';
|
||||
import { Post } from './entities/Post';
|
||||
import { Raymarcher } from './entities/Raymarcher';
|
||||
import { Trails } from './entities/Trails';
|
||||
import { Glitch } from './entities/Glitch';
|
||||
import { Rings } from './entities/Rings';
|
||||
import { RTInspector } from './entities/RTInspector';
|
||||
import { Component } from './heck/components/Component';
|
||||
|
||||
// == gl ===========================================================================================
|
||||
const { canvas, glCat } = DISPLAY;
|
||||
|
||||
// == music ========================================================================================
|
||||
const audio = new AudioContext();
|
||||
const music = new Music( glCat, audio );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
music.isPlaying = true;
|
||||
}
|
||||
|
||||
// == dom ==========================================================================================
|
||||
document.body.style.margin = '0';
|
||||
document.body.style.padding = '0';
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
document.body.style.background = '#000';
|
||||
document.body.style.width = '100%';
|
||||
|
||||
document.body.appendChild( canvas );
|
||||
canvas.style.left = '0';
|
||||
canvas.style.top = '0';
|
||||
canvas.style.width = 'calc( 100% - 240px )';
|
||||
|
||||
window.divPath = document.createElement( 'div' );
|
||||
document.body.appendChild( window.divPath );
|
||||
window.divPath.style.position = 'fixed';
|
||||
window.divPath.style.textAlign = 'right';
|
||||
window.divPath.style.right = '8px';
|
||||
window.divPath.style.bottom = '248px';
|
||||
window.divPath.style.textShadow = '1px 1px 1px #ffffff';
|
||||
|
||||
window.divAutomaton = document.createElement( 'div' );
|
||||
document.body.appendChild( window.divAutomaton );
|
||||
window.divAutomaton.style.position = 'fixed';
|
||||
window.divAutomaton.style.width = '100%';
|
||||
window.divAutomaton.style.height = '240px';
|
||||
window.divAutomaton.style.right = '0';
|
||||
window.divAutomaton.style.bottom = '0';
|
||||
|
||||
window.checkActive = document.createElement( 'input' );
|
||||
document.body.appendChild( window.checkActive );
|
||||
window.checkActive.type = 'checkbox';
|
||||
window.checkActive.checked = true;
|
||||
window.checkActive.style.position = 'fixed';
|
||||
window.checkActive.style.left = '8px';
|
||||
window.checkActive.style.bottom = '248px';
|
||||
|
||||
window.strComponentsUpdate = '';
|
||||
|
||||
window.divComponentsUpdate = document.createElement( 'div' );
|
||||
document.body.appendChild( window.divComponentsUpdate );
|
||||
window.divComponentsUpdate.style.whiteSpace = 'pre-wrap';
|
||||
window.divComponentsUpdate.style.color = '#ffffff';
|
||||
window.divComponentsUpdate.style.font = '500 10px Wt-Position-Mono';
|
||||
window.divComponentsUpdate.style.position = 'fixed';
|
||||
window.divComponentsUpdate.style.padding = '0';
|
||||
window.divComponentsUpdate.style.boxSizing = 'border-box';
|
||||
window.divComponentsUpdate.style.width = '240px';
|
||||
window.divComponentsUpdate.style.height = 'calc( ( 100% - 240px ) * 0.5 )';
|
||||
window.divComponentsUpdate.style.right = '0';
|
||||
window.divComponentsUpdate.style.top = '0';
|
||||
window.divComponentsUpdate.style.overflow = 'scroll';
|
||||
|
||||
window.strComponentsDraw = '';
|
||||
|
||||
window.divComponentsDraw = document.createElement( 'div' );
|
||||
document.body.appendChild( window.divComponentsDraw );
|
||||
window.divComponentsDraw.style.whiteSpace = 'pre-wrap';
|
||||
window.divComponentsDraw.style.color = '#ffffff';
|
||||
window.divComponentsDraw.style.font = '500 10px Wt-Position-Mono';
|
||||
window.divComponentsDraw.style.position = 'fixed';
|
||||
window.divComponentsDraw.style.padding = '0';
|
||||
window.divComponentsDraw.style.boxSizing = 'border-box';
|
||||
window.divComponentsDraw.style.width = '240px';
|
||||
window.divComponentsDraw.style.height = 'calc( ( 100% - 240px ) * 0.5 )';
|
||||
window.divComponentsDraw.style.right = '0';
|
||||
window.divComponentsDraw.style.top = 'calc( ( 100% - 240px ) * 0.5 )';
|
||||
window.divComponentsDraw.style.overflow = 'scroll';
|
||||
} else {
|
||||
canvas.style.position = 'fixed';
|
||||
canvas.style.left = '0';
|
||||
canvas.style.top = '0';
|
||||
document.body.style.width = canvas.style.width = '100%';
|
||||
document.body.style.height = canvas.style.height = '100%';
|
||||
|
||||
const button = document.createElement( 'a' );
|
||||
document.body.appendChild( button );
|
||||
button.innerHTML = 'click me!';
|
||||
|
||||
button.onclick = () => {
|
||||
document.body.appendChild( canvas );
|
||||
music.isPlaying = true;
|
||||
document.body.requestFullscreen();
|
||||
};
|
||||
}
|
||||
|
||||
// == automaton ====================================================================================
|
||||
let totalFrame = 0;
|
||||
let isInitialFrame = true;
|
||||
|
||||
let automaton: Automaton;
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
// this cast smells so bad
|
||||
// https://github.com/FMS-Cat/automaton/issues/121
|
||||
const automatonWithGUI = new AutomatonWithGUI(
|
||||
automatonData,
|
||||
{
|
||||
gui: window.divAutomaton,
|
||||
isPlaying: true,
|
||||
fxDefinitions: automatonFxs,
|
||||
}
|
||||
);
|
||||
|
||||
automatonWithGUI.on( 'play', () => { music.isPlaying = true; } );
|
||||
automatonWithGUI.on( 'pause', () => { music.isPlaying = false; } );
|
||||
automatonWithGUI.on( 'seek', ( { time } ) => {
|
||||
music.time = Math.max( 0.0, time );
|
||||
automaton.reset();
|
||||
} );
|
||||
|
||||
if ( module.hot ) {
|
||||
module.hot.accept( './automaton.json', () => {
|
||||
// we probably don't need this feature for now...
|
||||
// See: https://github.com/FMS-Cat/automaton/issues/120
|
||||
// automatonWithGUI.deserialize( automatonData );
|
||||
} );
|
||||
}
|
||||
|
||||
automaton = automatonWithGUI;
|
||||
} else {
|
||||
// this cast smells so bad
|
||||
// https://github.com/FMS-Cat/automaton/issues/121
|
||||
automaton = new Automaton(
|
||||
automatonData,
|
||||
{
|
||||
fxDefinitions: automatonFxs
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// == random texture ===============================================================================
|
||||
const randomTexture = new RandomTexture(
|
||||
glCat,
|
||||
RANDOM_RESOLUTION[ 0 ],
|
||||
RANDOM_RESOLUTION[ 1 ]
|
||||
);
|
||||
randomTexture.update();
|
||||
|
||||
const randomTextureStatic = new RandomTexture(
|
||||
glCat,
|
||||
STATIC_RANDOM_RESOLUTION[ 0 ],
|
||||
STATIC_RANDOM_RESOLUTION[ 1 ]
|
||||
);
|
||||
randomTextureStatic.update();
|
||||
|
||||
// == scene ========================================================================================
|
||||
const dog = new Dog( music );
|
||||
|
||||
const canvasRenderTarget = new CanvasRenderTarget();
|
||||
|
||||
// Mr. Update Everything
|
||||
dog.root.components.push( new Lambda( {
|
||||
onUpdate: () => {
|
||||
totalFrame ++;
|
||||
isInitialFrame = false;
|
||||
|
||||
randomTexture.update();
|
||||
automaton.update( music.time );
|
||||
},
|
||||
visible: false,
|
||||
name: process.env.DEV && 'main/update',
|
||||
} ) );
|
||||
|
||||
// -- "objects" ------------------------------------------------------------------------------------
|
||||
const sphereParticles = new SphereParticles( {
|
||||
particlesSqrt: 256,
|
||||
textureRandom: randomTexture.texture,
|
||||
textureRandomStatic: randomTextureStatic.texture
|
||||
} );
|
||||
dog.root.children.push( sphereParticles.entity );
|
||||
|
||||
const trails = new Trails( {
|
||||
trails: 4096,
|
||||
trailLength: 64,
|
||||
textureRandom: randomTexture.texture,
|
||||
textureRandomStatic: randomTextureStatic.texture
|
||||
} );
|
||||
dog.root.children.push( trails.entity );
|
||||
|
||||
const rings = new Rings();
|
||||
dog.root.children.push( rings.entity );
|
||||
|
||||
const raymarcher = new Raymarcher( {
|
||||
textureRandom: randomTexture.texture,
|
||||
textureRandomStatic: randomTextureStatic.texture
|
||||
} );
|
||||
dog.root.children.push( raymarcher.entity );
|
||||
|
||||
// -- things that is not an "object" ---------------------------------------------------------------
|
||||
const swapOptions = {
|
||||
width: canvasRenderTarget.width,
|
||||
height: canvasRenderTarget.height
|
||||
};
|
||||
|
||||
const swap = new Swap(
|
||||
new BufferRenderTarget( {
|
||||
...swapOptions,
|
||||
name: process.env.DEV && 'main/postSwap0',
|
||||
} ),
|
||||
new BufferRenderTarget( {
|
||||
...swapOptions,
|
||||
name: process.env.DEV && 'main/postSwap1',
|
||||
} ),
|
||||
);
|
||||
|
||||
const light = new LightEntity( {
|
||||
root: dog.root,
|
||||
shadowMapFov: 90.0,
|
||||
shadowMapNear: 1.0,
|
||||
shadowMapFar: 20.0,
|
||||
namePrefix: process.env.DEV && 'light1',
|
||||
} );
|
||||
light.color = [ 60.0, 60.0, 60.0 ];
|
||||
light.entity.transform.lookAt( new Vector3( [ -1.0, 2.0, 8.0 ] ) );
|
||||
dog.root.children.push( light.entity );
|
||||
|
||||
const light2 = new LightEntity( {
|
||||
root: dog.root,
|
||||
shadowMapFov: 90.0,
|
||||
shadowMapNear: 1.0,
|
||||
shadowMapFar: 20.0,
|
||||
namePrefix: process.env.DEV && 'light2',
|
||||
} );
|
||||
light2.color = [ 50.0, 30.0, 40.0 ];
|
||||
light2.entity.transform.lookAt( new Vector3( [ -4.0, -2.0, 6.0 ] ) );
|
||||
dog.root.children.push( light2.entity );
|
||||
|
||||
const camera = new CameraEntity( {
|
||||
root: dog.root,
|
||||
target: swap.o,
|
||||
lights: [
|
||||
light,
|
||||
// light2
|
||||
],
|
||||
textureRandom: randomTexture.texture
|
||||
} );
|
||||
camera.camera.clear = [ 0.0, 0.0, 0.0, 0.0 ];
|
||||
camera.entity.components.unshift( new Lambda( {
|
||||
onUpdate: ( event ) => {
|
||||
const t1 = 0.02 * Math.sin( event.time );
|
||||
const s1 = Math.sin( t1 );
|
||||
const c1 = Math.cos( t1 );
|
||||
const t2 = 0.02 * Math.cos( event.time );
|
||||
const s2 = Math.sin( t2 );
|
||||
const c2 = Math.cos( t2 );
|
||||
const r = 5.0;
|
||||
|
||||
camera.entity.transform.lookAt( new Vector3( [
|
||||
r * c1 * s2,
|
||||
r * s1,
|
||||
r * c1 * c2
|
||||
] ) );
|
||||
},
|
||||
visible: false,
|
||||
name: process.env.DEV && 'main/updateCamera',
|
||||
} ) );
|
||||
dog.root.children.push( camera.entity );
|
||||
|
||||
swap.swap();
|
||||
const bloom = new Bloom( {
|
||||
input: swap.i.texture,
|
||||
target: swap.o
|
||||
} );
|
||||
dog.root.children.push( bloom.entity );
|
||||
|
||||
swap.swap();
|
||||
const glitch = new Glitch( {
|
||||
input: swap.i.texture,
|
||||
target: swap.o,
|
||||
automaton,
|
||||
} );
|
||||
dog.root.children.push( glitch.entity );
|
||||
|
||||
swap.swap();
|
||||
const post = new Post( {
|
||||
input: swap.i.texture,
|
||||
target: canvasRenderTarget
|
||||
} );
|
||||
dog.root.children.push( post.entity );
|
||||
|
||||
if ( process.env.DEV ) {
|
||||
const rtInspector = new RTInspector( {
|
||||
target: canvasRenderTarget
|
||||
} );
|
||||
dog.root.children.push( rtInspector.entity );
|
||||
}
|
||||
|
||||
// -- keyboards ------------------------------------------------------------------------------------
|
||||
if ( process.env.DEV ) {
|
||||
window.addEventListener( 'keydown', ( event ) => {
|
||||
if ( event.key === 'Escape' ) { // panic button
|
||||
dog.root.active = false;
|
||||
music.isPlaying = false;
|
||||
window.checkActive!.checked = false;
|
||||
}
|
||||
} );
|
||||
|
||||
window.checkActive!.addEventListener( 'input', ( event: any ) => {
|
||||
dog.root.active = event.target.checked;
|
||||
music.isPlaying = event.target.checked;
|
||||
} );
|
||||
}
|
||||
|
||||
if ( !process.env.DEV ) {
|
||||
window.addEventListener( 'keydown', ( event ) => {
|
||||
if ( event.key === 'Escape' ) { // panic button
|
||||
dog.root.active = false;
|
||||
music.isPlaying = false;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// -- wenis ----------------------------------------------------------------------------------------
|
||||
if ( process.env.DEV ) {
|
||||
console.info( Component.nameMap );
|
||||
console.info( BufferRenderTarget.nameMap );
|
||||
}
|
54
src/shaders/-distFunc.glsl
Normal file
54
src/shaders/-distFunc.glsl
Normal file
@@ -0,0 +1,54 @@
|
||||
float fractSin( float i ) {
|
||||
return fract( sin( i ) * 1846.42 );
|
||||
}
|
||||
|
||||
// https://www.iquilezles.org/www/articles/smin/smin.htm
|
||||
float smin( float a, float b, float k ) {
|
||||
float h = max( k - abs( a - b ), 0.0 ) / k;
|
||||
return min( a, b ) - h * h * h * k * ( 1.0 / 6.0 );
|
||||
}
|
||||
|
||||
mat2 rot2d( float t ) {
|
||||
float c = cos( t );
|
||||
float s = sin( t );
|
||||
return mat2( c, -s, s, c );
|
||||
}
|
||||
|
||||
vec3 ifs( vec3 p, vec3 r, vec3 t ) {
|
||||
vec3 s = t;
|
||||
|
||||
for ( int i = 0; i < 5; i ++ ) {
|
||||
p = abs( p ) - abs( s ) * pow( 0.5, float( i ) );
|
||||
|
||||
s.yz = rot2d( r.x ) * s.yz;
|
||||
s.zx = rot2d( r.y ) * s.zx;
|
||||
s.xy = rot2d( r.z ) * s.xy;
|
||||
|
||||
p.xy = p.x < p.y ? p.yx : p.xy;
|
||||
p.yz = p.y < p.z ? p.zy : p.yz;
|
||||
p.xz = p.x < p.z ? p.zx : p.xz;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
float box( vec3 p, vec3 d ) {
|
||||
vec3 absp = abs( p );
|
||||
return max( ( absp.x - d.x ), max( ( absp.y - d.y ), ( absp.z - d.z ) ) );
|
||||
}
|
||||
|
||||
float distFunc( vec3 p, float time ) {
|
||||
float dist = 1E9;
|
||||
for ( int i = 0; i < 5; i ++ ) {
|
||||
float fi = float( i );
|
||||
vec3 trans = 0.5 * vec3(
|
||||
sin( 6.0 * fractSin( fi * 2.874 ) + time * ( 1.0 + fractSin( fi * 2.271 ) ) ),
|
||||
sin( 6.0 * fractSin( fi * 4.512 ) + time * ( 1.0 + fractSin( fi * 1.271 ) ) ),
|
||||
sin( 6.0 * fractSin( fi * 3.112 ) + time * ( 1.0 + fractSin( fi * 3.271 ) ) )
|
||||
);
|
||||
dist = smin( dist, length( p - trans ) - 0.5, 1.0 );
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
#pragma glslify: export(distFunc)
|
18
src/shaders/-prng.glsl
Normal file
18
src/shaders/-prng.glsl
Normal file
@@ -0,0 +1,18 @@
|
||||
float GPURnd(inout vec4 n)
|
||||
{
|
||||
// Based on the post http://gpgpu.org/forums/viewtopic.php?t=2591&sid=17051481b9f78fb49fba5b98a5e0f1f3
|
||||
// (The page no longer exists as of March 17th, 2015. Please let me know if you see why this code works.)
|
||||
const vec4 q = vec4( 1225.0, 1585.0, 2457.0, 2098.0);
|
||||
const vec4 r = vec4( 1112.0, 367.0, 92.0, 265.0);
|
||||
const vec4 a = vec4( 3423.0, 2646.0, 1707.0, 1999.0);
|
||||
const vec4 m = vec4(4194287.0, 4194277.0, 4194191.0, 4194167.0);
|
||||
|
||||
vec4 beta = floor(n / q);
|
||||
vec4 p = a * (n - beta * q) - beta * r;
|
||||
beta = (sign(-p) + vec4(1.0)) * vec4(0.5) * m;
|
||||
n = (p + beta);
|
||||
|
||||
return fract(dot(n / m, vec4(1.0, -1.0, 1.0, -1.0)));
|
||||
}
|
||||
|
||||
#pragma glslify: export(GPURnd)
|
127
src/shaders/-simplex4d.glsl
Normal file
127
src/shaders/-simplex4d.glsl
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Description : Array and textureless GLSL 2D/3D/4D simplex
|
||||
// noise functions.
|
||||
// Author : Ian McEwan, Ashima Arts.
|
||||
// Maintainer : ijm
|
||||
// Lastmod : 20110822 (ijm)
|
||||
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
|
||||
// Distributed under the MIT License. See LICENSE file.
|
||||
// https://github.com/ashima/webgl-noise
|
||||
//
|
||||
|
||||
vec4 v4Mod289(vec4 x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
|
||||
float fMod289(float x) {
|
||||
return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
|
||||
vec4 v4Permute(vec4 x) {
|
||||
return v4Mod289(((x*34.0)+1.0)*x);
|
||||
}
|
||||
|
||||
float fPermute(float x) {
|
||||
return fMod289(((x*34.0)+1.0)*x);
|
||||
}
|
||||
|
||||
vec4 v4TaylorInvSqrt(vec4 r)
|
||||
{
|
||||
return 1.79284291400159 - 0.85373472095314 * r;
|
||||
}
|
||||
|
||||
float fTaylorInvSqrt(float r)
|
||||
{
|
||||
return 1.79284291400159 - 0.85373472095314 * r;
|
||||
}
|
||||
|
||||
vec4 grad4(float j, vec4 ip)
|
||||
{
|
||||
const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
|
||||
vec4 p,s;
|
||||
|
||||
p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
|
||||
p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
|
||||
s = vec4(lessThan(p, vec4(0.0)));
|
||||
p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
float snoise(vec4 v)
|
||||
{
|
||||
const vec4 C = vec4( 0.138196601125011, // (5 - sqrt(5))/20 G4
|
||||
0.276393202250021, // 2 * G4
|
||||
0.414589803375032, // 3 * G4
|
||||
-0.447213595499958); // -1 + 4 * G4
|
||||
|
||||
// First corner
|
||||
vec4 i = floor(v + dot(v, vec4(0.309016994374947451)) ); // (sqrt(5) - 1)/4
|
||||
vec4 x0 = v - i + dot(i, C.xxxx);
|
||||
|
||||
// Other corners
|
||||
|
||||
// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
|
||||
vec4 i0;
|
||||
vec3 isX = step( x0.yzw, x0.xxx );
|
||||
vec3 isYZ = step( x0.zww, x0.yyz );
|
||||
// i0.x = dot( isX, vec3( 1.0 ) );
|
||||
i0.x = isX.x + isX.y + isX.z;
|
||||
i0.yzw = 1.0 - isX;
|
||||
// i0.y += dot( isYZ.xy, vec2( 1.0 ) );
|
||||
i0.y += isYZ.x + isYZ.y;
|
||||
i0.zw += 1.0 - isYZ.xy;
|
||||
i0.z += isYZ.z;
|
||||
i0.w += 1.0 - isYZ.z;
|
||||
|
||||
// i0 now contains the unique values 0,1,2,3 in each channel
|
||||
vec4 i3 = clamp( i0, 0.0, 1.0 );
|
||||
vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
|
||||
vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
|
||||
|
||||
// x0 = x0 - 0.0 + 0.0 * C.xxxx
|
||||
// x1 = x0 - i1 + 1.0 * C.xxxx
|
||||
// x2 = x0 - i2 + 2.0 * C.xxxx
|
||||
// x3 = x0 - i3 + 3.0 * C.xxxx
|
||||
// x4 = x0 - 1.0 + 4.0 * C.xxxx
|
||||
vec4 x1 = x0 - i1 + C.xxxx;
|
||||
vec4 x2 = x0 - i2 + C.yyyy;
|
||||
vec4 x3 = x0 - i3 + C.zzzz;
|
||||
vec4 x4 = x0 + C.wwww;
|
||||
|
||||
// Permutations
|
||||
i = v4Mod289(i);
|
||||
float j0 = fPermute( fPermute( fPermute( fPermute(i.w) + i.z) + i.y) + i.x);
|
||||
vec4 j1 = v4Permute( v4Permute( v4Permute( v4Permute (
|
||||
i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
|
||||
+ i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
|
||||
+ i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
|
||||
+ i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
|
||||
|
||||
// Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope
|
||||
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
|
||||
vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
|
||||
|
||||
vec4 p0 = grad4(j0, ip);
|
||||
vec4 p1 = grad4(j1.x, ip);
|
||||
vec4 p2 = grad4(j1.y, ip);
|
||||
vec4 p3 = grad4(j1.z, ip);
|
||||
vec4 p4 = grad4(j1.w, ip);
|
||||
|
||||
// Normalise gradients
|
||||
vec4 norm = v4TaylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
|
||||
p0 *= norm.x;
|
||||
p1 *= norm.y;
|
||||
p2 *= norm.z;
|
||||
p3 *= norm.w;
|
||||
p4 *= fTaylorInvSqrt(dot(p4,p4));
|
||||
|
||||
// Mix contributions from the five corners
|
||||
vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
|
||||
vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0);
|
||||
m0 = m0 * m0;
|
||||
m1 = m1 * m1;
|
||||
return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
|
||||
+ dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
|
||||
|
||||
}
|
||||
|
||||
#pragma glslify: export(snoise)
|
78
src/shaders/ao.frag
Normal file
78
src/shaders/ao.frag
Normal file
@@ -0,0 +1,78 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int AO_ITER = 16;
|
||||
const float AO_BIAS = 0.0;
|
||||
const float AO_RADIUS = 0.5;
|
||||
const float PI = 3.14159265359;
|
||||
const float TAU = 6.28318530718;
|
||||
const float EPSILON = 1E-3;
|
||||
|
||||
#define saturate(x) clamp(x,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 resolution;
|
||||
uniform mat4 cameraPV;
|
||||
uniform sampler2D sampler0; // position.xyz, depth
|
||||
uniform sampler2D sampler1; // normal.xyz
|
||||
uniform sampler2D samplerRandom;
|
||||
|
||||
// == commons ======================================================================================
|
||||
#pragma glslify: prng = require( ./-prng );
|
||||
|
||||
vec3 randomDirection( inout vec4 seed ) {
|
||||
float phi16_ = TAU * prng( seed );
|
||||
float theta = acos( -1.0 + 2.0 * prng( seed ) );
|
||||
|
||||
return vec3(
|
||||
sin( theta ) * sin( phi16_ ),
|
||||
cos( theta ),
|
||||
sin( theta ) * cos( phi16_ )
|
||||
);
|
||||
}
|
||||
|
||||
// == features =====================================================================================
|
||||
float ambientOcclusion( vec2 uv, vec3 position, vec3 normal ) {
|
||||
float ao = 0.0;
|
||||
|
||||
vec4 seed = texture( samplerRandom, uv );
|
||||
prng( seed );
|
||||
|
||||
for ( int i = 0; i < AO_ITER; i ++ ) {
|
||||
vec3 dir = randomDirection( seed ) * prng( seed );
|
||||
if ( dot( dir, normal ) < 0.0 ) { dir = -dir; }
|
||||
|
||||
vec4 screenPos = cameraPV * vec4( position + dir * AO_RADIUS, 1.0 );
|
||||
screenPos.x *= resolution.y / resolution.x;
|
||||
vec2 screenUv = screenPos.xy / screenPos.w * 0.5 + 0.5;
|
||||
vec4 s0 = texture( sampler0, screenUv );
|
||||
|
||||
vec3 dDir = s0.xyz - position;
|
||||
if ( length( dDir ) < 1E-2 ) {
|
||||
ao += 1.0;
|
||||
} else {
|
||||
float dNor = dot( normalize( normal ), normalize( dDir ) );
|
||||
ao += 1.0 - saturate( dNor - AO_BIAS ) / ( length( dDir ) + 1.0 );
|
||||
}
|
||||
}
|
||||
|
||||
ao = ao / float( AO_ITER );
|
||||
return ao;
|
||||
}
|
||||
|
||||
// == main procedure ===============================================================================
|
||||
void main() {
|
||||
vec4 tex0 = texture( sampler0, vUv );
|
||||
vec4 tex1 = texture( sampler1, vUv );
|
||||
|
||||
vec3 position = tex0.xyz;
|
||||
vec3 normal = tex1.xyz;
|
||||
|
||||
float ao = ambientOcclusion( vUv, position, normal );
|
||||
fragColor = vec4( vec3( ao ), 1.0 );
|
||||
}
|
35
src/shaders/bloom-blur.frag
Normal file
35
src/shaders/bloom-blur.frag
Normal file
@@ -0,0 +1,35 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform bool isVert;
|
||||
uniform vec2 resolution;
|
||||
uniform sampler2D sampler0;
|
||||
|
||||
void main() {
|
||||
vec2 halfTexel = 1.0 / resolution; // * 0.5;
|
||||
|
||||
float lv = ceil( -log( 1.0 - min( vUv.x, vUv.y ) ) / log( 2.0 ) );
|
||||
float p = pow( 0.5, lv );
|
||||
vec2 clampMin = floor( vUv / p ) * p + halfTexel;
|
||||
vec2 clampMax = clampMin + p - 2.0 * halfTexel;
|
||||
|
||||
vec2 bv = halfTexel * ( isVert ? vec2( 0.0, 1.0 ) : vec2( 1.0, 0.0 ) );
|
||||
vec4 sum = vec4( 0.0 );
|
||||
|
||||
sum += 0.2270270270 * texture( sampler0, vUv );
|
||||
vec2 suv = clamp( vUv - bv * 1.3846153846, clampMin, clampMax );
|
||||
sum += 0.3162162162 * texture( sampler0, suv );
|
||||
suv = clamp( vUv + bv * 1.3846153846, clampMin, clampMax );
|
||||
sum += 0.3162162162 * texture( sampler0, suv );
|
||||
suv = clamp( vUv - bv * 3.2307692308, clampMin, clampMax );
|
||||
sum += 0.0702702703 * texture( sampler0, suv );
|
||||
suv = clamp( vUv + bv * 3.2307692308, clampMin, clampMax );
|
||||
sum += 0.0702702703 * texture( sampler0, suv );
|
||||
|
||||
fragColor = vec4( sum.xyz, 1.0 );
|
||||
}
|
20
src/shaders/bloom-post.frag
Normal file
20
src/shaders/bloom-post.frag
Normal file
@@ -0,0 +1,20 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D samplerDry;
|
||||
uniform sampler2D samplerWet;
|
||||
|
||||
void main() {
|
||||
fragColor = texture( samplerDry, vUv );
|
||||
for ( int i = 0; i < 5; i ++ ) {
|
||||
float fuck = pow( 0.5, float( i ) );
|
||||
vec2 suv = mix( vec2( 1.0 - fuck ), vec2( 1.0 - 0.5 * fuck ), vUv );
|
||||
fragColor += texture( samplerWet, suv );
|
||||
}
|
||||
fragColor.xyz = max( vec3( 0.0 ), fragColor.xyz );
|
||||
}
|
16
src/shaders/bloom-pre.frag
Normal file
16
src/shaders/bloom-pre.frag
Normal file
@@ -0,0 +1,16 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D sampler0;
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(
|
||||
max( vec3( 0.0 ), ( texture( sampler0, vUv ).xyz - 1.0 ) * 1.0 ),
|
||||
1.0
|
||||
);
|
||||
}
|
2
src/shaders/discard.frag
Normal file
2
src/shaders/discard.frag
Normal file
@@ -0,0 +1,2 @@
|
||||
#version 300 es
|
||||
void main(){discard;}
|
63
src/shaders/glitch.frag
Normal file
63
src/shaders/glitch.frag
Normal file
@@ -0,0 +1,63 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int BARREL_ITER = 10;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
#define lofi(i,m) (floor((i)/(m))*(m))
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform float time;
|
||||
uniform float amp;
|
||||
uniform float barrelAmp;
|
||||
uniform float barrelOffset;
|
||||
uniform vec2 resolution;
|
||||
uniform sampler2D sampler0;
|
||||
|
||||
// == common =======================================================================================
|
||||
float fractSin( float v ) {
|
||||
return fract( 17.351 * sin( 27.119 * v ) );
|
||||
}
|
||||
|
||||
// == glitch =======================================================================================
|
||||
vec2 displace( vec2 uv, float threshold ) {
|
||||
float seed = fractSin( lofi( uv.y, 0.0625 ) + fractSin( lofi( uv.x, 0.25 ) ) );
|
||||
if ( seed < threshold ) { return vec2( 0.0 ); }
|
||||
|
||||
vec2 d = vec2( 0.0 );
|
||||
seed = fractSin( seed );
|
||||
d.x = seed - 0.5;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
// == fetch ========================================================================================
|
||||
vec4 fetch( vec2 uv ) {
|
||||
vec2 uvt = saturate( uv );
|
||||
vec4 color = texture( sampler0, uvt );
|
||||
return color;
|
||||
}
|
||||
|
||||
// == main procedure ===============================================================================
|
||||
void main() {
|
||||
vec2 uv = vUv.xy;
|
||||
|
||||
vec2 d = vec2( 0.0 );
|
||||
for ( int i = 0; i < 3; i ++ ) {
|
||||
float p = pow( 2.4, float( i ) );
|
||||
float thr = 1.0 - pow( amp, 6.0 );
|
||||
thr = thr * pow( thr, float( i ) );
|
||||
d += displace( uv * p + 50.0 * fractSin( 0.1 * time ), thr ) * 0.4 / p;
|
||||
}
|
||||
|
||||
vec4 col = vec4( 0.0 );
|
||||
col += fetch( uv + d * 0.60 ) * vec4( 1.0, 0.0, 0.0, 1.0 );
|
||||
col += fetch( uv + d * 0.90 ) * vec4( 0.0, 1.0, 0.0, 1.0 );
|
||||
col += fetch( uv + d * 1.20 ) * vec4( 0.0, 0.0, 1.0, 1.0 );
|
||||
|
||||
fragColor = col;
|
||||
}
|
47
src/shaders/music.vert
Normal file
47
src/shaders/music.vert
Normal file
@@ -0,0 +1,47 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
const float TAU = 6.28318530718;
|
||||
|
||||
uniform float bpm;
|
||||
uniform float sampleRate;
|
||||
uniform float _deltaSample;
|
||||
uniform vec4 timeLength;
|
||||
uniform vec4 _timeHead;
|
||||
|
||||
in float off;
|
||||
|
||||
out float outL;
|
||||
out float outR;
|
||||
|
||||
float kick( float t ) {
|
||||
if ( t < 0.0 ) { return 0.0; }
|
||||
|
||||
float attack = 4.0;
|
||||
|
||||
return exp( -4.0 * t ) * sin( TAU * (
|
||||
50.0 * t - attack * ( exp( -40.0 * t ) + exp( -10.0 * t ) )
|
||||
) );
|
||||
}
|
||||
|
||||
vec2 mainAudio( vec4 time ) {
|
||||
vec2 dest = vec2( 0.0 );
|
||||
|
||||
float tKick = time.x; // time.x = a beat
|
||||
float aKick = kick( tKick );
|
||||
dest += 0.0 * aKick;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 out2 = mainAudio( mod( _timeHead + off * _deltaSample, timeLength ) );
|
||||
outL = out2.x;
|
||||
outR = out2.y;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
23
src/shaders/normal.frag
Normal file
23
src/shaders/normal.frag
Normal file
@@ -0,0 +1,23 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int MTL_UNLIT = 1;
|
||||
|
||||
in float vLife;
|
||||
in vec4 vPosition;
|
||||
in vec3 vNormal;
|
||||
|
||||
layout (location = 0) out vec4 fragPosition;
|
||||
layout (location = 1) out vec4 fragNormal;
|
||||
layout (location = 2) out vec4 fragColor;
|
||||
layout (location = 3) out vec4 fragWTF;
|
||||
|
||||
uniform float time;
|
||||
|
||||
void main() {
|
||||
fragPosition = vPosition;
|
||||
fragNormal = vec4( vNormal, 1.0 );
|
||||
fragColor = vec4( 0.5 + 0.5 * vNormal, 1.0 );
|
||||
fragWTF = vec4( vec3( 0.0, 0.0, 0.0 ), MTL_UNLIT );
|
||||
}
|
40
src/shaders/object-inflate.vert
Normal file
40
src/shaders/object-inflate.vert
Normal file
@@ -0,0 +1,40 @@
|
||||
#version 300 es
|
||||
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
in vec2 uv;
|
||||
|
||||
out vec4 vPosition;
|
||||
out vec3 vNormal;
|
||||
out vec2 vUv;
|
||||
|
||||
#ifdef USE_VERTEX_COLOR
|
||||
in vec4 color;
|
||||
out vec4 vColor;
|
||||
#endif
|
||||
|
||||
uniform float inflate;
|
||||
uniform vec2 resolution;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 normalMatrix;
|
||||
|
||||
// ------
|
||||
|
||||
void main() {
|
||||
vNormal = normalize( ( normalMatrix * vec4( normal, 1.0 ) ).xyz );
|
||||
|
||||
vPosition = modelMatrix * vec4( position + inflate * normal, 1.0 );
|
||||
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
|
||||
outPos.x *= resolution.y / resolution.x;
|
||||
gl_Position = outPos;
|
||||
|
||||
vPosition.w = outPos.z / outPos.w;
|
||||
|
||||
#ifdef USE_VERTEX_COLOR
|
||||
vColor = color;
|
||||
#endif
|
||||
|
||||
vUv = uv;
|
||||
}
|
39
src/shaders/object.vert
Normal file
39
src/shaders/object.vert
Normal file
@@ -0,0 +1,39 @@
|
||||
#version 300 es
|
||||
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
in vec2 uv;
|
||||
|
||||
out vec4 vPosition;
|
||||
out vec3 vNormal;
|
||||
out vec2 vUv;
|
||||
|
||||
#ifdef USE_VERTEX_COLOR
|
||||
in vec4 color;
|
||||
out vec4 vColor;
|
||||
#endif
|
||||
|
||||
uniform vec2 resolution;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 normalMatrix;
|
||||
|
||||
// ------
|
||||
|
||||
void main() {
|
||||
vNormal = normalize( ( normalMatrix * vec4( normal, 1.0 ) ).xyz );
|
||||
|
||||
vPosition = modelMatrix * vec4( position, 1.0 );
|
||||
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
|
||||
outPos.x *= resolution.y / resolution.x;
|
||||
gl_Position = outPos;
|
||||
|
||||
vPosition.w = outPos.z / outPos.w;
|
||||
|
||||
#ifdef USE_VERTEX_COLOR
|
||||
vColor = color;
|
||||
#endif
|
||||
|
||||
vUv = uv;
|
||||
}
|
24
src/shaders/pos-to-depth.frag
Normal file
24
src/shaders/pos-to-depth.frag
Normal file
@@ -0,0 +1,24 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
#define saturate(x) clamp(x,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 cameraNearFar;
|
||||
uniform vec3 cameraPos;
|
||||
uniform sampler2D sampler0;
|
||||
|
||||
void main() {
|
||||
vec4 tex = texture( sampler0, vUv );
|
||||
float depth = linearstep(
|
||||
cameraNearFar.x,
|
||||
cameraNearFar.y,
|
||||
length( cameraPos - tex.xyz )
|
||||
);
|
||||
fragColor = vec4( depth, depth * depth, depth, 1.0 );
|
||||
}
|
66
src/shaders/post.frag
Normal file
66
src/shaders/post.frag
Normal file
@@ -0,0 +1,66 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int BARREL_ITER = 10;
|
||||
const float BARREL_OFFSET = 0.05;
|
||||
const float BARREL_AMP = 0.05;
|
||||
const float HUGE = 9E16;
|
||||
const float PI = 3.14159265;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
#define lofi(i,m) (floor((i)/(m))*(m))
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform float time;
|
||||
uniform vec2 resolution;
|
||||
uniform sampler2D sampler0;
|
||||
|
||||
vec3 colorMap( vec3 i ) {
|
||||
return vec3(
|
||||
smoothstep( 0.0, 1.0, i.r ),
|
||||
i.g,
|
||||
0.1 + 0.8 * i.b
|
||||
);
|
||||
}
|
||||
|
||||
vec3 barrel( float amp, vec2 uv ) {
|
||||
float corn = length( vec2( 0.5 ) );
|
||||
float a = min( 3.0 * sqrt( amp ), corn * PI );
|
||||
float zoom = corn / ( tan( corn * a ) + corn );
|
||||
vec2 p = saturate(
|
||||
( uv + normalize( uv - 0.5 ) * tan( length( uv - 0.5 ) * a ) ) * zoom +
|
||||
0.5 * ( 1.0 - zoom )
|
||||
);
|
||||
return texture( sampler0, vec2( p.x, p.y ) ).xyz;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = vUv;
|
||||
vec2 p = ( uv * resolution * 2.0 - resolution ) / resolution.y;
|
||||
float vig = 1.0 - length( p ) * 0.2;
|
||||
|
||||
vec3 tex = vec3( 0.0 );
|
||||
|
||||
for ( int i = 0; i < BARREL_ITER; i ++ ) {
|
||||
float fi = ( float( i ) + 0.5 ) / float( BARREL_ITER );
|
||||
vec3 a = saturate( vec3(
|
||||
1.0 - 3.0 * abs( 1.0 / 6.0 - fi ),
|
||||
1.0 - 3.0 * abs( 1.0 / 2.0 - fi ),
|
||||
1.0 - 3.0 * abs( 5.0 / 6.0 - fi )
|
||||
) ) / float( BARREL_ITER ) * 4.0;
|
||||
tex += a * barrel( BARREL_OFFSET + BARREL_AMP * fi, uv );
|
||||
}
|
||||
|
||||
tex = mix( vec3( 0.0 ), tex, vig );
|
||||
|
||||
vec3 col = pow( saturate( tex.xyz ), vec3( 0.4545 ) );
|
||||
col.x = linearstep( 0.0, 1.2, col.x + 0.2 * uv.y );
|
||||
col = colorMap( col );
|
||||
|
||||
fragColor = vec4( col, 1.0 );
|
||||
}
|
12
src/shaders/quad.vert
Normal file
12
src/shaders/quad.vert
Normal file
@@ -0,0 +1,12 @@
|
||||
#version 300 es
|
||||
|
||||
in vec2 p;
|
||||
|
||||
out vec2 vUv;
|
||||
|
||||
uniform vec4 range;
|
||||
|
||||
void main() {
|
||||
vUv = 0.5 + 0.5 * p;
|
||||
gl_Position = vec4( mix( range.xy, range.zw, vUv ), 0.0, 1.0 );
|
||||
}
|
83
src/shaders/raymarcher.frag
Normal file
83
src/shaders/raymarcher.frag
Normal file
@@ -0,0 +1,83 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
#define saturate(x) clamp(x,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
|
||||
const int MARCH_ITER = 50;
|
||||
const int MTL_UNLIT = 1;
|
||||
const int MTL_PBR = 2;
|
||||
const int MTL_GRADIENT = 3;
|
||||
const int MTL_IRIDESCENT = 4;
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
layout (location = 0) out vec4 fragPosition;
|
||||
layout (location = 1) out vec4 fragNormal;
|
||||
layout (location = 2) out vec4 fragColor;
|
||||
layout (location = 3) out vec4 fragWTF;
|
||||
|
||||
uniform float time;
|
||||
uniform vec2 resolution;
|
||||
uniform vec2 cameraNearFar;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform mat4 inversePV;
|
||||
uniform sampler2D samplerRandom;
|
||||
uniform sampler2D samplerRandomStatic;
|
||||
uniform sampler2D samplerCapture;
|
||||
|
||||
vec3 divideByW( vec4 v ) {
|
||||
return v.xyz / v.w;
|
||||
}
|
||||
|
||||
#pragma glslify: distFunc = require( ./-distFunc );
|
||||
|
||||
float fDistFunc( vec3 p ) {
|
||||
return distFunc( p, time );
|
||||
}
|
||||
|
||||
vec3 normalFunc( vec3 p, float dd ) {
|
||||
vec2 d = vec2( 0.0, dd );
|
||||
return normalize( vec3(
|
||||
fDistFunc( p + d.yxx ) - fDistFunc( p - d.yxx ),
|
||||
fDistFunc( p + d.xyx ) - fDistFunc( p - d.xyx ),
|
||||
fDistFunc( p + d.xxy ) - fDistFunc( p - d.xxy )
|
||||
) );
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = vUv * 2.0 - 1.0;
|
||||
p.x *= resolution.x / resolution.y;
|
||||
vec3 rayOri = divideByW( inversePV * vec4( p, 0.0, 1.0 ) );
|
||||
vec3 farPos = divideByW( inversePV * vec4( p, 1.0, 1.0 ) );
|
||||
vec3 rayDir = normalize( farPos - rayOri );
|
||||
float rayLen = cameraNearFar.x;
|
||||
vec3 rayPos = rayOri + rayDir * rayLen;
|
||||
float dist;
|
||||
|
||||
for ( int i = 0; i < MARCH_ITER; i ++ ) {
|
||||
dist = distFunc( rayPos, time );
|
||||
rayLen += 0.7 * dist;
|
||||
rayPos = rayOri + rayDir * rayLen;
|
||||
|
||||
if ( abs( dist ) < 1E-3 ) { break; }
|
||||
}
|
||||
|
||||
if ( 0.01 < dist ) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec3 normal = normalFunc( rayPos, 1E-4 );
|
||||
vec4 color = vec4( 0.1, 0.2, 0.4, 1.0 );
|
||||
|
||||
vec4 projPos = projectionMatrix * viewMatrix * vec4( rayPos, 1.0 ); // terrible
|
||||
float depth = projPos.z / projPos.w;
|
||||
gl_FragDepth = 0.5 + 0.5 * depth;
|
||||
|
||||
fragPosition = vec4( rayPos, depth );
|
||||
fragNormal = vec4( normal, 1.0 );
|
||||
fragColor = color;
|
||||
fragWTF = vec4( vec3( 2.0, 0.9, 0.9 ), MTL_IRIDESCENT );
|
||||
}
|
13
src/shaders/return.frag
Normal file
13
src/shaders/return.frag
Normal file
@@ -0,0 +1,13 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D sampler0;
|
||||
|
||||
void main() {
|
||||
fragColor = texture( sampler0, vUv );
|
||||
}
|
23
src/shaders/rings.frag
Normal file
23
src/shaders/rings.frag
Normal file
@@ -0,0 +1,23 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int MTL_UNLIT = 1;
|
||||
|
||||
in float vLife;
|
||||
in vec4 vPosition;
|
||||
in vec3 vNormal;
|
||||
|
||||
layout (location = 0) out vec4 fragPosition;
|
||||
layout (location = 1) out vec4 fragNormal;
|
||||
layout (location = 2) out vec4 fragColor;
|
||||
layout (location = 3) out vec4 fragWTF;
|
||||
|
||||
uniform float time;
|
||||
|
||||
void main() {
|
||||
fragPosition = vPosition;
|
||||
fragNormal = vec4( vNormal, 1.0 );
|
||||
fragColor = vec4( 0.6, 1.0, 4.0, 1.0 );
|
||||
fragWTF = vec4( vec3( 0.0, 0.0, 0.0 ), MTL_UNLIT );
|
||||
}
|
65
src/shaders/rings.vert
Normal file
65
src/shaders/rings.vert
Normal file
@@ -0,0 +1,65 @@
|
||||
#version 300 es
|
||||
|
||||
const float TAU = 6.283185307;
|
||||
|
||||
in float instanceId;
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
in vec2 uv;
|
||||
|
||||
out vec4 vPosition;
|
||||
out vec3 vNormal;
|
||||
out vec2 vUv;
|
||||
|
||||
#ifdef USE_VERTEX_COLOR
|
||||
in vec4 color;
|
||||
out vec4 vColor;
|
||||
#endif
|
||||
|
||||
uniform float time;
|
||||
uniform vec2 resolution;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 normalMatrix;
|
||||
|
||||
float seed;
|
||||
|
||||
mat2 rotate2D( float t ) {
|
||||
return mat2( cos( t ), sin( t ), -sin( t ), cos( t ) );
|
||||
}
|
||||
|
||||
float fs( float s ) {
|
||||
return fract( sin( s * 114.514 ) * 1919.810 );
|
||||
}
|
||||
|
||||
float random() {
|
||||
seed = fs( seed );
|
||||
return seed;
|
||||
}
|
||||
|
||||
void main() {
|
||||
seed = instanceId;
|
||||
|
||||
vNormal = normalize( ( normalMatrix * vec4( normal, 1.0 ) ).xyz );
|
||||
|
||||
vPosition = vec4( mix( 2.0, 2.7, random() ) * position, 1.0 );
|
||||
vPosition.xyz += mix( 0.002, 0.005, random() ) * normal;
|
||||
vPosition.y += sin( random() * time + random() * vPosition.x + TAU * random() ) * 0.2 * random();
|
||||
vPosition.y += sin( random() * time + random() * vPosition.z + TAU * random() ) * 0.2 * random();
|
||||
vPosition.xy = rotate2D( 0.2 * ( random() - 0.5 ) ) * vPosition.xy;
|
||||
vPosition.yz = rotate2D( 0.2 * ( random() - 0.5 ) ) * vPosition.yz;
|
||||
vPosition = modelMatrix * vPosition;
|
||||
|
||||
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
|
||||
outPos.x *= resolution.y / resolution.x;
|
||||
gl_Position = outPos;
|
||||
|
||||
vPosition.w = outPos.z / outPos.w;
|
||||
|
||||
#ifdef USE_VERTEX_COLOR
|
||||
vColor = color;
|
||||
#endif
|
||||
|
||||
vUv = uv;
|
||||
}
|
275
src/shaders/shading.frag
Normal file
275
src/shaders/shading.frag
Normal file
@@ -0,0 +1,275 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int MTL_NONE = 0;
|
||||
const int MTL_UNLIT = 1;
|
||||
const int MTL_PBR = 2;
|
||||
const int MTL_GRADIENT = 3;
|
||||
const int MTL_IRIDESCENT = 4;
|
||||
const int AO_ITER = 8;
|
||||
const float AO_BIAS = 0.0;
|
||||
const float AO_RADIUS = 0.5;
|
||||
const float PI = 3.14159265359;
|
||||
const float TAU = 6.28318530718;
|
||||
const float EPSILON = 1E-3;
|
||||
const vec3 BLACK = vec3( 0.0 );
|
||||
const vec3 DIELECTRIC_SPECULAR = vec3( 0.04 );
|
||||
|
||||
#define saturate(x) clamp(x,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 lightNearFar;
|
||||
uniform vec2 cameraNearFar;
|
||||
uniform vec3 cameraPos;
|
||||
uniform vec3 lightPos;
|
||||
uniform vec3 lightColor;
|
||||
uniform mat4 lightPV;
|
||||
uniform mat4 cameraView;
|
||||
uniform mat4 cameraPV;
|
||||
uniform sampler2D sampler0; // position.xyz, depth
|
||||
uniform sampler2D sampler1; // normal.xyz (yes, this is not good)
|
||||
uniform sampler2D sampler2; // color.rgba (what is a though????)
|
||||
uniform sampler2D sampler3; // materialParams.xyz, materialId
|
||||
uniform sampler2D samplerShadow;
|
||||
uniform sampler2D samplerBRDFLUT;
|
||||
uniform sampler2D samplerEnv;
|
||||
uniform sampler2D samplerAo;
|
||||
uniform sampler2D samplerRandom;
|
||||
|
||||
// == commons ======================================================================================
|
||||
#pragma glslify: prng = require( ./-prng );
|
||||
|
||||
vec3 catColor( float _p ) {
|
||||
return 0.5 + 0.5 * vec3(
|
||||
cos( _p ),
|
||||
cos( _p + PI / 3.0 * 4.0 ),
|
||||
cos( _p + PI / 3.0 * 2.0 )
|
||||
);
|
||||
}
|
||||
|
||||
vec3 randomSphere( inout vec4 seed ) {
|
||||
vec3 v;
|
||||
for ( int i = 0; i < 10; i ++ ) {
|
||||
v = vec3(
|
||||
prng( seed ),
|
||||
prng( seed ),
|
||||
prng( seed )
|
||||
) * 2.0 - 1.0;
|
||||
if ( length( v ) < 1.0 ) { break; }
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
vec3 blurpleGradient( float t ) {
|
||||
vec3 colorA = vec3( 0.01, 0.04, 0.2 );
|
||||
vec3 colorB = vec3( 0.02, 0.3, 0.9 );
|
||||
vec3 colorC = vec3( 0.9, 0.01, 0.6 );
|
||||
vec3 colorD = vec3( 0.5, 0.02, 0.02 );
|
||||
|
||||
return mix(
|
||||
colorA,
|
||||
mix(
|
||||
colorB,
|
||||
mix(
|
||||
colorC,
|
||||
colorD,
|
||||
linearstep( 0.67, 1.0, t )
|
||||
),
|
||||
linearstep( 0.33, 0.67, t )
|
||||
),
|
||||
linearstep( 0.0, 0.33, t )
|
||||
);
|
||||
}
|
||||
|
||||
// == structs ======================================================================================
|
||||
struct Isect {
|
||||
vec2 screenUv;
|
||||
vec3 albedo;
|
||||
vec3 position;
|
||||
float depth;
|
||||
vec3 normal;
|
||||
int materialId;
|
||||
vec3 materialParams;
|
||||
};
|
||||
|
||||
struct AngularInfo {
|
||||
vec3 V;
|
||||
vec3 L;
|
||||
vec3 H;
|
||||
float dotNV;
|
||||
float dotNL;
|
||||
float dotNH;
|
||||
float dotVH;
|
||||
};
|
||||
|
||||
AngularInfo genAngularInfo( Isect isect ) {
|
||||
AngularInfo aI;
|
||||
aI.V = normalize( cameraPos - isect.position );
|
||||
aI.L = normalize( lightPos - isect.position );
|
||||
aI.H = normalize( aI.V + aI.L );
|
||||
aI.dotNV = clamp( dot( isect.normal, aI.V ), EPSILON, 1.0 );
|
||||
aI.dotNL = clamp( dot( isect.normal, aI.L ), EPSILON, 1.0 );
|
||||
aI.dotNH = clamp( dot( isect.normal, aI.H ), EPSILON, 1.0 );
|
||||
aI.dotVH = clamp( dot( aI.V, aI.H ), EPSILON, 1.0 );
|
||||
return aI;
|
||||
}
|
||||
|
||||
// == features =====================================================================================
|
||||
float castShadow( Isect isect, AngularInfo aI ) {
|
||||
float depth = linearstep( lightNearFar.x, lightNearFar.y, length( isect.position - lightPos ) );
|
||||
float bias = 0.0001 + 0.0001 * ( 1.0 - aI.dotNL );
|
||||
depth -= bias;
|
||||
|
||||
vec4 proj = lightPV * vec4( isect.position, 1.0 );
|
||||
vec2 uv = proj.xy / proj.w * 0.5 + 0.5;
|
||||
|
||||
vec4 tex = texture( samplerShadow, uv );
|
||||
|
||||
float edgeClip = smoothstep( 0.4, 0.5, max( abs( uv.x - 0.5 ), abs( uv.y - 0.5 ) ) );
|
||||
|
||||
float variance = saturate( tex.y - tex.x * tex.x );
|
||||
float md = depth - tex.x;
|
||||
float p = linearstep( 0.2, 1.0, variance / ( variance + md * md ) );
|
||||
|
||||
float softShadow = md < 0.0 ? 1.0 : p;
|
||||
|
||||
return mix(
|
||||
softShadow,
|
||||
1.0,
|
||||
edgeClip
|
||||
);
|
||||
}
|
||||
|
||||
float calcDepth( vec3 pos ) {
|
||||
float dist = length( cameraPos - pos );
|
||||
float near = cameraNearFar.x;
|
||||
float far = cameraNearFar.y;
|
||||
return linearstep( near, far, dist );
|
||||
}
|
||||
|
||||
// == shading functions ============================================================================
|
||||
vec3 shadePBR( Isect isect, AngularInfo aI ) {
|
||||
// ref: https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/master/src/shaders/metallic-roughness.frag
|
||||
|
||||
float roughness = isect.materialParams.x;
|
||||
float metallic = isect.materialParams.y;
|
||||
float emissive = isect.materialParams.z;
|
||||
|
||||
float shadow = castShadow( isect, aI );
|
||||
shadow = mix( 1.0, shadow, 0.8 );
|
||||
|
||||
float ao = texture( samplerAo, isect.screenUv ).x;
|
||||
shadow *= ao;
|
||||
|
||||
float lenL = length( isect.position - lightPos );
|
||||
float decay = 1.0 / ( lenL * lenL );
|
||||
|
||||
vec3 F0 = mix( DIELECTRIC_SPECULAR, isect.albedo, metallic );
|
||||
vec3 F = F0 + ( 1.0 - F0 ) * pow( 1.0 - aI.dotVH, 5.0 );
|
||||
|
||||
float roughnessSq = roughness * roughness;
|
||||
float GGXV = aI.dotNL * sqrt( aI.dotNV * aI.dotNV * ( 1.0 - roughnessSq ) + roughnessSq );
|
||||
float GGXL = aI.dotNV * sqrt( aI.dotNL * aI.dotNL * ( 1.0 - roughnessSq ) + roughnessSq );
|
||||
float GGX = GGXV + GGXL;
|
||||
float Vis = ( 0.0 < GGX ) ? ( 0.5 / GGX ) : 0.0;
|
||||
|
||||
float f = ( aI.dotNH * roughnessSq - aI.dotNH ) * aI.dotNH + 1.0;
|
||||
float D = roughnessSq / ( PI * f * f );
|
||||
|
||||
vec3 diffuse = max( vec3( 0.0 ), ( 1.0 - F ) * ( isect.albedo / PI ) );
|
||||
vec3 spec = max( vec3( 0.0 ), F * Vis * D );
|
||||
vec3 shade = PI * lightColor * decay * shadow * aI.dotNL * ( diffuse + spec );
|
||||
|
||||
vec3 color = shade;
|
||||
|
||||
#ifdef IS_FIRST_LIGHT
|
||||
// vec3 refl = reflect( aI.V, isect.normal );
|
||||
// vec2 envCoord = vec2(
|
||||
// 0.5 + atan( refl.z, refl.x ) / TAU,
|
||||
// 0.5 + atan( refl.y, length( refl.zx ) ) / PI
|
||||
// );
|
||||
// vec2 brdf = texture( samplerBRDFLUT, vec2( aI.dotNV, 1.0 - roughness ) ).xy;
|
||||
|
||||
// vec3 texEnv = 0.2 * pow( texture( samplerEnv, envCoord ).rgb, vec3( 2.2 ) );
|
||||
// color += PI * texEnv * ( brdf.x * F0 + brdf.y );
|
||||
|
||||
color += emissive * aI.dotNV * isect.albedo;
|
||||
#endif // IS_FIRST_LIGHT
|
||||
|
||||
return color;
|
||||
|
||||
}
|
||||
|
||||
vec3 shadeGradient( Isect isect ) {
|
||||
vec3 ret;
|
||||
|
||||
#ifdef IS_FIRST_LIGHT
|
||||
float shade = isect.normal.y;
|
||||
ret = blurpleGradient( 0.5 + 0.5 * shade );
|
||||
#else // IS_FIRST_LIGHT
|
||||
ret = vec3( 0.0 );
|
||||
#endif // IS_FIRST_LIGHT
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// == main procedure ===============================================================================
|
||||
void main() {
|
||||
vec4 tex0 = texture( sampler0, vUv );
|
||||
vec4 tex1 = texture( sampler1, vUv );
|
||||
vec4 tex2 = texture( sampler2, vUv );
|
||||
vec4 tex3 = texture( sampler3, vUv );
|
||||
|
||||
Isect isect;
|
||||
isect.screenUv = vUv;
|
||||
isect.position = tex0.xyz;
|
||||
isect.depth = tex0.w;
|
||||
isect.normal = tex1.xyz;
|
||||
isect.albedo = tex2.rgb;
|
||||
isect.materialId = int( tex3.w + 0.5 );
|
||||
isect.materialParams = tex3.xyz;
|
||||
|
||||
vec3 color = vec3( 0.0 );
|
||||
|
||||
if ( isect.materialId == MTL_NONE ) {
|
||||
// do nothing
|
||||
|
||||
} else if ( isect.materialId == MTL_UNLIT ) {
|
||||
#ifdef IS_FIRST_LIGHT
|
||||
color = isect.albedo;
|
||||
#endif
|
||||
|
||||
} else if ( isect.materialId == MTL_PBR ) {
|
||||
AngularInfo aI = genAngularInfo( isect );
|
||||
color = shadePBR( isect, aI );
|
||||
|
||||
} else if ( isect.materialId == MTL_GRADIENT ) {
|
||||
color = shadeGradient( isect );
|
||||
|
||||
} else if ( isect.materialId == MTL_IRIDESCENT ) {
|
||||
AngularInfo aI = genAngularInfo( isect );
|
||||
isect.albedo *= mix(
|
||||
vec3( 1.0 ),
|
||||
catColor( isect.materialParams.x * aI.dotNV ),
|
||||
isect.materialParams.y
|
||||
);
|
||||
isect.materialParams = vec3( 0.1, isect.materialParams.z, 0.0 );
|
||||
color = shadePBR( isect, aI );
|
||||
|
||||
}
|
||||
|
||||
#ifdef IS_FIRST_LIGHT
|
||||
// color = 0.5 + 0.5 * isect.normal;
|
||||
// color = vec3( calcDepth( tex0.xyz ) );
|
||||
// color = vec3( 0.8 ) * ( 1.0 - texture( samplerAo, isect.screenUv ).xyz );
|
||||
#endif
|
||||
// xfdA = shadeGradient( isect );
|
||||
|
||||
fragColor = vec4( color, 1.0 );
|
||||
// fragColor.xyz *= smoothstep( 1.0, 0.7, calcDepth( tex0.xyz ) );
|
||||
}
|
38
src/shaders/shadow-blur.frag
Normal file
38
src/shaders/shadow-blur.frag
Normal file
@@ -0,0 +1,38 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int SAMPLES = 30;
|
||||
const float PI = 3.14159265;
|
||||
const float MUL_THR = 1E-4;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
|
||||
in vec2 vUv;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform bool isVert;
|
||||
uniform vec2 resolution;
|
||||
uniform sampler2D sampler0;
|
||||
|
||||
float gaussian( float _x, float _v ) {
|
||||
return 1.0 / sqrt( 2.0 * PI * _v ) * exp( - _x * _x / 2.0 / _v );
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 texel = 1.0 / resolution;
|
||||
|
||||
vec2 bv = texel * ( isVert ? vec2( 0.0, 1.0 ) : vec2( 1.0, 0.0 ) );
|
||||
vec4 sum = vec4( 0.0 );
|
||||
|
||||
vec4 tex = texture( sampler0, vUv );
|
||||
|
||||
sum += 0.29411764705882354 * tex;
|
||||
vec2 suv = vUv - bv * 1.3333333333333333;
|
||||
sum += 0.35294117647058826 * texture( sampler0, suv );
|
||||
suv = vUv + bv * 1.3333333333333333;
|
||||
sum += 0.35294117647058826 * texture( sampler0, suv );
|
||||
|
||||
fragColor = vec4( sum.xy, tex.z, 1.0 );
|
||||
}
|
181
src/shaders/sphere-particles-compute.frag
Normal file
181
src/shaders/sphere-particles-compute.frag
Normal file
@@ -0,0 +1,181 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const float PARTICLE_LIFE_LENGTH = 1.0;
|
||||
const float HUGE = 9E16;
|
||||
const float PI = 3.14159265;
|
||||
const float TAU = 6.283185307;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
#define lofi(i,m) (floor((i)/(m))*(m))
|
||||
#define lofir(i,m) (floor((i)/(m)+.5)*(m))
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform bool init;
|
||||
uniform float time;
|
||||
uniform float beat;
|
||||
uniform float particlesSqrt;
|
||||
uniform float ppp;
|
||||
uniform float totalFrame;
|
||||
uniform float deltaTime;
|
||||
uniform float noiseScale;
|
||||
uniform float noisePhase;
|
||||
uniform vec2 resolution;
|
||||
uniform sampler2D samplerCompute;
|
||||
uniform sampler2D samplerRandom;
|
||||
// uniform float velScale;
|
||||
// uniform float genRate;
|
||||
|
||||
// ------
|
||||
|
||||
vec2 uvInvT( vec2 _uv ) {
|
||||
return vec2( 0.0, 1.0 ) + vec2( 1.0, -1.0 ) * _uv;
|
||||
}
|
||||
|
||||
// ------
|
||||
|
||||
#pragma glslify: distFunc = require( ./-distFunc );
|
||||
|
||||
float fDistFunc( vec3 p ) {
|
||||
return distFunc( p, time );
|
||||
}
|
||||
|
||||
vec3 normalFunc( vec3 p, float dd ) {
|
||||
vec2 d = vec2( 0.0, dd );
|
||||
return normalize( vec3(
|
||||
fDistFunc( p + d.yxx ) - fDistFunc( p - d.yxx ),
|
||||
fDistFunc( p + d.xyx ) - fDistFunc( p - d.xyx ),
|
||||
fDistFunc( p + d.xxy ) - fDistFunc( p - d.xxy )
|
||||
) );
|
||||
}
|
||||
|
||||
mat2 rotate2D( float _t ) {
|
||||
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
|
||||
}
|
||||
|
||||
float fractSin( float i ) {
|
||||
return fract( sin( i ) * 1846.42 );
|
||||
}
|
||||
|
||||
vec4 sampleRandom( vec2 _uv ) {
|
||||
return texture( samplerRandom, _uv );
|
||||
}
|
||||
|
||||
#pragma glslify: prng = require( ./-prng );
|
||||
#pragma glslify: noise = require( ./-simplex4d );
|
||||
|
||||
vec3 randomSphere( inout vec4 seed ) {
|
||||
vec3 v;
|
||||
for ( int i = 0; i < 10; i ++ ) {
|
||||
v = vec3(
|
||||
prng( seed ),
|
||||
prng( seed ),
|
||||
prng( seed )
|
||||
) * 2.0 - 1.0;
|
||||
if ( length( v ) < 1.0 ) { break; }
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
vec2 randomCircle( inout vec4 seed ) {
|
||||
vec2 v;
|
||||
for ( int i = 0; i < 10; i ++ ) {
|
||||
v = vec2(
|
||||
prng( seed ),
|
||||
prng( seed )
|
||||
) * 2.0 - 1.0;
|
||||
if ( length( v ) < 1.0 ) { break; }
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
vec3 randomBox( inout vec4 seed ) {
|
||||
vec3 v;
|
||||
v = vec3(
|
||||
prng( seed ),
|
||||
prng( seed ),
|
||||
prng( seed )
|
||||
) * 2.0 - 1.0;
|
||||
return v;
|
||||
}
|
||||
|
||||
// ------
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / resolution;
|
||||
vec2 puv = vec2( ( floor( gl_FragCoord.x / ppp ) * ppp + 0.5 ) / resolution.x, uv.y );
|
||||
float pixId = mod( gl_FragCoord.x, ppp );
|
||||
vec2 dpix = vec2( 1.0 ) / resolution;
|
||||
|
||||
float dt = deltaTime;
|
||||
|
||||
// == prepare some vars ==========================================================================
|
||||
vec4 seed = texture( samplerRandom, puv );
|
||||
prng( seed );
|
||||
|
||||
vec4 tex0 = texture( samplerCompute, puv );
|
||||
vec4 tex1 = texture( samplerCompute, puv + dpix * vec2( 1.0, 0.0 ) );
|
||||
|
||||
vec3 pos = tex0.xyz;
|
||||
float life = tex0.w;
|
||||
vec3 vel = tex1.xyz;
|
||||
|
||||
float timing = mix(
|
||||
0.0,
|
||||
PARTICLE_LIFE_LENGTH,
|
||||
( floor( puv.x * particlesSqrt ) / particlesSqrt + floor( puv.y * particlesSqrt ) ) / particlesSqrt
|
||||
);
|
||||
timing += lofi( time, PARTICLE_LIFE_LENGTH );
|
||||
|
||||
if ( time - deltaTime + PARTICLE_LIFE_LENGTH < timing ) {
|
||||
timing -= PARTICLE_LIFE_LENGTH;
|
||||
}
|
||||
|
||||
// == initialize particles =======================================================================
|
||||
if (
|
||||
time - deltaTime < timing && timing <= time
|
||||
) {
|
||||
dt = time - timing;
|
||||
|
||||
pos = 0.5 * randomSphere( seed );
|
||||
|
||||
vel = 1.0 * randomSphere( seed );
|
||||
|
||||
life = 1.0;
|
||||
} else {
|
||||
// do nothing
|
||||
// if you want to remove init frag from the particle, do at here
|
||||
}
|
||||
|
||||
// == update particles ===========================================================================
|
||||
// distFunc
|
||||
float dist = fDistFunc( pos.xyz ) - 0.2;
|
||||
vec3 nor = normalFunc( pos.xyz, 1E-4 );
|
||||
vel -= dt * 100.0 * dist * nor;
|
||||
|
||||
// spin around center
|
||||
vel.zx += dt * 20.0 * vec2( -1.0, 1.0 ) * normalize( nor.xz );
|
||||
|
||||
// noise field
|
||||
vel += 40.0 * vec3(
|
||||
noise( vec4( pos.xyz, 1.485 + sin( time * 0.1 ) + noisePhase ) ),
|
||||
noise( vec4( pos.xyz, 3.485 + sin( time * 0.1 ) + noisePhase ) ),
|
||||
noise( vec4( pos.xyz, 5.485 + sin( time * 0.1 ) + noisePhase ) )
|
||||
) * dt;
|
||||
|
||||
// resistance
|
||||
vel *= exp( -10.0 * dt );
|
||||
|
||||
vec3 v = vel;
|
||||
float vmax = max( abs( v.x ), max( abs( v.y ), abs( v.z ) ) );
|
||||
|
||||
pos += vel * dt;
|
||||
life -= dt / PARTICLE_LIFE_LENGTH;
|
||||
|
||||
fragColor = (
|
||||
pixId < 1.0 ? vec4( pos, life ) :
|
||||
vec4( vel, 1.0 )
|
||||
);
|
||||
}
|
42
src/shaders/sphere-particles-render.frag
Normal file
42
src/shaders/sphere-particles-render.frag
Normal file
@@ -0,0 +1,42 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int MTL_UNLIT = 1;
|
||||
const int MTL_PBR = 2;
|
||||
const int MTL_IRIDESCENT = 4;
|
||||
|
||||
const float PI = 3.14159265;
|
||||
const float TAU = 6.28318531;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
|
||||
// == varings / uniforms ===========================================================================
|
||||
in float vLife;
|
||||
in vec4 vPosition;
|
||||
in vec3 vNormal;
|
||||
in vec4 vColor;
|
||||
in vec4 vRandom;
|
||||
|
||||
layout (location = 0) out vec4 fragPosition;
|
||||
layout (location = 1) out vec4 fragNormal;
|
||||
layout (location = 2) out vec4 fragColor;
|
||||
layout (location = 3) out vec4 fragWTF;
|
||||
|
||||
uniform float time;
|
||||
|
||||
// == common =======================================================================================
|
||||
mat2 rotate2D( float _t ) {
|
||||
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
|
||||
}
|
||||
|
||||
// == main procedure ===============================================================================
|
||||
void main() {
|
||||
if ( vColor.a < 0.0 ) { discard; }
|
||||
|
||||
fragPosition = vPosition;
|
||||
fragNormal = vec4( vNormal, 1.0 );
|
||||
fragColor = vec4( vColor.xyz, 1.0 );
|
||||
fragWTF = vec4( vec3( 0.5, 0.5, 0.0 ), MTL_PBR );
|
||||
}
|
108
src/shaders/sphere-particles-render.vert
Normal file
108
src/shaders/sphere-particles-render.vert
Normal file
@@ -0,0 +1,108 @@
|
||||
#version 300 es
|
||||
|
||||
const int MODE_RECT = 0;
|
||||
const int MODE_GRID = 1;
|
||||
const int MODE_CIRCLE = 2;
|
||||
const int MODE_CHAR = 3;
|
||||
const int MODE_BUTTON = 4;
|
||||
const int MODE_ICON = 5;
|
||||
const int MODES = 6;
|
||||
|
||||
const float HUGE = 9E16;
|
||||
const float PI = 3.14159265;
|
||||
const float TAU = 6.283185307;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
#define lofi(i,m) (floor((i)/(m))*(m))
|
||||
#define lofir(i,m) (floor((i+0.5)/(m))*(m))
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
in vec2 computeUV;
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
|
||||
out float vLife;
|
||||
out vec2 vUv;
|
||||
out vec3 vNormal;
|
||||
out vec4 vPosition;
|
||||
out vec4 vColor;
|
||||
out vec4 vDice;
|
||||
|
||||
uniform bool isShadow;
|
||||
uniform float ppp;
|
||||
uniform float trailShaker;
|
||||
uniform float colorVar;
|
||||
uniform float colorOffset;
|
||||
uniform vec2 resolution;
|
||||
uniform vec2 resolutionCompute;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 normalMatrix;
|
||||
uniform sampler2D samplerCompute;
|
||||
uniform sampler2D samplerRandomStatic;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
vec3 catColor( float _p ) {
|
||||
return 0.5 + 0.5 * vec3(
|
||||
cos( _p ),
|
||||
cos( _p + PI / 3.0 * 4.0 ),
|
||||
cos( _p + PI / 3.0 * 2.0 )
|
||||
);
|
||||
}
|
||||
|
||||
vec4 random( vec2 _uv ) {
|
||||
return texture( samplerRandomStatic, _uv );
|
||||
}
|
||||
|
||||
mat2 rotate2D( float _t ) {
|
||||
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
void main() {
|
||||
vec2 puv = vec2( computeUV );
|
||||
vec2 dppix = vec2( 1.0 ) / resolutionCompute;
|
||||
|
||||
// == fetch texture ==============================================================================
|
||||
vec4 tex0 = texture( samplerCompute, puv );
|
||||
vec4 tex1 = texture( samplerCompute, puv + dppix * vec2( 1.0, 0.0 ) );
|
||||
|
||||
// == assign varying variables ===================================================================
|
||||
vDice = random( puv.xy * 182.92 );
|
||||
|
||||
vColor.xyz = 0.8 * mix( catColor( 2.0 + 40.0 * vDice.x ), vec3( 0.9 ), 0.0 );
|
||||
vColor.xyz = vec3( 0.2 );
|
||||
|
||||
vLife = tex0.w;
|
||||
|
||||
// == compute size ===============================================================================
|
||||
vPosition = vec4( tex0.xyz, 1.0 );
|
||||
|
||||
float size = vDice.x * 0.08;
|
||||
size *= sin( PI * saturate( vLife ) );
|
||||
|
||||
vec3 shape = position * size;
|
||||
shape.yz = rotate2D( 7.0 * vPosition.x ) * shape.yz;
|
||||
shape.zx = rotate2D( 7.0 * vPosition.y ) * shape.zx;
|
||||
|
||||
vPosition.xyz += shape;
|
||||
|
||||
// == compute normals ============================================================================
|
||||
vNormal = ( normalMatrix * vec4( normal, 1.0 ) ).xyz;
|
||||
vNormal.yz = rotate2D( 7.0 * vPosition.x ) * vNormal.yz;
|
||||
vNormal.zx = rotate2D( 7.0 * vPosition.y ) * vNormal.zx;
|
||||
|
||||
// == send the vertex position ===================================================================
|
||||
vPosition = modelMatrix * vPosition;
|
||||
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
|
||||
outPos.x *= resolution.y / resolution.x;
|
||||
gl_Position = outPos;
|
||||
|
||||
vPosition.w = outPos.z / outPos.w;
|
||||
|
||||
// gl_PointSize = resolution.y * size / outPos.z;
|
||||
}
|
218
src/shaders/trails-compute.frag
Normal file
218
src/shaders/trails-compute.frag
Normal file
@@ -0,0 +1,218 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const float PARTICLE_LIFE_LENGTH = 5.0;
|
||||
const float HUGE = 9E16;
|
||||
const float PI = 3.14159265;
|
||||
const float TAU = 6.283185307;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
#define lofi(i,m) (floor((i)/(m))*(m))
|
||||
#define lofir(i,m) (floor((i)/(m)+.5)*(m))
|
||||
|
||||
// ------
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform float time;
|
||||
uniform float beat;
|
||||
|
||||
uniform float trails;
|
||||
uniform float trailLength;
|
||||
uniform float ppp;
|
||||
|
||||
uniform float totalFrame;
|
||||
uniform bool init;
|
||||
uniform float deltaTime;
|
||||
uniform vec2 resolution;
|
||||
|
||||
uniform sampler2D samplerCompute;
|
||||
uniform sampler2D samplerRandom;
|
||||
|
||||
uniform float noiseScale;
|
||||
uniform float noisePhase;
|
||||
// uniform float velScale;
|
||||
// uniform float genRate;
|
||||
|
||||
// ------
|
||||
|
||||
#pragma glslify: distFunc = require( ./-distFunc );
|
||||
|
||||
float fDistFunc( vec3 p ) {
|
||||
return distFunc( p, time );
|
||||
}
|
||||
|
||||
vec3 normalFunc( vec3 p, float dd ) {
|
||||
vec2 d = vec2( 0.0, dd );
|
||||
return normalize( vec3(
|
||||
fDistFunc( p + d.yxx ) - fDistFunc( p - d.yxx ),
|
||||
fDistFunc( p + d.xyx ) - fDistFunc( p - d.xyx ),
|
||||
fDistFunc( p + d.xxy ) - fDistFunc( p - d.xxy )
|
||||
) );
|
||||
}
|
||||
|
||||
vec2 uvInvT( vec2 _uv ) {
|
||||
return vec2( 0.0, 1.0 ) + vec2( 1.0, -1.0 ) * _uv;
|
||||
}
|
||||
|
||||
// ------
|
||||
|
||||
mat2 rotate2D( float _t ) {
|
||||
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
|
||||
}
|
||||
|
||||
float fractSin( float i ) {
|
||||
return fract( sin( i ) * 1846.42 );
|
||||
}
|
||||
|
||||
vec4 sampleRandom( vec2 _uv ) {
|
||||
return texture( samplerRandom, _uv );
|
||||
}
|
||||
|
||||
#pragma glslify: prng = require( ./-prng );
|
||||
#pragma glslify: noise = require( ./-simplex4d );
|
||||
|
||||
vec3 randomSphere( inout vec4 seed ) {
|
||||
vec3 v;
|
||||
for ( int i = 0; i < 10; i ++ ) {
|
||||
v = vec3(
|
||||
prng( seed ),
|
||||
prng( seed ),
|
||||
prng( seed )
|
||||
) * 2.0 - 1.0;
|
||||
if ( length( v ) < 1.0 ) { break; }
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
vec2 randomCircle( inout vec4 seed ) {
|
||||
vec2 v;
|
||||
for ( int i = 0; i < 10; i ++ ) {
|
||||
v = vec2(
|
||||
prng( seed ),
|
||||
prng( seed )
|
||||
) * 2.0 - 1.0;
|
||||
if ( length( v ) < 1.0 ) { break; }
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
vec3 randomBox( inout vec4 seed ) {
|
||||
vec3 v;
|
||||
v = vec3(
|
||||
prng( seed ),
|
||||
prng( seed ),
|
||||
prng( seed )
|
||||
) * 2.0 - 1.0;
|
||||
return v;
|
||||
}
|
||||
|
||||
float uneune( float i, float p ) {
|
||||
return sin( TAU * (
|
||||
fractSin( i ) + floor( 1.0 + 4.0 * fractSin( i + 54.12 ) ) * p
|
||||
) );
|
||||
}
|
||||
|
||||
vec3 uneune3( float i, float p ) {
|
||||
return vec3( uneune( i, p ), uneune( i + 11.87, p ), uneune( i + 21.92, p ) );
|
||||
}
|
||||
|
||||
// ------
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / resolution;
|
||||
vec2 puv = vec2( ( floor( gl_FragCoord.x / ppp ) * ppp + 0.5 ) / resolution.x, uv.y );
|
||||
float pixId = mod( gl_FragCoord.x, ppp );
|
||||
vec2 dpix = vec2( 1.0 ) / resolution;
|
||||
|
||||
float dt = deltaTime;
|
||||
|
||||
// == if it is not head of particles =============================================================
|
||||
if ( ppp < gl_FragCoord.x ) {
|
||||
puv.x -= ppp / resolution.x;
|
||||
vec4 tex0 = texture( samplerCompute, puv );
|
||||
vec4 tex1 = texture( samplerCompute, puv + dpix * vec2( 1.0, 0.0 ) );
|
||||
|
||||
tex0.w = saturate( tex0.w - 1.0 / trailLength ); // decrease the life
|
||||
|
||||
fragColor = (
|
||||
pixId < 1.0 ? tex0 :
|
||||
tex1
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// == prepare some vars ==========================================================================
|
||||
vec4 seed = texture( samplerRandom, puv );
|
||||
prng( seed );
|
||||
|
||||
vec4 tex0 = texture( samplerCompute, puv );
|
||||
vec4 tex1 = texture( samplerCompute, puv + dpix * vec2( 1.0, 0.0 ) );
|
||||
|
||||
vec3 pos = tex0.xyz;
|
||||
float life = tex0.w;
|
||||
vec3 vel = tex1.xyz;
|
||||
float jumpFlag = tex1.w;
|
||||
|
||||
float timing = mix( 0.0, PARTICLE_LIFE_LENGTH, floor( puv.y * trails ) / trails );
|
||||
timing += lofi( time, PARTICLE_LIFE_LENGTH );
|
||||
|
||||
if ( time - deltaTime + PARTICLE_LIFE_LENGTH < timing ) {
|
||||
timing -= PARTICLE_LIFE_LENGTH;
|
||||
}
|
||||
|
||||
// == initialize particles =======================================================================
|
||||
if (
|
||||
time - deltaTime < timing && timing <= time
|
||||
) {
|
||||
dt = time - timing;
|
||||
|
||||
pos = 1.0 * randomSphere( seed );
|
||||
|
||||
vel = 1.0 * randomSphere( seed );
|
||||
|
||||
life = 1.0;
|
||||
|
||||
jumpFlag = 1.0;
|
||||
} else {
|
||||
jumpFlag = 0.0; // remove jumping flag
|
||||
}
|
||||
|
||||
// == update particles ===========================================================================
|
||||
// distFunc
|
||||
float dist = fDistFunc( pos.xyz ) - 0.2;
|
||||
vec3 nor = normalFunc( pos.xyz, 1E-4 );
|
||||
vel -= dt * 100.0 * dist * nor;
|
||||
|
||||
// spin around center
|
||||
vel.zx += dt * 20.0 * vec2( -1.0, 1.0 ) * normalize( nor.xz );
|
||||
|
||||
// noise field
|
||||
vel += 40.0 * vec3(
|
||||
noise( vec4( pos.xyz, 1.485 + sin( time * 0.1 ) + noisePhase ) ),
|
||||
noise( vec4( pos.xyz, 3.485 + sin( time * 0.1 ) + noisePhase ) ),
|
||||
noise( vec4( pos.xyz, 5.485 + sin( time * 0.1 ) + noisePhase ) )
|
||||
) * dt;
|
||||
|
||||
// resistance
|
||||
vel *= exp( -10.0 * dt );
|
||||
// vel.z += 10.0 * dt;
|
||||
|
||||
vec3 v = vel;
|
||||
// float vmax = max( abs( v.x ), max( abs( v.y ), abs( v.z ) ) );
|
||||
// v *= (
|
||||
// abs( v.x ) == vmax ? vec3( 1.0, 0.0, 0.0 ) :
|
||||
// abs( v.y ) == vmax ? vec3( 0.0, 1.0, 0.0 ) :
|
||||
// vec3( 0.0, 0.0, 1.0 )
|
||||
// );
|
||||
|
||||
// pos.xyz += velScale * v * dt;
|
||||
pos += v * dt;
|
||||
life -= dt / PARTICLE_LIFE_LENGTH;
|
||||
|
||||
fragColor = (
|
||||
pixId < 1.0 ? vec4( pos, life ) :
|
||||
vec4( vel, jumpFlag )
|
||||
);
|
||||
}
|
44
src/shaders/trails-render.frag
Normal file
44
src/shaders/trails-render.frag
Normal file
@@ -0,0 +1,44 @@
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
const int MTL_UNLIT = 1;
|
||||
const int MTL_PBR = 2;
|
||||
|
||||
const float PI = 3.14159265;
|
||||
const float TAU = 6.28318531;
|
||||
|
||||
#define saturate(i) clamp(i,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
|
||||
// == varings / uniforms ===========================================================================
|
||||
in vec4 vPosition;
|
||||
in vec3 vNormal;
|
||||
in vec4 vColor;
|
||||
in float vLife;
|
||||
in vec4 vRandom;
|
||||
|
||||
layout (location = 0) out vec4 fragPosition;
|
||||
layout (location = 1) out vec4 fragNormal;
|
||||
layout (location = 2) out vec4 fragColor;
|
||||
layout (location = 3) out vec4 fragWTF;
|
||||
|
||||
uniform float time;
|
||||
|
||||
// == common =======================================================================================
|
||||
mat2 rotate2D( float _t ) {
|
||||
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
|
||||
}
|
||||
|
||||
// == main procedure ===============================================================================
|
||||
void main() {
|
||||
if ( vColor.a < 0.0 ) { discard; }
|
||||
|
||||
float emissive = 4.0;
|
||||
// emissive *= 0.5 + 0.5 * sin( TAU * vRandom.z + 20.0 * time );
|
||||
|
||||
fragPosition = vPosition;
|
||||
fragNormal = vec4( vNormal, 1.0 );
|
||||
fragColor = vColor;
|
||||
fragWTF = vec4( vec3( 0.9, 0.9, emissive ), MTL_PBR );
|
||||
}
|
123
src/shaders/trails-render.vert
Normal file
123
src/shaders/trails-render.vert
Normal file
@@ -0,0 +1,123 @@
|
||||
#version 300 es
|
||||
|
||||
const float HUGE = 9E16;
|
||||
const float PI = 3.14159265;
|
||||
const float TAU = 6.283185307;
|
||||
|
||||
#define saturate(x) clamp(x,0.,1.)
|
||||
#define linearstep(a,b,x) saturate(((x)-(a))/((b)-(a)))
|
||||
#define lofi(i,m) (floor((i)/(m))*(m))
|
||||
#define lofir(i,m) (floor((i+0.5)/(m))*(m))
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
in float computeU;
|
||||
in float computeV;
|
||||
in float triIndex;
|
||||
|
||||
out float vLife;
|
||||
out vec3 vNormal;
|
||||
out vec4 vPosition;
|
||||
out vec4 vColor;
|
||||
out vec4 vRandom;
|
||||
|
||||
uniform bool isShadow;
|
||||
uniform float ppp;
|
||||
uniform float trailShaker;
|
||||
uniform float colorVar;
|
||||
uniform float colorOffset;
|
||||
uniform vec2 resolution;
|
||||
uniform vec2 resolutionCompute;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 normalMatrix;
|
||||
uniform sampler2D samplerCompute;
|
||||
uniform sampler2D samplerRandomStatic;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
vec3 blurpleGradient( float t ) {
|
||||
vec3 colorA = vec3( 0.01, 0.04, 0.2 );
|
||||
vec3 colorB = vec3( 0.02, 0.3, 0.9 );
|
||||
vec3 colorC = vec3( 0.9, 0.01, 0.6 );
|
||||
vec3 colorD = vec3( 0.5, 0.02, 0.02 );
|
||||
|
||||
return mix(
|
||||
colorA,
|
||||
mix(
|
||||
colorB,
|
||||
mix(
|
||||
colorC,
|
||||
colorD,
|
||||
linearstep( 0.67, 1.0, t )
|
||||
),
|
||||
linearstep( 0.33, 0.67, t )
|
||||
),
|
||||
linearstep( 0.0, 0.33, t )
|
||||
);
|
||||
}
|
||||
|
||||
vec3 catColor( float _p ) {
|
||||
return 0.5 + 0.5 * vec3(
|
||||
cos( _p ),
|
||||
cos( _p + PI / 3.0 * 4.0 ),
|
||||
cos( _p + PI / 3.0 * 2.0 )
|
||||
);
|
||||
}
|
||||
|
||||
vec4 random( vec2 _uv ) {
|
||||
return texture( samplerRandomStatic, _uv );
|
||||
}
|
||||
|
||||
mat2 rotate2D( float _t ) {
|
||||
return mat2( cos( _t ), sin( _t ), -sin( _t ), cos( _t ) );
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
void main() {
|
||||
vec2 puv = vec2( computeU, computeV );
|
||||
vec2 dppix = vec2( 1.0 ) / resolutionCompute;
|
||||
|
||||
// == fetch texture ==============================================================================
|
||||
vec4 pos = texture( samplerCompute, puv );
|
||||
vec4 vel = texture( samplerCompute, puv + dppix * vec2( 1.0, 0.0 ) );
|
||||
vec4 velp = texture( samplerCompute, puv + dppix * vec2( -ppp + 1.0, 0.0 ) );
|
||||
|
||||
// == assign varying variables ===================================================================
|
||||
vLife = pos.w;
|
||||
|
||||
vRandom = random( puv.yy * 182.92 );
|
||||
|
||||
vColor.xyz = (
|
||||
vRandom.y < 0.8
|
||||
? pow( catColor( TAU * ( ( vRandom.x * 2.0 - 1.0 ) * colorVar + 0.6 + colorOffset ) ), vec3( 2.0 ) )
|
||||
: vec3( 0.4 )
|
||||
);
|
||||
vColor.xyz = blurpleGradient( vLife );
|
||||
// vColor.xyz = catColor( 3.0 + 4.0 * vLife );
|
||||
|
||||
vColor.w = ( velp.w < 0.5 && vel.w < 0.5 && 0.0 < vLife ) ? 1.0 : -1.0;
|
||||
|
||||
// == compute size and direction =================================================================
|
||||
float size = 0.005;
|
||||
size *= 1.0 + pow( vRandom.w, 2.0 );
|
||||
// size *= max( 0.0, sin( PI * vLife ) );
|
||||
|
||||
vec3 dir = normalize( vel.xyz );
|
||||
vec3 sid = normalize( cross( dir, vec3( 0.0, 1.0, 0.0 ) ) );
|
||||
vec3 top = normalize( cross( sid, dir ) );
|
||||
|
||||
float theta = triIndex / 3.0 * TAU + vLife * 1.0;
|
||||
vec2 tri = vec2( sin( theta ), cos( theta ) );
|
||||
vNormal = ( normalMatrix * vec4( tri.x * sid + tri.y * top, 1.0 ) ).xyz;
|
||||
pos.xyz += size * vNormal;
|
||||
|
||||
vPosition = modelMatrix * vec4( pos.xyz, 1.0 );
|
||||
vec4 outPos = projectionMatrix * viewMatrix * vPosition;
|
||||
outPos.x *= resolution.y / resolution.x;
|
||||
gl_Position = outPos;
|
||||
|
||||
vPosition.w = outPos.z / outPos.w;
|
||||
|
||||
// gl_PointSize = resolution.y * size / outPos.z;
|
||||
}
|
18
src/utils/Pool.ts
Normal file
18
src/utils/Pool.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export class Pool<T> {
|
||||
public array: T[];
|
||||
|
||||
private __index = 0;
|
||||
|
||||
public get current(): T {
|
||||
return this.array[ this.__index ];
|
||||
}
|
||||
|
||||
public constructor( array: T[] ) {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public next(): T {
|
||||
this.__index = ( this.__index + 1 ) % this.array.length;
|
||||
return this.current;
|
||||
}
|
||||
}
|
54
src/utils/RandomTexture.ts
Normal file
54
src/utils/RandomTexture.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { GLCat, GLCatTexture } from '@fms-cat/glcat-ts';
|
||||
import { Xorshift } from '@fms-cat/experimental';
|
||||
import { DISPLAY } from '../heck/DISPLAY';
|
||||
|
||||
export class RandomTexture {
|
||||
private __texture: GLCatTexture<WebGL2RenderingContext>;
|
||||
private __array: Uint8Array;
|
||||
private __rng: Xorshift;
|
||||
private __width: number;
|
||||
private __height: number;
|
||||
|
||||
public constructor(
|
||||
glCat: GLCat<WebGL2RenderingContext>,
|
||||
width: number,
|
||||
height: number,
|
||||
) {
|
||||
this.__width = width;
|
||||
this.__height = height;
|
||||
this.__rng = new Xorshift();
|
||||
this.__array = new Uint8Array( width * height * 4 );
|
||||
this.__texture = glCat.createTexture()!;
|
||||
this.__texture.textureWrap( DISPLAY.gl.REPEAT );
|
||||
}
|
||||
|
||||
public get texture(): GLCatTexture<WebGL2RenderingContext> {
|
||||
return this.__texture;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.__texture.dispose();
|
||||
}
|
||||
|
||||
public resize( width: number, height: number, seed?: number ): void {
|
||||
this.__width = width;
|
||||
this.__height = height;
|
||||
this.__array = new Uint8Array( width * height * 4 );
|
||||
|
||||
this.update( seed );
|
||||
}
|
||||
|
||||
public update( seed?: number ): void {
|
||||
if ( seed ) { this.__rng.seed = seed; }
|
||||
|
||||
for ( let i = 0; i < this.__array.length; i ++ ) {
|
||||
this.__array[ i ] = Math.floor( this.__rng.gen() * 256.0 );
|
||||
}
|
||||
|
||||
this.__texture.setTextureFromArray(
|
||||
this.__width,
|
||||
this.__height,
|
||||
this.__array
|
||||
);
|
||||
}
|
||||
}
|
5
src/utils/createDefineString.ts
Normal file
5
src/utils/createDefineString.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function createDefineString( defines: { [ identifier: string ]: string | undefined } ): string {
|
||||
return Object.entries( defines ).map( ( [ identifier, value ] ) => (
|
||||
value ? `#define ${identifier} ${value}\n` : ''
|
||||
) ).join( '' );
|
||||
}
|
3
src/utils/mod.ts
Normal file
3
src/utils/mod.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function mod( value: number, divisor: number ): number {
|
||||
return value - Math.floor( value / divisor ) * divisor;
|
||||
}
|
4
src/vert.d.ts
vendored
Normal file
4
src/vert.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module '*.vert' {
|
||||
const code: string;
|
||||
export default code;
|
||||
}
|
9
src/window.d.ts
vendored
Normal file
9
src/window.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
interface Window {
|
||||
divPath?: HTMLDivElement;
|
||||
divAutomaton?: HTMLDivElement;
|
||||
checkActive?: HTMLInputElement;
|
||||
strComponentsUpdate?: string;
|
||||
divComponentsUpdate?: HTMLDivElement;
|
||||
strComponentsDraw?: string;
|
||||
divComponentsDraw?: HTMLDivElement;
|
||||
}
|
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": false,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
84
webpack.config.js
Normal file
84
webpack.config.js
Normal file
@@ -0,0 +1,84 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const HtmlWebpackPlugin = require( 'html-webpack-plugin' );
|
||||
const packageJson = require( './package.json' );
|
||||
const path = require( 'path' );
|
||||
const webpack = require( 'webpack' );
|
||||
|
||||
module.exports = ( env, argv ) => {
|
||||
const VERSION = packageJson.version;
|
||||
const DEV = argv.mode === 'development';
|
||||
// console.info( `Webpack: Building ${ packageJson.name } v${ VERSION } under ${ argv.mode } settings...` );
|
||||
|
||||
return {
|
||||
entry: path.resolve( __dirname, 'src/main.ts' ),
|
||||
output: {
|
||||
path: path.join( __dirname, 'dist' ),
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.js', '.ts' ],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /automaton\.json$/,
|
||||
use: [
|
||||
{
|
||||
loader: path.resolve( __dirname, './loaders/automaton-json-loader.js' ),
|
||||
options: {
|
||||
minimize: DEV ? false : {
|
||||
precisionTime: 3,
|
||||
precisionValue: 3,
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
{
|
||||
test: /\.(glsl|frag|vert)$/,
|
||||
use: [
|
||||
'raw-loader',
|
||||
// shader minifier is kinda jej
|
||||
// want to try it works well in development phase
|
||||
{
|
||||
loader: path.resolve( __dirname, './loaders/shader-minifier-loader.js' ),
|
||||
options: {
|
||||
preserveExternals: true,
|
||||
},
|
||||
},
|
||||
'glslify-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
'ts-loader',
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
minimize: !DEV,
|
||||
moduleIds: DEV ? 'named' : undefined,
|
||||
usedExports: !DEV,
|
||||
},
|
||||
devServer: {
|
||||
inline: true,
|
||||
hot: true
|
||||
},
|
||||
devtool: DEV ? 'inline-source-map' : 'source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin( {
|
||||
'process.env': {
|
||||
DEV,
|
||||
VERSION: `"${ VERSION }"`
|
||||
},
|
||||
} ),
|
||||
new HtmlWebpackPlugin(),
|
||||
],
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user