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
================================================

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 = ``
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;
}