Cowboy prototyping

This commit is contained in:
Danny McGee
2023-10-10 19:46:40 -04:00
parent c84fd31387
commit 86a948ef09
17 changed files with 1191 additions and 40 deletions
+2
View File
@@ -0,0 +1,2 @@
@fortawesome:registry=https://npm.fontawesome.com/
//npm.fontawesome.com/:_authToken=104779DB-FD41-4BEA-BDB4-B1845D758A79
Generated
+1
View File
@@ -2856,6 +2856,7 @@ name = "studio"
version = "0.1.0"
dependencies = [
"bevy",
"bitflags 2.4.0",
"console_error_panic_hook",
"parking_lot",
"wasm-bindgen",
+577
View File
@@ -9,6 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-pro": "^6.4.2",
"lit": "^2.8.0",
"lodash": "^4.17.21",
"tslib": "^2.3.0"
@@ -2387,6 +2388,14 @@
"node": ">=12"
}
},
"node_modules/@fortawesome/fontawesome-pro": {
"version": "6.4.2",
"resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-pro/-/6.4.2/fontawesome-pro-6.4.2.tgz",
"integrity": "sha512-b7VtzrgoASuCbnWJ1A17Sw3wemaFGukyBk0Y2ks/rgcnhlJ1uzdyv2uM3QbEaq9OgyZAKGJD1Tli6qHRI68zSQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -2536,6 +2545,17 @@
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
@@ -3671,6 +3691,28 @@
"@types/chai": "*"
}
},
"node_modules/@types/eslint": {
"version": "8.44.4",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.4.tgz",
"integrity": "sha512-lOzjyfY/D9QR4hY9oblZ76B90MYTB3RrQ4z2vBIJKj9ROCRqdkYl2gSUx1x1a4IWPjKJZLL4Aw1Zfay7eMnmnA==",
"dev": true,
"peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.5",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz",
"integrity": "sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==",
"dev": true,
"peer": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
@@ -3683,6 +3725,13 @@
"integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
"integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==",
"dev": true,
"peer": true
},
"node_modules/@types/lodash": {
"version": "4.14.199",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz",
@@ -3831,6 +3880,181 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
"integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
}
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
"integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
"integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6"
}
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"dev": true,
"peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"dev": true,
"peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
"integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/helper-wasm-section": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-opt": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6",
"@webassemblyjs/wast-printer": "1.11.6"
}
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
"integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
}
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
"integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6"
}
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
"integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
}
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
"integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true,
"peer": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true,
"peer": true
},
"node_modules/@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
@@ -3902,6 +4126,16 @@
"node": ">=0.4.0"
}
},
"node_modules/acorn-import-assertions": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"peer": true,
"peerDependencies": {
"acorn": "^8"
}
},
"node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
@@ -3948,6 +4182,16 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peer": true,
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -4504,6 +4748,16 @@
"node": ">=10"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
"integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6.0"
}
},
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -4606,6 +4860,13 @@
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
"peer": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -4889,6 +5150,20 @@
"once": "^1.4.0"
}
},
"node_modules/enhanced-resolve": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
"integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"dev": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
@@ -4922,6 +5197,13 @@
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-module-lexer": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz",
"integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==",
"dev": true,
"peer": true
},
"node_modules/esbuild": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
@@ -4977,6 +5259,30 @@
"node": ">=0.8.0"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"peer": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/eslint-scope/node_modules/estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@@ -5002,6 +5308,19 @@
"node": ">=0.10"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"peer": true,
"dependencies": {
"estraverse": "^5.2.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
@@ -5032,6 +5351,16 @@
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"dev": true
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5377,6 +5706,13 @@
"node": ">= 6"
}
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true,
"peer": true
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -5950,6 +6286,37 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"engines": {
"node": ">= 10.13.0"
}
},
"node_modules/jest-worker/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/jju": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
@@ -6113,6 +6480,16 @@
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6.11.5"
}
},
"node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
@@ -6260,6 +6637,13 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true,
"peer": true
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -6442,6 +6826,13 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"peer": true
},
"node_modules/node-addon-api": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
@@ -7109,6 +7500,16 @@
}
]
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"peer": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@@ -7432,6 +7833,25 @@
"node": ">=v12.22.7"
}
},
"node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
@@ -7471,6 +7891,16 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/serialize-javascript": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
"dev": true,
"peer": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7743,6 +8173,16 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/tar": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
@@ -7794,6 +8234,71 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/terser": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz",
"integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser-webpack-plugin": {
"version": "5.3.9",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
"integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
"jest-worker": "^27.4.5",
"schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.1",
"terser": "^5.16.8"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.1.0"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"esbuild": {
"optional": true
},
"uglify-js": {
"optional": true
}
}
},
"node_modules/terser/node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"peer": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -8492,6 +8997,20 @@
"wasm-pack": "run.js"
}
},
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
"dev": true,
"peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
@@ -8510,6 +9029,64 @@
"node": ">=12"
}
},
"node_modules/webpack": {
"version": "5.88.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
"integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
"dev": true,
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
"@webassemblyjs/ast": "^1.11.5",
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
"acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.15.0",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.9",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"watchpack": "^2.4.0",
"webpack-sources": "^3.2.3"
},
"bin": {
"webpack": "bin/webpack.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependenciesMeta": {
"webpack-cli": {
"optional": true
}
}
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
"peer": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/whatwg-encoding": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+1
View File
@@ -31,6 +31,7 @@
"wasm-pack": "^0.12.1"
},
"dependencies": {
"@fortawesome/fontawesome-pro": "^6.4.2",
"lit": "^2.8.0",
"lodash": "^4.17.21",
"tslib": "^2.3.0"
+85
View File
@@ -10,6 +10,91 @@
right: 16px;
}
.toolbar {
display: flex;
gap: 16px;
position: absolute;
top: 16px;
left: 50%;
transform: translateX(-50%);
mix-blend-mode: multiply;
}
.toolbar-group {
display: flex;
gap: 2px;
mix-blend-mode: multiply;
}
.btn-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
font-size: 15px;
padding: 10px 12px;
pointer-events: auto;
// TODO: Copypasta
background: #0006;
color: #FFFC;
backdrop-filter: blur(16px);
transition: {
property: background, color;
duration: 0.1s;
timing-function: linear;
}
mix-blend-mode: multiply;
&:first-child {
border: {
top-left-radius: 8px;
bottom-left-radius: 8px;
}
}
&:last-child {
border: {
top-right-radius: 8px;
bottom-right-radius: 8px;
}
}
&:hover {
background: #0008;
color: #FFF;
}
&:has(:checked) {
background: #7A2C34E3;
color: #FFF;
background-blend-mode: multiply;
&:hover {
background: #90363F;
}
}
&:has(:disabled) {
background: #0003 !important;
color: #FFF6 !important;
}
input {
display: block;
appearance: none;
border: 0;
margin: 0;
padding: 0;
opacity: 0;
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 1;
}
}
.help {
position: absolute;
top: 60px;
+115 -2
View File
@@ -1,14 +1,21 @@
import { on } from "@storyteller/framework";
import * as studio from "@storyteller/studio";
import { ViewportSize } from "@storyteller/studio";
import { TransformSpace, ViewportSize } from "@storyteller/studio";
import { LitElement, html, unsafeCSS } from "lit";
import { customElement, state } from "lit/decorators.js";
import "./button.element";
import "./help.element";
import "./icons/fa-circle-info.element";
import "./icon.element";
import styles from "./app.element.scss?inline";
import { match } from "@storyteller/utility";
// FIXME: Figure out how to generate the correct code from `wasm_bindgen`
enum InteractionTransform {
TRANSLATE = 0b0001,
ROTATE = 0b0010,
}
@customElement("app-root")
export class AppElement extends LitElement {
@@ -16,6 +23,12 @@ export class AppElement extends LitElement {
@state() showHelp = false;
@state() transformSpace: "local"|"world" = "local";
@state() translateGizmoEnabled = true;
@state() rotateGizmoEnabled = true;
@state() scaleGizmoEnabled = false;
#studioCanvas?: HTMLCanvasElement | null;
override connectedCallback(): void {
@@ -34,6 +47,58 @@ export class AppElement extends LitElement {
this.showHelp = !this.showHelp;
}
onSpaceChange(event: InputEvent): void {
const { value } = event.target as HTMLInputElement;
if (value === this.transformSpace)
return;
match (value, {
"local": () => studio.setTransformSpace(TransformSpace.Local),
"world": () => studio.setTransformSpace(TransformSpace.World),
_: () => {
console.warn(`Unknown transform space: "${value}"`);
return;
}
});
this.transformSpace = value as "local"|"world";
}
onGizmoToggled(event: InputEvent): void {
const { name, checked } = event.target as HTMLInputElement;
match (name, {
"translate": () => {
if (this.translateGizmoEnabled === checked)
return;
this.translateGizmoEnabled = checked;
},
"rotate": () => {
if (this.rotateGizmoEnabled === checked)
return;
this.rotateGizmoEnabled = checked;
},
"scale": () => {
if (this.scaleGizmoEnabled === checked)
return;
this.scaleGizmoEnabled = checked;
},
_: () => {
console.warn(`Unknown gizmo name: "${name}"`);
return;
}
});
let transform = 0;
if (this.translateGizmoEnabled) transform |= InteractionTransform.TRANSLATE;
if (this.rotateGizmoEnabled) transform |= InteractionTransform.ROTATE;
// TODO: Scale
studio.setInteractionTransform(transform);
}
#initStudio(): HTMLCanvasElement | null {
const width = window.innerWidth;
const height = window.innerHeight;
@@ -48,6 +113,54 @@ export class AppElement extends LitElement {
}
protected override render = () => html`
<!-- TODO: What's a "component"? -->
<div class="toolbar" role="toolbar">
<div class="toolbar-group" role="group">
<label class="btn-toggle">
<sts-icon icon=${"cube"}>Local</sts-icon>
<input type="radio"
name="transform-space"
value="local"
checked
@input=${this.onSpaceChange}
/>
</label>
<label class="btn-toggle">
<sts-icon icon="globe">Global</sts-icon>
<input type="radio"
name="transform-space"
value="world"
@input=${this.onSpaceChange}
/>
</label>
</div>
<div class="toolbar-group" role="group">
<label class="btn-toggle">
<sts-icon icon="up-down-left-right">Translate</sts-icon>
<input type="checkbox"
name="translate"
checked
@input=${this.onGizmoToggled}
/>
</label>
<label class="btn-toggle">
<sts-icon icon="rotate">Rotate</sts-icon>
<input type="checkbox"
name="rotate"
checked
@input=${this.onGizmoToggled}
/>
</label>
<label class="btn-toggle">
<sts-icon icon="scale">Scale</sts-icon>
<input type="checkbox"
name="scale"
disabled
/>
</label>
</div>
</div>
<sts-button class="btn-help"
icon="circle-info"
@click=${this.toggleHelp}
+5 -6
View File
@@ -1,6 +1,5 @@
import { match } from "@storyteller/utility";
import { on } from "@storyteller/framework";
import { LitElement, html, unsafeCSS } from "lit";
import { LitElement, html, nothing, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators.js";
import styles from "./button.element.scss?inline";
@@ -21,10 +20,10 @@ export class ButtonElement extends LitElement {
}
protected override render = () => html`
${match (this.icon, {
"circle-info": () => html`<fa-circle-info class="icon"></fa-circle-info>`,
_: () => null,
})}
${this.icon
? html`<sts-icon icon=${this.icon} aria-hidden="true"></sts-icon>`
: nothing}
<slot></slot>
`
}
+28
View File
@@ -0,0 +1,28 @@
:host {
display: flex;
width: auto;
align-items: center;
justify-content: center;
height: 1em;
}
.a11y-label {
position: absolute;
overflow: hidden;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
clip: rect(1px, 1px, 1px, 1px);
border: 0;
white-space: nowrap;
}
.svg-wrapper {
display: flex;
}
svg {
height: 1em;
fill: currentColor;
}
+69
View File
@@ -0,0 +1,69 @@
import { match } from "@storyteller/utility";
import { ElementPart, LitElement, html, nothing, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Directive, directive } from "lit/directive.js";
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
import fasCircleInfo from "@fortawesome/fontawesome-pro/svgs/solid/circle-info.svg?raw";
import fasCube from "@fortawesome/fontawesome-pro/svgs/solid/cube.svg?raw";
import fasGlobe from "@fortawesome/fontawesome-pro/svgs/solid/globe.svg?raw";
import fasUpDownLeftRight from "@fortawesome/fontawesome-pro/svgs/solid/up-down-left-right.svg?raw";
import fasRotate from "@fortawesome/fontawesome-pro/svgs/solid/rotate.svg?raw";
import fasScale from "@fortawesome/fontawesome-pro/svgs/solid/arrow-up-right-and-arrow-down-left-from-center.svg?raw";
import styles from "./icon.element.scss?inline";
@customElement("sts-icon")
export class IconElement extends LitElement {
static override styles = unsafeCSS(styles);
@property() icon = "";
protected override render = () => html`
<span class="a11y-label">
<slot></slot>
</span>
<div class="svg-wrapper" role="presentation">
${match (this.icon, {
"circle-info": () => renderSvg(fasCircleInfo),
"cube": () => renderSvg(fasCube),
"globe": () => renderSvg(fasGlobe),
"up-down-left-right": () => renderSvg(fasUpDownLeftRight),
"rotate": () => renderSvg(fasRotate),
"scale": () => renderSvg(fasScale),
_: () => nothing,
})}
</div>
`;
}
// TODO: Consider moving this
function renderSvg(fullSvg: string) {
console.log("parsing:", fullSvg);
const [, attrsString, body] = fullSvg.match(/<svg(.+?)>(.+?)<\/svg>/)!;
const attrs = Object.fromEntries(
Array.from(attrsString.matchAll(/([-:_a-zA-Z0-9]+)(?:=['"]?([^'"]*)['"]?)/g))
.map(([, attr, value]) => [attr, value])
);
return html`
<svg ${spread(attrs)}>
${unsafeSVG(body)}
</svg>
`;
}
// TODO: Definitely move this
const spread = directive(class extends Directive {
override render(_: Record<string, string>) {
return nothing;
}
override update(part: ElementPart, [attrs]: [Record<string, string>]) {
for (let [key, value] of Object.entries(attrs)) {
part.element.setAttribute(key, value);
}
return part;
}
});
+4
View File
@@ -0,0 +1,4 @@
declare module "*.svg?raw" {
const svg: string;
export default svg;
}
+1
View File
@@ -16,6 +16,7 @@ path = "src/main.rs"
wasm-bindgen = { version = "0.2.84" }
console_error_panic_hook = { version = "0.1.7", optional = true }
parking_lot = "0.12.1"
bitflags = "2.4.0"
[features]
wasm = ["dep:console_error_panic_hook"]
+72
View File
@@ -0,0 +1,72 @@
use bevy::{prelude::*, window::PrimaryWindow};
use super::{InteractionMode, InteractionTransform, Selected, TransformSpace};
pub(super) fn draw(
q_selection: Query<&GlobalTransform, With<Selected>>,
q_camera_xform: Query<&Transform, With<Camera>>,
q_window: Query<&Window, With<PrimaryWindow>>,
interaction_mode: Res<InteractionMode>,
mut gizmos: Gizmos,
mut config: ResMut<GizmoConfig>,
) {
if !q_selection.is_empty() {
// TODO: Use an app state or something so we only have to set this up once
let scale_factor = q_window.single().scale_factor() as f32;
config.line_width = 4. * scale_factor;
config.line_perspective = false;
config.depth_bias = -1.;
let InteractionMode { transform, space } = *interaction_mode;
let camera_xform = q_camera_xform.single();
// TODO: We only want to render one gizmo -- this is just for testing
for world_xform in q_selection.iter() {
let xform = world_xform.compute_transform();
// Note: Scaling the gizmo size by this isn't perfect due to
// distortion from the perspective projection. It bugged me at
// first, but even Blender exhibits this type of gizmo distortion if
// you dial the camera's focal length way down, so I'm gonna call it
// good enough.
let dist = Vec3::distance(camera_xform.translation, xform.translation);
if transform.contains(InteractionTransform::TRANSLATE) {
draw_translate_gizmo(&xform, &mut gizmos, space, dist / 14.);
}
if transform.contains(InteractionTransform::ROTATE) {
draw_rotate_gizmo(&xform, &mut gizmos, space, dist / 20.);
}
}
}
}
#[inline]
fn draw_translate_gizmo(xform: &Transform, gizmos: &mut Gizmos, space: TransformSpace, size: f32) {
let origin = xform.translation;
let basis = match space {
TransformSpace::Local => xform,
TransformSpace::World => &Transform::IDENTITY,
};
let mut draw_axis = |direction: Vec3, color: Color| {
let end = origin + direction * size;
gizmos.line(origin, end, color);
};
draw_axis(basis.local_x(), Color::RED);
draw_axis(basis.local_y(), Color::GREEN);
draw_axis(basis.local_z(), Color::BLUE);
}
#[inline]
fn draw_rotate_gizmo(xform: &Transform, gizmos: &mut Gizmos, space: TransformSpace, radius: f32) {
let origin = xform.translation;
let basis = match space {
TransformSpace::Local => xform,
TransformSpace::World => &Transform::IDENTITY,
};
gizmos.circle(origin, basis.local_x(), radius, Color::RED);
gizmos.circle(origin, basis.local_y(), radius, Color::GREEN);
gizmos.circle(origin, basis.local_z(), radius, Color::BLUE);
}
+130
View File
@@ -0,0 +1,130 @@
use bevy::prelude::*;
use bitflags::bitflags;
use wasm_bindgen::prelude::*;
mod gizmos;
pub struct InteractionPlugin;
impl Plugin for InteractionPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(SelectionMode::default())
.insert_resource(InteractionMode::default())
.add_systems(Update, gizmos::draw);
app.add_systems(
PreUpdate,
(wasm::update_selection_mode, wasm::update_interaction_mode),
);
}
}
#[derive(Component, Clone, Copy)]
pub struct Selectable(SelectionChannel);
#[derive(Clone, Copy)]
#[wasm_bindgen]
pub enum SelectionChannel {
Objects,
Bones,
Widgets,
}
impl Default for SelectionChannel {
fn default() -> Self {
Self::Objects
}
}
#[derive(Component, Clone, Copy)]
pub struct Selected;
#[derive(Resource, Default)]
pub struct SelectionMode(SelectionChannel);
#[derive(Resource)]
pub struct InteractionMode {
pub transform: InteractionTransform,
pub space: TransformSpace,
}
impl Default for InteractionMode {
fn default() -> Self {
Self {
transform: InteractionTransform::TRANSLATE | InteractionTransform::ROTATE,
space: default(),
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
#[wasm_bindgen]
pub struct InteractionTransform(u8);
bitflags! {
impl InteractionTransform: u8 {
const TRANSLATE = 0b0001;
const ROTATE = 0b0010;
}
}
#[derive(Clone, Copy, Debug)]
#[wasm_bindgen]
pub enum TransformSpace {
Local,
World,
}
impl Default for TransformSpace {
fn default() -> Self {
Self::Local
}
}
// #[cfg(target_arch = "wasm32")]
pub mod wasm {
use bevy::prelude::*;
use wasm_bindgen::prelude::*;
use crate::wasm::{self, WasmMessageBuffer};
use super::{
InteractionMode, InteractionTransform, SelectionChannel, SelectionMode, TransformSpace,
};
static SELECT_MODE_BUF: WasmMessageBuffer<SelectionChannel> = WasmMessageBuffer::new();
static INTERACTION_TRANSFORM_BUF: WasmMessageBuffer<InteractionTransform> =
WasmMessageBuffer::new();
static INTERACTION_SPACE_BUF: WasmMessageBuffer<TransformSpace> = WasmMessageBuffer::new();
#[wasm_bindgen(js_name = setSelectionMode)]
pub fn set_selection_mode(mode: SelectionChannel) {
wasm::write_message!(SELECT_MODE_BUF, mode);
}
#[wasm_bindgen(js_name = setInteractionTransform)]
pub fn set_interaction_transform(flags: u8) {
wasm::write_message!(INTERACTION_TRANSFORM_BUF, InteractionTransform(flags));
}
#[wasm_bindgen(js_name = setTransformSpace)]
pub fn set_transform_space(space: TransformSpace) {
wasm::write_message!(INTERACTION_SPACE_BUF, space);
}
pub(super) fn update_selection_mode(mut selection_mode: ResMut<SelectionMode>) {
wasm::read_buffer!(SELECT_MODE_BUF, mode => {
selection_mode.0 = mode;
});
}
pub(super) fn update_interaction_mode(mut interaction_mode: ResMut<InteractionMode>) {
wasm::read_buffer!(INTERACTION_TRANSFORM_BUF, transform => {
interaction_mode.transform = transform;
});
wasm::read_buffer!(INTERACTION_SPACE_BUF, space => {
interaction_mode.space = space;
});
}
}
+22 -12
View File
@@ -2,7 +2,13 @@
use bevy::prelude::*;
use input::StudioInputPlugin;
use orbit_cam::{CameraMotionSystem, OrbitCam, OrbitCamPlugin};
use interaction::InteractionPlugin;
#[rustfmt::skip]
use orbit_cam::{
// CameraMotionSystem,
// OrbitCam,
OrbitCamPlugin,
};
use skybox::SkyboxPlugin;
use test_scene::TestScenePlugin;
use ui::UiLayerPlugin;
@@ -10,11 +16,14 @@ use viewport::ViewportSize;
use wasm_bindgen::prelude::*;
mod input;
mod interaction;
mod orbit_cam;
mod skybox;
mod test_scene;
mod ui;
mod viewport;
// #[cfg(target_arch = "wasm32")]
mod wasm;
#[wasm_bindgen]
pub fn init(viewport_size: ViewportSize) {
@@ -36,24 +45,25 @@ pub fn init(viewport_size: ViewportSize) {
..default()
}),
StudioInputPlugin,
InteractionPlugin,
OrbitCamPlugin,
TestScenePlugin,
SkyboxPlugin,
UiLayerPlugin,
))
.add_systems(Update, draw_world_axes.after(CameraMotionSystem))
// .add_systems(Update, draw_world_axes.after(CameraMotionSystem))
.run();
}
fn draw_world_axes(mut gizmos: Gizmos, q: Query<&OrbitCam>) {
let &OrbitCam { focus, radius, .. } = q.single();
// fn draw_world_axes(mut gizmos: Gizmos, q: Query<&OrbitCam>) {
// let &OrbitCam { focus, radius, .. } = q.single();
let mut draw_axis = |direction: Vec3, color: Color| {
let end = focus + direction * radius / 15.;
gizmos.line(focus, end, color);
};
// let mut draw_axis = |direction: Vec3, color: Color| {
// let end = focus + direction * radius / 15.;
// gizmos.line(focus, end, color);
// };
draw_axis(Vec3::X, Color::RED);
draw_axis(Vec3::Y, Color::GREEN);
draw_axis(Vec3::Z, Color::BLUE);
}
// draw_axis(Vec3::X, Color::RED);
// draw_axis(Vec3::Y, Color::GREEN);
// draw_axis(Vec3::Z, Color::BLUE);
// }
+32 -5
View File
@@ -1,3 +1,4 @@
use bevy::math::vec3;
#[rustfmt::skip]
use bevy::{
pbr::NotShadowCaster,
@@ -6,7 +7,10 @@ use bevy::{
render::render_resource::{AsBindGroup, ShaderRef},
};
use crate::orbit_cam::{CameraMotionSystem, OrbitCam};
use crate::{
interaction::Selected,
orbit_cam::{CameraMotionSystem, OrbitCam},
};
pub struct TestScenePlugin;
@@ -15,6 +19,7 @@ impl Plugin for TestScenePlugin {
app.insert_resource(ClearColor(Color::hsl(0.0, 0.0, 0.25)))
.add_plugins(MaterialPlugin::<ProceduralGridMaterial>::default())
.add_systems(Startup, spawn_scene)
.add_systems(Update, right_round_round_round)
.add_systems(PostUpdate, move_plane_with_camera.after(CameraMotionSystem));
}
}
@@ -22,6 +27,9 @@ impl Plugin for TestScenePlugin {
#[derive(Component)]
struct WorldGridPlane;
#[derive(Component)]
struct Spin(Vec3); // TODO: Debug
fn spawn_scene(
mut cmd: Commands,
assets: Res<AssetServer>,
@@ -29,10 +37,18 @@ fn spawn_scene(
mut mat_proc_grid: ResMut<Assets<ProceduralGridMaterial>>,
) {
// Skeletal mesh
cmd.spawn(SceneBundle {
scene: assets.load("gltf/base_human_male.gltf#Scene0"),
..default()
});
cmd.spawn((SpatialBundle::default(), Spin(Vec3::Y * 0.5)))
.with_children(|parent| {
parent.spawn((
SceneBundle {
scene: assets.load("gltf/base_human_male.gltf#Scene0"),
transform: Transform::from_translation(vec3(0., 0., 2.5)),
..default()
},
Selected,
Spin(vec3(0., -1.5, 1.)),
));
});
// Plane
let plane = meshes.add(Mesh::from(shape::Plane {
@@ -79,6 +95,17 @@ fn move_plane_with_camera(
}
}
fn right_round_round_round(mut q_spin: Query<(&mut Transform, &Spin)>, time: Res<Time>) {
let dt = time.delta_seconds();
for (mut xform, spin) in q_spin.iter_mut() {
let speed = spin.0.length();
let axis = spin.0 / speed;
xform.rotate_local_axis(axis, std::f32::consts::PI * speed * dt);
}
}
#[derive(AsBindGroup, Clone, TypePath, TypeUuid)]
#[uuid = "CE331A13-F247-4F5B-A75E-670B2BD5776A"]
struct ProceduralGridMaterial {}
+8 -15
View File
@@ -45,24 +45,20 @@ impl fmt::Debug for ViewportSize {
}
}
#[cfg(target_arch = "wasm32")]
// #[cfg(target_arch = "wasm32")]
pub mod wasm {
use std::{cell::RefCell, sync::OnceLock};
use bevy::prelude::*;
use parking_lot::ReentrantMutex;
use wasm_bindgen::prelude::*;
use crate::wasm::{self, WasmMessageBuffer};
use super::ViewportSize;
static RESIZE_BUF: OnceLock<ReentrantMutex<RefCell<Option<ViewportSize>>>> = OnceLock::new();
static RESIZE_BUF: WasmMessageBuffer<ViewportSize> = WasmMessageBuffer::new();
#[wasm_bindgen]
pub fn resize(viewport_size: ViewportSize) {
let buffer = RESIZE_BUF.get_or_init(|| ReentrantMutex::new(RefCell::new(None)));
if let Some(lock) = buffer.try_lock() {
*lock.borrow_mut() = Some(viewport_size);
}
wasm::write_message!(RESIZE_BUF, viewport_size);
}
pub struct ViewportPlugin;
@@ -89,11 +85,8 @@ pub mod wasm {
}
fn dispatch_resize_events(mut events: EventWriter<ViewportResizeEvent>) {
let buffer = RESIZE_BUF.get_or_init(|| ReentrantMutex::new(RefCell::new(None)));
if let Some(lock) = buffer.try_lock() {
if let Some(resize) = lock.borrow_mut().take() {
events.send(ViewportResizeEvent(resize));
}
}
wasm::read_buffer!(RESIZE_BUF, resize => {
events.send(ViewportResizeEvent(resize));
});
}
}
+39
View File
@@ -0,0 +1,39 @@
use std::{cell::RefCell, sync::OnceLock};
use parking_lot::ReentrantMutex;
pub(crate) type WasmMessageBuffer<T> = OnceLock<ReentrantMutex<RefCell<Option<T>>>>;
macro_rules! get_buffer {
($buffer:ident) => {
$buffer.get_or_init(|| {
::parking_lot::ReentrantMutex::new(::std::cell::RefCell::new(
::core::option::Option::None,
))
})
};
}
macro_rules! write_message {
($buffer:ident, $expr:expr) => {{
let buffer = crate::wasm::get_buffer!($buffer);
if let Some(lock) = buffer.try_lock() {
*lock.borrow_mut() = Some($expr);
}
}};
}
macro_rules! read_buffer {
($buffer:ident, $msg:ident => $stmt:stmt) => {{
let buffer = crate::wasm::get_buffer!($buffer);
if let Some(lock) = buffer.try_lock() {
if let Some($msg) = lock.borrow_mut().take() {
$stmt
}
}
}};
}
pub(crate) use get_buffer;
pub(crate) use read_buffer;
pub(crate) use write_message;