feat: add GLTF transformation and compression support
CI/CD / test-and-build (push) Waiting to run
CI/CD / deploy (push) Blocked by required conditions

- Updated package.json to include @gltf-transform/core, @gltf-transform/extensions, @gltf-transform/functions, and draco3dgltf for GLB file processing.
- Implemented GLB compression using Draco in the upload model API route.
- Adjusted camera keyframes in ScrollScene for improved animation.
- Enhanced overlay styles in ScrollOverlays for better visual clarity.
- Added original GLB files for Kandoura, Suit, Unitree_G1, and Vest.
- Updated existing GLB files for Kandoura, Suit, Unitree_G1, and Vest.
This commit is contained in:
Najjar\NajjarV02
2026-04-17 15:34:41 +04:00
parent 6dc705b332
commit 83d0d9ab00
13 changed files with 2198 additions and 11 deletions
+2166 -5
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -11,6 +11,9 @@
"test:watch": "vitest" "test:watch": "vitest"
}, },
"dependencies": { "dependencies": {
"@gltf-transform/core": "^4.3.0",
"@gltf-transform/extensions": "^4.3.0",
"@gltf-transform/functions": "^4.3.0",
"@libsql/client": "^0.17.2", "@libsql/client": "^0.17.2",
"@prisma/adapter-libsql": "^7.7.0", "@prisma/adapter-libsql": "^7.7.0",
"@prisma/client": "^7.7.0", "@prisma/client": "^7.7.0",
@@ -22,6 +25,7 @@
"@stripe/stripe-js": "^9.1.0", "@stripe/stripe-js": "^9.1.0",
"@types/three": "^0.183.1", "@types/three": "^0.183.1",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"draco3dgltf": "^1.5.7",
"framer-motion": "^12.38.0", "framer-motion": "^12.38.0",
"gsap": "^3.14.2", "gsap": "^3.14.2",
"i18next": "^26.0.3", "i18next": "^26.0.3",
@@ -37,6 +41,7 @@
"zustand": "^5.0.12" "zustand": "^5.0.12"
}, },
"devDependencies": { "devDependencies": {
"@gltf-transform/cli": "^4.3.0",
"@tailwindcss/postcss": "^4.2.2", "@tailwindcss/postcss": "^4.2.2",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+22 -1
View File
@@ -1,8 +1,22 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { jwtVerify } from 'jose'; import { jwtVerify } from 'jose';
import { writeFile, mkdir } from 'fs/promises'; import { writeFile, mkdir, readFile } from 'fs/promises';
import path from 'path'; import path from 'path';
import { Document, NodeIO } from '@gltf-transform/core';
import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
import { draco, dedup, prune, weld, simplify } from '@gltf-transform/functions';
import draco3d from 'draco3dgltf';
async function compressGLB(inputPath: string): Promise<void> {
const io = new NodeIO()
.registerExtensions(ALL_EXTENSIONS)
.registerDependencies({ 'draco3d.decoder': await draco3d.createDecoderModule(), 'draco3d.encoder': await draco3d.createEncoderModule() });
const document = await io.read(inputPath);
await document.transform(dedup(), prune(), weld(), draco());
await io.write(inputPath, document);
}
export async function POST(request: Request) { export async function POST(request: Request) {
// Verify admin JWT // Verify admin JWT
@@ -51,6 +65,13 @@ export async function POST(request: Request) {
const buffer = Buffer.from(await file.arrayBuffer()); const buffer = Buffer.from(await file.arrayBuffer());
await writeFile(destPath, buffer); await writeFile(destPath, buffer);
// Compress the uploaded GLB with Draco to reduce file size
try {
await compressGLB(destPath);
} catch (err) {
console.warn('GLB compression failed, serving uncompressed:', err);
}
// Append a version timestamp so useGLTF cache is busted on replacement uploads // Append a version timestamp so useGLTF cache is busted on replacement uploads
const version = Date.now(); const version = Date.now();
return NextResponse.json({ modelPath: `/models/${safeId}.glb?v=${version}` }); return NextResponse.json({ modelPath: `/models/${safeId}.glb?v=${version}` });
+3 -3
View File
@@ -74,9 +74,9 @@ function OverlaySection({
const panelStyle: React.CSSProperties = isCenter const panelStyle: React.CSSProperties = isCenter
? { textAlign: 'center' } ? { textAlign: 'center' }
: { : {
background: 'rgba(255, 255, 255, 0.45)', background: 'rgba(255, 255, 255, 0.92)',
backdropFilter: 'blur(16px)', backdropFilter: 'blur(20px)',
WebkitBackdropFilter: 'blur(16px)', WebkitBackdropFilter: 'blur(20px)',
padding: '2.5rem', padding: '2.5rem',
borderRadius: '1.5rem', borderRadius: '1.5rem',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.08)', boxShadow: '0 8px 32px rgba(0, 0, 0, 0.08)',
+2 -2
View File
@@ -13,8 +13,8 @@ const CAMERA_KEYFRAMES: [number, [number, number, number]][] = [
[0.40, [0.3, 1.5, 2.0]], // HEAD+CHEST: tight, high angle looking down [0.40, [0.3, 1.5, 2.0]], // HEAD+CHEST: tight, high angle looking down
[0.55, [1.2, 2.2, 2.2]], // ABOVE: bird's eye with slight right offset [0.55, [1.2, 2.2, 2.2]], // ABOVE: bird's eye with slight right offset
[0.68, [1.6, 0.7, 2.5]], // Dramatic right side sweep [0.68, [1.6, 0.7, 2.5]], // Dramatic right side sweep
[0.80, [0.3, 0.3, 2.2]], // Circle to front-low [0.80, [-1.8, 0.3, 3.0]], // Circle to front-left, pulled back to avoid text overlap
[0.92, [0, -0.3, 2.5]], // LOW ANGLE: heroic looking up [0.92, [-1.5, -0.1, 3.2]], // LOW ANGLE: heroic, offset left for right-side text
[1.0, [0, 1.2, 5.5]], // Final: pull back for configurator [1.0, [0, 1.2, 5.5]], // Final: pull back for configurator
]; ];