Text Generation
MLX
Safetensors
qwen3_5
quantized
mixed-precision
4bit
8bit
optiq
apple-silicon
qwen3.5
three.js
3d
game-assets
conversational
4-bit precision
Instructions to use mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit") prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( messages, add_generation_prompt=True ) text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Pi
How to use mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit"
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit" } ] } } }Run Pi
# Start Pi in your project directory: pi
- MLX LM
How to use mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Interactive chat REPL mlx_lm.chat --model "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit"
Run an OpenAI-compatible server
# Install MLX LM uv tool install mlx-lm # Start the server mlx_lm.server --model "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit" # Calling the OpenAI-compatible server with curl curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit", "messages": [ {"role": "user", "content": "Hello"} ] }' - Hermes Agent
How to use mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit with Hermes Agent:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit"
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit"
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit" \ --custom-provider-id mlx-lm \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Download examples/potions.html from mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit: direct link, hf CLI and curl.
- Browser
- Download file 17.3 kB
-
https://huggingface.co/mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit/resolve/main/examples/potions.html
- Command line
-
hf download hf://mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit/examples/potions.html
-
curl -L -o potions.html https://huggingface.co/mlx-community/Qwen3.5-4B-3D-Coder-OptiQ-4bit/resolve/main/examples/potions.html
17.3 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Three Potion Bottles</title> | |
| <style> | |
| :root { | |
| color-scheme: light; | |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, | |
| "Segoe UI", sans-serif; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| html, | |
| body { | |
| width: 100%; | |
| height: 100%; | |
| margin: 0; | |
| overflow: hidden; | |
| background: #dfe5df; | |
| } | |
| #stage { | |
| position: fixed; | |
| inset: 0; | |
| } | |
| canvas { | |
| display: block; | |
| width: 100%; | |
| height: 100%; | |
| touch-action: none; | |
| cursor: grab; | |
| outline: none; | |
| } | |
| canvas.dragging { | |
| cursor: grabbing; | |
| } | |
| .hud { | |
| position: fixed; | |
| top: 24px; | |
| left: 26px; | |
| z-index: 2; | |
| max-width: min(360px, calc(100vw - 52px)); | |
| padding: 16px 18px; | |
| border: 1px solid rgba(255, 255, 255, 0.58); | |
| border-radius: 16px; | |
| background: rgba(250, 248, 240, 0.72); | |
| box-shadow: 0 12px 36px rgba(49, 58, 51, 0.12); | |
| backdrop-filter: blur(12px); | |
| pointer-events: none; | |
| } | |
| .eyebrow { | |
| margin-bottom: 5px; | |
| color: #718078; | |
| font-size: 10px; | |
| font-weight: 800; | |
| letter-spacing: 0.18em; | |
| text-transform: uppercase; | |
| } | |
| h1 { | |
| margin: 0; | |
| color: #303b36; | |
| font-size: clamp(25px, 4vw, 38px); | |
| font-weight: 750; | |
| letter-spacing: -0.045em; | |
| } | |
| .hud p { | |
| margin: 8px 0 0; | |
| color: #69746d; | |
| font-size: 13px; | |
| } | |
| .controls { | |
| position: fixed; | |
| right: 22px; | |
| bottom: 22px; | |
| z-index: 3; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 10px 12px; | |
| border: 1px solid rgba(255, 255, 255, 0.62); | |
| border-radius: 12px; | |
| background: rgba(250, 248, 240, 0.76); | |
| box-shadow: 0 10px 30px rgba(49, 58, 51, 0.12); | |
| backdrop-filter: blur(10px); | |
| } | |
| .status { | |
| color: #738078; | |
| font-size: 11px; | |
| font-weight: 700; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| } | |
| button { | |
| border: 0; | |
| border-radius: 8px; | |
| padding: 9px 12px; | |
| background: #415047; | |
| color: #fffdf5; | |
| font: inherit; | |
| font-size: 12px; | |
| font-weight: 700; | |
| cursor: pointer; | |
| transition: transform 150ms ease, background 150ms ease; | |
| } | |
| button:hover { | |
| background: #526157; | |
| transform: translateY(-1px); | |
| } | |
| button:active { | |
| transform: translateY(0); | |
| } | |
| @media (max-width: 600px) { | |
| .hud { | |
| top: 14px; | |
| left: 14px; | |
| padding: 13px 15px; | |
| } | |
| .hud p { | |
| max-width: 230px; | |
| font-size: 12px; | |
| } | |
| .controls { | |
| right: 14px; | |
| bottom: 14px; | |
| } | |
| .status { | |
| display: none; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <main id="stage" aria-label="Interactive three-dimensional potion bottle scene"></main> | |
| <section class="hud"> | |
| <div class="eyebrow">Interactive 3D asset</div> | |
| <h1>Three Potions</h1> | |
| <p>Drag to orbit · Scroll or pinch to zoom · Double-click to reset</p> | |
| </section> | |
| <div class="controls"> | |
| <span class="status" id="status">Auto spin</span> | |
| <button id="pause" type="button">Pause rotation</button> | |
| </div> | |
| <script type="module"> | |
| import * as THREE from "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.module.min.js"; | |
| const stage = document.querySelector("#stage"); | |
| const pauseButton = document.querySelector("#pause"); | |
| const status = document.querySelector("#status"); | |
| // --------------------------------------------------------------------- | |
| // Renderer, scene, and camera | |
| // --------------------------------------------------------------------- | |
| const renderer = new THREE.WebGLRenderer({ | |
| antialias: true, | |
| powerPreference: "high-performance" | |
| }); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| renderer.outputColorSpace = THREE.SRGBColorSpace; | |
| renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
| renderer.toneMappingExposure = 1.08; | |
| renderer.domElement.setAttribute( | |
| "aria-label", | |
| "Three colored potion bottles with corks resting on a wooden tray" | |
| ); | |
| stage.appendChild(renderer.domElement); | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0xdfe5df); | |
| scene.fog = new THREE.Fog(0xdfe5df, 13, 30); | |
| const camera = new THREE.PerspectiveCamera( | |
| 38, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 100 | |
| ); | |
| const controls = { | |
| yaw: 0.58, | |
| pitch: 0.52, | |
| distance: 8.4, | |
| target: new THREE.Vector3(0, 1.08, 0) | |
| }; | |
| function updateCamera() { | |
| const horizontalDistance = | |
| controls.distance * Math.cos(controls.pitch); | |
| camera.position.set( | |
| controls.target.x + Math.sin(controls.yaw) * horizontalDistance, | |
| controls.target.y + Math.sin(controls.pitch) * controls.distance, | |
| controls.target.z + Math.cos(controls.yaw) * horizontalDistance | |
| ); | |
| camera.lookAt(controls.target); | |
| } | |
| updateCamera(); | |
| // --------------------------------------------------------------------- | |
| // Lighting | |
| // --------------------------------------------------------------------- | |
| scene.add(new THREE.HemisphereLight(0xf8fcff, 0x8a6848, 2.1)); | |
| const keyLight = new THREE.DirectionalLight(0xfff1d7, 3.2); | |
| keyLight.position.set(-4.5, 8, 5.5); | |
| keyLight.castShadow = true; | |
| keyLight.shadow.mapSize.set(2048, 2048); | |
| keyLight.shadow.camera.left = -7; | |
| keyLight.shadow.camera.right = 7; | |
| keyLight.shadow.camera.top = 7; | |
| keyLight.shadow.camera.bottom = -7; | |
| keyLight.shadow.camera.near = 0.5; | |
| keyLight.shadow.camera.far = 20; | |
| keyLight.shadow.normalBias = 0.025; | |
| keyLight.target.position.set(0, 0.8, 0); | |
| scene.add(keyLight, keyLight.target); | |
| const fillLight = new THREE.DirectionalLight(0xc5dcff, 1.25); | |
| fillLight.position.set(5, 3.5, -4); | |
| scene.add(fillLight); | |
| // --------------------------------------------------------------------- | |
| // Materials and geometry helpers | |
| // --------------------------------------------------------------------- | |
| function createWoodTexture() { | |
| const canvas = document.createElement("canvas"); | |
| canvas.width = 512; | |
| canvas.height = 512; | |
| const context = canvas.getContext("2d"); | |
| context.fillStyle = "#8a542f"; | |
| context.fillRect(0, 0, 512, 512); | |
| let seed = 9137; | |
| function random() { | |
| seed = (seed * 16807) % 2147483647; | |
| return (seed - 1) / 2147483646; | |
| } | |
| for (let i = 0; i < 100; i++) { | |
| const y = random() * 512; | |
| const amplitude = 1 + random() * 5; | |
| const frequency = 0.008 + random() * 0.018; | |
| context.beginPath(); | |
| context.moveTo(0, y); | |
| for (let x = 0; x <= 512; x += 8) { | |
| const wave = Math.sin(x * frequency + i) * amplitude; | |
| const noise = (random() - 0.5) * 2; | |
| context.lineTo(x, y + wave + noise); | |
| } | |
| context.strokeStyle = `rgba(48, 23, 10, ${0.08 + random() * 0.16})`; | |
| context.lineWidth = 0.5 + random() * 2.2; | |
| context.stroke(); | |
| } | |
| for (let i = 0; i < 8; i++) { | |
| const x = random() * 512; | |
| const y = random() * 512; | |
| const radiusX = 8 + random() * 24; | |
| const radiusY = 2 + random() * 7; | |
| context.beginPath(); | |
| context.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI * 2); | |
| context.strokeStyle = "rgba(45, 22, 9, 0.18)"; | |
| context.lineWidth = 2; | |
| context.stroke(); | |
| } | |
| const texture = new THREE.CanvasTexture(canvas); | |
| texture.colorSpace = THREE.SRGBColorSpace; | |
| texture.wrapS = THREE.RepeatWrapping; | |
| texture.wrapT = THREE.RepeatWrapping; | |
| texture.repeat.set(1.5, 1.5); | |
| texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); | |
| return texture; | |
| } | |
| const woodTexture = createWoodTexture(); | |
| const materials = { | |
| wood: new THREE.MeshStandardMaterial({ | |
| map: woodTexture, | |
| color: 0xffffff, | |
| roughness: 0.78 | |
| }), | |
| darkWood: new THREE.MeshStandardMaterial({ | |
| map: woodTexture, | |
| color: 0x6d4127, | |
| roughness: 0.86 | |
| }), | |
| cork: new THREE.MeshStandardMaterial({ | |
| color: 0xb9864e, | |
| roughness: 0.82 | |
| }), | |
| corkTop: new THREE.MeshStandardMaterial({ | |
| color: 0xd0a264, | |
| roughness: 0.72 | |
| }), | |
| label: new THREE.MeshStandardMaterial({ | |
| color: 0xf3e3bd, | |
| roughness: 0.88 | |
| }) | |
| }; | |
| function addMesh( | |
| geometry, | |
| material, | |
| parent, | |
| name, | |
| position = [0, 0, 0], | |
| rotation = [0, 0, 0], | |
| castShadow = true, | |
| receiveShadow = true | |
| ) { | |
| const object = new THREE.Mesh(geometry, material); | |
| object.name = name; | |
| object.position.set(...position); | |
| object.rotation.set(...rotation); | |
| object.castShadow = castShadow; | |
| object.receiveShadow = receiveShadow; | |
| parent.add(object); | |
| return object; | |
| } | |
| // --------------------------------------------------------------------- | |
| // Ground plane | |
| // --------------------------------------------------------------------- | |
| const ground = addMesh( | |
| new THREE.PlaneGeometry(40, 40), | |
| new THREE.MeshStandardMaterial({ | |
| color: 0xd9dfda, | |
| roughness: 1 | |
| }), | |
| scene, | |
| "Plain ground plane", | |
| [0, -0.012, 0], | |
| [-Math.PI / 2, 0, 0], | |
| false, | |
| true | |
| ); | |
| // --------------------------------------------------------------------- | |
| // Turntable and wooden tray | |
| // --------------------------------------------------------------------- | |
| const turntable = new THREE.Group(); | |
| turntable.name = "Rotating turntable"; | |
| scene.add(turntable); | |
| addMesh( | |
| new THREE.CylinderGeometry(3.08, 3.14, 0.1, 96), | |
| materials.darkWood, | |
| turntable, | |
| "Turntable plinth", | |
| [0, -0.04, 0] | |
| ); | |
| addMesh( | |
| new THREE.TorusGeometry(3.08, 0.025, 10, 96), | |
| materials.wood, | |
| turntable, | |
| "Turntable rim", | |
| [0, 0.012, 0], | |
| [Math.PI / 2, 0, 0] | |
| ); | |
| const tray = new THREE.Group(); | |
| tray.name = "Small wooden tray"; | |
| tray.position.y = 0.07; | |
| turntable.add(tray); | |
| addMesh( | |
| new THREE.BoxGeometry(4.95, 0.16, 3.3), | |
| materials.wood, | |
| tray, | |
| "Tray base" | |
| ); | |
| addMesh( | |
| new THREE.BoxGeometry(0.16, 0.36, 3.3), | |
| materials.wood, | |
| tray, | |
| "Left tray rail", | |
| [-2.39, 0, 0] | |
| ); | |
| addMesh( | |
| new THREE.BoxGeometry(0.16, 0.36, 3.3), | |
| materials.wood, | |
| tray, | |
| "Right tray rail", | |
| [2.39, 0, 0] | |
| ); | |
| addMesh( | |
| new THREE.BoxGeometry(4.83, 0.36, 0.16), | |
| materials.wood, | |
| tray, | |
| "Front tray rail", | |
| [0, 0, 1.59] | |
| ); | |
| addMesh( | |
| new THREE.BoxGeometry(4.83, 0.36, 0.16), | |
| materials.wood, | |
| tray, | |
| "Back tray rail", | |
| [0, 0, -1.59] | |
| ); | |
| addMesh( | |
| new THREE.BoxGeometry(4.68, 0.035, 3.08), | |
| materials.wood, | |
| tray, | |
| "Tray floor" | |
| ); | |
| // --------------------------------------------------------------------- | |
| // Bottle construction | |
| // --------------------------------------------------------------------- | |
| const bottleProfile = [ | |
| new THREE.Vector2(0.0, 0.0), | |
| new THREE.Vector2(0.23, 0.0), | |
| new THREE.Vector2(0.33, 0.08), | |
| new THREE.Vector2(0.41, 0.22), | |
| new THREE.Vector2(0.43, 0.55), | |
| new THREE.Vector2(0.43, 0.95), | |
| new THREE.Vector2(0.4, 1.2), | |
| new THREE.Vector2(0.31, 1.4), | |
| new THREE.Vector2(0.22, 1.55), | |
| new THREE.Vector2(0.17, 1.68), | |
| new THREE.Vector2(0.17, 1.82) | |
| ]; | |
| function createLabelTexture() { | |
| const canvas = document.createElement("canvas"); | |
| canvas.width = 256; | |
| canvas.height = 128; | |
| const context = canvas.getContext("2d"); | |
| context.fillStyle = "#f3e3bd"; | |
| context.fillRect(0, 0, 256, 128); | |
| context.strokeStyle = "#b78b45"; | |
| context.lineWidth = 8; | |
| context.strokeRect(9, 9, 238, 110); | |
| context.fillStyle = "#59452d"; | |
| context.textAlign = "center"; | |
| context.textBaseline = "middle"; | |
| context.font = "700 42px Georgia, serif"; | |
| context.fillText("POTION", 128, 65); | |
| const texture = new THREE.CanvasTexture(canvas); | |
| texture.colorSpace = THREE.SRGBColorSpace; | |
| texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); | |
| return texture; | |
| } | |
| const labelTexture = createLabelTexture(); | |
| function createBottle(name, bodyColor, xPosition) { | |
| const bottle = new THREE.Group(); | |
| bottle.name = name; | |
| bottle.position.set(xPosition, 0.17, 0); | |
| turntable.add(bottle); | |
| const bodyMaterial = new THREE.MeshPhysicalMaterial({ | |
| color: bodyColor, | |
| roughness: 0.22, | |
| metalness: 0, | |
| clearcoat: 0.65, | |
| clearcoatRoughness: 0.24 | |
| }); | |
| addMesh( | |
| new THREE.LatheGeometry(bottleProfile, 64), | |
| bodyMaterial, | |
| bottle, | |
| `${name} bottle body` | |
| ); | |
| addMesh( | |
| new THREE.CylinderGeometry(0.17, 0.19, 0.42, 32), | |
| materials.cork, | |
| bottle, | |
| `${name} cork` | |
| ); | |
| addMesh( | |
| new THREE.CylinderGeometry(0.172, 0.172, 0.025, 32), | |
| materials.corkTop, | |
| bottle, | |
| `${name} cork top` | |
| ); | |
| const label = addMesh( | |
| new THREE.PlaneGeometry(0.48, 0.24), | |
| new THREE.MeshStandardMaterial({ | |
| map: labelTexture, | |
| roughness: 0.88 | |
| }), | |
| bottle, | |
| `${name} front label`, | |
| [0, 0.77, 0.425] | |
| ); | |
| label.castShadow = false; | |
| return bottle; | |
| } | |
| createBottle("Crimson potion bottle", 0xc6353b, -1.25); | |
| createBottle("Cobalt potion bottle", 0x245ad4, 0); | |
| createBottle("Emerald potion bottle", 0x2d8d65, 1.25); | |
| // --------------------------------------------------------------------- | |
| // Direct mouse orbit controls — no external control modules | |
| // --------------------------------------------------------------------- | |
| let dragging = false; | |
| let previousX = 0; | |
| let previousY = 0; | |
| const canvas = renderer.domElement; | |
| canvas.addEventListener("pointerdown", (event) => { | |
| if (event.button !== 0) return; | |
| dragging = true; | |
| previousX = event.clientX; | |
| previousY = event.clientY; | |
| canvas.setPointerCapture(event.pointerId); | |
| canvas.classList.add("dragging"); | |
| }); | |
| canvas.addEventListener("pointermove", (event) => { | |
| if (!dragging) return; | |
| const deltaX = event.clientX - previousX; | |
| const deltaY = event.clientY - previousY; | |
| previousX = event.clientX; | |
| previousY = event.clientY; | |
| controls.yaw -= deltaX * 0.006; | |
| controls.pitch = THREE.MathUtils.clamp( | |
| controls.pitch + deltaY * 0.005, | |
| 0.25, | |
| 1.35 | |
| ); | |
| updateCamera(); | |
| }); | |
| function endDrag(event) { | |
| dragging = false; | |
| canvas.classList.remove("dragging"); | |
| if (event.pointerId !== undefined && canvas.hasPointerCapture(event.pointerId)) { | |
| canvas.releasePointerCapture(event.pointerId); | |
| } | |
| } | |
| canvas.addEventListener("pointerup", endDrag); | |
| canvas.addEventListener("pointercancel", endDrag); | |
| canvas.addEventListener( | |
| "wheel", | |
| (event) => { | |
| event.preventDefault(); | |
| controls.distance = THREE.MathUtils.clamp( | |
| controls.distance * Math.exp(event.deltaY * 0.001), | |
| 5.6, | |
| 13 | |
| ); | |
| updateCamera(); | |
| }, | |
| { passive: false } | |
| ); | |
| canvas.addEventListener("dblclick", () => { | |
| controls.yaw = 0.58; | |
| controls.pitch = 0.52; | |
| controls.distance = 8.4; | |
| updateCamera(); | |
| }); | |
| canvas.addEventListener("contextmenu", (event) => { | |
| event.preventDefault(); | |
| }); | |
| // --------------------------------------------------------------------- | |
| // Animation and UI | |
| // --------------------------------------------------------------------- | |
| let elapsed = 0; | |
| let paused = false; | |
| pauseButton.addEventListener("click", () => { | |
| paused = !paused; | |
| pauseButton.textContent = paused ? "Resume rotation" : "Pause rotation"; | |
| status.textContent = paused ? "Paused" : "Auto spin"; | |
| }); | |
| const clock = new THREE.Clock(); | |
| function animate() { | |
| const delta = Math.min(clock.getDelta(), 0.05); | |
| if (!paused) { | |
| elapsed += delta; | |
| } | |
| turntable.rotation.y = elapsed * 0.28; | |
| renderer.render(scene, camera); | |
| } | |
| renderer.setAnimationLoop(animate); | |
| window.addEventListener("resize", () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |