mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
fix: directional clip
This commit is contained in:
1 parent
b559878667
commit
6bcf5c25dc
6 files changed
+149
-31
No files matched your search
Vendored
+2
@@ -41,6 +41,7 @@
|
|||||||
"CUBETEXCOORD",
|
"CUBETEXCOORD",
|
||||||
"customperiod",
|
"customperiod",
|
||||||
"datasource",
|
"datasource",
|
||||||
|
"Dava",
|
||||||
"dbid",
|
"dbid",
|
||||||
"Ddpf",
|
"Ddpf",
|
||||||
"Ddsd",
|
"Ddsd",
|
||||||
@@ -169,6 +170,7 @@
|
|||||||
"UNORM",
|
"UNORM",
|
||||||
"unpickled",
|
"unpickled",
|
||||||
"Unregistering",
|
"Unregistering",
|
||||||
|
"unrotate",
|
||||||
"vickers",
|
"vickers",
|
||||||
"vitepress",
|
"vitepress",
|
||||||
"Vovko",
|
"Vovko",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useThree } from '@react-three/fiber';
|
import { MeshProps, useThree } from '@react-three/fiber';
|
||||||
import { clamp } from 'lodash';
|
import { clamp } from 'lodash';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -12,8 +12,15 @@ import {
|
|||||||
Object3D,
|
Object3D,
|
||||||
Plane,
|
Plane,
|
||||||
Quaternion,
|
Quaternion,
|
||||||
|
Vector3,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
|
import { I_HAT, J_HAT } from '../../../constants/axis';
|
||||||
|
import { unrotateDavaVector } from '../../../core/blitz/unrotateDavaVector';
|
||||||
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
||||||
|
import {
|
||||||
|
modelTransformEvent,
|
||||||
|
ModelTransformEventData,
|
||||||
|
} from '../../../core/blitzkit/modelTransform';
|
||||||
import { discardClippingPlane } from '../../../core/three/discardClippingPlane';
|
import { discardClippingPlane } from '../../../core/three/discardClippingPlane';
|
||||||
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
|
||||||
import { ThicknessRange } from '../../StaticArmor';
|
import { ThicknessRange } from '../../StaticArmor';
|
||||||
@@ -25,10 +32,10 @@ import {
|
|||||||
|
|
||||||
type StaticArmorSceneComponentProps = {
|
type StaticArmorSceneComponentProps = {
|
||||||
name: string;
|
name: string;
|
||||||
clip?: Plane;
|
|
||||||
thickness: number;
|
thickness: number;
|
||||||
thicknessRange: ThicknessRange;
|
thicknessRange: ThicknessRange;
|
||||||
node: Object3D;
|
node: Object3D;
|
||||||
|
onPointerDown?: MeshProps['onPointerDown'];
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
type: Exclude<ArmorType, ArmorType.External>;
|
type: Exclude<ArmorType, ArmorType.External>;
|
||||||
@@ -37,7 +44,18 @@ type StaticArmorSceneComponentProps = {
|
|||||||
type: ArmorType.External;
|
type: ArmorType.External;
|
||||||
variant: ExternalModuleVariant;
|
variant: ExternalModuleVariant;
|
||||||
}
|
}
|
||||||
);
|
) &
|
||||||
|
(
|
||||||
|
| {
|
||||||
|
clip?: undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
clip: Plane;
|
||||||
|
hullOrigin: Vector3;
|
||||||
|
turretOrigin: Vector3;
|
||||||
|
gunOrigin: Vector3;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const unselectedColor = new Color(0x404040);
|
const unselectedColor = new Color(0x404040);
|
||||||
const externalModuleColor = new Color(0, 1 / 4, 1 / 2);
|
const externalModuleColor = new Color(0, 1 / 4, 1 / 2);
|
||||||
@@ -47,13 +65,13 @@ export function StaticArmorSceneComponent({
|
|||||||
thickness,
|
thickness,
|
||||||
thicknessRange,
|
thicknessRange,
|
||||||
node,
|
node,
|
||||||
clip,
|
onPointerDown,
|
||||||
...props
|
...props
|
||||||
}: StaticArmorSceneComponentProps) {
|
}: StaticArmorSceneComponentProps) {
|
||||||
const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
|
||||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
|
const scene = useThree((state) => state.scene);
|
||||||
const x = thickness / thicknessRange.value;
|
const x = thickness / thicknessRange.value;
|
||||||
const xClamped = clamp(x, 0, 1);
|
const xClamped = clamp(x, 0, 1);
|
||||||
let color: Color;
|
let color: Color;
|
||||||
@@ -95,7 +113,7 @@ export function StaticArmorSceneComponent({
|
|||||||
opacity,
|
opacity,
|
||||||
transparent: opacity < 1,
|
transparent: opacity < 1,
|
||||||
depthWrite,
|
depthWrite,
|
||||||
...(clip ? { clippingPlanes: [clip] } : {}),
|
...(props.clip ? { clippingPlanes: [props.clip] } : {}),
|
||||||
}),
|
}),
|
||||||
[thickness],
|
[thickness],
|
||||||
);
|
);
|
||||||
@@ -109,19 +127,6 @@ export function StaticArmorSceneComponent({
|
|||||||
[thickness],
|
[thickness],
|
||||||
);
|
);
|
||||||
|
|
||||||
// if (clip) {
|
|
||||||
// /**
|
|
||||||
// * hook inside an if statement?? don't panic! I assure you the clip prop
|
|
||||||
// * never mutates :)
|
|
||||||
// */
|
|
||||||
// useFrame(() => {
|
|
||||||
// surfaceMaterial.clippingPlanes!.forEach((plane) => {
|
|
||||||
// // console.log(plane.normal);
|
|
||||||
// plane.applyMatrix4(node.matrix);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleHighlightArmor(selectedName?: string) {
|
function handleHighlightArmor(selectedName?: string) {
|
||||||
if (selectedName === undefined) {
|
if (selectedName === undefined) {
|
||||||
@@ -160,18 +165,59 @@ export function StaticArmorSceneComponent({
|
|||||||
surfaceMaterial.needsUpdate = true;
|
surfaceMaterial.needsUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribe = tankopediaEphemeralStore.subscribe(
|
const unsubscribes = [
|
||||||
(state) => state.highlightArmor?.name,
|
tankopediaEphemeralStore.subscribe(
|
||||||
handleHighlightArmor,
|
(state) => state.highlightArmor?.name,
|
||||||
);
|
handleHighlightArmor,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
return unsubscribe;
|
if (props.clip) {
|
||||||
|
/**
|
||||||
|
* hook inside an if statement?? don't panic! I assure you the clip prop
|
||||||
|
* never mutates :)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { clip } = props;
|
||||||
|
const gunOrigin = unrotateDavaVector(props.gunOrigin.clone());
|
||||||
|
const neckOrigin = unrotateDavaVector(props.hullOrigin.clone()).add(
|
||||||
|
unrotateDavaVector(props.turretOrigin.clone()),
|
||||||
|
);
|
||||||
|
const barrelOrigin = neckOrigin.clone().add(gunOrigin);
|
||||||
|
const distanceToBarrel = clip.distanceToPoint(barrelOrigin);
|
||||||
|
const point = new Vector3();
|
||||||
|
|
||||||
|
function handleModelTransform(data: ModelTransformEventData) {
|
||||||
|
clip.normal
|
||||||
|
.set(0, 0, -1)
|
||||||
|
.applyAxisAngle(I_HAT, data.pitch)
|
||||||
|
.applyAxisAngle(J_HAT, data.yaw ?? 0);
|
||||||
|
clip.constant = 0;
|
||||||
|
|
||||||
|
point
|
||||||
|
.copy(gunOrigin)
|
||||||
|
.applyAxisAngle(J_HAT, data.yaw ?? 0)
|
||||||
|
.add(clip.normal.multiplyScalar(-distanceToBarrel))
|
||||||
|
.add(neckOrigin);
|
||||||
|
clip.normal.normalize();
|
||||||
|
clip.constant -= clip.distanceToPoint(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
modelTransformEvent.on(handleModelTransform);
|
||||||
|
|
||||||
|
unsubscribes.push(() => modelTransformEvent.off(handleModelTransform));
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
|
};
|
||||||
}, [thickness]);
|
}, [thickness]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{jsxTree(node, {
|
{jsxTree(node, {
|
||||||
material: surfaceMaterial,
|
material: surfaceMaterial,
|
||||||
|
onPointerDown,
|
||||||
userData: {
|
userData: {
|
||||||
type: props.type,
|
type: props.type,
|
||||||
variant: props.type === ArmorType.External ? props.variant : 'gun',
|
variant: props.type === ArmorType.External ? props.variant : 'gun',
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={hullOrigin} onPointerDown={onPointerDown}>
|
<group position={hullOrigin}>
|
||||||
<group key={node.uuid} position={turretOrigin}>
|
<group key={node.uuid} position={turretOrigin}>
|
||||||
<StaticArmorSceneComponent
|
<StaticArmorSceneComponent
|
||||||
name={node.name}
|
name={node.name}
|
||||||
@@ -206,6 +206,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
||||||
thickness={thickness}
|
thickness={thickness}
|
||||||
node={node}
|
node={node}
|
||||||
|
onPointerDown={onPointerDown}
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -289,7 +290,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={hullOrigin} onPointerDown={onPointerDown}>
|
<group position={hullOrigin}>
|
||||||
<group
|
<group
|
||||||
key={node.uuid}
|
key={node.uuid}
|
||||||
position={turretOrigin.clone().add(gunOrigin)}
|
position={turretOrigin.clone().add(gunOrigin)}
|
||||||
@@ -300,6 +301,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
type={spaced ? ArmorType.Spaced : ArmorType.Primary}
|
||||||
thickness={thickness}
|
thickness={thickness}
|
||||||
node={node}
|
node={node}
|
||||||
|
onPointerDown={onPointerDown}
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -315,6 +317,61 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
|
|
||||||
if (!isVisible || !showExternalModules) return null;
|
if (!isVisible || !showExternalModules) return null;
|
||||||
|
|
||||||
|
const position = new Vector2();
|
||||||
|
const delta = new Vector2();
|
||||||
|
let pitch = 0;
|
||||||
|
let yaw = 0;
|
||||||
|
|
||||||
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.controlsEnabled = false;
|
||||||
|
});
|
||||||
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.shot = undefined;
|
||||||
|
draft.highlightArmor = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
position.set(event.clientX, event.clientY);
|
||||||
|
pitch = protagonist.pitch;
|
||||||
|
yaw = protagonist.yaw;
|
||||||
|
|
||||||
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
async function handlePointerMove(event: PointerEvent) {
|
||||||
|
const duel = duelStore.getState();
|
||||||
|
const hasImprovedVerticalStabilizer = await hasEquipment(
|
||||||
|
122,
|
||||||
|
duel.protagonist.tank.equipment,
|
||||||
|
duel.protagonist.equipmentMatrix,
|
||||||
|
);
|
||||||
|
const boundingRect = canvas.getBoundingClientRect();
|
||||||
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
|
position.set(event.clientX, event.clientY);
|
||||||
|
|
||||||
|
[pitch, yaw] = applyPitchYawLimits(
|
||||||
|
pitch - delta.y * (Math.PI / boundingRect.height),
|
||||||
|
yaw + delta.x * (Math.PI / boundingRect.width),
|
||||||
|
gunModelDefinition.pitch,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
hasImprovedVerticalStabilizer,
|
||||||
|
);
|
||||||
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
|
}
|
||||||
|
function handlePointerUp() {
|
||||||
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.controlsEnabled = true;
|
||||||
|
});
|
||||||
|
mutateDuel((draft) => {
|
||||||
|
draft.protagonist.pitch = normalizeAngleRad(pitch);
|
||||||
|
draft.protagonist.yaw = normalizeAngleRad(yaw);
|
||||||
|
});
|
||||||
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StaticArmorSceneComponent
|
<StaticArmorSceneComponent
|
||||||
name={node.name}
|
name={node.name}
|
||||||
@@ -329,6 +386,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
? undefined
|
? undefined
|
||||||
: new Plane(new Vector3(0, 0, -1), -maskOrigin)
|
: new Plane(new Vector3(0, 0, -1), -maskOrigin)
|
||||||
}
|
}
|
||||||
|
hullOrigin={hullOrigin}
|
||||||
|
turretOrigin={turretOrigin}
|
||||||
|
gunOrigin={gunOrigin}
|
||||||
|
onPointerDown={onPointerDown}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Vector3, Vector3Tuple } from 'three';
|
import { Vector3, Vector3Tuple } from 'three';
|
||||||
import { I_HAT } from '../../constants/axis';
|
import { rotateDavaVector } from './rotateDavaVector';
|
||||||
|
|
||||||
export function correctZYTuple(tuple: Vector3Tuple) {
|
export function correctZYTuple(tuple: Vector3Tuple) {
|
||||||
return new Vector3(tuple[0], tuple[1], -tuple[2]).applyAxisAngle(
|
return rotateDavaVector(new Vector3(tuple[0], tuple[1], -tuple[2]));
|
||||||
I_HAT,
|
|
||||||
Math.PI / 2,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { Vector3 } from 'three';
|
||||||
|
import { I_HAT } from '../../constants/axis';
|
||||||
|
|
||||||
|
export function rotateDavaVector(vector: Vector3) {
|
||||||
|
return vector.applyAxisAngle(I_HAT, Math.PI / 2);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { Vector3 } from 'three';
|
||||||
|
import { I_HAT } from '../../constants/axis';
|
||||||
|
|
||||||
|
export function unrotateDavaVector(vector: Vector3) {
|
||||||
|
return vector.applyAxisAngle(I_HAT, -Math.PI / 2);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user