mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: he splash visual
This commit is contained in:
1 parent
16500793a5
commit
8b5638f82d
4 files changed
+91
-11
No files matched your search
@@ -1,9 +1,21 @@
|
||||
import { Box, Flex, Text } from '@radix-ui/themes';
|
||||
import { Html } from '@react-three/drei';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { ComponentProps, useRef } from 'react';
|
||||
import { Euler, Mesh, Quaternion } from 'three';
|
||||
import { J_HAT } from '../../../constants/axis';
|
||||
import { clamp } from 'lodash';
|
||||
import { ComponentProps, useEffect, useRef } from 'react';
|
||||
import {
|
||||
BufferGeometry,
|
||||
DoubleSide,
|
||||
Euler,
|
||||
Group,
|
||||
Line,
|
||||
LineDashedMaterial,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
Path,
|
||||
Quaternion,
|
||||
} from 'three';
|
||||
import { J_HAT, K_HAT } from '../../../constants/axis';
|
||||
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
|
||||
import { ShotDisplayCard } from './ShotDisplayCard';
|
||||
|
||||
@@ -26,20 +38,65 @@ export function ShotDisplay() {
|
||||
const shot = TankopediaEphemeral.use((state) => state.shot);
|
||||
const inTracer = useRef<Mesh>(null);
|
||||
const outTracer = useRef<Mesh>(null);
|
||||
const splashRadiusWrapper = useRef<Group>(null);
|
||||
const splashRadiusMaterial = useRef<MeshBasicMaterial>(null);
|
||||
let animationStartTime: number | null = null;
|
||||
|
||||
useEffect(() => {
|
||||
if (shot && shot.splashRadius !== undefined) {
|
||||
const outlineGeometry = new BufferGeometry().setFromPoints(
|
||||
new Path().absarc(0, 0, 1, 0, Math.PI * 2).getSpacedPoints(50),
|
||||
);
|
||||
const outlineMaterial = new LineDashedMaterial({
|
||||
color: 'orange',
|
||||
dashSize: 0.05,
|
||||
gapSize: 0.025,
|
||||
});
|
||||
const outline = new Line(
|
||||
outlineGeometry,
|
||||
outlineMaterial,
|
||||
).computeLineDistances();
|
||||
|
||||
splashRadiusWrapper.current?.add(outline);
|
||||
|
||||
return () => {
|
||||
splashRadiusWrapper.current?.remove(outline);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!shot || !inTracer.current) return;
|
||||
if (!shot) return;
|
||||
|
||||
const t1 = clock.elapsedTime % 1;
|
||||
const t2 = (clock.elapsedTime + 0.5) % 1;
|
||||
const t = clock.elapsedTime;
|
||||
|
||||
inTracer.current.scale.set(1, 1 - 2 * Math.abs(t1 - 0.5), 1);
|
||||
inTracer.current.position.set(0, (1 - t1) * inLength, 0);
|
||||
if (inTracer.current) {
|
||||
const tracerT1 = t % 1;
|
||||
const tracerT2 = (t + 0.5) % 1;
|
||||
|
||||
if (!outTracer.current) return;
|
||||
inTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT1 - 0.5), 1);
|
||||
inTracer.current.position.set(0, (1 - tracerT1) * inLength, 0);
|
||||
|
||||
outTracer.current.scale.set(1, 1 - 2 * Math.abs(t2 - 0.5), 1);
|
||||
outTracer.current.position.set(0, t2 * outLength, 0);
|
||||
if (outTracer.current) {
|
||||
outTracer.current.scale.set(1, 1 - 2 * Math.abs(tracerT2 - 0.5), 1);
|
||||
outTracer.current.position.set(0, tracerT2 * outLength, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
shot.splashRadius !== undefined &&
|
||||
splashRadiusWrapper.current &&
|
||||
splashRadiusMaterial.current
|
||||
) {
|
||||
if (animationStartTime === null) animationStartTime = t;
|
||||
|
||||
const scale =
|
||||
clamp(4 * (t - animationStartTime), 0, 1) * shot.splashRadius;
|
||||
const opacity = 1 - clamp(2 * (t - animationStartTime), 0, 1);
|
||||
|
||||
splashRadiusWrapper.current.scale.set(scale, scale, scale);
|
||||
splashRadiusMaterial.current.opacity = opacity;
|
||||
}
|
||||
});
|
||||
|
||||
if (!shot) return null;
|
||||
@@ -68,6 +125,26 @@ export function ShotDisplay() {
|
||||
|
||||
return (
|
||||
<group>
|
||||
{shot.splashRadius && (
|
||||
<group
|
||||
position={inLast.point}
|
||||
ref={splashRadiusWrapper}
|
||||
rotation={new Euler().setFromQuaternion(
|
||||
new Quaternion().setFromUnitVectors(K_HAT, shot.in.surfaceNormal),
|
||||
)}
|
||||
>
|
||||
<mesh>
|
||||
<circleGeometry />
|
||||
<meshBasicMaterial
|
||||
side={DoubleSide}
|
||||
color="orange"
|
||||
transparent
|
||||
ref={splashRadiusMaterial}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
)}
|
||||
|
||||
<Html
|
||||
position={inLast.point}
|
||||
style={{
|
||||
|
||||
@@ -85,6 +85,7 @@ export function SpacedArmorSceneComponent({
|
||||
const shell = antagonist.shell;
|
||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||
const shot: TankopediaEphemeral.Shot = {
|
||||
splashRadius: shell.explosionRadius,
|
||||
damage: -1,
|
||||
containsGaps: shell.type === ShellType.HEAT,
|
||||
in: {
|
||||
|
||||
@@ -43,6 +43,7 @@ export type ShotLayer = ShotLayerExternal | ShotLayerNonExternal | ShotLayerGap;
|
||||
export type Shot = {
|
||||
containsGaps: boolean;
|
||||
damage: number;
|
||||
splashRadius?: number;
|
||||
|
||||
in: {
|
||||
surfaceNormal: Vector3;
|
||||
|
||||
Reference in new issue
Block a user