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
+77 -25

No files matched your search

@@ -132,6 +132,7 @@ export function TankSandbox() {
onPointerMissed={() => {
mutateTankopediaEphemeral((draft) => {
draft.shot = undefined;
draft.highlightArmor = undefined;
});
}}
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 { useCallback } from 'react';
import { useCallback, useEffect } from 'react';
import {
Color,
Intersection,
MeshBasicMaterial,
Object3D,
@@ -30,6 +31,7 @@ function to255(value: number) {
}
type SpacedArmorSceneComponentProps = {
name: string;
node: Object3D;
thickness: number;
scene: Scene;
@@ -70,6 +72,7 @@ const omitMaterial = new MeshBasicMaterial({
colorWrite: false,
depthWrite: true,
});
const unselectedColor = new Color(0x404040);
/**
* Render orders allocations:
@@ -80,12 +83,14 @@ const omitMaterial = new MeshBasicMaterial({
*/
export function SpacedArmorSceneComponent({
node,
name,
thickness,
scene,
clip,
...props
}: SpacedArmorSceneComponentProps) {
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
const camera = useThree((state) => state.camera);
const duelStore = Duel.useStore();
@@ -369,61 +374,93 @@ export function SpacedArmorSceneComponent({
[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) {
const x = clamp(thickness / props.thicknessRange.quartile, 0, 1);
const y = Math.sqrt(x);
const r = -((1 - y) ** 2) + 1;
const g = -(y ** 2) + 1;
let color: string;
let color: Color;
let opacity: number;
let renderOrder = 1;
let depthWrite = true;
switch (props.type) {
case ArmorType.Core:
color = `rgb(${to255(r)}, ${to255(g)}, 0)`;
color = new Color(r, g, 0);
opacity = 1;
break;
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;
break;
case ArmorType.External:
color = `rgb(0, 255, 255)`;
color = unselectedColor;
opacity = 1 / 8;
renderOrder = 0;
depthWrite = false;
break;
}
opacity = clamp(opacity, 0, 1);
const material = new MeshBasicMaterial({
color,
opacity,
transparent: true,
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: new MeshBasicMaterial({
color,
opacity,
transparent: opacity < 1,
depthWrite,
}),
material,
renderOrder,
userData: {
type: props.type,
variant: props.type === ArmorType.External ? props.variant : 'gun',
thickness,
} 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,
thickness,
} satisfies ArmorUserData,
onClick,
async onClick(event) {
event.stopPropagation();
const shot = (await shoot(
event.point,
event.intersections,
true,
))!;
tankopediaEphemeralStore.setState({ shot });
},
},
node.uuid,
)}
@@ -130,6 +130,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
return (
<SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
static
scene={scene}
@@ -151,6 +152,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
return (
<SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
static
scene={scene}
@@ -181,6 +183,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
<group position={hullOrigin}>
<group key={node.uuid} position={turretOrigin}>
<SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
static
scene={scene}
@@ -215,6 +218,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
position={turretOrigin.clone().add(gunOrigin)}
>
<SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
static
scene={scene}
@@ -238,6 +242,7 @@ export const StaticArmorScene = memo<StaticArmorSceneProps>(
return (
<SpacedArmorSceneComponent
name={node.name}
thicknessRange={thicknessRange}
static
scene={scene}
+1
View File
@@ -11,6 +11,7 @@ interface TankopediaEphemeral {
shot?: Shot;
skills: Record<string, number>;
controlsEnabled: boolean;
highlightArmor?: string;
}
export interface ShotLayerBase {