mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-29 05:29:52 +01:00
Compare commits
8
Commits
d2ee528c52
...
91ac93670f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91ac93670f | ||
|
|
8a9b02e584 | ||
|
|
c16ccc4b4c | ||
|
|
5dc11473bb | ||
|
|
2213f7d032 | ||
|
|
9d12c936b4 | ||
|
|
3f54e467d8 | ||
|
|
3dd0c4916d |
No files matched your search
+5
-1
@@ -10,7 +10,11 @@
|
||||
"hocs",
|
||||
"cooldown",
|
||||
"Lods",
|
||||
"Skia"
|
||||
"Skia",
|
||||
"RMAO",
|
||||
"Verts",
|
||||
"drei",
|
||||
"frameloop"
|
||||
],
|
||||
"userWords": [
|
||||
"WOTB",
|
||||
|
||||
@@ -7,12 +7,16 @@ using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Assets.Objects.Properties;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using Microsoft.JavaScript.NodeApi;
|
||||
using SharpGLTF.Materials;
|
||||
@@ -245,25 +249,48 @@ public partial class GameInterface
|
||||
|
||||
var visualData = row.Get<UObject>("VisualData");
|
||||
var meshSettings = visualData.Get<FStructFallback>("MeshSettings");
|
||||
var meshes = new List<UStaticMesh>();
|
||||
// var meshes = CollectMeshes(meshSettings);
|
||||
// var gltf = new MonoGltf(meshes);
|
||||
|
||||
foreach (var property in meshSettings.Properties)
|
||||
{
|
||||
var name = property.Name.Text;
|
||||
|
||||
if (name == "CollisionMesh")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (meshSettings.TryGet<UStaticMesh>(name, out var mesh))
|
||||
{
|
||||
meshes.Add(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
var gltf = new MonoGltf(meshes);
|
||||
var gltf = new MonoGltf(meshSettings);
|
||||
|
||||
return gltf.Write($"{tag}/{part}");
|
||||
}
|
||||
|
||||
static List<(string, UObject)> CollectMeshes(FStructFallback settings)
|
||||
{
|
||||
var meshes = new List<(string, UObject)>();
|
||||
|
||||
foreach (var property in settings.Properties)
|
||||
{
|
||||
var key = property.Name.Text;
|
||||
|
||||
if (settings.TryGet<UStaticMesh>(key, out var staticMesh))
|
||||
{
|
||||
meshes.Add((key, staticMesh));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (settings.TryGet<USkeletalMesh>(key, out var skeletalMesh))
|
||||
{
|
||||
meshes.Add((key, skeletalMesh));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (settings.TryGet<FStructFallback[]>(key, out var array))
|
||||
{
|
||||
foreach (var item in array)
|
||||
{
|
||||
meshes.AddRange(CollectMeshes(item));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Console.WriteLine(
|
||||
// $"{key} -> {property.Tag} ({property.TagData}) [{property.PropertyTagFlags}]"
|
||||
// );
|
||||
}
|
||||
|
||||
return meshes;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using BlitzKit.Game.JSExport;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Materials;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Meshes.glTF;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Meshes;
|
||||
using SharpGLTF.Geometry;
|
||||
@@ -18,7 +18,6 @@ using SharpGLTF.Materials;
|
||||
using SharpGLTF.Memory;
|
||||
using SharpGLTF.Scenes;
|
||||
using SharpGLTF.Schema2;
|
||||
using SharpGLTF.Validation;
|
||||
|
||||
namespace BlitzKit.Game.Models;
|
||||
|
||||
@@ -31,7 +30,10 @@ using ConfiguredMeshBuilder = MeshBuilder<
|
||||
public class MonoGltf
|
||||
{
|
||||
readonly SceneBuilder scene = new();
|
||||
readonly Dictionary<string, MaterialBuilder> materialMap = [];
|
||||
|
||||
readonly MaterialBuilder emptyMaterial = new("empty_material");
|
||||
readonly ExporterOptions options = new() { TextureFormat = ETextureFormat.Jpeg };
|
||||
public static readonly Dictionary<string, KnownChannel> knownChannels = new()
|
||||
{
|
||||
{ "BaseColor", KnownChannel.BaseColor },
|
||||
@@ -41,92 +43,153 @@ public class MonoGltf
|
||||
};
|
||||
readonly byte[] stubBytes = File.ReadAllBytes("../game/stub/small.png");
|
||||
|
||||
public MonoGltf(List<UStaticMesh> meshes)
|
||||
public MonoGltf(FStructFallback settings)
|
||||
{
|
||||
var options = new ExporterOptions() { TextureFormat = ETextureFormat.Jpeg };
|
||||
var root = new NodeBuilder("root");
|
||||
|
||||
foreach (var mesh in meshes)
|
||||
Traverse(settings, root);
|
||||
scene.AddNode(root);
|
||||
}
|
||||
|
||||
void Traverse(FStructFallback settings, NodeBuilder parent)
|
||||
{
|
||||
foreach (var property in settings.Properties)
|
||||
{
|
||||
mesh.TryConvert(out var converted);
|
||||
var lod0 =
|
||||
converted.LODs.Find(lod => !lod.SkipLod) ?? throw new Exception("Failed to find lod0");
|
||||
var key = property.Name.Text;
|
||||
|
||||
var emptyMaterial = new MaterialBuilder("empty_material");
|
||||
var materialMap = new Dictionary<string, MaterialBuilder>();
|
||||
|
||||
int sectionIndex = 0;
|
||||
foreach (var section in lod0.Sections.Value)
|
||||
if (settings.TryGet<UStaticMesh>(key, out var staticMesh))
|
||||
{
|
||||
var meshBuilder = new ConfiguredMeshBuilder();
|
||||
MaterialBuilder materialBuilder;
|
||||
AddMesh(key, parent, staticMesh);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (section.Material == null || section.MaterialName == null)
|
||||
if (settings.TryGet<USkeletalMesh>(key, out var skeletalMesh))
|
||||
{
|
||||
AddMesh(key, parent, skeletalMesh);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (settings.TryGet<FStructFallback[]>(key, out var array))
|
||||
{
|
||||
var group = parent.CreateNode(key);
|
||||
|
||||
foreach (var item in array)
|
||||
{
|
||||
materialBuilder = emptyMaterial;
|
||||
}
|
||||
else if (materialMap.TryGetValue(section.MaterialName, out MaterialBuilder? value))
|
||||
{
|
||||
materialBuilder = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var materialInterface = section.Material.Load<UMaterialInterface>()!;
|
||||
var materialData = new MaterialData() { Parameters = new() };
|
||||
|
||||
materialInterface.GetParams(materialData.Parameters, options.MaterialFormat);
|
||||
materialBuilder = new();
|
||||
materialMap.Add(section.MaterialName, materialBuilder);
|
||||
|
||||
foreach (var parameterTexture in materialData.Parameters.Textures)
|
||||
{
|
||||
if (knownChannels.TryGetValue(parameterTexture.Key, out var channel))
|
||||
{
|
||||
var texture = parameterTexture.Value;
|
||||
var name = Path.GetFileNameWithoutExtension(texture.GetPathName());
|
||||
var path = $"../../../textures/{name}.webp";
|
||||
|
||||
var lastIndex = stubBytes.Length - 1;
|
||||
stubBytes[lastIndex] = (byte)(++stubBytes[lastIndex] % 255);
|
||||
var uniqueBytes = (byte[])stubBytes.Clone();
|
||||
var stubImage = new MemoryImage(uniqueBytes);
|
||||
|
||||
// Console.WriteLine(
|
||||
// $"created stub image {stubImage} @ {stubImage.SourcePath} w/ byte {uniqueBytes[lastIndex]}"
|
||||
// );
|
||||
|
||||
materialBuilder.WithChannelImage(channel, stubImage);
|
||||
materialBuilder.GetChannel(channel).Texture.PrimaryImage.Name = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Console.WriteLine($"Unsupported texture type: {parameterTexture.Key}");
|
||||
}
|
||||
}
|
||||
Traverse(item, group);
|
||||
}
|
||||
|
||||
var primitive = meshBuilder.UsePrimitive(materialBuilder);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int faceIndex = 0; faceIndex < section.NumFaces; faceIndex++)
|
||||
// Console.WriteLine(
|
||||
// $"{key} -> {property.Tag} ({property.TagData}) [{property.PropertyTagFlags}]"
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
void AddMesh(string key, NodeBuilder parent, UObject mesh)
|
||||
{
|
||||
var meshNode = parent.CreateNode(key);
|
||||
|
||||
if (mesh is UStaticMesh staticMesh)
|
||||
{
|
||||
staticMesh.TryConvert(out var convertedStaticMesh);
|
||||
|
||||
var lod0 = convertedStaticMesh.LODs.Find(lod => !lod.SkipLod)!;
|
||||
|
||||
var i = 0;
|
||||
foreach (var section in lod0.Sections!.Value)
|
||||
{
|
||||
var sectionNode = meshNode.CreateNode($"{i++}");
|
||||
var configured = ParseSection(section, lod0, lod0.Verts!);
|
||||
|
||||
scene.AddRigidMesh(configured, sectionNode);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mesh is USkeletalMesh skeletalMesh)
|
||||
{
|
||||
skeletalMesh.TryConvert(out var convertedSkeletalMesh);
|
||||
|
||||
var lod0 = convertedSkeletalMesh.LODs.Find(lod => !lod.SkipLod)!;
|
||||
|
||||
var i = 0;
|
||||
foreach (var section in lod0.Sections!.Value)
|
||||
{
|
||||
var sectionNode = meshNode.CreateNode($"{i++}");
|
||||
var configured = ParseSection(section, lod0, lod0.Verts!);
|
||||
|
||||
scene.AddRigidMesh(configured, sectionNode);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ConfiguredMeshBuilder ParseSection(CMeshSection section, CBaseMeshLod lod, CMeshVertex[] vertices)
|
||||
{
|
||||
var meshBuilder = new ConfiguredMeshBuilder();
|
||||
MaterialBuilder materialBuilder;
|
||||
|
||||
if (section.Material == null || section.MaterialName == null)
|
||||
{
|
||||
materialBuilder = emptyMaterial;
|
||||
}
|
||||
else if (materialMap.TryGetValue(section.MaterialName, out MaterialBuilder? value))
|
||||
{
|
||||
materialBuilder = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var materialInterface = section.Material.Load<UMaterialInterface>()!;
|
||||
var materialData = new MaterialData() { Parameters = new() };
|
||||
|
||||
materialInterface.GetParams(materialData.Parameters, options.MaterialFormat);
|
||||
materialBuilder = new();
|
||||
materialMap.Add(section.MaterialName, materialBuilder);
|
||||
|
||||
foreach (var parameterTexture in materialData.Parameters.Textures)
|
||||
{
|
||||
if (knownChannels.TryGetValue(parameterTexture.Key, out var channel))
|
||||
{
|
||||
var wedgeIndex = new int[3];
|
||||
for (var k = 0; k < wedgeIndex.Length; k++)
|
||||
{
|
||||
wedgeIndex[k] = (int)lod0.Indices.Value[section.FirstIndex + faceIndex * 3 + k];
|
||||
}
|
||||
var texture = parameterTexture.Value;
|
||||
var name = Path.GetFileNameWithoutExtension(texture.GetPathName());
|
||||
var path = $"../../../textures/{name}.webp";
|
||||
|
||||
var vertex1 = lod0.Verts[wedgeIndex[0]];
|
||||
var vertex2 = lod0.Verts[wedgeIndex[1]];
|
||||
var vertex3 = lod0.Verts[wedgeIndex[2]];
|
||||
var lastIndex = stubBytes.Length - 1;
|
||||
stubBytes[lastIndex] = (byte)(++stubBytes[lastIndex] % 255);
|
||||
var uniqueBytes = (byte[])stubBytes.Clone();
|
||||
var stubImage = new MemoryImage(uniqueBytes);
|
||||
|
||||
var (v1, v2, v3) = PrepareTris(vertex1, vertex2, vertex3);
|
||||
var (c1, c2, c3) = PrepareUVsAndTexCoords(lod0, vertex1, vertex2, vertex3, wedgeIndex);
|
||||
|
||||
primitive.AddTriangle((v1, c1), (v2, c2), (v3, c3));
|
||||
materialBuilder.WithChannelImage(channel, stubImage);
|
||||
materialBuilder.GetChannel(channel).Texture.PrimaryImage.Name = path;
|
||||
}
|
||||
|
||||
scene.AddRigidMesh(meshBuilder, Matrix4x4.Identity);
|
||||
}
|
||||
}
|
||||
|
||||
var primitive = meshBuilder.UsePrimitive(materialBuilder);
|
||||
|
||||
for (int faceIndex = 0; faceIndex < section.NumFaces; faceIndex++)
|
||||
{
|
||||
var wedgeIndex = new int[3];
|
||||
for (var k = 0; k < wedgeIndex.Length; k++)
|
||||
{
|
||||
wedgeIndex[k] = (int)lod.Indices!.Value[section.FirstIndex + faceIndex * 3 + k];
|
||||
}
|
||||
|
||||
var vertex1 = vertices[wedgeIndex[0]];
|
||||
var vertex2 = vertices[wedgeIndex[1]];
|
||||
var vertex3 = vertices[wedgeIndex[2]];
|
||||
|
||||
var (v1, v2, v3) = PrepareTriangles(vertex1, vertex2, vertex3);
|
||||
var (c1, c2, c3) = PrepareUVsAndTexCoords(lod, vertex1, vertex2, vertex3, wedgeIndex);
|
||||
|
||||
primitive.AddTriangle((v1, c1), (v2, c2), (v3, c3));
|
||||
}
|
||||
|
||||
return meshBuilder;
|
||||
}
|
||||
|
||||
public byte[] Write(string name)
|
||||
@@ -138,11 +201,8 @@ public class MonoGltf
|
||||
|
||||
var mappedTextures = new Dictionary<MemoryImage, string>();
|
||||
|
||||
// Console.WriteLine($"found {model.LogicalImages.Count} logical images");
|
||||
|
||||
foreach (var image in model.LogicalImages)
|
||||
{
|
||||
// Console.WriteLine($"mapping {image.Content} ({image.Name})");
|
||||
mappedTextures[image.Content] = image.Name!;
|
||||
image.Name = null;
|
||||
}
|
||||
@@ -156,8 +216,6 @@ public class MonoGltf
|
||||
ImageWriting = ResourceWriteMode.SatelliteFile,
|
||||
ImageWriteCallback = (context, assetName, image) =>
|
||||
{
|
||||
// Console.WriteLine($"got {image} -> {mappedTextures[image]}");
|
||||
|
||||
return mappedTextures[image];
|
||||
},
|
||||
}
|
||||
@@ -172,7 +230,7 @@ public class MonoGltf
|
||||
VertexPositionNormalTangent,
|
||||
VertexPositionNormalTangent,
|
||||
VertexPositionNormalTangent
|
||||
) PrepareTris(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3)
|
||||
) PrepareTriangles(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3)
|
||||
{
|
||||
var v1 = new VertexPositionNormalTangent(
|
||||
SwapYZ(vert1.Position * 0.01f),
|
||||
@@ -228,7 +286,7 @@ public class MonoGltf
|
||||
vert1,
|
||||
vert2,
|
||||
vert3,
|
||||
lod.ExtraUV.Value,
|
||||
lod.ExtraUV!.Value,
|
||||
indices
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ import {
|
||||
type StageParameters,
|
||||
} from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import { OrbitControls } from "@react-three/drei";
|
||||
import { Canvas, useLoader } from "@react-three/fiber";
|
||||
import { Mesh, MeshStandardMaterial } from "three";
|
||||
import { GLTFLoader } from "three-stdlib";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { useProtagonist } from "../../hooks/useProtagonist";
|
||||
import { useTankChassisModel } from "../../hooks/useTankChassisModel";
|
||||
import { useTankGunModel } from "../../hooks/useTankGunModel";
|
||||
import { useTankHullModel } from "../../hooks/useTankHullModel";
|
||||
import { useTankTurretModel } from "../../hooks/useTankTurretModel";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
interface TankopediaSandboxProps {
|
||||
@@ -40,47 +42,26 @@ function Content({ parameters }: TankopediaSandboxProps) {
|
||||
({ tank_part }) => tank_part === VisualChanges_TankPart.TANK_PART_GUN,
|
||||
)!.name;
|
||||
|
||||
const hull = useLoader(GLTFLoader, `/models/tanks/${tank.id}/hull.glb`);
|
||||
const chassis = useLoader(GLTFLoader, `/models/tanks/${tank.id}/chassis.glb`);
|
||||
const turret = useLoader(
|
||||
GLTFLoader,
|
||||
`/models/tanks/${tank.id}/${selectedTurret}.glb`,
|
||||
);
|
||||
const gun = useLoader(
|
||||
GLTFLoader,
|
||||
`/models/tanks/${tank.id}/${selectedGun}.glb`,
|
||||
);
|
||||
const collision = false;
|
||||
|
||||
const gltfs = [turret, gun, hull, chassis];
|
||||
|
||||
for (const gltf of gltfs) {
|
||||
gltf.scene.traverse((child) => {
|
||||
if (
|
||||
child instanceof Mesh &&
|
||||
child.material instanceof MeshStandardMaterial
|
||||
) {
|
||||
// TODO: remove this hack and fix the gltf
|
||||
child.material.vertexColors = false;
|
||||
}
|
||||
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
});
|
||||
}
|
||||
const hull = useTankHullModel();
|
||||
const chassis = useTankChassisModel();
|
||||
const turret = useTankTurretModel(parameters);
|
||||
const gun = useTankGunModel(parameters);
|
||||
|
||||
return (
|
||||
<>
|
||||
<group>
|
||||
<primitive object={chassis.scene} />
|
||||
<primitive object={hull.scene} />
|
||||
<primitive object={chassis} />
|
||||
<primitive object={hull} />
|
||||
</group>
|
||||
|
||||
<group position={[0, 0, 5]}>
|
||||
<primitive object={turret.scene} />
|
||||
<primitive object={turret} />
|
||||
</group>
|
||||
|
||||
<group position={[0, 0, -5]}>
|
||||
<primitive object={gun.scene} />
|
||||
<primitive object={gun} />
|
||||
</group>
|
||||
|
||||
<spotLight
|
||||
@@ -89,6 +70,12 @@ function Content({ parameters }: TankopediaSandboxProps) {
|
||||
intensity={2 ** 10}
|
||||
penumbra={1}
|
||||
/>
|
||||
<spotLight
|
||||
castShadow
|
||||
position={[-3, -10, -5]}
|
||||
intensity={2 ** 10}
|
||||
penumbra={1}
|
||||
/>
|
||||
<ambientLight />
|
||||
|
||||
<mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]}>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useProtagonist } from "./useProtagonist";
|
||||
import { useTankModel } from "./useTankModel";
|
||||
|
||||
export function useTankChassisModel() {
|
||||
const tank = useProtagonist();
|
||||
|
||||
const map = {
|
||||
Wheels: true,
|
||||
LeftTracks: true,
|
||||
RightTracks: true,
|
||||
LeftTrackCrashed: false,
|
||||
RightTrackCrashed: false,
|
||||
CollisionMesh: false,
|
||||
};
|
||||
|
||||
return useTankModel(`/models/tanks/${tank.id}/chassis.glb`, map);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
VisualChanges_TankPart,
|
||||
type StageParameters,
|
||||
} from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import { useProtagonist } from "./useProtagonist";
|
||||
import { useTankModel } from "./useTankModel";
|
||||
|
||||
export function useTankGunModel(parameters: StageParameters) {
|
||||
const tank = useProtagonist();
|
||||
const selected = parameters.visual_changes.find(
|
||||
({ tank_part }) => tank_part === VisualChanges_TankPart.TANK_PART_GUN,
|
||||
);
|
||||
|
||||
const map = {
|
||||
GunMesh: true,
|
||||
MaskMesh: true,
|
||||
CollisionMesh: false,
|
||||
};
|
||||
|
||||
return useTankModel(`/models/tanks/${tank.id}/${selected!.name}.glb`, map);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useProtagonist } from "./useProtagonist";
|
||||
import { useTankModel } from "./useTankModel";
|
||||
|
||||
export function useTankHullModel() {
|
||||
const tank = useProtagonist();
|
||||
|
||||
const map = {
|
||||
Mesh: true,
|
||||
CollisionMesh: false,
|
||||
};
|
||||
|
||||
return useTankModel(`/models/tanks/${tank.id}/hull.glb`, map);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useLoader } from "@react-three/fiber";
|
||||
import { Mesh, MeshStandardMaterial } from "three";
|
||||
import { GLTFLoader } from "three-stdlib";
|
||||
|
||||
export function useTankModel(url: string, map: Record<string, boolean>) {
|
||||
const gltf = useLoader(GLTFLoader, url);
|
||||
const [root] = gltf.scene.children;
|
||||
|
||||
root.traverse((child) => {
|
||||
if (
|
||||
child instanceof Mesh &&
|
||||
child.material instanceof MeshStandardMaterial
|
||||
) {
|
||||
// TODO: remove this hack and fix the gltf
|
||||
child.material.vertexColors = false;
|
||||
}
|
||||
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
});
|
||||
|
||||
for (const child of root.children) {
|
||||
if (child.name in map) {
|
||||
child.visible = map[child.name];
|
||||
continue;
|
||||
}
|
||||
|
||||
child.visible = false;
|
||||
console.warn(`Root child ${child.name} isn't recognized`);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
VisualChanges_TankPart,
|
||||
type StageParameters,
|
||||
} from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import { useProtagonist } from "./useProtagonist";
|
||||
import { useTankModel } from "./useTankModel";
|
||||
|
||||
export function useTankTurretModel(parameters: StageParameters) {
|
||||
const tank = useProtagonist();
|
||||
const selected = parameters.visual_changes.find(
|
||||
({ tank_part }) => tank_part === VisualChanges_TankPart.TANK_PART_TURRET,
|
||||
);
|
||||
|
||||
const map = {
|
||||
Mesh: true,
|
||||
CollisionMesh: false,
|
||||
};
|
||||
|
||||
return useTankModel(`/models/tanks/${tank.id}/${selected!.name}.glb`, map);
|
||||
}
|
||||
Reference in new issue
Block a user