using System; using System.Collections.Generic; using System.Runtime.InteropServices; /// /// Managed equivalent of model_t /// [StructLayout(LayoutKind.Sequential, Pack = 0, CharSet = CharSet.Ansi)] public class QModel { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = QConstants.MaxQPath)] public string name; public uint pathId; public bool needLoad; public QModelType type; public int numFrames; public QSyncType syncType; public int flags; // Volume occupied by the model graphics public QVec3 mins, maxs; public QVec3 ymins, ymaxs; public QVec3 rmins, rmaxs; // Solid volume for clipping public bool clipBox; public QVec3 clipMins, clipMaxs; // Brush model public int firstModelSurface, numModelSurfaces; public int numSubModels; public IntPtr subModels; // Array of dmodel_t public QSubModel[] SubModels => subModels.ToStructArray(numSubModels); public int numPlanes; public IntPtr planes; // Array of mplane_t public int numLeafs; public IntPtr leafs; // Array of mleaf_t public int numVertices; public IntPtr vertices; // Array of mvertex_t (which is just a single vec3_t) public int numEdges; public IntPtr edges; // Array of medge_t public int numNodes; public IntPtr nodes; // Array of mnode_t public int numTexInfo; public IntPtr texInfo; // Array of mtexinfo_t public int numSurfaces; public IntPtr surfaces; // Array of msurface_t public QSurface[] Surfaces => surfaces.ToStructArray(numSurfaces); public int numSurfEdges; public IntPtr surfEdges; // Array of int public int numClipNodes; public IntPtr clipNodes; // Array of dclipnode_t public int numMarkSurfaces; public IntPtr markSurfaces; // Array of msurface_t pointers [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxMapHulls)] public QHull[] hulls; public int numTextures; public IntPtr textures; // Array of texture_t pointers public IntPtr visData; // Array of bytes public IntPtr lightData; // Array of bytes [MarshalAs(UnmanagedType.LPStr)] public string entities; public bool visWarn; public int bspVersion; // VBO data from QuakeSpasm; unused by UniQuake public uint meshvbo; public uint meshIndexesVbo; public int vboIndexOfs; public int vboXyzOfs; public int vboStofs; public IntPtr userCache; } /// /// Managed equivalent of aliashdr_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public class QAliasHeader { public int ident; public int version; public QVec3 scale; public QVec3 scaleOrigin; public float boundingRadius; public QVec3 eyePosition; public int numSkins; public int skinWidth; public int skinHeight; public int numVerts; public int numTriangles; public int numFrames; public QSyncType syncType; public int flags; public float size; // VBO data from QuakeSpasm; unused by UniQuake public int numVertsVbo; public IntPtr meshDesc; public int numIndexes; public IntPtr indexes; public IntPtr vertexes; public int numPoses; public int poseVerts; public int poseData; public int commands; [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxSkins * 4)] public IntPtr[] glTextures; [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxSkins * 4)] public IntPtr[] fbTextures; [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxSkins)] public int[] texels; // Actually variable sized, but we receive an additional pointer to the first element so we can manually marshal the entire array [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public QAliasFrameDesc[] frames; } /// /// Managed equivalent of modtype_t /// public enum QModelType { Brush, Sprite, Alias, } /// /// Managed equivalent of synctype_t /// public enum QSyncType { Sync = 0, Rand } /// /// Managed equivalent of aliasframetype_t /// public enum QAliasFrameType { Single = 0, Group, } /// /// Managed equivalent of aliasskintype_t /// public enum QAliasSkinType { Single = 0, Group, } /// /// Managed equivalent of hull_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QHull { public IntPtr clipNodes; // Array of dclipnode_t public IntPtr planes; // Array of mplane_t public int firstClipNode; public int lastClipNode; public QVec3 clipMins; public QVec3 clipMaxs; } /// /// Managed equivalent of maliasframedesc_t /// [StructLayout(LayoutKind.Sequential, Pack = 0, CharSet = CharSet.Ansi)] public struct QAliasFrameDesc { public int firstPose; public int numPoses; public float interval; public QTriVertex bboxMin; public QTriVertex bboxMax; public int frame; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string name; } /// /// Managed equivalent of trivertx_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QTriVertex { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] v; public byte lightNormalIndex; } /// /// Managed equivalent of mtriangle_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QTriangle { public int facesFront; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public int[] vertIndex; } /// /// Managed equivalent of stvert_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QSTVert { public int onSeam; public int s, t; } /// /// Managed equivalent of dmodel_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QSubModel { public QVec3 mins, maxs; public QVec3 origin; [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxMapHulls)] public int[] headNode; public int visLeafs; public int firstFace, numFaces; public QNode GetHeadNode(QModel model) { IntPtr nodePtr = IntPtr.Add(model.nodes, headNode[0] * Marshal.SizeOf()); return Marshal.PtrToStructure(nodePtr); } } /// /// Managed equivalent of mnode_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QNode { // Common with leaf public int contents; // 0 for nodes, negative for leafs public int visFrame; public QVec3 mins, maxs; public IntPtr parent; // Pointer to mnode_t // Node specific public IntPtr plane; // Pointer to mplane_t [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public IntPtr[] children; // Array of pointers to mnode_t public uint firstSurface; public uint numSurfaces; public QNode[] Children => new[] { Marshal.PtrToStructure(children[0]), Marshal.PtrToStructure(children[1]), }; } /// /// Managed equivalent of mleaf_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QLeaf { // Common with node public int contents; // 0 for nodes, negative for leafs public int visFrame; public QVec3 mins, maxs; public IntPtr parent; // Pointer to mnode_t // Leaf-specific data is unused } /// /// Managed equivalent of msurface_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QSurface { public int visFrame; public bool culled; public QVec3 mins, maxs; public IntPtr plane; // Pointer to mplane_t public int flags; public int firstEdge; public int numEdges; public QVec2s textureMins; public QVec2s extents; public QVec2i lightST; public IntPtr polys; // Pointer to glpoly_t public IntPtr textureChain; // Pointer to msurface_t public IntPtr texInfo; // Pointer to mtexinfo_t public int vboFirstVert; // QuakeSpasm VBO stuff public int dLightFrame; [MarshalAs(UnmanagedType.ByValArray, SizeConst = (QConstants.MaxDLights + 31) >> 5)] public uint[] dLightBits; public int lightmapTextureNum; [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxLightmaps)] public byte[] styles; [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxLightmaps)] public int[] cachedLight; public bool cachedDLight; public IntPtr samples; // Pointer to byte // This is safe as the Quake engine guarantees this pointer is never null (see: Mod_LoadFaces) public QTexInfo TextureInfo => Marshal.PtrToStructure(texInfo); public IEnumerable GetPolygons() { // This is so nasty. We have to deconstruct a linked list of variable-sized structs. Yuck. int offset = Marshal.SizeOf() * 2 + Marshal.SizeOf(); IntPtr polyPtr = polys; while (polyPtr != IntPtr.Zero) { QGLPoly polygon = Marshal.PtrToStructure(polyPtr); QGLPolyVert[] vertices = IntPtr.Add(polyPtr, offset).ToStructArray(polygon.numVerts); yield return vertices; polyPtr = polygon.next; } } } /// /// Managed equivalent of glpoly_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QGLPoly { public IntPtr next; // Pointer to glpoly_t (next in linked list) public IntPtr chain; // Pointer to glpoly_t (start of linked list) public int numVerts; // No need to include this field in the struct, as we calculate its offset and marshal the data manually //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public QGLPolyVert[] verts; // Variable sized } [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QGLPolyVert { public QVec3 position; public QVec2 textureUV; public QVec2 lightmapUV; } /// /// Managed equivalent of mtexinfo_t /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QTexInfo { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2 * 4)] public float[] vecs; public float mipAdjust; public IntPtr texture; // Pointer to texture_t public int flags; // This is safe as the Quake engine guarantees this pointer is never null (see: Mod_LoadTexinfo) public QTexture Texture => Marshal.PtrToStructure(texture); }