frontend/src/bin/3dView/Data/BuildCableData.ts

210 lines
No EOL
7.1 KiB
TypeScript

import { RadiansToDegrees } from "common/TrigFunctions";
import { Bin } from "models";
import { GrainCable } from "models/GrainCable";
import { pond } from "protobuf-ts/pond";
import { Vector3 } from "three";
import { avg } from "utils";
export interface CableData {
cableIndex: number
radius: number
topPosition: Vector3
bottomPosition: Vector3
grainCable: pond.GrainCable
radialSegments?: number
heightSegments?: number
}
export interface CableOrbit {
orbit: number;
radius: number; // in cm
expectedCount: number;
assignedCount?: number;
}
/**
* Effective bin dimensions used for cable placement.
* Passed in from Bin3dView so that default fallback values are applied
* consistently — cables will always match the rendered shell even when
* the bin has no advanced dimensions configured.
*/
export interface BinDims {
diameter: number
sidewallHeight: number
roofHeight: number
hopperHeight: number
}
function getLayoutFromDiameter(diameterCm: number): CableOrbit[] {
const diameterFt = diameterCm / 30.48;
let summaries: { Orbit: number; DistanceFromCenter: number; NumberOfCables: number }[] = [];
if (diameterFt < 24) {
summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
} else if (diameterFt < 36) {
summaries.push({ Orbit: 1, DistanceFromCenter: 7, NumberOfCables: 3 });
} else if (diameterFt < 42) {
summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
summaries.push({ Orbit: 1, DistanceFromCenter: 9, NumberOfCables: 3 });
} else if (diameterFt < 48) {
summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
summaries.push({ Orbit: 1, DistanceFromCenter: 14, NumberOfCables: 4 });
} else if (diameterFt < 54) {
summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
summaries.push({ Orbit: 1, DistanceFromCenter: 16, NumberOfCables: 5 });
} else if (diameterFt < 60) {
summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
summaries.push({ Orbit: 1, DistanceFromCenter: 18, NumberOfCables: 6 });
} else if (diameterFt < 72) {
summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
summaries.push({ Orbit: 1, DistanceFromCenter: 20, NumberOfCables: 7 });
} else if (diameterFt < 78) {
summaries.push({ Orbit: 1, DistanceFromCenter: 10, NumberOfCables: 4 });
summaries.push({ Orbit: 2, DistanceFromCenter: 27, NumberOfCables: 9 });
} else if (diameterFt < 90) {
summaries.push({ Orbit: 1, DistanceFromCenter: 10, NumberOfCables: 4 });
summaries.push({ Orbit: 2, DistanceFromCenter: 29, NumberOfCables: 10 });
} else if (diameterFt < 105) {
summaries.push({ Orbit: 0, DistanceFromCenter: 2, NumberOfCables: 1 });
summaries.push({ Orbit: 1, DistanceFromCenter: 18, NumberOfCables: 6 });
summaries.push({ Orbit: 2, DistanceFromCenter: 36, NumberOfCables: 12 });
} else {
summaries.push({ Orbit: 1, DistanceFromCenter: 9, NumberOfCables: 3 });
summaries.push({ Orbit: 2, DistanceFromCenter: 26.5, NumberOfCables: 9 });
summaries.push({ Orbit: 3, DistanceFromCenter: 44, NumberOfCables: 14 });
}
return summaries.map(s => ({
orbit: s.Orbit,
radius: s.DistanceFromCenter * 30.48, // ft → cm
expectedCount: s.NumberOfCables
}));
};
function distributeCables(cableCount: number, layout: CableOrbit[]) {
const totalExpected = layout.reduce((sum, o) => sum + o.expectedCount, 0);
let assigned = layout.map(o => ({
...o,
assignedCount: Math.round((o.expectedCount / totalExpected) * cableCount)
}));
// normalize rounding
let currentTotal = assigned.reduce((sum, o) => sum + (o.assignedCount ?? 0), 0);
while (currentTotal < cableCount) {
assigned[assigned.length - 1].assignedCount!++;
currentTotal++;
}
while (currentTotal > cableCount) {
assigned[assigned.length - 1].assignedCount!--;
currentTotal--;
}
return assigned;
};
function getTopY(radiusFromCenter: number, dims: BinDims) {
const binRadius = dims.diameter / 2;
const distanceFromEdge = binRadius - radiusFromCenter;
// Use the real roof angle when available; when dimensions aren't configured
// the angle will be 0 which gives a flat cable top — correct for a default shape.
const angleRad = Math.atan((dims.roofHeight) / (binRadius));
const roofRise = Math.tan(angleRad) * distanceFromEdge;
return dims.sidewallHeight / 2 + roofRise;
};
function getBottomY(radiusFromCenter: number, dims: BinDims) {
const binRadius = dims.diameter / 2;
const distanceFromCenter = binRadius - radiusFromCenter;
const angleRad = Math.atan((dims.hopperHeight) / (binRadius))
const hopperDrop = Math.tan(angleRad) * distanceFromCenter;
const clearance = 60; // cm
return -dims.sidewallHeight / 2 - hopperDrop + clearance;
};
export function BuildCableData(bin: Bin, dims: BinDims): CableData[] {
const cables = [...(bin.status.grainCables ?? [])];
if (cables.length === 0) return [];
// optional: sort cables (better visual consistency later)
//cables.sort((a, b) => b.celcius.length - a.celcius.length);
cables.sort((a, b) => {
//if the cables have the same number of nodes
if (b.celcius.length === a.celcius.length) {
//sort by temp only last and any type that has humidity first
if ((avg(a.relativeHumidity) === 0) && (avg(b.relativeHumidity) !== 0)) {
return 1;
} else if ((avg(b.relativeHumidity) === 0) && (avg(a.relativeHumidity) !== 0)) {
return -1;
}
else {
return a.key > b.key ? 1 : -1;
}
}
//otherwise sort by the longest cable first
return b.celcius.length - a.celcius.length;
});
const layout = getLayoutFromDiameter(dims.diameter);
const distributed = distributeCables(cables.length, layout);
const cableRadius = 3;
const result: CableData[] = [];
let cableIndex = 0;
distributed.forEach(orbit => {
const count = orbit.assignedCount ?? 0;
const topY = getTopY(orbit.radius, dims);
const bottomY = getBottomY(orbit.radius, dims);
if (orbit.orbit === 0 && count > 0) {
const cable = cables[cableIndex];
if (!cable) return;
result.push({
cableIndex: cableIndex,
radius: cableRadius,
topPosition: new Vector3(0, topY, 0),
bottomPosition: new Vector3(0, bottomY, 0),
grainCable: cable
});
cableIndex++;
return;
}
for (let i = 0; i < count; i++) {
const angle = (i / count) * Math.PI * 2;
const x = Math.cos(angle) * orbit.radius;
const z = Math.sin(angle) * orbit.radius;
const cable = cables[cableIndex];
result.push({
cableIndex: cableIndex,
radius: cableRadius,
topPosition: new Vector3(x, topY, z),
bottomPosition: new Vector3(x, bottomY, z),
grainCable: cable
});
cableIndex++;
}
});
return result;
};