mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: on click highlighting
This commit is contained in:
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}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in new issue
Block a user