ECMAPortal integration / Development start
This commit is contained in:
parent
c5f9981c86
commit
309452f16e
|
@ -0,0 +1,20 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'@nuxtjs/eslint-config-typescript',
|
||||||
|
'plugin:nuxt/recommended',
|
||||||
|
'prettier',
|
||||||
|
],
|
||||||
|
plugins: [],
|
||||||
|
// add your custom rules here
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off',
|
||||||
|
indent: ['error', 2],
|
||||||
|
'vue/no-multiple-template-root': 'off',
|
||||||
|
quotes: [2, 'single', { avoidEscape: true }],
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
###
|
||||||
|
# Place your Prettier ignore content here
|
||||||
|
|
||||||
|
###
|
||||||
|
# .gitignore content is duplicated here due to https://github.com/prettier/prettier/issues/8506
|
||||||
|
|
||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
/logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# Nuxt generate
|
||||||
|
dist
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless
|
||||||
|
|
||||||
|
# IDE / Editor
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Service worker
|
||||||
|
sw.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Vim swap files
|
||||||
|
*.swp
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
91
app.vue
91
app.vue
|
@ -1,5 +1,90 @@
|
||||||
<template>
|
<script>
|
||||||
|
// Lazy loading: prepend Lazy to the component name.
|
||||||
|
// Imports come from #components (usually it's an auto import though)
|
||||||
|
// Auto import from npm package
|
||||||
|
/* import { addComponent, defineNuxtModule } from '@nuxt/kit'
|
||||||
|
|
||||||
|
export default defineNuxtModule({
|
||||||
|
setup() {
|
||||||
|
// import { MyComponent as MyAutoImportedComponent } from 'my-npm-package'
|
||||||
|
addComponent({
|
||||||
|
name: 'MyAutoImportedComponent',
|
||||||
|
export: 'MyComponent',
|
||||||
|
filePath: 'my-npm-package',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}) */
|
||||||
|
// Client components: Component.client.vue
|
||||||
|
// .client components are rendered only after being mounted
|
||||||
|
// To access the rendered template using onMounted(), add await nextTick() in the callback of the onMounted() hook.
|
||||||
|
// <ClientOnly></ClientOnly>
|
||||||
|
// Use slot as fallback until clientonly is mounted:
|
||||||
|
/* <template>
|
||||||
<div>
|
<div>
|
||||||
<NuxtWelcome />
|
<Sidebar />
|
||||||
</div>
|
<!-- This renders the "span" element on the server side -->
|
||||||
|
<ClientOnly fallbackTag="span">
|
||||||
|
<!-- this component will only be rendered on client side -->
|
||||||
|
<Comments />
|
||||||
|
<template #fallback>
|
||||||
|
<!-- this will be rendered on server side -->
|
||||||
|
<p>Loading comments...</p>
|
||||||
|
</template>
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
</template> */
|
||||||
|
// <DevOnly></DevOnly>
|
||||||
|
/* <DevOnly>
|
||||||
|
<!-- this component will only be rendered during development -->
|
||||||
|
<LazyDebugBar />
|
||||||
|
|
||||||
|
<!-- if you ever require to have a replacement during production -->
|
||||||
|
<!-- be sure to test these using `nuxt preview` -->
|
||||||
|
<template #fallback>
|
||||||
|
<div><!-- empty div for flex.justify-between --></div>
|
||||||
|
</template>
|
||||||
|
</DevOnly> */
|
||||||
|
// <NuxtClientFallback></NuxtClientFallback> component to render its content on the client if any of its children trigger an error in SSR.
|
||||||
|
// You can specify a fallbackTag to make it render a specific tag if it fails to render on the server.
|
||||||
|
/* <template>
|
||||||
|
<div>
|
||||||
|
<Sidebar />
|
||||||
|
<!-- this component will be rendered on client-side -->
|
||||||
|
<NuxtClientFallback fallback-tag="span">
|
||||||
|
<Comments />
|
||||||
|
<BrokeInSSR />
|
||||||
|
</NuxtClientFallback>
|
||||||
|
</div>
|
||||||
|
</template> */
|
||||||
|
// Then in awesome-ui/nuxt.js you can use the components:dirs hook:
|
||||||
|
/* import { defineNuxtModule, createResolver } from '@nuxt/kit'
|
||||||
|
|
||||||
|
export default defineNuxtModule({
|
||||||
|
hooks: {
|
||||||
|
'components:dirs': (dirs) => {
|
||||||
|
const { resolve } = createResolver(import.meta.url)
|
||||||
|
// Add ./components dir to the list
|
||||||
|
dirs.push({
|
||||||
|
path: resolve('./components'),
|
||||||
|
prefix: 'awesome'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) */
|
||||||
|
/* export default defineNuxtConfig({
|
||||||
|
modules: ['awesome-ui/nuxt']
|
||||||
|
}) */
|
||||||
|
// Catch-all route using <ContentDoc> component
|
||||||
|
/*
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<ContentDoc :path="$route.path" />
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main></main>
|
||||||
|
<ECMAPortal directory="/images/portal" animate randomize fade />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
body {
|
||||||
|
background-color: rgb(6, 25, 28);
|
||||||
|
background-image: url("~/assets/images/sky.png");
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-size: cover;
|
||||||
|
background-blend-mode: multiply;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
color: white;
|
||||||
|
font-family: "Lato", sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=main.css.map */
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sourceRoot":"","sources":["main.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA","file":"main.css"}
|
|
@ -0,0 +1,14 @@
|
||||||
|
body {
|
||||||
|
background-color: rgb(6, 25, 28);
|
||||||
|
background-image: url('~/assets/images/sky.png');
|
||||||
|
background-attachment: fixed;
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
background-blend-mode: multiply;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
font-family: 'Lato', sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
|
@ -0,0 +1,635 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
directory: {
|
||||||
|
default: '/images/portal',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
create: Boolean,
|
||||||
|
animate: Boolean,
|
||||||
|
randomize: Boolean,
|
||||||
|
fade: Boolean,
|
||||||
|
speed: {
|
||||||
|
default: 1,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
class Canvas {
|
||||||
|
element: HTMLCanvasElement
|
||||||
|
|
||||||
|
constructor(create: boolean = true, transparent: boolean = false) {
|
||||||
|
this.element = create ? this.create() : this.query()
|
||||||
|
|
||||||
|
if (transparent) this.element.style.opacity = '0'
|
||||||
|
|
||||||
|
this.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): HTMLCanvasElement {
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
|
||||||
|
canvas.width = window.innerWidth
|
||||||
|
canvas.height = window.innerHeight
|
||||||
|
|
||||||
|
canvas.style.display = 'block'
|
||||||
|
canvas.style.position = 'fixed'
|
||||||
|
canvas.style.bottom = '0'
|
||||||
|
canvas.style.left = '0'
|
||||||
|
canvas.style.zIndex = '-1'
|
||||||
|
canvas.style.transition = 'all 0.69420s ease-in'
|
||||||
|
|
||||||
|
canvas.innerHTML =
|
||||||
|
'<span>Your browser does not support the <canvas /> element, which is required for parallax animation.</span>'
|
||||||
|
|
||||||
|
// Add a class for subclassing support.
|
||||||
|
canvas.classList.add('ecmaportal')
|
||||||
|
canvas.id = 'ecmaportal'
|
||||||
|
|
||||||
|
document.body.appendChild(canvas)
|
||||||
|
|
||||||
|
return canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
compatible(
|
||||||
|
obj: HTMLCanvasElement | HTMLElement | Element,
|
||||||
|
): obj is HTMLCanvasElement {
|
||||||
|
return obj.tagName === 'CANVAS'
|
||||||
|
}
|
||||||
|
|
||||||
|
query(): HTMLCanvasElement {
|
||||||
|
const canvas = document.querySelector('#ecmaportal')
|
||||||
|
|
||||||
|
if (!canvas) throw new Error('ECMAPortal canvas not found.')
|
||||||
|
if (!this.compatible(canvas))
|
||||||
|
throw new Error('The ECMAPortal element is not a canvas.')
|
||||||
|
|
||||||
|
return canvas
|
||||||
|
}
|
||||||
|
|
||||||
|
fadeIn() {
|
||||||
|
this.element.style.opacity = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
fadeOut() {
|
||||||
|
this.element.style.opacity = '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
fill() {
|
||||||
|
this.element.width = window.innerWidth
|
||||||
|
this.element.height = window.innerHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
full(): boolean {
|
||||||
|
return (
|
||||||
|
this.element.width === window.innerWidth &&
|
||||||
|
this.element.height === window.innerHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.element.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Portal {
|
||||||
|
private readonly version: string = '1.4T'
|
||||||
|
private readonly canvas: Canvas
|
||||||
|
private readonly prog?: WebGLProgram | null
|
||||||
|
private readonly gl: WebGL2RenderingContext | null
|
||||||
|
|
||||||
|
private animate: boolean = false
|
||||||
|
private readonly randomize: boolean = false
|
||||||
|
private speed: number = 1
|
||||||
|
private readonly initialSpeed: number = 1
|
||||||
|
private readonly dv = 20
|
||||||
|
|
||||||
|
// It's integral for the animation to tie it to the current time, else it will be tied to the framerate.
|
||||||
|
private currentTime: number = Date.now()
|
||||||
|
private pauseTime: number = 0
|
||||||
|
private clickTime: number = 0
|
||||||
|
|
||||||
|
private tick: number = 0
|
||||||
|
private dt: number = 6 / 1000000
|
||||||
|
|
||||||
|
private promises: Promise<HTMLImageElement>[] = []
|
||||||
|
private uniforms!: {
|
||||||
|
modelViewMatrix: WebGLUniformLocation
|
||||||
|
projectionMatrix: WebGLUniformLocation
|
||||||
|
dt: WebGLUniformLocation
|
||||||
|
resolution: WebGLUniformLocation
|
||||||
|
sky: WebGLUniformLocation
|
||||||
|
particles: WebGLUniformLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
directory: string = '/',
|
||||||
|
create: boolean = true,
|
||||||
|
animate: boolean = true,
|
||||||
|
randomize: boolean = false,
|
||||||
|
fade: boolean = false,
|
||||||
|
speed: number = 1,
|
||||||
|
) {
|
||||||
|
// Vertex shader.
|
||||||
|
const vglsl = `#version 300 es
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 Position;
|
||||||
|
|
||||||
|
uniform mat4 modelViewMatrix;
|
||||||
|
uniform mat4 projectionMatrix;
|
||||||
|
|
||||||
|
uniform vec2 canvasResolution;
|
||||||
|
|
||||||
|
vec4 projection_from_position(vec4 position) {
|
||||||
|
vec4 projection = position * 0.5;
|
||||||
|
projection.xy = vec2(projection.x + projection.w, projection.y + projection.w);
|
||||||
|
projection.zw = position.zw;
|
||||||
|
return projection;
|
||||||
|
}
|
||||||
|
|
||||||
|
out vec4 texProj0;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(Position, 1.0);
|
||||||
|
|
||||||
|
texProj0 = projection_from_position(gl_Position);
|
||||||
|
texProj0 = vec4(texProj0.xy * canvasResolution / max(canvasResolution.x, canvasResolution.y), texProj0.zw);
|
||||||
|
}`
|
||||||
|
|
||||||
|
// Fragment shader.
|
||||||
|
const fglsl = `#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
uniform sampler2D sky;
|
||||||
|
uniform sampler2D particles;
|
||||||
|
uniform float dt;
|
||||||
|
|
||||||
|
in vec4 texProj0;
|
||||||
|
|
||||||
|
const int LAYERS = 15;
|
||||||
|
const vec3 COLORS[] = vec3[](
|
||||||
|
vec3(0.022087, 0.098399, 0.110818),
|
||||||
|
vec3(0.011892, 0.095924, 0.089485),
|
||||||
|
vec3(0.027636, 0.101689, 0.100326),
|
||||||
|
vec3(0.046564, 0.109883, 0.114838),
|
||||||
|
vec3(0.064901, 0.117696, 0.097189),
|
||||||
|
vec3(0.063761, 0.086895, 0.123646),
|
||||||
|
vec3(0.084817, 0.111994, 0.166380),
|
||||||
|
vec3(0.097489, 0.154120, 0.091064),
|
||||||
|
vec3(0.106152, 0.131144, 0.195191),
|
||||||
|
vec3(0.097721, 0.110188, 0.187229),
|
||||||
|
vec3(0.133516, 0.138278, 0.148582),
|
||||||
|
vec3(0.070006, 0.243332, 0.235792),
|
||||||
|
vec3(0.196766, 0.142899, 0.214696),
|
||||||
|
vec3(0.047281, 0.315338, 0.321970),
|
||||||
|
vec3(0.204675, 0.390010, 0.302066),
|
||||||
|
vec3(0.080955, 0.314821, 0.661491)
|
||||||
|
);
|
||||||
|
|
||||||
|
const mat4 SCALE_TRANSLATE = mat4(
|
||||||
|
0.5, 0.0, 0.0, 0.25,
|
||||||
|
0.0, 0.5, 0.0, 0.25,
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0
|
||||||
|
);
|
||||||
|
|
||||||
|
mat2 mat2_rotate_z(float radians) {
|
||||||
|
return mat2(
|
||||||
|
cos(radians), -sin(radians),
|
||||||
|
sin(radians), cos(radians)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 portal_layer(float layer) {
|
||||||
|
mat4 translate = mat4(
|
||||||
|
1.0, 0.0, 0.0, 17.0 / layer,
|
||||||
|
0.0, 1.0, 0.0, (2.0 + layer / 1.5) * (dt * 1.5),
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0
|
||||||
|
);
|
||||||
|
|
||||||
|
mat2 rotate = mat2_rotate_z(radians((layer * layer * 4321.0 + layer * 9.0) * 2.0));
|
||||||
|
|
||||||
|
mat2 scale = mat2((4.5 - layer / 4.0) * 2.0);
|
||||||
|
|
||||||
|
return mat4(scale * rotate) * translate * SCALE_TRANSLATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 color = textureProj(sky, texProj0).rgb * COLORS[0];
|
||||||
|
|
||||||
|
for (int i = 0; i < LAYERS; i++) {
|
||||||
|
color += textureProj(particles, texProj0 * portal_layer(float(i + 1))).rgb * COLORS[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
fragColor = vec4(color, 1.0);
|
||||||
|
}`
|
||||||
|
|
||||||
|
// Make sure the resource directory ends with a slash.
|
||||||
|
if (directory.at(-1) !== '/') directory += '/'
|
||||||
|
|
||||||
|
// Images to load.
|
||||||
|
const images = [`${directory}sky.png`, `${directory}particles.png`]
|
||||||
|
|
||||||
|
// Notice.
|
||||||
|
this.notice()
|
||||||
|
|
||||||
|
// Set initial property values.
|
||||||
|
this.animate = animate
|
||||||
|
this.randomize = randomize
|
||||||
|
|
||||||
|
// Set default speed.
|
||||||
|
this.speed = speed
|
||||||
|
this.initialSpeed = this.speed
|
||||||
|
this.dv = 20
|
||||||
|
|
||||||
|
// Create a canvas and acquire its context.
|
||||||
|
this.canvas = new Canvas(create, fade)
|
||||||
|
this.gl = this.canvas.element.getContext('webgl2')
|
||||||
|
|
||||||
|
// If WebGL isn't part of available features, fail.
|
||||||
|
if (!this.gl) {
|
||||||
|
this.canvas.destroy()
|
||||||
|
|
||||||
|
alert(
|
||||||
|
'Unable to initialize WebGL 2.\nThe website will lack parallax animation.',
|
||||||
|
)
|
||||||
|
console.error(
|
||||||
|
'Unable to initialize WebGL 2. Your browser or machine may not support it.',
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current tick and slightly randomize it if animation is enabled.
|
||||||
|
this.tick = Number(this.animate && this.randomize) * this.randomRange(0, 10)
|
||||||
|
|
||||||
|
// Build shaders.
|
||||||
|
this.prog = this.build(vglsl, fglsl)
|
||||||
|
|
||||||
|
if (!this.prog) {
|
||||||
|
this.canvas.destroy()
|
||||||
|
|
||||||
|
alert(
|
||||||
|
'Failed to compile WebGL 2 shaders.\nOpen the developer console for debug output.',
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an event listener to the canvas if animation is enabled.
|
||||||
|
if (this.animate) this.canvas.element.onclick = this.click.bind(this)
|
||||||
|
|
||||||
|
// Bind class context to the resize handler, pass it to the event bus.
|
||||||
|
window.addEventListener('resize', this.resize.bind(this))
|
||||||
|
|
||||||
|
// Create image loading promises.
|
||||||
|
this.promises = images.map((image) => this.loadImage(image))
|
||||||
|
|
||||||
|
// Initialize the scene.
|
||||||
|
this.initialize()
|
||||||
|
|
||||||
|
// Once all promises have been fulfilled, build the scene.
|
||||||
|
Promise.all(this.promises).then((resources) => {
|
||||||
|
// Load resources.
|
||||||
|
resources.forEach((image, index) => this.loadResource(image, index))
|
||||||
|
|
||||||
|
// Bind program
|
||||||
|
this.gl!.useProgram(this.prog!)
|
||||||
|
|
||||||
|
// Begin render.
|
||||||
|
this.render()
|
||||||
|
if (fade) this.canvas.fadeIn()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
notice() {
|
||||||
|
console.log(
|
||||||
|
`%c \u2587%c\u2587%c\u2587 %c\u2587%c\u2587%c\u2587 %c // ECMAPortal v${this.version} by Endermanch & WiPet\n\n\thttps://enderman.ch\n\thttps://go.enderman.ch/wipet`,
|
||||||
|
'color: #E58EFF',
|
||||||
|
'color: #D52DFF',
|
||||||
|
'color: #E58EFF',
|
||||||
|
'color: #E58EFF',
|
||||||
|
'color: #D52DFF',
|
||||||
|
'color: #E58EFF',
|
||||||
|
'color: #008000',
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'If any errors occur below, please send a screenshot of them my way!\n\t%ccontact@enderman.ch',
|
||||||
|
'color: #87CEFA',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
randomRange(min: number, max: number): number {
|
||||||
|
return Math.random() * (max - min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
build(vshSource: string, fshSource: string): WebGLProgram | null {
|
||||||
|
const vsh = this.gl!.createShader(this.gl!.VERTEX_SHADER)
|
||||||
|
|
||||||
|
this.gl!.shaderSource(vsh!, vshSource)
|
||||||
|
this.gl!.compileShader(vsh!)
|
||||||
|
|
||||||
|
if (!this.gl!.getShaderParameter(vsh!, this.gl!.COMPILE_STATUS)) {
|
||||||
|
console.error(this.gl!.getShaderInfoLog(vsh!))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fsh = this.gl!.createShader(this.gl!.FRAGMENT_SHADER)
|
||||||
|
|
||||||
|
this.gl!.shaderSource(fsh!, fshSource)
|
||||||
|
this.gl!.compileShader(fsh!)
|
||||||
|
|
||||||
|
if (!this.gl!.getShaderParameter(fsh!, this.gl!.COMPILE_STATUS)) {
|
||||||
|
console.error(this.gl!.getShaderInfoLog(fsh!))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const program: WebGLProgram = this.gl!.createProgram()!
|
||||||
|
|
||||||
|
this.gl!.attachShader(program, vsh!)
|
||||||
|
this.gl!.attachShader(program, fsh!)
|
||||||
|
|
||||||
|
this.gl!.linkProgram(program)
|
||||||
|
|
||||||
|
if (!this.gl!.getProgramParameter(program, this.gl!.LINK_STATUS)) {
|
||||||
|
console.error(this.gl!.getProgramInfoLog(program))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return program
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage(url: string): Promise<HTMLImageElement> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image()
|
||||||
|
|
||||||
|
img.onload = () => resolve(img)
|
||||||
|
img.onerror = reject
|
||||||
|
img.src = url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadResource(image: HTMLImageElement, index: number) {
|
||||||
|
const samplerParameters = [this.gl!.CLAMP_TO_EDGE, this.gl!.MIRRORED_REPEAT]
|
||||||
|
|
||||||
|
const sampler = this.gl!.createSampler()!
|
||||||
|
|
||||||
|
this.gl!.samplerParameteri(
|
||||||
|
sampler,
|
||||||
|
this.gl!.TEXTURE_WRAP_S,
|
||||||
|
samplerParameters[index],
|
||||||
|
)
|
||||||
|
this.gl!.samplerParameteri(
|
||||||
|
sampler,
|
||||||
|
this.gl!.TEXTURE_WRAP_T,
|
||||||
|
samplerParameters[index],
|
||||||
|
)
|
||||||
|
this.gl!.samplerParameteri(
|
||||||
|
sampler,
|
||||||
|
this.gl!.TEXTURE_WRAP_R,
|
||||||
|
samplerParameters[index],
|
||||||
|
)
|
||||||
|
|
||||||
|
this.gl!.samplerParameteri(
|
||||||
|
sampler,
|
||||||
|
this.gl!.TEXTURE_MIN_FILTER,
|
||||||
|
this.gl!.NEAREST_MIPMAP_LINEAR,
|
||||||
|
)
|
||||||
|
this.gl!.samplerParameteri(
|
||||||
|
sampler,
|
||||||
|
this.gl!.TEXTURE_MAG_FILTER,
|
||||||
|
this.gl!.NEAREST,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.gl!.bindSampler(index, sampler)
|
||||||
|
|
||||||
|
const texture = this.gl!.createTexture()
|
||||||
|
|
||||||
|
this.gl!.activeTexture(this.gl!.TEXTURE0 + index)
|
||||||
|
this.gl!.bindTexture(this.gl!.TEXTURE_2D, texture)
|
||||||
|
|
||||||
|
this.gl!.texImage2D(
|
||||||
|
this.gl!.TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
this.gl!.RGBA,
|
||||||
|
this.gl!.RGBA,
|
||||||
|
this.gl!.UNSIGNED_BYTE,
|
||||||
|
image,
|
||||||
|
)
|
||||||
|
this.gl!.generateMipmap(this.gl!.TEXTURE_2D)
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.uniforms = {
|
||||||
|
modelViewMatrix: this.gl!.getUniformLocation(
|
||||||
|
this.prog!,
|
||||||
|
'modelViewMatrix',
|
||||||
|
)!,
|
||||||
|
projectionMatrix: this.gl!.getUniformLocation(
|
||||||
|
this.prog!,
|
||||||
|
'projectionMatrix',
|
||||||
|
)!,
|
||||||
|
dt: this.gl!.getUniformLocation(this.prog!, 'dt')!,
|
||||||
|
resolution: this.gl!.getUniformLocation(this.prog!, 'canvasResolution')!,
|
||||||
|
sky: this.gl!.getUniformLocation(this.prog!, 'sky')!,
|
||||||
|
particles: this.gl!.getUniformLocation(this.prog!, 'particles')!,
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertexPosBuffer = this.gl!.createBuffer()
|
||||||
|
const positions = new Float32Array([
|
||||||
|
-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0,
|
||||||
|
])
|
||||||
|
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, vertexPosBuffer)
|
||||||
|
this.gl!.bufferData(this.gl!.ARRAY_BUFFER, positions, this.gl!.STATIC_DRAW)
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, null)
|
||||||
|
|
||||||
|
const vertexTexBuffer = this.gl!.createBuffer()
|
||||||
|
const texCoords = new Float32Array([
|
||||||
|
0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
|
])
|
||||||
|
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, vertexTexBuffer)
|
||||||
|
this.gl!.bufferData(this.gl!.ARRAY_BUFFER, texCoords, this.gl!.STATIC_DRAW)
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, null)
|
||||||
|
|
||||||
|
const vertexPosLocation = 0
|
||||||
|
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, vertexPosBuffer)
|
||||||
|
this.gl!.vertexAttribPointer(
|
||||||
|
vertexPosLocation,
|
||||||
|
2,
|
||||||
|
this.gl!.FLOAT,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
this.gl!.enableVertexAttribArray(vertexPosLocation)
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, null)
|
||||||
|
|
||||||
|
const vertexTexLocation = 4
|
||||||
|
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, vertexTexBuffer)
|
||||||
|
this.gl!.vertexAttribPointer(
|
||||||
|
vertexTexLocation,
|
||||||
|
2,
|
||||||
|
this.gl!.FLOAT,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
this.gl!.enableVertexAttribArray(vertexTexLocation)
|
||||||
|
this.gl!.bindBuffer(this.gl!.ARRAY_BUFFER, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
scene() {
|
||||||
|
const identityMatrix = new Float32Array([
|
||||||
|
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
1.0,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (this.animate) {
|
||||||
|
// Speed up the animation if the user has clicked.
|
||||||
|
if (this.clickTime !== null) {
|
||||||
|
this.speed =
|
||||||
|
this.dv * this.initialSpeed -
|
||||||
|
Math.min(
|
||||||
|
((Date.now() - this.clickTime) / 100) * this.initialSpeed,
|
||||||
|
(this.dv - 1) * this.initialSpeed,
|
||||||
|
)
|
||||||
|
if (this.speed === this.initialSpeed) this.clickTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// R(t) = t + dt (mod 20)
|
||||||
|
this.tick += this.dt
|
||||||
|
this.tick %= 20
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gl!.uniformMatrix4fv(
|
||||||
|
this.uniforms.modelViewMatrix,
|
||||||
|
false,
|
||||||
|
identityMatrix,
|
||||||
|
)
|
||||||
|
this.gl!.uniformMatrix4fv(
|
||||||
|
this.uniforms.projectionMatrix,
|
||||||
|
false,
|
||||||
|
identityMatrix,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.gl!.uniform2f(
|
||||||
|
this.uniforms.resolution,
|
||||||
|
this.canvas.element.width,
|
||||||
|
this.canvas.element.height,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.gl!.uniform1f(this.uniforms.dt, this.tick)
|
||||||
|
this.gl!.uniform1i(this.uniforms.sky, 0)
|
||||||
|
this.gl!.uniform1i(this.uniforms.particles, 1)
|
||||||
|
|
||||||
|
this.gl!.drawArraysInstanced(this.gl!.TRIANGLES, 0, 6, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// Make sure the viewport matches the size of the canvas' drawingBuffer.
|
||||||
|
this.gl!.viewport(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
this.gl!.drawingBufferWidth,
|
||||||
|
this.gl!.drawingBufferHeight,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Calculate the delta time.
|
||||||
|
this.dt = ((Date.now() - this.currentTime) * this.speed) / 1000000
|
||||||
|
this.currentTime = Date.now()
|
||||||
|
|
||||||
|
// Run the scene.
|
||||||
|
this.scene()
|
||||||
|
|
||||||
|
// Request the next animation frame if animation is enabled.
|
||||||
|
if (this.animate) requestAnimationFrame(this.render.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
resize() {
|
||||||
|
if (!this.canvas.full()) this.canvas.fill()
|
||||||
|
|
||||||
|
// If we have animation disabled, we still have to re-render the scene once on resize.
|
||||||
|
if (!this.animate) requestAnimationFrame(this.render.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
click() {
|
||||||
|
this.clickTime = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
continue() {
|
||||||
|
if (this.pauseTime === 0) return
|
||||||
|
|
||||||
|
// Add the time that has passed since the pause to the current time.
|
||||||
|
this.currentTime += Date.now() - this.pauseTime
|
||||||
|
this.pauseTime = 0
|
||||||
|
this.animate = true
|
||||||
|
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
if (this.pauseTime !== 0) return
|
||||||
|
|
||||||
|
// Save the pause time to calculate the delta time.
|
||||||
|
this.pauseTime = Date.now()
|
||||||
|
this.animate = false
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// Doesn't need to be called usually, the garbage collector should do its job.
|
||||||
|
// The method isn't implemented, but the pseudocode is as follows:
|
||||||
|
|
||||||
|
// this.gl!.deleteBuffer(vertexPosBuffer);
|
||||||
|
// this.gl!.deleteBuffer(vertexTexBuffer);
|
||||||
|
|
||||||
|
// this.gl!.deleteSampler(sampler);
|
||||||
|
// this.gl!.deleteTexture(texture);
|
||||||
|
|
||||||
|
this.gl!.deleteProgram(this.prog!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(
|
||||||
|
() =>
|
||||||
|
new Portal(
|
||||||
|
props.directory,
|
||||||
|
props.create,
|
||||||
|
props.animate,
|
||||||
|
props.randomize,
|
||||||
|
props.fade,
|
||||||
|
props.speed,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<canvas id="ecmaportal" class="parallax">
|
||||||
|
<span>
|
||||||
|
Your browser does not support the <canvas /> element, which is
|
||||||
|
required for parallax animation.
|
||||||
|
</span>
|
||||||
|
</canvas>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.parallax {
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.6942s ease-in;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,4 +1,39 @@
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
devtools: { enabled: true }
|
app: {
|
||||||
|
head: {
|
||||||
|
title: "Enderman's Website",
|
||||||
|
htmlAttrs: {
|
||||||
|
lang: 'en',
|
||||||
|
},
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
charset: 'utf-8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'viewport',
|
||||||
|
content: 'width=device-width, initial-scale=1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'http-equiv': 'X-UA-Compatible',
|
||||||
|
content: 'ie=edge',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
dirs: [
|
||||||
|
{
|
||||||
|
path: '~/components',
|
||||||
|
pathPrefix: false,
|
||||||
|
extensions: ['.vue'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
css: ['~/assets/styles/main.css'],
|
||||||
|
plugins: [],
|
||||||
|
modules: ['@nuxt/content', '@nuxtjs/eslint-module', '@pinia/nuxt'],
|
||||||
|
typescript: {
|
||||||
|
typeCheck: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
|
@ -7,12 +7,33 @@
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare",
|
||||||
|
"lint:js": "eslint --ext \".js,.ts,.vue\" --ignore-path .gitignore .",
|
||||||
|
"lint:prettier": "prettier --check .",
|
||||||
|
"lint": "npm run lint:js && npm run lint:style && npm run lint:prettier",
|
||||||
|
"lintfix": "prettier --write --list-different . && npm run lint:js -- --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nuxt/content": "^2.9.0",
|
||||||
"@nuxt/devtools": "latest",
|
"@nuxt/devtools": "latest",
|
||||||
|
"@nuxt/types": "^2.17.2",
|
||||||
|
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
||||||
|
"@nuxtjs/eslint-module": "^4.1.0",
|
||||||
|
"@pinia/nuxt": "^0.5.1",
|
||||||
|
"@typescript-eslint/parser": "^6.10.0",
|
||||||
|
"eslint": "^8.53.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-nuxt": "^4.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"nuxt": "^3.8.1",
|
"nuxt": "^3.8.1",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"sass": "^1.69.5",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
"vue": "^3.3.8",
|
"vue": "^3.3.8",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5",
|
||||||
|
"vue-tsc": "^1.8.22"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pinia": "^2.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -1,3 +1,21 @@
|
||||||
{
|
{
|
||||||
"extends": "../.nuxt/tsconfig.server.json"
|
"compilerOptions": {
|
||||||
|
"target": "ES2018",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./*"],
|
||||||
|
"@/*": ["./*"]
|
||||||
|
},
|
||||||
|
"types": ["@nuxt/types", "@nuxtjs/axios", "@nuxt/content", "@types/node"]
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", ".nuxt", "dist"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue