mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: start of mixer
This commit is contained in:
1 parent
5747d7549f
commit
f3762e356f
7 files changed
+278
No files matched your search
@@ -861,6 +861,12 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"tools": {
|
"tools": {
|
||||||
|
"mixer": {
|
||||||
|
"name": "Mixer",
|
||||||
|
"button": "Mix and match",
|
||||||
|
"description": "Mix tank modules"
|
||||||
|
},
|
||||||
|
|
||||||
"guess": {
|
"guess": {
|
||||||
"name": "Guess the tank",
|
"name": "Guess the tank",
|
||||||
"button": "Guess away",
|
"button": "Guess away",
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { Vector3 } from 'three';
|
||||||
|
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
||||||
|
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
||||||
|
import { useModel } from '../../../hooks/useModel';
|
||||||
|
import { MixerEphemeral } from '../../../stores/mixerEphemeral';
|
||||||
|
|
||||||
|
const modelDefinitions = await awaitableModelDefinitions;
|
||||||
|
|
||||||
|
export function Model() {
|
||||||
|
const hull = MixerEphemeral.use((state) => state.hull);
|
||||||
|
const turret = MixerEphemeral.use((state) => state.turret);
|
||||||
|
const gun = MixerEphemeral.use((state) => state.gun);
|
||||||
|
|
||||||
|
const hullGltf = useModel(hull.id);
|
||||||
|
const turretGltf = useModel(turret.tank.id);
|
||||||
|
const gunGltf = useModel(gun.tank.id);
|
||||||
|
|
||||||
|
const hullNodes = Object.values(hullGltf.gltf.nodes);
|
||||||
|
const turretNodes = Object.values(turretGltf.gltf.nodes);
|
||||||
|
const gunNodes = Object.values(gunGltf.gltf.nodes);
|
||||||
|
|
||||||
|
const tankModel = modelDefinitions.models[hull.id];
|
||||||
|
const trackModel = tankModel.tracks[hull.tracks.at(-1)!.id];
|
||||||
|
|
||||||
|
const turretTankModel = modelDefinitions.models[turret.tank.id];
|
||||||
|
const turretTrackModel =
|
||||||
|
turretTankModel.tracks[turret.tank.tracks.at(-1)!.id];
|
||||||
|
const turretModel = turretTankModel.turrets[turret.turret.id];
|
||||||
|
|
||||||
|
const gunTankModel = modelDefinitions.models[gun.tank.id];
|
||||||
|
const gunTrackModel = gunTankModel.tracks[gun.tank.tracks.at(-1)!.id];
|
||||||
|
const gunTurretModel = gunTankModel.turrets[gun.turret.id];
|
||||||
|
const gunModel = gunTurretModel.guns[gun.gun.id];
|
||||||
|
|
||||||
|
const turretDelta = new Vector3(
|
||||||
|
tankModel.turret_origin.x - turretTankModel.turret_origin.x,
|
||||||
|
tankModel.turret_origin.y - turretTankModel.turret_origin.y,
|
||||||
|
tankModel.turret_origin.z - turretTankModel.turret_origin.z,
|
||||||
|
);
|
||||||
|
const gunDelta = new Vector3(
|
||||||
|
turretModel.gun_origin.x - gunTurretModel.gun_origin.x,
|
||||||
|
turretModel.gun_origin.z - gunTurretModel.gun_origin.z,
|
||||||
|
turretModel.gun_origin.y - gunTurretModel.gun_origin.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
|
{hullNodes.map((node) => {
|
||||||
|
const isHull = node.name === 'hull';
|
||||||
|
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||||
|
const isTrack = node.name.startsWith('chassis_track_');
|
||||||
|
const isVisible = isHull || isWheel || isTrack;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return jsxTree(node, {
|
||||||
|
mesh(_, props, key) {
|
||||||
|
return <mesh {...props} key={key} />;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
|
||||||
|
<group
|
||||||
|
position={[
|
||||||
|
tankModel.turret_origin.x +
|
||||||
|
trackModel.origin.x -
|
||||||
|
turretTankModel.turret_origin.x -
|
||||||
|
turretTrackModel.origin.x,
|
||||||
|
tankModel.turret_origin.z +
|
||||||
|
trackModel.origin.z -
|
||||||
|
turretTankModel.turret_origin.z -
|
||||||
|
turretTrackModel.origin.z,
|
||||||
|
tankModel.turret_origin.y +
|
||||||
|
trackModel.origin.y -
|
||||||
|
turretTankModel.turret_origin.y -
|
||||||
|
turretTrackModel.origin.y,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{turretNodes.map((node) => {
|
||||||
|
const isCurrentTurret =
|
||||||
|
node.name ===
|
||||||
|
`turret_${turretModel.model_id.toString().padStart(2, '0')}`;
|
||||||
|
const isVisible = isCurrentTurret;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return jsxTree(node, {
|
||||||
|
mesh(_, props, key) {
|
||||||
|
return <mesh {...props} key={key} />;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
|
||||||
|
<group
|
||||||
|
position={[
|
||||||
|
turretTankModel.turret_origin.x +
|
||||||
|
turretTrackModel.origin.x +
|
||||||
|
turretModel.gun_origin.x -
|
||||||
|
gunTankModel.turret_origin.x -
|
||||||
|
gunTrackModel.origin.x -
|
||||||
|
gunTurretModel.gun_origin.x,
|
||||||
|
turretTankModel.turret_origin.z +
|
||||||
|
turretTrackModel.origin.z +
|
||||||
|
turretModel.gun_origin.z -
|
||||||
|
gunTankModel.turret_origin.z -
|
||||||
|
gunTrackModel.origin.z -
|
||||||
|
gunTurretModel.gun_origin.z,
|
||||||
|
turretTankModel.turret_origin.y +
|
||||||
|
turretTrackModel.origin.y +
|
||||||
|
turretModel.gun_origin.y -
|
||||||
|
gunTankModel.turret_origin.y -
|
||||||
|
gunTrackModel.origin.y -
|
||||||
|
gunTurretModel.gun_origin.y,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{gunNodes.map((node) => {
|
||||||
|
const isCurrentMantlet =
|
||||||
|
node.name ===
|
||||||
|
`gun_${gunModel.model_id.toString().padStart(2, '0')}_mask`;
|
||||||
|
const isCurrentGun =
|
||||||
|
node.name ===
|
||||||
|
`gun_${gunModel.model_id.toString().padStart(2, '0')}`;
|
||||||
|
const isVisible = isCurrentGun || isCurrentMantlet;
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return jsxTree(node, {
|
||||||
|
mesh(_, props, key) {
|
||||||
|
return <mesh {...props} key={key} />;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Environment, OrbitControls } from '@react-three/drei';
|
||||||
|
import { Canvas } from '@react-three/fiber';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
import { Model } from './components/Model';
|
||||||
|
|
||||||
|
export function MixerScene() {
|
||||||
|
return (
|
||||||
|
<Canvas
|
||||||
|
camera={{
|
||||||
|
fov: 65,
|
||||||
|
position: [5, 5, -5],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<OrbitControls />
|
||||||
|
|
||||||
|
<Environment preset="lobby" />
|
||||||
|
{/* <ambientLight intensity={6} />
|
||||||
|
<pointLight position={[10, 10, 10]} /> */}
|
||||||
|
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Model />
|
||||||
|
</Suspense>
|
||||||
|
</Canvas>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -127,11 +127,18 @@ export const guessTool: Tool = {
|
|||||||
branches: ['main', 'dev', 'opentest', 'preview'],
|
branches: ['main', 'dev', 'opentest', 'preview'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mixerTool: Tool = {
|
||||||
|
id: 'mixer',
|
||||||
|
button: { color: 'red' },
|
||||||
|
branches: ['main', 'dev', 'opentest', 'preview'],
|
||||||
|
};
|
||||||
|
|
||||||
export const TOOLS: Tool[] = [
|
export const TOOLS: Tool[] = [
|
||||||
tankopediaTool,
|
tankopediaTool,
|
||||||
compareTool,
|
compareTool,
|
||||||
performanceTool,
|
performanceTool,
|
||||||
guessTool,
|
guessTool,
|
||||||
|
mixerTool,
|
||||||
// chartsTool,
|
// chartsTool,
|
||||||
galleryTool,
|
galleryTool,
|
||||||
sessionTool,
|
sessionTool,
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Box } from '@radix-ui/themes';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { MixerScene } from '../../../components/MixerScene';
|
||||||
|
import { PageWrapper } from '../../../components/PageWrapper';
|
||||||
|
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||||
|
import {
|
||||||
|
LocaleProvider,
|
||||||
|
type LocaleAcceptorProps,
|
||||||
|
} from '../../../hooks/useLocale';
|
||||||
|
import { MixerEphemeral } from '../../../stores/mixerEphemeral';
|
||||||
|
|
||||||
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
const tanks = Object.values(tankDefinitions.tanks);
|
||||||
|
|
||||||
|
export function Page({ locale }: LocaleAcceptorProps) {
|
||||||
|
const hull = useRef(tanks[Math.floor(Math.random() * tanks.length)]);
|
||||||
|
const turretTank = useRef(tanks[Math.floor(Math.random() * tanks.length)]);
|
||||||
|
const turret = useRef(
|
||||||
|
turretTank.current.turrets[
|
||||||
|
Math.floor(Math.random() * turretTank.current.turrets.length)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const gunTank = useRef(tanks[Math.floor(Math.random() * tanks.length)]);
|
||||||
|
const gunTurret = useRef(
|
||||||
|
gunTank.current.turrets[
|
||||||
|
Math.floor(Math.random() * gunTank.current.turrets.length)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
const gun = useRef(
|
||||||
|
gunTurret.current.guns[
|
||||||
|
Math.floor(Math.random() * gunTurret.current.guns.length)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LocaleProvider locale={locale}>
|
||||||
|
<MixerEphemeral.Provider
|
||||||
|
data={{
|
||||||
|
hull: hull.current,
|
||||||
|
turret: { tank: turretTank.current, turret: turret.current },
|
||||||
|
gun: {
|
||||||
|
tank: gunTank.current,
|
||||||
|
turret: gunTurret.current,
|
||||||
|
gun: gun.current,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Content />
|
||||||
|
</MixerEphemeral.Provider>
|
||||||
|
</LocaleProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content() {
|
||||||
|
return (
|
||||||
|
<PageWrapper p="0">
|
||||||
|
<Box flexGrow="1" position="relative">
|
||||||
|
<Box position="absolute" width="100%" height="100%" top="0" left="0">
|
||||||
|
<MixerScene />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
import { PageWrapper } from '../../../components/PageWrapper';
|
||||||
|
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
||||||
|
import { Page } from './_index';
|
||||||
|
|
||||||
|
export { getStaticPaths } from '../index.astro';
|
||||||
|
|
||||||
|
const { locale } = Astro.params;
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout>
|
||||||
|
<PageWrapper p="0" maxWidth="unset">
|
||||||
|
<Page {locale} client:load />
|
||||||
|
</PageWrapper>
|
||||||
|
</BaseLayout>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type {
|
||||||
|
GunDefinition,
|
||||||
|
TankDefinition,
|
||||||
|
TurretDefinition,
|
||||||
|
} from '@blitzkit/core';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { createContextualStore } from '../core/zustand/createContextualStore';
|
||||||
|
|
||||||
|
export interface MixerEphemeral {
|
||||||
|
hull: TankDefinition;
|
||||||
|
turret: {
|
||||||
|
tank: TankDefinition;
|
||||||
|
turret: TurretDefinition;
|
||||||
|
};
|
||||||
|
gun: {
|
||||||
|
tank: TankDefinition;
|
||||||
|
turret: TurretDefinition;
|
||||||
|
gun: GunDefinition;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MixerEphemeral = createContextualStore((data: MixerEphemeral) =>
|
||||||
|
create<MixerEphemeral>()(() => data),
|
||||||
|
);
|
||||||
Reference in new issue
Block a user