mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: advanced jsxTree
This commit is contained in:
1 parent
2bef96475c
commit
0e6aa734cb
10 files changed
+305
-226
No files matched your search
+19
-10
@@ -211,16 +211,25 @@ export function PrimaryArmorSceneComponent({
|
||||
|
||||
return (
|
||||
<>
|
||||
{jsxTree(
|
||||
node,
|
||||
{ mesh: { renderOrder: 0, material: excludeMaterial } },
|
||||
`${node.uuid}-exclude`,
|
||||
)}
|
||||
{jsxTree(
|
||||
node,
|
||||
{ mesh: { renderOrder: 1, material } },
|
||||
`${node.uuid}-include`,
|
||||
)}
|
||||
{jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
renderOrder={0}
|
||||
material={excludeMaterial}
|
||||
/>
|
||||
);
|
||||
},
|
||||
})}
|
||||
{jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh {...props} key={key} renderOrder={1} material={material} />
|
||||
);
|
||||
},
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
+32
-25
@@ -114,33 +114,40 @@ export function SpacedArmorSubExternal({
|
||||
|
||||
return (
|
||||
<>
|
||||
{jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
renderOrder: 3,
|
||||
material: new MeshBasicMaterial({
|
||||
colorWrite: false,
|
||||
depthTest: true,
|
||||
depthWrite: true,
|
||||
...(clip ? { clippingPlanes: [clip] } : {}),
|
||||
}),
|
||||
onClick() {},
|
||||
userData: {
|
||||
type: ArmorType.External,
|
||||
thickness,
|
||||
variant,
|
||||
} satisfies ArmorUserData,
|
||||
},
|
||||
{jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
renderOrder={3}
|
||||
material={
|
||||
new MeshBasicMaterial({
|
||||
colorWrite: false,
|
||||
depthTest: true,
|
||||
depthWrite: true,
|
||||
...(clip ? { clippingPlanes: [clip] } : {}),
|
||||
})
|
||||
}
|
||||
userData={
|
||||
{
|
||||
type: ArmorType.External,
|
||||
thickness,
|
||||
variant,
|
||||
} satisfies ArmorUserData
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
`${node.uuid}-exclude`,
|
||||
)}
|
||||
})}
|
||||
|
||||
{jsxTree(
|
||||
node,
|
||||
{ mesh: { renderOrder: 4, material } },
|
||||
`${node.uuid}-include`,
|
||||
)}
|
||||
{jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh {...props} key={key} renderOrder={4} material={material} />
|
||||
);
|
||||
},
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
+29
-23
@@ -124,32 +124,38 @@ export function SpacedArmorSubSpaced({
|
||||
|
||||
return (
|
||||
<>
|
||||
{jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
renderOrder: 2,
|
||||
material,
|
||||
onClick() {},
|
||||
userData: {
|
||||
type: ArmorType.Spaced,
|
||||
thickness,
|
||||
} satisfies ArmorUserData,
|
||||
},
|
||||
{jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
renderOrder={2}
|
||||
material={material}
|
||||
onClick={() => {}}
|
||||
userData={
|
||||
{
|
||||
type: ArmorType.Spaced,
|
||||
thickness,
|
||||
} satisfies ArmorUserData
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
`${node.uuid}-exclude`,
|
||||
)}
|
||||
})}
|
||||
|
||||
{jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
renderOrder: 5,
|
||||
material: depthWriteMaterial,
|
||||
},
|
||||
{jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
renderOrder={5}
|
||||
material={depthWriteMaterial}
|
||||
/>
|
||||
);
|
||||
},
|
||||
`${node.uuid}-include`,
|
||||
)}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
+26
-22
@@ -372,29 +372,33 @@ export function SpacedArmorSceneComponent({
|
||||
return (
|
||||
<>
|
||||
{props.type === ArmorType.Primary &&
|
||||
jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: {
|
||||
renderOrder: 0,
|
||||
material: omitMaterial,
|
||||
userData: {
|
||||
type: ArmorType.Primary,
|
||||
thickness,
|
||||
} satisfies ArmorUserData,
|
||||
async onClick(event) {
|
||||
event.stopPropagation();
|
||||
const shot = (await shoot(
|
||||
event.point,
|
||||
event.intersections,
|
||||
true,
|
||||
))!;
|
||||
tankopediaEphemeralStore.setState({ shot });
|
||||
},
|
||||
},
|
||||
jsxTree(node, {
|
||||
mesh(mesh, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
renderOrder={0}
|
||||
material={omitMaterial}
|
||||
userData={
|
||||
{
|
||||
type: ArmorType.Primary,
|
||||
thickness,
|
||||
} satisfies ArmorUserData
|
||||
}
|
||||
onClick={async (event) => {
|
||||
event.stopPropagation();
|
||||
const shot = (await shoot(
|
||||
event.point,
|
||||
event.intersections,
|
||||
true,
|
||||
))!;
|
||||
tankopediaEphemeralStore.setState({ shot });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
node.uuid,
|
||||
)}
|
||||
})}
|
||||
|
||||
{props.type === ArmorType.Spaced && (
|
||||
<SpacedArmorSubSpaced node={node} thickness={thickness} />
|
||||
|
||||
@@ -215,50 +215,61 @@ export function StaticArmorSceneComponent({
|
||||
return (
|
||||
<>
|
||||
{jsxTree(node, {
|
||||
mesh: {
|
||||
material: surfaceMaterial,
|
||||
onPointerDown,
|
||||
userData: {
|
||||
type: props.type,
|
||||
variant: props.type === ArmorType.External ? props.variant : 'gun',
|
||||
thickness,
|
||||
} satisfies ArmorUserData,
|
||||
mesh(_, meshProps, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...meshProps}
|
||||
key={key}
|
||||
material={surfaceMaterial}
|
||||
userData={
|
||||
{
|
||||
type: props.type,
|
||||
variant:
|
||||
props.type === ArmorType.External ? props.variant : 'gun',
|
||||
thickness,
|
||||
} satisfies ArmorUserData
|
||||
}
|
||||
onClick={(event) => {
|
||||
if (discardClippingPlane(event.object, event.point)) return;
|
||||
|
||||
onClick(event) {
|
||||
if (discardClippingPlane(event.object, event.point)) return;
|
||||
event.stopPropagation();
|
||||
|
||||
event.stopPropagation();
|
||||
const { editStatic } = tankopediaEphemeralStore.getState();
|
||||
|
||||
const { editStatic } = tankopediaEphemeralStore.getState();
|
||||
const bounds = new Box3().setFromObject(event.object);
|
||||
const point = bounds.min
|
||||
.clone()
|
||||
.add(bounds.max)
|
||||
.divideScalar(2)
|
||||
.setY(bounds.max.y);
|
||||
const cameraNormal = camera.position
|
||||
.clone()
|
||||
.sub(point)
|
||||
.normalize();
|
||||
const surfaceNormal = event
|
||||
.normal!.clone()
|
||||
.applyQuaternion(
|
||||
event.object.getWorldQuaternion(new Quaternion()),
|
||||
);
|
||||
const angle = surfaceNormal.angleTo(cameraNormal);
|
||||
const thicknessAngled =
|
||||
thickness / Math.sin(Math.PI / 2 - angle);
|
||||
|
||||
const bounds = new Box3().setFromObject(event.object);
|
||||
const point = bounds.min
|
||||
.clone()
|
||||
.add(bounds.max)
|
||||
.divideScalar(2)
|
||||
.setY(bounds.max.y);
|
||||
const cameraNormal = camera.position.clone().sub(point).normalize();
|
||||
const surfaceNormal = event
|
||||
.normal!.clone()
|
||||
.applyQuaternion(
|
||||
event.object.getWorldQuaternion(new Quaternion()),
|
||||
);
|
||||
const angle = surfaceNormal.angleTo(cameraNormal);
|
||||
const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle);
|
||||
|
||||
mutateTankopediaEphemeralStore((draft) => {
|
||||
draft.highlightArmor = {
|
||||
type: props.type,
|
||||
name,
|
||||
point,
|
||||
thickness,
|
||||
thicknessAngled,
|
||||
angle,
|
||||
color: `#${color.getHexString()}`,
|
||||
editingPlate: editStatic,
|
||||
};
|
||||
});
|
||||
},
|
||||
mutateTankopediaEphemeralStore((draft) => {
|
||||
draft.highlightArmor = {
|
||||
type: props.type,
|
||||
name,
|
||||
point,
|
||||
thickness,
|
||||
thicknessAngled,
|
||||
angle,
|
||||
color: `#${color.getHexString()}`,
|
||||
editingPlate: editStatic,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
})}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PlusIcon, TrashIcon } from '@radix-ui/react-icons';
|
||||
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
|
||||
import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions';
|
||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
@@ -15,7 +16,10 @@ interface ControlsProps {
|
||||
onAddTankDialogOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
const [provisionDefinitions, modelDefinitions] = await Promise.all([
|
||||
awaitableProvisionDefinitions,
|
||||
awaitableModelDefinitions,
|
||||
]);
|
||||
|
||||
export function Controls({
|
||||
addTankDialogOpen,
|
||||
@@ -56,9 +60,11 @@ export function Controls({
|
||||
<TankSearch
|
||||
compact
|
||||
onSelect={(tank) => {
|
||||
const model = modelDefinitions.models[tank.id];
|
||||
|
||||
mutateCompareEphemeral((draft) => {
|
||||
draft.members.push(
|
||||
tankToCompareMember(tank, provisionDefinitions),
|
||||
tankToCompareMember(tank, model, provisionDefinitions),
|
||||
);
|
||||
draft.sorting = undefined;
|
||||
});
|
||||
@@ -67,9 +73,15 @@ export function Controls({
|
||||
onSelectAll={(tanks) => {
|
||||
mutateCompareEphemeral((draft) => {
|
||||
draft.members.push(
|
||||
...tanks.map((tank) =>
|
||||
tankToCompareMember(tank, provisionDefinitions),
|
||||
),
|
||||
...tanks.map((tank) => {
|
||||
const model = modelDefinitions.models[tank.id];
|
||||
|
||||
return tankToCompareMember(
|
||||
tank,
|
||||
model,
|
||||
provisionDefinitions,
|
||||
);
|
||||
}),
|
||||
);
|
||||
draft.sorting = undefined;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
|
||||
import { Dialog, Flex, IconButton } from '@radix-ui/themes';
|
||||
import { useState } from 'react';
|
||||
import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions';
|
||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
@@ -13,7 +14,10 @@ interface TankControlProps {
|
||||
id: number;
|
||||
}
|
||||
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
const [provisionDefinitions, modelDefinitions] = await Promise.all([
|
||||
awaitableProvisionDefinitions,
|
||||
awaitableModelDefinitions,
|
||||
]);
|
||||
|
||||
export function TankControl({ index, id }: TankControlProps) {
|
||||
const { locale } = useLocale();
|
||||
@@ -57,9 +61,12 @@ export function TankControl({ index, id }: TankControlProps) {
|
||||
<TankSearch
|
||||
compact
|
||||
onSelect={(tank) => {
|
||||
const model = modelDefinitions.models[tank.id];
|
||||
|
||||
mutateCompareEphemeral((draft) => {
|
||||
draft.members[index] = tankToCompareMember(
|
||||
tank,
|
||||
model,
|
||||
provisionDefinitions,
|
||||
);
|
||||
draft.sorting = undefined;
|
||||
|
||||
+86
-68
@@ -114,29 +114,35 @@ export function TankModel() {
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
return jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: { onPointerDown, castShadow: true },
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive && group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
return jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
onPointerDown={onPointerDown}
|
||||
castShadow
|
||||
/>
|
||||
);
|
||||
},
|
||||
node.uuid,
|
||||
);
|
||||
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive && group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
});
|
||||
})}
|
||||
|
||||
<group ref={turretContainer}>
|
||||
@@ -217,30 +223,36 @@ export function TankModel() {
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
return jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: { onPointerDown, castShadow: true },
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
return jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
onPointerDown={onPointerDown}
|
||||
castShadow
|
||||
/>
|
||||
);
|
||||
},
|
||||
node.uuid,
|
||||
);
|
||||
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
});
|
||||
})}
|
||||
|
||||
<group ref={gunContainer}>
|
||||
@@ -324,30 +336,36 @@ export function TankModel() {
|
||||
window.removeEventListener('pointerup', handlePointerUp);
|
||||
}
|
||||
|
||||
return jsxTree(
|
||||
node,
|
||||
{
|
||||
mesh: { onPointerDown, castShadow: true },
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
return jsxTree(node, {
|
||||
mesh(_, props, key) {
|
||||
return (
|
||||
<mesh
|
||||
{...props}
|
||||
key={key}
|
||||
onPointerDown={onPointerDown}
|
||||
castShadow
|
||||
/>
|
||||
);
|
||||
},
|
||||
node.uuid,
|
||||
);
|
||||
|
||||
// group(group) {
|
||||
// return {
|
||||
// visible:
|
||||
// group.name !== 'ST_base' &&
|
||||
// group.name !== 'MR_Base2' &&
|
||||
// (!group.name.includes('state_entity') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_01')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.includes('state_entity_00'))) &&
|
||||
// (!group.name.includes('shields') ||
|
||||
// (isDynamicArmorActive &&
|
||||
// group.name.includes('shields_rev')) ||
|
||||
// (!isDynamicArmorActive &&
|
||||
// group.name.endsWith('shields'))),
|
||||
// };
|
||||
// },
|
||||
});
|
||||
})}
|
||||
</group>
|
||||
</group>
|
||||
|
||||
@@ -1,43 +1,44 @@
|
||||
import type { GroupProps, MeshProps } from '@react-three/fiber';
|
||||
import type { MeshProps, Object3DProps } from '@react-three/fiber';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Mesh, Object3D } from 'three';
|
||||
|
||||
export function jsxTree(
|
||||
node: Object3D,
|
||||
mergers?: {
|
||||
mesh?: MeshProps | ((mesh: Mesh) => MeshProps);
|
||||
group?: GroupProps | ((object3d: Object3D) => GroupProps);
|
||||
mesh?: (mesh: Mesh, props: MeshProps, key: string) => ReactNode;
|
||||
group?: (
|
||||
object3d: Object3D,
|
||||
props: Object3DProps,
|
||||
key: string,
|
||||
) => ReactNode;
|
||||
},
|
||||
key?: string,
|
||||
) {
|
||||
): ReactNode {
|
||||
if (node instanceof Mesh) {
|
||||
return (
|
||||
<mesh
|
||||
key={key}
|
||||
geometry={node.geometry}
|
||||
material={node.material}
|
||||
position={node.position}
|
||||
rotation={node.rotation}
|
||||
scale={node.scale}
|
||||
{...(mergers?.mesh &&
|
||||
(typeof mergers.mesh === 'function'
|
||||
? mergers.mesh(node)
|
||||
: mergers.mesh))}
|
||||
/>
|
||||
const props = {
|
||||
geometry: node.geometry,
|
||||
material: node.material,
|
||||
position: node.position,
|
||||
rotation: node.rotation,
|
||||
scale: node.scale,
|
||||
};
|
||||
|
||||
return mergers?.mesh ? (
|
||||
mergers.mesh(node, props, node.uuid)
|
||||
) : (
|
||||
<mesh key={node.uuid} {...props} />
|
||||
);
|
||||
} else if (node instanceof Object3D) {
|
||||
return (
|
||||
<group
|
||||
key={key}
|
||||
position={node.position}
|
||||
rotation={node.rotation}
|
||||
scale={node.scale}
|
||||
{...(mergers?.group &&
|
||||
(typeof mergers.group === 'function'
|
||||
? mergers.group(node)
|
||||
: mergers.group))}
|
||||
>
|
||||
{node.children.map((child) => jsxTree(child, mergers, child.uuid))}
|
||||
</group>
|
||||
const props = {
|
||||
position: node.position,
|
||||
rotation: node.rotation,
|
||||
scale: node.scale,
|
||||
children: node.children.map((child) => jsxTree(child, mergers)),
|
||||
};
|
||||
|
||||
return mergers?.group ? (
|
||||
mergers.group(node, props, node.uuid)
|
||||
) : (
|
||||
<group key={node.uuid} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,11 @@ function Content() {
|
||||
|
||||
if (tank === undefined) return;
|
||||
|
||||
draft.members.push(tankToCompareMember(tank, provisionDefinitions));
|
||||
const model = modelDefinitions.models[tank.id];
|
||||
|
||||
draft.members.push(
|
||||
tankToCompareMember(tank, model, provisionDefinitions),
|
||||
);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
Reference in new issue
Block a user