some tweaks to the 3d bin and nodes, finished up the table view
This commit is contained in:
parent
325d3ea7a0
commit
619f9b7bf4
9 changed files with 291 additions and 146 deletions
|
|
@ -21,7 +21,7 @@ export interface NodeData {
|
|||
}
|
||||
|
||||
export function BuildNodeData(cables: CableData[]): NodeData[] {
|
||||
const nodeRadius = 20
|
||||
const nodeRadius = 15
|
||||
let nodeData: NodeData[] = []
|
||||
|
||||
cables.forEach((cable, cableIndex) => {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { pond } from "protobuf-ts/pond";
|
|||
import GrainCableFill from "../Systems/Inventory/GrainCableFill";
|
||||
import TempHeatMap from "../Systems/Heatmap/TempHeatMap";
|
||||
import NodePointCloud from "../Systems/Heatmap/NodePointCloud";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
|
|
@ -35,8 +36,6 @@ interface Props {
|
|||
export default function Bin3dView(props: Props) {
|
||||
const { bin, scale = 100, fillPercent, nodeClick, showTempHeatmap, showGrain, showHotspots, showResetButton, showMoistureHeatmap, showTemp, showMoisture } = props
|
||||
|
||||
const binCenter = useMemo(() => new Vector3(0, 0, 0), []);
|
||||
|
||||
// Compute the initial camera radius so the full bin fits in frame.
|
||||
// Uses the perspective camera FOV (default 75°) with a padding factor.
|
||||
// Both the diameter and total height are considered so neither is clipped.
|
||||
|
|
@ -129,7 +128,6 @@ export default function Bin3dView(props: Props) {
|
|||
cableData={cableData}
|
||||
nodeData={nodeData}
|
||||
bin={bin}
|
||||
binCenter={binCenter}
|
||||
onNodeClick={(node, cable) => {
|
||||
if (nodeClick) nodeClick(node, cable)
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import GrainCable from "./GrainCable";
|
||||
import { Vector3 } from "three";
|
||||
import { Bin } from "models";
|
||||
import React from "react";
|
||||
import { CableData } from "../../Data/BuildCableData";
|
||||
import { NodeData } from "../../Data/BuildNodeData";
|
||||
|
||||
interface Props {
|
||||
cableData: CableData[]
|
||||
nodeData: NodeData[]
|
||||
bin: Bin
|
||||
binCenter: Vector3,
|
||||
cableData: CableData[],
|
||||
nodeData: NodeData[],
|
||||
bin: Bin,
|
||||
renderOrder?: number,
|
||||
displayTemp?: boolean,
|
||||
displayMoisture?: boolean,
|
||||
|
|
@ -17,7 +15,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export default function BinCables(props: Props){
|
||||
const {bin, binCenter, cableData, nodeData, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
|
||||
const {bin, cableData, nodeData, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
|
||||
return (
|
||||
<React.Fragment>
|
||||
{cableData.map((cable, i) => {
|
||||
|
|
@ -29,7 +27,6 @@ export default function BinCables(props: Props){
|
|||
cable={cable}
|
||||
nodes={cableNodes}
|
||||
bin={bin}
|
||||
binCenter={binCenter}
|
||||
displayTemp={displayTemp}
|
||||
displayMoisture={displayMoisture}
|
||||
onNodeClick={(node) => {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,18 @@
|
|||
import Sphere, { SphereGeometry } from "3dModels/Shapes/3D/Sphere"
|
||||
import { useFrame, useThree } from "@react-three/fiber"
|
||||
import { Billboard, Text } from "@react-three/drei"
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { Text } from "@react-three/drei"
|
||||
import React, { useMemo } from "react"
|
||||
import { Euler, Group, Vector3 } from "three"
|
||||
import { describeMeasurement } from "pbHelpers/MeasurementDescriber"
|
||||
import { quack } from "protobuf-ts/quack"
|
||||
import { green, grey, orange, red } from "@mui/material/colors";
|
||||
import { useGlobalState } from "providers"
|
||||
import { pond } from "protobuf-ts/pond"
|
||||
import Ring, { RingGeometry } from "3dModels/Shapes/3D/Ring"
|
||||
import { NodeData } from "../../Data/BuildNodeData"
|
||||
import { TempToColour } from "bin/3dView/utils/tempToColour"
|
||||
|
||||
interface Props {
|
||||
node: NodeData
|
||||
binCenter: Vector3
|
||||
lowerThreshold: number
|
||||
upperThreshold: number,
|
||||
targetMoisture: number,
|
||||
renderOrder?: number,
|
||||
displayTemp?: boolean,
|
||||
displayMoisture?: boolean,
|
||||
|
|
@ -23,33 +20,31 @@ interface Props {
|
|||
}
|
||||
|
||||
export default function CableNode(props: Props) {
|
||||
const { node, binCenter, lowerThreshold, upperThreshold, onNodeClick, renderOrder, displayTemp, displayMoisture } = props
|
||||
const { node, upperThreshold, targetMoisture, onNodeClick, renderOrder, displayTemp, displayMoisture } = props
|
||||
const { camera } = useThree();
|
||||
const [{ user }] = useGlobalState();
|
||||
// const [showLabel, setShowLabel] = useState(false);
|
||||
const labelGroupRef = React.useRef<Group>(null);
|
||||
const ROW_HEIGHT = 35;
|
||||
const PADDING = 20;
|
||||
const ROW_HEIGHT = 45;
|
||||
const PADDING = 15;
|
||||
const LABEL_WIDTH = 220;
|
||||
const flagRef = React.useRef<Group>(null);
|
||||
|
||||
//this can be used to control label visibility through the cameras zoom level
|
||||
// useFrame(() => {
|
||||
// const distance = camera.position.distanceTo(binCenter)
|
||||
|
||||
// const SHOW_DISTANCE = 15;
|
||||
// const HIDE_DISTANCE = 16;
|
||||
|
||||
// if (!showLabel && distance < SHOW_DISTANCE) {
|
||||
// setShowLabel(true);
|
||||
// } else if (showLabel && distance > HIDE_DISTANCE) {
|
||||
// setShowLabel(false);
|
||||
// }
|
||||
|
||||
// const scale = distance * 0.07;
|
||||
|
||||
// if (labelGroupRef.current) {
|
||||
// labelGroupRef.current.scale.set(scale, scale, 1);
|
||||
// }
|
||||
// });
|
||||
useFrame(() => {
|
||||
if (!flagRef.current || !showLabel) return;
|
||||
|
||||
// Get camera's right direction in world space
|
||||
const cameraRight = new Vector3();
|
||||
camera.getWorldDirection(cameraRight); // forward
|
||||
const up = new Vector3(0, 1, 0);
|
||||
cameraRight.crossVectors(cameraRight, up).normalize(); // right = forward × up
|
||||
|
||||
const pos = node.position.clone();
|
||||
pos.addScaledVector(cameraRight, LABEL_WIDTH / 2); // offset right in screen space
|
||||
pos.addScaledVector(camera.position.clone().sub(node.position).normalize(), 1); // nudge toward camera
|
||||
|
||||
flagRef.current.position.copy(pos);
|
||||
flagRef.current.quaternion.copy(camera.quaternion);
|
||||
});
|
||||
|
||||
const tempDisplay = useMemo(() => {
|
||||
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
||||
|
|
@ -63,7 +58,7 @@ export default function CableNode(props: Props) {
|
|||
|
||||
if (node.excluded) {
|
||||
if (!anyToggleOn) return [];
|
||||
return [{ text: "Excluded", color: "red" }];
|
||||
return [{ text: "Excluded", color: grey[700] }];
|
||||
}
|
||||
|
||||
const r: { text: string; color: string }[] = [];
|
||||
|
|
@ -72,7 +67,9 @@ export default function CableNode(props: Props) {
|
|||
if (displayTemp && node.celcius !== 0) {
|
||||
r.push({
|
||||
text: tempDisplay,
|
||||
color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour()
|
||||
// color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour()
|
||||
//will be green when below threshold, orange when it goeas above but by less than 5 degrees, and red if more than 5 over
|
||||
color: node.celcius > upperThreshold ? node.celcius > upperThreshold + 5 ? red[700] : orange[800]: green[800]
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -81,21 +78,25 @@ export default function CableNode(props: Props) {
|
|||
if (node.moisture && node.moisture > 0) {
|
||||
r.push({
|
||||
text: `${node.moisture.toFixed(2)}% EMC`,
|
||||
color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour()
|
||||
// color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour()
|
||||
color: node.moisture > targetMoisture ? node.moisture > targetMoisture + 0.5 ? red[700] : orange[800]: green[800]
|
||||
});
|
||||
} else if (node.humidity && node.humidity > 0) {
|
||||
r.push({
|
||||
text: `${node.humidity.toFixed(2)}%`,
|
||||
color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT).colour()
|
||||
});
|
||||
}
|
||||
}
|
||||
//apparently we dont want to display humidity at all
|
||||
// else if (node.humidity && node.humidity > 0) {
|
||||
// r.push({
|
||||
// text: `${node.humidity.toFixed(2)}%`,
|
||||
// // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT).colour()
|
||||
// color: "black"
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
const hasData = r.length > 0;
|
||||
|
||||
// Only show top node if there's actual data
|
||||
if (node.topNode && hasData) {
|
||||
r.unshift({ text: "Top Node", color: "yellow" });
|
||||
r.unshift({ text: "Top Node", color: grey[700] });
|
||||
}
|
||||
|
||||
return r;
|
||||
|
|
@ -117,12 +118,8 @@ export default function CableNode(props: Props) {
|
|||
|
||||
const topNodeRotation = React.useMemo(() => new Euler(-Math.PI / 2, 0, 0), []);
|
||||
|
||||
const labelPosition = node.position.clone();
|
||||
const labelHeight = rows.length * ROW_HEIGHT + PADDING;
|
||||
|
||||
const dir = camera.position.clone().sub(node.position).normalize();
|
||||
labelPosition.add(dir.multiplyScalar(1));
|
||||
|
||||
const getRowY = (index: number) => {
|
||||
const totalHeight = rows.length * ROW_HEIGHT;
|
||||
return totalHeight / 2 - (index * ROW_HEIGHT) - ROW_HEIGHT / 2;
|
||||
|
|
@ -130,9 +127,7 @@ export default function CableNode(props: Props) {
|
|||
|
||||
const nodeColour = () => {
|
||||
if (node.excluded) return "grey";
|
||||
if (!node.inGrain) return "white";
|
||||
const c = TempToColour(node, lowerThreshold, upperThreshold, "node");
|
||||
return c !== null ? c.getStyle() : "white";
|
||||
return "white";
|
||||
};
|
||||
|
||||
const handleClick = (e: any) => {
|
||||
|
|
@ -144,49 +139,46 @@ export default function CableNode(props: Props) {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{(!node.inGrain || !showLabel) &&
|
||||
<React.Fragment>
|
||||
<Sphere
|
||||
geometry={geometry}
|
||||
position={node.position}
|
||||
colour={nodeColour()}
|
||||
onClick={handleClick}
|
||||
renderOrder={renderOrder}
|
||||
|
||||
/>
|
||||
{node.topNode && (
|
||||
<Ring geometry={topNodeGeo} position={topNodePosition} rotation={topNodeRotation} renderOrder={renderOrder}/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
<React.Fragment>
|
||||
<Sphere
|
||||
geometry={geometry}
|
||||
position={node.position}
|
||||
colour={nodeColour()}
|
||||
onClick={handleClick}
|
||||
renderOrder={renderOrder}
|
||||
|
||||
/>
|
||||
{node.topNode && (
|
||||
<Ring geometry={topNodeGeo} position={topNodePosition} rotation={topNodeRotation} renderOrder={renderOrder}/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
{node.inGrain && showLabel &&
|
||||
<Billboard position={labelPosition}>
|
||||
<group ref={labelGroupRef} renderOrder={999} onClick={handleClick}>
|
||||
<mesh position={[0, 0, -1]}>
|
||||
<planeGeometry args={[200, labelHeight]} />
|
||||
<meshBasicMaterial
|
||||
color="white"
|
||||
transparent
|
||||
opacity={0.9}
|
||||
depthWrite={false}
|
||||
depthTest={false}
|
||||
/>
|
||||
</mesh>
|
||||
{rows.map((row, i) => (
|
||||
<Text
|
||||
key={i}
|
||||
position={[0, getRowY(i), 0]}
|
||||
fontSize={30}
|
||||
color={row.color}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{row.text}
|
||||
</Text>
|
||||
))}
|
||||
</group>
|
||||
</Billboard>
|
||||
}
|
||||
<group ref={flagRef} renderOrder={999} onClick={handleClick}>
|
||||
<mesh position={[0, 0, -1]}>
|
||||
<planeGeometry args={[LABEL_WIDTH, labelHeight]} />
|
||||
<meshBasicMaterial
|
||||
color="white"
|
||||
transparent
|
||||
opacity={0.9}
|
||||
depthWrite={false}
|
||||
depthTest={false}
|
||||
/>
|
||||
</mesh>
|
||||
{rows.map((row, i) => (
|
||||
<Text
|
||||
key={i}
|
||||
position={[0, getRowY(i), 0]}
|
||||
fontSize={35}
|
||||
fontWeight={650}
|
||||
color={row.color}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{row.text}
|
||||
</Text>
|
||||
))}
|
||||
</group>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
@ -7,17 +7,16 @@ import { CableData } from "../../Data/BuildCableData"
|
|||
import { NodeData } from "../../Data/BuildNodeData"
|
||||
|
||||
interface Props {
|
||||
cable: CableData
|
||||
nodes: NodeData[]
|
||||
bin: Bin
|
||||
binCenter: Vector3,
|
||||
cable: CableData,
|
||||
nodes: NodeData[],
|
||||
bin: Bin,
|
||||
renderOrder?: number,
|
||||
displayTemp?: boolean,
|
||||
displayMoisture?: boolean
|
||||
displayMoisture?: boolean,
|
||||
onNodeClick?: (node: NodeData) => void
|
||||
}
|
||||
export default function GrainCable(props: Props) {
|
||||
const {cable, nodes, bin, binCenter, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
|
||||
const {cable, nodes, bin, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
|
||||
|
||||
const calculateHeight = () => {
|
||||
return cable.topPosition.distanceTo(cable.bottomPosition)
|
||||
|
|
@ -44,7 +43,7 @@ export default function GrainCable(props: Props) {
|
|||
<React.Fragment>
|
||||
<Cylinder geometry={geometry} position={calculateCenter()} opacity={0.5} renderOrder={renderOrder} depthWrite={false} depthTest={false}/>
|
||||
{nodes.map((node, i) => (
|
||||
<CableNode displayTemp={displayTemp} displayMoisture={displayMoisture} onNodeClick={onNodeClick} key={"node " + i} renderOrder={renderOrder} node={node} binCenter={binCenter} lowerThreshold={bin.lowerTempThreshold()} upperThreshold={bin.upperTempThreshold()}/>
|
||||
<CableNode displayTemp={displayTemp} displayMoisture={displayMoisture} onNodeClick={onNodeClick} key={"node " + i} renderOrder={renderOrder} node={node} targetMoisture={bin.targetMoisture()} upperThreshold={bin.upperTempThreshold()}/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue