123 lines
No EOL
3.5 KiB
TypeScript
123 lines
No EOL
3.5 KiB
TypeScript
import React, { useMemo } from "react";
|
|
import Cylinder from "3dModels/Shapes/3D/Cylinder";
|
|
import Cone from "3dModels/Shapes/3D/Cone";
|
|
import { Vector3, Euler } from "three";
|
|
|
|
interface Props {
|
|
diameter: number;
|
|
sidewallHeight: number;
|
|
hopperHeight?: number;
|
|
fillPercent: number;
|
|
}
|
|
|
|
export default function GrainFillFlat(props: Props) {
|
|
const {
|
|
diameter,
|
|
sidewallHeight,
|
|
hopperHeight = 0,
|
|
fillPercent
|
|
} = props;
|
|
|
|
const radius = diameter / 2;
|
|
|
|
// Slightly smaller to avoid z-fighting with shell
|
|
const grainRadius = radius * 0.98;
|
|
|
|
// --- volumes ---
|
|
const cylinderVolume = Math.PI * radius * radius * sidewallHeight;
|
|
const hopperVolume = hopperHeight > 0
|
|
? (1 / 3) * Math.PI * radius * radius * hopperHeight
|
|
: 0;
|
|
|
|
const totalVolume = cylinderVolume + hopperVolume;
|
|
const filledVolume = totalVolume * fillPercent;
|
|
|
|
// --- result values ---
|
|
let hopperFillHeight = 0;
|
|
let cylinderFillHeight = 0;
|
|
|
|
if (hopperHeight > 0 && filledVolume <= hopperVolume) {
|
|
// ONLY hopper filling
|
|
const ratio = filledVolume / hopperVolume;
|
|
|
|
hopperFillHeight = hopperHeight * Math.pow(ratio, 1 / 3);
|
|
cylinderFillHeight = 0;
|
|
} else {
|
|
// hopper full (or no hopper)
|
|
hopperFillHeight = hopperHeight;
|
|
|
|
const remainingVolume = filledVolume - hopperVolume;
|
|
|
|
cylinderFillHeight = Math.max(
|
|
0,
|
|
remainingVolume / (Math.PI * radius * radius)
|
|
);
|
|
}
|
|
|
|
// --- positions ---
|
|
|
|
const hopperPosition = useMemo(
|
|
() => new Vector3(
|
|
0,
|
|
-(sidewallHeight / 2 + hopperHeight) + hopperFillHeight / 2, //adding the hopper height so it starts at the cone and not the flat side
|
|
0
|
|
),
|
|
[sidewallHeight, hopperFillHeight]
|
|
);
|
|
|
|
//flip the cone so it matches the hoppers rotation with the cone pointed down
|
|
const hopperRotation = useMemo(
|
|
() => new Euler(Math.PI, 0, 0),
|
|
[]
|
|
);
|
|
|
|
const cylinderPosition = useMemo(
|
|
() => new Vector3(
|
|
0,
|
|
-sidewallHeight / 2 + cylinderFillHeight / 2,
|
|
0
|
|
),
|
|
[sidewallHeight, cylinderFillHeight]
|
|
);
|
|
|
|
// --- material ---
|
|
const grainColour = "#fff302";
|
|
|
|
return (
|
|
<>
|
|
{/* Hopper fill */}
|
|
{hopperHeight > 0 && hopperFillHeight > 0 && (
|
|
<Cone
|
|
geometry={{
|
|
radius: grainRadius * (hopperFillHeight / hopperHeight),
|
|
height: hopperFillHeight,
|
|
radialSegments: 20,
|
|
openEnded: false
|
|
}}
|
|
position={hopperPosition}
|
|
rotation={hopperRotation}
|
|
colour={grainColour}
|
|
roughness={1}
|
|
metalness={0}
|
|
/>
|
|
)}
|
|
|
|
{/* Cylinder fill */}
|
|
{cylinderFillHeight > 0 && (
|
|
<Cylinder
|
|
geometry={{
|
|
radiusTop: grainRadius,
|
|
radiusBottom: grainRadius,
|
|
height: cylinderFillHeight,
|
|
radialSegments: 20,
|
|
openEnded: false
|
|
}}
|
|
position={cylinderPosition}
|
|
colour={grainColour}
|
|
roughness={1}
|
|
metalness={0}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
} |