diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
new file mode 100644
index 000000000..59ed7a886
--- /dev/null
+++ b/.github/workflows/gh-pages.yml
@@ -0,0 +1,41 @@
+name: github pages
+
+on:
+ push:
+ branches:
+ - main
+
+env:
+ GITEA_MAIL: ${{ secrets.GITEA_MAIL }}
+ GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
+
+jobs:
+ build-deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ ref: main
+ submodules: true
+ fetch-depth: 0
+ - run: |
+ yarn install
+ rm -rf dist/vrma
+ git clone https://${GITEA_TOKEN}@git.syui.ai/ai/vrma dist/vrma
+ rm -rf dist/vrma/.git
+
+ - name: Build
+ env:
+ TZ: "Asia/Tokyo"
+ run: |
+ yarn build
+
+ - name: Deploy
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./dist
+ user_name: 'ai[bot]'
+ user_email: '138105980+yui-syui-ai[bot]@users.noreply.github.com'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..d3e6b1306
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+package-lock.json
+example
+yarn.lock
+**DS_Store
+dist/*.js
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..bf34da5a2
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "dist/vrma"]
+ path = dist/vrma
+ url = git@git.syui.ai:ai/vrma
diff --git a/README.md b/README.md
index ccdc4c76d..dedffe126 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,30 @@
+# vrm
+
+three-vrm + vrma
+
+## vrm & vrma
```sh
-$ npm i
-$ npm run dev
-$ npm run build
+$ git submodule update --init --recursive
```
+
+or
+
+- vrm : [download](https://hub.vroid.com/characters/675572020956181239/models/7175071267176594918)
+- vrma : [download](https://vroid.booth.pm/items/5512385)
+
+> ./dist/vrma
+
+```js
+load("/vrma/model.vrm");
+load("/vrma/VRMA_01.vrma");
+```
+
+## build
+
+```sh
+$ yarn install
+$ yarn dev
+$ yarn build
+```
+
diff --git a/dist/CNAME b/dist/CNAME
new file mode 100644
index 000000000..63a8a5072
--- /dev/null
+++ b/dist/CNAME
@@ -0,0 +1 @@
+vrm.syui.ai
diff --git a/dist/css/icomoon/css/icomoon.css b/dist/css/icomoon/css/icomoon.css
new file mode 100644
index 000000000..0d3359f8f
--- /dev/null
+++ b/dist/css/icomoon/css/icomoon.css
@@ -0,0 +1,72 @@
+@font-face {
+ font-family: 'icomoon';
+ src: url('../fonts/icomoon.eot?czi5du');
+ src: url('../fonts/icomoon.eot?czi5du#iefix') format('embedded-opentype'),
+ url('../fonts/icomoon.ttf?czi5du') format('truetype'),
+ url('../fonts/icomoon.woff?czi5du') format('woff'),
+ url('../fonts/icomoon.svg?czi5du#icomoon') format('svg');
+ font-weight: normal;
+ font-style: normal;
+ font-display: block;
+}
+
+[class^="icon-"], [class*=" icon-"] {
+ /* use !important to prevent issues with browser extensions that change fonts */
+ font-family: 'icomoon' !important;
+ speak: never;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-ai:before {
+ content: "\e908";
+}
+.icon-robot:before {
+ content: "\e909";
+}
+.icon-api:before {
+ content: "\e90e";
+}
+.icon-sandar:before {
+ content: "\e90a";
+}
+.icon-moon:before {
+ content: "\e90b";
+}
+.icon-home:before {
+ content: "\e90c";
+}
+.icon-cloud:before {
+ content: "\e90d";
+}
+.icon-phoenix:before {
+ content: "\e906";
+}
+.icon-honeycomb:before {
+ content: "\e907";
+}
+.icon-phoenix-power:before {
+ content: "\e903";
+}
+.icon-phoenix-world:before {
+ content: "\e904";
+}
+.icon-power:before {
+ content: "\e905";
+}
+.icon-syui:before {
+ content: "\e902";
+}
+.icon-archlinux:before {
+ content: "\e900";
+}
+.icon-archlinuxjp:before {
+ content: "\e901";
+}
diff --git a/dist/css/icomoon/fonts/icomoon.eot b/dist/css/icomoon/fonts/icomoon.eot
new file mode 100644
index 000000000..d120dd487
Binary files /dev/null and b/dist/css/icomoon/fonts/icomoon.eot differ
diff --git a/dist/css/icomoon/fonts/icomoon.svg b/dist/css/icomoon/fonts/icomoon.svg
new file mode 100644
index 000000000..25356f9bc
--- /dev/null
+++ b/dist/css/icomoon/fonts/icomoon.svg
@@ -0,0 +1,25 @@
+
+
+
\ No newline at end of file
diff --git a/dist/css/icomoon/fonts/icomoon.ttf b/dist/css/icomoon/fonts/icomoon.ttf
new file mode 100644
index 000000000..f366cf679
Binary files /dev/null and b/dist/css/icomoon/fonts/icomoon.ttf differ
diff --git a/dist/css/icomoon/fonts/icomoon.woff b/dist/css/icomoon/fonts/icomoon.woff
new file mode 100644
index 000000000..13ec0c2dd
Binary files /dev/null and b/dist/css/icomoon/fonts/icomoon.woff differ
diff --git a/dist/css/style.css b/dist/css/style.css
new file mode 100644
index 000000000..4331c61c9
--- /dev/null
+++ b/dist/css/style.css
@@ -0,0 +1,36 @@
+div#canvas {
+ width: 100%;
+ height: 640px;
+}
+
+div#menu {
+ padding: 20px;
+ border-bottom:solid 1px #ccc;
+}
+
+button {
+ border: none;
+ margin: 0;
+ padding: 0 20px 0 20px;
+ width: auto;
+ overflow: visible;
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ line-height: normal;
+ -webkit-font-smoothing: inherit;
+ -moz-osx-font-smoothing: inherit;
+ appearance: none;
+ -webkit-appearance: none;
+ cursor: pointer;
+ user-select: none;
+}
+
+a {
+text-decoration: none;
+}
+
+a:hover{
+ /* color: #fff700; */
+ color: #847e00;
+}
diff --git a/dist/favicon.ico b/dist/favicon.ico
new file mode 100644
index 000000000..7f94540e2
Binary files /dev/null and b/dist/favicon.ico differ
diff --git a/dist/img/0.hdr b/dist/img/0.hdr
new file mode 100644
index 000000000..74bef49a8
Binary files /dev/null and b/dist/img/0.hdr differ
diff --git a/dist/img/1.hdr b/dist/img/1.hdr
new file mode 100644
index 000000000..bc15b9a0c
Binary files /dev/null and b/dist/img/1.hdr differ
diff --git a/dist/index.html b/dist/index.html
new file mode 100644
index 000000000..99e68ca8b
--- /dev/null
+++ b/dist/index.html
@@ -0,0 +1,18 @@
+
+
+
+ ai
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/vrma b/dist/vrma
new file mode 160000
index 000000000..3e83eb3ef
--- /dev/null
+++ b/dist/vrma
@@ -0,0 +1 @@
+Subproject commit 3e83eb3efde28bcc4665c63ad6f0c02b849619e3
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..8428f1f84
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "vrm",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "build": "webpack",
+ "dev": "webpack-dev-server --open"
+ },
+ "devDependencies": {
+ "ts-loader": "^9.5.1",
+ "typescript": "^5.4.2",
+ "webpack": "^5.90.3",
+ "webpack-cli": "^5.1.4",
+ "webpack-dev-server": "^5.0.3"
+ },
+ "dependencies": {
+ "@pixiv/three-vrm": "^2.1.1",
+ "@pixiv/three-vrm-animation": "^2.1.1",
+ "@pixiv/three-vrm-springbone": "^2.1.1",
+ "three": "^0.162.0"
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 000000000..eb73b0ecb
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,266 @@
+import * as THREE from "three"
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
+import { VRMLoaderPlugin } from "@pixiv/three-vrm";
+import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from "@pixiv/three-vrm-animation";
+
+import { Color, DirectionalLight, Fog, HemisphereLight } from 'three';
+import { GridHelper, Mesh, MeshLambertMaterial, BoxGeometry, Vector3 } from 'three';
+
+import { VRMSpringBoneManager, VRMSpringBoneJoint, VRMSpringBoneJointHelper } from '@pixiv/three-vrm-springbone';
+
+import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
+
+window.addEventListener("DOMContentLoaded", () => {
+ const canvas = document.getElementById("canvas");
+ if (canvas == null) return;
+
+ const scene = new THREE.Scene();
+
+ const camera = new THREE.PerspectiveCamera(
+ 30, canvas.clientWidth/canvas.clientHeight, 0.1, 20);
+ camera.position.set(0.0, 0.9, -4.0)
+ camera.rotation.set(0.0, Math.PI, 0.0)
+ camera.lookAt(new THREE.Vector3(0, 0, 0));
+
+ // https://threejs.org/docs/#api/en/constants/Renderer
+ const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
+ renderer.setPixelRatio(window.devicePixelRatio);
+ renderer.setSize(canvas.clientWidth, canvas.clientHeight);
+ renderer.setClearColor(0x7fbfff, 1.0);
+ renderer.shadowMap.enabled = true;
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
+ renderer.toneMapping = THREE.ReinhardToneMapping;
+ renderer.toneMapping = THREE.NeutralToneMapping;
+ canvas.appendChild(renderer.domElement);
+ renderer.toneMappingExposure = 1.5;
+
+ //renderer.toneMapping = THREE.ACESFilmicToneMapping;
+
+ const light = new THREE.DirectionalLight(0xffffff, Math.PI);
+ light.position.set(1.0, 1.0, 1.0);
+ scene.add(light);
+
+ let currentVrm: any = undefined;
+ let currentVrmAnimation: any = undefined;
+ let currentMixer:any = undefined;
+
+ function load(url: string) {
+ loader.load(
+ url,
+ (gltf) => {
+ tryInitVRM(gltf);
+ tryInitVRMA(gltf);
+ },
+ (progress) => console.log(
+ "Loading model...",
+ 100.0 * (progress.loaded / progress.total), "%"
+ ),
+ (error) => console.error(error)
+ );
+ }
+
+ function tryInitVRM(gltf: any) {
+ const vrm = gltf.userData.vrm;
+ if ( vrm == null ) {
+ return;
+ }
+ currentVrm = vrm;
+ scene.add(vrm.scene);
+ initAnimationClip();
+ }
+
+ function tryInitVRMA(gltf: any) {
+ const vrmAnimations = gltf.userData.vrmAnimations;
+ if (vrmAnimations == null) {
+ return;
+ }
+ currentVrmAnimation = vrmAnimations[0] ?? null;
+ initAnimationClip();
+ }
+
+ function initAnimationClip() {
+ if (currentVrm && currentVrmAnimation) {
+ currentMixer = new THREE.AnimationMixer(currentVrm.scene);
+ const clip = createVRMAnimationClip(currentVrmAnimation, currentVrm);
+ currentMixer.clipAction(clip).play();
+ }
+ }
+
+ const loader = new GLTFLoader();
+ loader.register((parser) => {
+ return new VRMLoaderPlugin(parser);
+ });
+ loader.register((parser) => {
+ return new VRMAnimationLoaderPlugin(parser);
+ });
+
+ load("/vrma/ai.vrm");
+ load("/vrma/fly_c.vrma");
+
+ const clock = new THREE.Clock();
+ clock.start();
+
+ const controls = new OrbitControls(camera, renderer.domElement);
+ controls.enableDamping = true;
+ controls.dampingFactor = 0.2;
+ controls.enableRotate = true;
+ controls.target.set( 0.0, 1.0, 0.0 );
+
+ function floor_default(){
+ scene.background = new THREE.Color( 0xffffff );
+ const directionalLight = new THREE.DirectionalLight(0xffffff);
+ directionalLight.position.set(1, 1, 1);
+ scene.add(directionalLight);
+ const ambientLight = new THREE.AmbientLight(0x333333);
+ scene.add(ambientLight);
+
+ const floor = new Mesh(
+ new BoxGeometry(50, 100),
+ new MeshLambertMaterial({
+ color: 0xffffff,
+ depthWrite: true,
+ })
+ );
+ floor.position.y = -1.0;
+ floor.rotation.x = -Math.PI / 2;
+ scene.add(floor);
+
+ const grid = new GridHelper(50, 100, 0xffffff, 0xffffff);
+ scene.add(grid);
+ grid.position.set(Math.round(0), 0, Math.round(0));
+ scene.fog = new Fog(0xffffff, 3, 20);
+ scene.fog?.color.set(0xffffff);
+ }
+
+ scene.background = new THREE.Color( 0xffffff );
+ const directionalLight = new THREE.DirectionalLight(0xffffff);
+ directionalLight.position.set(1, 1, 1);
+ scene.add(directionalLight);
+ const ambientLight = new THREE.AmbientLight(0x333333);
+ scene.add(ambientLight);
+
+ //const floor = new Mesh(
+ // new BoxGeometry(50, 100),
+ // new MeshLambertMaterial({
+ // color: 0xffffff,
+ // depthWrite: true,
+ // })
+ //);
+ //floor.position.y = -1.0;
+ //floor.rotation.x = -Math.PI / 2;
+ //scene.add(floor);
+
+ const grid = new GridHelper(50, 100, 0xffffff, 0xffffff);
+ scene.add(grid);
+ grid.position.set(Math.round(0), 0, Math.round(0));
+ scene.fog = new Fog(0xffffff, 3, 20);
+ scene.fog?.color.set(0xffffff);
+
+ function animate() {
+ controls.update();
+ const delta = clock.getDelta();
+ if (currentMixer) {
+ currentMixer.update(delta);
+ }
+ if (currentVrm) {
+ currentVrm.update(delta);
+ }
+ requestAnimationFrame(animate);
+ scene.rotation.y += 0.005;
+ renderer.render(scene, camera);
+ }
+ animate();
+
+ function random_happy() {
+ // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/expressions.ja.md
+ currentVrm.expressionManager.setValue('relaxed', 0.5);
+ }
+
+ function random_head() {
+ // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/lookAt.ja.md
+ currentVrm.lookAt.target = camera;
+ currentVrm.VRMLookAtBoneApplier = camera;
+ currentVrm.VRMLookAtExpressionApplier = camera;
+
+ // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/humanoid.ja.md
+ const head = currentVrm.humanoid.getRawBoneNode("head");
+ head.target = camera;
+ }
+
+ function random_blink(){
+ setInterval(() => {
+ currentVrm.expressionManager.setValue('relaxed', 0);
+ currentVrm.expressionManager.setValue('blink', 0);
+ random_head();
+ const r = Math.floor(Math.random() * 3);
+ if (r == 1) {
+ setTimeout(() => { currentVrm.expressionManager.setValue('blink', 1); }, 5000);
+ setTimeout(() => {
+ currentVrm.expressionManager.setValue('blink', 0);
+ }, 5500);
+ };
+ setTimeout(() => {
+ currentVrm.expressionManager.setValue('relaxed', 0.5);
+ currentVrm.expressionManager.setValue('blink', 1);
+ }, 6000);
+ }, 6500);
+ }
+ random_blink();
+
+ setInterval(() => {
+ const r = Math.floor(Math.random() * 4 + 1);
+ load("/vrma/" + r + ".vrma");
+ setTimeout(() => {
+ load("/vrma/fly_c.vrma");
+ }, 10000);
+ }, 15000);
+
+ const el_light = document.querySelector('#btn-moon') as HTMLInputElement | null;
+ if(el_light != null) {
+ el_light.addEventListener('click', function(){
+ light_s();
+ });
+ }
+
+ let light_enable = true;
+ function light_s(){
+ if (light_enable == true) {
+ light_enable = false;
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
+ light.intensity = -0.5;
+ scene.background = new THREE.Color(0x000000);
+ scene.fog = new Fog(0x000000, 3, 20);
+ } else {
+ light_enable = true;
+ renderer.toneMapping = THREE.NeutralToneMapping;
+ light.intensity = 1;
+ scene.background = new THREE.Color(0xffffff);
+ scene.fog = new Fog(0xffffff, 3, 20);
+ }
+ console.log(light_enable);
+ }
+
+ const el_hdr = document.querySelector('#btn-sandar') as HTMLInputElement | null;
+ if(el_hdr != null) {
+ el_hdr.addEventListener('click', function(){
+ hdr_s();
+ });
+ }
+
+ let hdr_r = 0;
+ function hdr_s() {
+ if (hdr_r == 0) {
+ hdr_r = 1;
+ } else {
+ hdr_r = 0;
+ }
+ let hdr = "/img/" + hdr_r + ".hdr";
+ new RGBELoader().load(hdr, function (texture) {
+ texture.mapping = THREE.EquirectangularReflectionMapping;
+ scene.background = texture;
+ scene.environment = texture;
+ });
+ }
+
+})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 000000000..6794acd31
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compilerOptions": {
+ "target": "es2016",
+ "module": "commonjs",
+ "skipLibCheck": true
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 000000000..8c36b9cf4
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,26 @@
+const path = require('path');
+
+module.exports = {
+ mode: 'development',
+ entry: './src/index.ts',
+ module: {
+ rules: [
+ {
+ test: /\.ts$/,
+ loader: 'ts-loader'
+ }
+ ]
+ },
+ resolve: {
+ extensions: ['.ts', '.js']
+ },
+ output: {
+ filename: 'main.js',
+ path: path.join(__dirname, "dist")
+ },
+ devServer: {
+ static: {
+ directory: path.join(__dirname, "dist"),
+ }
+ }
+}