mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +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": {
|
||||
"mixer": {
|
||||
"name": "Mixer",
|
||||
"button": "Mix and match",
|
||||
"description": "Mix tank modules"
|
||||
},
|
||||
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"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'],
|
||||
};
|
||||
|
||||
export const mixerTool: Tool = {
|
||||
id: 'mixer',
|
||||
button: { color: 'red' },
|
||||
branches: ['main', 'dev', 'opentest', 'preview'],
|
||||
};
|
||||
|
||||
export const TOOLS: Tool[] = [
|
||||
tankopediaTool,
|
||||
compareTool,
|
||||
performanceTool,
|
||||
guessTool,
|
||||
mixerTool,
|
||||
// chartsTool,
|
||||
galleryTool,
|
||||
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