Merge branch 'main' into ue

This commit is contained in:
TrèsAbhi committed 2026-02-18 19:19:21 -06:00
commit 5412c479c8
42 files changed
+1044 -934

No files matched your search

+3 -2
View File
@@ -32,12 +32,13 @@
"compare-versions": "^6.1.1",
"fuzzysort": "^3.1.0",
"immer": "^10.1.3",
"jimp": "^1.6.0",
"lodash-es": "^4.17.21",
"markdown-to-jsx": "^7.7.13",
"pwa-install-handler": "^2.6.3",
"quicklime": "^1.2.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-promise-suspense": "^0.3.4",
"three": "^0.180.0",
"three-stdlib": "^2.36.0",
@@ -1,5 +1,5 @@
import { I_HAT, J_HAT } from "@blitzkit/core";
import { useThree } from "@react-three/fiber";
import { invalidate, useThree } from "@react-three/fiber";
import { clamp } from "lodash-es";
import type { QuicklimeEvent } from "quicklime";
import { useEffect, useMemo, type ComponentProps } from "react";
@@ -91,7 +91,7 @@ export function StaticArmorSceneComponent({
color = new Color(
clamp(1 - (7 / 8) * xClamped, 0, 1),
0,
clamp(1 - (1 / 8) * xClamped, 0, 1)
clamp(1 - (1 / 8) * xClamped, 0, 1),
);
opacity = clamp(x + 1 / 2, 0, 1);
break;
@@ -115,7 +115,7 @@ export function StaticArmorSceneComponent({
opacity: 0,
userData: { opacity0: opacity },
}),
[thickness]
[thickness],
);
const outlineMaterial = useMemo(
() =>
@@ -126,7 +126,7 @@ export function StaticArmorSceneComponent({
opacity: 0,
transparent: true,
}),
[thickness]
[thickness],
);
useEffect(() => {
@@ -165,8 +165,11 @@ export function StaticArmorSceneComponent({
}
surfaceMaterial.needsUpdate = true;
invalidate();
}
handleHighlightArmor();
function handleTransitionEvent(event: QuicklimeEvent<number>) {
surfaceMaterial.opacity = surfaceMaterial.userData.opacity0 * event.data;
outlineMaterial.opacity = event.data;
@@ -180,7 +183,7 @@ export function StaticArmorSceneComponent({
const unsubscribes = [
Tankopedia.on(
(state) => state.highlightArmor?.name,
handleHighlightArmor
handleHighlightArmor,
),
() => transitionEvent.off(handleTransitionEvent),
];
@@ -194,14 +197,14 @@ export function StaticArmorSceneComponent({
const { clip } = props;
const gunOrigin = unrotateDavaVector(props.gunOrigin.clone());
const neckOrigin = unrotateDavaVector(props.hullOrigin.clone()).add(
unrotateDavaVector(props.turretOrigin.clone())
unrotateDavaVector(props.turretOrigin.clone()),
);
const barrelOrigin = neckOrigin.clone().add(gunOrigin);
const distanceToBarrel = clip.distanceToPoint(barrelOrigin);
const point = new Vector3();
function handleModelTransform(
event: QuicklimeEvent<ModelTransformEventData>
event: QuicklimeEvent<ModelTransformEventData>,
) {
clip.normal
.set(0, 0, -1)
@@ -266,7 +269,7 @@ export function StaticArmorSceneComponent({
const surfaceNormal = event
.normal!.clone()
.applyQuaternion(
event.object.getWorldQuaternion(new Quaternion())
event.object.getWorldQuaternion(new Quaternion()),
);
const angle = surfaceNormal.angleTo(cameraNormal);
const thicknessAngled =
@@ -38,7 +38,7 @@ export function ConsumablesManager({
timers
? consumable.duration === undefined
? undefined
: consumable.duration * (hasHighEndConsumables ? 1.7 : 1)
: consumable.duration * (hasHighEndConsumables ? 1.33 : 1)
: undefined
}
cooldown={
@@ -1,26 +1,3 @@
<script>
import locales from "@blitzkit/i18n/locales.json";
import { getBestLocale } from "../core/i18n/getBestLocale";
const isAPI = window.location.pathname.startsWith("/api");
const isI18nPath = locales.supported.some(({ locale }) =>
window.location.pathname.startsWith(`/${locale}/`)
);
const bestLocale = getBestLocale();
const currentPath = locales.supported.find(({ locale }) =>
window.location.pathname.startsWith(`/${locale}/`)
);
if (
!isAPI && // not an api, duh!
localStorage.getItem("preferred-locale") !== null && // no preference, so probably google's crawler
bestLocale !== currentPath?.locale // we need to redirect
) {
window.location.pathname = isI18nPath
? window.location.pathname.replace(
`/${currentPath?.locale}`,
bestLocale === undefined ? "" : `/${bestLocale}`
)
: `/${bestLocale}${window.location.pathname}`;
}
</script>
@@ -6,7 +6,8 @@ import {
Popover,
type ButtonProps,
} from "@radix-ui/themes";
import type { RefObject } from "react";
import { Jimp } from "jimp";
import { useCallback, type RefObject } from "react";
import { useLocale } from "../hooks/useLocale";
import { screenshotReadyEvent } from "./Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
@@ -18,6 +19,35 @@ interface Props extends ButtonProps {
export function ScreenshotButton({ canvas, name, children, ...props }: Props) {
const { strings } = useLocale();
const screenshot = useCallback(async () => {
if (!canvas.current) {
throw new Error("Canvas not found");
}
screenshotReadyEvent.dispatch(true);
const blob = await new Promise<Blob>((resolve, reject) => {
requestAnimationFrame(() => {
canvas.current.toBlob((blob) => {
screenshotReadyEvent.dispatch(false);
if (!blob) {
reject(new Error("Failed to capture screenshot"));
} else {
resolve(blob);
}
});
});
});
const buffer = await blob.arrayBuffer();
const image = await Jimp.fromBuffer(buffer);
image.autocrop();
return image;
}, [canvas]);
return (
<Popover.Root>
<Popover.Trigger>
@@ -37,23 +67,13 @@ export function ScreenshotButton({ canvas, name, children, ...props }: Props) {
<Flex direction="column" gap="2">
<Popover.Close>
<Button
onClick={() => {
screenshotReadyEvent.dispatch(true);
onClick={async () => {
const image = await screenshot();
const anchor = document.createElement("a");
requestAnimationFrame(() => {
if (!canvas.current) return;
const anchor = document.createElement("a");
anchor.setAttribute("download", `${name}.png`);
anchor.setAttribute(
"href",
canvas.current.toDataURL("image/png")
);
anchor.click();
screenshotReadyEvent.dispatch(false);
});
anchor.setAttribute("download", `${name}.png`);
anchor.setAttribute("href", await image.getBase64("image/png"));
anchor.click();
}}
>
<DownloadIcon />
@@ -63,22 +83,16 @@ export function ScreenshotButton({ canvas, name, children, ...props }: Props) {
<Popover.Close>
<Button
variant="outline"
onClick={() => {
screenshotReadyEvent.dispatch(true);
requestAnimationFrame(() => {
if (!canvas.current) return;
canvas.current.toBlob((blob) => {
if (!blob) return;
navigator.clipboard.write([
new ClipboardItem({ "image/png": blob }),
]);
});
screenshotReadyEvent.dispatch(false);
onClick={async () => {
const image = await screenshot();
const buffer = await image.getBuffer("image/png");
const blob = new Blob([buffer as unknown as ArrayBuffer], {
type: "image/png",
});
navigator.clipboard.write([
new ClipboardItem({ "image/png": blob }),
]);
}}
>
<CopyIcon />
@@ -3,12 +3,12 @@ import {
resolveDpm,
unionBoundingBox,
type TankDefinition,
} from '@blitzkit/core';
import { useMemo } from 'react';
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
import { resolveReload } from '../../../core/blitzkit/resolveReload';
import { TankSort } from '../../../stores/tankopediaSort';
import { TankCard } from '../../TankCard';
} from "@blitzkit/core";
import { useMemo } from "react";
import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions";
import { resolveReload } from "../../../core/blitzkit/resolveReload";
import { TankSort } from "../../../stores/tankopediaSort";
import { TankCard } from "../../TankCard";
interface TankSearchCardProps {
tank: TankDefinition;
@@ -20,7 +20,7 @@ const modelDefinitions = await awaitableModelDefinitions;
export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
const by = TankSort.use((state) => state.by);
const discriminator = useMemo(() => {
if (by.startsWith('meta')) return undefined;
if (by.startsWith("meta")) return undefined;
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
@@ -34,53 +34,53 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
const gunModelDefinition = turretModelDefinition.guns[gun.id];
switch (by) {
case 'fire.aimTime':
case "fire.aimTime":
return gun.aim_time.toFixed(2);
case 'fire.caliber':
case "fire.caliber":
return shell0.caliber.toFixed(0);
case 'fire.damage':
case "fire.damage":
return shell0.armor_damage.toFixed(0);
case 'fire.moduleDamage':
case "fire.moduleDamage":
return shell0.module_damage.toFixed(0);
case 'fire.dispersionMoving':
case "fire.dispersionMoving":
return tracks.dispersion_move.toFixed(3);
case 'fire.dispersionStill':
case "fire.dispersionStill":
return gun.dispersion_base.toFixed(3);
case 'fire.dpm':
case "fire.dpm":
return resolveDpm(gun, shell0).toFixed(0);
case 'fire.dpmPremium':
return shell1 ? resolveDpm(gun, shell1).toFixed(0) : '--';
case 'fire.reload':
case "fire.dpmPremium":
return shell1 ? resolveDpm(gun, shell1).toFixed(0) : "--";
case "fire.reload":
return resolveReload(gun).toFixed(2);
case 'fire.standardPenetration':
return shell0.penetration.near.toFixed(0);
case 'fire.premiumPenetration':
return shell1 ? shell1.penetration.near.toFixed(0) : '--';
case 'fire.tertiaryPenetration':
return shell1 ? shell2.penetration.near.toFixed(0) : '--';
case 'fire.shellVelocity':
case "fire.standardPenetration":
return shell0?.penetration.near.toFixed(0);
case "fire.premiumPenetration":
return shell1 ? shell1?.penetration.near.toFixed(0) : "--";
case "fire.tertiaryPenetration":
return shell1 ? shell2?.penetration.near.toFixed(0) : "--";
case "fire.shellVelocity":
return shell0.velocity.toFixed(0);
case 'fire.shellCapacity':
case "fire.shellCapacity":
return gun.shell_capacity.toFixed(0);
case 'fire.shellRange':
case "fire.shellRange":
return shell0.range.toFixed(0);
case 'fire.gunDepression':
case "fire.gunDepression":
return (
gunModelDefinition.pitch.max +
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
).toFixed(1);
case 'fire.gunElevation':
case "fire.gunElevation":
return (
-gunModelDefinition.pitch.min -
(tankModelDefinition.initial_turret_rotation?.pitch ?? 0)
).toFixed(1);
case 'maneuverability.forwardsSpeed':
case "maneuverability.forwardsSpeed":
return tank.speed_forwards.toFixed(0);
case 'maneuverability.backwardsSpeed':
case "maneuverability.backwardsSpeed":
return tank.speed_backwards.toFixed(0);
case 'maneuverability.power':
case "maneuverability.power":
return engine.power.toFixed(0);
case 'maneuverability.powerToWeight':
case "maneuverability.powerToWeight":
return (
(1000 * engine.power) /
(tank.weight +
@@ -89,7 +89,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
turret.weight +
gun.weight)
).toFixed(1);
case 'maneuverability.weight':
case "maneuverability.weight":
return (
(tank.weight +
engine.weight +
@@ -98,19 +98,19 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
gun.weight) /
1000
).toFixed(1);
case 'maneuverability.traverseSpeed':
case "maneuverability.traverseSpeed":
return tracks.traverse_speed.toFixed(1);
case 'survivability.health':
case "survivability.health":
return (tank.health + turret.health).toFixed(0);
case 'survivability.viewRange':
case "survivability.viewRange":
return turret.view_range.toFixed(0);
case 'survivability.camouflageStill':
case "survivability.camouflageStill":
return (tank.camouflage_still * 100).toFixed(0);
case 'survivability.camouflageMoving':
case "survivability.camouflageMoving":
return (tank.camouflage_moving * 100).toFixed(0);
case 'survivability.camouflageShooting':
case "survivability.camouflageShooting":
return (tank.camouflage_still * gun.camouflage_loss * 100).toFixed(0);
case 'survivability.volume': {
case "survivability.volume": {
const dimensions = normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.bounding_box,
@@ -121,7 +121,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
return (dimensions.x * dimensions.y * dimensions.z).toFixed(0);
}
case 'survivability.length': {
case "survivability.length": {
const bounds = normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.bounding_box,
@@ -129,7 +129,7 @@ const consumablesArray = Object.values(consumableDefinitions.consumables)
.filter(
(consumable) =>
!consumable.game_mode_exclusive &&
consumable.name.locales[locales.default]
consumable.name.locales[locales.default],
)
.map((consumable) => consumable.id);
const provisionsArray = Array.from(
@@ -137,7 +137,7 @@ const provisionsArray = Array.from(
.filter(
(provision) =>
!provision.game_mode_exclusive &&
provision.name.locales[locales.default]
provision.name.locales[locales.default],
)
.reduce((uniqueMap, consumable) => {
if (!uniqueMap.has(consumable.name.locales[locales.default])) {
@@ -146,7 +146,7 @@ const provisionsArray = Array.from(
return uniqueMap;
}, new Map<string, number>())
.values()
.values(),
);
const shellTypeIcons: Record<ShellType, string> = {
[ShellType.AP]: "ap",
@@ -171,7 +171,7 @@ const TANK_TYPE_COLORS: Record<TankType, string> = {
};
const GUN_TYPES = Object.keys(
GUN_TYPE_ICONS
GUN_TYPE_ICONS,
) as (keyof typeof GUN_TYPE_ICONS)[];
const MAX_ICONS = 4;
@@ -180,7 +180,12 @@ const TIERS = times(10, (i) => 10 - i);
export function FilterControl() {
return (
<Flex align="center" justify="center" gap="2" wrap="wrap">
<Flex
align="center"
justify={{ initial: "start", md: "center" }}
gap="2"
wrap="wrap"
>
<TiersFilter />
<ClassFilter />
<TypeFilter />
@@ -346,7 +351,7 @@ function NationsFilter() {
mr="-2"
style={{
backgroundImage: `url(${asset(
`flags/scratched/${nation}.webp`
`flags/scratched/${nation}.webp`,
)})`,
backgroundSize: "100%",
backgroundRepeat: "no-repeat",
@@ -437,7 +442,7 @@ function ClassFilter() {
TankFilters.mutate((draft) => {
if (selected) {
draft.classes = draft.classes.filter(
(c) => c !== tankClass
(c) => c !== tankClass,
);
} else {
draft.classes = [...draft.classes, tankClass];
@@ -683,7 +688,7 @@ function IndividualShellFilter({
src={asset(
`icons/shells/${shellTypeIcons[shells[index]]}${
premium ? "_premium" : ""
}.webp`
}.webp`,
)}
/>
)}
@@ -718,7 +723,7 @@ function IndividualShellFilter({
src={asset(
`icons/shells/${shellTypeIcons[shellType]}${
premium ? "_premium" : ""
}.webp`
}.webp`,
)}
style={{ width: "1.25em", height: "1.25em" }}
/>
@@ -917,7 +922,7 @@ function ConsumablesFilter() {
TankFilters.mutate((draft) => {
if (selected) {
draft.consumables = draft.consumables.filter(
(n) => n !== consumable
(n) => n !== consumable,
);
} else {
draft.consumables = [...draft.consumables, consumable];
@@ -1011,7 +1016,7 @@ function ProvisionsFilter() {
TankFilters.mutate((draft) => {
if (selected) {
draft.provisions = draft.provisions.filter(
(n) => n !== provision
(n) => n !== provision,
);
} else {
draft.provisions = [...draft.provisions, provision];
@@ -1140,7 +1145,7 @@ function GameModeAbilitiesFilter() {
TankFilters.mutate((draft) => {
if (selected) {
draft.abilities = draft.abilities.filter(
(n) => n !== consumable
(n) => n !== consumable,
);
} else {
draft.abilities = [...draft.abilities, consumable];
@@ -1189,7 +1194,7 @@ function GameModeAbilitiesFilter() {
TankFilters.mutate((draft) => {
if (selected) {
draft.powers = draft.powers.filter(
(n) => n !== provision
(n) => n !== provision,
);
} else {
draft.powers = [...draft.powers, provision];
@@ -30,9 +30,11 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
<Flex direction="column" mb="6" align="center">
<VisualizerCard
mb="0"
style={{ width: "100%" }}
style={{ width: "100%", touchAction: "none" }}
ref={container}
onPointerMove={(event) => {
event.preventDefault();
if (!container.current || explodes) return;
const rect = container.current.getBoundingClientRect();
@@ -174,7 +176,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
? "-"
: literals(strings.common.units.deg, {
value: radToDeg(
Math.min(normalization, Math.abs(angle))
Math.min(normalization, Math.abs(angle)),
).toFixed(0),
})
}
@@ -55,17 +55,17 @@ export function Controls({
const protagonistHullOrigin = new Vector3(
protagonistTrackModelDefinition.origin.x,
protagonistTrackModelDefinition.origin.y,
-protagonistTrackModelDefinition.origin.z
-protagonistTrackModelDefinition.origin.z,
);
const protagonistTurretOrigin = new Vector3(
protagonistModelDefinition.turret_origin.x,
protagonistModelDefinition.turret_origin.y,
-protagonistModelDefinition.turret_origin.z
-protagonistModelDefinition.turret_origin.z,
);
const protagonistGunOrigin = new Vector3(
protagonistTurretModelDefinition.gun_origin.x,
protagonistTurretModelDefinition.gun_origin.y,
-protagonistTurretModelDefinition.gun_origin.z
-protagonistTurretModelDefinition.gun_origin.z,
);
const antagonistGunHeight =
protagonistTrackModelDefinition.origin.y +
@@ -106,12 +106,12 @@ export function Controls({
const hasImprovedVerticalStabilizer = hasEquipment(
122,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix
Duel.state.protagonist.equipmentMatrix,
);
const hasDownImprovedVerticalStabilizer = hasEquipment(
124,
Duel.state.protagonist.tank.equipment_preset,
Duel.state.protagonist.equipmentMatrix
Duel.state.protagonist.equipmentMatrix,
);
switch (event) {
@@ -122,7 +122,7 @@ export function Controls({
protagonistGunModelDefinition.pitch,
protagonistTurretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
hasDownImprovedVerticalStabilizer,
);
camera.position
@@ -134,14 +134,14 @@ export function Controls({
new Vector3(
0,
poseDistances[event] * Math.sin(pitch),
poseDistances[event] * -Math.cos(pitch)
)
poseDistances[event] * -Math.cos(pitch),
),
);
camera.lookAt(
protagonistHullOrigin
.clone()
.add(protagonistTurretOrigin)
.add(protagonistGunOrigin)
.add(protagonistGunOrigin),
);
orbitControls.current?.target.set(0, antagonistGunHeight, 0);
@@ -155,7 +155,7 @@ export function Controls({
protagonistGunModelDefinition.pitch,
protagonistTurretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
hasDownImprovedVerticalStabilizer,
);
camera.position
@@ -167,8 +167,8 @@ export function Controls({
new Vector3(
0,
poseDistances[event] * Math.sin(pitch),
poseDistances[event] * -Math.cos(pitch)
)
poseDistances[event] * -Math.cos(pitch),
),
);
orbitControls.current?.target
.set(0, 0, 0)
@@ -179,8 +179,8 @@ export function Controls({
new Vector3(
0,
0.5 * poseDistances[event] * Math.sin(pitch),
0.5 * poseDistances[event] * -Math.cos(pitch)
)
0.5 * poseDistances[event] * -Math.cos(pitch),
),
);
break;
@@ -251,7 +251,7 @@ export function Controls({
return (
<OrbitControls
enableRotate={enableRotate}
maxDistance={20}
maxDistance={40}
minDistance={5}
enableZoom={zoomable}
zoomSpeed={zoomable ? undefined : 0}
@@ -47,7 +47,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id];
const display = Tankopedia.use((state) => state.display);
const hideTankModelUnderArmor = TankopediaPersistent.use(
(state) => state.hideTankModelUnderArmor
(state) => state.hideTankModelUnderArmor,
);
useImperativeHandle(ref, () => canvas.current!, []);
@@ -76,7 +76,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer
hasDownImprovedVerticalStabilizer,
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -90,7 +90,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer
hasImprovedVerticalStabilizer,
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -104,7 +104,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
0,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer
hasImprovedVerticalStabilizer,
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -126,7 +126,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
modelTransformEvent.last!.yaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
hasImprovedVerticalStabilizer
hasImprovedVerticalStabilizer,
);
modelTransformEvent.dispatch({ pitch, yaw });
@@ -146,8 +146,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
}
}, [display]);
console.log(TankopediaDisplay[display]);
return (
<SmartCanvas
resize={{ debounce: 0 }}
@@ -196,5 +194,5 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
<InitialAligner />
</SmartCanvas>
);
}
},
);
@@ -2,9 +2,9 @@ import {
normalizeAngleRad,
type PitchLimits,
type YawLimits,
} from '@blitzkit/core';
import { clamp } from 'lodash-es';
import { degToRad } from 'three/src/math/MathUtils.js';
} from "@blitzkit/core";
import { clamp } from "lodash-es";
import { degToRad } from "three/src/math/MathUtils.js";
export const DEFAULT_PITCH_TRANSITION = 15;
@@ -87,5 +87,5 @@ export function applyPitchYawLimits(
newPitch = clamp(newPitch, lowerPitch, upperPitch);
return [newPitch, newYaw] as const;
return [newPitch, normalizeAngleRad(newYaw)] as const;
}
@@ -1,5 +1,5 @@
import type { ComponentProps, ReactNode } from "react";
import { Mesh, Object3D } from "three";
import { Mesh, MeshStandardMaterial, Object3D } from "three";
export function jsxTree(
node: Object3D,
@@ -7,14 +7,14 @@ export function jsxTree(
mesh?: (
mesh: Mesh,
props: ComponentProps<"mesh">,
key: string
key: string,
) => ReactNode;
group?: (
object3d: Object3D,
props: ComponentProps<"group">,
key: string
key: string,
) => ReactNode;
}
},
): ReactNode {
if (node instanceof Mesh) {
const props = {
@@ -25,6 +25,10 @@ export function jsxTree(
scale: node.scale,
};
if (props.material instanceof MeshStandardMaterial) {
props.material.depthWrite = true;
}
return mergers?.mesh ? (
mergers.mesh(node, props, node.uuid)
) : (
@@ -5,11 +5,11 @@ export function getBestLocale() {
localStorage.getItem("preferred-locale") ??
navigator.language.split("-")[0];
const isSupported = locales.supported.some(
({ locale }) => locale === desiredLocale
({ locale }) => locale === desiredLocale,
);
const isDefault = desiredLocale === locales.default;
if (!isSupported || isDefault) return undefined;
if (!isSupported || isDefault) return locales.default;
return desiredLocale;
}
@@ -12,7 +12,8 @@ export const getStaticPaths = (async () => {
.map((tank) =>
parentPaths.map((parent) => ({
params: { ...parent.params, id: tank.id },
}))
props: parent.props,
})),
)
.flat();
}) satisfies GetStaticPaths;