fix: 3D viewer position scaling and stage coloring
- Add SCALE constant (0.1) to convert pixel coords to world units - Fix stage coloring: use FLOWERING/DRYING/CURING instead of FLOWER/DRY/CURE - Apply scaling to FacilityScene, RoomObject, SmartRack consistently - Plants now render inside their proper rooms
This commit is contained in:
parent
71f1e23ff3
commit
d56b7f0b11
4 changed files with 62 additions and 26 deletions
|
|
@ -6,6 +6,9 @@ import { RoomObject } from './RoomObject';
|
||||||
import { Beacon } from './Beacon';
|
import { Beacon } from './Beacon';
|
||||||
import { PlantPosition, VisMode } from './types';
|
import { PlantPosition, VisMode } from './types';
|
||||||
|
|
||||||
|
// Convert pixel coordinates to world units (same as RoomObject/SmartRack)
|
||||||
|
const SCALE = 0.1;
|
||||||
|
|
||||||
interface FacilitySceneProps {
|
interface FacilitySceneProps {
|
||||||
data: Floor3DData;
|
data: Floor3DData;
|
||||||
visMode: VisMode;
|
visMode: VisMode;
|
||||||
|
|
@ -30,6 +33,12 @@ export function FacilityScene({
|
||||||
const controlsRef = useRef<CameraControls>(null);
|
const controlsRef = useRef<CameraControls>(null);
|
||||||
const [keysPressed, setKeysPressed] = useState<Record<string, boolean>>({});
|
const [keysPressed, setKeysPressed] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
// Scale floor dimensions for centering
|
||||||
|
const scaledFloor = {
|
||||||
|
width: data.floor.width * SCALE,
|
||||||
|
height: data.floor.height * SCALE,
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (controlsRef.current) onControlsReady(controlsRef.current);
|
if (controlsRef.current) onControlsReady(controlsRef.current);
|
||||||
}, [controlsRef.current, onControlsReady]);
|
}, [controlsRef.current, onControlsReady]);
|
||||||
|
|
@ -88,7 +97,7 @@ export function FacilityScene({
|
||||||
shadow-mapSize={[2048, 2048]}
|
shadow-mapSize={[2048, 2048]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<group position={[-data.floor.width / 2, 0, -data.floor.height / 2]}>
|
<group position={[-scaledFloor.width / 2, 0, -scaledFloor.height / 2]}>
|
||||||
{data.rooms.map((room: Room3D) => (
|
{data.rooms.map((room: Room3D) => (
|
||||||
<RoomObject
|
<RoomObject
|
||||||
key={room.id}
|
key={room.id}
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,13 @@ export function PlantSystem({
|
||||||
let color: string;
|
let color: string;
|
||||||
switch (visMode) {
|
switch (visMode) {
|
||||||
case 'STANDARD':
|
case 'STANDARD':
|
||||||
color = pos.plant?.stage === 'FLOWER' ? COLORS.FLOWER :
|
// Match actual BatchStage enum values from schema
|
||||||
pos.plant?.stage === 'DRYING' ? COLORS.DRY :
|
const stage = pos.plant?.stage;
|
||||||
pos.plant?.stage === 'CURE' ? COLORS.CURE : COLORS.VEG;
|
color = (stage === 'FLOWERING') ? COLORS.FLOWER :
|
||||||
|
(stage === 'DRYING') ? COLORS.DRY :
|
||||||
|
(stage === 'CURING') ? COLORS.CURE :
|
||||||
|
(stage === 'CLONE_IN' || stage === 'VEGETATIVE') ? COLORS.VEG :
|
||||||
|
COLORS.VEG; // Default fallback
|
||||||
break;
|
break;
|
||||||
case 'HEALTH':
|
case 'HEALTH':
|
||||||
const status = getMockPlantHealth(pos.plant!.id);
|
const status = getMockPlantHealth(pos.plant!.id);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import type { Room3D } from '../../lib/layoutApi';
|
||||||
import { PlantPosition, VisMode, COLORS } from './types';
|
import { PlantPosition, VisMode, COLORS } from './types';
|
||||||
import { SmartRack } from './SmartRack';
|
import { SmartRack } from './SmartRack';
|
||||||
|
|
||||||
|
// Convert pixel coordinates to world units (same as SmartRack)
|
||||||
|
const SCALE = 0.1;
|
||||||
|
|
||||||
// Mock environment data
|
// Mock environment data
|
||||||
const getMockRoomEnv = (roomName: string) => {
|
const getMockRoomEnv = (roomName: string) => {
|
||||||
const hash = roomName.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
|
const hash = roomName.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
|
||||||
|
|
@ -30,6 +33,14 @@ interface RoomObjectProps {
|
||||||
export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMode }: RoomObjectProps) {
|
export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMode }: RoomObjectProps) {
|
||||||
const env = getMockRoomEnv(room.name);
|
const env = getMockRoomEnv(room.name);
|
||||||
|
|
||||||
|
// Scale room dimensions to world units
|
||||||
|
const scaledRoom = {
|
||||||
|
posX: room.posX * SCALE,
|
||||||
|
posY: room.posY * SCALE,
|
||||||
|
width: room.width * SCALE,
|
||||||
|
height: room.height * SCALE,
|
||||||
|
};
|
||||||
|
|
||||||
let floorColor: string = COLORS.ROOM_FLOOR;
|
let floorColor: string = COLORS.ROOM_FLOOR;
|
||||||
if (visMode === 'TEMP') {
|
if (visMode === 'TEMP') {
|
||||||
const t = (env.temp - 65) / 20;
|
const t = (env.temp - 65) / 20;
|
||||||
|
|
@ -44,11 +55,11 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={[room.posX, 0, room.posY]}>
|
<group position={[scaledRoom.posX, 0, scaledRoom.posY]}>
|
||||||
<Text
|
<Text
|
||||||
position={[room.width / 2, 0.05, room.height / 2]}
|
position={[scaledRoom.width / 2, 0.05, scaledRoom.height / 2]}
|
||||||
rotation={[-Math.PI / 2, 0, 0]}
|
rotation={[-Math.PI / 2, 0, 0]}
|
||||||
fontSize={Math.min(room.width, room.height) / 6}
|
fontSize={Math.min(scaledRoom.width, scaledRoom.height) / 6}
|
||||||
color="#f8fafc"
|
color="#f8fafc"
|
||||||
fillOpacity={0.7}
|
fillOpacity={0.7}
|
||||||
anchorX="center"
|
anchorX="center"
|
||||||
|
|
@ -59,9 +70,9 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
|
||||||
|
|
||||||
{(visMode === 'TEMP' || visMode === 'HUMIDITY') && (
|
{(visMode === 'TEMP' || visMode === 'HUMIDITY') && (
|
||||||
<Text
|
<Text
|
||||||
position={[room.width / 2, 4, room.height / 2]}
|
position={[scaledRoom.width / 2, 4, scaledRoom.height / 2]}
|
||||||
rotation={[-Math.PI / 4, 0, 0]}
|
rotation={[-Math.PI / 4, 0, 0]}
|
||||||
fontSize={4}
|
fontSize={Math.min(4, scaledRoom.width / 4)}
|
||||||
color="#fff"
|
color="#fff"
|
||||||
outlineColor="#000"
|
outlineColor="#000"
|
||||||
outlineWidth={0.15}
|
outlineWidth={0.15}
|
||||||
|
|
@ -71,8 +82,8 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[room.width / 2, 0, room.height / 2]} receiveShadow>
|
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[scaledRoom.width / 2, 0, scaledRoom.height / 2]} receiveShadow>
|
||||||
<planeGeometry args={[room.width, room.height]} />
|
<planeGeometry args={[scaledRoom.width, scaledRoom.height]} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial
|
||||||
color={floorColor}
|
color={floorColor}
|
||||||
roughness={0.95}
|
roughness={0.95}
|
||||||
|
|
@ -82,8 +93,8 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
<lineSegments position={[room.width / 2, 0.02, room.height / 2]} rotation={[-Math.PI / 2, 0, 0]}>
|
<lineSegments position={[scaledRoom.width / 2, 0.02, scaledRoom.height / 2]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
<edgesGeometry args={[new THREE.PlaneGeometry(room.width, room.height)]} />
|
<edgesGeometry args={[new THREE.PlaneGeometry(scaledRoom.width, scaledRoom.height)]} />
|
||||||
<lineBasicMaterial color="#64748b" />
|
<lineBasicMaterial color="#64748b" />
|
||||||
</lineSegments>
|
</lineSegments>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ import type { Section3D, Position3D } from '../../lib/layoutApi';
|
||||||
import { PlantPosition, VisMode } from './types';
|
import { PlantPosition, VisMode } from './types';
|
||||||
import { PlantSystem } from './PlantSystem';
|
import { PlantSystem } from './PlantSystem';
|
||||||
|
|
||||||
|
// Convert pixel coordinates to world units (DB stores pixels, 3D uses meters/units)
|
||||||
|
const SCALE = 0.1; // 1 pixel = 0.1 world units (so 700px = 70 units)
|
||||||
|
|
||||||
interface SmartRackProps {
|
interface SmartRackProps {
|
||||||
section: Section3D;
|
section: Section3D;
|
||||||
visMode: VisMode;
|
visMode: VisMode;
|
||||||
|
|
@ -14,27 +17,36 @@ interface SmartRackProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dimMode }: SmartRackProps) {
|
export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dimMode }: SmartRackProps) {
|
||||||
|
// Scale section dimensions to world units
|
||||||
|
const scaledSection = {
|
||||||
|
posX: section.posX * SCALE,
|
||||||
|
posY: section.posY * SCALE,
|
||||||
|
width: section.width * SCALE,
|
||||||
|
height: section.height * SCALE,
|
||||||
|
};
|
||||||
|
|
||||||
const positions: PlantPosition[] = useMemo(() => {
|
const positions: PlantPosition[] = useMemo(() => {
|
||||||
const spacing = 0.5;
|
const plantSpacing = 0.5; // Spacing between plants in world units
|
||||||
return section.positions.map((pos: Position3D) => ({
|
return section.positions.map((pos: Position3D) => ({
|
||||||
...pos,
|
...pos,
|
||||||
x: section.posX + (pos.column * spacing),
|
// Position plants within the scaled section bounds
|
||||||
z: section.posY + (pos.row * spacing),
|
x: scaledSection.posX + (pos.column * plantSpacing),
|
||||||
|
z: scaledSection.posY + (pos.row * plantSpacing),
|
||||||
y: 0.4 + (pos.tier * 0.6),
|
y: 0.4 + (pos.tier * 0.6),
|
||||||
}));
|
}));
|
||||||
}, [section]);
|
}, [section, scaledSection]);
|
||||||
|
|
||||||
const distinctTiers = [...new Set(positions.map(p => p.tier))].sort((a, b) => a - b);
|
const distinctTiers = [...new Set(positions.map(p => p.tier))].sort((a, b) => a - b);
|
||||||
const distinctRows = [...new Set(positions.map(p => p.row))].sort((a, b) => a - b);
|
const distinctRows = [...new Set(positions.map(p => p.row))].sort((a, b) => a - b);
|
||||||
const distinctCols = [...new Set(positions.map(p => p.column))].sort((a, b) => a - b);
|
const distinctCols = [...new Set(positions.map(p => p.column))].sort((a, b) => a - b);
|
||||||
|
|
||||||
const spacing = 0.5;
|
const plantSpacing = 0.5;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{visMode === 'STANDARD' && (
|
{visMode === 'STANDARD' && (
|
||||||
<Text
|
<Text
|
||||||
position={[section.posX + section.width / 2, 3, section.posY + section.height / 2]}
|
position={[scaledSection.posX + scaledSection.width / 2, 3, scaledSection.posY + scaledSection.height / 2]}
|
||||||
fontSize={0.8}
|
fontSize={0.8}
|
||||||
color="#94a3b8"
|
color="#94a3b8"
|
||||||
anchorX="center"
|
anchorX="center"
|
||||||
|
|
@ -50,14 +62,14 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
|
||||||
<mesh
|
<mesh
|
||||||
key={`shelf-${tier}`}
|
key={`shelf-${tier}`}
|
||||||
position={[
|
position={[
|
||||||
section.posX + section.width / 2,
|
scaledSection.posX + scaledSection.width / 2,
|
||||||
0.35 + (tier * 0.6),
|
0.35 + (tier * 0.6),
|
||||||
section.posY + section.height / 2
|
scaledSection.posY + scaledSection.height / 2
|
||||||
]}
|
]}
|
||||||
rotation={[-Math.PI / 2, 0, 0]}
|
rotation={[-Math.PI / 2, 0, 0]}
|
||||||
receiveShadow
|
receiveShadow
|
||||||
>
|
>
|
||||||
<planeGeometry args={[section.width * 0.95, section.height * 0.95]} />
|
<planeGeometry args={[scaledSection.width * 0.95, scaledSection.height * 0.95]} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial
|
||||||
color="#64748b"
|
color="#64748b"
|
||||||
roughness={0.3}
|
roughness={0.3}
|
||||||
|
|
@ -72,9 +84,9 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
|
||||||
<mesh
|
<mesh
|
||||||
key={`support-${xOffset}-${zOffset}`}
|
key={`support-${xOffset}-${zOffset}`}
|
||||||
position={[
|
position={[
|
||||||
section.posX + (xOffset * section.width),
|
scaledSection.posX + (xOffset * scaledSection.width),
|
||||||
(distinctTiers.length * 0.6) / 2 + 0.2,
|
(distinctTiers.length * 0.6) / 2 + 0.2,
|
||||||
section.posY + (zOffset * section.height)
|
scaledSection.posY + (zOffset * scaledSection.height)
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<boxGeometry args={[0.08, distinctTiers.length * 0.6 + 0.3, 0.08]} />
|
<boxGeometry args={[0.08, distinctTiers.length * 0.6 + 0.3, 0.08]} />
|
||||||
|
|
@ -86,7 +98,7 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
|
||||||
{visMode === 'STANDARD' && distinctRows.map(row => (
|
{visMode === 'STANDARD' && distinctRows.map(row => (
|
||||||
<Text
|
<Text
|
||||||
key={`row-${row}`}
|
key={`row-${row}`}
|
||||||
position={[section.posX - 0.3, 0.8, section.posY + row * spacing]}
|
position={[scaledSection.posX - 0.3, 0.8, scaledSection.posY + row * plantSpacing]}
|
||||||
rotation={[-Math.PI / 2, 0, 0]}
|
rotation={[-Math.PI / 2, 0, 0]}
|
||||||
fontSize={0.18}
|
fontSize={0.18}
|
||||||
color="#64748b"
|
color="#64748b"
|
||||||
|
|
@ -99,7 +111,7 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim
|
||||||
{visMode === 'STANDARD' && distinctCols.map(col => (
|
{visMode === 'STANDARD' && distinctCols.map(col => (
|
||||||
<Text
|
<Text
|
||||||
key={`col-${col}`}
|
key={`col-${col}`}
|
||||||
position={[section.posX + col * spacing, 0.15, section.posY + section.height + 0.2]}
|
position={[scaledSection.posX + col * plantSpacing, 0.15, scaledSection.posY + scaledSection.height + 0.2]}
|
||||||
rotation={[-Math.PI / 2, 0, 0]}
|
rotation={[-Math.PI / 2, 0, 0]}
|
||||||
fontSize={0.18}
|
fontSize={0.18}
|
||||||
color="#64748b"
|
color="#64748b"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue