diff --git a/Assets/Scripts/Data/QModel.cs b/Assets/Scripts/Data/QModel.cs index e20c332..d3f7310 100644 --- a/Assets/Scripts/Data/QModel.cs +++ b/Assets/Scripts/Data/QModel.cs @@ -281,7 +281,7 @@ public struct QSurface public QVec3 mins, maxs; public IntPtr plane; // Pointer to mplane_t - public int flags; + public QSurfaceFlags flags; public int firstEdge; public int numEdges; @@ -355,3 +355,24 @@ public struct QTexInfo // This is safe as the Quake engine guarantees this pointer is never null (see: Mod_LoadTexinfo) public QTexture Texture => Marshal.PtrToStructure(texture); } + +/// +/// Managed equivalent of SURF_ defines +/// +[Flags] +public enum QSurfaceFlags : int +{ + PlaneBack = 0x2, + DrawSky = 0x4, + DrawSprite = 0x8, + DrawTurbulence = 0x10, + DrawTiled = 0x20, + DrawBackground = 0x40, + Underwater = 0x80, + NoTexture = 0x100, + DrawFence = 0x200, + DrawLava = 0x400, + DrawSlime = 0x800, + DrawTeleporter = 0x1000, + DrawWater = 0x2000, +} diff --git a/Assets/Scripts/Game/GameState.cs b/Assets/Scripts/Game/GameState.cs index 25d1192..8eba75d 100644 --- a/Assets/Scripts/Game/GameState.cs +++ b/Assets/Scripts/Game/GameState.cs @@ -50,7 +50,9 @@ public class GameState // TODO FIXME This is wrong for brush model entities uq.CurrentStyle.SetupWorldRenderer(mr); - mr.material = uq.CurrentStyle.CreateWorldMaterial(); // TODO FIXME this currently leaks Materials + mr.material = surfaceMesh.Flags.HasFlag(QSurfaceFlags.DrawTurbulence) + ? uq.CurrentStyle.CreateLiquidMaterial(surfaceMesh.Flags) + : uq.CurrentStyle.CreateWorldMaterial(); // TODO FIXME this currently leaks Materials uint texNum = surfaceMesh.TextureNum; if (uq.GameAssets.TryGetTexture(texNum, out var texture)) diff --git a/Assets/Scripts/Support/BrushModel.cs b/Assets/Scripts/Support/BrushModel.cs index cbb4658..b810c33 100644 --- a/Assets/Scripts/Support/BrushModel.cs +++ b/Assets/Scripts/Support/BrushModel.cs @@ -40,16 +40,16 @@ public class BrushModel // Traverse the BSP tree and group the surfaces based on their material properties var headNode = inSubModels[modelIdx].GetHeadNode(model); - var surfaceGroups = new Dictionary<(IntPtr, int), List>(); + var surfaceGroups = new Dictionary<(IntPtr, int, QSurfaceFlags), List>(); GroupSurfaces(headNode, inSurfaces, surfaceGroups); // Create a single mesh for each group of surfaces foreach (var group in surfaceGroups) { var key = group.Key; - var mesh = CreateMeshFromSurfaces(group.Value, $"T{key.Item1}_L{key.Item2}"); + var mesh = CreateMeshFromSurfaces(group.Value, $"T{key.Item1}_L{key.Item2}_F{(int)key.Item3}"); - subModel.AddSurfaceMesh(new SurfaceMesh(mesh, key.Item1, key.Item2)); + subModel.AddSurfaceMesh(new SurfaceMesh(mesh, key.Item1, key.Item2, key.Item3)); } } } @@ -64,7 +64,7 @@ public class BrushModel subModels.Clear(); } - private void GroupSurfaces(QNode node, QSurface[] surfaces, Dictionary<(IntPtr, int), List> surfaceGroups) + private void GroupSurfaces(QNode node, QSurface[] surfaces, Dictionary<(IntPtr, int, QSurfaceFlags), List> surfaceGroups) { if (node.contents < 0) // Leaf node return; @@ -75,7 +75,8 @@ public class BrushModel IntPtr texPtr = surface.TextureInfo.texture; int lightNum = surface.lightmapTextureNum; - var key = (texPtr, lightNum); + QSurfaceFlags flags = surface.flags; + var key = (texPtr, lightNum, flags); if (!surfaceGroups.ContainsKey(key)) surfaceGroups[key] = new List(); @@ -162,8 +163,9 @@ public class BrushModel public uint FullBrightNum { get; } public uint WarpImageNum { get; } public int Lightmap { get; } + public QSurfaceFlags Flags { get; } - public SurfaceMesh(Mesh mesh, IntPtr texturePtr, int lightmap) + public SurfaceMesh(Mesh mesh, IntPtr texturePtr, int lightmap, QSurfaceFlags flags) { Mesh = mesh; var texture = Marshal.PtrToStructure(texturePtr); @@ -171,6 +173,7 @@ public class BrushModel FullBrightNum = texture.FullBrightNum; WarpImageNum = texture.WarpImageNum; Lightmap = lightmap; + Flags = flags; } public void Dispose() diff --git a/Assets/Scripts/VisualStyle.cs b/Assets/Scripts/VisualStyle.cs index 21cf7ee..048f7c9 100644 --- a/Assets/Scripts/VisualStyle.cs +++ b/Assets/Scripts/VisualStyle.cs @@ -11,8 +11,16 @@ public class VisualStyle : ScriptableObject [SerializeField] protected Material worldMaterial; // TODO: split into wall, liquid, sky, etc - - // TODO: add particle effects + + [SerializeField] + protected Material liquidMaterial; + + [SerializeField] + protected LiquidProperties liquidProperties = new LiquidProperties(); + + [SerializeField] + protected ParticleSystems particles = new ParticleSystems(); + public ParticleSystems Particles => particles; public virtual Material CreateEntityMaterial() { @@ -23,6 +31,26 @@ public class VisualStyle : ScriptableObject { return new Material(worldMaterial); } + + public virtual Material CreateLiquidMaterial(QSurfaceFlags surfaceFlags) + { + if (liquidMaterial == null) + return CreateWorldMaterial(); + + float alpha = 1f; + if (surfaceFlags.HasFlag(QSurfaceFlags.DrawWater)) + alpha = liquidProperties.waterAlpha; + else if (surfaceFlags.HasFlag(QSurfaceFlags.DrawSlime)) + alpha = liquidProperties.slimeAlpha; + else if (surfaceFlags.HasFlag(QSurfaceFlags.DrawLava)) + alpha = liquidProperties.lavaAlpha; + else if (surfaceFlags.HasFlag(QSurfaceFlags.DrawTeleporter)) + alpha = liquidProperties.teleporterAlpha; + + var material = new Material(liquidMaterial); + material.SetColor("_BaseColor", new Color(1, 1, 1, alpha)); + return material; + } public virtual void SetupEntityRenderer(MeshRenderer meshRenderer) { @@ -76,3 +104,27 @@ public class VisualStyle : ScriptableObject // TODO: lightmap texture } } + +[System.Serializable] +public class LiquidProperties +{ + [Range(0, 1)] + public float + waterAlpha = 0.85f, + slimeAlpha = 0.9f, + lavaAlpha = 0.95f, + teleporterAlpha = 1.0f; +} + +[System.Serializable] +public class ParticleSystems +{ + [SerializeField] + protected ParticleSystem explosion; + + public virtual void CreateExplosion(Vector3 position) + { + var go = Object.Instantiate(explosion.gameObject); + go.transform.position = position; + } +} diff --git a/Assets/Styles/GLQuake/GLQuake.asset b/Assets/Styles/GLQuake/GLQuake.asset index a5495ac..ad7c1fd 100644 --- a/Assets/Styles/GLQuake/GLQuake.asset +++ b/Assets/Styles/GLQuake/GLQuake.asset @@ -14,3 +14,11 @@ MonoBehaviour: m_EditorClassIdentifier: entityMaterial: {fileID: 2100000, guid: 4d7703ac1adf3534f89b4041b779ff5e, type: 2} worldMaterial: {fileID: 2100000, guid: fcbaf32c00bea2344bbb1419c61364b6, type: 2} + liquidMaterial: {fileID: 2100000, guid: cd59502a1689c0847a1963c60e347987, type: 2} + liquidProperties: + waterAlpha: 0.85 + slimeAlpha: 0.9 + lavaAlpha: 0.95 + teleporterAlpha: 1 + particles: + explosion: {fileID: 0} diff --git a/Assets/Styles/GLQuake/Materials/GLQuake_Liquid.mat b/Assets/Styles/GLQuake/Materials/GLQuake_Liquid.mat new file mode 100644 index 0000000..b79d064 --- /dev/null +++ b/Assets/Styles/GLQuake/Materials/GLQuake_Liquid.mat @@ -0,0 +1,135 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-538773200983810463 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 4 +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GLQuake_Liquid + m_Shader: {fileID: 4800000, guid: 8d2bb70cbf9db8d4da26e15b26e74248, type: 3} + m_ShaderKeywords: _EMISSION _RECEIVE_SHADOWS_OFF + m_LightmapFlags: 2 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: + - SHADOWCASTER + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ClearCoatMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Floats: + - _AlphaClip: 0 + - _Blend: 0 + - _BumpScale: 1 + - _ClearCoat: 0 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossinessSource: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 0 + - _SampleGI: 0 + - _Shininess: 0 + - _Smoothness: 0.5 + - _SmoothnessSource: 0 + - _SmoothnessTextureChannel: 0 + - _SpecSource: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 5 + - _Surface: 1 + - _WorkflowMode: 1 + - _ZWrite: 0 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 1, g: 1, b: 1, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] diff --git a/Assets/Styles/GLQuake/Materials/GLQuake_Liquid.mat.meta b/Assets/Styles/GLQuake/Materials/GLQuake_Liquid.mat.meta new file mode 100644 index 0000000..cbc3841 --- /dev/null +++ b/Assets/Styles/GLQuake/Materials/GLQuake_Liquid.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cd59502a1689c0847a1963c60e347987 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Styles/GLQuake/Materials/GLQuake_World.mat b/Assets/Styles/GLQuake/Materials/GLQuake_World.mat index aed4ae6..f8f23a8 100644 --- a/Assets/Styles/GLQuake/Materials/GLQuake_World.mat +++ b/Assets/Styles/GLQuake/Materials/GLQuake_World.mat @@ -41,6 +41,10 @@ Material: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} + - _ClearCoatMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} - _DetailAlbedoMap: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} @@ -93,6 +97,7 @@ Material: - _AlphaClip: 0 - _Blend: 0 - _BumpScale: 1 + - _ClearCoat: 0 - _ClearCoatMask: 0 - _ClearCoatSmoothness: 0 - _Cull: 2 @@ -110,6 +115,7 @@ Material: - _Parallax: 0.005 - _QueueOffset: 0 - _ReceiveShadows: 0 + - _SampleGI: 0 - _Shininess: 0 - _Smoothness: 0.5 - _SmoothnessSource: 0