diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 642fc76..0000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..713d500
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+.env
diff --git a/README.md b/README.md
index 3adb1f1..d1ecd72 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ An interactive web app built with threejs, mediapipe computer vision, web speech
- Say "drag", "rotate", "scale", or "animate" to change the interaction mode
- Pinch fingers to control the 3D model
-- Drag/drop a new 3D model onto the page to import it (GLTF format only for now)
+- Drag/drop or import a new 3D model (.gltf or .glb) onto the page to import it
[Video](https://x.com/measure_plan/status/1929900748235550912) | [Live Demo](https://collidingscopes.github.io/3d-model-playground/)
@@ -32,11 +32,19 @@ git clone https://github.com/collidingScopes/3d-model-playground
# Navigate to the project directory
cd 3d-model-playground
-# Serve with your preferred method (example using Python)
+# Install dependencies
+npm install
+
+# Start the dev server (uses vite)
+npm run dev
+
+# Or serve with your preferred method (example using Python)
python -m http.server
```
-Then navigate to `http://localhost:8000` in your browser.
+Then navigate to `http://localhost:8000` or the port printed by `npm run dev` in your browser.
+
+Vite handles the dev/preview server, so any scripts such as `npm run dev` or `npm run preview` rely on it.
## License
@@ -71,4 +79,4 @@ If you found this tool useful, feel free to buy me a coffee.
My name is Alan, and I enjoy building open source software for computer vision, games, and more. This would be much appreciated during late-night coding sessions!
-[](https://www.buymeacoffee.com/stereoDrift)
\ No newline at end of file
+[](https://www.buymeacoffee.com/stereoDrift)
diff --git a/SpeechManager.js b/SpeechManager.js
index 90f7921..a1033ae 100644
--- a/SpeechManager.js
+++ b/SpeechManager.js
@@ -358,4 +358,4 @@ export var SpeechManager = /*#__PURE__*/ function() {
}
]);
return SpeechManager;
-}();
\ No newline at end of file
+}();
diff --git a/game.js b/game.js
index 88f8394..457a15c 100644
--- a/game.js
+++ b/game.js
@@ -218,6 +218,9 @@ export var Game = /*#__PURE__*/ function() {
this.speechManager = null;
this.speechBubble = null;
this.speechBubbleTimeout = null;
+ this.openAIApiKey = window.OPENAI_API_KEY || null;
+ this.importInput = null;
+ this.importButton = null;
this.isSpeechActive = false; // Track if speech recognition is active for styling
this.grabbingHandIndex = -1; // -1: no hand, 0: first hand, 1: second hand grabbing
this.pickedUpModel = null; // Reference to the model being dragged
@@ -395,6 +398,36 @@ export var Game = /*#__PURE__*/ function() {
this.speechBubble.style.pointerEvents = 'none'; // Not interactive
this.speechBubble.innerHTML = "..."; // Default text
this.renderDiv.appendChild(this.speechBubble);
+ // Import button and file input
+ this.importInput = document.createElement("input");
+ this.importInput.type = "file";
+ this.importInput.accept = ".gltf,.glb,model/gltf+json,model/gltf-binary";
+ this.importInput.style.display = "none";
+ this.importInput.addEventListener("change", function(event) {
+ if (event.target.files && event.target.files[0]) {
+ _this._loadDroppedModel(event.target.files[0]);
+ event.target.value = "";
+ }
+ });
+ this.importButton = document.createElement("button");
+ this.importButton.innerText = "Import Model";
+ this.importButton.style.position = "absolute";
+ this.importButton.style.top = "60px";
+ this.importButton.style.left = "50%";
+ this.importButton.style.transform = "translateX(-50%)";
+ this.importButton.style.zIndex = "30";
+ this.importButton.style.padding = "12px 24px";
+ this.importButton.style.fontSize = "20px";
+ this.importButton.style.border = "2px solid black";
+ this.importButton.style.borderRadius = "4px";
+ this.importButton.style.cursor = "pointer";
+ this.importButton.style.background = "white";
+ this.importButton.style.boxShadow = "2px 2px 0px black";
+ this.importButton.addEventListener("click", function() {
+ return _this.importInput.click();
+ });
+ this.renderDiv.appendChild(this.importButton);
+ this.renderDiv.appendChild(this.importInput);
// Animation buttons container
this.animationButtonsContainer = document.createElement('div');
this.animationButtonsContainer.id = 'animation-buttons-container';
@@ -432,8 +465,8 @@ export var Game = /*#__PURE__*/ function() {
var button = document.createElement('button');
button.innerText = mode;
button.id = "interaction-mode-".concat(mode.toLowerCase());
- button.style.padding = '10px 22px'; // Increased padding
- button.style.fontSize = '18px'; // Increased font size further
+ button.style.padding = '16px 28px'; // Increased padding
+ button.style.fontSize = 'clamp(22px, 4vw, 32px)'; // Increased font size further
button.style.border = '2px solid black'; // Consistent black border
button.style.borderRadius = '4px'; // Sharper corners
button.style.cursor = 'pointer';
@@ -1517,6 +1550,9 @@ export var Game = /*#__PURE__*/ function() {
if (finalTranscript) {
_this.speechBubble.innerHTML = finalTranscript;
_this.speechBubble.style.opacity = '1';
+ _this._sendAIMessage(finalTranscript).then(function(reply){
+ _this.speechBubble.innerHTML = reply;
+ }).catch(function(err){ console.error("AI error", err); });
_this.speechBubbleTimeout = setTimeout(function() {
_this.speechBubble.innerHTML = "...";
_this.speechBubble.style.opacity = '0.7';
@@ -1598,6 +1634,31 @@ export var Game = /*#__PURE__*/ function() {
}
}
},
+ {
+ key: "_sendAIMessage",
+ value: function _sendAIMessage(message) {
+ if (!this.openAIApiKey) {
+ return Promise.resolve("(AI API key not set)");
+ }
+ return fetch("https://api.openai.com/v1/chat/completions", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": "Bearer " + this.openAIApiKey
+ },
+ body: JSON.stringify({
+ model: "gpt-3.5-turbo",
+ messages: [{ role: "user", content: message }]
+ })
+ }).then(function(res){ return res.json(); }).then(function(data){
+ if (data && data.choices && data.choices[0] && data.choices[0].message) {
+ return data.choices[0].message.content.trim();
+ } else {
+ throw new Error("Invalid AI response");
+ }
+ });
+ }
+ },
{
key: "_setInteractionMode",
value: function _setInteractionMode(mode) {
@@ -1889,4 +1950,4 @@ export var Game = /*#__PURE__*/ function() {
}
]);
return Game;
-}();
\ No newline at end of file
+}();
diff --git a/index.html b/index.html
index 06f1e0b..9934ad2 100644
--- a/index.html
+++ b/index.html
@@ -24,12 +24,11 @@
-
-