From 5390483b0be82e40b9d1793041b7703734c5ea71 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Mon, 12 Jul 2021 15:12:37 +0200 Subject: [PATCH] Upload non-world brush models and allow them to be assigned to entities. It's not terribly pretty but it (mostly) works. There still seems to be an issue with texture assignment on subsequent map loads. --- Assets/Scripts/Game/Entity.cs | 10 ++++++++++ Assets/Scripts/Game/GameAssets.cs | 15 +++++++++++++++ Assets/Scripts/Game/GameState.cs | 26 +++++++++++++++++++++++--- Assets/Scripts/Modules/GameModule.cs | 3 +-- Assets/Scripts/Support/AliasModel.cs | 8 ++++++++ engine/Quake/cl_parse.c | 9 +++++++++ 6 files changed, 66 insertions(+), 5 deletions(-) diff --git a/Assets/Scripts/Game/Entity.cs b/Assets/Scripts/Game/Entity.cs index 40e5201..d8dfde5 100644 --- a/Assets/Scripts/Game/Entity.cs +++ b/Assets/Scripts/Game/Entity.cs @@ -12,6 +12,7 @@ public class Entity private SkinnedMeshRenderer skinnedMeshRenderer; private AliasModel aliasModel; + private GameObject brushModel; private GameObject worldModel; public Entity(int entityNum) @@ -33,6 +34,8 @@ public class Entity public void ClearModel() { aliasModel = null; + brushModel = null; + worldModel = null; Object.Destroy(meshFilter); Object.Destroy(meshRenderer); @@ -52,6 +55,13 @@ public class Entity UpdateAnimation(0); } + public void SetBrushModel(GameObject brushModelGO) + { + ClearModel(); + brushModel = brushModelGO; + brushModel.transform.SetParent(gameObject.transform); + } + public void SetWorldModel(GameObject worldModelGO) { ClearModel(); diff --git a/Assets/Scripts/Game/GameAssets.cs b/Assets/Scripts/Game/GameAssets.cs index 713c5f0..015dd7d 100644 --- a/Assets/Scripts/Game/GameAssets.cs +++ b/Assets/Scripts/Game/GameAssets.cs @@ -65,6 +65,21 @@ public class GameAssets brushModels.Add(brushModel); } + public bool TryGetBrushModel(string name, out BrushModel brushModel) + { + foreach (var model in brushModels) + { + if (model.Name == name) + { + brushModel = model; + return true; + } + } + + brushModel = null; + return false; + } + public void SetWorldModel(BrushModel brushModel) { worldModel?.Dispose(); diff --git a/Assets/Scripts/Game/GameState.cs b/Assets/Scripts/Game/GameState.cs index e426f93..b1a0016 100644 --- a/Assets/Scripts/Game/GameState.cs +++ b/Assets/Scripts/Game/GameState.cs @@ -30,11 +30,11 @@ public class GameState // The first sub-model contains all of the static geometry var subModel = worldModel.GetSubModel(0); - var subModelGO = CreateWorldGameObject(subModel); + var subModelGO = CreateBrushGameObject(subModel); subModelGO.transform.SetParent(worldGameObject.transform); } - private GameObject CreateWorldGameObject(BrushModel.SubModel subModel) + private GameObject CreateBrushGameObject(BrushModel.SubModel subModel) { var subModelGO = new GameObject(subModel.Name); @@ -104,6 +104,26 @@ public class GameState entity.SetAliasModel(aliasModel); } + public void SetEntityBrushModel(int entityNum, string modelName) + { + var entity = GetOrCreateEntity(entityNum); + + if (!uq.GameAssets.TryGetBrushModel(modelName, out var brushModel)) + { + Debug.LogWarning($"Unknown brush model name: {modelName}"); + return; + } + + var brushModelGO = new GameObject(brushModel.Name); + for (int i = 0; i < brushModel.SubModelCount; ++i) + { + var subModelGO = CreateBrushGameObject(brushModel.GetSubModel(i)); + subModelGO.transform.SetParent(brushModelGO.transform); + } + + entity.SetBrushModel(brushModelGO); + } + public void SetEntityWorldModel(int entityNum, int subModelNum) { var entity = GetOrCreateEntity(entityNum); @@ -117,7 +137,7 @@ public class GameState // 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); + var worldModelGO = CreateBrushGameObject(subModel); entity.SetWorldModel(worldModelGO); } diff --git a/Assets/Scripts/Modules/GameModule.cs b/Assets/Scripts/Modules/GameModule.cs index 64ba3ea..72b0720 100644 --- a/Assets/Scripts/Modules/GameModule.cs +++ b/Assets/Scripts/Modules/GameModule.cs @@ -34,8 +34,7 @@ public partial class GameModule if (modelName.EndsWith(".bsp")) { - // TODO: non-world brush model - uq.GameState.GetEntity(entityNum)?.ClearModel(); + uq.GameState.SetEntityBrushModel(entityNum, modelName); return; } diff --git a/Assets/Scripts/Support/AliasModel.cs b/Assets/Scripts/Support/AliasModel.cs index c6b285c..c6a0c1f 100644 --- a/Assets/Scripts/Support/AliasModel.cs +++ b/Assets/Scripts/Support/AliasModel.cs @@ -91,6 +91,10 @@ public class AliasModel ImportGroupFrameAnimations(poseVertices, indices, uvs); } + /// + /// Single frame animations are used for dynamically animated entities. The game logic determines which frame is displayed at which time. + /// In Unity we still need to identify groups of frames that form a single animation sequence, so that we can smoothly loop animations when interpolation is enabled. + /// private void ImportSingleFrameAnimations(QTriVertex[][] poseVertices, ushort[] indices, Vector2[] uvs) { Mesh mesh; @@ -122,6 +126,10 @@ public class AliasModel animationMeshes.Add((startFrame, mesh)); } + /// + /// Grouped frame animations are used for entities that animate automatically in an endless cycle, e.g. flames. + /// They are set up once and then the renderer ensures they are updated at a steady rate. + /// private void ImportGroupFrameAnimations(QTriVertex[][] poseVertices, ushort[] indices, Vector2[] uvs) { for (int frameIdx = 0; frameIdx < header.numFrames; ++frameIdx) diff --git a/engine/Quake/cl_parse.c b/engine/Quake/cl_parse.c index 1577c13..1bcc2f1 100644 --- a/engine/Quake/cl_parse.c +++ b/engine/Quake/cl_parse.c @@ -393,6 +393,15 @@ void CL_ParseServerInfo (void) R_NewMap (); + for (i = 1; i < nummodels; i++) + { + if (cl.model_precache[i] == NULL || cl.model_precache[i] == cl.worldmodel || cl.model_precache[i]->type != mod_brush || cl.model_precache[i]->name[0] == '*') + continue; + + UQ_GL_UploadBrushModel(cl.model_precache[i]); + CL_KeepaliveMessage(); + } + //johnfitz -- clear out string; we don't consider identical //messages to be duplicates if the map has changed in between con_lastcenterstring[0] = 0;