Skip to content

Commit

Permalink
make texture loading better, add toneMappingExposure, update packages
Browse files Browse the repository at this point in the history
  • Loading branch information
Igor-Vuk committed Oct 17, 2024
1 parent 3fe8d42 commit 9d46e9c
Show file tree
Hide file tree
Showing 29 changed files with 660 additions and 408 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ module.exports = {
{
files: ["**/components/ui/*.tsx", "**/components/hooks/*.tsx"],
rules: {
'react/prop-types': 0 // This rule doesn't make sense with TypeScript because we are already checking types.
"react/prop-types": 0, // This rule doesn't make sense with TypeScript because we are already checking types.
"react-refresh/only-export-components": "off",
"tailwindcss/enforces-shorthand": "off",
"@typescript-eslint/no-unused-vars": "off",
"tailwindcss/no-custom-classname": "off",
},
},
],
Expand Down
491 changes: 173 additions & 318 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,32 @@
"devDependencies": {
"@react-three/eslint-plugin": "^0.1.1",
"@react-three/test-renderer": "^8.2.1",
"@stylistic/eslint-plugin": "^2.8.0",
"@testing-library/jest-dom": "^6.5.0",
"@stylistic/eslint-plugin": "^2.9.0",
"@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.7.4",
"@types/node": "^22.7.6",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@types/react-dom": "^18.3.1",
"@types/three": "^0.169.0",
"@typescript-eslint/eslint-plugin": "^8.8.0",
"@typescript-eslint/parser": "^8.8.0",
"@typescript-eslint/eslint-plugin": "^8.10.0",
"@typescript-eslint/parser": "^8.10.0",
"@vitejs/plugin-react": "^4.3.2",
"@vitest/coverage-v8": "^2.1.2",
"@vitest/ui": "^2.1.2",
"@vitest/coverage-v8": "^2.1.3",
"@vitest/ui": "^2.1.3",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-tailwindcss": "^3.17.4",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-tailwindcss": "^3.17.5",
"jsdom": "^25.0.1",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"prettier-eslint": "^16.3.0",
"rollup-plugin-visualizer": "^5.12.0",
"tailwindcss": "^3.4.13",
"typescript": "^5.6.2",
"vite": "^5.4.8",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"vite": "^5.4.9",
"vite-plugin-glsl": "^1.3.0",
"vitest": "^2.1.2",
"vitest-webgl-canvas-mock": "^1.1.0"
Expand All @@ -49,20 +49,20 @@
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@react-three/drei": "^9.114.0",
"@react-three/fiber": "^8.17.9",
"@react-three/rapier": "^1.4.0",
"@react-three/drei": "^9.114.4",
"@react-three/fiber": "^8.17.10",
"@react-three/rapier": "^1.5.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"leva": "^0.9.35",
"lucide-react": "^0.447.0",
"lucide-react": "^0.453.0",
"mapbox-gl": "^3.7.0",
"r3f-perf": "^7.2.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-map-gl": "^7.1.7",
"react-three-map": "^0.8.2",
"tailwind-merge": "^2.5.2",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"three": "^0.169.0"
},
Expand Down
Binary file modified public/models/city-map-models.glb
Binary file not shown.
Binary file added public/textures/AO-assets.webp
Binary file not shown.
Binary file added public/textures/AO-terrain.webp
Binary file not shown.
Binary file removed public/textures/AO.jpg
Binary file not shown.
Binary file added public/textures/DIFFUSE-assets.webp
Binary file not shown.
Binary file added public/textures/DIFFUSE-terrain.webp
Binary file not shown.
Binary file removed public/textures/DIFFUSE.jpg
Binary file not shown.
Binary file removed public/textures/DISPLACEMENT.jpg
Binary file not shown.
Binary file added public/textures/NORMAL-assets.webp
Binary file not shown.
Binary file added public/textures/NORMAL-terrain.webp
Binary file not shown.
Binary file removed public/textures/NORMAL.jpg
Binary file not shown.
Binary file added public/textures/ROUGHNESS-assets.webp
Binary file not shown.
Binary file added public/textures/ROUGHNESS-terrain.webp
Binary file not shown.
Binary file removed public/textures/ROUGHNESS.jpg
Binary file not shown.
22 changes: 11 additions & 11 deletions src/Experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ const Models = lazy(

/* ------------------- Preload ------------------------------- */
useEnvironment.preload({ files: assetsPath.environmentMapFiles })
useGLTF.preload(assetsPath.cityModels)
useTexture.preload(
assetsPath.modelsTexture.map,
) /* If we have for example map and aoMap for the same object we need to preload them separately */
useGLTF.preload(assetsPath.cityModel)
useTexture.preload(assetsPath.modelsAssetsTexture)
useTexture.preload(assetsPath.modelsTerrainTexture)
/* ----------------------------------------------------------- */

export default function Experience() {
Expand Down Expand Up @@ -73,7 +72,7 @@ export default function Experience() {
environment_map,
} = sceneRender.values

const { toneMapping, colorSpace } = canvas.values
const { toneMapping, colorSpace, toneMappingExposure } = canvas.values

const mapStyle = import.meta.env.VITE_MAPSTYLE

Expand Down Expand Up @@ -102,10 +101,10 @@ export default function Experience() {
maxPitch={70}
mapStyle={mapStyle}
initialViewState={{
longitude: 15.934696,
latitude: 45.756005,
zoom: 15.5,
pitch: 51,
longitude: 15.935,
latitude: 45.754,
zoom: 17.0,
pitch: 60,
}}
onZoom={handleZoom}
>
Expand All @@ -116,15 +115,16 @@ export default function Experience() {

{/* Canvas is imported from react-three-map, not @react-three/fiber. Because the scene now lives in a map,
we leave a lot of the render and camera control to the map, rather than to R3F, so the following <Canvas>
props are ignored: gl, camera, resize, orthographic, dpr. This is why toneMapping and outputColorSpace will not be
updated using leva but will still work on first render*/}
props are ignored: gl, camera, resize, orthographic, dpr. This is why toneMapping, outputColorSpace and exposure
will not be updated using leva but will still work on first render*/}
<Canvas
shadows
latitude={45.756005}
longitude={15.934696}
gl={{
toneMapping: THREE[toneMapping],
outputColorSpace: THREE[colorSpace],
toneMappingExposure: toneMappingExposure, // default is 1.0. It doesn't work with "NoToneMapping"
}}
>
{performance_monitor && <PerformanceMonitor />}
Expand Down
150 changes: 150 additions & 0 deletions src/contentComponents/canvasComponents/AssetModels/AssetModels.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { FC, useState, useMemo, useRef } from "react"
import { useFrame } from "@react-three/fiber"
import { AssetProps } from "../canvasComponents.types"
import { Instances, Instance, Outlines } from "@react-three/drei"
import * as THREE from "three"
/* import instanceTransformsData from "../../data/instanceTransforms.json" */

const AssetModels: FC<AssetProps> = ({
groupedMeshes = null,
textures = null,
}) => {
console.log("ASSET MODELS", groupedMeshes)

const [ready, setReady] = useState(false)
const instancesGroupRef = useRef<THREE.Group | null>(null)

// Memoize the material to avoid re-creating it on each render
const material = useMemo(() => {
const mat = new THREE.MeshStandardMaterial()

if (textures && textures.length > 0) {
textures.forEach((texture) => {
if (texture.name === "diffuseTexture") {
mat.map = texture
} else if (texture.name === "roughnessTexture") {
mat.roughnessMap = texture
} else if (texture.name === "normalTexture") {
mat.normalMap = texture
// mat.normalScale = new THREE.Vector2(1.0, 1.0) // Adjust as needed
} else if (texture.name === "aoTexture") {
mat.aoMap = texture
// Handle any additional textures here
}
})
}

return mat
}, [textures])

const outlines = useMemo(
() => <Outlines screenspace={true} thickness={0.1} angle={Math.PI} />,
[],
)

/* We use useFrame here instead of useEffect because useEffect runs after React renders the component
tree but before WebGL assets (like textures, geometries, or instances) are fully processed by Three.js,
which can lead to timing issues. On the other hand, useFrame runs on every frame before the scene is
rendered, making it more reliable for tasks that depend on the full readiness of WebGL objects, such
as applying Outlines. */
useFrame(() => {
if (!ready && instancesGroupRef.current) {
setReady(true)
}
})

const renderNode = useMemo(() => {
/* safeguard that ensures the component only attempts to render content when groupedMeshes has been properly initialized and populated */
if (!groupedMeshes) return null

/* when we export mesh from blender that has none or one material and we import it in Three.js we get THREE.Mesh but when we export a mesh that has multiple materials
we get THREE.Group with "children" property where every material turns into a separate mesh. Makes it much more complicated to work with instances so we don't use them*/
const renderMeshInstances = (meshGroup: THREE.Mesh[]) => {
/* custom condition if we don't want to render something here we exclude it using name */
if (
meshGroup[0].name !== "border" &&
meshGroup[0].name !== "grass" &&
meshGroup[0].name !== "road" &&
meshGroup[0].userData.lineArt === false
) {
return (
<Instances
key={meshGroup[0].uuid}
geometry={meshGroup[0].geometry}
material={material}
castShadow // be careful, shadows increase draw calls
receiveShadow
>
{/* We can use this to render if we have extracted just transform data of Instances from Blender inside of JSON file */}
{/* {instanceTransformsData
.filter((mesh) => mesh.name === meshGroup[0].name)
.map((mesh) => (
<Instance
key={mesh.uuid}
position={mesh.position}
rotation={mesh.rotation}
scale={mesh.scale}
/>
))} */}
{meshGroup.map((mesh) => (
<Instance
key={mesh.uuid}
position={mesh.position}
rotation={mesh.rotation}
scale={mesh.scale}
/>
))}

{/* {ready && outlines} */}
</Instances>
)
}
}

const renderGroups = (groupGroup: THREE.Group[]) =>
groupGroup.map((group) => (
<group key={group.uuid} position={group.position} scale={group.scale}>
{group.children
/* inside of children we can have THREE.Mesh which we will render but also THREE.Light, THREE.Camera, even THREE.Group again */
.filter((child): child is THREE.Mesh => child instanceof THREE.Mesh)
.map((child) => (
<mesh
key={child.uuid}
name={child.name}
geometry={child.geometry}
material={child.material} // child.material will use material that is exported from blender
position={child.position}
rotation={child.rotation}
scale={child.scale}
>
{ready && outlines}
</mesh>
))}
</group>
))

/* We can render groups more simply as primitive if we don't need control over every mesh like above where we add Outlines */
// const renderGroups = (groupGroup: THREE.Group[]) => {
// return groupGroup.map((group) => {
// return <primitive object={group} key={group.uuid} material={material} />
// })
// }

return (
/* Only after the entire subtree (the <group> and all its children) has been mounted into the DOM will instancesGroupRef.current be populated. */
<group position={[218, 0, 112]} ref={instancesGroupRef}>
{/* groupedMeshes[0] are all meshes grouped by the name and groupedMeshes[1] are groups */}
{groupedMeshes[0].length > 0 && (
<group>{groupedMeshes[0].map(renderMeshInstances)}</group>
)}
{groupedMeshes[1].length > 0 && (
<group>{groupedMeshes[1].map(renderGroups)}</group>
)}
</group>
)
}, [material, outlines, ready, groupedMeshes])

return renderNode
}

export default AssetModels
Loading

0 comments on commit 9d46e9c

Please sign in to comment.