feat: on click highlighting

This commit is contained in:
TrèsAbhi committed 2024-07-27 14:21:12 -05:00
1 parent a0612b2beb
commit f0e8153a67
4 files changed
+74 -22

No files matched your search

@@ -132,6 +132,7 @@ export function TankSandbox() {
onPointerMissed={() => { onPointerMissed={() => {
mutateTankopediaEphemeral((draft) => { mutateTankopediaEphemeral((draft) => {
draft.shot = undefined; draft.shot = undefined;
draft.highlightArmor = undefined;
}); });
}} }}
style={{ userSelect: 'none' }} style={{ userSelect: 'none' }}
@@ -1,7 +1,8 @@
import { ThreeEvent, useThree } from '@react-three/fiber'; import { useThree } from '@react-three/fiber';
import { clamp } from 'lodash'; import { clamp } from 'lodash';
import { useCallback } from 'react'; import { useCallback, useEffect } from 'react';
import { import {
Color,
Intersection, Intersection,
MeshBasicMaterial, MeshBasicMaterial,
Object3D, Object3D,
@@ -30,6 +31,7 @@ function to255(value: number) {
} }
type SpacedArmorSceneComponentProps = { type SpacedArmorSceneComponentProps = {
name: string;
node: Object3D; node: Object3D;
thickness: number; thickness: number;
scene: Scene; scene: Scene;
@@ -70,6 +72,7 @@ const omitMaterial = new MeshBasicMaterial({
colorWrite: false, colorWrite: false,
depthWrite: true, depthWrite: true,
}); });
const unselectedColor = new Color(0x404040);
/** /**
* Render orders allocations: * Render orders allocations:
@@ -80,12 +83,14 @@ const omitMaterial = new MeshBasicMaterial({
*/ */
export function SpacedArmorSceneComponent({ export function SpacedArmorSceneComponent({
node, node,
name,
thickness, thickness,
scene, scene,
clip, clip,
...props ...props
}: SpacedArmorSceneComponentProps) { }: SpacedArmorSceneComponentProps) {
const tankopediaEphemeralStore = TankopediaEphemeral.useStore(); const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
const duelStore = Duel.useStore(); const duelStore = Duel.useStore();
@@ -369,61 +374,93 @@ export function SpacedArmorSceneComponent({
[camera], [camera],
); );
const onClick = useCallback(
async (event: ThreeEvent<MouseEvent>) => {
event.stopPropagation();
const shot = (await shoot(event.point, event.intersections, true))!;
tankopediaEphemeralStore.setState({ shot });
},
[camera],
);
if (props.static) { if (props.static) {
const x = clamp(thickness / props.thicknessRange.quartile, 0, 1); const x = clamp(thickness / props.thicknessRange.quartile, 0, 1);
const y = Math.sqrt(x); const y = Math.sqrt(x);
const r = -((1 - y) ** 2) + 1; const r = -((1 - y) ** 2) + 1;
const g = -(y ** 2) + 1; const g = -(y ** 2) + 1;
let color: string; let color: Color;
let opacity: number; let opacity: number;
let renderOrder = 1; let renderOrder = 1;
let depthWrite = true; let depthWrite = true;
switch (props.type) { switch (props.type) {
case ArmorType.Core: case ArmorType.Core:
color = `rgb(${to255(r)}, ${to255(g)}, 0)`; color = new Color(r, g, 0);
opacity = 1; opacity = 1;
break; break;
case ArmorType.Spaced: case ArmorType.Spaced:
color = `rgb(${to255(1 - y)}, 0, ${to255(0.75 * (1 - y))})`; color = new Color(1 - y, 0, 0.75 * (1 - y));
opacity = x + 0.5; opacity = x + 0.5;
break; break;
case ArmorType.External: case ArmorType.External:
color = `rgb(0, 255, 255)`; color = unselectedColor;
opacity = 1 / 8; opacity = 1 / 8;
renderOrder = 0; renderOrder = 0;
depthWrite = false; depthWrite = false;
break; break;
} }
return jsxTree(node, { opacity = clamp(opacity, 0, 1);
material: new MeshBasicMaterial({
const material = new MeshBasicMaterial({
color, color,
opacity, opacity,
transparent: opacity < 1, transparent: true,
depthWrite, depthWrite,
}), ...(clip ? { clippingPlanes: [clip] } : {}),
});
/**
* hook inside an if statement?? don't panic! I assure you the static prop
* never mutates :)
*/
useEffect(() => {
function handleHighlightArmor(thisName?: string) {
if (thisName === undefined) {
// nothing selected, go back to defaults
material.opacity = opacity;
material.color = color;
} else if (thisName === name) {
// this selected, stand out!
material.opacity = 1;
material.color = color;
} else {
// something else selected, become background
material.opacity = 1 / 4;
material.color = unselectedColor;
}
}
const unsubscribe = tankopediaEphemeralStore.subscribe(
(state) => state.highlightArmor,
handleHighlightArmor,
);
return unsubscribe;
});
return jsxTree(node, {
material,
renderOrder, renderOrder,
userData: { userData: {
type: props.type, type: props.type,
variant: props.type === ArmorType.External ? props.variant : 'gun', variant: props.type === ArmorType.External ? props.variant : 'gun',
thickness, thickness,
} satisfies ArmorUserData, } satisfies ArmorUserData,
onClick: props.type === ArmorType.Core ? onClick : () => {},
onClick:
props.type === ArmorType.External
? undefined
: (event) => {
event.stopPropagation();
mutateTankopediaEphemeralStore((draft) => {
draft.highlightArmor = name;
});
},
}); });
} }
@@ -439,7 +476,15 @@ export function SpacedArmorSceneComponent({
type: ArmorType.Core, type: ArmorType.Core,
thickness, thickness,
} satisfies ArmorUserData, } satisfies ArmorUserData,
onClick, async onClick(event) {
event.stopPropagation();
const shot = (await shoot(
event.point,
event.intersections,
true,
))!;
tankopediaEphemeralStore.setState({ shot });
},
}, },
node.uuid, node.uuid,
)} )}
@@ -130,6 +130,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
return ( return (
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange} thicknessRange={thicknessRange}
static static
scene={scene} scene={scene}
@@ -151,6 +152,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
return ( return (
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange} thicknessRange={thicknessRange}
static static
scene={scene} scene={scene}
@@ -181,6 +183,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
<group position={hullOrigin}> <group position={hullOrigin}>
<group key={node.uuid} position={turretOrigin}> <group key={node.uuid} position={turretOrigin}>
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange} thicknessRange={thicknessRange}
static static
scene={scene} scene={scene}
@@ -215,6 +218,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
position={turretOrigin.clone().add(gunOrigin)} position={turretOrigin.clone().add(gunOrigin)}
> >
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange} thicknessRange={thicknessRange}
static static
scene={scene} scene={scene}
@@ -238,6 +242,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
return ( return (
<SpacedArmorSceneComponent <SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange} thicknessRange={thicknessRange}
static static
scene={scene} scene={scene}
+1
View File
@@ -11,6 +11,7 @@ interface TankopediaEphemeral {
shot?: Shot; shot?: Shot;
skills: Record<string, number>; skills: Record<string, number>;
controlsEnabled: boolean; controlsEnabled: boolean;
highlightArmor?: string;
} }
export interface ShotLayerBase { export interface ShotLayerBase {