Merge pull request #3 from lord-saviord1/stud-3d-bottle-to-real-rende… · lord-saviord1/tag-it-on@e8a84dc · GitHub
Skip to content

Commit e8a84dc

Browse files
Merge pull request #3 from lord-saviord1/stud-3d-bottle-to-real-rendered-bottle
Use the real roll-on model and label art; real label facts
2 parents 659069c + 0048f04 commit e8a84dc

15 files changed

Lines changed: 410 additions & 27 deletions

‎3D-NOTES.md‎

Lines changed: 14 additions & 11 deletions

‎components/BottleSlot.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Scent } from "@/lib/scents";
22

3-
type Colors = Pick<Scent, "body" | "ball" | "base" | "trim" | "accent" | "accentDeep" | "fx">;
3+
type Colors = Pick<Scent, "body" | "ball" | "base" | "trim" | "accent" | "accentDeep" | "fx" | "slug">;
44

55
/** An empty placeholder in the layout. The single 3D bottle (SceneCanvas) glides
66
* to whichever slot is nearest the middle of the screen and takes on its colors. */
@@ -16,6 +16,7 @@ export default function BottleSlot({ colors, label }: { colors: Colors; label: s
1616
data-accent={colors.accent}
1717
data-deep={colors.accentDeep}
1818
data-fx={colors.fx}
19+
data-tex={colors.slug}
1920
data-label={label}
2021
aria-hidden="true"
2122
/>

‎components/SceneCanvas.tsx‎

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useMemo, useRef, useState, type RefObject } from "react";
44
import { Canvas, useFrame, useThree } from "@react-three/fiber";
55
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
6+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
67
import * as THREE from "three";
78
import { makeLabelTexture } from "@/lib/bottleLabel";
89
import { EVT } from "@/lib/events";
@@ -30,6 +31,9 @@ const STAR_PICK = [0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7]; // weighted towards t
3031
const NEB: { x: number; y: number; z: number; size: number; col: string; ph: number; op: number }[] = []; // glows now live in the page background (cheaper)
3132

3233
const WHITE = new THREE.Color("#ffffff");
34+
// the real roll-on model and one texture per fragrance (made from the official product renders)
35+
const TEX_SLUGS = ["cool-breeze", "turbo", "whitening", "powerful-defense", "fresh-mint", "sensitive-skin", "romantic-charm"];
36+
type Assets = { hi: THREE.BufferGeometry; lo: THREE.BufferGeometry; tex: Record<string, THREE.Texture> };
3337

3438
const FX_TYPES = ["tag", "drop", "bolt", "sparkle", "shield", "leaf", "bubble", "petal"] as const;
3539
type FxType = (typeof FX_TYPES)[number];
@@ -47,7 +51,7 @@ const FX_PARAMS: Record<FxType, { fall: number; sway: number; amp: number; spin:
4751
};
4852

4953
type Colors = { body: string; ball: string; base: string; trim: string };
50-
type Slot = Colors & { el: HTMLElement; accent: string; deep: string; label: string; fx: number; top: number; left: number; w: number; h: number };
54+
type Slot = Colors & { el: HTMLElement; accent: string; deep: string; label: string; fx: number; tex: string; top: number; left: number; w: number; h: number };
5155
type World = { el: HTMLElement; color: THREE.Color; haze: number; space: number; top: number; bottom: number };
5256
type BgSpec = {
5357
fx: number;
@@ -63,6 +67,7 @@ type BgSpec = {
6367
tilt: number;
6468
colors: Colors;
6569
map: THREE.Texture;
70+
texSlug: string;
6671
};
6772
type Phys = { dx: number; dy: number; vx: number; vy: number; rot: number; spinV: number; ang: number; angV: number };
6873
type ScreenInfo = { sx: number; sy: number; hw: number; hh: number; z: number };
@@ -464,6 +469,64 @@ function Scene() {
464469
};
465470
}, []);
466471

