diff --git a/Assets/Scripts/DataDefs/QExtensions.cs b/Assets/Scripts/DataDefs/QExtensions.cs new file mode 100644 index 0000000..053044a --- /dev/null +++ b/Assets/Scripts/DataDefs/QExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; + +public static class QExtensions +{ + public static TStruct[] ToStructArray(this IntPtr ptr, int count) + { + if (ptr == IntPtr.Zero) + return null; + + TStruct[] result = new TStruct[count]; + int size = Marshal.SizeOf(); + IntPtr current = ptr; + for (int i = 0; i < count; ++i) + { + result[i] = Marshal.PtrToStructure(current); + current = new IntPtr(current.ToInt64() + size); + } + return result; + } +} diff --git a/Assets/Scripts/DataDefs/QExtensions.cs.meta b/Assets/Scripts/DataDefs/QExtensions.cs.meta new file mode 100644 index 0000000..a1faf30 --- /dev/null +++ b/Assets/Scripts/DataDefs/QExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c414049936614c2ab8169a12cf45f430 +timeCreated: 1617358962 \ No newline at end of file diff --git a/Assets/Scripts/DataDefs/QMath.cs b/Assets/Scripts/DataDefs/QMath.cs new file mode 100644 index 0000000..4d75335 --- /dev/null +++ b/Assets/Scripts/DataDefs/QMath.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; + +/// +/// Managed equivalent of vec3_t +/// +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public struct QVec3 +{ + public float x; + public float y; + public float z; +} + +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public struct QVec4i +{ + public int x; + public int y; + public int z; + public int w; +} \ No newline at end of file diff --git a/Assets/Scripts/DataDefs/QMath.cs.meta b/Assets/Scripts/DataDefs/QMath.cs.meta new file mode 100644 index 0000000..aed233f --- /dev/null +++ b/Assets/Scripts/DataDefs/QMath.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 23d0952cd4454f4cbd94d4c4d1ae2667 +timeCreated: 1617282271 \ No newline at end of file diff --git a/Assets/Scripts/DataDefs/QModel.cs b/Assets/Scripts/DataDefs/QModel.cs new file mode 100644 index 0000000..c331f9d --- /dev/null +++ b/Assets/Scripts/DataDefs/QModel.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +/// +/// Managed equivalent of model_t +/// +[StructLayout(LayoutKind.Sequential, Pack = 0, CharSet = CharSet.Ansi)] +public class QModel +{ + private const int MaxPath = 64; // Should correspond to MAX_QPATH + private const int MaxMapHulls = 4; // Should correspond to MAX_MAP_HULLS + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MaxPath)] public string name; + 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 float radius; + + // 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 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 + + 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 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 = 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 IntPtr userCache; +} + +/// +/// Managed equivalent of aliashdr_t +/// +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public class QAliasHeader +{ + private const int MaxSkins = 32; // Should correspond to MAX_SKINS + + 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; + + public int numPoses; + public int poseVerts; + public int poseData; + public int commands; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public QVec4i[] glTextureNum; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public int[] texels; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public bool[] skinLuma; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public bool[] skinLuma8bit; + + // 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 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; +} diff --git a/Assets/Scripts/DataDefs/QModel.cs.meta b/Assets/Scripts/DataDefs/QModel.cs.meta new file mode 100644 index 0000000..220932e --- /dev/null +++ b/Assets/Scripts/DataDefs/QModel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5c427bd710e259c40813a869b8d12150 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/ModCalls.cs b/Assets/Scripts/ModCalls.cs new file mode 100644 index 0000000..f332d1a --- /dev/null +++ b/Assets/Scripts/ModCalls.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using AOT; +using UnityEngine; + +public class ModCalls : CallbackHandler +{ + private readonly UniQuake uq; + + public ModCalls(UniQuake uniQuake) + { + uq = uniQuake; + + var callbacks = new Callbacks + { + target = SelfPtr, + + ModUploadModel = CreateCallback(Callback_ModUploadModel), + }; + + RegisterCallbacks(callbacks); + } + + /// + /// This matches struct unity_syscalls_s from uniquake.h in native code. + /// + [StructLayout(LayoutKind.Sequential, Pack = 0)] + private class Callbacks + { + public IntPtr target; + + public IntPtr ModUploadModel; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void ModUploadModelCallback(IntPtr target, QModel model, QAliasHeader header, IntPtr frames); + + [MonoPInvokeCallback(typeof(ModUploadModelCallback))] + private static void Callback_ModUploadModel(IntPtr target, QModel model, QAliasHeader header, IntPtr frames) + { + if (header != null) + header.frames = frames.ToStructArray(header.numFrames); + + GetSelf(target).UploadModel(model, header); + } + + private void UploadModel(QModel model, QAliasHeader header) + { + Debug.Log($"Model '{model?.name}' of type {model?.type}, num frames = {header?.numFrames}"); + if (header?.frames != null) + { + string str = string.Join(", ", header.frames.Select(f => f.name)); + Debug.Log($"Frame list: {str}"); + } + } +} diff --git a/Assets/Scripts/ModCalls.cs.meta b/Assets/Scripts/ModCalls.cs.meta new file mode 100644 index 0000000..7c31553 --- /dev/null +++ b/Assets/Scripts/ModCalls.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aa8562de402f45e896cda0a84a21342c +timeCreated: 1617282485 \ No newline at end of file diff --git a/Assets/Scripts/UniQuake.cs b/Assets/Scripts/UniQuake.cs index 4db221c..2d04ce3 100644 --- a/Assets/Scripts/UniQuake.cs +++ b/Assets/Scripts/UniQuake.cs @@ -9,6 +9,7 @@ public class UniQuake: MonoBehaviour private QuakeParms quakeParms; private SysCalls sysCalls; + private ModCalls modCalls; void Start() { @@ -18,6 +19,7 @@ public class UniQuake: MonoBehaviour "-window", "-width", "1440", "-height", "1080", + "+developer", "1", }; quakeParms = new QuakeParms @@ -29,8 +31,9 @@ public class UniQuake: MonoBehaviour quakeParms.AllocateMemory(MemSize); sysCalls = new SysCalls(this); + modCalls = new ModCalls(this); - UniQuake_Init(quakeParms, sysCalls.ToIntPtr); + UniQuake_Init(quakeParms, sysCalls.ToIntPtr, modCalls.ToIntPtr); } void Update() @@ -50,7 +53,7 @@ public class UniQuake: MonoBehaviour } [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] - private static extern void UniQuake_Init(QuakeParms parms, IntPtr syscalls); + private static extern void UniQuake_Init(QuakeParms parms, IntPtr sysCalls, IntPtr modCalls); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] private static extern void UniQuake_Update(float deltaTime); diff --git a/engine/code/gl_mesh.c b/engine/code/gl_mesh.c index a62fa48..6fd56fe 100644 --- a/engine/code/gl_mesh.c +++ b/engine/code/gl_mesh.c @@ -358,5 +358,7 @@ void GL_MakeAliasModelDisplayLists (model_t *m, aliashdr_t *hdr) for (i=0 ; inumposes ; i++) for (j=0 ; jModUploadModel(unity_modcalls->target, mdl, aliashdr, &aliashdr->frames[0]); + else + unity_modcalls->ModUploadModel(unity_modcalls->target, mdl, aliashdr, NULL); +} diff --git a/engine/projects/uniquake/uniquake.c b/engine/projects/uniquake/uniquake.c index c1cf13a..5a3e606 100644 --- a/engine/projects/uniquake/uniquake.c +++ b/engine/projects/uniquake/uniquake.c @@ -2,9 +2,10 @@ #include "../../code/quakedef.h" -UNIQUAKE_API void UniQuake_Init(quakeparms_t *parms, const unity_syscalls_t *syscalls) +UNIQUAKE_API void UniQuake_Init(quakeparms_t *parms, const unity_syscalls_t *syscalls, const unity_modcalls_t *modcalls) { unity_syscalls = syscalls; + unity_modcalls = modcalls; COM_InitArgv(parms->argc, parms->argv); parms->argc = com_argc; diff --git a/engine/projects/uniquake/uniquake.h b/engine/projects/uniquake/uniquake.h index 644676d..6a77303 100644 --- a/engine/projects/uniquake/uniquake.h +++ b/engine/projects/uniquake/uniquake.h @@ -29,3 +29,6 @@ typedef struct unity_syscalls_s } unity_syscalls_t; extern const unity_syscalls_t *unity_syscalls; + +typedef struct unity_modcalls_s unity_modcalls_t; +extern const unity_modcalls_t *unity_modcalls; \ No newline at end of file diff --git a/engine/projects/uniquake/uniquake.vcxproj b/engine/projects/uniquake/uniquake.vcxproj index 0488c68..19df830 100644 --- a/engine/projects/uniquake/uniquake.vcxproj +++ b/engine/projects/uniquake/uniquake.vcxproj @@ -352,6 +352,7 @@ + diff --git a/engine/projects/uniquake/uniquake.vcxproj.filters b/engine/projects/uniquake/uniquake.vcxproj.filters index 201581e..184d612 100644 --- a/engine/projects/uniquake/uniquake.vcxproj.filters +++ b/engine/projects/uniquake/uniquake.vcxproj.filters @@ -530,6 +530,9 @@ Source Files + + Source Files +