Repository: drcmda/floating-shoe Branch: master Commit: 36146274d44d Files: 12 Total size: 30.4 KB Directory structure: gitextract_s73tg7he/ ├── .gitignore ├── .prettierrc ├── package.json ├── public/ │ ├── index.html │ ├── royal_esplanade_1k.hdr │ └── shoe-draco.glb ├── readme.md ├── resources/ │ ├── gltf/ │ │ └── shoe.gltf │ └── shoe.blend └── src/ ├── App.js ├── index.js └── styles.css ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Compiled binary addons (https://nodejs.org/api/addons.html) build/ # Dependency directories node_modules/ jspm_packages/ # TypeScript cache *.tsbuildinfo # Optional eslint cache .eslintcache # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # pnpm lock pnpm-lock.yaml dist ================================================ FILE: .prettierrc ================================================ { "printWidth": 140, "tabWidth": 2, "useTabs": false, "semi": false, "singleQuote": false, "trailingComma": "all", "bracketSpacing": true, "jsxBracketSameLine": true, "fluid": false } ================================================ FILE: package.json ================================================ { "name": "floating-shoe", "version": "1.0.0", "description": "", "keywords": [], "main": "src/index.js", "dependencies": { "drei": "2.2.12", "react": "17.0.1", "react-colorful": "4.4.4", "react-dom": "17.0.1", "react-scripts": "4.0.1", "react-three-fiber": "5.3.10", "three": "0.123.0", "valtio": "0.4.8" }, "devDependencies": { "typescript": "4.1.3" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] } ================================================ FILE: public/index.html ================================================ React App
================================================ FILE: readme.md ================================================ ![](jumbo.jpg) npm install npm start This is a small primer on how to use GLTF models on the web, specifically how to use them as dynamic assets. Tutorial: https://www.youtube.com/watch?v=xy_tbV4pC54 Live demo: https://codesandbox.io/s/floating-shoe-forked-qxjoj ### How to compress assets and turn them into JSX components 1. `npx gltf-pipeline -i model.gltf -o model.glb --draco.compressionLevel=7` 1. `npx gltfjsx model.glb` ### How to include them in your project 1. Set up [react-three-fiber](https://github.com/pmndrs/react-three-fiber) 1. Put `model.glb` into `/public` 1. Put `Model.js` (the output of [gltfjsx](https://github.com/pmndrs/react-three-fiber)) anywhere inside `/src` ================================================ FILE: resources/gltf/shoe.gltf ================================================ { "asset" : { "generator" : "Khronos glTF Blender I/O v1.3.48", "version" : "2.0" }, "scene" : 0, "scenes" : [ { "name" : "Scene", "nodes" : [ 0, 1, 2 ] } ], "nodes" : [ { "mesh" : 0, "name" : "Shoe" }, { "name" : "Light", "rotation" : [ 0.16907575726509094, 0.7558803558349609, -0.27217137813568115, 0.570947527885437 ], "translation" : [ 4.076245307922363, 5.903861999511719, -1.0054539442062378 ] }, { "name" : "Camera", "rotation" : [ 0.483536034822464, 0.33687159419059753, -0.20870360732078552, 0.7804827094078064 ], "translation" : [ 7.358891487121582, 4.958309173583984, 6.925790786743164 ] } ], "materials" : [ { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "laces", "normalTexture" : { "index" : 0, "texCoord" : 0 }, "occlusionTexture" : { "index" : 1, "texCoord" : 0 }, "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicRoughnessTexture" : { "index" : 1, "texCoord" : 0 } } }, { "emissiveFactor" : [ 0, 0, 0 ], "name" : "mesh", "normalTexture" : { "index" : 2, "texCoord" : 0 }, "occlusionTexture" : { "index" : 3, "texCoord" : 0 }, "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicRoughnessTexture" : { "index" : 3, "texCoord" : 0 } } }, { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "caps", "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicFactor" : 0.5, "roughnessFactor" : 0.5 } }, { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "inner", "normalTexture" : { "index" : 4, "texCoord" : 0 }, "occlusionTexture" : { "index" : 5, "texCoord" : 0 }, "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicRoughnessTexture" : { "index" : 5, "texCoord" : 0 } } }, { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "sole", "normalTexture" : { "index" : 6, "texCoord" : 0 }, "occlusionTexture" : { "index" : 7, "texCoord" : 0 }, "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicRoughnessTexture" : { "index" : 7, "texCoord" : 0 } } }, { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "stripes", "normalTexture" : { "index" : 8, "texCoord" : 0 }, "occlusionTexture" : { "index" : 9, "texCoord" : 0 }, "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicRoughnessTexture" : { "index" : 9, "texCoord" : 0 } } }, { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "band", "normalTexture" : { "index" : 10, "texCoord" : 0 }, "occlusionTexture" : { "index" : 11, "texCoord" : 0 }, "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicRoughnessTexture" : { "index" : 11, "texCoord" : 0 } } }, { "doubleSided" : true, "emissiveFactor" : [ 0, 0, 0 ], "name" : "patch", "normalTexture" : { "index" : 12, "texCoord" : 0 }, "occlusionTexture" : { "index" : 13, "texCoord" : 0 }, "pbrMetallicRoughness" : { "baseColorFactor" : [ 1, 1, 1, 1 ], "metallicRoughnessTexture" : { "index" : 13, "texCoord" : 0 } } } ], "meshes" : [ { "name" : "shoe", "primitives" : [ { "attributes" : { "POSITION" : 0, "NORMAL" : 1, "TEXCOORD_0" : 2 }, "indices" : 3, "material" : 0 }, { "attributes" : { "POSITION" : 4, "NORMAL" : 5, "TEXCOORD_0" : 6 }, "indices" : 7, "material" : 1 }, { "attributes" : { "POSITION" : 8, "NORMAL" : 9, "TEXCOORD_0" : 10 }, "indices" : 11, "material" : 2 }, { "attributes" : { "POSITION" : 12, "NORMAL" : 13, "TEXCOORD_0" : 14 }, "indices" : 15, "material" : 3 }, { "attributes" : { "POSITION" : 16, "NORMAL" : 17, "TEXCOORD_0" : 18 }, "indices" : 19, "material" : 4 }, { "attributes" : { "POSITION" : 20, "NORMAL" : 21, "TEXCOORD_0" : 22 }, "indices" : 23, "material" : 5 }, { "attributes" : { "POSITION" : 24, "NORMAL" : 25, "TEXCOORD_0" : 26 }, "indices" : 27, "material" : 6 }, { "attributes" : { "POSITION" : 28, "NORMAL" : 29, "TEXCOORD_0" : 30 }, "indices" : 31, "material" : 7 } ] } ], "textures" : [ { "source" : 0 }, { "source" : 1 }, { "source" : 0 }, { "source" : 1 }, { "source" : 0 }, { "source" : 1 }, { "source" : 0 }, { "source" : 1 }, { "source" : 0 }, { "source" : 1 }, { "source" : 0 }, { "source" : 1 }, { "source" : 0 }, { "source" : 1 } ], "images" : [ { "mimeType" : "image/jpeg", "name" : "normal", "uri" : "normal.jpg" }, { "mimeType" : "image/jpeg", "name" : "occlusionRougnessMetalness", "uri" : "occlusionRougnessMetalness.jpg" } ], "accessors" : [ { "bufferView" : 0, "componentType" : 5126, "count" : 1552, "max" : [ 0.31010252237319946, 0.23435352742671967, 0.19393111765384674 ], "min" : [ -0.21236468851566315, -0.11542611569166183, -0.27246636152267456 ], "type" : "VEC3" }, { "bufferView" : 1, "componentType" : 5126, "count" : 1552, "type" : "VEC3" }, { "bufferView" : 2, "componentType" : 5126, "count" : 1552, "type" : "VEC2" }, { "bufferView" : 3, "componentType" : 5123, "count" : 7227, "type" : "SCALAR" }, { "bufferView" : 4, "componentType" : 5126, "count" : 5199, "max" : [ 0.9846919178962708, 0.3809230625629425, 0.3442471921443939 ], "min" : [ -0.9474887251853943, -0.3825278878211975, -0.35044431686401367 ], "type" : "VEC3" }, { "bufferView" : 5, "componentType" : 5126, "count" : 5199, "type" : "VEC3" }, { "bufferView" : 6, "componentType" : 5126, "count" : 5199, "type" : "VEC2" }, { "bufferView" : 7, "componentType" : 5123, "count" : 21165, "type" : "SCALAR" }, { "bufferView" : 8, "componentType" : 5126, "count" : 1374, "max" : [ 0.2774718105792999, 0.16645346581935883, 0.19632135331630707 ], "min" : [ -0.2291366010904312, -0.13262774050235748, -0.2687131464481354 ], "type" : "VEC3" }, { "bufferView" : 9, "componentType" : 5126, "count" : 1374, "type" : "VEC3" }, { "bufferView" : 10, "componentType" : 5126, "count" : 1374, "type" : "VEC2" }, { "bufferView" : 11, "componentType" : 5123, "count" : 6210, "type" : "SCALAR" }, { "bufferView" : 12, "componentType" : 5126, "count" : 2166, "max" : [ 0.9421579241752625, 0.40126022696495056, 0.2965231239795685 ], "min" : [ -0.8118161559104919, -0.38051360845565796, -0.3006590008735657 ], "type" : "VEC3" }, { "bufferView" : 13, "componentType" : 5126, "count" : 2166, "type" : "VEC3" }, { "bufferView" : 14, "componentType" : 5126, "count" : 2166, "type" : "VEC2" }, { "bufferView" : 15, "componentType" : 5123, "count" : 11025, "type" : "SCALAR" }, { "bufferView" : 16, "componentType" : 5126, "count" : 1317, "max" : [ 1.0000001192092896, -0.220381498336792, 0.38874176144599915 ], "min" : [ -1, -0.5139704942703247, -0.38874176144599915 ], "type" : "VEC3" }, { "bufferView" : 17, "componentType" : 5126, "count" : 1317, "type" : "VEC3" }, { "bufferView" : 18, "componentType" : 5126, "count" : 1317, "type" : "VEC2" }, { "bufferView" : 19, "componentType" : 5123, "count" : 6315, "type" : "SCALAR" }, { "bufferView" : 20, "componentType" : 5126, "count" : 1570, "max" : [ 0.20826250314712524, 0.004239952191710472, 0.32903969287872314 ], "min" : [ -0.2780020833015442, -0.3307887613773346, -0.3230103552341461 ], "type" : "VEC3" }, { "bufferView" : 21, "componentType" : 5126, "count" : 1570, "type" : "VEC3" }, { "bufferView" : 22, "componentType" : 5126, "count" : 1570, "type" : "VEC2" }, { "bufferView" : 23, "componentType" : 5123, "count" : 7350, "type" : "SCALAR" }, { "bufferView" : 24, "componentType" : 5126, "count" : 1705, "max" : [ 0.34729886054992676, 0.5139704942703247, 0.0058010234497487545 ], "min" : [ -0.8557736277580261, -0.08276855945587158, -0.09417246282100677 ], "type" : "VEC3" }, { "bufferView" : 25, "componentType" : 5126, "count" : 1705, "type" : "VEC3" }, { "bufferView" : 26, "componentType" : 5126, "count" : 1705, "type" : "VEC2" }, { "bufferView" : 27, "componentType" : 5123, "count" : 7860, "type" : "SCALAR" }, { "bufferView" : 28, "componentType" : 5126, "count" : 219, "max" : [ -0.7029263377189636, 0.17990857362747192, 0.1358068436384201 ], "min" : [ -0.9019400477409363, -0.021011920645833015, -0.22399701178073883 ], "type" : "VEC3" }, { "bufferView" : 29, "componentType" : 5126, "count" : 219, "type" : "VEC3" }, { "bufferView" : 30, "componentType" : 5126, "count" : 219, "type" : "VEC2" }, { "bufferView" : 31, "componentType" : 5123, "count" : 948, "type" : "SCALAR" } ], "bufferViews" : [ { "buffer" : 0, "byteLength" : 18624, "byteOffset" : 0 }, { "buffer" : 0, "byteLength" : 18624, "byteOffset" : 18624 }, { "buffer" : 0, "byteLength" : 12416, "byteOffset" : 37248 }, { "buffer" : 0, "byteLength" : 14454, "byteOffset" : 49664 }, { "buffer" : 0, "byteLength" : 62388, "byteOffset" : 64120 }, { "buffer" : 0, "byteLength" : 62388, "byteOffset" : 126508 }, { "buffer" : 0, "byteLength" : 41592, "byteOffset" : 188896 }, { "buffer" : 0, "byteLength" : 42330, "byteOffset" : 230488 }, { "buffer" : 0, "byteLength" : 16488, "byteOffset" : 272820 }, { "buffer" : 0, "byteLength" : 16488, "byteOffset" : 289308 }, { "buffer" : 0, "byteLength" : 10992, "byteOffset" : 305796 }, { "buffer" : 0, "byteLength" : 12420, "byteOffset" : 316788 }, { "buffer" : 0, "byteLength" : 25992, "byteOffset" : 329208 }, { "buffer" : 0, "byteLength" : 25992, "byteOffset" : 355200 }, { "buffer" : 0, "byteLength" : 17328, "byteOffset" : 381192 }, { "buffer" : 0, "byteLength" : 22050, "byteOffset" : 398520 }, { "buffer" : 0, "byteLength" : 15804, "byteOffset" : 420572 }, { "buffer" : 0, "byteLength" : 15804, "byteOffset" : 436376 }, { "buffer" : 0, "byteLength" : 10536, "byteOffset" : 452180 }, { "buffer" : 0, "byteLength" : 12630, "byteOffset" : 462716 }, { "buffer" : 0, "byteLength" : 18840, "byteOffset" : 475348 }, { "buffer" : 0, "byteLength" : 18840, "byteOffset" : 494188 }, { "buffer" : 0, "byteLength" : 12560, "byteOffset" : 513028 }, { "buffer" : 0, "byteLength" : 14700, "byteOffset" : 525588 }, { "buffer" : 0, "byteLength" : 20460, "byteOffset" : 540288 }, { "buffer" : 0, "byteLength" : 20460, "byteOffset" : 560748 }, { "buffer" : 0, "byteLength" : 13640, "byteOffset" : 581208 }, { "buffer" : 0, "byteLength" : 15720, "byteOffset" : 594848 }, { "buffer" : 0, "byteLength" : 2628, "byteOffset" : 610568 }, { "buffer" : 0, "byteLength" : 2628, "byteOffset" : 613196 }, { "buffer" : 0, "byteLength" : 1752, "byteOffset" : 615824 }, { "buffer" : 0, "byteLength" : 1896, "byteOffset" : 617576 } ], "buffers" : [ { "byteLength" : 619472, "uri" : "shoe.bin" } ] } ================================================ FILE: src/App.js ================================================ import React, { Suspense, useRef, useState, useEffect } from "react" import { Canvas, useFrame } from "react-three-fiber" import { ContactShadows, Environment, useGLTF, OrbitControls } from "drei" import { HexColorPicker } from "react-colorful" import { proxy, useProxy } from "valtio" // Using a Valtio state model to bridge reactivity between // the canvas and the dom, both can write to it and/or react to it. const state = proxy({ current: null, items: { laces: "#ffffff", mesh: "#ffffff", caps: "#ffffff", inner: "#ffffff", sole: "#ffffff", stripes: "#ffffff", band: "#ffffff", patch: "#ffffff", }, }) function Shoe() { const ref = useRef() const snap = useProxy(state) // Drei's useGLTF hook sets up draco automatically, that's how it differs from useLoader(GLTFLoader, url) // { nodes, materials } are extras that come from useLoader, these do not exist in threejs/GLTFLoader // nodes is a named collection of meshes, materials a named collection of materials const { nodes, materials } = useGLTF("shoe-draco.glb") // Animate model useFrame((state) => { const t = state.clock.getElapsedTime() ref.current.rotation.z = -0.2 - (1 + Math.sin(t / 1.5)) / 20 ref.current.rotation.x = Math.cos(t / 4) / 8 ref.current.rotation.y = Math.sin(t / 4) / 8 ref.current.position.y = (1 + Math.sin(t / 1.5)) / 10 }) // Cursor showing current color const [hovered, set] = useState(null) useEffect(() => { const cursor = `${hovered}` const auto = `` document.body.style.cursor = `url('data:image/svg+xml;base64,${btoa(hovered ? cursor : auto)}'), auto` }, [hovered]) // Using the GLTFJSX output here to wire in app-state and hook up events return ( (e.stopPropagation(), set(e.object.material.name))} onPointerOut={(e) => e.intersections.length === 0 && set(null)} onPointerMissed={() => (state.current = null)} onPointerDown={(e) => (e.stopPropagation(), (state.current = e.object.material.name))}> ) } function Picker() { const snap = useProxy(state) return (
(state.items[snap.current] = color)} />

{snap.current}

) } export default function App() { return ( <> ) } ================================================ FILE: src/index.js ================================================ import React from "react" import ReactDOM from "react-dom" import "./styles.css" import "react-colorful/dist/index.css" import App from "./App" ReactDOM.render(, document.getElementById("root")) ================================================ FILE: src/styles.css ================================================ @import url("https://rsms.me/inter/inter.css"); html, body, #root { position: relative; margin: 0; padding: 0; overflow: hidden; outline: none; width: 100vw; height: 100vh; } body { background: white; font-family: "Inter var", sans-serif; display: flex; justify-content: center; } #root { width: 76ch; } h1 { position: absolute; top: 43px; left: 140px; padding: 0; margin: 40px; font-size: 5em; line-height: 0.7em; letter-spacing: -6px; color: #272730; } .picker { position: absolute !important; top: 74px; left: 70px; width: 90px !important; height: 90px !important; }