From edb2cb31eeb70b5a760f7250fb56731db977f040 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 31 May 2020 00:01:00 +0200 Subject: [PATCH] - backend update with GZDoom model code. --- source/CMakeLists.txt | 12 + source/blood/src/view.cpp | 9 +- source/common/models/model.cpp | 236 ++++++ source/common/models/model.h | 83 +++ source/common/models/model_kvx.h | 69 ++ source/common/models/model_md2.h | 137 ++++ source/common/models/model_md3.h | 75 ++ source/common/models/model_obj.h | 106 +++ source/common/models/model_ue1.h | 111 +++ source/common/models/modelrenderer.h | 29 + source/common/models/models_md2.cpp | 555 ++++++++++++++ source/common/models/models_md3.cpp | 381 ++++++++++ source/common/models/models_obj.cpp | 700 ++++++++++++++++++ source/common/models/models_ue1.cpp | 307 ++++++++ source/common/models/models_voxel.cpp | 407 ++++++++++ source/common/models/tab_anorms.h | 489 ++++++++++++ source/common/models/voxels.cpp | 489 ++++++++++++ source/common/models/voxels.h | 83 +++ source/common/rendering/gl/gl_postprocess.cpp | 5 +- source/common/rendering/gl/gl_renderstate.cpp | 2 +- source/common/rendering/gl/gl_shader.cpp | 4 +- .../rendering/hwrenderer/data/hw_vrmodes.cpp | 5 +- source/common/rendering/r_videoscale.cpp | 3 +- source/common/textures/texture.cpp | 5 +- source/common/utility/m_bbox.cpp | 0 source/core/gameconfigfile.cpp | 9 +- source/core/gamecontrol.cpp | 10 +- .../core/rendering/gl/renderer/gl_renderer.h | 2 - .../rendering/gl/system/gl_framebuffer.cpp | 9 +- .../core/rendering/gl/system/gl_framebuffer.h | 2 +- source/core/rendering/v_framebuffer.cpp | 143 +--- source/core/rendering/v_video.cpp | 1 + source/core/rendering/v_video.h | 80 +- source/core/version.h | 2 + source/duke3d/src/game.cpp | 9 +- source/duke3d/src/gameexec.cpp | 5 +- source/duke3d/src/sector.cpp | 5 +- source/exhumed/src/view.cpp | 5 +- source/glbackend/glbackend.cpp | 55 +- source/glbackend/glbackend.h | 4 + source/rr/src/game.cpp | 5 +- source/rr/src/sector.cpp | 5 +- source/sw/src/draw.cpp | 5 +- source/sw/src/jsector.cpp | 5 +- 44 files changed, 4443 insertions(+), 220 deletions(-) create mode 100644 source/common/models/model.cpp create mode 100644 source/common/models/model.h create mode 100644 source/common/models/model_kvx.h create mode 100644 source/common/models/model_md2.h create mode 100644 source/common/models/model_md3.h create mode 100644 source/common/models/model_obj.h create mode 100644 source/common/models/model_ue1.h create mode 100644 source/common/models/modelrenderer.h create mode 100644 source/common/models/models_md2.cpp create mode 100644 source/common/models/models_md3.cpp create mode 100644 source/common/models/models_obj.cpp create mode 100644 source/common/models/models_ue1.cpp create mode 100644 source/common/models/models_voxel.cpp create mode 100644 source/common/models/tab_anorms.h create mode 100644 source/common/models/voxels.cpp create mode 100644 source/common/models/voxels.h create mode 100644 source/common/utility/m_bbox.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 117a7041f..45063ecd6 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -636,6 +636,7 @@ file( GLOB HEADER_FILES common/platform/posix/cocoa/*.h common/platform/posix/sdl/*.h common/platform/win32/*.h + common/models/*.h common/textures/*.h common/textures/hires/hqnx/*.h common/textures/hires/hqnx_asm/*.h @@ -815,6 +816,13 @@ set (PCH_SOURCES common/textures/formats/tgatexture.cpp common/textures/formats/stbtexture.cpp common/textures/hires/hqresize.cpp + common/models/models_md3.cpp + common/models/models_md2.cpp + common/models/models_voxel.cpp + common/models/models_ue1.cpp + common/models/models_obj.cpp + common/models/model.cpp + common/models/voxels.cpp common/console/c_commandline.cpp common/console/c_buttons.cpp common/console/c_bind.cpp @@ -838,6 +846,7 @@ set (PCH_SOURCES common/utility/s_playlist.cpp common/utility/zstrformat.cpp common/utility/name.cpp + common/utility/m_bbox.cpp common/thirdparty/md5.cpp common/thirdparty/superfasthash.cpp common/filesystem/filesystem.cpp @@ -1037,6 +1046,7 @@ include_directories( common/textures/formats common/textures/hires common/textures + common/models common/filesystem common/utility common/console @@ -1185,6 +1195,8 @@ source_group("Common\\Rendering\\Hardware Renderer" REGULAR_EXPRESSION "^${CMAKE source_group("Common\\Rendering\\Hardware Renderer\\Data" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/rendering/hwrenderer/data/.+") source_group("Common\\Rendering\\Hardware Renderer\\Postprocessing" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/rendering/hwrenderer/postprocessing/.+") source_group("Common\\Rendering\\OpenGL Loader" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/rendering/gl_load/.+") +source_group("Common\\Rendering\\OpenGL Backend" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/rendering/gl/.+") +source_group("Common\\Models" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/models/.+") source_group("Common\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/.+") source_group("Common\\Textures\\Hires" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/hires/.+") source_group("Common\\Textures\\Hires\\HQ Resize" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/hires/hqnx/.+") diff --git a/source/blood/src/view.cpp b/source/blood/src/view.cpp index 1ba7c276c..ff7e6569d 100644 --- a/source/blood/src/view.cpp +++ b/source/blood/src/view.cpp @@ -64,6 +64,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "gstrings.h" #include "v_2ddrawer.h" #include "v_video.h" +#include "glbackend/glbackend.h" CVARD(Bool, hud_powerupduration, true, CVAR_ARCHIVE/*|CVAR_FRONTEND_BLOOD*/, "enable/disable displaying the remaining seconds for power-ups") @@ -3241,7 +3242,7 @@ void viewDrawScreen(bool sceneonly) } r enderSetTarget(4079, 128, 128); renderSetAspect(65536, 78643); - screen->BeginScene(); + renderBeginScene(); int vd8 = pOther->pSprite->x; int vd4 = pOther->pSprite->y; int vd0 = pOther->zView; @@ -3307,7 +3308,7 @@ void viewDrawScreen(bool sceneonly) memcpy(gotpic+510, bakMirrorGotpic, 2); viewProcessSprites(vd8, vd4, vd0, v50, gInterpolate); renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); renderRestoreTarget(); #endif } @@ -3349,7 +3350,7 @@ void viewDrawScreen(bool sceneonly) } nSprite = nextspritestat[nSprite]; } - screen->BeginScene(); + renderBeginScene(); g_visibility = (int32_t)(ClipLow(gVisibility - 32 * gView->visibility - unk, 0) * (numplayers > 1 ? 1.f : r_ambientlightrecip)); cA = (cA + interpolateangfix16(fix16_from_int(deliriumTurnO), fix16_from_int(deliriumTurn), gInterpolate)) & 0x7ffffff; int vfc, vf8; @@ -3404,7 +3405,7 @@ void viewDrawScreen(bool sceneonly) sub_557C4(cX, cY, gInterpolate); renderDrawMasks(); gView->pSprite->cstat = bakCstat; - screen->FinishScene(); + renderFinishScene(); if ((v78 || bDelirium) && !sceneonly) { diff --git a/source/common/models/model.cpp b/source/common/models/model.cpp new file mode 100644 index 000000000..90dedc272 --- /dev/null +++ b/source/common/models/model.cpp @@ -0,0 +1,236 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2005-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_models.cpp +** +** General model handling code +** +**/ + +#include "filesystem.h" +#include "cmdlib.h" +#include "sc_man.h" +#include "m_crc32.h" +#include "printf.h" +#include "model_ue1.h" +#include "model_obj.h" +#include "model_md2.h" +#include "model_md3.h" +#include "model_kvx.h" +#include "i_time.h" +#include "voxels.h" +#include "texturemanager.h" +#include "modelrenderer.h" + + +TDeletingArray Models; +TArray SpriteModelFrames; + + +///////////////////////////////////////////////////////////////////////////// + +void FlushModels() +{ + for (int i = Models.Size() - 1; i >= 0; i--) + { + Models[i]->DestroyVertexBuffer(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +FModel::FModel() +{ + for (int i = 0; i < NumModelRendererTypes; i++) + mVBuf[i] = nullptr; +} + +FModel::~FModel() +{ + DestroyVertexBuffer(); +} + +void FModel::DestroyVertexBuffer() +{ + for (int i = 0; i < NumModelRendererTypes; i++) + { + delete mVBuf[i]; + mVBuf[i] = nullptr; + } +} + +//=========================================================================== +// +// FindGFXFile +// +//=========================================================================== + +static int FindGFXFile(FString & fn) +{ + int lump = fileSystem.CheckNumForFullName(fn); // if we find something that matches the name plus the extension, return it and do not enter the substitution logic below. + if (lump != -1) return lump; + + int best = -1; + int dot = fn.LastIndexOf('.'); + int slash = fn.LastIndexOf('/'); + if (dot > slash) fn.Truncate(dot); + + static const char * extensions[] = { ".png", ".jpg", ".tga", ".pcx", nullptr }; + + for (const char ** extp=extensions; *extp; extp++) + { + int lump = fileSystem.CheckNumForFullName(fn + *extp); + if (lump >= best) best = lump; + } + return best; +} + + +//=========================================================================== +// +// LoadSkin +// +//=========================================================================== + +FTextureID LoadSkin(const char * path, const char * fn) +{ + FString buffer; + + buffer.Format("%s%s", path, fn); + + int texlump = FindGFXFile(buffer); + const char * const texname = texlump < 0 ? fn : fileSystem.GetFileFullName(texlump); + return TexMan.CheckForTexture(texname, ETextureType::Any, FTextureManager::TEXMAN_TryAny); +} + +//=========================================================================== +// +// ModelFrameHash +// +//=========================================================================== + +int ModelFrameHash(FSpriteModelFrame * smf) +{ + const uint32_t *table = GetCRCTable (); + uint32_t hash = 0xffffffff; + + const char * s = (const char *)(&smf->type); // this uses type, sprite and frame for hashing + const char * se= (const char *)(&smf->hashnext); + + for (; smFileName.CompareNoCase(fullname)) return i; + } + + int len = fileSystem.FileLength(lump); + FileData lumpd = fileSystem.ReadFile(lump); + char * buffer = (char*)lumpd.GetMem(); + + if ( (size_t)fullname.LastIndexOf("_d.3d") == fullname.Len()-5 ) + { + FString anivfile = fullname.GetChars(); + anivfile.Substitute("_d.3d","_a.3d"); + if ( fileSystem.CheckNumForFullName(anivfile) > 0 ) + { + model = new FUE1Model; + } + } + else if ( (size_t)fullname.LastIndexOf("_a.3d") == fullname.Len()-5 ) + { + FString datafile = fullname.GetChars(); + datafile.Substitute("_a.3d","_d.3d"); + if ( fileSystem.CheckNumForFullName(datafile) > 0 ) + { + model = new FUE1Model; + } + } + else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 4 ) + { + model = new FOBJModel; + } + else if (!memcmp(buffer, "DMDM", 4)) + { + model = new FDMDModel; + } + else if (!memcmp(buffer, "IDP2", 4)) + { + model = new FMD2Model; + } + else if (!memcmp(buffer, "IDP3", 4)) + { + model = new FMD3Model; + } + + if (model != nullptr) + { + if (!model->Load(path, lump, buffer, len)) + { + delete model; + return -1; + } + } + else + { + // try loading as a voxel + FVoxel *voxel = R_LoadKVX(lump); + if (voxel != nullptr) + { + model = new FVoxelModel(voxel, true); + } + else + { + Printf("LoadModel: Unknown model format in '%s'\n", fullname.GetChars()); + return -1; + } + } + // The vertex buffer cannot be initialized here because this gets called before OpenGL is initialized + model->mFileName = fullname; + return Models.Push(model); +} + diff --git a/source/common/models/model.h b/source/common/models/model.h new file mode 100644 index 000000000..5fdec020c --- /dev/null +++ b/source/common/models/model.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include "textureid.h" +#include "i_modelvertexbuffer.h" + +class FModelRenderer; +class FGameTexture; +class IModelVertexBuffer; +class FModel; +struct FSpriteModelFrame; + +FTextureID LoadSkin(const char* path, const char* fn); +void FlushModels(); +extern TDeletingArray Models; +extern TArray SpriteModelFrames; + +#define MAX_MODELS_PER_FRAME 4 +#define MD3_MAX_SURFACES 32 + +struct FSpriteModelFrame +{ + int modelIDs[MAX_MODELS_PER_FRAME]; + FTextureID skinIDs[MAX_MODELS_PER_FRAME]; + FTextureID surfaceskinIDs[MAX_MODELS_PER_FRAME][MD3_MAX_SURFACES]; + int modelframes[MAX_MODELS_PER_FRAME]; + float xscale, yscale, zscale; + // [BB] Added zoffset, rotation parameters and flags. + // Added xoffset, yoffset + float xoffset, yoffset, zoffset; + float xrotate, yrotate, zrotate; + float rotationCenterX, rotationCenterY, rotationCenterZ; + float rotationSpeed; + unsigned int flags; + const void* type; // used for hashing, must point to something usable as identifier for the model's owner. + short sprite; + short frame; + int hashnext; + float angleoffset; + // added pithoffset, rolloffset. + float pitchoffset, rolloffset; // I don't want to bother with type transformations, so I made this variables float. + bool isVoxel; +}; + + +enum ModelRendererType +{ + GLModelRendererType, + SWModelRendererType, + PolyModelRendererType, + NumModelRendererTypes +}; + +class FModel +{ +public: + FModel(); + virtual ~FModel(); + + virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length) = 0; + virtual int FindFrame(const char * name) = 0; + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0) = 0; + virtual void BuildVertexBuffer(FModelRenderer *renderer) = 0; + virtual void AddSkins(uint8_t *hitlist) = 0; + virtual float getAspectFactor(float vscale) { return 1.f; } + + void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; } + IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; } + void DestroyVertexBuffer(); + + const FSpriteModelFrame *curSpriteMDLFrame; + int curMDLIndex; + void PushSpriteMDLFrame(const FSpriteModelFrame *smf, int index) { curSpriteMDLFrame = smf; curMDLIndex = index; }; + + FString mFileName; + +private: + IModelVertexBuffer *mVBuf[NumModelRendererTypes]; +}; + +int ModelFrameHash(FSpriteModelFrame* smf); +unsigned FindModel(const char* path, const char* modelfile); + diff --git a/source/common/models/model_kvx.h b/source/common/models/model_kvx.h new file mode 100644 index 000000000..bea2fdfd2 --- /dev/null +++ b/source/common/models/model_kvx.h @@ -0,0 +1,69 @@ +#pragma once + +#include "model.h" +#include "i_modelvertexbuffer.h" +#include "tarray.h" +#include "xs_Float.h" + +struct FVoxel; +struct kvxslab_t; +class FModelRenderer; +class FGameTexture; + +struct FVoxelVertexHash +{ + // Returns the hash value for a key. + hash_t Hash(const FModelVertex &key) + { + int ix = xs_RoundToInt(key.x); + int iy = xs_RoundToInt(key.y); + int iz = xs_RoundToInt(key.z); + return (hash_t)(ix + (iy<<9) + (iz<<18)); + } + + // Compares two keys, returning zero if they are the same. + int Compare(const FModelVertex &left, const FModelVertex &right) + { + return left.x != right.x || left.y != right.y || left.z != right.z || left.u != right.u || left.v != right.v; + } +}; + +struct FIndexInit +{ + void Init(unsigned int &value) + { + value = 0xffffffff; + } +}; + +typedef TMap FVoxelMap; + + +class FVoxelModel : public FModel +{ +protected: + FVoxel *mVoxel; + bool mOwningVoxel; // if created through MODELDEF deleting this object must also delete the voxel object + FTextureID mPalette; + unsigned int mNumIndices; + TArray mVertices; + TArray mIndices; + + void MakeSlabPolys(int x, int y, kvxslab_t *voxptr, FVoxelMap &check); + void AddFace(int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int x4, int y4, int z4, uint8_t color, FVoxelMap &check); + unsigned int AddVertex(FModelVertex &vert, FVoxelMap &check); + +public: + FVoxelModel(FVoxel *voxel, bool owned); + ~FVoxelModel(); + bool Load(const char * fn, int lumpnum, const char * buffer, int length); + void Initialize(); + virtual int FindFrame(const char * name); + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0); + virtual void AddSkins(uint8_t *hitlist); + FTextureID GetPaletteTexture() const { return mPalette; } + void BuildVertexBuffer(FModelRenderer *renderer); + float getAspectFactor(float vscale) override; +}; + + diff --git a/source/common/models/model_md2.h b/source/common/models/model_md2.h new file mode 100644 index 000000000..c04445535 --- /dev/null +++ b/source/common/models/model_md2.h @@ -0,0 +1,137 @@ +#pragma once +#include "model.h" + +#define MD2_MAGIC 0x32504449 +#define DMD_MAGIC 0x4D444D44 +#define MAX_LODS 4 + +class FDMDModel : public FModel +{ +protected: + + struct FTriangle + { + unsigned short vertexIndices[3]; + unsigned short textureIndices[3]; + }; + + + struct DMDHeader + { + int magic; + int version; + int flags; + }; + + struct DMDModelVertex + { + float xyz[3]; + }; + + struct FTexCoord + { + short s, t; + }; + + struct FGLCommandVertex + { + float s, t; + int index; + }; + + struct DMDInfo + { + int skinWidth; + int skinHeight; + int frameSize; + int numSkins; + int numVertices; + int numTexCoords; + int numFrames; + int numLODs; + int offsetSkins; + int offsetTexCoords; + int offsetFrames; + int offsetLODs; + int offsetEnd; + }; + + struct ModelFrame + { + char name[16]; + unsigned int vindex; + }; + + struct ModelFrameVertexData + { + DMDModelVertex *vertices; + DMDModelVertex *normals; + }; + + struct DMDLoDInfo + { + int numTriangles; + int numGlCommands; + int offsetTriangles; + int offsetGlCommands; + }; + + struct DMDLoD + { + FTriangle * triangles; + }; + + + int mLumpNum; + DMDHeader header; + DMDInfo info; + FTextureID * skins; + ModelFrame * frames; + bool allowTexComp; // Allow texture compression with this. + + // Temp data only needed for buffer construction + FTexCoord * texCoords; + ModelFrameVertexData *framevtx; + DMDLoDInfo lodInfo[MAX_LODS]; + DMDLoD lods[MAX_LODS]; + +public: + FDMDModel() + { + mLumpNum = -1; + frames = NULL; + skins = NULL; + for (int i = 0; i < MAX_LODS; i++) + { + lods[i].triangles = NULL; + } + info.numLODs = 0; + texCoords = NULL; + framevtx = NULL; + } + virtual ~FDMDModel(); + + virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length); + virtual int FindFrame(const char * name); + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0); + virtual void LoadGeometry(); + virtual void AddSkins(uint8_t *hitlist); + + void UnloadGeometry(); + void BuildVertexBuffer(FModelRenderer *renderer); + +}; + +// This uses the same internal representation as DMD +class FMD2Model : public FDMDModel +{ +public: + FMD2Model() {} + virtual ~FMD2Model(); + + virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length); + virtual void LoadGeometry(); + +}; + + diff --git a/source/common/models/model_md3.h b/source/common/models/model_md3.h new file mode 100644 index 000000000..3e6630a5a --- /dev/null +++ b/source/common/models/model_md3.h @@ -0,0 +1,75 @@ +#pragma once +#include "model.h" + +#define MD3_MAGIC 0x33504449 + +class FMD3Model : public FModel +{ + struct MD3Tag + { + // Currently I have no use for this + }; + + struct MD3TexCoord + { + float s,t; + }; + + struct MD3Vertex + { + float x,y,z; + float nx,ny,nz; + }; + + struct MD3Triangle + { + int VertIndex[3]; + }; + + struct MD3Surface + { + unsigned numVertices; + unsigned numTriangles; + unsigned numSkins; + + TArray Skins; + TArray Tris; + TArray Texcoords; + TArray Vertices; + + unsigned int vindex = UINT_MAX; // contains numframes arrays of vertices + unsigned int iindex = UINT_MAX; + + void UnloadGeometry() + { + Tris.Reset(); + Vertices.Reset(); + Texcoords.Reset(); + } + }; + + struct MD3Frame + { + // The bounding box information is of no use in the Doom engine + // That will still be done with the actor's size information. + char Name[16]; + float origin[3]; + }; + + int numTags; + int mLumpNum; + + TArray Frames; + TArray Surfaces; + +public: + FMD3Model() = default; + + virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length); + virtual int FindFrame(const char * name); + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0); + void LoadGeometry(); + void BuildVertexBuffer(FModelRenderer *renderer); + virtual void AddSkins(uint8_t *hitlist); +}; + diff --git a/source/common/models/model_obj.h b/source/common/models/model_obj.h new file mode 100644 index 000000000..ed190c1cd --- /dev/null +++ b/source/common/models/model_obj.h @@ -0,0 +1,106 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Kevin Caccamo +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#ifndef __GL_MODELS_OBJ_H__ +#define __GL_MODELS_OBJ_H__ + +#include "model.h" +#include "sc_man.h" +#include "tarray.h" +#include "vectors.h" + +class FOBJModel : public FModel +{ +private: + const char *newSideSep = "$"; // OBJ side separator is /, which is parsed as a line comment by FScanner if two of them are next to each other. + bool hasMissingNormals; + bool hasSmoothGroups; + + enum class FaceElement + { + VertexIndex, + UVIndex, + VNormalIndex + }; + + struct OBJTriRef + { + unsigned int surf; + unsigned int tri; + OBJTriRef(): surf(0), tri(0) {} + OBJTriRef(unsigned int surf, unsigned int tri): surf(surf), tri(tri) {} + bool operator== (OBJTriRef other) { return surf == other.surf && tri == other.tri; } + }; + struct OBJFaceSide + { + int vertref; + int normref; + int uvref; + }; + struct OBJFace + { + unsigned int sideCount; + unsigned int smoothGroup; + OBJFaceSide sides[4]; + OBJFace(): sideCount(0), smoothGroup(0) {} + }; + struct OBJSurface // 1 surface per 'usemtl' + { + unsigned int numTris; // Number of triangulated faces + unsigned int numFaces; // Number of faces + unsigned int vbStart; // First index in vertex buffer + unsigned int faceStart; // Index of first face in faces array + OBJFace* tris; // Triangles + FTextureID skin; + OBJSurface(FTextureID skin): numTris(0), numFaces(0), vbStart(0), faceStart(0), tris(nullptr), skin(skin) {} + }; + + TArray verts; + TArray norms; + TArray uvs; + TArray faces; + TArray surfaces; + FScanner sc; + TArray* vertFaces; + + int ResolveIndex(int origIndex, FaceElement el); + template void ParseVector(TArray &array); + bool ParseFaceSide(const FString &side, OBJFace &face, int sidx); + void ConstructSurfaceTris(OBJSurface &surf); + void AddVertFaces(); + void TriangulateQuad(const OBJFace &quad, OBJFace *tris); + FVector3 RealignVector(FVector3 vecToRealign); + FVector2 FixUV(FVector2 vecToRealign); + FVector3 CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx); + FVector3 CalculateNormalFlat(OBJTriRef otr); + FVector3 CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup); +public: + FOBJModel(): hasMissingNormals(false), hasSmoothGroups(false), vertFaces(nullptr) {} + ~FOBJModel(); + bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; + int FindFrame(const char* name) override; + void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, int translation=0) override; + void BuildVertexBuffer(FModelRenderer* renderer) override; + void AddSkins(uint8_t* hitlist) override; +}; + +#endif diff --git a/source/common/models/model_ue1.h b/source/common/models/model_ue1.h new file mode 100644 index 000000000..5696cbf43 --- /dev/null +++ b/source/common/models/model_ue1.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include "model.h" +#include "vectors.h" + +class FUE1Model : public FModel +{ +public: + enum EPolyType + { + PT_Normal = 0, // normal renderstyle + PT_TwoSided = 1, // like normal, but don't cull backfaces + PT_Translucent = 2, // additive blending + PT_Masked = 3, // draw with alpha testing + PT_Modulated = 4, // overlay-like blending (rgb values below 128 darken, 128 is unchanged, and above 128 lighten) + // types mask + PT_Type = 7, + // flags + PT_WeaponTriangle = 0x08, // this poly is used for positioning a weapon attachment and should not be drawn + PT_Unlit = 0x10, // this poly is fullbright + PT_Curvy = 0x20, // this poly uses the facet normal + PT_EnvironmentMap = 0x40, // vertex UVs are remapped to their view-space X and Z normals, fake cubemap look + PT_NoSmooth = 0x80 // this poly forcibly uses nearest filtering + }; + + bool Load(const char * fn, int lumpnum, const char * buffer, int length) override; + int FindFrame(const char * name) override; + void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0) override; + void BuildVertexBuffer(FModelRenderer *renderer) override; + void AddSkins(uint8_t *hitlist) override; + void LoadGeometry(); + void UnloadGeometry(); + FUE1Model() + { + mDataLump = -1; + mAnivLump = -1; + mDataLoaded = false; + dhead = NULL; + dpolys = NULL; + ahead = NULL; + averts = NULL; + numVerts = 0; + numFrames = 0; + numPolys = 0; + numGroups = 0; + } + ~FUE1Model(); + +private: + int mDataLump, mAnivLump; + bool mDataLoaded; + + // raw data structures + struct d3dhead + { + uint16_t numpolys, numverts; + uint16_t bogusrot, bogusframe; + uint32_t bogusnorm[3]; + uint32_t fixscale; + uint32_t unused[3]; + uint8_t padding[12]; + }; + struct d3dpoly + { + uint16_t vertices[3]; + uint8_t type, color; + uint8_t uv[3][2]; + uint8_t texnum, flags; + }; + struct a3dhead + { + uint16_t numframes, framesize; + }; + d3dhead * dhead; + d3dpoly * dpolys; + a3dhead * ahead; + uint32_t * averts; + struct dxvert + { + int16_t x, y, z, pad; + }; + dxvert * dxverts; + + // converted data structures + struct UE1Vertex + { + FVector3 Pos, Normal; + }; + struct UE1Poly + { + int V[3]; + FVector2 C[3]; + TArray Normals; + }; + struct UE1Group + { + TArray P; + int numPolys, texNum, type; + }; + + int numVerts; + int numFrames; + int numPolys; + int numGroups; + int weaponPoly; // for future model attachment support, unused for now + + TArray verts; + TArray polys; + TArray groups; +}; diff --git a/source/common/models/modelrenderer.h b/source/common/models/modelrenderer.h new file mode 100644 index 000000000..1018d7ebb --- /dev/null +++ b/source/common/models/modelrenderer.h @@ -0,0 +1,29 @@ + +#include "renderstyle.h" +#include "matrix.h" +#include "model.h" + +class FModelRenderer +{ +public: + virtual ~FModelRenderer() = default; + + virtual ModelRendererType GetType() const = 0; + + virtual void BeginDrawModel(FRenderStyle style, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) = 0; + virtual void EndDrawModel(FRenderStyle style, FSpriteModelFrame *smf) = 0; + + virtual IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) = 0; + + virtual VSMatrix GetViewToWorldMatrix() = 0; + + virtual void BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored) = 0; + virtual void EndDrawHUDModel(FRenderStyle style) = 0; + + virtual void SetInterpolation(double interpolation) = 0; + virtual void SetMaterial(FGameTexture *skin, bool clampNoFilter, int translation) = 0; + virtual void DrawArrays(int start, int count) = 0; + virtual void DrawElements(int numIndices, size_t offset) = 0; + virtual void SetupFrame(FModel *model, unsigned int frame1, unsigned int frame2, unsigned int size) = 0; +}; + diff --git a/source/common/models/models_md2.cpp b/source/common/models/models_md2.cpp new file mode 100644 index 000000000..c3439faea --- /dev/null +++ b/source/common/models/models_md2.cpp @@ -0,0 +1,555 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2005-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** models.cpp +** +** MD2/DMD model format code +** +**/ + +#include "filesystem.h" +#include "model_md2.h" +#include "texturemanager.h" +#include "modelrenderer.h" +#include "printf.h" + +#ifdef _MSC_VER +#pragma warning(disable:4244) // warning C4244: conversion from 'double' to 'float', possible loss of data +#endif + +enum { VX, VZ, VY }; +#define NUMVERTEXNORMALS 162 + +static float avertexnormals[NUMVERTEXNORMALS][3] = { +#include "tab_anorms.h" +}; + +//=========================================================================== +// +// UnpackVector +// Packed: pppppppy yyyyyyyy. Yaw is on the XY plane. +// +//=========================================================================== + +static void UnpackVector(unsigned short packed, float vec[3]) +{ + float yaw = (packed & 511) / 512.0f * 2 * M_PI; + float pitch = ((packed >> 9) / 127.0f - 0.5f) * M_PI; + float cosp = (float) cos(pitch); + + vec[VX] = (float) cos(yaw) * cosp; + vec[VY] = (float) sin(yaw) * cosp; + vec[VZ] = (float) sin(pitch); +} + + +//=========================================================================== +// +// DMD file structure +// +//=========================================================================== + +struct dmd_chunk_t +{ + int type; + int length; // Next chunk follows... +}; + +#pragma pack(1) +struct dmd_packedVertex_t +{ + uint8_t vertex[3]; + unsigned short normal; // Yaw and pitch. +}; + +struct dmd_packedFrame_t +{ + float scale[3]; + float translate[3]; + char name[16]; + dmd_packedVertex_t vertices[1]; +}; +#pragma pack() + +// Chunk types. +enum +{ + DMC_END, // Must be the last chunk. + DMC_INFO // Required; will be expected to exist. +}; + +//=========================================================================== +// +// FDMDModel::Load +// +//=========================================================================== + +bool FDMDModel::Load(const char * path, int lumpnum, const char * buffer, int length) +{ + dmd_chunk_t * chunk = (dmd_chunk_t*)(buffer + 12); + char *temp; + ModelFrame *frame; + int i; + + int fileoffset = 12 + sizeof(dmd_chunk_t); + + chunk->type = LittleLong(chunk->type); + while (chunk->type != DMC_END) + { + switch (chunk->type) + { + case DMC_INFO: // Standard DMD information chunk. + memcpy(&info, buffer + fileoffset, LittleLong(chunk->length)); + info.skinWidth = LittleLong(info.skinWidth); + info.skinHeight = LittleLong(info.skinHeight); + info.frameSize = LittleLong(info.frameSize); + info.numSkins = LittleLong(info.numSkins); + info.numVertices = LittleLong(info.numVertices); + info.numTexCoords = LittleLong(info.numTexCoords); + info.numFrames = LittleLong(info.numFrames); + info.numLODs = LittleLong(info.numLODs); + info.offsetSkins = LittleLong(info.offsetSkins); + info.offsetTexCoords = LittleLong(info.offsetTexCoords); + info.offsetFrames = LittleLong(info.offsetFrames); + info.offsetLODs = LittleLong(info.offsetLODs); + info.offsetEnd = LittleLong(info.offsetEnd); + fileoffset += chunk->length; + break; + + default: + // Just skip all unknown chunks. + fileoffset += chunk->length; + break; + } + // Read the next chunk header. + chunk = (dmd_chunk_t*)(buffer + fileoffset); + chunk->type = LittleLong(chunk->type); + fileoffset += sizeof(dmd_chunk_t); + } + + // Allocate and load in the data. + skins = new FTextureID[info.numSkins]; + + for (i = 0; i < info.numSkins; i++) + { + skins[i] = LoadSkin(path, buffer + info.offsetSkins + i * 64); + } + temp = (char*)buffer + info.offsetFrames; + frames = new ModelFrame[info.numFrames]; + + for (i = 0, frame = frames; i < info.numFrames; i++, frame++) + { + dmd_packedFrame_t *pfr = (dmd_packedFrame_t *)(temp + info.frameSize * i); + + memcpy(frame->name, pfr->name, sizeof(pfr->name)); + frame->vindex = UINT_MAX; + } + mLumpNum = lumpnum; + return true; +} + +//=========================================================================== +// +// FDMDModel::LoadGeometry +// +//=========================================================================== + +void FDMDModel::LoadGeometry() +{ + static int axis[3] = { VX, VY, VZ }; + FileData lumpdata = fileSystem.ReadFile(mLumpNum); + const char *buffer = (const char *)lumpdata.GetMem(); + texCoords = new FTexCoord[info.numTexCoords]; + memcpy(texCoords, buffer + info.offsetTexCoords, info.numTexCoords * sizeof(FTexCoord)); + + const char *temp = buffer + info.offsetFrames; + framevtx= new ModelFrameVertexData[info.numFrames]; + + ModelFrameVertexData *framev; + int i, k, c; + for(i = 0, framev = framevtx; i < info.numFrames; i++, framev++) + { + dmd_packedFrame_t *pfr = (dmd_packedFrame_t *) (temp + info.frameSize * i); + dmd_packedVertex_t *pVtx; + + framev->vertices = new DMDModelVertex[info.numVertices]; + framev->normals = new DMDModelVertex[info.numVertices]; + + // Translate each vertex. + for(k = 0, pVtx = pfr->vertices; k < info.numVertices; k++, pVtx++) + { + UnpackVector((unsigned short)(pVtx->normal), framev->normals[k].xyz); + for(c = 0; c < 3; c++) + { + framev->vertices[k].xyz[axis[c]] = + (pVtx->vertex[c] * float(pfr->scale[c]) + float(pfr->translate[c])); + } + } + } + + memcpy(lodInfo, buffer+info.offsetLODs, info.numLODs * sizeof(DMDLoDInfo)); + for(i = 0; i < info.numLODs; i++) + { + lodInfo[i].numTriangles = LittleLong(lodInfo[i].numTriangles); + lodInfo[i].offsetTriangles = LittleLong(lodInfo[i].offsetTriangles); + if (lodInfo[i].numTriangles > 0) + { + lods[i].triangles = new FTriangle[lodInfo[i].numTriangles]; + memcpy(lods[i].triangles, buffer + lodInfo[i].offsetTriangles, lodInfo[i].numTriangles * sizeof(FTriangle)); + for (int j = 0; j < lodInfo[i].numTriangles; j++) + { + for (int k = 0; k < 3; k++) + { + lods[i].triangles[j].textureIndices[k] = LittleShort(lods[i].triangles[j].textureIndices[k]); + lods[i].triangles[j].vertexIndices[k] = LittleShort(lods[i].triangles[j].vertexIndices[k]); + } + } + } + } + +} + +//=========================================================================== +// +// Deletes everything that's no longer needed after building the vertex buffer +// +//=========================================================================== + +void FDMDModel::UnloadGeometry() +{ + int i; + + if (framevtx != NULL) + { + for (i=0;iGetType())) + { + LoadGeometry(); + + int VertexBufferSize = info.numFrames * lodInfo[0].numTriangles * 3; + unsigned int vindex = 0; + + auto vbuf = renderer->CreateVertexBuffer(false, info.numFrames == 1); + SetVertexBuffer(renderer->GetType(), vbuf); + + FModelVertex *vertptr = vbuf->LockVertexBuffer(VertexBufferSize); + + for (int i = 0; i < info.numFrames; i++) + { + DMDModelVertex *vert = framevtx[i].vertices; + DMDModelVertex *norm = framevtx[i].normals; + + frames[i].vindex = vindex; + + FTriangle *tri = lods[0].triangles; + + for (int i = 0; i < lodInfo[0].numTriangles; i++) + { + for (int j = 0; j < 3; j++) + { + + int ti = tri->textureIndices[j]; + int vi = tri->vertexIndices[j]; + + FModelVertex *bvert = &vertptr[vindex++]; + bvert->Set(vert[vi].xyz[0], vert[vi].xyz[1], vert[vi].xyz[2], (float)texCoords[ti].s / info.skinWidth, (float)texCoords[ti].t / info.skinHeight); + bvert->SetNormal(norm[vi].xyz[0], norm[vi].xyz[1], norm[vi].xyz[2]); + } + tri++; + } + } + vbuf->UnlockVertexBuffer(); + UnloadGeometry(); + } +} + +//=========================================================================== +// +// for skin precaching +// +//=========================================================================== + +void FDMDModel::AddSkins(uint8_t *hitlist) +{ + for (int i = 0; i < info.numSkins; i++) + { + if (skins[i].isValid()) + { + hitlist[skins[i].GetIndex()] |= FTextureManager::HIT_Flat; + } + } +} + +//=========================================================================== +// +// FDMDModel::FindFrame +// +//=========================================================================== +int FDMDModel::FindFrame(const char * name) +{ + for (int i=0;i= info.numFrames || frameno2 >= info.numFrames) return; + + if (!skin) + { + if (info.numSkins == 0 || !skins[0].isValid()) return; + skin = TexMan.GetGameTexture(skins[0], true); + if (!skin) return; + } + + renderer->SetInterpolation(inter); + renderer->SetMaterial(skin, false, translation); + renderer->SetupFrame(this, frames[frameno].vindex, frames[frameno2].vindex, lodInfo[0].numTriangles * 3); + renderer->DrawArrays(0, lodInfo[0].numTriangles * 3); + renderer->SetInterpolation(0.f); +} + + + +//=========================================================================== +// +// Internal data structures of MD2 files - only used during loading +// +//=========================================================================== + +struct md2_header_t +{ + int magic; + int version; + int skinWidth; + int skinHeight; + int frameSize; + int numSkins; + int numVertices; + int numTexCoords; + int numTriangles; + int numGlCommands; + int numFrames; + int offsetSkins; + int offsetTexCoords; + int offsetTriangles; + int offsetFrames; + int offsetGlCommands; + int offsetEnd; +}; + +struct md2_triangleVertex_t +{ + uint8_t vertex[3]; + uint8_t lightNormalIndex; +}; + +struct md2_packedFrame_t +{ + float scale[3]; + float translate[3]; + char name[16]; + md2_triangleVertex_t vertices[1]; +}; + +//=========================================================================== +// +// FMD2Model::Load +// +//=========================================================================== + +bool FMD2Model::Load(const char * path, int lumpnum, const char * buffer, int length) +{ + md2_header_t * md2header = (md2_header_t *)buffer; + ModelFrame *frame; + uint8_t *md2_frames; + int i; + + // Convert it to DMD. + header.magic = MD2_MAGIC; + header.version = 8; + header.flags = 0; + info.skinWidth = LittleLong(md2header->skinWidth); + info.skinHeight = LittleLong(md2header->skinHeight); + info.frameSize = LittleLong(md2header->frameSize); + info.numLODs = 1; + info.numSkins = LittleLong(md2header->numSkins); + info.numTexCoords = LittleLong(md2header->numTexCoords); + info.numVertices = LittleLong(md2header->numVertices); + info.numFrames = LittleLong(md2header->numFrames); + info.offsetSkins = LittleLong(md2header->offsetSkins); + info.offsetTexCoords = LittleLong(md2header->offsetTexCoords); + info.offsetFrames = LittleLong(md2header->offsetFrames); + info.offsetLODs = LittleLong(md2header->offsetEnd); // Doesn't exist. + lodInfo[0].numTriangles = LittleLong(md2header->numTriangles); + lodInfo[0].numGlCommands = LittleLong(md2header->numGlCommands); + lodInfo[0].offsetTriangles = LittleLong(md2header->offsetTriangles); + lodInfo[0].offsetGlCommands = LittleLong(md2header->offsetGlCommands); + info.offsetEnd = LittleLong(md2header->offsetEnd); + + if (info.offsetFrames + info.frameSize * info.numFrames > length) + { + Printf("LoadModel: Model '%s' file too short\n", path); + return false; + } + if (lodInfo[0].numGlCommands <= 0) + { + Printf("LoadModel: Model '%s' invalid NumGLCommands\n", path); + return false; + } + + skins = new FTextureID[info.numSkins]; + + for (i = 0; i < info.numSkins; i++) + { + skins[i] = LoadSkin(path, buffer + info.offsetSkins + i * 64); + } + + // The frames need to be unpacked. + md2_frames = (uint8_t*)buffer + info.offsetFrames; + frames = new ModelFrame[info.numFrames]; + + for (i = 0, frame = frames; i < info.numFrames; i++, frame++) + { + md2_packedFrame_t *pfr = (md2_packedFrame_t *)(md2_frames + info.frameSize * i); + + memcpy(frame->name, pfr->name, sizeof(pfr->name)); + frame->vindex = UINT_MAX; + } + mLumpNum = lumpnum; + return true; +} + +//=========================================================================== +// +// FMD2Model::LoadGeometry +// +//=========================================================================== + +void FMD2Model::LoadGeometry() +{ + static int axis[3] = { VX, VY, VZ }; + uint8_t *md2_frames; + FileData lumpdata = fileSystem.ReadFile(mLumpNum); + const char *buffer = (const char *)lumpdata.GetMem(); + + texCoords = new FTexCoord[info.numTexCoords]; + memcpy(texCoords, (uint8_t*)buffer + info.offsetTexCoords, info.numTexCoords * sizeof(FTexCoord)); + + md2_frames = (uint8_t*)buffer + info.offsetFrames; + framevtx = new ModelFrameVertexData[info.numFrames]; + ModelFrameVertexData *framev; + int i, k, c; + + for(i = 0, framev = framevtx; i < info.numFrames; i++, framev++) + { + md2_packedFrame_t *pfr = (md2_packedFrame_t *) (md2_frames + info.frameSize * i); + md2_triangleVertex_t *pVtx; + + framev->vertices = new DMDModelVertex[info.numVertices]; + framev->normals = new DMDModelVertex[info.numVertices]; + + // Translate each vertex. + for(k = 0, pVtx = pfr->vertices; k < info.numVertices; k++, pVtx++) + { + memcpy(framev->normals[k].xyz, + avertexnormals[pVtx->lightNormalIndex], sizeof(float) * 3); + + for(c = 0; c < 3; c++) + { + framev->vertices[k].xyz[axis[c]] = + (pVtx->vertex[c] * pfr->scale[c] + pfr->translate[c]); + } + } + } + + lods[0].triangles = new FTriangle[lodInfo[0].numTriangles]; + + int cnt = lodInfo[0].numTriangles; + memcpy(lods[0].triangles, buffer + lodInfo[0].offsetTriangles, sizeof(FTriangle) * cnt); + for (int j = 0; j < cnt; j++) + { + for (int k = 0; k < 3; k++) + { + lods[0].triangles[j].textureIndices[k] = LittleShort(lods[0].triangles[j].textureIndices[k]); + lods[0].triangles[j].vertexIndices[k] = LittleShort(lods[0].triangles[j].vertexIndices[k]); + } + } +} + +FMD2Model::~FMD2Model() +{ +} + diff --git a/source/common/models/models_md3.cpp b/source/common/models/models_md3.cpp new file mode 100644 index 000000000..15224e4fe --- /dev/null +++ b/source/common/models/models_md3.cpp @@ -0,0 +1,381 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2006-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "filesystem.h" +#include "cmdlib.h" +#include "model_md3.h" +#include "texturemanager.h" +#include "modelrenderer.h" + +#define MAX_QPATH 64 + +#ifdef _MSC_VER +#pragma warning(disable:4244) // warning C4244: conversion from 'double' to 'float', possible loss of data +#endif + +//=========================================================================== +// +// decode the lat/lng normal to a 3 float normal +// +//=========================================================================== + +static void UnpackVector(unsigned short packed, float & nx, float & ny, float & nz) +{ + double lat = ( packed >> 8 ) & 0xff; + double lng = ( packed & 0xff ); + lat *= M_PI/128; + lng *= M_PI/128; + + nx = cos(lat) * sin(lng); + ny = sin(lat) * sin(lng); + nz = cos(lng); +} + +//=========================================================================== +// +// MD3 File structure +// +//=========================================================================== + +#pragma pack(4) +struct md3_header_t +{ + uint32_t Magic; + uint32_t Version; + char Name[MAX_QPATH]; + uint32_t Flags; + uint32_t Num_Frames; + uint32_t Num_Tags; + uint32_t Num_Surfaces; + uint32_t Num_Skins; + uint32_t Ofs_Frames; + uint32_t Ofs_Tags; + uint32_t Ofs_Surfaces; + uint32_t Ofs_Eof; +}; + +struct md3_surface_t +{ + uint32_t Magic; + char Name[MAX_QPATH]; + uint32_t Flags; + uint32_t Num_Frames; + uint32_t Num_Shaders; + uint32_t Num_Verts; + uint32_t Num_Triangles; + uint32_t Ofs_Triangles; + uint32_t Ofs_Shaders; + uint32_t Ofs_Texcoord; + uint32_t Ofs_XYZNormal; + uint32_t Ofs_End; +}; + +struct md3_triangle_t +{ + uint32_t vt_index[3]; +}; + +struct md3_shader_t +{ + char Name[MAX_QPATH]; + uint32_t index; +}; + +struct md3_texcoord_t +{ + float s, t; +}; + +struct md3_vertex_t +{ + short x, y, z, n; +}; + +struct md3_frame_t +{ + float min_Bounds[3]; + float max_Bounds[3]; + float localorigin[3]; + float radius; + char Name[16]; +}; +#pragma pack() + + +//=========================================================================== +// +// +// +//=========================================================================== + +bool FMD3Model::Load(const char * path, int lumpnum, const char * buffer, int length) +{ + md3_header_t * hdr = (md3_header_t *)buffer; + + auto numFrames = LittleLong(hdr->Num_Frames); + auto numSurfaces = LittleLong(hdr->Num_Surfaces); + + numTags = LittleLong(hdr->Num_Tags); + + md3_frame_t * frm = (md3_frame_t*)(buffer + LittleLong(hdr->Ofs_Frames)); + + Frames.Resize(numFrames); + for (unsigned i = 0; i < numFrames; i++) + { + strncpy(Frames[i].Name, frm[i].Name, 16); + for (int j = 0; j < 3; j++) Frames[i].origin[j] = frm[i].localorigin[j]; + } + + md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces)); + + Surfaces.Resize(numSurfaces); + + for (unsigned i = 0; i < numSurfaces; i++) + { + MD3Surface * s = &Surfaces[i]; + md3_surface_t * ss = surf; + + surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End)); + + s->numSkins = LittleLong(ss->Num_Shaders); + s->numTriangles = LittleLong(ss->Num_Triangles); + s->numVertices = LittleLong(ss->Num_Verts); + + // copy shaders (skins) + md3_shader_t * shader = (md3_shader_t*)(((char*)ss) + LittleLong(ss->Ofs_Shaders)); + s->Skins.Resize(s->numSkins); + + for (unsigned i = 0; i < s->numSkins; i++) + { + // [BB] According to the MD3 spec, Name is supposed to include the full path. + // ... and since some tools seem to output backslashes, these need to be replaced with forward slashes to work. + FixPathSeperator(shader[i].Name); + s->Skins[i] = LoadSkin("", shader[i].Name); + // [BB] Fall back and check if Name is relative. + if (!s->Skins[i].isValid()) + s->Skins[i] = LoadSkin(path, shader[i].Name); + } + } + mLumpNum = lumpnum; + return true; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FMD3Model::LoadGeometry() +{ + FileData lumpdata = fileSystem.ReadFile(mLumpNum); + const char *buffer = (const char *)lumpdata.GetMem(); + md3_header_t * hdr = (md3_header_t *)buffer; + md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces)); + + for (unsigned i = 0; i < Surfaces.Size(); i++) + { + MD3Surface * s = &Surfaces[i]; + md3_surface_t * ss = surf; + + surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End)); + + // copy triangle indices + md3_triangle_t * tris = (md3_triangle_t*)(((char*)ss) + LittleLong(ss->Ofs_Triangles)); + s->Tris.Resize(s->numTriangles); + + for (unsigned i = 0; i < s->numTriangles; i++) for (int j = 0; j < 3; j++) + { + s->Tris[i].VertIndex[j] = LittleLong(tris[i].vt_index[j]); + } + + // Load texture coordinates + md3_texcoord_t * tc = (md3_texcoord_t*)(((char*)ss) + LittleLong(ss->Ofs_Texcoord)); + s->Texcoords.Resize(s->numVertices); + + for (unsigned i = 0; i < s->numVertices; i++) + { + s->Texcoords[i].s = tc[i].s; + s->Texcoords[i].t = tc[i].t; + } + + // Load vertices and texture coordinates + md3_vertex_t * vt = (md3_vertex_t*)(((char*)ss) + LittleLong(ss->Ofs_XYZNormal)); + s->Vertices.Resize(s->numVertices * Frames.Size()); + + for (unsigned i = 0; i < s->numVertices * Frames.Size(); i++) + { + s->Vertices[i].x = LittleShort(vt[i].x) / 64.f; + s->Vertices[i].y = LittleShort(vt[i].y) / 64.f; + s->Vertices[i].z = LittleShort(vt[i].z) / 64.f; + UnpackVector(LittleShort(vt[i].n), s->Vertices[i].nx, s->Vertices[i].ny, s->Vertices[i].nz); + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FMD3Model::BuildVertexBuffer(FModelRenderer *renderer) +{ + if (!GetVertexBuffer(renderer->GetType())) + { + LoadGeometry(); + + unsigned int vbufsize = 0; + unsigned int ibufsize = 0; + + for (unsigned i = 0; i < Surfaces.Size(); i++) + { + MD3Surface * surf = &Surfaces[i]; + vbufsize += Frames.Size() * surf->numVertices; + ibufsize += 3 * surf->numTriangles; + } + + auto vbuf = renderer->CreateVertexBuffer(true, Frames.Size() == 1); + SetVertexBuffer(renderer->GetType(), vbuf); + + FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize); + unsigned int *indxptr = vbuf->LockIndexBuffer(ibufsize); + + assert(vertptr != nullptr && indxptr != nullptr); + + unsigned int vindex = 0, iindex = 0; + + for (unsigned i = 0; i < Surfaces.Size(); i++) + { + MD3Surface * surf = &Surfaces[i]; + + surf->vindex = vindex; + surf->iindex = iindex; + for (unsigned j = 0; j < Frames.Size() * surf->numVertices; j++) + { + MD3Vertex* vert = &surf->Vertices[j]; + + FModelVertex *bvert = &vertptr[vindex++]; + + int tc = j % surf->numVertices; + bvert->Set(vert->x, vert->z, vert->y, surf->Texcoords[tc].s, surf->Texcoords[tc].t); + bvert->SetNormal(vert->nx, vert->nz, vert->ny); + } + + for (unsigned k = 0; k < surf->numTriangles; k++) + { + for (int l = 0; l < 3; l++) + { + indxptr[iindex++] = surf->Tris[k].VertIndex[l]; + } + } + surf->UnloadGeometry(); + } + vbuf->UnlockVertexBuffer(); + vbuf->UnlockIndexBuffer(); + } +} + + +//=========================================================================== +// +// for skin precaching +// +//=========================================================================== + +void FMD3Model::AddSkins(uint8_t *hitlist) +{ + for (unsigned i = 0; i < Surfaces.Size(); i++) + { + if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + hitlist[curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].GetIndex()] |= FTextureManager::HIT_Flat; + } + + MD3Surface * surf = &Surfaces[i]; + for (unsigned j = 0; j < surf->numSkins; j++) + { + if (surf->Skins[j].isValid()) + { + hitlist[surf->Skins[j].GetIndex()] |= FTextureManager::HIT_Flat; + } + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== + +int FMD3Model::FindFrame(const char * name) +{ + for (unsigned i = 0; i < Frames.Size(); i++) + { + if (!stricmp(name, Frames[i].Name)) return i; + } + return -1; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FMD3Model::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation) +{ + if ((unsigned)frameno >= Frames.Size() || (unsigned)frameno2 >= Frames.Size()) return; + + renderer->SetInterpolation(inter); + for (unsigned i = 0; i < Surfaces.Size(); i++) + { + MD3Surface * surf = &Surfaces[i]; + + // [BB] In case no skin is specified via MODELDEF, check if the MD3 has a skin for the current surface. + // Note: Each surface may have a different skin. + FGameTexture *surfaceSkin = skin; + if (!surfaceSkin) + { + if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + surfaceSkin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i], true); + } + else if (surf->numSkins > 0 && surf->Skins[0].isValid()) + { + surfaceSkin = TexMan.GetGameTexture(surf->Skins[0], true); + } + + if (!surfaceSkin) + { + continue; + } + } + + renderer->SetMaterial(surfaceSkin, false, translation); + renderer->SetupFrame(this, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices); + renderer->DrawElements(surf->numTriangles * 3, surf->iindex * sizeof(unsigned int)); + } + renderer->SetInterpolation(0.f); +} + diff --git a/source/common/models/models_obj.cpp b/source/common/models/models_obj.cpp new file mode 100644 index 000000000..264ffd6ac --- /dev/null +++ b/source/common/models/models_obj.cpp @@ -0,0 +1,700 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Kevin Caccamo +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- + +#include "filesystem.h" +#include "model_obj.h" +#include "texturemanager.h" +#include "modelrenderer.h" +#include "printf.h" +#include "textureid.h" + +/** + * Load an OBJ model + * + * @param fn The path to the model file + * @param lumpnum The lump index in the wad collection + * @param buffer The contents of the model file + * @param length The size of the model file + * @return Whether or not the model was parsed successfully + */ +bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length) +{ + FString objName = fileSystem.GetFileFullPath(lumpnum); + FString objBuf(buffer, length); + + // Do some replacements before we parse the OBJ string + { + // Ensure usemtl statements remain intact + TArray mtlUsages; + TArray mtlUsageIdxs; + long bpos = 0, nlpos = 0, slashpos = 0; + while (1) + { + bpos = objBuf.IndexOf("\nusemtl", bpos); + if (bpos == -1) break; + slashpos = objBuf.IndexOf('/', bpos); + nlpos = objBuf.IndexOf('\n', ++bpos); + if (slashpos > nlpos || slashpos == -1) + { + continue; + } + if (nlpos == -1) + { + nlpos = (long)objBuf.Len(); + } + FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos); + mtlUsages.Push(lineStr); + mtlUsageIdxs.Push(bpos); + } + + // Replace forward slashes with percent signs so they aren't parsed as line comments + objBuf.ReplaceChars('/', *newSideSep); + char* wObjBuf = objBuf.LockBuffer(); + + // Substitute broken usemtl statements with old ones + for (size_t i = 0; i < mtlUsages.Size(); i++) + { + bpos = mtlUsageIdxs[i]; + nlpos = objBuf.IndexOf('\n', bpos); + if (nlpos == -1) + { + nlpos = (long)objBuf.Len(); + } + memcpy(wObjBuf + bpos, mtlUsages[i].GetChars(), nlpos - bpos); + } + + bpos = 0; + // Find each OBJ line comment, and convert each to a C-style line comment + while (1) + { + bpos = objBuf.IndexOf('#', bpos); + if (bpos == -1) break; + if (objBuf[(unsigned int)bpos + 1] == '\n') + { + wObjBuf[bpos] = ' '; + } + else + { + wObjBuf[bpos] = '/'; + wObjBuf[bpos+1] = '/'; + } + bpos += 1; + } + wObjBuf = nullptr; + objBuf.UnlockBuffer(); + } + sc.OpenString(objName, objBuf); + + FTextureID curMtl = FNullTextureID(); + OBJSurface *curSurface = nullptr; + unsigned int aggSurfFaceCount = 0; + unsigned int curSurfFaceCount = 0; + unsigned int curSmoothGroup = 0; + + while(sc.GetString()) + { + if (sc.Compare("v")) // Vertex + { + ParseVector(this->verts); + } + else if (sc.Compare("vn")) // Vertex normal + { + ParseVector(this->norms); + } + else if (sc.Compare("vt")) // UV Coordinates + { + ParseVector(this->uvs); + } + else if (sc.Compare("usemtl")) + { + // Get material name and try to load it + sc.MustGetString(); + + curMtl = LoadSkin("", sc.String); + if (!curMtl.isValid()) + { + // Relative to model file path? + curMtl = LoadSkin(fn, sc.String); + } + + if (!curMtl.isValid()) + { + sc.ScriptMessage("Material %s (#%u) not found.", sc.String, surfaces.Size()); + } + + // Build surface... + if (curSurface == nullptr) + { + // First surface + curSurface = new OBJSurface(curMtl); + } + else + { + if (curSurfFaceCount > 0) + { + // Add previous surface + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + // Go to next surface + curSurface = new OBJSurface(curMtl); + aggSurfFaceCount += curSurfFaceCount; + } + else + { + curSurface->skin = curMtl; + } + } + curSurfFaceCount = 0; + } + else if (sc.Compare("f")) + { + FString sides[4]; + OBJFace face; + for (int i = 0; i < 3; i++) + { + // A face must have at least 3 sides + sc.MustGetString(); + sides[i] = sc.String; + if (!ParseFaceSide(sides[i], face, i)) return false; + } + face.sideCount = 3; + if (sc.GetString()) + { + if (!sc.Compare("f") && FString(sc.String).IndexOfAny("-0123456789") == 0) + { + sides[3] = sc.String; + face.sideCount += 1; + if (!ParseFaceSide(sides[3], face, 3)) return false; + } + else + { + sc.UnGet(); // No 4th side, move back + } + } + face.smoothGroup = curSmoothGroup; + faces.Push(face); + curSurfFaceCount += 1; + } + else if (sc.Compare("s")) + { + sc.MustGetString(); + if (sc.Compare("off")) + { + curSmoothGroup = 0; + } + else + { + sc.UnGet(); + sc.MustGetNumber(); + curSmoothGroup = sc.Number; + hasSmoothGroups = hasSmoothGroups || curSmoothGroup > 0; + } + } + } + sc.Close(); + + if (curSurface == nullptr) + { // No valid materials detected + FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom + curSurface = new OBJSurface(dummyMtl); + } + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + + if (uvs.Size() == 0) + { // Needed so that OBJs without UVs can work + uvs.Push(FVector2(0.0, 0.0)); + } + + return true; +} + +/** + * Parse an x-Dimensional vector + * + * @tparam T A subclass of TVector2 to be used + * @tparam L The length of the vector to parse + * @param[out] array The array to append the parsed vector to + */ +template void FOBJModel::ParseVector(TArray &array) +{ + float coord[L]; + for (size_t axis = 0; axis < L; axis++) + { + sc.MustGetFloat(); + coord[axis] = (float)sc.Float; + } + T vec(coord); + array.Push(vec); +} + +/** + * Parse a side of a face + * + * @param[in] sideStr The side definition string + * @param[out] face The face to assign the parsed side data to + * @param sidx The 0-based index of the side + * @return Whether or not the face side was parsed successfully + */ +bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) +{ + OBJFaceSide side; + int origIdx; + if (sideStr.IndexOf(newSideSep) >= 0) + { + TArray sides = sideStr.Split(newSideSep, FString::TOK_KEEPEMPTY); + + if (sides[0].Len() > 0) + { + origIdx = atoi(sides[0].GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + } + else + { + sc.ScriptError("Vertex reference is not optional!"); + return false; + } + + if (sides[1].Len() > 0) + { + origIdx = atoi(sides[1].GetChars()); + side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex); + } + else + { + side.uvref = -1; + } + + if (sides.Size() > 2) + { + if (sides[2].Len() > 0) + { + origIdx = atoi(sides[2].GetChars()); + side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex); + } + else + { + side.normref = -1; + hasMissingNormals = true; + } + } + else + { + side.normref = -1; + hasMissingNormals = true; + } + } + else + { + origIdx = atoi(sideStr.GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + side.normref = -1; + hasMissingNormals = true; + side.uvref = -1; + } + face.sides[sidx] = side; + return true; +} + +/** + * Resolve an OBJ index to an absolute index + * + * OBJ indices are 1-based, and can also be negative + * + * @param origIndex The original OBJ index to resolve + * @param el What type of element the index references + * @return The absolute index of the element + */ +int FOBJModel::ResolveIndex(int origIndex, FaceElement el) +{ + if (origIndex > 0) + { + return origIndex - 1; // OBJ indices start at 1 + } + else if (origIndex < 0) + { + if (el == FaceElement::VertexIndex) + { + return verts.Size() + origIndex; // origIndex is negative + } + else if (el == FaceElement::UVIndex) + { + return uvs.Size() + origIndex; + } + else if (el == FaceElement::VNormalIndex) + { + return norms.Size() + origIndex; + } + } + return -1; +} + +/** + * Construct the vertex buffer for this model + * + * @param renderer A pointer to the model renderer. Used to allocate the vertex buffer. + */ +void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) +{ + if (GetVertexBuffer(renderer->GetType())) + { + return; + } + + unsigned int vbufsize = 0; + + for (size_t i = 0; i < surfaces.Size(); i++) + { + ConstructSurfaceTris(surfaces[i]); + surfaces[i].vbStart = vbufsize; + vbufsize += surfaces[i].numTris * 3; + } + // Initialize/populate vertFaces + if (hasMissingNormals && hasSmoothGroups) + { + AddVertFaces(); + } + + auto vbuf = renderer->CreateVertexBuffer(false,true); + SetVertexBuffer(renderer->GetType(), vbuf); + + FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize); + + for (unsigned int i = 0; i < surfaces.Size(); i++) + { + for (unsigned int j = 0; j < surfaces[i].numTris; j++) + { + for (size_t side = 0; side < 3; side++) + { + FModelVertex *mdv = vertptr + + side + j * 3 + // Current surface and previous triangles + surfaces[i].vbStart; // Previous surfaces + + OBJFaceSide &curSide = surfaces[i].tris[j].sides[2 - side]; + + int vidx = curSide.vertref; + int uvidx = (curSide.uvref >= 0 && (unsigned int)curSide.uvref < uvs.Size()) ? curSide.uvref : 0; + int nidx = curSide.normref; + + FVector3 curVvec = RealignVector(verts[vidx]); + FVector2 curUvec = FixUV(uvs[uvidx]); + FVector3 nvec; + + mdv->Set(curVvec.X, curVvec.Y, curVvec.Z, curUvec.X, curUvec.Y); + + if (nidx >= 0 && (unsigned int)nidx < norms.Size()) + { + nvec = RealignVector(norms[nidx]); + } + else + { + if (surfaces[i].tris[j].smoothGroup == 0) + { + nvec = CalculateNormalFlat(i, j); + } + else + { + nvec = CalculateNormalSmooth(vidx, surfaces[i].tris[j].smoothGroup); + } + } + mdv->SetNormal(nvec.X, nvec.Y, nvec.Z); + } + } + delete[] surfaces[i].tris; + } + + // Destroy vertFaces + if (hasMissingNormals && hasSmoothGroups) + { + for (size_t i = 0; i < verts.Size(); i++) + { + vertFaces[i].Clear(); + } + delete[] vertFaces; + } + vbuf->UnlockVertexBuffer(); +} + +/** + * Fill in the triangle data for a surface + * + * @param[in,out] surf The surface to fill in the triangle data for + */ +void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) +{ + unsigned int triCount = 0; + + size_t start = surf.faceStart; + size_t end = start + surf.numFaces; + for (size_t i = start; i < end; i++) + { + triCount += faces[i].sideCount - 2; + } + + surf.numTris = triCount; + surf.tris = new OBJFace[triCount]; + + for (size_t i = start, triIdx = 0; i < end; i++, triIdx++) + { + surf.tris[triIdx].sideCount = 3; + if (faces[i].sideCount == 3) + { + surf.tris[triIdx].smoothGroup = faces[i].smoothGroup; + memcpy(surf.tris[triIdx].sides, faces[i].sides, sizeof(OBJFaceSide) * 3); + } + else if (faces[i].sideCount == 4) // Triangulate face + { + OBJFace *triangulated = new OBJFace[2]; + TriangulateQuad(faces[i], triangulated); + memcpy(surf.tris[triIdx].sides, triangulated[0].sides, sizeof(OBJFaceSide) * 3); + memcpy(surf.tris[triIdx+1].sides, triangulated[1].sides, sizeof(OBJFaceSide) * 3); + delete[] triangulated; + triIdx += 1; // Filling out two faces + } + DPrintf(DMSG_SPAMMY, "Smooth group: %d\n", surf.tris[triIdx].smoothGroup); + } +} + +/** + * Triangulate a 4-sided face + * + * @param[in] quad The 4-sided face to triangulate + * @param[out] tris The resultant triangle data + */ +void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) +{ + tris[0].sideCount = 3; + tris[0].smoothGroup = quad.smoothGroup; + tris[1].sideCount = 3; + tris[1].smoothGroup = quad.smoothGroup; + + int tsidx[2][3] = {{0, 1, 3}, {1, 2, 3}}; + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 2; j++) + { + tris[j].sides[i].vertref = quad.sides[tsidx[j][i]].vertref; + tris[j].sides[i].uvref = quad.sides[tsidx[j][i]].uvref; + tris[j].sides[i].normref = quad.sides[tsidx[j][i]].normref; + } + } +} + +/** + * Add the vertices of all surfaces' triangles to the array of vertex->triangle references + */ +void FOBJModel::AddVertFaces() { + // Initialize and populate vertFaces - this array stores references to triangles per vertex + vertFaces = new TArray[verts.Size()]; + for (unsigned int i = 0; i < surfaces.Size(); i++) + { + for (unsigned int j = 0; j < surfaces[i].numTris; j++) + { + OBJTriRef otr = OBJTriRef(i, j); + for (size_t k = 0; k < surfaces[i].tris[j].sideCount; k++) + { + int vidx = surfaces[i].tris[j].sides[k].vertref; + vertFaces[vidx].Push(otr); + } + } + } +} + +/** + * Re-align a vector to match MD3 alignment + * + * @param vecToRealign The vector to re-align + * @return The re-aligned vector + */ +inline FVector3 FOBJModel::RealignVector(FVector3 vecToRealign) +{ + vecToRealign.Z *= -1; + return vecToRealign; +} + +/** + * Fix UV coordinates of a UV vector + * + * @param vecToRealign The vector to fix + * @return The fixed UV coordinate vector + */ +inline FVector2 FOBJModel::FixUV(FVector2 vecToRealign) +{ + vecToRealign.Y *= -1; + return vecToRealign; +} + +/** + * Calculate the surface normal for a triangle + * + * @param surfIdx The surface index + * @param triIdx The triangle Index + * @return The surface normal vector + */ +FVector3 FOBJModel::CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx) +{ + // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal + int curVert = surfaces[surfIdx].tris[triIdx].sides[0].vertref; + int nextVert = surfaces[surfIdx].tris[triIdx].sides[2].vertref; + int lastVert = surfaces[surfIdx].tris[triIdx].sides[1].vertref; + + // Cross-multiply the U-vector and V-vector + FVector3 curVvec = RealignVector(verts[curVert]); + FVector3 uvec = RealignVector(verts[nextVert]) - curVvec; + FVector3 vvec = RealignVector(verts[lastVert]) - curVvec; + + return uvec ^ vvec; +} + +/** + * Calculate the surface normal for a triangle + * + * @param otr A reference to the surface, and a triangle within that surface, as an OBJTriRef + * @return The surface normal vector + */ +FVector3 FOBJModel::CalculateNormalFlat(OBJTriRef otr) +{ + return CalculateNormalFlat(otr.surf, otr.tri); +} + +/** + * Calculate the normal of a vertex in a specific smooth group + * + * @param vidx The index of the vertex in the array of vertices + * @param smoothGroup The smooth group number + */ +FVector3 FOBJModel::CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup) +{ + unsigned int connectedFaces = 0; + TArray& vTris = vertFaces[vidx]; + + FVector3 vNormal(0,0,0); + for (size_t face = 0; face < vTris.Size(); face++) + { + OBJFace& tri = surfaces[vTris[face].surf].tris[vTris[face].tri]; + if (tri.smoothGroup == smoothGroup) + { + FVector3 fNormal = CalculateNormalFlat(vTris[face]); + connectedFaces += 1; + vNormal += fNormal; + } + } + vNormal /= (float)connectedFaces; + return vNormal; +} + +/** + * Find the index of the frame with the given name + * + * OBJ models are not animated, so this always returns 0 + * + * @param name The name of the frame + * @return The index of the frame + */ +int FOBJModel::FindFrame(const char* name) +{ + return 0; // OBJs are not animated. +} + +/** + * Render the model + * + * @param renderer The model renderer + * @param skin The loaded skin for the surface + * @param frameno Unused + * @param frameno2 Unused + * @param inter Unused + * @param translation The translation for the skin + */ +void FOBJModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation) +{ + for (unsigned int i = 0; i < surfaces.Size(); i++) + { + OBJSurface *surf = &surfaces[i]; + + FGameTexture *userSkin = skin; + if (!userSkin) + { + if (i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + userSkin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i], true); + } + else if (surf->skin.isValid()) + { + userSkin = TexMan.GetGameTexture(surf->skin, true); + } + } + + // Still no skin after checking for one? + if (!userSkin) + { + continue; + } + + renderer->SetMaterial(userSkin, false, translation); + renderer->SetupFrame(this, surf->vbStart, surf->vbStart, surf->numTris * 3); + renderer->DrawArrays(0, surf->numTris * 3); + } +} + +/** + * Pre-cache skins for the model + * + * @param hitlist The list of textures + */ +void FOBJModel::AddSkins(uint8_t* hitlist) +{ + for (size_t i = 0; i < surfaces.Size(); i++) + { + if (i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + // Precache skins manually reassigned by the user. + // On OBJs with lots of skins, such as Doom map OBJs exported from GZDB, + // there may be too many skins for the user to manually change, unless + // the limit is bumped or surfaceskinIDs is changed to a TArray. + hitlist[curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].GetIndex()] |= FTextureManager::HIT_Flat; + return; // No need to precache skin that was replaced + } + + OBJSurface * surf = &surfaces[i]; + if (surf->skin.isValid()) + { + hitlist[surf->skin.GetIndex()] |= FTextureManager::HIT_Flat; + } + } +} + +/** + * Remove the data that was loaded + */ +FOBJModel::~FOBJModel() +{ + verts.Clear(); + norms.Clear(); + uvs.Clear(); + faces.Clear(); + surfaces.Clear(); +} diff --git a/source/common/models/models_ue1.cpp b/source/common/models/models_ue1.cpp new file mode 100644 index 000000000..1fb6146a1 --- /dev/null +++ b/source/common/models/models_ue1.cpp @@ -0,0 +1,307 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2018 Marisa Kirisame +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "filesystem.h" +#include "cmdlib.h" +#include "model_ue1.h" +#include "texturemanager.h" +#include "modelrenderer.h" + +float unpackuvert( uint32_t n, int c ) +{ + switch( c ) + { + case 0: + return ((int16_t)((n&0x7ff)<<5))/128.f; + case 1: + return ((int16_t)(((n>>11)&0x7ff)<<5))/128.f; + case 2: + return ((int16_t)(((n>>22)&0x3ff)<<6))/128.f; + default: + return 0.f; + } +} + +bool FUE1Model::Load( const char *filename, int lumpnum, const char *buffer, int length ) +{ + int lumpnum2; + FString realfilename = fileSystem.GetFileFullName(lumpnum); + if ( (size_t)realfilename.IndexOf("_d.3d") == realfilename.Len()-5 ) + { + realfilename.Substitute("_d.3d","_a.3d"); + lumpnum2 = fileSystem.CheckNumForFullName(realfilename); + mDataLump = lumpnum; + mAnivLump = lumpnum2; + } + else + { + realfilename.Substitute("_a.3d","_d.3d"); + lumpnum2 = fileSystem.CheckNumForFullName(realfilename); + mAnivLump = lumpnum; + mDataLump = lumpnum2; + } + return true; +} + +void FUE1Model::LoadGeometry() +{ + FileData lump, lump2; + const char *buffer, *buffer2; + lump = fileSystem.ReadFile(mDataLump); + buffer = (char*)lump.GetMem(); + lump2 = fileSystem.ReadFile(mAnivLump); + buffer2 = (char*)lump2.GetMem(); + // map structures + dhead = (d3dhead*)(buffer); + dpolys = (d3dpoly*)(buffer+sizeof(d3dhead)); + ahead = (a3dhead*)(buffer2); + // detect deus ex format + if ( (ahead->framesize/dhead->numverts) == 8 ) + { + averts = NULL; + dxverts = (dxvert*)(buffer2+sizeof(a3dhead)); + } + else + { + averts = (uint32_t*)(buffer2+sizeof(a3dhead)); + dxverts = NULL; + } + weaponPoly = -1; + // set counters + numVerts = dhead->numverts; + numFrames = ahead->numframes; + numPolys = dhead->numpolys; + numGroups = 0; + // populate vertex arrays + for ( int i=0; i= numFrames) || (frame2 >= numFrames) ) return; + renderer->SetInterpolation(inter); + int vsize, fsize = 0, vofs = 0; + for ( int i=0; isurfaceskinIDs[curMDLIndex][groups[i].texNum].isValid() ) + sskin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][groups[i].texNum], true); + if ( !sskin ) + { + vofs += vsize; + continue; + } + } + // TODO: Handle per-group render styles and other flags once functions for it are implemented + // Future note: poly renderstyles should always be enforced unless the actor itself has a style other than Normal + renderer->SetMaterial(sskin,false,translation); + renderer->SetupFrame(this, vofs+frame*fsize,vofs+frame2*fsize,vsize); + renderer->DrawArrays(0,vsize); + vofs += vsize; + } + renderer->SetInterpolation(0.f); +} + +void FUE1Model::BuildVertexBuffer( FModelRenderer *renderer ) +{ + if (GetVertexBuffer(renderer->GetType())) + return; + if ( !mDataLoaded ) + LoadGeometry(); + int vsize = 0; + for ( int i=0; iCreateVertexBuffer(false,numFrames==1); + SetVertexBuffer(renderer->GetType(), vbuf); + FModelVertex *vptr = vbuf->LockVertexBuffer(vsize); + int vidx = 0; + for ( int i=0; iSet(V.Pos.X,V.Pos.Y,V.Pos.Z,C.X,C.Y); + if ( groups[j].type&PT_Curvy ) // use facet normal + { + vert->SetNormal(polys[groups[j].P[k]].Normals[i].X, + polys[groups[j].P[k]].Normals[i].Y, + polys[groups[j].P[k]].Normals[i].Z); + } + else vert->SetNormal(V.Normal.X,V.Normal.Y,V.Normal.Z); + } + } + } + } + vbuf->UnlockVertexBuffer(); +} + +void FUE1Model::AddSkins( uint8_t *hitlist ) +{ + for ( int i=0; isurfaceskinIDs[curMDLIndex][groups[i].texNum].isValid() ) + hitlist[curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][groups[i].texNum].GetIndex()] |= FTextureManager::HIT_Flat; +} + +FUE1Model::~FUE1Model() +{ + UnloadGeometry(); +} diff --git a/source/common/models/models_voxel.cpp b/source/common/models/models_voxel.cpp new file mode 100644 index 000000000..cfac12372 --- /dev/null +++ b/source/common/models/models_voxel.cpp @@ -0,0 +1,407 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2010-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// +/* +** gl_voxels.cpp +** +** Voxel management +** +**/ + +#include "filesystem.h" +#include "colormatcher.h" +#include "bitmap.h" +#include "model_kvx.h" +#include "image.h" +#include "texturemanager.h" +#include "modelrenderer.h" +#include "voxels.h" +#include "texturemanager.h" +#include "palettecontainer.h" +#include "textures.h" + +#ifdef _MSC_VER +#pragma warning(disable:4244) // warning C4244: conversion from 'double' to 'float', possible loss of data +#endif + +//=========================================================================== +// +// Creates a 16x16 texture from the palette so that we can +// use the existing palette manipulation code to render the voxel +// Otherwise all shaders had to be duplicated and the non-shader code +// would be a lot less efficient. +// +//=========================================================================== + +class FVoxelTexture : public FImageSource +{ +public: + FVoxelTexture(FVoxel *voxel); + + int CopyPixels(FBitmap *bmp, int conversion) override; + TArray CreatePalettedPixels(int conversion) override; + +protected: + FVoxel *SourceVox; +}; + +//=========================================================================== +// +// +// +//=========================================================================== + +FVoxelTexture::FVoxelTexture(FVoxel *vox) +{ + SourceVox = vox; + Width = 16; + Height = 16; + //bNoCompress = true; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +TArray FVoxelTexture::CreatePalettedPixels(int conversion) +{ + // GetPixels gets called when a translated palette is used so we still need to implement it here. + TArray Pixels(256, true); + uint8_t *pp = SourceVox->Palette.Data(); + + if(pp != NULL) + { + for(int i=0;i<256;i++, pp+=3) + { + PalEntry pe; + pe.r = (pp[0] << 2) | (pp[0] >> 4); + pe.g = (pp[1] << 2) | (pp[1] >> 4); + pe.b = (pp[2] << 2) | (pp[2] >> 4); + // Alphatexture handling is just for completeness, but rather unlikely to be used ever. + Pixels[i] = conversion == luminance ? pe.r : ColorMatcher.Pick(pe); + } + } + else + { + for(int i=0;i<256;i++, pp+=3) + { + Pixels[i] = (uint8_t)i; + } + } + return Pixels; +} + +//=========================================================================== +// +// FVoxelTexture::CopyPixels +// +// This creates a dummy 16x16 paletted bitmap and converts that using the +// voxel palette +// +//=========================================================================== + +int FVoxelTexture::CopyPixels(FBitmap *bmp, int conversion) +{ + PalEntry pe[256]; + uint8_t bitmap[256]; + uint8_t *pp = SourceVox->Palette.Data(); + + if(pp != nullptr) + { + for(int i=0;i<256;i++, pp+=3) + { + bitmap[i] = (uint8_t)i; + pe[i].r = (pp[0] << 2) | (pp[0] >> 4); + pe[i].g = (pp[1] << 2) | (pp[1] >> 4); + pe[i].b = (pp[2] << 2) | (pp[2] >> 4); + pe[i].a = 255; + } + } + else + { + for(int i=0;i<256;i++, pp+=3) + { + bitmap[i] = (uint8_t)i; + pe[i] = GPalette.BaseColors[i]; + pe[i].a = 255; + } + } + bmp->CopyPixelData(0, 0, bitmap, Width, Height, 1, 16, 0, pe); + return 0; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +FVoxelModel::FVoxelModel(FVoxel *voxel, bool owned) +{ + mVoxel = voxel; + mOwningVoxel = owned; + mPalette = TexMan.AddGameTexture(MakeGameTexture(new FImageTexture(new FVoxelTexture(voxel)), nullptr, ETextureType::Override)); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +FVoxelModel::~FVoxelModel() +{ + if (mOwningVoxel) delete mVoxel; +} + + +//=========================================================================== +// +// +// +//=========================================================================== + +unsigned int FVoxelModel::AddVertex(FModelVertex &vert, FVoxelMap &check) +{ + unsigned int index = check[vert]; + if (index == 0xffffffff) + { + index = check[vert] =mVertices.Push(vert); + } + return index; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FVoxelModel::AddFace(int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int x4, int y4, int z4, uint8_t col, FVoxelMap &check) +{ + float PivotX = mVoxel->Mips[0].Pivot.X; + float PivotY = mVoxel->Mips[0].Pivot.Y; + float PivotZ = mVoxel->Mips[0].Pivot.Z; + int h = mVoxel->Mips[0].SizeZ; + FModelVertex vert; + unsigned int indx[4]; + + vert.packedNormal = 0; // currently this is not being used for voxels. + vert.u = (((col & 15) * 255 / 16) + 7) / 255.f; + vert.v = (((col / 16) * 255 / 16) + 7) / 255.f; + + vert.x = x1 - PivotX; + vert.z = -y1 + PivotY; + vert.y = -z1 + PivotZ; + indx[0] = AddVertex(vert, check); + + vert.x = x2 - PivotX; + vert.z = -y2 + PivotY; + vert.y = -z2 + PivotZ; + indx[1] = AddVertex(vert, check); + + vert.x = x4 - PivotX; + vert.z = -y4 + PivotY; + vert.y = -z4 + PivotZ; + indx[2] = AddVertex(vert, check); + + vert.x = x3 - PivotX; + vert.z = -y3 + PivotY; + vert.y = -z3 + PivotZ; + indx[3] = AddVertex(vert, check); + + + mIndices.Push(indx[0]); + mIndices.Push(indx[1]); + mIndices.Push(indx[3]); + mIndices.Push(indx[1]); + mIndices.Push(indx[2]); + mIndices.Push(indx[3]); +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FVoxelModel::MakeSlabPolys(int x, int y, kvxslab_t *voxptr, FVoxelMap &check) +{ + const uint8_t *col = voxptr->col; + int zleng = voxptr->zleng; + int ztop = voxptr->ztop; + int cull = voxptr->backfacecull; + + if (cull & 16) + { + AddFace(x, y, ztop, x+1, y, ztop, x, y+1, ztop, x+1, y+1, ztop, *col, check); + } + int z = ztop; + while (z < ztop+zleng) + { + int c = 0; + while (z+c < ztop+zleng && col[c] == col[0]) c++; + + if (cull & 1) + { + AddFace(x, y, z, x, y+1, z, x, y, z+c, x, y+1, z+c, *col, check); + } + if (cull & 2) + { + AddFace(x+1, y+1, z, x+1, y, z, x+1, y+1, z+c, x+1, y, z+c, *col, check); + } + if (cull & 4) + { + AddFace(x+1, y, z, x, y, z, x+1, y, z+c, x, y, z+c, *col, check); + } + if (cull & 8) + { + AddFace(x, y+1, z, x+1, y+1, z, x, y+1, z+c, x+1, y+1, z+c, *col, check); + } + z+=c; + col+=c; + } + if (cull & 32) + { + int z = ztop+zleng-1; + AddFace(x+1, y, z+1, x, y, z+1, x+1, y+1, z+1, x, y+1, z+1, voxptr->col[zleng-1], check); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FVoxelModel::Initialize() +{ + FVoxelMap check; + FVoxelMipLevel *mip = &mVoxel->Mips[0]; + for (int x = 0; x < mip->SizeX; x++) + { + uint8_t *slabxoffs = &mip->GetSlabData(false)[mip->OffsetX[x]]; + short *xyoffs = &mip->OffsetXY[x * (mip->SizeY + 1)]; + for (int y = 0; y < mip->SizeY; y++) + { + kvxslab_t *voxptr = (kvxslab_t *)(slabxoffs + xyoffs[y]); + kvxslab_t *voxend = (kvxslab_t *)(slabxoffs + xyoffs[y+1]); + for (; voxptr < voxend; voxptr = (kvxslab_t *)((uint8_t *)voxptr + voxptr->zleng + 3)) + { + MakeSlabPolys(x, y, voxptr, check); + } + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== + +void FVoxelModel::BuildVertexBuffer(FModelRenderer *renderer) +{ + if (!GetVertexBuffer(renderer->GetType())) + { + Initialize(); + + auto vbuf = renderer->CreateVertexBuffer(true, true); + SetVertexBuffer(renderer->GetType(), vbuf); + + FModelVertex *vertptr = vbuf->LockVertexBuffer(mVertices.Size()); + unsigned int *indxptr = vbuf->LockIndexBuffer(mIndices.Size()); + + memcpy(vertptr, &mVertices[0], sizeof(FModelVertex)* mVertices.Size()); + memcpy(indxptr, &mIndices[0], sizeof(unsigned int)* mIndices.Size()); + + vbuf->UnlockVertexBuffer(); + vbuf->UnlockIndexBuffer(); + mNumIndices = mIndices.Size(); + + // delete our temporary buffers + mVertices.Clear(); + mIndices.Clear(); + mVertices.ShrinkToFit(); + mIndices.ShrinkToFit(); + } +} + + +//=========================================================================== +// +// for skin precaching +// +//=========================================================================== + +void FVoxelModel::AddSkins(uint8_t *hitlist) +{ + hitlist[mPalette.GetIndex()] |= FTextureManager::HIT_Flat; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +bool FVoxelModel::Load(const char * fn, int lumpnum, const char * buffer, int length) +{ + return false; // not needed +} + +//=========================================================================== +// +// Voxels don't have frames so always return 0 +// +//=========================================================================== + +int FVoxelModel::FindFrame(const char * name) +{ + return 0; +} + +//=========================================================================== +// +// Voxels need aspect ratio correction according to the current map's setting +// +//=========================================================================== + +float FVoxelModel::getAspectFactor(float stretch) +{ + return stretch; +} + +//=========================================================================== +// +// Voxels never interpolate between frames, they only have one. +// +//=========================================================================== + +void FVoxelModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation) +{ + renderer->SetMaterial(skin, true, translation); + renderer->SetupFrame(this, 0, 0, 0); + renderer->DrawElements(mNumIndices, 0); +} + diff --git a/source/common/models/tab_anorms.h b/source/common/models/tab_anorms.h new file mode 100644 index 000000000..dfd9f691d --- /dev/null +++ b/source/common/models/tab_anorms.h @@ -0,0 +1,489 @@ +#ifdef _WIN32 +#pragma warning(disable:4305) +#endif + +{ +-0.525731f, 0.000000f, 0.850651f}, + +{ +-0.442863f, 0.238856f, 0.864188f}, + +{ +-0.295242f, 0.000000f, 0.955423f}, + +{ +-0.309017f, 0.500000f, 0.809017f}, + +{ +-0.162460f, 0.262866f, 0.951056f}, + +{ +0.000000f, 0.000000f, 1.000000f}, + +{ +0.000000f, 0.850651f, 0.525731f}, + +{ +-0.147621f, 0.716567f, 0.681718f}, + +{ +0.147621f, 0.716567f, 0.681718f}, + +{ +0.000000f, 0.525731f, 0.850651f}, + +{ +0.309017f, 0.500000f, 0.809017f}, + +{ +0.525731f, 0.000000f, 0.850651f}, + +{ +0.295242f, 0.000000f, 0.955423f}, + +{ +0.442863f, 0.238856f, 0.864188f}, + +{ +0.162460f, 0.262866f, 0.951056f}, + +{ +-0.681718f, 0.147621f, 0.716567f}, + +{ +-0.809017f, 0.309017f, 0.500000f}, + +{ +-0.587785f, 0.425325f, 0.688191f}, + +{ +-0.850651f, 0.525731f, 0.000000f}, + +{ +-0.864188f, 0.442863f, 0.238856f}, + +{ +-0.716567f, 0.681718f, 0.147621f}, + +{ +-0.688191f, 0.587785f, 0.425325f}, + +{ +-0.500000f, 0.809017f, 0.309017f}, + +{ +-0.238856f, 0.864188f, 0.442863f}, + +{ +-0.425325f, 0.688191f, 0.587785f}, + +{ +-0.716567f, 0.681718f, -0.147621f}, + +{ +-0.500000f, 0.809017f, -0.309017f}, + +{ +-0.525731f, 0.850651f, 0.000000f}, + +{ +0.000000f, 0.850651f, -0.525731f}, + +{ +-0.238856f, 0.864188f, -0.442863f}, + +{ +0.000000f, 0.955423f, -0.295242f}, + +{ +-0.262866f, 0.951056f, -0.162460f}, + +{ +0.000000f, 1.000000f, 0.000000f}, + +{ +0.000000f, 0.955423f, 0.295242f}, + +{ +-0.262866f, 0.951056f, 0.162460f}, + +{ +0.238856f, 0.864188f, 0.442863f}, + +{ +0.262866f, 0.951056f, 0.162460f}, + +{ +0.500000f, 0.809017f, 0.309017f}, + +{ +0.238856f, 0.864188f, -0.442863f}, + +{ +0.262866f, 0.951056f, -0.162460f}, + +{ +0.500000f, 0.809017f, -0.309017f}, + +{ +0.850651f, 0.525731f, 0.000000f}, + +{ +0.716567f, 0.681718f, 0.147621f}, + +{ +0.716567f, 0.681718f, -0.147621f}, + +{ +0.525731f, 0.850651f, 0.000000f}, + +{ +0.425325f, 0.688191f, 0.587785f}, + +{ +0.864188f, 0.442863f, 0.238856f}, + +{ +0.688191f, 0.587785f, 0.425325f}, + +{ +0.809017f, 0.309017f, 0.500000f}, + +{ +0.681718f, 0.147621f, 0.716567f}, + +{ +0.587785f, 0.425325f, 0.688191f}, + +{ +0.955423f, 0.295242f, 0.000000f}, + +{ +1.000000f, 0.000000f, 0.000000f}, + +{ +0.951056f, 0.162460f, 0.262866f}, + +{ +0.850651f, -0.525731f, 0.000000f}, + +{ +0.955423f, -0.295242f, 0.000000f}, + +{ +0.864188f, -0.442863f, 0.238856f}, + +{ +0.951056f, -0.162460f, 0.262866f}, + +{ +0.809017f, -0.309017f, 0.500000f}, + +{ +0.681718f, -0.147621f, 0.716567f}, + +{ +0.850651f, 0.000000f, 0.525731f}, + +{ +0.864188f, 0.442863f, -0.238856f}, + +{ +0.809017f, 0.309017f, -0.500000f}, + +{ +0.951056f, 0.162460f, -0.262866f}, + +{ +0.525731f, 0.000000f, -0.850651f}, + +{ +0.681718f, 0.147621f, -0.716567f}, + +{ +0.681718f, -0.147621f, -0.716567f}, + +{ +0.850651f, 0.000000f, -0.525731f}, + +{ +0.809017f, -0.309017f, -0.500000f}, + +{ +0.864188f, -0.442863f, -0.238856f}, + +{ +0.951056f, -0.162460f, -0.262866f}, + +{ +0.147621f, 0.716567f, -0.681718f}, + +{ +0.309017f, 0.500000f, -0.809017f}, + +{ +0.425325f, 0.688191f, -0.587785f}, + +{ +0.442863f, 0.238856f, -0.864188f}, + +{ +0.587785f, 0.425325f, -0.688191f}, + +{ +0.688191f, 0.587785f, -0.425325f}, + +{ +-0.147621f, 0.716567f, -0.681718f}, + +{ +-0.309017f, 0.500000f, -0.809017f}, + +{ +0.000000f, 0.525731f, -0.850651f}, + +{ +-0.525731f, 0.000000f, -0.850651f}, + +{ +-0.442863f, 0.238856f, -0.864188f}, + +{ +-0.295242f, 0.000000f, -0.955423f}, + +{ +-0.162460f, 0.262866f, -0.951056f}, + +{ +0.000000f, 0.000000f, -1.000000f}, + +{ +0.295242f, 0.000000f, -0.955423f}, + +{ +0.162460f, 0.262866f, -0.951056f}, + +{ +-0.442863f, -0.238856f, -0.864188f}, + +{ +-0.309017f, -0.500000f, -0.809017f}, + +{ +-0.162460f, -0.262866f, -0.951056f}, + +{ +0.000000f, -0.850651f, -0.525731f}, + +{ +-0.147621f, -0.716567f, -0.681718f}, + +{ +0.147621f, -0.716567f, -0.681718f}, + +{ +0.000000f, -0.525731f, -0.850651f}, + +{ +0.309017f, -0.500000f, -0.809017f}, + +{ +0.442863f, -0.238856f, -0.864188f}, + +{ +0.162460f, -0.262866f, -0.951056f}, + +{ +0.238856f, -0.864188f, -0.442863f}, + +{ +0.500000f, -0.809017f, -0.309017f}, + +{ +0.425325f, -0.688191f, -0.587785f}, + +{ +0.716567f, -0.681718f, -0.147621f}, + +{ +0.688191f, -0.587785f, -0.425325f}, + +{ +0.587785f, -0.425325f, -0.688191f}, + +{ +0.000000f, -0.955423f, -0.295242f}, + +{ +0.000000f, -1.000000f, 0.000000f}, + +{ +0.262866f, -0.951056f, -0.162460f}, + +{ +0.000000f, -0.850651f, 0.525731f}, + +{ +0.000000f, -0.955423f, 0.295242f}, + +{ +0.238856f, -0.864188f, 0.442863f}, + +{ +0.262866f, -0.951056f, 0.162460f}, + +{ +0.500000f, -0.809017f, 0.309017f}, + +{ +0.716567f, -0.681718f, 0.147621f}, + +{ +0.525731f, -0.850651f, 0.000000f}, + +{ +-0.238856f, -0.864188f, -0.442863f}, + +{ +-0.500000f, -0.809017f, -0.309017f}, + +{ +-0.262866f, -0.951056f, -0.162460f}, + +{ +-0.850651f, -0.525731f, 0.000000f}, + +{ +-0.716567f, -0.681718f, -0.147621f}, + +{ +-0.716567f, -0.681718f, 0.147621f}, + +{ +-0.525731f, -0.850651f, 0.000000f}, + +{ +-0.500000f, -0.809017f, 0.309017f}, + +{ +-0.238856f, -0.864188f, 0.442863f}, + +{ +-0.262866f, -0.951056f, 0.162460f}, + +{ +-0.864188f, -0.442863f, 0.238856f}, + +{ +-0.809017f, -0.309017f, 0.500000f}, + +{ +-0.688191f, -0.587785f, 0.425325f}, + +{ +-0.681718f, -0.147621f, 0.716567f}, + +{ +-0.442863f, -0.238856f, 0.864188f}, + +{ +-0.587785f, -0.425325f, 0.688191f}, + +{ +-0.309017f, -0.500000f, 0.809017f}, + +{ +-0.147621f, -0.716567f, 0.681718f}, + +{ +-0.425325f, -0.688191f, 0.587785f}, + +{ +-0.162460f, -0.262866f, 0.951056f}, + +{ +0.442863f, -0.238856f, 0.864188f}, + +{ +0.162460f, -0.262866f, 0.951056f}, + +{ +0.309017f, -0.500000f, 0.809017f}, + +{ +0.147621f, -0.716567f, 0.681718f}, + +{ +0.000000f, -0.525731f, 0.850651f}, + +{ +0.425325f, -0.688191f, 0.587785f}, + +{ +0.587785f, -0.425325f, 0.688191f}, + +{ +0.688191f, -0.587785f, 0.425325f}, + +{ +-0.955423f, 0.295242f, 0.000000f}, + +{ +-0.951056f, 0.162460f, 0.262866f}, + +{ +-1.000000f, 0.000000f, 0.000000f}, + +{ +-0.850651f, 0.000000f, 0.525731f}, + +{ +-0.955423f, -0.295242f, 0.000000f}, + +{ +-0.951056f, -0.162460f, 0.262866f}, + +{ +-0.864188f, 0.442863f, -0.238856f}, + +{ +-0.951056f, 0.162460f, -0.262866f}, + +{ +-0.809017f, 0.309017f, -0.500000f}, + +{ +-0.864188f, -0.442863f, -0.238856f}, + +{ +-0.951056f, -0.162460f, -0.262866f}, + +{ +-0.809017f, -0.309017f, -0.500000f}, + +{ +-0.681718f, 0.147621f, -0.716567f}, + +{ +-0.681718f, -0.147621f, -0.716567f}, + +{ +-0.850651f, 0.000000f, -0.525731f}, + +{ +-0.688191f, 0.587785f, -0.425325f}, + +{ +-0.587785f, 0.425325f, -0.688191f}, + +{ +-0.425325f, 0.688191f, -0.587785f}, + +{ +-0.425325f, -0.688191f, -0.587785f}, + +{ +-0.587785f, -0.425325f, -0.688191f}, + +{ +-0.688191f, -0.587785f, -0.425325f}, diff --git a/source/common/models/voxels.cpp b/source/common/models/voxels.cpp new file mode 100644 index 000000000..75acb8dda --- /dev/null +++ b/source/common/models/voxels.cpp @@ -0,0 +1,489 @@ +/* +** voxels.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2010-2011 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include +#include +#include +#include + +#include "m_swap.h" +#include "m_argv.h" +#include "filesystem.h" +#include "v_video.h" +#include "sc_man.h" +#include "voxels.h" +#include "printf.h" + +void VOX_AddVoxel(int sprnum, int frame, FVoxelDef *def); + +TDeletingArray Voxels; // used only to auto-delete voxels on exit. +TDeletingArray VoxelDefs; + +//========================================================================== +// +// GetVoxelRemap +// +// Calculates a remap table for the voxel's palette. Results are cached so +// passing the same palette repeatedly will not require repeated +// recalculations. +// +//========================================================================== + +static uint8_t *GetVoxelRemap(const uint8_t *pal) +{ + static uint8_t remap[256]; + static uint8_t oldpal[768]; + static bool firsttime = true; + + if (firsttime || memcmp(oldpal, pal, 768) != 0) + { // Not the same palette as last time, so recalculate. + firsttime = false; + memcpy(oldpal, pal, 768); + for (int i = 0; i < 256; ++i) + { + // The voxel palette uses VGA colors, so we have to expand it + // from 6 to 8 bits per component. + remap[i] = BestColor((uint32_t *)GPalette.BaseColors, + (oldpal[i*3 + 0] << 2) | (oldpal[i*3 + 0] >> 4), + (oldpal[i*3 + 1] << 2) | (oldpal[i*3 + 1] >> 4), + (oldpal[i*3 + 2] << 2) | (oldpal[i*3 + 2] >> 4)); + } + } + return remap; +} + +//========================================================================== +// +// CopyVoxelSlabs +// +// Copy all the slabs in a block of slabs. +// +//========================================================================== + +static bool CopyVoxelSlabs(kvxslab_t *dest, const kvxslab_t *src, int size) +{ + while (size >= 3) + { + int slabzleng = src->zleng; + + if (3 + slabzleng > size) + { // slab is too tall + return false; + } + + dest->ztop = src->ztop; + dest->zleng = src->zleng; + dest->backfacecull = src->backfacecull; + + for (int j = 0; j < slabzleng; ++j) + { + dest->col[j] = src->col[j]; + } + slabzleng += 3; + src = (kvxslab_t *)((uint8_t *)src + slabzleng); + dest = (kvxslab_t *)((uint8_t *)dest + slabzleng); + size -= slabzleng; + } + return true; +} + +//========================================================================== +// +// RemapVoxelSlabs +// +// Remaps all the slabs in a block of slabs. +// +//========================================================================== + +static void RemapVoxelSlabs(kvxslab_t *dest, int size, const uint8_t *remap) +{ + while (size >= 3) + { + int slabzleng = dest->zleng; + + for (int j = 0; j < slabzleng; ++j) + { + dest->col[j] = remap[dest->col[j]]; + } + slabzleng += 3; + dest = (kvxslab_t *)((uint8_t *)dest + slabzleng); + size -= slabzleng; + } +} + +//========================================================================== +// +// R_LoadKVX +// +//========================================================================== + +#if defined __GNUC__ && !defined __clang__ +#pragma GCC push_options +#pragma GCC optimize ("-fno-tree-loop-vectorize") +#endif // __GNUC__ && !__clang__ + +FVoxel *R_LoadKVX(int lumpnum) +{ + const kvxslab_t *slabs[MAXVOXMIPS]; + FVoxel *voxel = new FVoxel; + const uint8_t *rawmip; + int mip, maxmipsize; + int i, j, n; + + FileData lump = fileSystem.ReadFile(lumpnum); // FileData adds an extra 0 byte to the end. + uint8_t *rawvoxel = (uint8_t *)lump.GetMem(); + int voxelsize = (int)(lump.GetSize()-1); + + // Oh, KVX, why couldn't you have a proper header? We'll just go through + // and collect each MIP level, doing lots of range checking, and if the + // last one doesn't end exactly 768 bytes before the end of the file, + // we'll reject it. + + for (mip = 0, rawmip = rawvoxel, maxmipsize = voxelsize - 768 - 4; + mip < MAXVOXMIPS; + mip++) + { + int numbytes = GetInt(rawmip); + if (numbytes > maxmipsize || numbytes < 24) + { + break; + } + rawmip += 4; + + FVoxelMipLevel *mipl = &voxel->Mips[mip]; + + // Load header data. + mipl->SizeX = GetInt(rawmip + 0); + mipl->SizeY = GetInt(rawmip + 4); + mipl->SizeZ = GetInt(rawmip + 8); + mipl->Pivot.X = GetInt(rawmip + 12) / 256.; + mipl->Pivot.Y = GetInt(rawmip + 16) / 256.; + mipl->Pivot.Z = GetInt(rawmip + 20) / 256.; + + // How much space do we have for voxdata? + int offsetsize = (mipl->SizeX + 1) * 4 + mipl->SizeX * (mipl->SizeY + 1) * 2; + int voxdatasize = numbytes - 24 - offsetsize; + if (voxdatasize < 0) + { // Clearly, not enough. + break; + } + if (voxdatasize != 0) + { // This mip level is not empty. + // Allocate slab data space. + mipl->OffsetX = new int[(numbytes - 24 + 3) / 4]; + mipl->OffsetXY = (short *)(mipl->OffsetX + mipl->SizeX + 1); + mipl->SlabData = (uint8_t *)(mipl->OffsetXY + mipl->SizeX * (mipl->SizeY + 1)); + + // Load x offsets. + for (i = 0, n = mipl->SizeX; i <= n; ++i) + { + // The X offsets stored in the KVX file are relative to the start of the + // X offsets array. Make them relative to voxdata instead. + mipl->OffsetX[i] = GetInt(rawmip + 24 + i * 4) - offsetsize; + } + + // The first X offset must be 0 (since we subtracted offsetsize), according to the spec: + // NOTE: xoffset[0] = (xsiz+1)*4 + xsiz*(ysiz+1)*2 (ALWAYS) + if (mipl->OffsetX[0] != 0) + { + break; + } + // And the final X offset must point just past the end of the voxdata. + if (mipl->OffsetX[mipl->SizeX] != voxdatasize) + { + break; + } + + // Load xy offsets. + i = 24 + i * 4; + for (j = 0, n *= mipl->SizeY + 1; j < n; ++j) + { + mipl->OffsetXY[j] = GetShort(rawmip + i + j * 2); + } + + // Ensure all offsets are within bounds. + for (i = 0; i < mipl->SizeX; ++i) + { + int xoff = mipl->OffsetX[i]; + for (j = 0; j < mipl->SizeY; ++j) + { + int yoff = mipl->OffsetXY[(mipl->SizeY + 1) * i + j]; + if (unsigned(xoff + yoff) > unsigned(voxdatasize)) + { + delete voxel; + return NULL; + } + } + } + + // Record slab location for the end. + slabs[mip] = (kvxslab_t *)(rawmip + 24 + offsetsize); + } + + // Time for the next mip Level. + rawmip += numbytes; + maxmipsize -= numbytes + 4; + } + // Did we get any mip levels, and if so, does the last one leave just + // enough room for the palette after it? + if (mip == 0 || rawmip != rawvoxel + voxelsize - 768) + { + delete voxel; + return NULL; + } + + // Do not count empty mips at the end. + for (; mip > 0; --mip) + { + if (voxel->Mips[mip - 1].SlabData != NULL) + break; + } + voxel->NumMips = mip; + + // Fix pivot data for submips, since some tools seem to like to just center these. + for (i = 1; i < mip; ++i) + { + voxel->Mips[i].Pivot = voxel->Mips[i - 1].Pivot / 2; + } + + for (i = 0; i < mip; ++i) + { + if (!CopyVoxelSlabs((kvxslab_t *)voxel->Mips[i].SlabData, slabs[i], voxel->Mips[i].OffsetX[voxel->Mips[i].SizeX])) + { // Invalid slabs encountered. Reject this voxel. + delete voxel; + return NULL; + } + } + + voxel->LumpNum = lumpnum; + voxel->Palette.Resize(768); + memcpy(voxel->Palette.Data(), rawvoxel + voxelsize - 768, 768); + + return voxel; +} + +#if defined __GNUC__ && !defined __clang__ +#pragma GCC pop_options +#endif // __GNUC__ && !__clang__ + +//========================================================================== +// +// +// +//========================================================================== + +FVoxelDef *R_LoadVoxelDef(int lumpnum, int spin) +{ + FVoxel *vox = R_LoadKVX(lumpnum); + if (vox == NULL) + { + Printf("%s is not a valid voxel file\n", fileSystem.GetFileFullName(lumpnum)); + return NULL; + } + else + { + FVoxelDef *voxdef = new FVoxelDef; + voxdef->Voxel = vox; + voxdef->Scale = 1.; + voxdef->DroppedSpin = voxdef->PlacedSpin = spin; + voxdef->AngleOffset = 90.; + + Voxels.Push(vox); + VoxelDefs.Push(voxdef); + return voxdef; + } +} + +//========================================================================== +// +// FVoxelMipLevel Constructor +// +//========================================================================== + +FVoxelMipLevel::FVoxelMipLevel() +{ + SizeZ = SizeY = SizeX = 0; + Pivot.Zero(); + OffsetX = NULL; + OffsetXY = NULL; + SlabData = NULL; +} + +//========================================================================== +// +// FVoxelMipLevel Destructor +// +//========================================================================== + +FVoxelMipLevel::~FVoxelMipLevel() +{ + if (OffsetX != NULL) + { + delete[] OffsetX; + } +} + +//========================================================================== +// +// FVoxelMipLevel :: GetSlabData +// +//========================================================================== + +uint8_t *FVoxelMipLevel::GetSlabData(bool wantremapped) const +{ + if (wantremapped && SlabDataRemapped.Size() > 0) return &SlabDataRemapped[0]; + return SlabData; +} + +//========================================================================== +// +// Create true color version of the slab data +// +//========================================================================== + +void FVoxel::CreateBgraSlabData() +{ + if (Bgramade) return; + Bgramade = true; + for (int i = 0; i < NumMips; ++i) + { + int size = Mips[i].OffsetX[Mips[i].SizeX]; + if (size <= 0) continue; + + Mips[i].SlabDataBgra.Resize(size); + + kvxslab_t *src = (kvxslab_t*)Mips[i].SlabData; + kvxslab_bgra_t *dest = (kvxslab_bgra_t*)&Mips[i].SlabDataBgra[0]; + + while (size >= 3) + { + dest->backfacecull = src->backfacecull; + dest->ztop = src->ztop; + dest->zleng = src->zleng; + + int slabzleng = src->zleng; + for (int j = 0; j < slabzleng; ++j) + { + int colorIndex = src->col[j]; + + uint32_t red, green, blue; + if (Palette.Size()) + { + red = (Palette[colorIndex * 3 + 0] << 2) | (Palette[colorIndex * 3 + 0] >> 4); + green = (Palette[colorIndex * 3 + 1] << 2) | (Palette[colorIndex * 3 + 1] >> 4); + blue = (Palette[colorIndex * 3 + 2] << 2) | (Palette[colorIndex * 3 + 2] >> 4); + } + else + { + red = GPalette.BaseColors[colorIndex].r; + green = GPalette.BaseColors[colorIndex].g; + blue = GPalette.BaseColors[colorIndex].b; + } + + dest->col[j] = 0xff000000 | (red << 16) | (green << 8) | blue; + } + slabzleng += 3; + + dest = (kvxslab_bgra_t *)((uint32_t *)dest + slabzleng); + src = (kvxslab_t *)((uint8_t *)src + slabzleng); + size -= slabzleng; + } + } +} + +//========================================================================== +// +// Remap the voxel to the game palette +// +//========================================================================== + +void FVoxel::Remap() +{ + if (Remapped) return; + Remapped = true; + if (Palette.Size()) + { + uint8_t *remap = GetVoxelRemap(Palette.Data()); + for (int i = 0; i < NumMips; ++i) + { + int size = Mips[i].OffsetX[Mips[i].SizeX]; + if (size <= 0) continue; + + Mips[i].SlabDataRemapped.Resize(size); + memcpy(&Mips[i].SlabDataRemapped [0], Mips[i].SlabData, size); + RemapVoxelSlabs((kvxslab_t *)&Mips[i].SlabDataRemapped[0], Mips[i].OffsetX[Mips[i].SizeX], remap); + } + } +} + +//========================================================================== +// +// Delete the voxel's built-in palette +// +//========================================================================== + +void FVoxel::RemovePalette() +{ + Palette.Reset(); +} + + +//========================================================================== +// +// VOX_GetVoxel +// +// Returns a voxel object for the given lump or NULL if it is not a valid +// voxel. If the voxel has already been loaded, it will be reused. +// +//========================================================================== + +FVoxel* VOX_GetVoxel(int lumpnum) +{ + // Is this voxel already loaded? If so, return it. + for (unsigned i = 0; i < Voxels.Size(); ++i) + { + if (Voxels[i]->LumpNum == lumpnum) + { + return Voxels[i]; + } + } + FVoxel* vox = R_LoadKVX(lumpnum); + if (vox != NULL) + { + Voxels.Push(vox); + } + return vox; +} + + diff --git a/source/common/models/voxels.h b/source/common/models/voxels.h new file mode 100644 index 000000000..b4a1cdfcd --- /dev/null +++ b/source/common/models/voxels.h @@ -0,0 +1,83 @@ +#ifndef __RES_VOXEL_H +#define __RES_VOXEL_H + +#include +// [RH] Voxels from Build + +#define MAXVOXMIPS 5 + +struct kvxslab_t +{ + uint8_t ztop; // starting z coordinate of top of slab + uint8_t zleng; // # of bytes in the color array - slab height + uint8_t backfacecull; // low 6 bits tell which of 6 faces are exposed + uint8_t col[1/*zleng*/];// color data from top to bottom +}; + +struct kvxslab_bgra_t +{ + uint32_t ztop; // starting z coordinate of top of slab + uint32_t zleng; // # of bytes in the color array - slab height + uint32_t backfacecull; // low 6 bits tell which of 6 faces are exposed + uint32_t col[1/*zleng*/];// color data from top to bottom +}; + +struct FVoxel; + +struct FVoxelMipLevel +{ + FVoxelMipLevel(); + ~FVoxelMipLevel(); + + int SizeX; + int SizeY; + int SizeZ; + DVector3 Pivot; + int *OffsetX; + short *OffsetXY; +private: + uint8_t *SlabData; + TArray SlabDataRemapped; +public: + TArray SlabDataBgra; + + uint8_t *GetSlabData(bool wantpaletted) const; + + friend FVoxel *R_LoadKVX(int lumpnum); + friend struct FVoxel; +}; + +struct FVoxel +{ + TArray Palette; + int LumpNum; + int NumMips; + int VoxelIndex; + FVoxelMipLevel Mips[MAXVOXMIPS]; + bool Remapped = false; + bool Bgramade = false; + + void CreateBgraSlabData(); + void Remap(); + void RemovePalette(); +}; + +struct FVoxelDef +{ + FVoxel *Voxel; + int PlacedSpin; // degrees/sec to spin actors without MF_DROPPED set + int DroppedSpin; // degrees/sec to spin actors with MF_DROPPED set + int VoxeldefIndex; // Needed by GZDoom + double Scale; + DAngle AngleOffset;// added to actor's angle to compensate for wrong-facing voxels +}; + +extern TDeletingArray Voxels; // used only to auto-delete voxels on exit. +extern TDeletingArray VoxelDefs; + +FVoxel* VOX_GetVoxel(int lumpnum); + +FVoxel *R_LoadKVX(int lumpnum); +FVoxelDef *R_LoadVoxelDef(int lumpnum, int spin); + +#endif diff --git a/source/common/rendering/gl/gl_postprocess.cpp b/source/common/rendering/gl/gl_postprocess.cpp index 283d6a4a6..c4ad56f49 100644 --- a/source/common/rendering/gl/gl_postprocess.cpp +++ b/source/common/rendering/gl/gl_postprocess.cpp @@ -35,6 +35,7 @@ #include "v_video.h" #include "templates.h" #include "hw_vrmodes.h" +#include "v_draw.h" extern bool vid_hdr_active; @@ -123,7 +124,7 @@ void FGLRenderer::Flush() if (eyeCount - eye_ix > 1) mBuffers->NextEye(eyeCount); } - screen->Clear2D(); + twod->Clear(); FGLPostProcessState savedState; FGLDebug::PushGroup("PresentEyes"); @@ -143,7 +144,7 @@ void FGLRenderer::Flush() void FGLRenderer::CopyToBackbuffer(const IntRect *bounds, bool applyGamma) { screen->Draw2D(); // draw all pending 2D stuff before copying the buffer - screen->Clear2D(); + twod->Clear(); GLPPRenderState renderstate(mBuffers); hw_postprocess.customShaders.Run(&renderstate, "screen"); diff --git a/source/common/rendering/gl/gl_renderstate.cpp b/source/common/rendering/gl/gl_renderstate.cpp index 7751dee52..df55f88bc 100644 --- a/source/common/rendering/gl/gl_renderstate.cpp +++ b/source/common/rendering/gl/gl_renderstate.cpp @@ -549,7 +549,7 @@ void FGLRenderState::ClearScreen() { bool multi = !!glIsEnabled(GL_MULTISAMPLE); - //screen->mViewpoints->Set2D(*this, SCREENWIDTH, SCREENHEIGHT); + screen->mViewpoints->Set2D(*this, SCREENWIDTH, SCREENHEIGHT); SetColor(0, 0, 0); Apply(); diff --git a/source/common/rendering/gl/gl_shader.cpp b/source/common/rendering/gl/gl_shader.cpp index e623cc279..4bf727efd 100644 --- a/source/common/rendering/gl/gl_shader.cpp +++ b/source/common/rendering/gl/gl_shader.cpp @@ -35,8 +35,8 @@ #include "md5.h" #include "gl_shader.h" #include "hw_shaderpatcher.h" -#include "hwrenderer/data/shaderuniforms.h" -#include "hwrenderer/scene/hw_viewpointuniforms.h" +#include "shaderuniforms.h" +#include "hw_viewpointuniforms.h" #include "hw_lightbuffer.h" #include "i_specialpaths.h" #include "printf.h" diff --git a/source/common/rendering/hwrenderer/data/hw_vrmodes.cpp b/source/common/rendering/hwrenderer/data/hw_vrmodes.cpp index a105f48c2..17bfefaef 100644 --- a/source/common/rendering/hwrenderer/data/hw_vrmodes.cpp +++ b/source/common/rendering/hwrenderer/data/hw_vrmodes.cpp @@ -30,6 +30,7 @@ #include "hw_vrmodes.h" #include "v_video.h" #include "version.h" +#include "i_interface.h" // Set up 3D-specific console variables: CVAR(Int, vr_mode, 0, CVAR_GLOBALCONFIG) @@ -60,7 +61,9 @@ static VRMode vrmi_checker = { 2, isqrt2, isqrt2, 1.f,{ { -.5f, 1.f },{ .5f, 1.f const VRMode *VRMode::GetVRMode(bool toscreen) { #ifdef VR3D_ENABLED - switch (toscreen && vid_rendermode == 4 ? vr_mode : 0) + int mode = !toscreen || (sysCallbacks && sysCallbacks->DisableTextureFilter && sysCallbacks->DisableTextureFilter()) ? 0 : vr_mode; + + switch (mode) { default: case VR_MONO: diff --git a/source/common/rendering/r_videoscale.cpp b/source/common/rendering/r_videoscale.cpp index 236493fb1..dee86723e 100644 --- a/source/common/rendering/r_videoscale.cpp +++ b/source/common/rendering/r_videoscale.cpp @@ -40,12 +40,11 @@ #include "v_draw.h" #include "i_interface.h" #include "printf.h" +#include "version.h" #define NUMSCALEMODES countof(vScaleTable) extern bool setsizeneeded; -EXTERN_CVAR(Int, vid_aspect) - CUSTOM_CVAR(Int, vid_scale_customwidth, VID_MIN_WIDTH, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) { if (self < VID_MIN_WIDTH) diff --git a/source/common/textures/texture.cpp b/source/common/textures/texture.cpp index bddd16c23..20a8d9776 100644 --- a/source/common/textures/texture.cpp +++ b/source/common/textures/texture.cpp @@ -49,6 +49,7 @@ #include "texturemanager.h" #include "c_cvars.h" #include "imagehelpers.h" +#include "v_video.h" // Wrappers to keep the definitions of these classes out of here. IHardwareTexture* CreateHardwareTexture(int numchannels); @@ -531,7 +532,7 @@ IHardwareTexture* FTexture::GetHardwareTexture(int translation, int scaleflags) IHardwareTexture* hwtex = SystemTextures.GetHardwareTexture(translation, scaleflags); if (hwtex == nullptr) { - hwtex = CreateHardwareTexture(4); + hwtex = screen->CreateHardwareTexture(4); SystemTextures.AddHardwareTexture(translation, scaleflags, hwtex); } return hwtex; @@ -552,7 +553,7 @@ FWrapperTexture::FWrapperTexture(int w, int h, int bits) Height = h; Format = bits; //bNoCompress = true; - auto hwtex = CreateHardwareTexture(4); + auto hwtex = screen->CreateHardwareTexture(4); // todo: Initialize here. SystemTextures.AddHardwareTexture(0, false, hwtex); } diff --git a/source/common/utility/m_bbox.cpp b/source/common/utility/m_bbox.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/source/core/gameconfigfile.cpp b/source/core/gameconfigfile.cpp index 6cfbe602e..b532b2925 100644 --- a/source/core/gameconfigfile.cpp +++ b/source/core/gameconfigfile.cpp @@ -263,7 +263,7 @@ void FGameConfigFile::DoGameSetup (const char *gamename) const char *key; const char *value; - sublen = countof(section) - 1 - snprintf (section, countof(section), "%s.", gamename); + sublen = countof(section) - 1 - mysnprintf (section, countof(section), "%s.", gamename); subsection = section + countof(section) - sublen - 1; section[countof(section) - 1] = '\0'; @@ -314,14 +314,14 @@ void FGameConfigFile::DoKeySetup(const char *gamename) { "AutomapBindings", &AutomapBindings }, { NULL, NULL } }; + const char *key, *value; - sublen = countof(section) - 1 - snprintf(section, countof(section), "%s.", gamename); + sublen = countof(section) - 1 - mysnprintf(section, countof(section), "%s.", gamename); subsection = section + countof(section) - sublen - 1; section[countof(section) - 1] = '\0'; C_SetDefaultBindings (); - const char* key, * value; for (int i = 0; binders[i].label != NULL; ++i) { strncpy(subsection, binders[i].label, sublen); @@ -372,7 +372,7 @@ void FGameConfigFile::ArchiveGameData (const char *gamename) { char section[32*3], *subsection; - sublen = countof(section) - 1 - snprintf (section, countof(section), "%s.", gamename); + sublen = countof(section) - 1 - mysnprintf (section, countof(section), "%s.", gamename); subsection = section + countof(section) - 1 - sublen; strncpy (subsection, "Player", sublen); @@ -425,7 +425,6 @@ void FGameConfigFile::ArchiveGameData (const char *gamename) strncpy (subsection, "AutomapBindings", sublen); SetSection (section, true); AutomapBindings.ArchiveBindings (this); - } void FGameConfigFile::ArchiveGlobalData () diff --git a/source/core/gamecontrol.cpp b/source/core/gamecontrol.cpp index 4b125f648..e269842b6 100644 --- a/source/core/gamecontrol.cpp +++ b/source/core/gamecontrol.cpp @@ -92,7 +92,6 @@ int myconnectindex, numplayers; int connecthead, connectpoint2[MAXMULTIPLAYERS]; int32_t xres = -1, yres = -1, bpp = 0; auto vsnprintfptr = vsnprintf; // This is an inline in Visual Studio but we need an address for it to satisfy the MinGW compiled libraries. -bool setsizeneeded; MapRecord mapList[512]; // Due to how this gets used it needs to be static. EDuke defines 7 episode plus one spare episode with 64 potential levels each and relies on the static array which is freely accessible by scripts. @@ -188,6 +187,11 @@ static bool System_CaptureModeInGame() return true; } +static bool System_DisableTextureFilter() +{ + return hw_useindexedcolortextures; +} + //========================================================================== // // DoomSpecificInfo @@ -473,6 +477,10 @@ int GameMain() System_NetGame, System_WantNativeMouse, System_CaptureModeInGame, + nullptr, + nullptr, + nullptr, + System_DisableTextureFilter, }; sysCallbacks = &syscb; diff --git a/source/core/rendering/gl/renderer/gl_renderer.h b/source/core/rendering/gl/renderer/gl_renderer.h index c731ba116..125e5ea89 100644 --- a/source/core/rendering/gl/renderer/gl_renderer.h +++ b/source/core/rendering/gl/renderer/gl_renderer.h @@ -88,8 +88,6 @@ public: private: - void DrawScene(HWDrawInfo *di, int drawmode); - bool QuadStereoCheckInitialRenderContextState(); void PresentAnaglyph(bool r, bool g, bool b); void PresentSideBySide(); diff --git a/source/core/rendering/gl/system/gl_framebuffer.cpp b/source/core/rendering/gl/system/gl_framebuffer.cpp index 890865b5c..513258cd5 100644 --- a/source/core/rendering/gl/system/gl_framebuffer.cpp +++ b/source/core/rendering/gl/system/gl_framebuffer.cpp @@ -72,6 +72,7 @@ EXTERN_CVAR(Int, gl_ssao) void gl_LoadExtensions(); void gl_PrintStartupLog(); +void DrawRateStuff(); //void Draw2D(F2DDrawer *drawer, FRenderState &state); extern bool vid_hdr_active; @@ -418,11 +419,11 @@ void OpenGLFrameBuffer::Draw2D() GLRenderer->mBuffers->BindCurrentFB(); ::DrawFullscreenBlends(); DrawRateStuff(); - GLInterface.Draw2D(&twodgen); + GLInterface.Draw2D(twod); } } -void OpenGLFrameBuffer::PostProcessScene(int fixedcm, const std::function &afterBloomDrawEndScene2D) +void OpenGLFrameBuffer::PostProcessScene(bool swscene, int fixedcm, const std::function &afterBloomDrawEndScene2D) { GLRenderer->PostProcessScene(fixedcm, afterBloomDrawEndScene2D); } @@ -448,7 +449,7 @@ void videoShowFrame(int32_t w) } OpenGLRenderer::GLRenderer->mBuffers->BlitSceneToTexture(); // Copy the resulting scene to the current post process texture - screen->PostProcessScene(0, []() { + screen->PostProcessScene(false, 0, []() { GLInterface.Draw2D(&twodpsp); // draws the weapon sprites }); screen->Update(); @@ -464,6 +465,6 @@ void videoShowFrame(int32_t w) OpenGLRenderer::GLRenderer->mBuffers->BindSceneFB(false); } twodpsp.Clear(); - twodgen.Clear(); + twod->Clear(); GLInterface.ResetFrame(); } diff --git a/source/core/rendering/gl/system/gl_framebuffer.h b/source/core/rendering/gl/system/gl_framebuffer.h index 830514ba6..9a5793abf 100644 --- a/source/core/rendering/gl/system/gl_framebuffer.h +++ b/source/core/rendering/gl/system/gl_framebuffer.h @@ -50,7 +50,7 @@ public: void SetVSync(bool vsync) override; //void Draw2D() override; - void PostProcessScene(int fixedcm, const std::function &afterBloomDrawEndScene2D) override; + void PostProcessScene(bool swscene, int fixedcm, const std::function &afterBloomDrawEndScene2D) override; bool HWGammaActive = false; // Are we using hardware or software gamma? std::shared_ptr mDebug; // Debug API diff --git a/source/core/rendering/v_framebuffer.cpp b/source/core/rendering/v_framebuffer.cpp index f201dbb9b..2262c5737 100644 --- a/source/core/rendering/v_framebuffer.cpp +++ b/source/core/rendering/v_framebuffer.cpp @@ -46,6 +46,7 @@ #include "i_time.h" #include "v_2ddrawer.h" #include "build.h" +#include "vm.h" #include "../glbackend/glbackend.h" #include "hw_material.h" /* @@ -53,42 +54,18 @@ #include "hwrenderer/utility/hw_clock.h" */ #include "hwrenderer/data/flatvertices.h" +#include "version.h" #include #include CVAR(Bool, gl_scale_viewport, true, CVAR_ARCHIVE); -CVAR(Bool, vid_fps, false, 0) -CVAR(Int, vid_showpalette, 0, 0) -EXTERN_CVAR(Bool, ticker) -EXTERN_CVAR(Float, vid_brightness) -EXTERN_CVAR(Float, vid_contrast) EXTERN_CVAR(Int, vid_maxfps) EXTERN_CVAR(Bool, cl_capfps) EXTERN_CVAR(Int, screenblocks) -//========================================================================== -// -// DCanvas :: CalcGamma -// -//========================================================================== - -void DFrameBuffer::CalcGamma (float gamma, uint8_t gammalookup[256]) -{ - // I found this formula on the web at - // , - // but that page no longer exits. - double invgamma = 1.f / gamma; - int i; - - for (i = 0; i < 256; i++) - { - gammalookup[i] = (uint8_t)(255.0 * pow (i / 255.0, invgamma) + 0.5); - } -} - //========================================================================== // // DFrameBuffer Constructor @@ -101,12 +78,10 @@ void DFrameBuffer::CalcGamma (float gamma, uint8_t gammalookup[256]) DFrameBuffer::DFrameBuffer (int width, int height) { SetSize(width, height); - //mPortalState = new FPortalSceneState; } DFrameBuffer::~DFrameBuffer() { - //delete mPortalState; } void DFrameBuffer::SetSize(int width, int height) @@ -117,62 +92,6 @@ void DFrameBuffer::SetSize(int width, int height) twodpsp.SetSize(Width, Height); } -//========================================================================== -// -// DFrameBuffer :: DrawRateStuff -// -// Draws the fps counter, dot ticker, and palette debug. -// -//========================================================================== - -void DFrameBuffer::DrawRateStuff () -{ - // Draws frame time and cumulative fps - if (vid_fps) - { - FString fpsbuff = gi->statFPS(); - - int textScale = active_con_scale(twod); - int rate_x = Width / textScale - NewConsoleFont->StringWidth(&fpsbuff[0]); - twod->AddColorOnlyQuad(rate_x * textScale, 0, Width, NewConsoleFont->GetHeight() * textScale, MAKEARGB(255,0,0,0)); - DrawText (twod, NewConsoleFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0], - DTA_VirtualWidth, screen->GetWidth() / textScale, - DTA_VirtualHeight, screen->GetHeight() / textScale, - DTA_KeepRatio, true, TAG_DONE); - - #if 0 - uint64_t ms = screen->FrameTime; - uint64_t howlong = ms - LastMS; - if ((signed)howlong >= 0) - { - char fpsbuff[40]; - int chars; - int rate_x; - - int textScale = active_con_scale(); - - chars = snprintf (fpsbuff, countof(fpsbuff), "%2llu ms (%3llu fps)", (unsigned long long)howlong, (unsigned long long)LastCount); - rate_x = Width / textScale - NewConsoleFont->StringWidth(&fpsbuff[0]); - twod->AddColorOnlyQuad(rate_x * textScale, 0, Width, NewConsoleFont->GetHeight() * textScale, 0); - DrawText (twod, NewConsoleFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0], - DTA_VirtualWidth, screen->GetWidth() / textScale, - DTA_VirtualHeight, screen->GetHeight() / textScale, - DTA_KeepRatio, true, TAG_DONE); - - uint32_t thisSec = (uint32_t)(ms/1000); - if (LastSec < thisSec) - { - LastCount = FrameCount / (thisSec - LastSec); - LastSec = thisSec; - FrameCount = 0; - } - FrameCount++; - } - LastMS = ms; - #endif - } -} - //========================================================================== // // Palette stuff. @@ -197,6 +116,15 @@ void DFrameBuffer::Update() } } +void DFrameBuffer::SetClearColor(int color) +{ + PalEntry pe = GPalette.BaseColors[color]; + mSceneClearColor[0] = pe.r / 255.f; + mSceneClearColor[1] = pe.g / 255.f; + mSceneClearColor[2] = pe.b / 255.f; + mSceneClearColor[3] = 1.f; +} + //========================================================================== // // DFrameBuffer :: SetVSync @@ -238,16 +166,6 @@ FTexture *DFrameBuffer::WipeEndScreen() return nullptr; } -//========================================================================== -// -// -// -//========================================================================== -void DFrameBuffer::WriteSavePic(FileWriter *file, int width, int height) -{ -} - - //========================================================================== // // Calculates the viewport values needed for 2D and 3D operations @@ -258,7 +176,7 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds) { if (bounds) { - //mSceneViewport = *bounds; + mSceneViewport = *bounds; mScreenViewport = *bounds; mOutputLetterbox = *bounds; return; @@ -382,28 +300,31 @@ FMaterial* DFrameBuffer::CreateMaterial(FGameTexture* tex, int scaleflags) return new FMaterial(tex, scaleflags); } -void DFrameBuffer::BeginScene() + +//========================================================================== +// +// ZScript wrappers for inlines +// +//========================================================================== + +DEFINE_ACTION_FUNCTION(_Screen, GetWidth) { - if (videoGetRenderMode() < REND_POLYMOST) return; - assert(BufferLock >= 0); - if (BufferLock++ == 0) - { - mVertexData->Map(); - } + PARAM_PROLOGUE; + ACTION_RETURN_INT(screen->GetWidth()); } -void DFrameBuffer::FinishScene() +DEFINE_ACTION_FUNCTION(_Screen, GetHeight) { - if (videoGetRenderMode() < REND_POLYMOST) return; - assert(BufferLock > 0); - if (--BufferLock == 0) - { - mVertexData->Unmap(); - GLInterface.DoDraw(); - } + PARAM_PROLOGUE; + ACTION_RETURN_INT(screen->GetHeight()); } -IHardwareTexture* CreateHardwareTexture(int numchannels) +DEFINE_ACTION_FUNCTION(_Screen, PaletteColor) { - return screen->CreateHardwareTexture(numchannels); + PARAM_PROLOGUE; + PARAM_INT(index); + if (index < 0 || index > 255) index = 0; + else index = GPalette.BaseColors[index]; + ACTION_RETURN_INT(index); } + diff --git a/source/core/rendering/v_video.cpp b/source/core/rendering/v_video.cpp index 8a278c896..978a9b9fc 100644 --- a/source/core/rendering/v_video.cpp +++ b/source/core/rendering/v_video.cpp @@ -174,6 +174,7 @@ CUSTOM_CVAR (Bool, vid_vsync, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // [RH] Set true when vid_setmode command has been executed bool setmodeneeded = false; +bool setsizeneeded = false; //========================================================================== // diff --git a/source/core/rendering/v_video.h b/source/core/rendering/v_video.h index 323764e00..336262e77 100644 --- a/source/core/rendering/v_video.h +++ b/source/core/rendering/v_video.h @@ -44,8 +44,6 @@ #include "intrect.h" #include "hw_shadowmap.h" -static const int VID_MIN_WIDTH = 640; -static const int VID_MIN_HEIGHT = 400; struct sector_t; struct FPortalSceneState; @@ -82,7 +80,6 @@ extern int DisplayWidth, DisplayHeight; void V_UpdateModeSize (int width, int height); void V_OutputResized (int width, int height); -EXTERN_CVAR(Int, vid_rendermode) EXTERN_CVAR(Bool, vid_fullscreen) EXTERN_CVAR(Int, win_x) EXTERN_CVAR(Int, win_y) @@ -90,28 +87,6 @@ EXTERN_CVAR(Int, win_w) EXTERN_CVAR(Int, win_h) EXTERN_CVAR(Bool, win_maximized) - -inline bool V_IsHardwareRenderer() -{ - return vid_rendermode == 4; -} - -inline bool V_IsSoftwareRenderer() -{ - return vid_rendermode < 2; -} - -inline bool V_IsPolyRenderer() -{ - return vid_rendermode == 2 || vid_rendermode == 3; -} - -inline bool V_IsTrueColor() -{ - return vid_rendermode == 1 || vid_rendermode == 3 || vid_rendermode == 4; -} - - struct FColormap; class FileWriter; enum FTextureFormat : uint32_t; @@ -152,11 +127,10 @@ class DFrameBuffer { protected: - F2DDrawer m2DDrawer; + //F2DDrawer m2DDrawer; private: int Width = 0; int Height = 0; - int BufferLock = 0; public: // Hardware render state that needs to be exposed to the API independent part of the renderer. For ease of access this is stored in the base class. @@ -167,10 +141,9 @@ public: unsigned int uniformblockalignment = 256; // Hardware dependent uniform buffer alignment. unsigned int maxuniformblock = 65536; const char *vendorstring; // We have to account for some issues with particular vendors. - //FPortalSceneState *mPortalState; // global portal state. - //FSkyVertexBuffer *mSkyData = nullptr; // the sky vertex buffer + FSkyVertexBuffer *mSkyData = nullptr; // the sky vertex buffer FFlatVertexBuffer *mVertexData = nullptr; // Global vertex data - //HWViewpointBuffer *mViewpoints = nullptr; // Viewpoint render data. + HWViewpointBuffer *mViewpoints = nullptr; // Viewpoint render data. FLightBuffer *mLights = nullptr; // Dynamic lights IShadowMap mShadowMap; @@ -246,23 +219,6 @@ public: virtual IDataBuffer *CreateDataBuffer(int bindingpoint, bool ssbo, bool needsresize) { return nullptr; } bool BuffersArePersistent() { return !!(hwcaps & RFL_BUFFER_STORAGE); } - // Begin/End 2D drawing operations. - void Begin2D() - { - m2DDrawer.Begin(Width, Height); - } - void End2D() { m2DDrawer.End(); } - - void BeginScene(); - void FinishScene(); - - void End2DAndUpdate() - { - DrawRateStuff(); - m2DDrawer.End(); - Update(); - } - // This is overridable in case Vulkan does it differently. virtual bool RenderTextureIsFlipped() const { @@ -270,25 +226,31 @@ public: } // Report a game restart + void SetClearColor(int color); virtual int Backend() { return 0; } virtual const char* DeviceName() const { return "Unknown"; } - virtual void WriteSavePic(FileWriter *file, int width, int height); + virtual void AmbientOccludeScene(float m5) {} + virtual void FirstEye() {} + virtual void NextEye(int eyecount) {} + virtual void SetSceneRenderTarget(bool useSSAO) {} + virtual void UpdateShadowMap() {} + virtual void WaitForCommands(bool finish) {} + virtual void SetSaveBuffers(bool yes) {} + virtual void ImageTransitionScene(bool unknown) {} + virtual void CopyScreenToBuffer(int width, int height, uint8_t* buffer) { memset(buffer, 0, width* height); } + virtual bool FlipSavePic() const { return false; } + virtual void RenderTextureView(FCanvasTexture* tex, std::function renderFunc) {} + virtual void SetActiveRenderTarget() {} // Screen wiping virtual FTexture *WipeStartScreen(); virtual FTexture *WipeEndScreen(); - virtual void PostProcessScene(int fixedcm, const std::function &afterBloomDrawEndScene2D) { if (afterBloomDrawEndScene2D) afterBloomDrawEndScene2D(); } + virtual void PostProcessScene(bool swscene, int fixedcm, const std::function &afterBloomDrawEndScene2D) { if (afterBloomDrawEndScene2D) afterBloomDrawEndScene2D(); } void ScaleCoordsFromWindow(int16_t &x, int16_t &y); - uint64_t GetLastFPS() const { return LastCount; } - virtual void Draw2D() {} - void Clear2D() {} - - // Calculate gamma table - void CalcGamma(float gamma, uint8_t gammalookup[256]); virtual void SetViewportRects(IntRect *bounds); int ScreenToWindowX(int x); @@ -307,14 +269,9 @@ public: // The original size of the framebuffer as selected in the video menu. uint64_t FrameTime = 0; -protected: - void DrawRateStuff (); - private: uint64_t fpsLimitTime = 0; - uint64_t LastMS = 0, LastSec = 0, FrameCount = 0, LastCount = 0, LastTic = 0; - bool isIn2D = false; }; @@ -339,9 +296,6 @@ void V_Init2 (); void V_Shutdown (); inline bool IsRatioWidescreen(int ratio) { return (ratio & 3) != 0; } - -void ScaleWithAspect(int &w, int &h, int Width, int Height); - extern bool setsizeneeded, setmodeneeded; diff --git a/source/core/version.h b/source/core/version.h index ce2dac741..61700266b 100644 --- a/source/core/version.h +++ b/source/core/version.h @@ -86,6 +86,8 @@ const char *GetVersionString(); const int SAVEPICWIDTH = 240; const int SAVEPICHEIGHT = 180; +const int VID_MIN_WIDTH = 640; +const int VID_MIN_HEIGHT = 400; #endif //__VERSION_H__ diff --git a/source/duke3d/src/game.cpp b/source/duke3d/src/game.cpp index 3c8e6916e..186b65d69 100644 --- a/source/duke3d/src/game.cpp +++ b/source/duke3d/src/game.cpp @@ -51,6 +51,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "menu/menu.h" #include "mapinfo.h" #include "rendering/v_video.h" +#include "glbackend/glbackend.h" // Uncomment to prevent anything except mirrors from drawing. It is sensible to // also uncomment ENGINE_CLEAR_SCREEN in build/src/engine_priv.h. @@ -678,7 +679,7 @@ void G_DrawRooms(int32_t playerNum, int32_t smoothRatio) "other values are reserved.\n"); #endif - screen->BeginScene(); + renderBeginScene(); #ifdef LEGACY_ROR G_SE40(smoothRatio); #endif @@ -691,7 +692,7 @@ void G_DrawRooms(int32_t playerNum, int32_t smoothRatio) yax_drawrooms(G_DoSpriteAnimations, pSprite->sectnum, 0, smoothRatio); G_DoSpriteAnimations(pSprite->x, pSprite->y, pSprite->z - ZOFFSET6, fix16_to_int(CAMERA(q16ang)), smoothRatio); renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); } } else @@ -843,7 +844,7 @@ void G_DrawRooms(int32_t playerNum, int32_t smoothRatio) Printf(TEXTCOLOR_RED "ERROR: EVENT_DISPLAYROOMS return value must be 0 or 1, " "other values are reserved.\n"); #endif - screen->BeginScene(); + renderBeginScene(); G_HandleMirror(CAMERA(pos.x), CAMERA(pos.y), CAMERA(pos.z), CAMERA(q16ang), CAMERA(q16horiz), smoothRatio); G_ClearGotMirror(); @@ -873,7 +874,7 @@ void G_DrawRooms(int32_t playerNum, int32_t smoothRatio) #endif renderDrawMasks(); #endif - screen->FinishScene(); + renderFinishScene(); } } diff --git a/source/duke3d/src/gameexec.cpp b/source/duke3d/src/gameexec.cpp index 6147b2426..0200ad024 100644 --- a/source/duke3d/src/gameexec.cpp +++ b/source/duke3d/src/gameexec.cpp @@ -47,6 +47,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "colormatcher.h" #include "debugbreak.h" +#include "glbackend/glbackend.h" FString C_CON_GetBoundKeyForLastInput(int gameFunc); const char* C_CON_GetButtonFunc(int num); @@ -1217,7 +1218,7 @@ LUNATIC_EXTERN void G_ShowView(vec3_t vec, fix16_t a, fix16_t horiz, int sect, i renderSetAspect(viewingRange, yxAspect); int const smoothratio = calc_smoothratio(totalclock, ototalclock); G_DoInterpolations(smoothratio); - screen->BeginScene(); + renderBeginScene(); if (!display_mirror) G_HandleMirror(vec.x, vec.y, vec.z, a, horiz, smoothratio); #ifdef POLYMER @@ -1232,7 +1233,7 @@ LUNATIC_EXTERN void G_ShowView(vec3_t vec, fix16_t a, fix16_t horiz, int sect, i G_DoSpriteAnimations(vec.x, vec.y, vec.z, fix16_to_int(a), smoothratio); display_mirror = 0; renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); G_RestoreInterpolations(); G_UpdateScreenArea(); renderSetAspect(viewingRange, yxAspect); diff --git a/source/duke3d/src/sector.cpp b/source/duke3d/src/sector.cpp index 158eb07d0..ccf9107ba 100644 --- a/source/duke3d/src/sector.cpp +++ b/source/duke3d/src/sector.cpp @@ -29,6 +29,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "printf.h" #include "secrets.h" #include "v_video.h" +#include "glbackend/glbackend.h" BEGIN_DUKE_NS @@ -400,7 +401,7 @@ static void G_SetupCamTile(int spriteNum, int tileNum, int smoothRatio) Printf(TEXTCOLOR_RED "ERROR: EVENT_DISPLAYROOMSCAMERATILE return value must be 0 or 1, " "other values are reserved.\n"); #endif - screen->BeginScene(); + renderBeginScene(); yax_preparedrawrooms(); drawrooms(camera.x, camera.y, camera.z, SA(spriteNum), 100 + sprite[spriteNum].shade, SECT(spriteNum)); @@ -410,7 +411,7 @@ static void G_SetupCamTile(int spriteNum, int tileNum, int smoothRatio) G_DoSpriteAnimations(camera.x, camera.y, camera.z, SA(spriteNum), smoothRatio); display_mirror = saveMirror; renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); finishTileSetup: renderRestoreTarget(); diff --git a/source/exhumed/src/view.cpp b/source/exhumed/src/view.cpp index 2d53fbce5..53273ff53 100644 --- a/source/exhumed/src/view.cpp +++ b/source/exhumed/src/view.cpp @@ -37,6 +37,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "trigdat.h" #include "runlist.h" #include "v_video.h" +#include "glbackend/glbackend.h" #include BEGIN_PS_NS @@ -493,11 +494,11 @@ void DrawView(int smoothRatio, bool sceneonly) } } - screen->BeginScene(); + renderBeginScene(); renderDrawRoomsQ16(nCamerax, nCameray, viewz, nCameraa, nCamerapan, nSector); analyzesprites(); renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); if (HavePLURemap()) { diff --git a/source/glbackend/glbackend.cpp b/source/glbackend/glbackend.cpp index 74b5282bd..9b005e9c7 100644 --- a/source/glbackend/glbackend.cpp +++ b/source/glbackend/glbackend.cpp @@ -33,6 +33,7 @@ ** */ #include +#include #include "gl_load.h" #include "glbackend.h" #include "gl_samplers.h" @@ -50,6 +51,8 @@ #include "flatvertices.h" #include "gl_renderer.h" #include "build.h" +#include "v_draw.h" +#include "v_font.h" float shadediv[MAXPALOOKUPS]; @@ -555,7 +558,7 @@ void WriteSavePic(FileWriter* file, int width, int height) videoSetViewableArea(oldwindowxy1.x, oldwindowxy1.y, oldwindowxy2.x, oldwindowxy2.y); // The 2D drawers can contain some garbage from the dirty render setup. Get rid of that first. - twodgen.Clear(); + twod->Clear(); twodpsp.Clear(); OpenGLRenderer::GLRenderer->CopyToBackbuffer(&bounds, false); @@ -580,3 +583,53 @@ void WriteSavePic(FileWriter* file, int width, int height) } +static int BufferLock = 0; + +void renderBeginScene() +{ + if (videoGetRenderMode() < REND_POLYMOST) return; + assert(BufferLock >= 0); + if (BufferLock++ == 0) + { + screen->mVertexData->Map(); + } +} + +void renderFinishScene() +{ + if (videoGetRenderMode() < REND_POLYMOST) return; + assert(BufferLock > 0); + if (--BufferLock == 0) + { + screen->mVertexData->Unmap(); + GLInterface.DoDraw(); + } +} + +//========================================================================== +// +// DFrameBuffer :: DrawRateStuff +// +// Draws the fps counter, dot ticker, and palette debug. +// +//========================================================================== +CVAR(Bool, vid_fps, false, 0) + +void DrawRateStuff() +{ + // Draws frame time and cumulative fps + if (vid_fps) + { + FString fpsbuff = gi->statFPS(); + + int textScale = active_con_scale(twod); + int rate_x = screen->GetWidth() / textScale - NewConsoleFont->StringWidth(&fpsbuff[0]); + twod->AddColorOnlyQuad(rate_x * textScale, 0, screen->GetWidth(), NewConsoleFont->GetHeight() * textScale, MAKEARGB(255, 0, 0, 0)); + DrawText(twod, NewConsoleFont, CR_WHITE, rate_x, 0, (char*)&fpsbuff[0], + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); + + } +} + diff --git a/source/glbackend/glbackend.h b/source/glbackend/glbackend.h index 864228791..cfc613280 100644 --- a/source/glbackend/glbackend.h +++ b/source/glbackend/glbackend.h @@ -514,3 +514,7 @@ public: }; extern GLInstance GLInterface; + +void renderBeginScene(); +void renderFinishScene(); +void DrawRateStuff(); diff --git a/source/rr/src/game.cpp b/source/rr/src/game.cpp index 6fd71ee1b..b2e48e747 100644 --- a/source/rr/src/game.cpp +++ b/source/rr/src/game.cpp @@ -48,6 +48,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "c_dispatch.h" #include "mapinfo.h" #include "rendering/v_video.h" +#include "glbackend/glbackend.h" #include "playmve.h" // Uncomment to prevent anything except mirrors from drawing. It is sensible to @@ -1132,7 +1133,7 @@ void G_DrawRooms(int32_t playerNum, int32_t smoothRatio) CAMERA(q16horiz) = fix16_clamp(CAMERA(q16horiz), F16(HORIZ_MIN), F16(HORIZ_MAX)); - screen->BeginScene(); + renderBeginScene(); G_HandleMirror(CAMERA(pos.x), CAMERA(pos.y), CAMERA(pos.z), CAMERA(q16ang), CAMERA(q16horiz), smoothRatio); #ifdef LEGACY_ROR if (!RR) @@ -1262,7 +1263,7 @@ void G_DrawRooms(int32_t playerNum, int32_t smoothRatio) #endif renderDrawMasks(); #endif - screen->FinishScene(); + renderFinishScene(); } G_RestoreInterpolations(); diff --git a/source/rr/src/sector.cpp b/source/rr/src/sector.cpp index 806a080b3..dcb1ae2f8 100644 --- a/source/rr/src/sector.cpp +++ b/source/rr/src/sector.cpp @@ -27,6 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "secrets.h" #include "v_video.h" +#include "glbackend/glbackend.h" BEGIN_RR_NS @@ -506,7 +507,7 @@ static void G_SetupCamTile(int spriteNum, int tileNum, int smoothRatio) int const saveMirror = display_mirror; renderSetTarget(tileNum, tilesiz[tileNum].y, tilesiz[tileNum].x); - screen->BeginScene(); + renderBeginScene(); yax_preparedrawrooms(); drawrooms(camera.x, camera.y, camera.z, SA(spriteNum), 100 + sprite[spriteNum].shade, SECT(spriteNum)); @@ -516,7 +517,7 @@ static void G_SetupCamTile(int spriteNum, int tileNum, int smoothRatio) G_DoSpriteAnimations(camera.x, camera.y, camera.z, SA(spriteNum), smoothRatio); display_mirror = saveMirror; renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); renderRestoreTarget(); } diff --git a/source/sw/src/draw.cpp b/source/sw/src/draw.cpp index 9b4d9e25a..fdbd5796e 100644 --- a/source/sw/src/draw.cpp +++ b/source/sw/src/draw.cpp @@ -60,6 +60,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "swcvar.h" #include "v_2ddrawer.h" #include "v_video.h" +#include "glbackend/glbackend.h" BEGIN_SW_NS @@ -2173,7 +2174,7 @@ drawscreen(PLAYERp pp) JS_DrawCameras(pp, tx, ty, tz); } - screen->BeginScene(); + renderBeginScene(); OverlapDraw = TRUE; DrawOverlapRoom(tx, ty, tz, tq16ang, tq16horiz, tsectnum); OverlapDraw = FALSE; @@ -2193,7 +2194,7 @@ drawscreen(PLAYERp pp) analyzesprites(tx, ty, tz, FALSE); post_analyzesprites(); renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); if (r_usenewaspect) { diff --git a/source/sw/src/jsector.cpp b/source/sw/src/jsector.cpp index 0a5516130..a8255ed2f 100644 --- a/source/sw/src/jsector.cpp +++ b/source/sw/src/jsector.cpp @@ -46,6 +46,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "pal.h" #include "parent.h" #include "v_video.h" +#include "glbackend/glbackend.h" BEGIN_SW_NS @@ -477,12 +478,12 @@ void drawroomstotile(int daposx, int daposy, int daposz, TileFiles.MakeCanvas(tilenume, tilesiz[tilenume].x, tilesiz[tilenume].y); renderSetTarget(tilenume, tilesiz[tilenume].x, tilesiz[tilenume].y); - screen->BeginScene(); + renderBeginScene(); renderDrawRoomsQ16(daposx, daposy, daposz, daq16ang, daq16horiz, dacursectnum); analyzesprites(daposx, daposy, daposz, FALSE); renderDrawMasks(); - screen->FinishScene(); + renderFinishScene(); renderRestoreTarget(); }