feat(3d-viewer): upgrade visuals with significant plant scaling and improved controls based on user feedback
Some checks are pending
Deploy to Production / deploy (push) Waiting to run
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

This commit is contained in:
fullsizemalt 2025-12-17 07:49:18 -08:00
parent 9da01f6338
commit 1ebe7b4d34

View file

@ -65,7 +65,7 @@ function PlantInstances({ positions, onPlantClick }: { positions: any[], onPlant
return ( return (
<group> <group>
{/* Active Plants */} {/* Active Plants - Render as Cylinders (Stalks/Canopy) for visibility */}
{plants.length > 0 && ( {plants.length > 0 && (
<Instances <Instances
range={plants.length} range={plants.length}
@ -77,12 +77,14 @@ function PlantInstances({ positions, onPlantClick }: { positions: any[], onPlant
} }
}} }}
> >
<sphereGeometry args={[0.15, 16, 16]} /> {/* Make plants much taller and visible: Cylinder radius 0.2, height 0.8 */}
<cylinderGeometry args={[0.2, 0.1, 0.8, 8]} />
<meshStandardMaterial /> <meshStandardMaterial />
{plants.map((pos, i) => ( {plants.map((pos, i) => (
<Instance <Instance
key={pos.id || i} key={pos.id || i}
position={[pos.x, pos.y, pos.z]} // Lift y by 0.4 (half height) so it sits ON the shelf
position={[pos.x, pos.y + 0.4, pos.z]}
color={pos.plant?.stage === 'FLOWER' ? COLORS.FLOWER : color={pos.plant?.stage === 'FLOWER' ? COLORS.FLOWER :
pos.plant?.stage === 'DRYING' ? COLORS.DRY : pos.plant?.stage === 'DRYING' ? COLORS.DRY :
pos.plant?.stage === 'CURE' ? COLORS.CURE : COLORS.VEG} pos.plant?.stage === 'CURE' ? COLORS.CURE : COLORS.VEG}
@ -91,11 +93,11 @@ function PlantInstances({ positions, onPlantClick }: { positions: any[], onPlant
</Instances> </Instances>
)} )}
{/* Empty Slots */} {/* Empty Slots - Keep subtle but visible */}
{emptySlots.length > 0 && ( {emptySlots.length > 0 && (
<Instances range={emptySlots.length}> <Instances range={emptySlots.length}>
<sphereGeometry args={[0.05, 8, 8]} /> <cylinderGeometry args={[0.05, 0.05, 0.1, 8]} />
<meshStandardMaterial color={COLORS.EMPTY_SLOT} opacity={0.5} transparent /> <meshStandardMaterial color={COLORS.EMPTY_SLOT} opacity={0.3} transparent />
{emptySlots.map((pos, i) => ( {emptySlots.map((pos, i) => (
<Instance <Instance
key={pos.id || i} key={pos.id || i}
@ -119,22 +121,30 @@ function FacilityScene({ data, onSelectPlant }: { data: Floor3DData, onSelectPla
<Text <Text
position={[room.width / 2, 0.1, room.height / 2]} position={[room.width / 2, 0.1, room.height / 2]}
rotation={[-Math.PI / 2, 0, 0]} rotation={[-Math.PI / 2, 0, 0]}
fontSize={0.5} fontSize={1.5} // Larger text
color="white" color="white"
fillOpacity={0.5} fillOpacity={0.8}
anchorX="center"
anchorY="middle"
> >
{room.name} {room.name}
</Text> </Text>
{/* Floor Plane */} {/* Floor Plane - Darker for contrast */}
<mesh <mesh
rotation={[-Math.PI / 2, 0, 0]} rotation={[-Math.PI / 2, 0, 0]}
position={[room.width / 2, 0, room.height / 2]} position={[room.width / 2, 0, room.height / 2]}
> >
<planeGeometry args={[room.width, room.height]} /> <planeGeometry args={[room.width, room.height]} />
<meshStandardMaterial color={COLORS.ROOM_FLOOR} /> <meshStandardMaterial color={COLORS.ROOM_FLOOR} metalness={0.2} roughness={0.8} />
</mesh> </mesh>
{/* Room Border/Walls */}
<lineSegments position={[room.width / 2, 0.1, room.height / 2]} rotation={[-Math.PI / 2, 0, 0]}>
<edgesGeometry args={[new THREE.PlaneGeometry(room.width, room.height)]} />
<lineBasicMaterial color="#4b5563" linewidth={2} />
</lineSegments>
{/* Render sections and positions */} {/* Render sections and positions */}
{room.sections.map(section => { {room.sections.map(section => {
// Calculate positions for this section // Calculate positions for this section
@ -151,10 +161,10 @@ function FacilityScene({ data, onSelectPlant }: { data: Floor3DData, onSelectPla
return ( return (
<group key={section.id}> <group key={section.id}>
{/* Section Label */} {/* Section Label - Lifted higher */}
<Text <Text
position={[section.posX, 2.5, section.posY]} position={[section.posX, 3, section.posY]}
fontSize={0.3} fontSize={0.5}
color="#9ca3af" color="#9ca3af"
> >
{section.code} {section.code}
@ -170,16 +180,26 @@ function FacilityScene({ data, onSelectPlant }: { data: Floor3DData, onSelectPla
return ( return (
<> <>
<ambientLight intensity={0.7} /> <ambientLight intensity={1.5} />
<pointLight position={[10, 10, 10]} intensity={1} /> <pointLight position={[20, 30, 20]} intensity={2} />
<directionalLight position={[-10, 20, -10]} intensity={0.5} /> <directionalLight position={[-10, 50, -10]} intensity={1} castShadow />
<group position={[-data.floor.width / 2, 0, -data.floor.height / 2]}> <group position={[-data.floor.width / 2, 0, -data.floor.height / 2]}>
{roomMeshes} {roomMeshes}
</group> </group>
<OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2.2} /> {/* Improved Controls: Allow panning, damping for smoothness */}
<gridHelper args={[100, 100, 0x444444, 0x222222]} /> <OrbitControls
makeDefault
enablePan={true}
enableZoom={true}
enableRotate={true}
minDistance={2}
maxDistance={100}
dampingFactor={0.05}
/>
<gridHelper args={[200, 100, 0x444444, 0x111111]} position={[0, -0.1, 0]} />
<axesHelper args={[5]} />
</> </>
); );
} }
@ -227,6 +247,7 @@ export default function Facility3DViewerPage() {
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400">
{floorData ? `${floorData.floor.name}${floorData.stats.occupiedPositions} Plants` : 'Loading...'} {floorData ? `${floorData.floor.name}${floorData.stats.occupiedPositions} Plants` : 'Loading...'}
</p> </p>
<p className="text-[10px] text-gray-500">Controls: Left Click=Rotate, Right Click=Pan, Scroll=Zoom</p>
</div> </div>
</div> </div>
@ -287,7 +308,7 @@ export default function Facility3DViewerPage() {
</div> </div>
)} )}
<Canvas camera={{ position: [10, 15, 10], fov: 50 }}> <Canvas camera={{ position: [20, 30, 20], fov: 60 }}>
<ErrorBoundary> <ErrorBoundary>
<Suspense fallback={<Html center>Loading 3D Scene...</Html>}> <Suspense fallback={<Html center>Loading 3D Scene...</Html>}>
{floorData && ( {floorData && (