mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: card
This commit is contained in:
1 parent
ce3c9b936b
commit
c12fb0d668
5 files changed
+88
-8
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
import { Canvas } from '@react-three/fiber';
|
import { Canvas } from '@react-three/fiber';
|
||||||
import { Suspense, use, useEffect, useRef } from 'react';
|
import { Suspense, use, useEffect, useRef } from 'react';
|
||||||
import { Armor } from '../../../../../../components/Armor';
|
import { Armor } from '../../../../../../components/Armor';
|
||||||
|
import { ArmorPlateDisplay } from '../../../../../../components/Armor/components/ArmorPlateDisplay';
|
||||||
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
|
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
|
||||||
import { StaticArmor } from '../../../../../../components/StaticArmor';
|
import { StaticArmor } from '../../../../../../components/StaticArmor';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
@@ -157,6 +158,9 @@ export function TankSandbox() {
|
|||||||
<SceneProps />
|
<SceneProps />
|
||||||
{display !== TankopediaDisplay.StaticArmor && <TankModel />}
|
{display !== TankopediaDisplay.StaticArmor && <TankModel />}
|
||||||
|
|
||||||
|
<ShotDisplay />
|
||||||
|
<ArmorPlateDisplay />
|
||||||
|
|
||||||
{/* idk why the shot display doesn't work without suspense here lol */}
|
{/* idk why the shot display doesn't work without suspense here lol */}
|
||||||
<Suspense fallback={<ModelLoader />}>
|
<Suspense fallback={<ModelLoader />}>
|
||||||
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
||||||
@@ -165,8 +169,6 @@ export function TankSandbox() {
|
|||||||
)}
|
)}
|
||||||
<Lighting />
|
<Lighting />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<ShotDisplay />
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Card, Flex, Text } from '@radix-ui/themes';
|
||||||
|
import { Html } from '@react-three/drei';
|
||||||
|
import { radToDeg } from 'three/src/math/MathUtils';
|
||||||
|
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
|
||||||
|
import { layerTypeNames } from './ShotDisplayCard';
|
||||||
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
|
|
||||||
|
export function ArmorPlateDisplay() {
|
||||||
|
const highlightArmor = TankopediaEphemeral.use(
|
||||||
|
(state) => state.highlightArmor,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (highlightArmor === undefined) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group position={highlightArmor.point}>
|
||||||
|
<Html>
|
||||||
|
<Card
|
||||||
|
ml="9"
|
||||||
|
style={{ whiteSpace: 'nowrap', color: highlightArmor.color }}
|
||||||
|
>
|
||||||
|
<Flex direction="column">
|
||||||
|
<Text weight="bold">
|
||||||
|
{layerTypeNames[highlightArmor.type]}{' '}
|
||||||
|
{highlightArmor.thickness.toFixed(0)}
|
||||||
|
<Text size="1" weight="regular">
|
||||||
|
mm
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
{highlightArmor.type !== ArmorType.External && (
|
||||||
|
<Text size="2" color="gray">
|
||||||
|
{highlightArmor.thicknessAngled.toFixed(0)}
|
||||||
|
<Text size="1">
|
||||||
|
mm {radToDeg(highlightArmor.angle).toFixed(0)}°
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
</Html>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import { shotStatusColors } from './ShotDisplay';
|
|||||||
import { ArmorType } from './SpacedArmorScene';
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
import { ExternalModuleVariant } from './SpacedArmorSceneComponent';
|
import { ExternalModuleVariant } from './SpacedArmorSceneComponent';
|
||||||
|
|
||||||
const layerTypeNames: Record<ArmorType | 'null', string> = {
|
export const layerTypeNames: Record<ArmorType | 'null', string> = {
|
||||||
[ArmorType.Core]: 'Core',
|
[ArmorType.Core]: 'Core',
|
||||||
[ArmorType.Spaced]: 'Spaced',
|
[ArmorType.Spaced]: 'Spaced',
|
||||||
[ArmorType.External]: 'External',
|
[ArmorType.External]: 'External',
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { clamp } from 'lodash';
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Color,
|
Color,
|
||||||
|
DoubleSide,
|
||||||
|
FrontSide,
|
||||||
Intersection,
|
Intersection,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
MeshStandardMaterial,
|
MeshStandardMaterial,
|
||||||
@@ -74,6 +76,7 @@ const omitMaterial = new MeshBasicMaterial({
|
|||||||
depthWrite: true,
|
depthWrite: true,
|
||||||
});
|
});
|
||||||
const unselectedColor = new Color(0x404040);
|
const unselectedColor = new Color(0x404040);
|
||||||
|
const externalModuleColor = new Color(0, 1 / 4, 1 / 2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render orders allocations:
|
* Render orders allocations:
|
||||||
@@ -395,7 +398,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ArmorType.External:
|
case ArmorType.External:
|
||||||
color = new Color(0, 1, 1);
|
color = externalModuleColor;
|
||||||
opacity = 1 / 8;
|
opacity = 1 / 8;
|
||||||
renderOrder = 0;
|
renderOrder = 0;
|
||||||
depthWrite = false;
|
depthWrite = false;
|
||||||
@@ -427,9 +430,10 @@ export function SpacedArmorSceneComponent({
|
|||||||
if (selectedName === undefined) {
|
if (selectedName === undefined) {
|
||||||
// nothing selected, go back to defaults
|
// nothing selected, go back to defaults
|
||||||
material.opacity = opacity;
|
material.opacity = opacity;
|
||||||
material.transparent = true;
|
material.transparent = opacity < 1;
|
||||||
material.color = color;
|
material.color = color;
|
||||||
material.depthWrite = props.type !== ArmorType.External;
|
material.depthWrite = props.type !== ArmorType.External;
|
||||||
|
material.side = FrontSide;
|
||||||
} else if (
|
} else if (
|
||||||
selectedName === props.name ||
|
selectedName === props.name ||
|
||||||
(props.name.startsWith('chassis_') &&
|
(props.name.startsWith('chassis_') &&
|
||||||
@@ -444,17 +448,19 @@ export function SpacedArmorSceneComponent({
|
|||||||
material.transparent = false;
|
material.transparent = false;
|
||||||
material.color = color;
|
material.color = color;
|
||||||
material.depthWrite = true;
|
material.depthWrite = true;
|
||||||
|
material.side = DoubleSide;
|
||||||
} else {
|
} else {
|
||||||
// something else selected, become background
|
// something else selected, become background
|
||||||
material.opacity = 1 / 4;
|
material.opacity = 1 / 4;
|
||||||
material.transparent = true;
|
material.transparent = true;
|
||||||
material.color = unselectedColor;
|
material.color = unselectedColor;
|
||||||
material.depthWrite = props.type !== ArmorType.External;
|
material.depthWrite = props.type !== ArmorType.External;
|
||||||
|
material.side = FrontSide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribe = tankopediaEphemeralStore.subscribe(
|
const unsubscribe = tankopediaEphemeralStore.subscribe(
|
||||||
(state) => state.highlightArmor,
|
(state) => state.highlightArmor?.name,
|
||||||
handleHighlightArmor,
|
handleHighlightArmor,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -474,8 +480,25 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
onClick(event) {
|
onClick(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const { point } = event;
|
||||||
|
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||||
|
const surfaceNormal = event
|
||||||
|
.normal!.clone()
|
||||||
|
.applyQuaternion(event.object.getWorldQuaternion(new Quaternion()));
|
||||||
|
const angle = surfaceNormal.angleTo(cameraNormal);
|
||||||
|
const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle);
|
||||||
|
|
||||||
mutateTankopediaEphemeralStore((draft) => {
|
mutateTankopediaEphemeralStore((draft) => {
|
||||||
draft.highlightArmor = props.name;
|
draft.highlightArmor = {
|
||||||
|
type: props.type,
|
||||||
|
name: props.name,
|
||||||
|
point,
|
||||||
|
thickness,
|
||||||
|
thicknessAngled,
|
||||||
|
angle,
|
||||||
|
color: `#${color.getHexString()}`,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,19 @@ interface TankopediaEphemeral {
|
|||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
controlsEnabled: boolean;
|
controlsEnabled: boolean;
|
||||||
highlightArmor?: string;
|
highlightArmor?: {
|
||||||
|
name: string;
|
||||||
|
point: Vector3;
|
||||||
|
thickness: number;
|
||||||
|
color: string;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
type: ArmorType.Core | ArmorType.Spaced;
|
||||||
|
thicknessAngled: number;
|
||||||
|
angle: number;
|
||||||
|
}
|
||||||
|
| { type: ArmorType.External }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShotLayerBase {
|
export interface ShotLayerBase {
|
||||||
|
|||||||
Reference in new issue
Block a user