diff --git a/Demo/index.html b/Demo/index.html
new file mode 100644
index 0000000..30bf3e4
--- /dev/null
+++ b/Demo/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Building Scene
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MFA_Demo/index.html b/MFA_Demo/index.html
new file mode 100644
index 0000000..fe2d2b6
--- /dev/null
+++ b/MFA_Demo/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+ home
+
+
+
+
+
+
+ Click to play
+
+
+ Move: WASD
+ Jump: SPACE
+ Look: MOUSE
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MFA_Demo/models/blue_flower_animated.glb b/MFA_Demo/models/blue_flower_animated.glb
new file mode 100644
index 0000000..84b9da8
Binary files /dev/null and b/MFA_Demo/models/blue_flower_animated.glb differ
diff --git a/MFA_Demo/models/claw_arcade_animated_.glb b/MFA_Demo/models/claw_arcade_animated_.glb
new file mode 100644
index 0000000..2249bdf
Binary files /dev/null and b/MFA_Demo/models/claw_arcade_animated_.glb differ
diff --git a/MFA_Demo/models/clock.glb b/MFA_Demo/models/clock.glb
new file mode 100644
index 0000000..9150758
Binary files /dev/null and b/MFA_Demo/models/clock.glb differ
diff --git a/MFA_Demo/package-lock.json b/MFA_Demo/package-lock.json
new file mode 100644
index 0000000..bc59025
--- /dev/null
+++ b/MFA_Demo/package-lock.json
@@ -0,0 +1,727 @@
+{
+ "name": "w14_interaction",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "w14_interaction",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "three": "^0.158.0"
+ },
+ "devDependencies": {
+ "vite": "^5.0.2"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz",
+ "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz",
+ "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz",
+ "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz",
+ "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz",
+ "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz",
+ "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz",
+ "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz",
+ "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz",
+ "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz",
+ "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz",
+ "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz",
+ "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz",
+ "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz",
+ "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz",
+ "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz",
+ "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz",
+ "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz",
+ "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz",
+ "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz",
+ "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz",
+ "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz",
+ "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.0.tgz",
+ "integrity": "sha512-keHkkWAe7OtdALGoutLY3utvthkGF+Y17ws9LYT8pxMBYXaCoH/8dXS2uzo6e8+sEhY7y/zi5RFo22Dy2lFpDw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.0.tgz",
+ "integrity": "sha512-y3Kt+34smKQNWilicPbBz/MXEY7QwDzMFNgwEWeYiOhUt9MTWKjHqe3EVkXwT2fR7izOvHpDWZ0o2IyD9SWX7A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.0.tgz",
+ "integrity": "sha512-oLzzxcUIHltHxOCmaXl+pkIlU+uhSxef5HfntW7RsLh1eHm+vJzjD9Oo4oUKso4YuP4PpbFJNlZjJuOrxo8dPg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.0.tgz",
+ "integrity": "sha512-+ANnmjkcOBaV25n0+M0Bere3roeVAnwlKW65qagtuAfIxXF9YxUneRyAn/RDcIdRa7QrjRNJL3jR7T43ObGe8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.0.tgz",
+ "integrity": "sha512-tBTSIkjSVUyrekddpkAqKOosnj1Fc0ZY0rJL2bIEWPKqlEQk0paORL9pUIlt7lcGJi3LzMIlUGXvtNi1Z6MOCQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.0.tgz",
+ "integrity": "sha512-Ed8uJI3kM11de9S0j67wAV07JUNhbAqIrDYhQBrQW42jGopgheyk/cdcshgGO4fW5Wjq97COCY/BHogdGvKVNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.0.tgz",
+ "integrity": "sha512-mZoNQ/qK4D7SSY8v6kEsAAyDgznzLLuSFCA3aBHZTmf3HP/dW4tNLTtWh9+LfyO0Z1aUn+ecpT7IQ3WtIg3ViQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.0.tgz",
+ "integrity": "sha512-rouezFHpwCqdEXsqAfNsTgSWO0FoZ5hKv5p+TGO5KFhyN/dvYXNMqMolOb8BkyKcPqjYRBeT+Z6V3aM26rPaYg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.0.tgz",
+ "integrity": "sha512-Bbm+fyn3S6u51urfj3YnqBXg5vI2jQPncRRELaucmhBVyZkbWClQ1fEsRmdnCPpQOQfkpg9gZArvtMVkOMsh1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.0.tgz",
+ "integrity": "sha512-+MRMcyx9L2kTrTUzYmR61+XVsliMG4odFb5UmqtiT8xOfEicfYAGEuF/D1Pww1+uZkYhBqAHpvju7VN+GnC3ng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.0.tgz",
+ "integrity": "sha512-rxfeE6K6s/Xl2HGeK6cO8SiQq3k/3BYpw7cfhW5Bk2euXNEpuzi2cc7llxx1si1QgwfjNtdRNTGqdBzGlFZGFw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.0.tgz",
+ "integrity": "sha512-QqmCsydHS172Y0Kc13bkMXvipbJSvzeglBncJG3LsYJSiPlxYACz7MmJBs4A8l1oU+jfhYEIC/+AUSlvjmiX/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz",
+ "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.19.8",
+ "@esbuild/android-arm64": "0.19.8",
+ "@esbuild/android-x64": "0.19.8",
+ "@esbuild/darwin-arm64": "0.19.8",
+ "@esbuild/darwin-x64": "0.19.8",
+ "@esbuild/freebsd-arm64": "0.19.8",
+ "@esbuild/freebsd-x64": "0.19.8",
+ "@esbuild/linux-arm": "0.19.8",
+ "@esbuild/linux-arm64": "0.19.8",
+ "@esbuild/linux-ia32": "0.19.8",
+ "@esbuild/linux-loong64": "0.19.8",
+ "@esbuild/linux-mips64el": "0.19.8",
+ "@esbuild/linux-ppc64": "0.19.8",
+ "@esbuild/linux-riscv64": "0.19.8",
+ "@esbuild/linux-s390x": "0.19.8",
+ "@esbuild/linux-x64": "0.19.8",
+ "@esbuild/netbsd-x64": "0.19.8",
+ "@esbuild/openbsd-x64": "0.19.8",
+ "@esbuild/sunos-x64": "0.19.8",
+ "@esbuild/win32-arm64": "0.19.8",
+ "@esbuild/win32-ia32": "0.19.8",
+ "@esbuild/win32-x64": "0.19.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.0.tgz",
+ "integrity": "sha512-R8i5Her4oO1LiMQ3jKf7MUglYV/mhQ5g5OKeld5CnkmPdIGo79FDDQYqPhq/PCVuTQVuxsWgIbDy9F+zdHn80w==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.6.0",
+ "@rollup/rollup-android-arm64": "4.6.0",
+ "@rollup/rollup-darwin-arm64": "4.6.0",
+ "@rollup/rollup-darwin-x64": "4.6.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.6.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.6.0",
+ "@rollup/rollup-linux-arm64-musl": "4.6.0",
+ "@rollup/rollup-linux-x64-gnu": "4.6.0",
+ "@rollup/rollup-linux-x64-musl": "4.6.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.6.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.6.0",
+ "@rollup/rollup-win32-x64-msvc": "4.6.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/three": {
+ "version": "0.158.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.158.0.tgz",
+ "integrity": "sha512-TALj4EOpdDPF1henk2Q+s17K61uEAAWQ7TJB68nr7FKxqwyDr3msOt5IWdbGm4TaWKjrtWS8DJJWe9JnvsWOhQ=="
+ },
+ "node_modules/vite": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.2.tgz",
+ "integrity": "sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.19.3",
+ "postcss": "^8.4.31",
+ "rollup": "^4.2.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/MFA_Demo/package.json b/MFA_Demo/package.json
new file mode 100644
index 0000000..5e0ab81
--- /dev/null
+++ b/MFA_Demo/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "w14_interaction",
+ "version": "1.0.0",
+ "description": "",
+ "main": "script.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "three": "^0.158.0"
+ },
+ "devDependencies": {
+ "vite": "^5.0.2"
+ }
+}
diff --git a/MFA_Demo/script.js b/MFA_Demo/script.js
new file mode 100644
index 0000000..67ce14d
--- /dev/null
+++ b/MFA_Demo/script.js
@@ -0,0 +1,405 @@
+//@ts-check
+
+
+/**
+ * SCRIPT LEVEL EXPLAINER
+ *
+ * [IMPORTS]
+ * |-> THREE.js: A JavaScript library used to create and display animated 3D graphics.
+ * |-> GLTFLoader: For loading 3D models in the GLTF format.
+ * |-> PointerLockControls: For first-person control mechanics.
+ *
+ * [VARIABLE DECLARATIONS]
+ * |-> camera, scene, renderer: Basic elements for any THREE.js application.
+ * |-> clock: Tracks time for animations.
+ * |-> controls: Manages the camera control via mouse/keyboard.
+ * |-> collidableObjects: Stores objects that the player can collide with.
+ * |-> loader, mixers: For loading 3D models and managing their animations.
+ * |-> raycaster: Used for collision detection.
+ * |-> Various variables for managing movement and physics.
+ *
+ * [INITIAL SETUP]
+ * |-> init(): Sets up the camera, scene, lighting, controls, event listeners, and the renderer.
+ * |-> loadModel(): Asynchronously loads 3D models, sets their position, and handles animations.
+ * |-> Returns a Promise, allowing for asynchronous operations.
+ * |-> Utilizes GLTFLoader to load models.
+ * |-> Adds loaded models to the scene and collidableObjects array.
+ * |-> animate(): Animation loop function.
+ * |-> requestAnimationFrame(animate): Creates a loop that updates the rendering as needed.
+ * |-> updateRaycaster(): Updates the raycaster for collision detection.
+ * |-> checkCollision(): Checks for collisions with collidable objects.
+ * |-> Handles the movement and physics of the camera based on user input.
+ * |-> Renders the scene from the camera's perspective.
+ *
+ * [EVENT LISTENERS]
+ * |-> For keyboard inputs to control movement.
+ * |-> Window resize event to adjust camera aspect ratio.
+ *
+ * [FUNCTIONALITIES]
+ * |-> Real-time rendering of 3D graphics.
+ * |-> Interactive camera control for exploring the 3D scene.
+ * |-> Collision detection with objects in the scene.
+ * |-> Loading and displaying animated 3D models.
+ * |-> Physics implementation for realistic movements.
+ *
+ * [MAIN EXECUTION]
+ * |-> The script starts by initializing the scene and then enters the animation loop.
+ *
+ * This script is a basic framework for a 3D interactive scene using THREE.js,
+ * demonstrating fundamental concepts like scene setup, rendering, model loading,
+ * animation, camera controls, and collision detection.
+ */
+
+
+
+
+import * as THREE from 'three';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
+
+// Camera, scene, and renderer declarations
+let camera, scene, renderer;
+
+// Clock for animation timing
+let clock;
+
+// PointerLockControls for camera movement
+let controls;
+
+// Array to store collidable objects in the scene
+let collidableObjects = [];
+
+// GLTFLoader for loading 3D models and mixers for animations
+let loader, mixers;
+
+// Raycaster for collision detection
+let raycaster;
+
+// Movement controls
+let moveForward = false;
+let moveLeft = false;
+let moveRight = false;
+let moveBackward = false;
+let canJump = false;
+
+// Time tracking for movement
+let prevTime = performance.now();
+const velocity = new THREE.Vector3();
+const direction = new THREE.Vector3();
+
+// Variables to manage clock model and its animations
+let clockLoaded;
+let clockAction;
+
+// Initialize and animate the scene
+init();
+animate();
+
+function init() {
+ // Animation loop
+ clock = new THREE.Clock();
+
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
+ camera.position.set(2, 0, -6);
+ camera.lookAt(0, 1, 0);
+
+ controls = new PointerLockControls(camera, document.body);
+
+ const blocker = document.getElementById('blocker');
+ const instructions = document.getElementById('instructions');
+
+ instructions.addEventListener('click', function () {
+
+ controls.lock();
+
+ });
+
+ controls.addEventListener('lock', function () {
+
+ instructions.style.display = 'none';
+ blocker.style.display = 'none';
+
+ });
+
+ controls.addEventListener('unlock', function () {
+
+ blocker.style.display = 'block';
+ instructions.style.display = '';
+
+ });
+
+
+ scene = new THREE.Scene();
+ scene.add(controls.getObject());
+ scene.background = new THREE.Color(0xa0a0a0);
+ scene.fog = new THREE.Fog(0xa0a0a0, 10, 50);
+
+ const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
+ hemiLight.position.set(0, 20, 0);
+ scene.add(hemiLight);
+
+ const dirLight = new THREE.DirectionalLight(0xffffff, 3);
+ dirLight.position.set(-3, 10, -10);
+ dirLight.castShadow = true;
+ dirLight.shadow.camera.top = 4;
+ dirLight.shadow.camera.bottom = -4;
+ dirLight.shadow.camera.left = -4;
+ dirLight.shadow.camera.right = 4;
+ dirLight.shadow.camera.near = 0.1;
+ dirLight.shadow.camera.far = 40;
+ scene.add(dirLight);
+
+ const onKeyDown = function (event) {
+
+ switch (event.code) {
+
+ case 'ArrowUp':
+ case 'KeyW':
+ moveForward = true;
+ break;
+
+ case 'ArrowLeft':
+ case 'KeyA':
+ moveLeft = true;
+ break;
+
+ case 'ArrowDown':
+ case 'KeyS':
+ moveBackward = true;
+ break;
+
+ case 'ArrowRight':
+ case 'KeyD':
+ moveRight = true;
+ break;
+
+ case 'Space':
+ if (canJump === true) velocity.y += 350;
+ canJump = false;
+ break;
+
+ }
+
+ };
+
+ const onKeyUp = function (event) {
+
+ switch (event.code) {
+
+ case 'ArrowUp':
+ case 'KeyW':
+ moveForward = false;
+ break;
+
+ case 'ArrowLeft':
+ case 'KeyA':
+ moveLeft = false;
+ break;
+
+ case 'ArrowDown':
+ case 'KeyS':
+ moveBackward = false;
+ break;
+
+ case 'ArrowRight':
+ case 'KeyD':
+ moveRight = false;
+ break;
+
+ }
+
+ };
+
+ document.addEventListener('keydown', onKeyDown);
+ document.addEventListener('keyup', onKeyUp);
+
+ raycaster = new THREE.Raycaster();
+ // raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0), 0, 10);
+ raycaster.near = 0.1;
+ raycaster.far = 10; // Only detect collisions within 10 units from the camera
+
+
+ // ground
+ const mesh = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), new THREE.MeshPhongMaterial({
+ color: 0xcbcbcb,
+ depthWrite: false
+ }));
+ mesh.rotation.x = -Math.PI / 2;
+ mesh.receiveShadow = true;
+ scene.add(mesh);
+
+ // GLTF Loader
+ loader = new GLTFLoader();
+ mixers = []; // Array to hold AnimationMixers
+
+ let clockModel = {
+ path: 'models/clock.glb',
+ position: new THREE.Vector3(-4, 15, 10),
+ rotation: new THREE.Euler(Math.PI, 0, 0),
+ scale: new THREE.Vector3(1, 1, 1)
+ }
+
+ function loadModel(model) {
+ // A Promise is used to handle asynchronous operations. It represents a value that may be available now, later, or never.
+ return new Promise((resolve, reject) => {
+ // Use the GLTFLoader to load a 3D model
+ loader.load(model.path, (gltf) => {
+ // Set the position, rotation, and scale of the loaded model to match the specified model
+ gltf.scene.position.copy(model.position);
+ gltf.scene.rotation.copy(model.rotation);
+ gltf.scene.scale.copy(model.scale);
+
+ // Add the model to the scene
+ scene.add(gltf.scene);
+
+ // Check if the model has animations
+ if (gltf.animations.length) {
+ // Create an AnimationMixer to handle the model's animations
+ let mixer = new THREE.AnimationMixer(gltf.scene);
+ mixers.push(mixer); // Add the mixer to the mixers array for later use
+
+ // Store the mixer in the loaded model for easy reference
+ gltf.mixer = mixer;
+ }
+
+ // Add the model to the list of collidable objects for collision detection
+ collidableObjects.push(gltf.scene);
+
+ // Resolve the promise, indicating that the model has been loaded successfully
+ resolve(gltf);
+ }, undefined, (error) => {
+ console.error('An error happened', error);
+ reject(error); // Reject the promise if an error occurs during loading
+ });
+ });
+ }
+
+ // Asynchronously load the clock model
+ loadModel(clockModel).then(gltf => {
+ // This code runs after the model has been successfully loaded
+ console.log('Clock model loaded', gltf);
+ clockLoaded = gltf; // Store the loaded model for later use
+ }).catch(error => {
+ // This code runs if there was an error loading the model
+ console.error('Error loading clock model', error);
+ });
+
+
+ renderer = new THREE.WebGLRenderer({
+ antialias: true
+ });
+ renderer.setPixelRatio(window.devicePixelRatio);
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.shadowMap.enabled = true;
+ document.body.appendChild(renderer.domElement);
+
+ window.addEventListener('resize', onWindowResize);
+
+}
+
+
+
+function onWindowResize() {
+
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+
+ renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function updateRaycaster() {
+ // Set the raycaster to start at the camera position and cast in the direction the camera is facing
+ raycaster.set(camera.position, camera.getWorldDirection(new THREE.Vector3()));
+}
+
+
+function checkCollision() {
+ const intersects = raycaster.intersectObjects(collidableObjects, true);
+
+ if (intersects.length === 0) {
+ return; // No collision detected
+ }
+
+ console.log("Collision detected with: ", intersects.map(obj => obj.object.name));
+
+ intersects.forEach(intersectedObject => {
+ const name = intersectedObject.object.name;
+
+ if (name.includes('clock') && clockLoaded) {
+ if (!clockAction) {
+ clockAction = clockLoaded.mixer.clipAction(clockLoaded.animations[0]);
+ }
+ if (!clockAction.isRunning()) {
+ clockAction.play();
+ }
+ }
+ });
+}
+
+
+
+
+function animate() {
+ // Request the browser to perform an animation and call the 'animate' function on the next frame
+ requestAnimationFrame(animate);
+
+ // Update the raycaster for collision detection
+ updateRaycaster();
+ checkCollision();
+
+ // Calculate the time passed since the last frame
+ const delta = clock.getDelta();
+
+ // Update all animation mixers for any animations in the scene
+ mixers.forEach((mixer) => mixer.update(delta));
+
+ // Get the current time for movement calculations
+ const time = performance.now();
+
+ // If the camera controls are locked (meaning, user is controlling the camera)
+ if (controls.isLocked === true) {
+
+ // Adjust the raycaster's origin to the camera's position for collision detection
+ raycaster.ray.origin.copy(controls.getObject().position);
+ raycaster.ray.origin.y -= 10; // Move it slightly downwards
+
+ // Calculate time difference since last movement
+ const delta = (time - prevTime) / 1000;
+
+ // Apply friction to slow down movement
+ velocity.x -= velocity.x * 10.0 * delta;
+ velocity.z -= velocity.z * 10.0 * delta;
+
+ // Apply gravity effect
+ velocity.y -= 9.8 * 100.0 * delta; // 100.0 represents a 'mass' value
+
+ // Determine the direction of movement based on key presses
+ direction.z = Number(moveForward) - Number(moveBackward);
+ direction.x = Number(moveRight) - Number(moveLeft);
+ direction.normalize(); // Normalize the direction for consistent movement speed
+
+ // Adjust velocity based on the direction of movement
+ if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta;
+ if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta;
+
+ // Move the camera based on calculated velocity
+ controls.moveRight(-velocity.x * delta);
+ controls.moveForward(-velocity.z * delta);
+
+ // Apply calculated velocity to camera's vertical position (Y-axis)
+ controls.getObject().position.y += (velocity.y * delta);
+
+ // Check if camera's position is below a certain threshold (to simulate ground level)
+ if (controls.getObject().position.y < 10) {
+ velocity.y = 0; // Stop vertical movement
+ controls.getObject().position.y = 10; // Set position to ground level
+ canJump = true; // Allow the camera to jump again
+ }
+ }
+
+ // Update the previous time for the next frame
+ prevTime = time;
+
+ // Render the scene from the perspective of the camera
+ renderer.render(scene, camera);
+}
diff --git a/MFA_Demo/style.css b/MFA_Demo/style.css
new file mode 100644
index 0000000..7c25ffb
--- /dev/null
+++ b/MFA_Demo/style.css
@@ -0,0 +1,34 @@
+body {
+ margin: 0px;
+ height: 100vh;
+}
+
+canvas {
+ display: block;
+}
+
+p {
+ font-size: "font-size:36px";
+ color: floralwhite;
+}
+
+#blocker {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 1);
+}
+
+#instructions {
+ width: 100%;
+ height: 100%;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ text-align: center;
+ font-size: 14px;
+ cursor: pointer;
+}
\ No newline at end of file