diff --git a/Assets/Scripts/Game/Entity.cs b/Assets/Scripts/Game/Entity.cs index 977313d..169239f 100644 --- a/Assets/Scripts/Game/Entity.cs +++ b/Assets/Scripts/Game/Entity.cs @@ -9,6 +9,7 @@ public class Entity private SkinnedMeshRenderer meshRenderer; private AliasModel aliasModel; + private GameObject worldModel; public Entity(int entityNum) { @@ -48,6 +49,12 @@ public class Entity UpdateAnimation(0); } + public void SetWorldModel(GameObject worldModelGO) + { + worldModel = worldModelGO; + worldModel.transform.SetParent(gameObject.transform); + } + public void UpdateAnimation(float frameNum) { if (aliasModel != null) @@ -58,6 +65,11 @@ public class Entity if (mesh != null && mesh.blendShapeCount > 0) meshRenderer.SetBlendShapeWeight(0, blendWeight); } + + if (worldModel != null) + { + // TODO: update texture animation + } } public void SetTransform(Vector3 position, Quaternion rotation) diff --git a/Assets/Scripts/Game/GameAssets.cs b/Assets/Scripts/Game/GameAssets.cs index 37e13c3..713c5f0 100644 --- a/Assets/Scripts/Game/GameAssets.cs +++ b/Assets/Scripts/Game/GameAssets.cs @@ -71,6 +71,18 @@ public class GameAssets worldModel = brushModel; } + public bool TryGetWorldSubModel(int subModelIndex, out BrushModel.SubModel subModel) + { + if (worldModel == null || subModelIndex < 0 || subModelIndex >= worldModel.SubModelCount) + { + subModel = null; + return false; + } + + subModel = worldModel.GetSubModel(subModelIndex); + return true; + } + public void Destroy() { if (worldModel != null) diff --git a/Assets/Scripts/Game/GameState.cs b/Assets/Scripts/Game/GameState.cs index e072f01..e426f93 100644 --- a/Assets/Scripts/Game/GameState.cs +++ b/Assets/Scripts/Game/GameState.cs @@ -26,38 +26,43 @@ public class GameState { Destroy(); - // DEBUG - we'll want to instantiate prefabs for each submodel and assign materials from a preset collection worldGameObject = new GameObject(worldModel.Name); - //for (int i = 0; i < worldModel.SubModelCount; ++i) - for (int i = 0; i < 1; ++i) + + // The first sub-model contains all of the static geometry + var subModel = worldModel.GetSubModel(0); + var subModelGO = CreateWorldGameObject(subModel); + subModelGO.transform.SetParent(worldGameObject.transform); + } + + private GameObject CreateWorldGameObject(BrushModel.SubModel subModel) + { + var subModelGO = new GameObject(subModel.Name); + + // DEBUG - we'll want to instantiate prefabs for each sub-model and assign materials from a preset collection + foreach (var surfaceMesh in subModel.SurfaceMeshes) { - var subModel = worldModel.GetSubModel(i); - var subModelGO = new GameObject($"SubModel_{i}"); - subModelGO.transform.SetParent(worldGameObject.transform); - - foreach (var surfaceMesh in subModel.SurfaceMeshes) + var meshGO = new GameObject(surfaceMesh.Mesh.name); + meshGO.transform.SetParent(subModelGO.transform); + + var mf = meshGO.AddComponent(); + mf.sharedMesh = surfaceMesh.Mesh; + + var material = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); + uint texNum = surfaceMesh.Texture.TextureNum; + if (uq.GameAssets.TryGetTexture(texNum, out var texture)) { - var meshGO = new GameObject(surfaceMesh.Mesh.name); - meshGO.transform.SetParent(subModelGO.transform); - - var mf = meshGO.AddComponent(); - mf.sharedMesh = surfaceMesh.Mesh; - - var material = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); - uint texNum = surfaceMesh.Texture.TextureNum; - if (uq.GameAssets.TryGetTexture(texNum, out var texture)) - { - material.mainTexture = texture; - } - - var mr = meshGO.AddComponent(); - mr.sharedMaterial = material; - mr.shadowCastingMode = ShadowCastingMode.Off; - mr.receiveShadows = false; - mr.lightProbeUsage = LightProbeUsage.Off; - mr.reflectionProbeUsage = ReflectionProbeUsage.Off; + material.mainTexture = texture; } + + var mr = meshGO.AddComponent(); + mr.sharedMaterial = material; + mr.shadowCastingMode = ShadowCastingMode.Off; + mr.receiveShadows = false; + mr.lightProbeUsage = LightProbeUsage.Off; + mr.reflectionProbeUsage = ReflectionProbeUsage.Off; } + + return subModelGO; } private void DestroyWorld() @@ -75,7 +80,7 @@ public class GameState return entity; } - public void SetEntityAliasModel(int entityNum, string modelName) + private Entity GetOrCreateEntity(int entityNum) { if (!entities.TryGetValue(entityNum, out var entity)) { @@ -83,6 +88,13 @@ public class GameState entities.Add(entityNum, entity); } + return entity; + } + + public void SetEntityAliasModel(int entityNum, string modelName) + { + var entity = GetOrCreateEntity(entityNum); + if (!uq.GameAssets.TryGetAliasModel(modelName, out var aliasModel)) { Debug.LogWarning($"Unknown alias model name: {modelName}"); @@ -94,7 +106,19 @@ public class GameState public void SetEntityWorldModel(int entityNum, int subModelNum) { - // TODO: obtain Mesh from brush submodel and assign it to MeshRenderer + var entity = GetOrCreateEntity(entityNum); + + if (!uq.GameAssets.TryGetWorldSubModel(subModelNum, out var subModel)) + { + Debug.LogWarning($"Invalid world sub-model number: {subModelNum}"); + return; + } + + // TODO: these relatively complex world game objects are going to get destroyed and re-created all the time + // as the player moves through the map and moves in and out of range of these entities. This can and should + // be done more efficiently by creating the game objects only once and enabling/disabling them on demand. + var worldModelGO = CreateWorldGameObject(subModel); + entity.SetWorldModel(worldModelGO); } public void RemoveEntity(int entityNum) diff --git a/Assets/Scripts/Support/BrushModel.cs b/Assets/Scripts/Support/BrushModel.cs index 18dbb0b..41d4c75 100644 --- a/Assets/Scripts/Support/BrushModel.cs +++ b/Assets/Scripts/Support/BrushModel.cs @@ -35,7 +35,7 @@ public class BrushModel for (int modelIdx = 0; modelIdx < inSubModels.Length; ++modelIdx) { - var subModel = new SubModel(); + var subModel = new SubModel($"SubModel_{modelIdx}"); subModels.Add(subModel); // Traverse the BSP tree and group the surfaces based on their material properties @@ -134,8 +134,14 @@ public class BrushModel public class SubModel { + public string Name { get; private set; } public List SurfaceMeshes { get; } = new List(); + public SubModel(string name) + { + Name = name; + } + public void AddSurfaceMesh(SurfaceMesh surfaceMesh) { SurfaceMeshes.Add(surfaceMesh); diff --git a/engine/Quake/cl_main.c b/engine/Quake/cl_main.c index c00f8c9..f184bef 100644 --- a/engine/Quake/cl_main.c +++ b/engine/Quake/cl_main.c @@ -588,7 +588,7 @@ void CL_RelinkEntities (void) cl_numvisedicts++; } - //UQ_Game_SetEntityTransform(i, ent->origin, ent->angles); + UQ_Game_SetEntityTransform(i, ent->origin, ent->angles); } } diff --git a/engine/Quake/cl_parse.c b/engine/Quake/cl_parse.c index d153925..1577c13 100644 --- a/engine/Quake/cl_parse.c +++ b/engine/Quake/cl_parse.c @@ -618,7 +618,8 @@ void CL_ParseUpdate (int bits) ent->lerpflags |= LERP_RESETANIM; //johnfitz -- don't lerp animation across model changes - UQ_Game_SetEntityModel(num, model ? model->name : NULL); + if (num != cl.viewentity) + UQ_Game_SetEntityModel(num, model ? model->name : NULL); } //johnfitz