├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── astro.config.mjs ├── package.json ├── postcss.config.cjs ├── public └── assets │ └── img │ ├── head │ ├── apple-touch-icon.webp │ ├── favicon.ico │ ├── screenshot.webp │ └── thumbnail.webp │ ├── item │ ├── blackpanther.webp │ ├── blackwidow.webp │ ├── captainamerica.webp │ ├── doctorstrange.webp │ ├── drax.webp │ ├── falcon.webp │ ├── gamora.webp │ ├── groot.webp │ ├── hulk.webp │ ├── ironman.webp │ ├── mantis.webp │ ├── nebula.webp │ ├── okoye.webp │ ├── rocket.webp │ ├── scarletwitch.webp │ ├── spiderman.webp │ ├── starroad.webp │ ├── syuri.webp │ ├── thor.webp │ ├── vision.webp │ ├── warmachine.webp │ ├── wintersoldier.webp │ └── wong.webp │ └── texture │ ├── matcap.jpg │ └── noise.png ├── replaceHtml.mjs ├── screenshot1.webp ├── screenshot2.webp ├── screenshot3.webp ├── src ├── assets │ ├── js │ │ ├── app.js │ │ ├── lib │ │ │ └── setGui.js │ │ └── webgl │ │ │ ├── Object.js │ │ │ ├── ObjectPlane.js │ │ │ ├── Stage.js │ │ │ ├── WebGL.js │ │ │ └── doxas │ │ │ ├── WebGLMath.js │ │ │ └── WebGLOrbitCamera.js │ ├── scss │ │ ├── app.scss │ │ ├── foundation │ │ │ ├── _reset.scss │ │ │ └── _root.scss │ │ ├── global │ │ │ ├── _app.scss │ │ │ ├── _function.scss │ │ │ └── _variable.scss │ │ └── other │ │ │ └── _utility.scss │ └── shader │ │ ├── _inc │ │ ├── matcap.glsl │ │ ├── optimizationUv.glsl │ │ └── ratio.glsl │ │ ├── frag │ │ └── plane.glsl │ │ └── vert │ │ └── plane.glsl ├── components │ └── Copyright.astro ├── env.d.ts ├── layouts │ └── Layout.astro └── pages │ └── index.astro └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | package-lock.json 24 | 25 | _note 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "trailingComma": "all", 4 | "useTabs": false, 5 | "semi": true, 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toys.029 | WebGL School Task.07 ~ Plane geometry animation using pure WebGL. 2 | 3 | ## 🪬 ~ 要件 4 | - テクスチャを複数同時に利用する実装に挑戦してみましょう。 5 | ※ 生 WebGL で実装すること 6 | 7 | 8 | ## 👾 ~ Demo 9 | 10 | 11 | - https://dev.shoya-kajita.com/029/ 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ## 🎮 ~ Getting Started 22 | 23 | ``` 24 | // install 25 | npm i 26 | 27 | // development 28 | npm run dev 29 | 30 | // production 31 | npm run build 32 | 33 | // build preview 34 | npm run preview 35 | ``` 36 | 37 | ## 📝 ~ Note 38 | 39 | ### matcap 40 | 41 | - https://github.com/hughsk/matcap 42 | 43 | ```glsl 44 | // vec3 eye: the camera's current position. 45 | // vec3 normal: the surface's normal vector. 46 | 47 | vec2 matcap(vec3 eye, vec3 normal) { 48 | vec3 reflected = reflect(eye, normal); 49 | float m = 2.8284271247461903 * sqrt( reflected.z+1.0 ); 50 | return reflected.xy / m + 0.5; 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import postcssMergeQueries from "postcss-merge-queries"; 3 | import htmlMinify from "@frontendista/astro-html-minify"; 4 | import glsl from "vite-plugin-glsl"; 5 | import sitemap from "@astrojs/sitemap"; 6 | import * as dotenv from "dotenv"; 7 | dotenv.config(); 8 | 9 | function createDate() { 10 | const now = new Date(), 11 | year = now.getFullYear(), 12 | month = now.getMonth() + 1, 13 | date = now.getDate(), 14 | hour = now.getHours(), 15 | minute = now.getMinutes(); 16 | return `${year}${month}${date}${hour}${minute}`; 17 | // return `${year}${month}${date}`; 18 | } 19 | 20 | const DATE = createDate(), 21 | MODE = process.env.NODE_ENV, 22 | SITE_URL = MODE === "production" ? process.env.PUBLIC_PROD_URL : process.env.PUBLIC_LOCAL_URL; 23 | // ? process.env.PUBLIC_TEST_URL : process.env.PUBLIC_LOCAL_URL; 24 | // ? process.env.PUBLIC_LOCAL_URL : process.env.PUBLIC_LOCAL_URL; 25 | 26 | console.log( 27 | `// --------------------------\n\n⚡️ ~ MODE : ${MODE}\n\n▕▔▔▔▔▔▔▔▔▔▔▔╲\n▕╮╭┻┻╮╭┻┻╮╭▕╮╲\n▕╯┃╭╮┃┃╭╮┃╰▕╯╭▏\n▕╭┻┻┻┛┗┻┻┛ ╰▏ ▏\n▕╰━━━┓┈┈┈╭╮▕╭╮▏\n▕╭╮╰┳┳┳┳╯╰╯▕╰╯▏\n▕╰╯┈┗┛┗┛┈╭╮▕╮┈▏\n\n// --------------------------`, 28 | ); 29 | 30 | console.log(SITE_URL); 31 | 32 | // https://astro.build/config 33 | export default defineConfig({ 34 | markdown: { 35 | drafts: true, 36 | }, 37 | build: { 38 | assets: "assets", 39 | }, 40 | site: SITE_URL, 41 | base: "/", 42 | vite: { 43 | plugins: [glsl()], 44 | esbuild: { 45 | drop: ["console", "debugger"], 46 | }, 47 | build: { 48 | assetsInlineLimit: 0, 49 | chunkSizeWarningLimit: 100000000, 50 | rollupOptions: { 51 | output: { 52 | assetFileNames: `assets/[ext]/[name].${DATE}[extname]`, 53 | entryFileNames: `assets/js/build.${DATE}.js`, 54 | }, 55 | }, 56 | cssCodeSplit: false, 57 | }, 58 | css: { 59 | postcss: { 60 | plugins: [postcssMergeQueries], 61 | }, 62 | }, 63 | server: { 64 | open: true, 65 | port: 8080, 66 | }, 67 | preview: { 68 | open: true, 69 | port: 8080, 70 | }, 71 | }, 72 | integrations: [ 73 | sitemap(), 74 | htmlMinify({ 75 | reportCompressedSize: false, 76 | htmlTerserMinifierOptions: { 77 | removeComments: true, 78 | removeTagWhitespace: false, 79 | }, 80 | }), 81 | ], 82 | server: { 83 | open: true, 84 | host: true, 85 | port: 3000, 86 | }, 87 | preview: { 88 | open: true, 89 | host: true, 90 | port: 3000, 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build && node replaceHtml.mjs", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "astro": "^2.2.1", 14 | "gsap": "^3.11.4", 15 | "lil-gui": "^0.18.2" 16 | }, 17 | "devDependencies": { 18 | "@astrojs/sitemap": "^1.2.2", 19 | "@frontendista/astro-html-minify": "^1.0.7", 20 | "autoprefixer": "^10.4.14", 21 | "dotenv": "^16.0.3", 22 | "fs-extra": "^11.1.1", 23 | "glob": "^9.3.4", 24 | "postcss-merge-queries": "^1.0.3", 25 | "prettier": "^2.8.7", 26 | "prettier-plugin-astro": "^0.8.0", 27 | "sass": "^1.61.0", 28 | "vite-plugin-glsl": "^1.1.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("autoprefixer")], 3 | }; 4 | -------------------------------------------------------------------------------- /public/assets/img/head/apple-touch-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/head/apple-touch-icon.webp -------------------------------------------------------------------------------- /public/assets/img/head/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/head/favicon.ico -------------------------------------------------------------------------------- /public/assets/img/head/screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/head/screenshot.webp -------------------------------------------------------------------------------- /public/assets/img/head/thumbnail.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/head/thumbnail.webp -------------------------------------------------------------------------------- /public/assets/img/item/blackpanther.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/blackpanther.webp -------------------------------------------------------------------------------- /public/assets/img/item/blackwidow.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/blackwidow.webp -------------------------------------------------------------------------------- /public/assets/img/item/captainamerica.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/captainamerica.webp -------------------------------------------------------------------------------- /public/assets/img/item/doctorstrange.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/doctorstrange.webp -------------------------------------------------------------------------------- /public/assets/img/item/drax.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/drax.webp -------------------------------------------------------------------------------- /public/assets/img/item/falcon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/falcon.webp -------------------------------------------------------------------------------- /public/assets/img/item/gamora.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/gamora.webp -------------------------------------------------------------------------------- /public/assets/img/item/groot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/groot.webp -------------------------------------------------------------------------------- /public/assets/img/item/hulk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/hulk.webp -------------------------------------------------------------------------------- /public/assets/img/item/ironman.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/ironman.webp -------------------------------------------------------------------------------- /public/assets/img/item/mantis.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/mantis.webp -------------------------------------------------------------------------------- /public/assets/img/item/nebula.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/nebula.webp -------------------------------------------------------------------------------- /public/assets/img/item/okoye.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/okoye.webp -------------------------------------------------------------------------------- /public/assets/img/item/rocket.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/rocket.webp -------------------------------------------------------------------------------- /public/assets/img/item/scarletwitch.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/scarletwitch.webp -------------------------------------------------------------------------------- /public/assets/img/item/spiderman.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/spiderman.webp -------------------------------------------------------------------------------- /public/assets/img/item/starroad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/starroad.webp -------------------------------------------------------------------------------- /public/assets/img/item/syuri.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/syuri.webp -------------------------------------------------------------------------------- /public/assets/img/item/thor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/thor.webp -------------------------------------------------------------------------------- /public/assets/img/item/vision.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/vision.webp -------------------------------------------------------------------------------- /public/assets/img/item/warmachine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/warmachine.webp -------------------------------------------------------------------------------- /public/assets/img/item/wintersoldier.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/wintersoldier.webp -------------------------------------------------------------------------------- /public/assets/img/item/wong.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/item/wong.webp -------------------------------------------------------------------------------- /public/assets/img/texture/matcap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/texture/matcap.jpg -------------------------------------------------------------------------------- /public/assets/img/texture/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/public/assets/img/texture/noise.png -------------------------------------------------------------------------------- /replaceHtml.mjs: -------------------------------------------------------------------------------- 1 | import { writeFileSync, readFileSync } from "fs"; 2 | import glob from "glob"; 3 | 4 | const replaceInHtmlFiles = () => { 5 | try { 6 | const files = glob.sync("dist/**/*.html"); 7 | console.log(files); 8 | for (const file of files) { 9 | // htmlファイルの読み込み 10 | const data = readFileSync(file, "utf8"); 11 | 12 | // htmlの置かれているパスから相対(., ..)を算出 13 | let relativePath = file.replace(/[^/]/g, "").replace(/\//g, "."); 14 | 15 | if (relativePath.length === 1) { 16 | relativePath = file.replace(/[^/]/g, "").replace(/\//g, "."); 17 | } else { 18 | relativePath = file.replace(/[^/]/g, "").replace(/\//g, "../"); 19 | relativePath = relativePath.slice(0, -1); 20 | // 3 ... | ../../ → ../.. 21 | // 4 .... | ../../../ → ../../.. 22 | // 5 ..... | ../../../../ → ../../../.. 23 | } 24 | 25 | // href, srcに指定されている絶対パスを置換 26 | const result = data 27 | .replace(/href="\//g, `href="${relativePath}/`) 28 | .replace(/href='\//g, `href='${relativePath}/`) 29 | .replace(/src="\//g, `src="${relativePath}/`) 30 | .replace(/src='\//g, `src='${relativePath}/`) 31 | .replace(/srcset="\//g, `srcset="${relativePath}/`) 32 | .replace(/srcset='\//g, `srcset='${relativePath}/`) 33 | .replace(/action="\//g, `action="${relativePath}/`) 34 | .replace(/action='\//g, `action='${relativePath}/`) 35 | .replace(/content="\//g, `content="${relativePath}/`) 36 | .replace(/content='\//g, `content='${relativePath}/`); 37 | 38 | writeFileSync(file, result, "utf8"); 39 | // console.log(file); 40 | } 41 | console.log(`\n// --------------------------\n\n👌 ~ replaceInHtmlFiles\n\n// --------------------------\n`); 42 | } catch (error) { 43 | console.log( 44 | `\n// -------------------------- \n\n🙅‍♀️ ~ replaceInHtmlFiles\n\n// --------------------------\n${error}\n`, 45 | ); 46 | } 47 | }; 48 | 49 | replaceInHtmlFiles(); 50 | // node replaceHtml.mjs 51 | -------------------------------------------------------------------------------- /screenshot1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/screenshot1.webp -------------------------------------------------------------------------------- /screenshot2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/screenshot2.webp -------------------------------------------------------------------------------- /screenshot3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sho1374k/Toys-No.029_WebGLSchool-Task07-PlaneGeometryAnimation/55997c03a44a4426c3e2df3df785f229c776c9af/screenshot3.webp -------------------------------------------------------------------------------- /src/assets/js/app.js: -------------------------------------------------------------------------------- 1 | // -------------------------- 2 | 3 | // scss 4 | 5 | // -------------------------- 6 | import "../scss/app.scss"; 7 | 8 | // -------------------------- 9 | 10 | // lib 11 | 12 | // -------------------------- 13 | import gsap from "gsap"; 14 | 15 | // -------------------------- 16 | 17 | // module 18 | 19 | // -------------------------- 20 | import { SetGui } from "./lib/setGui"; 21 | import { WebGL } from "./webgl/WebGL"; 22 | 23 | // -------------------------- 24 | 25 | // window 26 | 27 | // -------------------------- 28 | window.MODE = process.env.NODE_ENV === "development"; 29 | window.GSAP = gsap; 30 | 31 | window.addEventListener("DOMContentLoaded", (e) => { 32 | new SetGui(); 33 | 34 | const world = document.getElementById("world"); 35 | const worldRect = world.getBoundingClientRect(); 36 | 37 | const bool = { 38 | isMatchMediaHover: window.matchMedia("(hover: hover)").matches, 39 | }; 40 | 41 | const params = { 42 | w: window.innerWidth, 43 | h: bool.isMatchMediaHover ? window.innerHeight : worldRect.height, 44 | }; 45 | 46 | const webgl = new WebGL(params); 47 | webgl.init(); 48 | 49 | GSAP.ticker.add(webgl.raf); 50 | GSAP.ticker.fps(30); 51 | 52 | const resize = () => { 53 | params.w = window.innerWidth; 54 | params.h = bool.isMatchMediaHover ? window.innerHeight : world.getBoundingClientRect().height; 55 | 56 | webgl.resize(params); 57 | }; 58 | window.addEventListener("resize", resize, { passive: true }); 59 | setTimeout(() => { 60 | resize(); 61 | }, 300); 62 | 63 | if (bool.isMatchMediaHover) { 64 | // 右クリック禁止 65 | document.oncontextmenu = function () { 66 | return false; 67 | }; 68 | document.getElementsByTagName("html")[0].oncontextmenu = function () { 69 | return false; 70 | }; 71 | document.body.oncontextmenu = function () { 72 | return false; 73 | }; 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /src/assets/js/lib/setGui.js: -------------------------------------------------------------------------------- 1 | import GUI from "lil-gui"; 2 | 3 | export class SetGui { 4 | constructor() { 5 | this.gui = null; 6 | window.GUI = null; 7 | // if (MODE) { 8 | // this.gui = new GUI(); 9 | // window.GUI = this.gui; 10 | // this.toOpen(); 11 | // // this.toClose(); 12 | // } 13 | } 14 | 15 | toOpen() { 16 | if (window.GUI != null) this.gui.open(); 17 | } 18 | 19 | toClose() { 20 | if (window.GUI != null) this.gui.close(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/js/webgl/Object.js: -------------------------------------------------------------------------------- 1 | import { WebGLMath } from "./doxas/WebGLMath"; 2 | 3 | /** 4 | * @class プログラムオブジェクトを作成する継承元 5 | */ 6 | export class Object { 7 | /** 8 | * @param {WebGLRenderingContext} gl WebGLコンテキスト 9 | * @param {source} fragment ピクセルシェーダ 10 | * @param {source} vertex 頂点シェーダ 11 | */ 12 | constructor(stage, fragment, vertex) { 13 | this.gl = stage.gl; 14 | this.canvas = stage.canvas; 15 | this.isOrbitCamera = stage.isOrbitCamera; 16 | this.camera = stage.camera; 17 | 18 | this.drawCount = null; 19 | this.uniforms = {}; 20 | 21 | const vs = this.createFragmentShader(fragment); 22 | const fs = this.createVertexShader(vertex); 23 | this.program = this.createProgramObject(vs, fs); 24 | 25 | // バックフェイスカリングと深度テストは初期状態で有効 26 | this.gl.enable(this.gl.CULL_FACE); 27 | this.gl.enable(this.gl.DEPTH_TEST); 28 | 29 | this.v2 = WebGLMath.Vec2; 30 | this.v3 = WebGLMath.Vec3; 31 | this.m4 = WebGLMath.Mat4; 32 | this.qtn = WebGLMath.Qtn; 33 | } 34 | 35 | /** 36 | * シェーダオブジェクトを生成 37 | * @param {WebGLRenderingContext} gl WebGLコンテキスト 38 | * @param {source} source シェーダ 39 | * @param {number} type gl.VERTEX_SHADER or gl.FRAGMENT_SHADER 40 | * @returns {WebGLShader} 41 | */ 42 | createShaderObject(gl, source, type) { 43 | const shader = gl.createShader(type); 44 | gl.shaderSource(shader, source); 45 | gl.compileShader(shader); 46 | if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 47 | return shader; 48 | } else { 49 | throw new Error(gl.getShaderInfoLog(shader)); 50 | return null; 51 | } 52 | } 53 | 54 | /** 55 | * ピクセルシェーダを生成 56 | * @param {source} source ピクセルシェーダ 57 | * @returns {WebGLShader} 58 | */ 59 | createFragmentShader(source) { 60 | console.log("🙌 ~ create fragment"); 61 | const gl = this.gl; 62 | return this.createShaderObject(gl, source, gl.FRAGMENT_SHADER); 63 | } 64 | 65 | /** 66 | * 頂点シェーダを生成 67 | * @param {souce} source 頂点シェーダ 68 | * @returns {WebGLShader} 69 | */ 70 | createVertexShader(source) { 71 | console.log("🙌 ~ create vertex"); 72 | const gl = this.gl; 73 | return this.createShaderObject(gl, source, gl.VERTEX_SHADER); 74 | } 75 | 76 | /** 77 | * プログラムオブジェクトを生成 78 | * @param {WebGLShader} vs 頂点シェーダ 79 | * @param {WebGLShader} fs ピクセルシェーダ 80 | * @returns {WebGLProgram} 81 | */ 82 | createProgramObject(vs, fs) { 83 | const gl = this.gl; 84 | const program = gl.createProgram(); 85 | 86 | // シェーダをアタッチ(関連付け)する 87 | gl.attachShader(program, vs); 88 | gl.attachShader(program, fs); 89 | 90 | // シェーダオブジェクトをリンクする 91 | gl.linkProgram(program); 92 | 93 | // リンクが完了するとシェーダオブジェクトは不要になるので削除する 94 | gl.deleteShader(vs); 95 | gl.deleteShader(fs); 96 | 97 | if (gl.getProgramParameter(program, gl.LINK_STATUS)) { 98 | gl.useProgram(program); 99 | return program; 100 | } else { 101 | throw new Error(gl.getProgramInfoLog(program)); 102 | return null; 103 | } 104 | } 105 | 106 | /** 107 | * IBOを生成 108 | * @param {Array} indexArray 頂点インデックスの結び順の配列 109 | * @return {WebGLBuffer} 110 | */ 111 | createIBO(indexArray) { 112 | const gl = this.gl; 113 | // 空のバッファオブジェクトを生成する 114 | const ibo = gl.createBuffer(); 115 | // バッファを gl.ELEMENT_ARRAY_BUFFER としてバインドする 116 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo); 117 | // バインドしたバッファに Int16Array オブジェクトに変換した配列を設定する 118 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(indexArray), gl.STATIC_DRAW); 119 | // 安全のために最後にバインドを解除してからバッファオブジェクトを返す 120 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 121 | return ibo; 122 | } 123 | 124 | /** 125 | * IBOを更新 126 | * @param {WebGLBuffer} ibo - インデックスバッファ 127 | */ 128 | updateIBO(ibo) { 129 | const gl = this.gl; 130 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo); 131 | } 132 | 133 | /** 134 | * 配列から VBO(Vertex Buffer Object)を生成する 135 | * @param {Array.} vertexArray - 頂点属性情報の配列 136 | * @return {WebGLBuffer} 137 | */ 138 | createVBO(vertexArray) { 139 | const gl = this.gl; 140 | // 空のバッファオブジェクトを生成する 141 | const vbo = gl.createBuffer(); 142 | // バッファを gl.ARRAY_BUFFER としてバインドする 143 | gl.bindBuffer(gl.ARRAY_BUFFER, vbo); 144 | // バインドしたバッファに Float32Array オブジェクトに変換した配列を設定する 145 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexArray), gl.STATIC_DRAW); 146 | // 安全のために最後にバインドを解除してからバッファオブジェクトを返す 147 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 148 | return vbo; 149 | } 150 | 151 | /** 152 | * VBOを有効化 153 | * @param {WebGLBuffer} vbo 頂点属性を格納した頂点バッファの配列 154 | * @param {number} location 頂点属性ロケーション 155 | * @param {number} stride 分割数 156 | */ 157 | updateVBO(vbo, location, stride) { 158 | const gl = this.gl; 159 | // 有効化したいバッファをまずバインドする 160 | gl.bindBuffer(gl.ARRAY_BUFFER, vbo); 161 | // 頂点属性ロケーションの有効化を行う 162 | gl.enableVertexAttribArray(location); 163 | // 対象のロケーションのストライドやデータ型を設定する 164 | gl.vertexAttribPointer(location, stride, gl.FLOAT, false, 0, 0); 165 | } 166 | 167 | getUniform(name) { 168 | const gl = this.gl; 169 | const program = this.program; 170 | const location = gl.getUniformLocation(program, name); 171 | return gl.getUniform(program, location); 172 | } 173 | 174 | /** 175 | * unifrom値を設定 176 | * @param {string} name 変数名 177 | * @param {string} type 型タイプ 178 | * @param {any} value 値 179 | */ 180 | createUniform(name, type, value) { 181 | const gl = this.gl; 182 | const program = this.program; 183 | this.uniforms[name] = gl.getUniformLocation(program, name); 184 | } 185 | 186 | /** 187 | * unifrom値を更新 188 | * @param {string} name 変数名 189 | * @param {string} type 型タイプ 190 | * @param {any} value 値 191 | * 変数の文字列タイプ(少し前の three.js 文字列変数): https://qiita.com/kitasenjudesign/items/1657d9556591284a43c8 192 | * uniformで利用するデータの型: https://webglfundamentals.org/webgl/lessons/ja/webgl-shaders-and-glsl.html 193 | */ 194 | updateUniform(name, type, value, transpose = false) { 195 | const gl = this.gl; 196 | switch (type) { 197 | case "t": 198 | gl.uniform1i(this.uniforms[name], value); // sampler2D (テクスチャ) 199 | break; 200 | case "i": 201 | gl.uniform1i(this.uniforms[name], value); // int:1つの整数 202 | break; 203 | case "f": 204 | gl.uniform1f(this.uniforms[name], value); // float: 1つの浮動小数点 205 | break; 206 | case "v1": 207 | gl.uniform1fv(this.uniforms[name], value); // vec1: 1つの浮動小数点を配列に入れたもの 208 | break; 209 | case "v2": 210 | gl.uniform2fv(this.uniforms[name], value); // vec2: 2つの浮動小数点を配列にいれたもの 211 | break; 212 | case "v3": 213 | gl.uniform3fv(this.uniforms[name], value); // vec3: 3つの浮動小数点を配列にいれたもの 214 | break; 215 | case "v4": 216 | gl.uniform4fv(this.uniforms[name], value); // vec4: 4つの浮動小数点を配列にいれたもの 217 | break; 218 | case "m2": 219 | gl.uniformMatrix2fv(this.uniforms[name], transpose, value); // mat2: 配列で表現された 2x2 の行列 220 | break; 221 | case "m3": 222 | gl.uniformMatrix3fv(this.uniforms[name], transpose, value); // mat3: 配列で表現された 3x3 の行列 223 | break; 224 | case "m4": 225 | gl.uniformMatrix4fv(this.uniforms[name], transpose, value); // mat4: 配列で表現された 4x4 の行列 226 | break; 227 | default: 228 | throw new Error("type is not defined"); 229 | break; 230 | } 231 | } 232 | 233 | /** 234 | * 235 | * @param {number} fov field of view 垂直視野 236 | * @param {number} aspect 画面のアスペクト比 237 | * @param {number} near 一番近い距離 238 | * @param {number} far 一番遠い距離 239 | * @returns 240 | */ 241 | createProjection(fov = 45, aspect = window.innerWidth / window.innerHeight, near = 0.1, far = 10.0) { 242 | return this.m4.perspective(fov, aspect, near, far); 243 | } 244 | 245 | /** 246 | * 247 | * @param {element} ele // imgタグ 248 | * @returns {Promise} objデータを返す 249 | */ 250 | loadEleImg(ele) { 251 | return new Promise((resolve) => { 252 | const src = ele.getAttribute("src"); 253 | const w = ele.getAttribute("width"); 254 | const h = ele.getAttribute("height"); 255 | 256 | const img = new Image(); 257 | img.src = src; 258 | img.addEventListener("load", (e) => { 259 | const data = { 260 | img: img, 261 | src: src, 262 | w: w, 263 | h: h, 264 | aspect: w / h, 265 | }; 266 | return resolve(data); 267 | }); 268 | }); 269 | } 270 | 271 | /** 272 | * 273 | * @param {path} path // 画像パス 274 | * @returns {Promise} objデータを返す 275 | */ 276 | loadImg(path) { 277 | return new Promise((resolve) => { 278 | const img = new Image(); 279 | img.src = path; 280 | img.addEventListener("load", (e) => { 281 | return resolve(img); 282 | }); 283 | }); 284 | } 285 | 286 | /** 287 | * テクスチャ用のリソースからテクスチャを生成する 288 | * @param {any} resource - 画像や HTMLCanvasElement などのテクスチャ用リソース 289 | * @return {WebGLTexture} 290 | */ 291 | createTexture(resource) { 292 | const gl = this.gl; 293 | // テクスチャオブジェクトを生成 294 | const texture = gl.createTexture(); 295 | // アクティブなテクスチャユニット番号を指定する 296 | gl.activeTexture(gl.TEXTURE0); 297 | // テクスチャをアクティブなユニットにバインドする 298 | gl.bindTexture(gl.TEXTURE_2D, texture); 299 | // バインドしたテクスチャにデータを割り当て 300 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, resource); 301 | // ミップマップを自動生成する 302 | gl.generateMipmap(gl.TEXTURE_2D); 303 | // テクスチャパラメータを設定する 304 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 305 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 306 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 307 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 308 | // 安全の為にテクスチャのバインドを解除してから返す 309 | gl.bindTexture(gl.TEXTURE_2D, null); 310 | return texture; 311 | } 312 | 313 | /** 314 | * バックフェイスカリングを設定する 315 | * @param {boolean} bool 316 | * @returns 317 | */ 318 | setCulling(bool = true) { 319 | const gl = this.gl; 320 | return bool ? gl.enable(gl.CULL_FACE) : gl.disable(gl.CULL_FACE); 321 | } 322 | 323 | /** 324 | * 深度テストを設定する 325 | * @param {boolean} bool - 設定する値 326 | */ 327 | setDepthTest(bool = true) { 328 | const gl = this.gl; 329 | bool ? gl.enable(gl.DEPTH_TEST) : gl.disable(gl.DEPTH_TEST); 330 | } 331 | 332 | raf() { 333 | if (this.program != null) this.gl.useProgram(this.program); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/assets/js/webgl/ObjectPlane.js: -------------------------------------------------------------------------------- 1 | // module 2 | import { Object } from "./Object"; 3 | 4 | // shader 5 | import fragmentShader from "../../shader/frag/plane.glsl"; 6 | import vertexShader from "../../shader/vert/plane.glsl"; 7 | 8 | export class ObjectPlane extends Object { 9 | constructor(params, stage) { 10 | super(stage, fragmentShader, vertexShader); 11 | this.stage = stage; 12 | this.params = params; 13 | 14 | this.isCreated = false; 15 | this.isOpened = false; 16 | this.isClicked = false; 17 | this.isChange = true; 18 | this.isMatchMediaHover = window.matchMedia("(hover: hover)").matches; 19 | 20 | this.mesh = {}; 21 | 22 | this.curve = { 23 | x: { 24 | target: 0, 25 | current: 0, 26 | }, 27 | y: { 28 | target: 0, 29 | current: 0, 30 | }, 31 | power: 0.3, 32 | }; 33 | 34 | this.variable = { 35 | progress: 0.0, 36 | scale: 0.0, 37 | change: 0.0, 38 | status: { 39 | before: 1, 40 | after: 0, 41 | }, 42 | }; 43 | } 44 | 45 | /** 46 | * @param {number} width 板ポリの横幅 47 | * @param {number} height 板ポリの縦幅 48 | * @param {number} widthSegments 横の1辺を分割する数 49 | * @param {number} heightSegments 縦の1辺を分割する数 50 | */ 51 | getPlaneGeometry(width = 1, height = 1, widthSegments = 1, heightSegments = 1) { 52 | const w = width / 2; 53 | const h = height / 2; 54 | 55 | const gridX = Math.floor(widthSegments); 56 | const gridY = Math.floor(heightSegments); 57 | 58 | const limitGridX = gridX + 1; 59 | const limitGridY = gridY + 1; 60 | 61 | const segmentWidth = width / gridX; 62 | const segmentHeight = height / gridY; 63 | 64 | const indexList = []; 65 | const positionList = []; 66 | const normalList = []; 67 | const uvList = []; 68 | 69 | for (let iy = 0; iy < limitGridY; iy++) { 70 | const y = iy * segmentHeight - h; 71 | 72 | for (let ix = 0; ix < limitGridX; ix++) { 73 | const x = ix * segmentWidth - w; 74 | 75 | positionList.push(x, -y, 0); 76 | 77 | normalList.push(0, 0, 1); 78 | 79 | uvList.push(ix / gridX); 80 | uvList.push(1 - iy / gridY); 81 | } 82 | } 83 | 84 | for (let iy = 0; iy < gridY; iy++) { 85 | for (let ix = 0; ix < gridX; ix++) { 86 | const a = ix + limitGridX * iy; 87 | const b = ix + limitGridX * (iy + 1); 88 | const c = ix + 1 + limitGridX * (iy + 1); 89 | const d = ix + 1 + limitGridX * iy; 90 | 91 | indexList.push(a, b, d); 92 | indexList.push(b, c, d); 93 | } 94 | } 95 | 96 | // prettier-ignore 97 | return { 98 | position: positionList, 99 | normal: normalList, 100 | uv: uvList, 101 | index: indexList 102 | }; 103 | } 104 | 105 | createMesh() { 106 | const gl = this.gl; 107 | 108 | // geometry作成 109 | this.mesh.geometry = this.getPlaneGeometry(1, 1, 32, 32); 110 | 111 | // BVO作成 112 | this.mesh.position = { 113 | array: this.createVBO(this.mesh.geometry.position), 114 | location: gl.getAttribLocation(this.program, "position"), 115 | stride: 3, 116 | }; 117 | this.mesh.uv = { 118 | array: this.createVBO(this.mesh.geometry.uv), 119 | location: gl.getAttribLocation(this.program, "uv"), 120 | stride: 2, 121 | }; 122 | this.mesh.normal = { 123 | array: this.createVBO(this.mesh.geometry.normal), 124 | location: gl.getAttribLocation(this.program, "normal"), 125 | stride: 3, 126 | }; 127 | 128 | // IBO作成 129 | this.mesh.ibo = { 130 | data: this.createIBO(this.mesh.geometry.index), 131 | length: this.mesh.geometry.index.length, 132 | }; 133 | 134 | // uniform作成 135 | this.createUniform("uMvpMatrix"); 136 | this.createUniform("uCurve"); 137 | this.createUniform("uTime"); 138 | this.createUniform("uNormalMatrix"); 139 | this.createUniform("uModelMatrix"); 140 | this.createUniform("uEyePosition"); 141 | this.createUniform("uPlaneAspect"); 142 | this.createUniform("uTextureAspect"); 143 | this.createUniform("uMatcap"); 144 | this.createUniform("uTexture1"); 145 | this.createUniform("uTexture2"); 146 | this.createUniform("uProgress"); 147 | this.createUniform("uNoise"); 148 | this.createUniform("uScale"); 149 | this.createUniform("uChange"); 150 | this.createUniform("uFullScale"); 151 | 152 | this.isCreated = true; 153 | } 154 | 155 | onMove(vector) { 156 | this.curve.x.target = vector.x.target; 157 | this.curve.y.target = vector.y.target; 158 | } 159 | 160 | resize(params) {} 161 | 162 | raf(time, vector) { 163 | const v = vector; 164 | const gl = this.stage.gl; 165 | if (this.isCreated) { 166 | super.raf(time); 167 | 168 | const m4 = this.m4; 169 | const v3 = this.v3; 170 | 171 | // ビュー座標変換行列 172 | const view = this.isOrbitCamera ? this.camera.update() : this.camera; 173 | 174 | // プロジェクション座標変換行列 175 | const fov = 45; 176 | const aspect = this.params.w / this.params.h; 177 | const near = 0.01; 178 | const far = 5000; 179 | const projection = this.createProjection(fov, aspect, near, far); 180 | 181 | // scale 182 | const rect = this.rectItem.getBoundingClientRect(); 183 | const scaleX = rect.width; 184 | const scaleY = rect.height; 185 | const scaleZ = 1.0; 186 | const scale = v3.create(scaleX, scaleY, scaleZ); 187 | 188 | // translate 189 | const translateX = 190 | v.x.current * this.params.w * 0.5 * this.variable.status.before + 0 * this.variable.status.after; 191 | const translateY = 192 | v.y.current * this.params.h * 0.5 * this.variable.status.before + 0 * this.variable.status.after; 193 | const translateZ = (this.params.h / Math.tan((fov * Math.PI) / 360)) * 0.49 * -1; // 0.49: 0.5だとアスペクト比によっては1 ~ 2px隙間ができる... 194 | const translate = v3.create(translateX, translateY, translateZ); 195 | 196 | // モデル座標変換行列 197 | const rotateAxis = v3.create(1.0, 0.0, 0.0); 198 | let model = m4.translate(m4.identity(), translate); // 移動 199 | model = m4.scale(model, scale); 200 | model = m4.rotate(model, 0, rotateAxis); 201 | 202 | // 行列を乗算して MVP 行列を生成する(掛ける順序に注意) 203 | const vp = m4.multiply(projection, view); 204 | const mvp = m4.multiply(vp, model); 205 | 206 | // モデル座標行列の逆行列を作成 207 | const normalMatrix = m4.transpose(m4.inverse(model)); 208 | 209 | const updateCurveValue = () => { 210 | const x = v.x.current; 211 | const y = v.y.current; 212 | 213 | // subtracts vector 214 | this.curve.x.current = this.curve.x.target - x; 215 | this.curve.y.current = this.curve.y.target - y; 216 | 217 | // multiply scalar 218 | this.curve.x.current *= this.curve.power; 219 | this.curve.y.current *= this.curve.power; 220 | 221 | this.updateUniform("uCurve", "v2", [this.curve.x.current, this.curve.y.current]); 222 | }; 223 | updateCurveValue(); 224 | 225 | // uniform更新 226 | this.updateUniform("uProgress", "f", this.variable.progress); 227 | this.updateUniform("uMvpMatrix", "m4", mvp); 228 | this.updateUniform("uNormalMatrix", "m4", normalMatrix); 229 | this.updateUniform("uModelMatrix", "m4", model); 230 | this.updateUniform("uEyePosition", "v3", this.camera.position); 231 | this.updateUniform("uTime", "f", time); 232 | this.updateUniform("uScale", "f", this.variable.scale); 233 | this.updateUniform("uChange", "f", this.variable.change); 234 | 235 | const fullScaleX = 1.0 * this.variable.status.before + (this.params.w / scaleX) * this.variable.status.after; 236 | const fullScaleY = 1.0 * this.variable.status.before + (this.params.h / scaleY) * this.variable.status.after; 237 | this.updateUniform("uFullScale", "v2", [fullScaleX, fullScaleY]); 238 | 239 | const textureAspect = scaleX / scaleY; 240 | const planeAspect = 241 | textureAspect * this.variable.status.before + (this.params.w / this.params.h) * this.variable.status.after; 242 | this.updateUniform("uPlaneAspect", "f", planeAspect); 243 | this.updateUniform("uTextureAspect", "f", textureAspect); 244 | 245 | // VBO設定 246 | this.updateVBO(this.mesh.position.array, this.mesh.position.location, this.mesh.position.stride); 247 | this.updateVBO(this.mesh.normal.array, this.mesh.normal.location, this.mesh.normal.stride); 248 | this.updateVBO(this.mesh.uv.array, this.mesh.uv.location, this.mesh.uv.stride); 249 | 250 | // IBO設定 251 | this.updateIBO(this.mesh.ibo.data); 252 | 253 | // テクスチャ設定 254 | gl.activeTexture(gl.TEXTURE0); 255 | gl.bindTexture(gl.TEXTURE_2D, this.texture.current.img); 256 | this.updateUniform("uTexture1", "t", 0); 257 | 258 | gl.activeTexture(gl.TEXTURE1); 259 | gl.bindTexture(gl.TEXTURE_2D, this.texture.before.img); 260 | this.updateUniform("uTexture2", "t", 1); 261 | 262 | gl.activeTexture(gl.TEXTURE2); 263 | gl.bindTexture(gl.TEXTURE_2D, this.matcap); 264 | this.updateUniform("uMatcap", "t", 2); 265 | 266 | gl.activeTexture(gl.TEXTURE3); 267 | gl.bindTexture(gl.TEXTURE_2D, this.noise); 268 | this.updateUniform("uNoise", "t", 3); 269 | 270 | // 描画 271 | gl.drawElements(gl.TRIANGLES, this.mesh.ibo.length, gl.UNSIGNED_SHORT, 0); 272 | // gl.drawElements(gl.LINE_LOOP, this.mesh.ibo.length, gl.UNSIGNED_SHORT, 0); // debug 273 | } 274 | } 275 | 276 | toClickItem(ele, i) { 277 | const DURATION = 1; 278 | if (ele.classList.contains("is-active")) { 279 | // 閉じる 280 | ele.classList.remove("is-active"); 281 | GSAP.to(this.variable.status, { 282 | duration: DURATION, 283 | before: 1, 284 | after: 0, 285 | }); 286 | GSAP.to(this.variable, { 287 | duration: DURATION, 288 | scale: 0, 289 | onComplete: () => { 290 | this.isOpened = false; 291 | }, 292 | }); 293 | } else { 294 | if (this.isChange) { 295 | this.itemList.forEach((element) => { 296 | element.classList.remove("is-active"); 297 | }); 298 | ele.classList.add("is-active"); 299 | 300 | if (this.isOpened) { 301 | // すでに開いている 302 | this.isChange = false; 303 | this.variable.change = 1.0; 304 | const current = this.texture.current; 305 | this.texture.before.img = current.img; 306 | this.texture.before.aspect = current.aspect; 307 | this.texture.current.img = this.createTexture(this.textureList[i].img); 308 | this.texture.current.aspect = this.textureList[i].aspect; 309 | 310 | GSAP.to(this.variable, { 311 | duration: DURATION, 312 | change: 0.0, 313 | onComplete: () => { 314 | this.isChange = true; 315 | }, 316 | }); 317 | } else { 318 | // 今から開く 319 | this.isOpened = true; 320 | GSAP.to(this.variable.status, { 321 | duration: DURATION, 322 | before: 0, 323 | after: 1, 324 | }); 325 | GSAP.to(this.variable, { 326 | duration: DURATION, 327 | scale: 1, 328 | }); 329 | } 330 | } 331 | } 332 | } 333 | 334 | toEnterItem(i) { 335 | if (!this.isOpened) { 336 | const current = this.texture.current; 337 | this.texture.before.img = current.img; 338 | this.texture.before.aspect = current.aspect; 339 | this.texture.current.img = this.createTexture(this.textureList[i].img); 340 | this.texture.current.aspect = this.textureList[i].aspect; 341 | } 342 | } 343 | 344 | async init() { 345 | console.log("🚀 ~ Plane init"); 346 | 347 | this.matcap = await this.loadImg("assets/img/texture/matcap.jpg"); 348 | this.matcap = this.createTexture(this.matcap); 349 | 350 | this.noise = await this.loadImg("assets/img/texture/noise.png"); 351 | this.noise = this.createTexture(this.noise); 352 | 353 | this.imgList = [...document.querySelectorAll(".js-img")]; 354 | this.textureList = []; 355 | this.textureList = await Promise.all( 356 | this.imgList.map((ele) => { 357 | return this.loadEleImg(ele); 358 | }), 359 | ); 360 | 361 | this.texture = { 362 | before: { 363 | img: this.createTexture(this.textureList[0].img), 364 | aspect: this.textureList[0].aspect, 365 | }, 366 | current: { 367 | img: this.createTexture(this.textureList[1].img), 368 | aspect: this.textureList[1].aspect, 369 | }, 370 | }; 371 | 372 | this.itemList = [...document.querySelectorAll(".js-item")]; 373 | this.itemList.forEach((ele, i) => { 374 | if (this.isMatchMediaHover) { 375 | ele.addEventListener("mouseenter", (e) => { 376 | this.toEnterItem(i); 377 | }); 378 | } 379 | ele.addEventListener("click", (e) => { 380 | this.toClickItem(ele, i); 381 | }); 382 | }); 383 | 384 | this.rectItem = document.getElementById("js-rect"); 385 | 386 | this.setCulling(); 387 | this.setDepthTest(); 388 | this.createMesh(); 389 | 390 | setTimeout(() => { 391 | const body = document.body; 392 | body.setAttribute("data-status", "enter"); 393 | }, 300); 394 | 395 | if (GUI != null) { 396 | const gui = GUI.addFolder("plane"); 397 | gui 398 | .add(this.variable, "progress", 0.0, 1.0) 399 | .name("uProgress") 400 | .onChange((value) => { 401 | this.variable.progress = value; 402 | }); 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/assets/js/webgl/Stage.js: -------------------------------------------------------------------------------- 1 | // lib 2 | import { WebGLMath } from "./doxas/WebGLMath.js"; 3 | import { WebGLOrbitCamera } from "./doxas/WebGLOrbitCamera"; 4 | 5 | export class Stage { 6 | constructor(params, isClearColor = true, isOrbitCamera = true) { 7 | this.gl = null; // WebGLRenderingContext 8 | this.canvas = null; // HTMLCanvasElement 9 | this.camera = null; 10 | this.isClearColor = isClearColor; 11 | this.isOrbitCamera = isOrbitCamera; 12 | this.params = params; 13 | this.params.color = { 14 | r: 1, 15 | g: 1, 16 | b: 1, 17 | a: 1, 18 | }; 19 | 20 | this.v3 = WebGLMath.Vec3; 21 | this.m4 = WebGLMath.Mat4; 22 | } 23 | 24 | createWebGLContext() { 25 | // WebGL コンテキストを初期化する 26 | const gl = this.canvas.getContext("webgl"); 27 | if (gl == null) { 28 | throw new Error("webgl not supported"); 29 | return null; 30 | } else { 31 | return gl; 32 | } 33 | } 34 | 35 | /** 36 | * canvasのサイズを設定 37 | * @param {number} w 38 | * @param {number} h 39 | */ 40 | setSize(w = window.innerWidth, h = window.innerHeight) { 41 | this.canvas.width = w; 42 | this.canvas.height = h; 43 | } 44 | 45 | /** 46 | * canvas内でのWebGLのviewportの設定 47 | * @param {number} x 48 | * @param {number} y 49 | * @param {number} w 50 | * @param {number} h 51 | */ 52 | setViewport(x = 0, y = 0, w = this.canvas.width, h = this.canvas.height) { 53 | this.gl.viewport(x, y, w, h); 54 | } 55 | 56 | /** 57 | * クリアする色を設定 58 | * @param {object} color {r, g, b, a}; 59 | */ 60 | setClearColor(color = { r: 1, g: 1, b: 1, a: 1 }) { 61 | this.params.color = color; 62 | this.gl.clearColor(this.params.color.r, this.params.color.g, this.params.color.b, this.params.color.a); 63 | this.gl.clearDepth(1.0); 64 | // this.gl.clear(this.gl.COLOR_BUFFER_BIT); 65 | this.gl.clear(gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); 66 | } 67 | 68 | setCamera() { 69 | if (this.isOrbitCamera) { 70 | this.camera = new WebGLOrbitCamera(this.canvas, { 71 | distance: 5.0, // Z 軸上の初期位置までの距離 72 | min: 0.1, // カメラが寄れる最小距離 73 | max: 10.0, // カメラが離れられる最大距離 74 | move: 2.0, // 右ボタンで平行移動する際の速度係数 75 | }); 76 | } else { 77 | const eye = this.v3.create(0.0, 0.0, 5.0); // カメラの位置 78 | const center = this.v3.create(0.0, 0.0, 0.0); // カメラの注視点 79 | const upDirection = this.v3.create(0.0, 1.0, 0.0); // カメラの天面の向き 80 | this.camera = this.m4.lookAt(eye, center, upDirection); 81 | this.camera.position = eye; 82 | } 83 | } 84 | 85 | /** 86 | * @param {number} w 87 | * @param {number} h 88 | */ 89 | resize(w = window.innerWidth, h = window.innerHeight) { 90 | this.setSize(w, h); 91 | this.setViewport(); 92 | } 93 | 94 | raf() { 95 | if (this.isClearColor) { 96 | this.setClearColor(); 97 | } else { 98 | this.gl.clearDepth(1.0); 99 | this.gl.clear(this.gl.DEPTH_BUFFER_BIT); 100 | } 101 | 102 | this.setViewport(0, 0, this.canvas.width, this.canvas.height); 103 | } 104 | 105 | /** 106 | * @param {HTMLElement} canvas WebGLを内包するcanvas要素 107 | */ 108 | init(canvas, w = window.innerWidth, h = window.innerHeight) { 109 | console.log("🚀 ~ Stage init"); 110 | this.canvas = canvas; 111 | this.gl = this.createWebGLContext(this.canvas); 112 | this.setSize(w, h); 113 | this.setViewport(0, 0, this.canvas.width, this.canvas.height); 114 | if (this.isClearColor) this.setClearColor(); 115 | this.setCamera(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/assets/js/webgl/WebGL.js: -------------------------------------------------------------------------------- 1 | // module 2 | import { Stage } from "./Stage"; 3 | import { ObjectPlane } from "./ObjectPlane"; 4 | 5 | export class WebGL { 6 | constructor(params) { 7 | this.params = params; 8 | 9 | this.vector = { 10 | x: { 11 | target: 0, 12 | current: 0, 13 | }, 14 | y: { 15 | target: 0, 16 | current: 0, 17 | }, 18 | ease: 0.2, 19 | }; 20 | 21 | this.stage = new Stage(params, false, false); 22 | this.stage.init(document.getElementById("webgl"), params.w, params.h); 23 | this.plane = new ObjectPlane(params, this.stage); 24 | 25 | this.raf = this.raf.bind(this); 26 | 27 | if (window.matchMedia("(hover: hover)").matches) { 28 | window.addEventListener("mousemove", this.onMove.bind(this), { 29 | passive: true, 30 | }); 31 | } else { 32 | window.addEventListener("touchmove", this.onMove.bind(this), { 33 | passive: true, 34 | }); 35 | } 36 | } 37 | 38 | /** 39 | * 線形補間 40 | * https://ja.wikipedia.org/wiki/%E7%B7%9A%E5%BD%A2%E8%A3%9C%E9%96%93 41 | * @param {number} start 42 | * @param {number} end 43 | * @param {number} ease 44 | * @returns {number} 補完した値 45 | */ 46 | lerp(start, end, ease) { 47 | return start * (1 - ease) + end * ease; 48 | } 49 | 50 | onMove(e) { 51 | const x = e.touches ? e.touches[0].clientX : e.clientX; 52 | const y = e.touches ? e.touches[0].clientY : e.clientY; 53 | 54 | this.vector.x.target = (x / this.params.w) * 2 - 1; 55 | this.vector.y.target = -(y / this.params.h) * 2 + 1; 56 | 57 | this.plane.onMove(this.vector); 58 | } 59 | 60 | raf() { 61 | const time = performance.now() * 0.001; 62 | 63 | this.vector.x.current = this.lerp(this.vector.x.current, this.vector.x.target, this.vector.ease); 64 | this.vector.y.current = this.lerp(this.vector.y.current, this.vector.y.target, this.vector.ease); 65 | 66 | this.stage.raf(); 67 | this.plane.raf(time, this.vector); 68 | } 69 | 70 | resize(params) { 71 | this.params.w = params.w; 72 | this.params.h = params.h; 73 | 74 | this.stage.resize(params.w, params.h); 75 | this.plane.resize(params); 76 | } 77 | 78 | init() { 79 | console.log("🚀 ~ WebGL init"); 80 | this.plane.init(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/assets/js/webgl/doxas/WebGLMath.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * ベクトルや行列演算の機能を提供する 4 | * @class 5 | */ 6 | export class WebGLMath { 7 | /** 8 | * @static 9 | * @type {Vec2} 10 | */ 11 | static get Vec2() {return Vec2;} 12 | /** 13 | * @static 14 | * @type {Vec3} 15 | */ 16 | static get Vec3() {return Vec3;} 17 | /** 18 | * @static 19 | * @type {Mat4} 20 | */ 21 | static get Mat4() {return Mat4;} 22 | /** 23 | * @static 24 | * @type {Qtn} 25 | */ 26 | static get Qtn() {return Qtn;} 27 | } 28 | 29 | /** 30 | * Vec2 31 | * @class Vec2 32 | */ 33 | class Vec2 { 34 | /** 35 | * 2つの要素を持つベクトルを生成する 36 | * @param {number} [x=0] - X 要素の値 37 | * @param {number} [y=0] - Y 要素の値 38 | * @return {Float32Array} ベクトル格納用の配列 39 | */ 40 | static create(x = 0, y = 0) { 41 | const out = new Float32Array(2); 42 | out[0] = x; 43 | out[1] = y; 44 | return out; 45 | } 46 | /** 47 | * ベクトルの長さ(大きさ)を返す 48 | * @param {Vec2} v - 2つの要素を持つベクトル 49 | * @return {number} ベクトルの長さ(大きさ) 50 | */ 51 | static length(v) { 52 | return Math.sqrt(v[0] * v[0] + v[1] * v[1]); 53 | } 54 | /** 55 | * ベクトルを正規化した結果を返す 56 | * @param {Vec2} v - 2つの要素を持つベクトル 57 | * @return {Vec2} 正規化したベクトル 58 | */ 59 | static normalize(v) { 60 | const n = Vec2.create(); 61 | const l = Vec2.length(v); 62 | if (l > 0) { 63 | const i = 1 / l; 64 | n[0] = v[0] * i; 65 | n[1] = v[1] * i; 66 | } 67 | return n; 68 | } 69 | /** 70 | * 2つのベクトルの内積の結果を返す 71 | * @param {Vec2} v0 - 2つの要素を持つベクトル 72 | * @param {Vec2} v1 - 2つの要素を持つベクトル 73 | * @return {number} 内積の結果 74 | */ 75 | static dot(v0, v1) { 76 | return v0[0] * v1[0] + v0[1] * v1[1]; 77 | } 78 | /** 79 | * 2つのベクトルの外積の結果を返す 80 | * @param {Vec2} v0 - 2つの要素を持つベクトル 81 | * @param {Vec2} v1 - 2つの要素を持つベクトル 82 | * @return {number} 外積の結果 83 | */ 84 | static cross(v0, v1) { 85 | const n = Vec2.create(); 86 | return v0[0] * v1[1] - v0[1] * v1[0]; 87 | } 88 | } 89 | 90 | /** 91 | * Vec3 92 | * @class Vec3 93 | */ 94 | class Vec3 { 95 | /** 96 | * 3つの要素を持つベクトルを生成する 97 | * @param {number} [x=0] - X 要素の値 98 | * @param {number} [y=0] - Y 要素の値 99 | * @param {number} [z=0] - Z 要素の値 100 | * @return {Float32Array} ベクトル格納用の配列 101 | */ 102 | static create(x = 0, y = 0, z = 0) { 103 | const out = new Float32Array(3); 104 | out[0] = x; 105 | out[1] = y; 106 | out[2] = z; 107 | return out; 108 | } 109 | /** 110 | * ベクトルの長さ(大きさ)を返す 111 | * @param {Vec3} v - 3つの要素を持つベクトル 112 | * @return {number} ベクトルの長さ(大きさ) 113 | */ 114 | static length(v) { 115 | return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); 116 | } 117 | /** 118 | * ベクトルを正規化した結果を返す 119 | * @param {Vec3} v - 3つの要素を持つベクトル 120 | * @return {Vec3} 正規化したベクトル 121 | */ 122 | static normalize(v) { 123 | const n = Vec3.create(); 124 | const l = Vec3.length(v); 125 | if (l > 0) { 126 | const i = 1 / l; 127 | n[0] = v[0] * i; 128 | n[1] = v[1] * i; 129 | n[2] = v[2] * i; 130 | } 131 | return n; 132 | } 133 | /** 134 | * 2つのベクトルの内積の結果を返す 135 | * @param {Vec3} v0 - 3つの要素を持つベクトル 136 | * @param {Vec3} v1 - 3つの要素を持つベクトル 137 | * @return {number} 内積の結果 138 | */ 139 | static dot(v0, v1) { 140 | return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2]; 141 | } 142 | /** 143 | * 2つのベクトルの外積の結果を返す 144 | * @param {Vec3} v0 - 3つの要素を持つベクトル 145 | * @param {Vec3} v1 - 3つの要素を持つベクトル 146 | * @return {Vec3} 外積の結果 147 | */ 148 | static cross(v0, v1) { 149 | return Vec3.create( 150 | v0[1] * v1[2] - v0[2] * v1[1], 151 | v0[2] * v1[0] - v0[0] * v1[2], 152 | v0[0] * v1[1] - v0[1] * v1[0], 153 | ); 154 | } 155 | /** 156 | * 3つのベクトルから面法線を求めて返す 157 | * @param {Vec3} v0 - 3つの要素を持つベクトル 158 | * @param {Vec3} v1 - 3つの要素を持つベクトル 159 | * @param {Vec3} v2 - 3つの要素を持つベクトル 160 | * @return {Vec3} 面法線ベクトル 161 | */ 162 | static faceNormal(v0, v1, v2) { 163 | const vec0 = Vec3.create(v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]); 164 | const vec1 = Vec3.create(v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]); 165 | const n = Vec3.create( 166 | vec0[1] * vec1[2] - vec0[2] * vec1[1], 167 | vec0[2] * vec1[0] - vec0[0] * vec1[2], 168 | vec0[0] * vec1[1] - vec0[1] * vec1[0], 169 | ); 170 | return Vec3.normalize(n); 171 | } 172 | } 173 | 174 | /** 175 | * Mat4 176 | * @class Mat4 177 | */ 178 | class Mat4 { 179 | /** 180 | * 4x4 の正方行列を生成する 181 | * @return {Float32Array} 行列格納用の配列 182 | */ 183 | static create() { 184 | return new Float32Array(16); 185 | } 186 | /** 187 | * 行列を単位化する(参照に注意) 188 | * @param {Mat4} dest - 単位化する行列 189 | * @return {Mat4} 単位化した行列 190 | */ 191 | static identity(dest) { 192 | const out = dest == null ? Mat4.create() : dest; 193 | out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; 194 | out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; 195 | out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; 196 | out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; 197 | return out; 198 | } 199 | /** 200 | * 行列を乗算する(参照に注意・戻り値としても結果を返す) 201 | * @param {Mat4} mat0 - 乗算される行列 202 | * @param {Mat4} mat1 - 乗算する行列 203 | * @param {Mat4} [dest] - 乗算結果を格納する行列 204 | * @return {Mat4} 乗算結果の行列 205 | */ 206 | static multiply(mat0, mat1, dest) { 207 | const out = dest == null ? Mat4.create() : dest; 208 | const a = mat0[0], b = mat0[1], c = mat0[2], d = mat0[3], 209 | e = mat0[4], f = mat0[5], g = mat0[6], h = mat0[7], 210 | i = mat0[8], j = mat0[9], k = mat0[10], l = mat0[11], 211 | m = mat0[12], n = mat0[13], o = mat0[14], p = mat0[15], 212 | A = mat1[0], B = mat1[1], C = mat1[2], D = mat1[3], 213 | E = mat1[4], F = mat1[5], G = mat1[6], H = mat1[7], 214 | I = mat1[8], J = mat1[9], K = mat1[10], L = mat1[11], 215 | M = mat1[12], N = mat1[13], O = mat1[14], P = mat1[15]; 216 | out[0] = A * a + B * e + C * i + D * m; 217 | out[1] = A * b + B * f + C * j + D * n; 218 | out[2] = A * c + B * g + C * k + D * o; 219 | out[3] = A * d + B * h + C * l + D * p; 220 | out[4] = E * a + F * e + G * i + H * m; 221 | out[5] = E * b + F * f + G * j + H * n; 222 | out[6] = E * c + F * g + G * k + H * o; 223 | out[7] = E * d + F * h + G * l + H * p; 224 | out[8] = I * a + J * e + K * i + L * m; 225 | out[9] = I * b + J * f + K * j + L * n; 226 | out[10] = I * c + J * g + K * k + L * o; 227 | out[11] = I * d + J * h + K * l + L * p; 228 | out[12] = M * a + N * e + O * i + P * m; 229 | out[13] = M * b + N * f + O * j + P * n; 230 | out[14] = M * c + N * g + O * k + P * o; 231 | out[15] = M * d + N * h + O * l + P * p; 232 | return out; 233 | } 234 | /** 235 | * 行列に拡大縮小を適用する(参照に注意・戻り値としても結果を返す) 236 | * @param {Mat4} mat - 適用を受ける行列 237 | * @param {Vec3} vec - XYZ の各軸に対して拡縮を適用する値の行列 238 | * @param {Mat4} [dest] - 結果を格納する行列 239 | * @return {Mat4} 結果の行列 240 | */ 241 | static scale(mat, vec, dest) { 242 | const out = dest == null ? Mat4.create() : dest; 243 | out[0] = mat[0] * vec[0]; 244 | out[1] = mat[1] * vec[0]; 245 | out[2] = mat[2] * vec[0]; 246 | out[3] = mat[3] * vec[0]; 247 | out[4] = mat[4] * vec[1]; 248 | out[5] = mat[5] * vec[1]; 249 | out[6] = mat[6] * vec[1]; 250 | out[7] = mat[7] * vec[1]; 251 | out[8] = mat[8] * vec[2]; 252 | out[9] = mat[9] * vec[2]; 253 | out[10] = mat[10] * vec[2]; 254 | out[11] = mat[11] * vec[2]; 255 | out[12] = mat[12]; 256 | out[13] = mat[13]; 257 | out[14] = mat[14]; 258 | out[15] = mat[15]; 259 | return out; 260 | } 261 | /** 262 | * 行列に平行移動を適用する(参照に注意・戻り値としても結果を返す) 263 | * @param {Mat4} mat - 適用を受ける行列 264 | * @param {Vec3} vec - XYZ の各軸に対して平行移動を適用する値の行列 265 | * @param {Mat4} [dest] - 結果を格納する行列 266 | * @return {Mat4} 結果の行列 267 | */ 268 | static translate(mat, vec, dest) { 269 | const out = dest == null ? Mat4.create() : dest; 270 | out[0] = mat[0]; out[1] = mat[1]; out[2] = mat[2]; out[3] = mat[3]; 271 | out[4] = mat[4]; out[5] = mat[5]; out[6] = mat[6]; out[7] = mat[7]; 272 | out[8] = mat[8]; out[9] = mat[9]; out[10] = mat[10]; out[11] = mat[11]; 273 | out[12] = mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2] + mat[12]; 274 | out[13] = mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2] + mat[13]; 275 | out[14] = mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2] + mat[14]; 276 | out[15] = mat[3] * vec[0] + mat[7] * vec[1] + mat[11] * vec[2] + mat[15]; 277 | return out; 278 | } 279 | /** 280 | * 行列に回転を適用する(参照に注意・戻り値としても結果を返す) 281 | * @param {Mat4} mat - 適用を受ける行列 282 | * @param {number} angle - 回転量を表す値(ラジアン) 283 | * @param {Vec3} axis - 回転の軸 284 | * @param {Mat4} [dest] - 結果を格納する行列 285 | * @return {Mat4} 結果の行列 286 | */ 287 | static rotate(mat, angle, axis, dest) { 288 | let out = dest == null ? Mat4.create() : dest; 289 | const sq = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]); 290 | if (!sq) { 291 | return null; 292 | } 293 | let a = axis[0], b = axis[1], c = axis[2]; 294 | if (sq != 1) { 295 | const inv = 1 / sq; 296 | a *= inv; 297 | b *= inv; 298 | c *= inv; 299 | } 300 | const d = Math.sin(angle), e = Math.cos(angle), f = 1 - e, 301 | g = mat[0], h = mat[1], i = mat[2], j = mat[3], 302 | k = mat[4], l = mat[5], m = mat[6], n = mat[7], 303 | o = mat[8], p = mat[9], q = mat[10], r = mat[11], 304 | s = a * a * f + e, 305 | t = b * a * f + c * d, 306 | u = c * a * f - b * d, 307 | v = a * b * f - c * d, 308 | w = b * b * f + e, 309 | x = c * b * f + a * d, 310 | y = a * c * f + b * d, 311 | z = b * c * f - a * d, 312 | A = c * c * f + e; 313 | if (angle) { 314 | if (mat != out) { 315 | out[12] = mat[12]; 316 | out[13] = mat[13]; 317 | out[14] = mat[14]; 318 | out[15] = mat[15]; 319 | } 320 | } else { 321 | out = mat; 322 | } 323 | out[0] = g * s + k * t + o * u; 324 | out[1] = h * s + l * t + p * u; 325 | out[2] = i * s + m * t + q * u; 326 | out[3] = j * s + n * t + r * u; 327 | out[4] = g * v + k * w + o * x; 328 | out[5] = h * v + l * w + p * x; 329 | out[6] = i * v + m * w + q * x; 330 | out[7] = j * v + n * w + r * x; 331 | out[8] = g * y + k * z + o * A; 332 | out[9] = h * y + l * z + p * A; 333 | out[10] = i * y + m * z + q * A; 334 | out[11] = j * y + n * z + r * A; 335 | return out; 336 | } 337 | /** 338 | * ビュー座標変換行列を生成する(参照に注意・戻り値としても結果を返す) 339 | * @param {Vec3} eye - 視点位置 340 | * @param {Vec3} center - 注視点 341 | * @param {Vec3} up - 上方向を示すベクトル 342 | * @param {Mat4} [dest] - 結果を格納する行列 343 | * @return {Mat4} 結果の行列 344 | */ 345 | static lookAt(eye, center, up, dest) { 346 | const out = dest == null ? Mat4.create() : dest; 347 | const eyeX = eye[0], eyeY = eye[1], eyeZ = eye[2], 348 | centerX = center[0], centerY = center[1], centerZ = center[2], 349 | upX = up[0], upY = up[1], upZ = up[2]; 350 | if (eyeX == centerX && eyeY == centerY && eyeZ == centerZ) { 351 | return Mat4.identity(out); 352 | } 353 | let x0, x1, x2, y0, y1, y2, z0, z1, z2, l; 354 | z0 = eyeX - centerX; 355 | z1 = eyeY - centerY; 356 | z2 = eyeZ - centerZ; 357 | l = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); 358 | z0 *= l; 359 | z1 *= l; 360 | z2 *= l; 361 | x0 = upY * z2 - upZ * z1; 362 | x1 = upZ * z0 - upX * z2; 363 | x2 = upX * z1 - upY * z0; 364 | l = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); 365 | if (!l) { 366 | x0 = 0; x1 = 0; x2 = 0; 367 | } else { 368 | l = 1 / l; 369 | x0 *= l; x1 *= l; x2 *= l; 370 | } 371 | y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; 372 | l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); 373 | if (!l) { 374 | y0 = 0; y1 = 0; y2 = 0; 375 | } else { 376 | l = 1 / l; 377 | y0 *= l; y1 *= l; y2 *= l; 378 | } 379 | out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0; 380 | out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0; 381 | out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0; 382 | out[12] = -(x0 * eyeX + x1 * eyeY + x2 * eyeZ); 383 | out[13] = -(y0 * eyeX + y1 * eyeY + y2 * eyeZ); 384 | out[14] = -(z0 * eyeX + z1 * eyeY + z2 * eyeZ); 385 | out[15] = 1; 386 | return out; 387 | } 388 | /** 389 | * 透視投影変換行列を生成する(参照に注意・戻り値としても結果を返す) 390 | * @param {number} fovy - 視野角(度数法) 391 | * @param {number} aspect - アスペクト比(幅 / 高さ) 392 | * @param {number} near - ニアクリップ面までの距離 393 | * @param {number} far - ファークリップ面までの距離 394 | * @param {Mat4} [dest] - 結果を格納する行列 395 | * @return {Mat4} 結果の行列 396 | */ 397 | static perspective(fovy, aspect, near, far, dest) { 398 | const out = dest == null ? Mat4.create() : dest; 399 | const t = near * Math.tan(fovy * Math.PI / 360); 400 | const r = t * aspect; 401 | const a = r * 2, b = t * 2, c = far - near; 402 | out[0] = near * 2 / a; 403 | out[1] = 0; 404 | out[2] = 0; 405 | out[3] = 0; 406 | out[4] = 0; 407 | out[5] = near * 2 / b; 408 | out[6] = 0; 409 | out[7] = 0; 410 | out[8] = 0; 411 | out[9] = 0; 412 | out[10] = -(far + near) / c; 413 | out[11] = -1; 414 | out[12] = 0; 415 | out[13] = 0; 416 | out[14] = -(far * near * 2) / c; 417 | out[15] = 0; 418 | return out; 419 | } 420 | /** 421 | * 正射影投影変換行列を生成する(参照に注意・戻り値としても結果を返す) 422 | * @param {number} left - 左端 423 | * @param {number} right - 右端 424 | * @param {number} top - 上端 425 | * @param {number} bottom - 下端 426 | * @param {number} near - ニアクリップ面までの距離 427 | * @param {number} far - ファークリップ面までの距離 428 | * @param {Mat4} [dest] - 結果を格納する行列 429 | * @return {Mat4} 結果の行列 430 | */ 431 | static ortho(left, right, top, bottom, near, far, dest) { 432 | const out = dest == null ? Mat4.create() : dest; 433 | const h = right - left; 434 | const v = top - bottom; 435 | const d = far - near; 436 | out[0] = 2 / h; 437 | out[1] = 0; 438 | out[2] = 0; 439 | out[3] = 0; 440 | out[4] = 0; 441 | out[5] = 2 / v; 442 | out[6] = 0; 443 | out[7] = 0; 444 | out[8] = 0; 445 | out[9] = 0; 446 | out[10] = -2 / d; 447 | out[11] = 0; 448 | out[12] = -(left + right) / h; 449 | out[13] = -(top + bottom) / v; 450 | out[14] = -(far + near) / d; 451 | out[15] = 1; 452 | return out; 453 | } 454 | /** 455 | * 転置行列を生成する(参照に注意・戻り値としても結果を返す) 456 | * @param {Mat4} mat - 適用する行列 457 | * @param {Mat4} [dest] - 結果を格納する行列 458 | * @return {Mat4} 結果の行列 459 | */ 460 | static transpose(mat, dest) { 461 | const out = dest == null ? Mat4.create() : dest; 462 | out[0] = mat[0]; out[1] = mat[4]; 463 | out[2] = mat[8]; out[3] = mat[12]; 464 | out[4] = mat[1]; out[5] = mat[5]; 465 | out[6] = mat[9]; out[7] = mat[13]; 466 | out[8] = mat[2]; out[9] = mat[6]; 467 | out[10] = mat[10]; out[11] = mat[14]; 468 | out[12] = mat[3]; out[13] = mat[7]; 469 | out[14] = mat[11]; out[15] = mat[15]; 470 | return out; 471 | } 472 | /** 473 | * 逆行列を生成する(参照に注意・戻り値としても結果を返す) 474 | * @param {Mat4} mat - 適用する行列 475 | * @param {Mat4} [dest] - 結果を格納する行列 476 | * @return {Mat4} 結果の行列 477 | */ 478 | static inverse(mat, dest) { 479 | const out = dest == null ? Mat4.create() : dest; 480 | const a = mat[0], b = mat[1], c = mat[2], d = mat[3], 481 | e = mat[4], f = mat[5], g = mat[6], h = mat[7], 482 | i = mat[8], j = mat[9], k = mat[10], l = mat[11], 483 | m = mat[12], n = mat[13], o = mat[14], p = mat[15], 484 | q = a * f - b * e, r = a * g - c * e, 485 | s = a * h - d * e, t = b * g - c * f, 486 | u = b * h - d * f, v = c * h - d * g, 487 | w = i * n - j * m, x = i * o - k * m, 488 | y = i * p - l * m, z = j * o - k * n, 489 | A = j * p - l * n, B = k * p - l * o, 490 | ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w); 491 | out[0] = ( f * B - g * A + h * z) * ivd; 492 | out[1] = (-b * B + c * A - d * z) * ivd; 493 | out[2] = ( n * v - o * u + p * t) * ivd; 494 | out[3] = (-j * v + k * u - l * t) * ivd; 495 | out[4] = (-e * B + g * y - h * x) * ivd; 496 | out[5] = ( a * B - c * y + d * x) * ivd; 497 | out[6] = (-m * v + o * s - p * r) * ivd; 498 | out[7] = ( i * v - k * s + l * r) * ivd; 499 | out[8] = ( e * A - f * y + h * w) * ivd; 500 | out[9] = (-a * A + b * y - d * w) * ivd; 501 | out[10] = ( m * u - n * s + p * q) * ivd; 502 | out[11] = (-i * u + j * s - l * q) * ivd; 503 | out[12] = (-e * z + f * x - g * w) * ivd; 504 | out[13] = ( a * z - b * x + c * w) * ivd; 505 | out[14] = (-m * t + n * r - o * q) * ivd; 506 | out[15] = ( i * t - j * r + k * q) * ivd; 507 | return out; 508 | } 509 | /** 510 | * 行列にベクトルを乗算する(ベクトルに行列を適用する) 511 | * @param {Mat4} mat - 適用する行列 512 | * @param {Array.} vec - 乗算するベクトル(4 つの要素を持つ配列) 513 | * @return {Float32Array} 結果のベクトル 514 | */ 515 | static toVecIV(mat, vec) { 516 | const a = mat[0], b = mat[1], c = mat[2], d = mat[3], 517 | e = mat[4], f = mat[5], g = mat[6], h = mat[7], 518 | i = mat[8], j = mat[9], k = mat[10], l = mat[11], 519 | m = mat[12], n = mat[13], o = mat[14], p = mat[15], 520 | x = vec[0], y = vec[1], z = vec[2], w = vec[3]; 521 | const out = new Float32Array(4); 522 | out[0] = x * a + y * e + z * i + w * m; 523 | out[1] = x * b + y * f + z * j + w * n; 524 | out[2] = x * c + y * g + z * k + w * o; 525 | out[3] = x * d + y * h + z * l + w * p; 526 | return out; 527 | } 528 | /** 529 | * MVP 行列に相当する行列を受け取りベクトルを変換して返す 530 | * @param {Mat4} mat - MVP 行列 531 | * @param {Vec3} vec - MVP 行列と乗算するベクトル 532 | * @param {number} width - ビューポートの幅 533 | * @param {number} height - ビューポートの高さ 534 | * @return {Vec2} 結果のベクトル 535 | */ 536 | static screenPositionFromMvp(mat, vec, width, height) { 537 | const halfWidth = width * 0.5; 538 | const halfHeight = height * 0.5; 539 | const v = Mat4.toVecIV(mat, [vec[0], vec[1], vec[2], 1.0]); 540 | if (v[3] <= 0.0) { 541 | return [NaN, NaN]; 542 | } 543 | v[0] /= v[3]; 544 | v[1] /= v[3]; 545 | v[2] /= v[3]; 546 | const out = Vec2.create(); 547 | out[0] = halfWidth + v[0] * halfWidth, 548 | out[1] = halfHeight - v[1] * halfHeight 549 | return out; 550 | } 551 | } 552 | 553 | /** 554 | * Qtn 555 | * @class Qtn 556 | */ 557 | class Qtn { 558 | /** 559 | * 4 つの要素からなるクォータニオンのデータ構造を生成する(虚部 x, y, z, 実部 w の順序で定義) 560 | * @return {Float32Array} クォータニオンデータ格納用の配列 561 | */ 562 | static create() { 563 | return new Float32Array(4); 564 | } 565 | /** 566 | * クォータニオンを初期化する(参照に注意) 567 | * @param {Qtn} dest - 初期化するクォータニオン 568 | * @return {Qtn} 結果のクォータニオン 569 | */ 570 | static identity(dest) { 571 | const out = dest == null ? Qtn.create() : dest; 572 | out[0] = 0; 573 | out[1] = 0; 574 | out[2] = 0; 575 | out[3] = 1; 576 | return out; 577 | } 578 | /** 579 | * 共役四元数を生成して返す(参照に注意・戻り値としても結果を返す) 580 | * @param {Qtn} qtn - 元となるクォータニオン 581 | * @param {Qtn} [dest] - 結果を格納するクォータニオン 582 | * @return {Qtn} 結果のクォータニオン 583 | */ 584 | static inverse(qtn, dest) { 585 | const out = dest == null ? Qtn.create() : dest; 586 | out[0] = -qtn[0]; 587 | out[1] = -qtn[1]; 588 | out[2] = -qtn[2]; 589 | out[3] = qtn[3]; 590 | return out; 591 | } 592 | /** 593 | * 虚部を正規化した結果を返す 594 | * @param {Qtn} qtn - 元となるクォータニオン 595 | * @return {Qtn} 結果のクォータニオン 596 | */ 597 | static normalize(qtn) { 598 | const out = Qtn.create(); 599 | const x = qtn[0], y = qtn[1], z = qtn[2]; 600 | const l = Math.sqrt(x * x + y * y + z * z); 601 | if (l > 0) { 602 | const i = 1 / l; 603 | out[0] = x * i; 604 | out[1] = y * i; 605 | out[2] = z * i; 606 | } 607 | return out; 608 | } 609 | /** 610 | * クォータニオンを乗算した結果を返す(参照に注意・戻り値としても結果を返す) 611 | * @param {Qtn} qtn0 - 乗算されるクォータニオン 612 | * @param {Qtn} qtn1 - 乗算するクォータニオン 613 | * @param {Qtn} [dest] - 結果を格納するクォータニオン 614 | * @return {Qtn} 結果のクォータニオン 615 | */ 616 | static multiply(qtn0, qtn1, dest) { 617 | const out = dest == null ? Qtn.create() : dest; 618 | const ax = qtn0[0], ay = qtn0[1], az = qtn0[2], aw = qtn0[3]; 619 | const bx = qtn1[0], by = qtn1[1], bz = qtn1[2], bw = qtn1[3]; 620 | out[0] = ax * bw + aw * bx + ay * bz - az * by; 621 | out[1] = ay * bw + aw * by + az * bx - ax * bz; 622 | out[2] = az * bw + aw * bz + ax * by - ay * bx; 623 | out[3] = aw * bw - ax * bx - ay * by - az * bz; 624 | return out; 625 | } 626 | /** 627 | * クォータニオンに回転を適用し返す(参照に注意・戻り値としても結果を返す) 628 | * @param {number} angle - 回転する量(ラジアン) 629 | * @param {Vec3} axis - 3つの要素を持つ軸ベクトル 630 | * @param {Qtn} [dest] - 結果を格納するクォータニオン 631 | * @return {Qtn} 結果のクォータニオン 632 | */ 633 | static rotate(angle, axis, dest) { 634 | const out = dest == null ? Qtn.create() : dest; 635 | let a = axis[0], b = axis[1], c = axis[2]; 636 | const sq = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]); 637 | if (sq !== 0) { 638 | const l = 1 / sq; 639 | a *= l; 640 | b *= l; 641 | c *= l; 642 | } 643 | const s = Math.sin(angle * 0.5); 644 | out[0] = a * s; 645 | out[1] = b * s; 646 | out[2] = c * s; 647 | out[3] = Math.cos(angle * 0.5); 648 | return out; 649 | } 650 | /** 651 | * ベクトルにクォータニオンを適用し返す(参照に注意・戻り値としても結果を返す) 652 | * @param {Vec3} vec - 3つの要素を持つベクトル 653 | * @param {Qtn} qtn - クォータニオン 654 | * @param {Vec3} [dest] - 3つの要素を持つベクトル 655 | * @return {Vec3} 結果のベクトル 656 | */ 657 | static toVecIII(vec, qtn, dest) { 658 | const out = dest == null ? Vec3.create() : dest; 659 | const qp = Qtn.create(); 660 | const qq = Qtn.create(); 661 | const qr = Qtn.create(); 662 | Qtn.inverse(qtn, qr); 663 | qp[0] = vec[0]; 664 | qp[1] = vec[1]; 665 | qp[2] = vec[2]; 666 | Qtn.multiply(qr, qp, qq); 667 | Qtn.multiply(qq, qtn, qr); 668 | out[0] = qr[0]; 669 | out[1] = qr[1]; 670 | out[2] = qr[2]; 671 | return out; 672 | } 673 | /** 674 | * 4x4 行列にクォータニオンを適用し返す(参照に注意・戻り値としても結果を返す) 675 | * @param {Qtn} qtn - クォータニオン 676 | * @param {Mat4} [dest] - 4x4 行列 677 | * @return {Mat4} 結果の行列 678 | */ 679 | static toMatIV(qtn, dest) { 680 | const out = dest == null ? Mat4.create() : dest; 681 | const x = qtn[0], y = qtn[1], z = qtn[2], w = qtn[3]; 682 | const x2 = x + x, y2 = y + y, z2 = z + z; 683 | const xx = x * x2, xy = x * y2, xz = x * z2; 684 | const yy = y * y2, yz = y * z2, zz = z * z2; 685 | const wx = w * x2, wy = w * y2, wz = w * z2; 686 | out[0] = 1 - (yy + zz); 687 | out[1] = xy - wz; 688 | out[2] = xz + wy; 689 | out[3] = 0; 690 | out[4] = xy + wz; 691 | out[5] = 1 - (xx + zz); 692 | out[6] = yz - wx; 693 | out[7] = 0; 694 | out[8] = xz - wy; 695 | out[9] = yz + wx; 696 | out[10] = 1 - (xx + yy); 697 | out[11] = 0; 698 | out[12] = 0; 699 | out[13] = 0; 700 | out[14] = 0; 701 | out[15] = 1; 702 | return out; 703 | } 704 | /** 705 | * 2つのクォータニオンの球面線形補間を行った結果を返す(参照に注意・戻り値としても結果を返す) 706 | * @param {Qtn} qtn0 - クォータニオン 707 | * @param {Qtn} qtn1 - クォータニオン 708 | * @param {number} time - 補間係数(0.0 から 1.0 で指定) 709 | * @param {Qtn} [dest] - 結果を格納するクォータニオン 710 | * @return {Qtn} 結果のクォータニオン 711 | */ 712 | static slerp(qtn0, qtn1, time, dest) { 713 | const out = dest == null ? Qtn.create() : dest; 714 | const ht = qtn0[0] * qtn1[0] + qtn0[1] * qtn1[1] + qtn0[2] * qtn1[2] + qtn0[3] * qtn1[3]; 715 | let hs = 1.0 - ht * ht; 716 | if (hs <= 0.0) { 717 | out[0] = qtn0[0]; 718 | out[1] = qtn0[1]; 719 | out[2] = qtn0[2]; 720 | out[3] = qtn0[3]; 721 | } else { 722 | hs = Math.sqrt(hs); 723 | if (Math.abs(hs) < 0.0001) { 724 | out[0] = (qtn0[0] * 0.5 + qtn1[0] * 0.5); 725 | out[1] = (qtn0[1] * 0.5 + qtn1[1] * 0.5); 726 | out[2] = (qtn0[2] * 0.5 + qtn1[2] * 0.5); 727 | out[3] = (qtn0[3] * 0.5 + qtn1[3] * 0.5); 728 | } else { 729 | const ph = Math.acos(ht); 730 | const pt = ph * time; 731 | const t0 = Math.sin(ph - pt) / hs; 732 | const t1 = Math.sin(pt) / hs; 733 | out[0] = qtn0[0] * t0 + qtn1[0] * t1; 734 | out[1] = qtn0[1] * t0 + qtn1[1] * t1; 735 | out[2] = qtn0[2] * t0 + qtn1[2] * t1; 736 | out[3] = qtn0[3] * t0 + qtn1[3] * t1; 737 | } 738 | } 739 | return out; 740 | } 741 | } 742 | 743 | -------------------------------------------------------------------------------- /src/assets/js/webgl/doxas/WebGLOrbitCamera.js: -------------------------------------------------------------------------------- 1 | import { WebGLMath } from "./WebGLMath.js"; 2 | 3 | // 短く書けるようにローカル変数に入れておく 4 | const Vec2 = WebGLMath.Vec2; 5 | const Vec3 = WebGLMath.Vec3; 6 | const Mat4 = WebGLMath.Mat4; 7 | const Qtn = WebGLMath.Qtn; 8 | 9 | /** 10 | * three.js の OrbitControls に似た挙動のカメラ操作用ユーティリティクラス 11 | * @class 12 | */ 13 | export class WebGLOrbitCamera { 14 | /** @type {number} */ 15 | static get DEFAULT_DISTANCE() { 16 | return 5.0; 17 | } 18 | /** @type {number} */ 19 | static get DEFAULT_MIN_DISTANCE() { 20 | return 1.0; 21 | } 22 | /** @type {number} */ 23 | static get DEFAULT_MAX_DISTANCE() { 24 | return 10.0; 25 | } 26 | /** @type {number} */ 27 | static get DEFAULT_MOVE_SCALE() { 28 | return 2.0; 29 | } 30 | 31 | /** 32 | * @constructor 33 | * @param {HTMLElement} target - イベントを設定するターゲットエレメント 34 | * @param {object} [option={}] 35 | * @property {number} option.distance - カメラの原点からの距離 36 | * @property {number} option.min - カメラが原点に寄れる最小距離 37 | * @property {number} option.max - カメラが原点から離れられる最大距離 38 | * @property {number} option.move - カメラが平行移動する際のスケール 39 | */ 40 | constructor(target, option = {}) { 41 | this.target = target; 42 | this.distance = option.distance || WebGLOrbitCamera.DEFAULT_DISTANCE; 43 | this.minDistance = option.min || WebGLOrbitCamera.DEFAULT_MIN_DISTANCE; 44 | this.maxDistance = option.max || WebGLOrbitCamera.DEFAULT_MAX_DISTANCE; 45 | this.moveScale = option.move || WebGLOrbitCamera.DEFAULT_MOVE_SCALE; 46 | this.position = Vec3.create(0.0, 0.0, this.distance); 47 | this.center = Vec3.create(0.0, 0.0, 0.0); 48 | this.upDirection = Vec3.create(0.0, 1.0, 0.0); 49 | this.defaultPosition = Vec3.create(0.0, 0.0, this.distance); 50 | this.defaultCenter = Vec3.create(0.0, 0.0, 0.0); 51 | this.defaultUpDirection = Vec3.create(0.0, 1.0, 0.0); 52 | this.movePosition = Vec3.create(0.0, 0.0, 0.0); 53 | this.rotateX = 0.0; 54 | this.rotateY = 0.0; 55 | this.scale = 0.0; 56 | this.isDown = false; 57 | this.prevPosition = Vec2.create(0, 0); 58 | this.offsetPosition = Vec2.create(0, 0); 59 | this.qt = Qtn.create(); 60 | this.qtx = Qtn.create(); 61 | this.qty = Qtn.create(); 62 | 63 | // self binding 64 | this.mouseInteractionStart = this.mouseInteractionStart.bind(this); 65 | this.mouseInteractionMove = this.mouseInteractionMove.bind(this); 66 | this.mouseInteractionEnd = this.mouseInteractionEnd.bind(this); 67 | this.wheelScroll = this.wheelScroll.bind(this); 68 | 69 | // event 70 | this.target.addEventListener( 71 | "mousedown", 72 | this.mouseInteractionStart, 73 | false 74 | ); 75 | this.target.addEventListener("mousemove", this.mouseInteractionMove, false); 76 | this.target.addEventListener("mouseup", this.mouseInteractionEnd, false); 77 | this.target.addEventListener("wheel", this.wheelScroll, false); 78 | this.target.addEventListener( 79 | "contextmenu", 80 | (event) => { 81 | event.preventDefault(); 82 | }, 83 | false 84 | ); 85 | } 86 | 87 | /** 88 | * マウスボタンが押された際のイベント 89 | */ 90 | mouseInteractionStart(event) { 91 | this.isDown = true; 92 | const bound = this.target.getBoundingClientRect(); 93 | this.prevPosition = Vec2.create( 94 | event.clientX - bound.left, 95 | event.clientY - bound.top 96 | ); 97 | } 98 | 99 | /** 100 | * マウスが移動した際のイベント 101 | */ 102 | mouseInteractionMove(event) { 103 | if (this.isDown !== true) { 104 | return; 105 | } 106 | const bound = this.target.getBoundingClientRect(); 107 | const w = bound.width; 108 | const h = bound.height; 109 | const x = event.clientX - bound.left; 110 | const y = event.clientY - bound.top; 111 | const s = 1.0 / Math.min(w, h); 112 | this.offsetPosition = Vec2.create( 113 | x - this.prevPosition[0], 114 | y - this.prevPosition[1] 115 | ); 116 | this.prevPosition = Vec2.create(x, y); 117 | switch (event.buttons) { 118 | case 1: // 左ボタン 119 | this.rotateX += this.offsetPosition[0] * s; 120 | this.rotateY += this.offsetPosition[1] * s; 121 | this.rotateX = this.rotateX % 1.0; 122 | this.rotateY = Math.min(Math.max(this.rotateY % 1.0, -0.25), 0.25); 123 | break; 124 | case 2: // 右ボタン 125 | const eyeOffset = Vec3.create( 126 | this.offsetPosition[0], 127 | -this.offsetPosition[1], 128 | 0.0 129 | ); 130 | const rotateEye = Qtn.toVecIII(eyeOffset, this.qt); 131 | this.movePosition[0] -= rotateEye[0] * s * this.moveScale; 132 | this.movePosition[1] -= rotateEye[1] * s * this.moveScale; 133 | this.movePosition[2] -= rotateEye[2] * s * this.moveScale; 134 | break; 135 | } 136 | } 137 | 138 | /** 139 | * マウスボタンが離された際のイベント 140 | */ 141 | mouseInteractionEnd(event) { 142 | this.isDown = false; 143 | } 144 | 145 | /** 146 | * スクロール操作に対するイベント 147 | */ 148 | wheelScroll(event) { 149 | const w = event.wheelDelta; 150 | if (w > 0) { 151 | this.scale = -0.5; 152 | } else if (w < 0) { 153 | this.scale = 0.5; 154 | } 155 | } 156 | 157 | /** 158 | * 現在のパラメータからビュー行列を生成して返す 159 | * @return {Mat4} 160 | */ 161 | update() { 162 | const PI2 = Math.PI * 2.0; 163 | const v = Vec3.create(1.0, 0.0, 0.0); 164 | const u = Vec3.create(0.0, 1.0, 0.0); 165 | // scale 166 | this.scale *= 0.7; 167 | this.distance += this.scale; 168 | this.distance = Math.min( 169 | Math.max(this.distance, this.minDistance), 170 | this.maxDistance 171 | ); 172 | this.defaultPosition[2] = this.distance; 173 | // rotate 174 | Qtn.identity(this.qt); 175 | Qtn.identity(this.qtx); 176 | Qtn.identity(this.qty); 177 | Qtn.rotate(this.rotateX * PI2, u, this.qtx); 178 | Qtn.toVecIII(v, this.qtx, v); 179 | Qtn.rotate(this.rotateY * PI2, v, this.qty); 180 | Qtn.multiply(this.qtx, this.qty, this.qt); 181 | Qtn.toVecIII(this.defaultPosition, this.qt, this.position); 182 | Qtn.toVecIII(this.defaultUpDirection, this.qt, this.upDirection); 183 | // translate 184 | this.position[0] += this.movePosition[0]; 185 | this.position[1] += this.movePosition[1]; 186 | this.position[2] += this.movePosition[2]; 187 | this.center[0] = this.defaultCenter[0] + this.movePosition[0]; 188 | this.center[1] = this.defaultCenter[1] + this.movePosition[1]; 189 | this.center[2] = this.defaultCenter[2] + this.movePosition[2]; 190 | 191 | return Mat4.lookAt(this.position, this.center, this.upDirection); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/assets/scss/app.scss: -------------------------------------------------------------------------------- 1 | @use "./global/app" as *; 2 | 3 | // foundation 4 | @use "./foundation/root"; 5 | @use "./foundation/reset"; 6 | 7 | // utility 8 | @use "./other/utility"; 9 | 10 | // -------------------------- 11 | 12 | html, 13 | body { 14 | background: var(--black); 15 | } 16 | 17 | body { 18 | &[data-status=""] { 19 | cursor: wait; 20 | } 21 | } 22 | 23 | .lil-gui { 24 | @include sp_w() { 25 | display: none !important; 26 | } 27 | } 28 | 29 | #world { 30 | pointer-events: none; 31 | user-select: none; 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | z-index: 0; 36 | width: 100vw; 37 | height: 100vh; 38 | canvas { 39 | cursor: grab; 40 | &:active { 41 | cursor: grabbing; 42 | } 43 | } 44 | 45 | opacity: 0; 46 | body[data-status="enter"] & { 47 | pointer-events: initial; 48 | user-select: initial; 49 | transition: 1s ease opacity; 50 | opacity: 1; 51 | } 52 | } 53 | 54 | // -------------------------- 55 | 56 | // コンテナ 57 | 58 | // -------------------------- 59 | .l-container { 60 | position: relative; 61 | z-index: 10; 62 | opacity: 0; 63 | body[data-status="enter"] & { 64 | opacity: 1; 65 | transition: 0.3s ease opacity; 66 | } 67 | } 68 | .l-container__head { 69 | border-left: solid 2px var(--white-solid); 70 | border-right: solid 2px var(--white-solid); 71 | border-top: solid 2px var(--white-solid); 72 | border-bottom: solid 1px var(--white-solid); 73 | box-sizing: border-box; 74 | padding: 32px; 75 | display: flex; 76 | flex-wrap: wrap; 77 | justify-content: space-between; 78 | align-items: flex-end; 79 | @include sp_w() { 80 | padding: 24px 24px; 81 | } 82 | } 83 | .c-title { 84 | width: 100%; 85 | color: var(--white); 86 | font-size: 84px; 87 | font-weight: 700; 88 | -webkit-text-stroke: 4px var(--white); 89 | text-stroke: 4px var(--white); 90 | line-height: 100%; 91 | @include sp_w() { 92 | font-size: 64px; 93 | } 94 | } 95 | .c-text { 96 | margin-top: 8px; 97 | color: var(--white); 98 | font-size: 14px; 99 | font-family: 100; 100 | line-height: 160%; 101 | @include sp_w() { 102 | font-size: 12px; 103 | margin-top: 12px; 104 | } 105 | } 106 | .c-text-right { 107 | text-align: right; 108 | } 109 | 110 | // -------------------------- 111 | 112 | // アイテムサイズ 113 | 114 | // -------------------------- 115 | #js-rect { 116 | pointer-events: none; 117 | user-select: none; 118 | position: absolute; 119 | top: 0; 120 | left: 0; 121 | opacity: 0; 122 | width: liquid(900 * 0.5); 123 | height: liquid(540 * 0.5); 124 | @include sp_w() { 125 | width: liquid(900 * 0.3); 126 | height: liquid(540 * 0.3); 127 | } 128 | } 129 | 130 | // -------------------------- 131 | 132 | // アイテムリスト 133 | 134 | // -------------------------- 135 | .c-itemList { 136 | @include hover() { 137 | .c-item__number, 138 | .c-item__label, 139 | .c-item__arrow { 140 | opacity: 0.3; 141 | } 142 | .c-item.is-active { 143 | .c-item__number, 144 | .c-item__label, 145 | .c-item__arrow { 146 | opacity: 1; 147 | } 148 | } 149 | } 150 | } 151 | .c-itemList__box { 152 | } 153 | .c-item { 154 | cursor: pointer; 155 | position: relative; 156 | z-index: 0; 157 | display: flex; 158 | align-items: center; 159 | gap: 12px; 160 | padding: 64px 32px; 161 | border-left: solid 2px var(--white-solid); 162 | border-right: solid 2px var(--white-solid); 163 | border-top: solid 1px var(--white-solid); 164 | border-bottom: solid 1px var(--white-solid); 165 | box-sizing: border-box; 166 | @include sp_w() { 167 | padding: 54px 24px; 168 | } 169 | @include hover() { 170 | .c-item__number, 171 | .c-item__label, 172 | .c-item__arrow { 173 | opacity: 1; 174 | } 175 | } 176 | 177 | &.is-active { 178 | .c-item__number, 179 | .c-item__label, 180 | .c-item__arrow { 181 | opacity: 1; 182 | } 183 | .c-item__arrow { 184 | transform: rotateZ(45deg); 185 | } 186 | } 187 | } 188 | .c-item__number, 189 | .c-item__label, 190 | .c-item__arrow { 191 | pointer-events: none; 192 | user-select: none; 193 | } 194 | .c-item__number { 195 | color: var(--white); 196 | font-size: 16px; 197 | font-weight: 700; 198 | } 199 | .c-item__label { 200 | color: var(--white); 201 | font-size: 16px; 202 | font-weight: 700; 203 | } 204 | .c-item__arrow { 205 | color: var(--white); 206 | font-size: 28px; 207 | font-weight: 700; 208 | margin-left: auto; 209 | transition: 0.2s ease opacity, 0.2s ease transform; 210 | } 211 | .c-item__number, 212 | .c-item__label { 213 | opacity: 1; 214 | transition: 0.2s ease opacity; 215 | } 216 | .c-item__img { 217 | display: none; 218 | visibility: hidden; 219 | img { 220 | } 221 | } 222 | 223 | // -------------------------- 224 | 225 | // footer 226 | 227 | // -------------------------- 228 | .l-footer { 229 | position: relative; 230 | z-index: 10; 231 | border-left: solid 2px var(--white-solid); 232 | border-right: solid 2px var(--white-solid); 233 | border-top: solid 1px var(--white-solid); 234 | border-bottom: solid 2px var(--white-solid); 235 | box-sizing: border-box; 236 | width: 100vw; 237 | height: 64px; 238 | } 239 | 240 | // -------------------------- 241 | 242 | // コピーライト 243 | 244 | // -------------------------- 245 | .c-copyright { 246 | user-select: none; 247 | position: fixed; 248 | bottom: 24px; 249 | left: 0; 250 | right: 0; 251 | margin: auto; 252 | z-index: 2000; 253 | display: block; 254 | font-size: 12px; 255 | font-family: var(--ja); 256 | line-height: 100%; 257 | font-weight: bold; 258 | width: max-content; 259 | color: var(--white); 260 | 261 | @include sp_w() { 262 | right: 0; 263 | left: 0; 264 | margin: auto; 265 | } 266 | &::before { 267 | pointer-events: none; 268 | user-select: none; 269 | content: ""; 270 | position: absolute; 271 | bottom: -6px; 272 | left: 0; 273 | z-index: 0; 274 | transform: translate3d(0, 0, 0px); 275 | height: 2px; 276 | width: 100%; 277 | background: var(--white); 278 | transform: scale(0, 1); 279 | transform-origin: left; 280 | transition: 0.4s $easeinout transform; 281 | @include sp_w() { 282 | bottom: -4px; 283 | } 284 | } 285 | @include hover() { 286 | &::before { 287 | transform: scale(1, 1); 288 | } 289 | } 290 | opacity: 0; 291 | body[data-status="enter"] & { 292 | transition: 0.6s 0.4s ease opacity; 293 | opacity: 1; 294 | } 295 | } 296 | 297 | // -------------------------- 298 | 299 | // GitHub 300 | 301 | // -------------------------- 302 | .c-github { 303 | user-select: none; 304 | width: 20px; 305 | height: 20px; 306 | display: flex; 307 | align-items: center; 308 | svg { 309 | pointer-events: none; 310 | user-select: none; 311 | width: 100%; 312 | height: 100%; 313 | fill: var(--white); 314 | } 315 | 316 | opacity: 0; 317 | body[data-status="enter"] & { 318 | transition: 0.4s ease opacity; 319 | opacity: 1; 320 | @include hover() { 321 | opacity: 0.5; 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/assets/scss/foundation/_reset.scss: -------------------------------------------------------------------------------- 1 | @use "../global/function" as *; 2 | 3 | html, 4 | body { 5 | font-family: var(--ja); 6 | font-weight: 400; 7 | font-size: calc(100 / var(--base-vw) * 1vw); 8 | @include pc_w() { 9 | --base-vw: 1440; 10 | } 11 | @include sp_w() { 12 | --base-vw: 375; 13 | } 14 | color: var(--black); 15 | background: var(--white); 16 | background-size: cover; 17 | background-repeat: no-repeat; 18 | overscroll-behavior: none; 19 | } 20 | 21 | body, 22 | div, 23 | dl, 24 | dt, 25 | dd, 26 | ul, 27 | ol, 28 | li, 29 | h1, 30 | h2, 31 | h3, 32 | h4, 33 | h5, 34 | h6, 35 | p, 36 | pre, 37 | code, 38 | form, 39 | fieldset, 40 | legend, 41 | input, 42 | textarea, 43 | blockquote, 44 | th, 45 | td, 46 | p { 47 | margin: 0; 48 | padding: 0; 49 | } 50 | 51 | table { 52 | border-collapse: collapse; 53 | border-spacing: 0; 54 | } 55 | 56 | iframe, 57 | fieldset, 58 | img { 59 | border: 0; 60 | } 61 | 62 | address, 63 | caption, 64 | cite, 65 | code, 66 | dfn, 67 | em, 68 | strong, 69 | th, 70 | var { 71 | font-style: normal; 72 | font-weight: normal; 73 | } 74 | 75 | ol, 76 | ul { 77 | list-style: none; 78 | } 79 | 80 | caption, 81 | th { 82 | text-align: left; 83 | } 84 | 85 | abbr, 86 | acronym { 87 | border: 0; 88 | font-variant: normal; 89 | } 90 | 91 | sup { 92 | vertical-align: text-top; 93 | } 94 | 95 | sub { 96 | vertical-align: text-bottom; 97 | } 98 | 99 | input, 100 | textarea, 101 | select { 102 | font-family: inherit; 103 | font-size: inherit; 104 | font-weight: inherit; 105 | } 106 | 107 | legend { 108 | color: var(--black); 109 | } 110 | 111 | *:focus { 112 | outline: none; 113 | } 114 | 115 | a { 116 | cursor: pointer; 117 | color: var(--black); 118 | text-decoration: none; 119 | box-shadow: none; 120 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 121 | } 122 | 123 | input { 124 | cursor: pointer; 125 | -webkit-appearance: none; 126 | appearance: none; 127 | background-color: #fff; 128 | border-radius: 0; 129 | } 130 | 131 | textarea { 132 | cursor: pointer; 133 | -webkit-appearance: none; 134 | appearance: none; 135 | border-radius: 0; 136 | resize: vertical; 137 | } 138 | 139 | select { 140 | cursor: pointer; 141 | appearance: none; 142 | -moz-appearance: none; 143 | -webkit-appearance: none; 144 | background: none; 145 | border: none; 146 | } 147 | 148 | button { 149 | cursor: pointer; 150 | border: none; 151 | background: initial; 152 | } 153 | 154 | ::-webkit-scrollbar { 155 | display: none; 156 | } 157 | -------------------------------------------------------------------------------- /src/assets/scss/foundation/_root.scss: -------------------------------------------------------------------------------- 1 | $gothic: "游ゴシック", YuGothic, "ヒラギノ角ゴ ProN W3", "Hiragino Kaku Gothic ProN", sans-serif; 2 | 3 | :root { 4 | // font 5 | --ja: "Noto Sans JP", sans-serif; // 100, 300, 400, 700 6 | --en: "Noto Sans JP", sans-serif; 7 | 8 | // color 9 | --white: #fff; 10 | --white-solid: rgba(255, 255, 255, 0.2); 11 | --black: #161616; 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/scss/global/_app.scss: -------------------------------------------------------------------------------- 1 | // entry point 2 | @forward "./function"; 3 | @forward "./variable"; 4 | -------------------------------------------------------------------------------- /src/assets/scss/global/_function.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; // math.div(a,b); => a / b 2 | @use "./variable" as *; 3 | 4 | // -------------------------- 5 | 6 | // ブレイクポイント 7 | 8 | // -------------------------- 9 | @mixin pc_w($w: $screen4) { 10 | @media (min-width: #{$w+1}px) { 11 | @content; 12 | } 13 | } 14 | @mixin sp_w($w: $screen4) { 15 | @media (max-width: #{$w}px) { 16 | @content; 17 | } 18 | } 19 | 20 | // -------------------------- 21 | 22 | // rem 23 | // htmlでfont-sizeが62.5%の時に使用できる関数 24 | 25 | // -------------------------- 26 | @function rem($px) { 27 | @return math.div($px, 16) * 1rem; 28 | } 29 | 30 | // -------------------------- 31 | 32 | // vw 33 | 34 | // -------------------------- 35 | @function vw($w, $base: 1440) { 36 | @return math.div($w, $base) * 100vw; 37 | } 38 | @function vw_sp($w, $base: 375) { 39 | @return math.div($w, $base) * 100vw; 40 | } 41 | 42 | // -------------------------- 43 | 44 | // vh 45 | 46 | // -------------------------- 47 | @function vh($h, $base: 800) { 48 | @return math.div($h, $base) * 100vh; 49 | } 50 | @function vh_sp($h, $base: 800) { 51 | @return math.div($h, $base) * 100vh; 52 | } 53 | 54 | // -------------------------- 55 | 56 | // liquid | viewportのwidth基準 57 | // htmlのfont-sizeがリッキド用に設定している時に使用できる関数 58 | // font-size: calc(100 / var(--base-vw) * 1vw); 59 | 60 | // -------------------------- 61 | @function liquid($px) { 62 | @return $px * 1rem; 63 | } 64 | 65 | // -------------------------- 66 | 67 | // hover && active 68 | 69 | // -------------------------- 70 | @mixin hover() { 71 | @media (hover: hover) { 72 | &:hover { 73 | @content; 74 | } 75 | } 76 | } 77 | @mixin active() { 78 | @media (hover: none) { 79 | &:active { 80 | @content; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/assets/scss/global/_variable.scss: -------------------------------------------------------------------------------- 1 | // --------------------- 2 | 3 | // screen 4 | 5 | // --------------------- 6 | $screen1: 320; 7 | $screen2: 375; 8 | $screen3: 480; 9 | $screen4: 768; 10 | $screen5: 960; 11 | $screen6: 1024; 12 | $screen7: 1280; 13 | $screen8: 1440; 14 | $screen9: 1680; 15 | $screen10: 1920; 16 | 17 | // --------------------- 18 | 19 | // responsive 20 | 21 | // --------------------- 22 | $maxWidth: #{$screen4}px; 23 | $minWidth: #{$screen4 + 1}px; 24 | 25 | // --------------------------------- 26 | 27 | // easing //https://easings.net/ja 28 | 29 | // --------------------------------- 30 | // ease in 31 | $easeInSine: cubic-bezier(0.12, 0, 0.39, 0); 32 | $easeInCubic: cubic-bezier(0.32, 0, 0.67, 0); 33 | $easeInQuint: cubic-bezier(0.64, 0, 0.78, 0); 34 | $easeInCirc: cubic-bezier(0.55, 0, 1, 0.45); 35 | $easeInQuad: cubic-bezier(0.11, 0, 0.5, 0); 36 | $easeInQuart: cubic-bezier(0.5, 0, 0.75, 0); 37 | $easeInExpo: cubic-bezier(0.7, 0, 0.84, 0); 38 | $easeInBack: cubic-bezier(0.36, 0, 0.66, -0.56); 39 | // ease out 40 | $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1); 41 | $easeOutCubic: cubic-bezier(0.33, 1, 0.68, 1); 42 | $easeOutQuint: cubic-bezier(0.22, 1, 0.36, 1); 43 | $easeOutCirc: cubic-bezier(0, 0.55, 0.45, 1); 44 | $easeOutQuad: cubic-bezier(0.5, 1, 0.89, 1); 45 | $easeOutQuart: cubic-bezier(0.25, 1, 0.5, 1); 46 | $easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1); 47 | $easeOutBack: cubic-bezier(0.34, 1.56, 0.64, 1); 48 | // ease in out 49 | $easeInOutSine: cubic-bezier(0.37, 0, 0.63, 1); 50 | $easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1); 51 | $easeInOutQuint: cubic-bezier(0.83, 0, 0.17, 1); 52 | $easeInOutCirc: cubic-bezier(0.85, 0, 0.15, 1); 53 | $easeInOutQuad: cubic-bezier(0.45, 0, 0.55, 1); 54 | $easeInOutQuart: cubic-bezier(0.76, 0, 0.24, 1); 55 | $easeInOutExpo: cubic-bezier(0.87, 0, 0.13, 1); 56 | $easeInOutBack: cubic-bezier(0.68, -0.6, 0.32, 1.6); 57 | 58 | // use easing 59 | $easein: $easeInExpo; 60 | $easeout: $easeOutExpo; 61 | $easeinout: $easeInOutExpo; 62 | 63 | // --------------------- 64 | 65 | // hover 66 | 67 | // --------------------- 68 | $hoverTime: 0.4s; 69 | $hoverEase: $easeOutQuart; 70 | -------------------------------------------------------------------------------- /src/assets/scss/other/_utility.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; // math.div(a,b); => a / b 2 | @use "../global/app" as *; 3 | 4 | @include pc_w() { 5 | .u-pc-hide { 6 | display: none !important; 7 | } 8 | } 9 | @include sp_w() { 10 | .u-sp-hide { 11 | display: none !important; 12 | } 13 | } 14 | 15 | .u-hidden { 16 | display: none !important; 17 | } 18 | -------------------------------------------------------------------------------- /src/assets/shader/_inc/matcap.glsl: -------------------------------------------------------------------------------- 1 | vec2 matcap(vec3 eye, vec3 normal) { 2 | vec3 reflected = reflect(eye, normal); 3 | float m = 2.8284271247461903 * sqrt( reflected.z+1.0 ); 4 | return reflected.xy / m + 0.5; 5 | } -------------------------------------------------------------------------------- /src/assets/shader/_inc/optimizationUv.glsl: -------------------------------------------------------------------------------- 1 | vec2 optimizationUv(vec2 uv, vec2 ratio){ 2 | return vec2( 3 | ((uv.x - 0.5) * ratio.x + 0.5), 4 | ((uv.y - 0.5) * ratio.y + 0.5) 5 | ); 6 | } -------------------------------------------------------------------------------- /src/assets/shader/_inc/ratio.glsl: -------------------------------------------------------------------------------- 1 | vec2 ratio(float p, float t, float s){ 2 | return vec2( 3 | min(p / t, 1.0) * s, 4 | (min((1.0 / p) / (1.0 / t), 1.0)) * s 5 | ); 6 | } -------------------------------------------------------------------------------- /src/assets/shader/frag/plane.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | varying float vScale; 3 | varying vec2 vUv; 4 | varying vec2 vCurve; 5 | varying vec3 vNormal; 6 | uniform float uChange; 7 | uniform float uPlaneAspect; 8 | uniform float uTextureAspect; 9 | uniform sampler2D uTexture1; 10 | uniform sampler2D uTexture2; 11 | uniform sampler2D uNoise; 12 | uniform sampler2D uMatcap; 13 | uniform vec3 uEyePosition; 14 | varying vec3 vPosition; 15 | 16 | uniform float uTime; 17 | uniform float uProgress; 18 | 19 | const vec3 LIGTH = vec3(0.0, 0.0, 5.0); 20 | 21 | #include ../_inc/matcap.glsl 22 | #include ../_inc/ratio.glsl 23 | #include ../_inc/optimizationUv.glsl 24 | 25 | void main() { 26 | float progress = uChange; 27 | float scaleProgress = 1.0 - vScale; 28 | vec2 uv = vec2(vUv.x, 1.0 - vUv.y); 29 | 30 | // モデルの頂点座標とカメラの位置から視線ベクトルを求める 31 | vec3 eyeDirection = normalize(vPosition - uEyePosition); 32 | 33 | // matcap 34 | vec2 matcapUv = matcap(eyeDirection, vNormal); 35 | vec4 matcapTexture = texture2D(uMatcap, matcapUv); 36 | 37 | // ノイズ(歪み) 38 | vec4 noise = texture2D(uNoise, optimizationUv(uv, vec2(1.0))); 39 | vec2 dispUv = noise.xy; 40 | 41 | vec2 textureUv = optimizationUv(uv, ratio(uPlaneAspect, uTextureAspect, 1.0)); 42 | 43 | float interpolation = pow( 44 | smoothstep( 45 | 0.0, 46 | 1.0, 47 | (progress * 0.95 + dispUv.x * 0.05 ) * 2.0 - textureUv.x 48 | ), 49 | 5.0 50 | ); 51 | 52 | vec2 uv1 = (textureUv - 0.5) * (1.0 - interpolation) + 0.5; 53 | vec4 t1 = vec4( 54 | texture2D(uTexture1, uv1).r, 55 | texture2D(uTexture1, uv1 + (vCurve.x + vCurve.y) * 0.4 * scaleProgress).g, 56 | texture2D(uTexture1, uv1 + (vCurve.x + vCurve.y) * 0.4 * scaleProgress).b, 57 | 1.0 58 | ); 59 | t1.r = t1.r * (1.0 + interpolation * (20.0 - 19.0 * progress)); 60 | t1.g = t1.g * (1.0 - interpolation); 61 | t1.b = t1.b * (1.0 - interpolation * (10.0 - 9.0 * progress)); 62 | 63 | vec2 uv2 = (textureUv - 0.5) * (interpolation) + 0.5; 64 | vec4 t2 = vec4( 65 | texture2D(uTexture2, uv2).r, 66 | texture2D(uTexture2, uv2 + (vCurve.x + vCurve.y) * 0.4 * scaleProgress).g, 67 | texture2D(uTexture2, uv2 + (vCurve.x + vCurve.y) * 0.4 * scaleProgress).b, 68 | 1.0 69 | ); 70 | t2.rgb = t2.rgb * interpolation; 71 | 72 | vec4 texture = mix(t1,t2,interpolation); 73 | vec4 dist = vec4(texture.rgb * matcapTexture.rgb, 1.0); 74 | gl_FragColor = dist; 75 | } -------------------------------------------------------------------------------- /src/assets/shader/vert/plane.glsl: -------------------------------------------------------------------------------- 1 | 2 | attribute vec3 position; 3 | attribute vec3 normal; 4 | attribute vec2 uv; 5 | varying float vScale; 6 | varying vec2 vUv; 7 | uniform float uScale; 8 | uniform vec2 uFullScale; 9 | varying vec2 vCurve; 10 | varying vec3 vNormal; 11 | varying vec3 vPosition; 12 | uniform vec2 uCurve; 13 | uniform mat4 uMvpMatrix; 14 | uniform mat4 uModelMatrix; 15 | uniform mat4 uNormalMatrix; 16 | 17 | const float PI = 3.1415925; 18 | 19 | void main() { 20 | vUv = uv; 21 | vCurve = uCurve; 22 | vScale = uScale; 23 | 24 | // ワールド空間上でのモデルの頂点を求める 25 | vPosition = (uModelMatrix * vec4(position, 1.0)).xyz; 26 | 27 | vNormal = normalize((uNormalMatrix * vec4(normal, 0.0)).xyz); 28 | 29 | // animation progress 30 | float transform = 1.0 - uv.y; 31 | float progressStartValue = 0.5; 32 | float progress = clamp( 33 | progressStartValue, 34 | 1.0, 35 | smoothstep( 36 | transform * progressStartValue, 37 | 1.0, 38 | uScale 39 | ) 40 | ); 41 | 42 | float x = position.x; 43 | float y = position.y; 44 | float z = position.z; 45 | 46 | // curve effects 47 | x = x + (sin(uv.y * PI) * uCurve.x) * (1.0 - uScale); 48 | y = y + (sin(uv.x * PI) * uCurve.y) * (1.0 - uScale); 49 | 50 | // plane size → fullscreen size 51 | x = x * uFullScale.x; 52 | y = y * uFullScale.y; 53 | 54 | // position adjustment 55 | x = x + (x * progress) - (x * uScale); 56 | y = y + (y * progress) - (y * uScale); 57 | 58 | gl_Position = uMvpMatrix * vec4(vec3(x, y, z), 1.0); 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Copyright.astro: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | ©2023 SHOYA KAJITA. 10 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../assets/scss/app.scss"; 3 | import Copyright from "../components/Copyright.astro"; 4 | 5 | export interface Props { 6 | title?: string; 7 | description?: string; 8 | themeColor?: string; 9 | ogType?: string; 10 | ogImg?: string; 11 | ogUrl?: string; 12 | thumbnail?: string; 13 | } 14 | 15 | const SITE = import.meta.env.SITE; 16 | const { 17 | title = "WebGL School Task.07 ~ Plane geometry animation using pure WebGL. | Toys.No.029 | ShoyaKajita.", 18 | description = "WebGL School Task.07 ~ Plane geometry animation using pure WebGL. | WebGL Three.js GLSL | Interactive toys created by ShoyaKajita.", 19 | themeColor = "#161616", 20 | ogType = "website", 21 | ogImg = "assets/img/head/screenshot.webp", 22 | ogUrl = import.meta.env.SITE, 23 | thumbnail = "assets/img/head/thumbnail.webp", 24 | } = Astro.props; 25 | 26 | const gtag = ``; 27 | --- 28 | 29 | 30 | 31 | 32 | 33 | {title} 34 | {import.meta.env.PROD && } 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../layouts/Layout.astro"; 3 | 4 | const itemList = [ 5 | { 6 | label: "Iron Man", 7 | imgName: "ironman", 8 | width: 900, 9 | height: 540, 10 | }, 11 | { 12 | label: "Spiderman", 13 | imgName: "spiderman", 14 | width: 900, 15 | height: 540, 16 | }, 17 | { 18 | label: "War machine", 19 | imgName: "warmachine", 20 | width: 900, 21 | height: 540, 22 | }, 23 | { 24 | label: "Captain America", 25 | imgName: "captainamerica", 26 | width: 900, 27 | height: 540, 28 | }, 29 | { 30 | label: "Falcon", 31 | imgName: "falcon", 32 | width: 900, 33 | height: 540, 34 | }, 35 | { 36 | label: "The Winter Soldier", 37 | imgName: "wintersoldier", 38 | width: 900, 39 | height: 540, 40 | }, 41 | { 42 | label: "Thor", 43 | imgName: "thor", 44 | width: 900, 45 | height: 540, 46 | }, 47 | { 48 | label: "Vision", 49 | imgName: "vision", 50 | width: 900, 51 | height: 540, 52 | }, 53 | { 54 | label: "Black Widow", 55 | imgName: "blackwidow", 56 | width: 900, 57 | height: 540, 58 | }, 59 | { 60 | label: "Doctor Strange", 61 | imgName: "doctorstrange", 62 | width: 900, 63 | height: 540, 64 | }, 65 | { 66 | label: "Wong", 67 | imgName: "wong", 68 | width: 900, 69 | height: 540, 70 | }, 71 | { 72 | label: "Drax the Destroyer", 73 | imgName: "drax", 74 | width: 900, 75 | height: 540, 76 | }, 77 | { 78 | label: "Hulk", 79 | imgName: "hulk", 80 | width: 900, 81 | height: 540, 82 | }, 83 | { 84 | label: "Black Panther", 85 | imgName: "blackpanther", 86 | width: 900, 87 | height: 540, 88 | }, 89 | { 90 | label: "Okoye", 91 | imgName: "okoye", 92 | width: 900, 93 | height: 540, 94 | }, 95 | { 96 | label: "Shuri", 97 | imgName: "syuri", 98 | width: 900, 99 | height: 540, 100 | }, 101 | { 102 | label: "Star Lord", 103 | imgName: "starroad", 104 | width: 900, 105 | height: 540, 106 | }, 107 | { 108 | label: "Rocket Raccoon", 109 | imgName: "rocket", 110 | width: 900, 111 | height: 540, 112 | }, 113 | { 114 | label: "Groot", 115 | imgName: "groot", 116 | width: 900, 117 | height: 540, 118 | }, 119 | { 120 | label: "Gamora", 121 | imgName: "gamora", 122 | width: 900, 123 | height: 540, 124 | }, 125 | { 126 | label: "Mantis", 127 | imgName: "mantis", 128 | width: 900, 129 | height: 540, 130 | }, 131 | { 132 | label: "Nebula", 133 | imgName: "nebula", 134 | width: 900, 135 | height: 540, 136 | }, 137 | { 138 | label: "Scarlet Witch", 139 | imgName: "scarletwitch", 140 | width: 900, 141 | height: 540, 142 | }, 143 | ]; 144 | --- 145 | 146 | 147 |
148 | 149 |
150 |
151 |
152 |
MARVEL HEROES
153 |

154 | WebGL School Task 07.
155 | Plane geometry animation using pure WebGL.
156 | Scroll Down 157 |

158 | 165 | 166 | 169 | 170 | 171 |
172 |
173 |
174 |
    175 | { 176 | itemList.map((data, i) => { 177 | const label = data.label; 178 | const imgName = data.imgName; 179 | const width = data.width; 180 | const height = data.height; 181 | let number = Number(i + 1); 182 | number = number < 10 ? String(`0${number}`) : String(number); 183 | return ( 184 |
  • 185 |
    186 |
    {number}
    187 |
    {label}
    188 |
    189 | 196 |
    197 |
    +
    198 |
    199 |
  • 200 | ); 201 | }) 202 | } 203 |
204 |
205 |
206 |
207 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base" 3 | } 4 | --------------------------------------------------------------------------------