472+
const [assets, setAssets] = useState<Assets | null>(null);
473+
const assetsRef = useRef<Assets | null>(null);
474+
const assetsStatus = useRef<"loading" | "ready" | "failed">("loading");
475+
const mainRealMat = useRef<THREE.MeshPhysicalMaterial>(null);
476+
477+
useEffect(() => {
478+
let alive = true;
479+
(async () => {
480+
try {
481+
const loader = new GLTFLoader();
482+
const texLoader = new THREE.TextureLoader();
483+
const geoOf = async (url: string) => {
484+
const gltf = await loader.loadAsync(url);
485+
const found: THREE.BufferGeometry[] = [];
486+
gltf.scene.traverse((o) => {
487+
const m = o as THREE.Mesh;
488+
if (m.isMesh) found.push(m.geometry);
489+
});
490+
if (found.length === 0) throw new Error("no mesh in " + url);
491+
return found[0];
492+
};
493+
const [hi, lo] = await Promise.all([geoOf("/models/roll-on-hi.glb"), geoOf("/models/roll-on-lo.glb")]);
494+
const tex: Record<string, THREE.Texture> = {};
495+
await Promise.all(
496+
TEX_SLUGS.map(async (slug) => {
497+
const t = await texLoader.loadAsync(`/textures/${slug}.webp`);
498+
t.colorSpace = THREE.SRGBColorSpace;
499+
t.flipY = false; // the model's UVs use the glTF convention
500+
t.anisotropy = 4;
501+
tex[slug] = t;
502+
})
503+
);
504+
if (!alive) return;
505+
const a = { hi, lo, tex };
506+
assetsRef.current = a;
507+
assetsStatus.current = "ready";
508+
setAssets(a);
509+
} catch {
510+
assetsStatus.current = "failed"; // keep the roll-on built in code
511+
}
512+
})();
513+
return () => {
514+
alive = false;
515+
const a = assetsRef.current;
516+
if (a) {
517+
a.hi.dispose();
518+
a.lo.dispose();
519+
Object.values(a.tex).forEach((t) => t.dispose());
520+
}
521+
};
522+
}, []);
523+
524+
useEffect(() => {
525+
// the model arrived after the first look was chosen: give it the right fragrance
526+
if (assets && st.current.index >= 0) applyLook(st.current.index);
527+
// eslint-disable-next-line react-hooks/exhaustive-deps
528+
}, [assets]);
529+
467530
/** Put the main roll-on into the look of slot `idx` (colours, label, halo). */
468531
const applyLook = (idx: number) => {
469532
const sl = slots.current[idx];
@@ -472,6 +535,15 @@ function Scene() {
472535
ballMat.current?.color.set(sl.ball);
473536
baseMat.current?.color.set(sl.base);
474537
trimMat.current?.color.set(sl.trim);
538+
const A = assetsRef.current;
539+
const real = mainRealMat.current;
540+
if (A && real) {
541+
const t = A.tex[sl.tex];
542+
if (t) {
543+
real.map = t;
544+
real.emissiveMap = t;
545+
}
546+
}
475547
const lm = labelMat.current;
476548
if (lm) {
477549
lm.map = textures.current[idx] ?? null;
@@ -510,6 +582,7 @@ function Scene() {
510582
deep: el.dataset.deep ?? "#0d4fa8",
511583
label: el.dataset.label ?? "TAG",
512584
fx: Math.max(0, FX_TYPES.indexOf((el.dataset.fx ?? "drop") as FxType)),
585+
tex: el.dataset.tex ?? "cool-breeze",
513586
top: 0,
514587
left: 0,
515588
w: 0,
@@ -602,6 +675,7 @@ function Scene() {
602675
tilt: (rnd() - 0.5) * 0.6,
603676
colors: { body: slot.body, ball: slot.ball, base: slot.base, trim: slot.trim },
604677
map: tex,
678+
texSlug: slot.tex,
605679
});
606680
};
607681
for (let i = 0; i < nHang; i++) make(i % 3, Math.floor(i / 3), i, 0);
@@ -1172,7 +1246,7 @@ function Scene() {
11721246
mt.opacity = nb.op * s.space;
11731247
}
11741248

1175-
if (!s.ready) {
1249+
if (!s.ready && assetsStatus.current !== "loading") {
11761250
s.ready = true;
11771251
window.__tagSceneReady = true;
11781252
window.dispatchEvent(new Event(EVT.sceneReady));
@@ -1269,13 +1343,46 @@ function Scene() {
12691343
bgRefs.current[i] = el;
12701344
}}
12711345
>
1272-
<BottleParts geo={loGeo} colors={b.colors} map={b.map} />
1346+
{assets ? (
1347+
<mesh geometry={assets.lo}>
1348+
<meshStandardMaterial
1349+
color="#555555"
1350+
map={assets.tex[b.texSlug]}
1351+
emissive="#ffffff"
1352+
emissiveMap={assets.tex[b.texSlug]}
1353+
emissiveIntensity={0.8}
1354+
roughness={0.45}
1355+
metalness={0}
1356+
envMapIntensity={0.4}
1357+
/>
1358+
</mesh>
1359+
) : (
1360+
<BottleParts geo={loGeo} colors={b.colors} map={b.map} />
1361+
)}
12731362
</group>
12741363
))}
12751364
</group>
12761365

12771366
<group ref={mainGroup}>
1278-
<BottleParts geo={hiGeo} refs={refs} fancy />
1367+
{assets ? (
1368+
<mesh geometry={assets.hi}>
1369+
<meshPhysicalMaterial
1370+
ref={mainRealMat}
1371+
color="#555555"
1372+
map={assets.tex["whitening"]}
1373+
emissive="#ffffff"
1374+
emissiveMap={assets.tex["whitening"]}
1375+
emissiveIntensity={0.8}
1376+
roughness={0.4}
1377+
metalness={0}
1378+
clearcoat={0.4}
1379+
clearcoatRoughness={0.2}
1380+
envMapIntensity={0.5}
1381+
/>
1382+
</mesh>
1383+
) : (
1384+
<BottleParts geo={hiGeo} refs={refs} fancy />
1385+
)}
12791386
</group>
12801387
</>
12811388
);

‎lib/scents.ts‎

Lines changed: 15 additions & 11 deletions

‎public/models/roll-on-hi.glb‎

579 KB
Binary file not shown.

‎public/models/roll-on-lo.glb‎

97.3 KB
Binary file not shown.

‎public/textures/3x-whitening.webp‎

51.6 KB
Loading

‎public/textures/cool-breeze.webp‎

65.4 KB
Loading

‎public/textures/fresh-mint.webp‎

59.8 KB
Loading
57.7 KB
Loading

0 commit comments

Comments
 (0)