From 7308e05ed0b8b1ba005133e77cb001fe3f19a5f9 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 4 Jul 2011 20:22:55 +0000 Subject: [PATCH] - separated all voxel related resource maintenance code from r_things.cpp and r_data.cpp into its own file. SVN r3246 (trunk) --- src/info.cpp | 6 +- src/info.h | 2 +- src/r_data.cpp | 299 ------------------ src/r_data.h | 2 - src/r_defs.h | 49 --- src/r_things.cpp | 272 +---------------- src/r_things.h | 2 + src/resources/voxels.cpp | 633 +++++++++++++++++++++++++++++++++++++++ src/resources/voxels.h | 62 ++++ src/v_video.cpp | 2 +- zdoom.vcproj | 8 + 11 files changed, 716 insertions(+), 621 deletions(-) create mode 100644 src/resources/voxels.cpp create mode 100644 src/resources/voxels.h diff --git a/src/info.cpp b/src/info.cpp index 21e756369a..3a59380cf3 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -61,7 +61,7 @@ extern void ClearStrifeTypes(); // //========================================================================== -int GetSpriteIndex(const char * spritename) +int GetSpriteIndex(const char * spritename, bool add) { static char lastsprite[5]; static int lastindex; @@ -87,6 +87,10 @@ int GetSpriteIndex(const char * spritename) return (lastindex = (int)i); } } + if (!add) + { + return (lastindex = -1); + } spritedef_t temp; strcpy (temp.name, upper); temp.numframes = 0; diff --git a/src/info.h b/src/info.h index c2d4f4cc2e..66d564606a 100644 --- a/src/info.h +++ b/src/info.h @@ -240,7 +240,7 @@ private: extern FDoomEdMap DoomEdMap; -int GetSpriteIndex(const char * spritename); +int GetSpriteIndex(const char * spritename, bool add = true); TArray &MakeStateNameList(const char * fname); void AddStateLight(FState *state, const char *lname); diff --git a/src/r_data.cpp b/src/r_data.cpp index 35225a850a..3e0a1d36a3 100644 --- a/src/r_data.cpp +++ b/src/r_data.cpp @@ -133,305 +133,6 @@ const BYTE *R_GetColumn (FTexture *tex, int col) return tex->GetColumn (col, NULL); } -//========================================================================== -// -// 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 BYTE *GetVoxelRemap(const BYTE *pal) -{ - static BYTE remap[256]; - static BYTE 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 *)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 *)((BYTE *)src + slabzleng); - dest = (kvxslab_t *)((BYTE *)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 BYTE *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 *)((BYTE *)dest + slabzleng); - size -= slabzleng; - } -} - -//========================================================================== -// -// R_LoadKVX -// -//========================================================================== - -FVoxel *R_LoadKVX(int lumpnum) -{ - const kvxslab_t *slabs[MAXVOXMIPS]; - FVoxel *voxel = new FVoxel; - const BYTE *rawmip; - int mip, maxmipsize; - int i, j, n; - - FMemLump lump = Wads.ReadLump(lumpnum); // FMemLump adds an extra 0 byte to the end. - BYTE *rawvoxel = (BYTE *)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->PivotX = GetInt(rawmip + 12); - mipl->PivotY = GetInt(rawmip + 16); - mipl->PivotZ = GetInt(rawmip + 20); - - // 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 empty. - goto nextmip; - } - - // Allocate slab data space. - mipl->OffsetX = new int[(numbytes - 24 + 3) / 4]; - mipl->OffsetXY = (short *)(mipl->OffsetX + mipl->SizeX + 1); - mipl->SlabData = (BYTE *)(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)) - { - goto bad; - } - } - } - - // Record slab location for the end. - slabs[mip] = (kvxslab_t *)(rawmip + 24 + offsetsize); - - // Time for the next mip Level. -nextmip: - 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) - { -bad: 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; - - 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 = new BYTE[768]; - memcpy(voxel->Palette, rawvoxel + voxelsize - 768, 768); - - return voxel; -} - -//========================================================================== -// -// FVoxelMipLevel Constructor -// -//========================================================================== - -FVoxelMipLevel::FVoxelMipLevel() -{ - SizeZ = SizeY = SizeX = 0; - PivotZ = PivotY = PivotX = 0; - OffsetX = NULL; - OffsetXY = NULL; - SlabData = NULL; -} - -//========================================================================== -// -// FVoxelMipLevel Destructor -// -//========================================================================== - -FVoxelMipLevel::~FVoxelMipLevel() -{ - if (OffsetX != NULL) - { - delete[] OffsetX; - } -} - -//========================================================================== -// -// FVoxel Constructor -// -//========================================================================== - -FVoxel::FVoxel() -{ - Palette = NULL; -} - -FVoxel::~FVoxel() -{ - if (Palette != NULL) delete [] Palette; -} - -//========================================================================== -// -// Remap the voxel to the game palette -// -//========================================================================== - -void FVoxel::Remap() -{ - if (Palette != NULL) - { - BYTE *remap = GetVoxelRemap(Palette); - for (int i = 0; i < NumMips; ++i) - { - RemapVoxelSlabs((kvxslab_t *)Mips[i].SlabData, Mips[i].OffsetX[Mips[i].SizeX], remap); - } - delete [] Palette; - Palette = NULL; - } -} - //========================================================================== // // Debug stuff diff --git a/src/r_data.h b/src/r_data.h index d0fabccd20..76a8591f31 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -138,6 +138,4 @@ void R_PrecacheLevel (void); int R_FindSkin (const char *name, int pclass); // [RH] Find a skin -FVoxel *R_LoadKVX(int lumpnum); - #endif diff --git a/src/r_defs.h b/src/r_defs.h index 22b5a04933..6d17b222cb 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1145,55 +1145,6 @@ public: }; -// [RH] Voxels from Build - -#define MAXVOXMIPS 5 - -struct kvxslab_t -{ - BYTE ztop; // starting z coordinate of top of slab - BYTE zleng; // # of bytes in the color array - slab height - BYTE backfacecull; // low 6 bits tell which of 6 faces are exposed - BYTE col[1/*zleng*/];// color data from top to bottom -}; - -struct FVoxelMipLevel -{ - FVoxelMipLevel(); - ~FVoxelMipLevel(); - - int SizeX; - int SizeY; - int SizeZ; - fixed_t PivotX; // 24.8 fixed point - fixed_t PivotY; // "" - fixed_t PivotZ; // "" - int *OffsetX; - short *OffsetXY; - BYTE *SlabData; -}; - -struct FVoxel -{ - int LumpNum; - int NumMips; - BYTE *Palette; - FVoxelMipLevel Mips[MAXVOXMIPS]; - - FVoxel(); - ~FVoxel(); - void Remap(); -}; - -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 - fixed_t Scale; - angle_t AngleOffset; // added to actor's angle to compensate for wrong-facing voxels -}; - // [RH] A c-buffer. Used for keeping track of offscreen voxel spans. struct FCoverageBuffer diff --git a/src/r_things.cpp b/src/r_things.cpp index 86b517626b..9876fe1d48 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -60,6 +60,7 @@ #include "v_palette.h" #include "r_translate.h" #include "resources/colormaps.h" +#include "resources/voxels.h" extern fixed_t globaluclip, globaldclip; @@ -96,9 +97,6 @@ TArray ParticlesInSubsec; short zeroarray[MAXWIDTH]; short screenheightarray[MAXWIDTH]; -#define MAX_SPRITE_FRAMES 29 // [RH] Macro-ized as in BOOM. - - CVAR (Bool, r_drawplayersprites, true, 0) // [RH] Draw player sprites? CVAR (Bool, r_drawvoxels, true, 0) @@ -113,9 +111,6 @@ TArray sprites; TArray SpriteFrames; DWORD NumStdSprites; // The first x sprites that don't belong to skins. -TDeletingArray Voxels; // used only to auto-delete voxels on exit. -TDeletingArray VoxelDefs; - int OffscreenBufferWidth, OffscreenBufferHeight; BYTE *OffscreenColorBuffer; FCoverageBuffer *OffscreenCoverageBuffer; @@ -128,18 +123,6 @@ sprtemp[MAX_SPRITE_FRAMES]; int maxframe; char* spritename; -struct VoxelOptions -{ - VoxelOptions() - : DroppedSpin(0), PlacedSpin(0), Scale(FRACUNIT), AngleOffset(0) - {} - - int DroppedSpin; - int PlacedSpin; - fixed_t Scale; - angle_t AngleOffset; -}; - // [RH] skin globals FPlayerSkin *skins; size_t numskins; @@ -487,23 +470,9 @@ void R_InitSpriteDefs () VHasher *vh = &vhashes[hash]; if (vh->Name == (int)intname) { - FVoxel *vox = R_LoadKVX(hash); - if (vox == NULL) + FVoxelDef *voxdef = R_LoadVoxelDef(hash, vh->Spin); + if (voxdef != NULL) { - Printf("%s is not a valid voxel file\n", Wads.GetLumpFullName(hash)); - } - else - { - FVoxelDef *voxdef = new FVoxelDef; - - voxdef->Voxel = vox; - voxdef->Scale = FRACUNIT; - voxdef->DroppedSpin = voxdef->PlacedSpin = vh->Spin; - voxdef->AngleOffset = 0; - - Voxels.Push(vox); - VoxelDefs.Push(voxdef); - if (vh->Frame == ' ' || vh->Frame == '\0') { // voxel applies to every sprite frame for (j = 0; j < MAX_SPRITE_FRAMES; ++j) @@ -573,163 +542,6 @@ static void R_ExtendSpriteFrames(spritedef_t &spr, int frame) spr.numframes = frame; } -//========================================================================== -// -// VOX_ReadSpriteNames -// -// Reads a list of sprite names from a VOXELDEF lump. -// -//========================================================================== - -static bool VOX_ReadSpriteNames(FScanner &sc, TArray &vsprites) -{ - unsigned int i; - - vsprites.Clear(); - while (sc.GetString()) - { - // A sprite name list is terminated by an '=' character. - if (sc.String[0] == '=') - { - if (vsprites.Size() == 0) - { - sc.ScriptMessage("No sprites specified for voxel.\n"); - } - return true; - } - if (sc.StringLen != 4 && sc.StringLen != 5) - { - sc.ScriptMessage("Sprite name \"%s\" is wrong size.\n", sc.String); - } - else if (sc.StringLen == 5 && (sc.String[4] = toupper(sc.String[4]), sc.String[4] < 'A' || sc.String[4] >= 'A' + MAX_SPRITE_FRAMES)) - { - sc.ScriptMessage("Sprite frame %s is invalid.\n", sc.String[4]); - } - else - { - int frame = (sc.StringLen == 4) ? 255 : sc.String[4] - 'A'; - int spritename; - - for (i = 0; i < 4; ++i) - { - sc.String[i] = toupper(sc.String[i]); - } - spritename = *(int *)sc.String; - for (i = 0; i < sprites.Size(); ++i) - { - if ((int)sprites[i].dwName == spritename) - { - break; - } - } - if (i != sprites.Size()) - { - vsprites.Push((frame << 24) | i); - } - } - } - if (vsprites.Size() != 0) - { - sc.ScriptMessage("Unexpected end of file\n"); - } - return false; -} - -//========================================================================== -// -// VOX_ReadOptions -// -// Reads a list of options from a VOXELDEF lump, terminated with a '}' -// character. The leading '{' must already be consumed -// -//========================================================================== - -static void VOX_ReadOptions(FScanner &sc, VoxelOptions &opts) -{ - while (sc.GetToken()) - { - if (sc.TokenType == '}') - { - return; - } - sc.TokenMustBe(TK_Identifier); - if (sc.Compare("scale")) - { - sc.MustGetToken('='); - sc.MustGetToken(TK_FloatConst); - opts.Scale = FLOAT2FIXED(sc.Float); - } - else if (sc.Compare("spin")) - { - sc.MustGetToken('='); - sc.MustGetToken(TK_IntConst); - opts.DroppedSpin = opts.PlacedSpin = sc.Number; - } - else if (sc.Compare("placedspin")) - { - sc.MustGetToken('='); - sc.MustGetToken(TK_IntConst); - opts.PlacedSpin = sc.Number; - } - else if (sc.Compare("droppedspin")) - { - sc.MustGetToken('='); - sc.MustGetToken(TK_IntConst); - opts.DroppedSpin = sc.Number; - } - else if (sc.Compare("angleoffset")) - { - sc.MustGetToken('='); - sc.MustGetAnyToken(); - if (sc.TokenType == TK_IntConst) - { - sc.Float = sc.Number; - } - else - { - sc.TokenMustBe(TK_FloatConst); - } - opts.AngleOffset = angle_t(sc.Float * ANGLE_180 / 180.0); - } - else - { - sc.ScriptMessage("Unknown voxel option '%s'\n", sc.String); - if (sc.CheckToken('=')) - { - sc.MustGetAnyToken(); - } - } - } - sc.ScriptMessage("Unterminated voxel option block\n"); -} - -//========================================================================== -// -// 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. -// -//========================================================================== - -static 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; -} - //========================================================================== // // VOX_AddVoxel @@ -738,88 +550,12 @@ static FVoxel *VOX_GetVoxel(int lumpnum) // //========================================================================== -static void VOX_AddVoxel(int sprnum, int frame, FVoxelDef *def) +void VOX_AddVoxel(int sprnum, int frame, FVoxelDef *def) { R_ExtendSpriteFrames(sprites[sprnum], frame); SpriteFrames[sprites[sprnum].spriteframes + frame].Voxel = def; } -//========================================================================== -// -// R_InitVoxels -// -// Process VOXELDEF lumps for defining voxel options that cannot be -// condensed neatly into a sprite name format. -// -//========================================================================== - -void R_InitVoxels() -{ - int lump, lastlump = 0; - - while ((lump = Wads.FindLump("VOXELDEF", &lastlump)) != -1) - { - FScanner sc(lump); - TArray vsprites; - - while (VOX_ReadSpriteNames(sc, vsprites)) - { - FVoxel *voxeldata = NULL; - int voxelfile; - VoxelOptions opts; - - sc.SetCMode(true); - sc.MustGetToken(TK_StringConst); - voxelfile = Wads.CheckNumForFullName(sc.String, true, ns_voxels); - if (voxelfile < 0) - { - sc.ScriptMessage("Voxel \"%s\" not found.\n", sc.String); - } - else - { - voxeldata = VOX_GetVoxel(voxelfile); - if (voxeldata == NULL) - { - sc.ScriptMessage("\"%s\" is not a valid voxel file.\n", sc.String); - } - } - if (sc.CheckToken('{')) - { - VOX_ReadOptions(sc, opts); - } - sc.SetCMode(false); - if (voxeldata != NULL && vsprites.Size() != 0) - { - FVoxelDef *def = new FVoxelDef; - - def->Voxel = voxeldata; - def->Scale = opts.Scale; - def->DroppedSpin = opts.DroppedSpin; - def->PlacedSpin = opts.PlacedSpin; - def->AngleOffset = opts.AngleOffset; - VoxelDefs.Push(def); - - for (unsigned i = 0; i < vsprites.Size(); ++i) - { - int sprnum = int(vsprites[i] & 0xFFFFFF); - int frame = int(vsprites[i] >> 24); - if (frame == 255) - { // Apply voxel to all frames. - for (int j = MAX_SPRITE_FRAMES - 1; j >= 0; --j) - { - VOX_AddVoxel(sprnum, j, def); - } - } - else - { // Apply voxel to only one frame. - VOX_AddVoxel(sprnum, frame, def); - } - } - } - } - } -} - // [RH] // R_InitSkins // Reads in everything applicable to a skin. The skins should have already diff --git a/src/r_things.h b/src/r_things.h index da31daf79d..0e345227dc 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -23,6 +23,8 @@ #ifndef __R_THINGS__ #define __R_THINGS__ +#define MAX_SPRITE_FRAMES 29 // [RH] Macro-ized as in BOOM. + // [RH] Particle details struct particle_t { diff --git a/src/resources/voxels.cpp b/src/resources/voxels.cpp new file mode 100644 index 0000000000..15ed679bdd --- /dev/null +++ b/src/resources/voxels.cpp @@ -0,0 +1,633 @@ +/* +** 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 "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "m_argv.h" +#include "i_system.h" +#include "w_wad.h" +#include "r_local.h" +#include "c_console.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "doomstat.h" +#include "v_video.h" +#include "sc_man.h" +#include "s_sound.h" +#include "sbar.h" +#include "gi.h" +#include "r_sky.h" +#include "cmdlib.h" +#include "g_level.h" +#include "d_net.h" +#include "colormatcher.h" +#include "d_netinf.h" +#include "r_bsp.h" +#include "r_plane.h" +#include "r_segs.h" +#include "v_palette.h" +#include "r_translate.h" +#include "resources/colormaps.h" +#include "voxels.h" + +void VOX_AddVoxel(int sprnum, int frame, FVoxelDef *def); + +TDeletingArray Voxels; // used only to auto-delete voxels on exit. +TDeletingArray VoxelDefs; + +struct VoxelOptions +{ + VoxelOptions() + : DroppedSpin(0), PlacedSpin(0), Scale(FRACUNIT), AngleOffset(0) + {} + + int DroppedSpin; + int PlacedSpin; + fixed_t Scale; + angle_t AngleOffset; +}; + +//========================================================================== +// +// 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 BYTE *GetVoxelRemap(const BYTE *pal) +{ + static BYTE remap[256]; + static BYTE 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 *)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 *)((BYTE *)src + slabzleng); + dest = (kvxslab_t *)((BYTE *)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 BYTE *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 *)((BYTE *)dest + slabzleng); + size -= slabzleng; + } +} + +//========================================================================== +// +// R_LoadKVX +// +//========================================================================== + +FVoxel *R_LoadKVX(int lumpnum) +{ + const kvxslab_t *slabs[MAXVOXMIPS]; + FVoxel *voxel = new FVoxel; + const BYTE *rawmip; + int mip, maxmipsize; + int i, j, n; + + FMemLump lump = Wads.ReadLump(lumpnum); // FMemLump adds an extra 0 byte to the end. + BYTE *rawvoxel = (BYTE *)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->PivotX = GetInt(rawmip + 12); + mipl->PivotY = GetInt(rawmip + 16); + mipl->PivotZ = GetInt(rawmip + 20); + + // 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 = (BYTE *)(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; + + 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 = new BYTE[768]; + memcpy(voxel->Palette, rawvoxel + voxelsize - 768, 768); + + return voxel; +} + +//========================================================================== +// +// +// +//========================================================================== + +FVoxelDef *R_LoadVoxelDef(int lumpnum, int spin) +{ + FVoxel *vox = R_LoadKVX(lumpnum); + if (vox == NULL) + { + Printf("%s is not a valid voxel file\n", Wads.GetLumpFullName(lumpnum)); + return NULL; + } + else + { + FVoxelDef *voxdef = new FVoxelDef; + voxdef->Voxel = vox; + voxdef->Scale = FRACUNIT; + voxdef->DroppedSpin = voxdef->PlacedSpin = spin; + voxdef->AngleOffset = 0; + + Voxels.Push(vox); + VoxelDefs.Push(voxdef); + return voxdef; + } +} + +//========================================================================== +// +// FVoxelMipLevel Constructor +// +//========================================================================== + +FVoxelMipLevel::FVoxelMipLevel() +{ + SizeZ = SizeY = SizeX = 0; + PivotZ = PivotY = PivotX = 0; + OffsetX = NULL; + OffsetXY = NULL; + SlabData = NULL; +} + +//========================================================================== +// +// FVoxelMipLevel Destructor +// +//========================================================================== + +FVoxelMipLevel::~FVoxelMipLevel() +{ + if (OffsetX != NULL) + { + delete[] OffsetX; + } +} + +//========================================================================== +// +// FVoxel Constructor +// +//========================================================================== + +FVoxel::FVoxel() +{ + Palette = NULL; +} + +FVoxel::~FVoxel() +{ + if (Palette != NULL) delete [] Palette; +} + +//========================================================================== +// +// Remap the voxel to the game palette +// +//========================================================================== + +void FVoxel::Remap() +{ + if (Palette != NULL) + { + BYTE *remap = GetVoxelRemap(Palette); + for (int i = 0; i < NumMips; ++i) + { + RemapVoxelSlabs((kvxslab_t *)Mips[i].SlabData, Mips[i].OffsetX[Mips[i].SizeX], remap); + } + delete [] Palette; + Palette = NULL; + } +} + + + +//========================================================================== +// +// VOX_ReadSpriteNames +// +// Reads a list of sprite names from a VOXELDEF lump. +// +//========================================================================== + +static bool VOX_ReadSpriteNames(FScanner &sc, TArray &vsprites) +{ + unsigned int i; + + vsprites.Clear(); + while (sc.GetString()) + { + // A sprite name list is terminated by an '=' character. + if (sc.String[0] == '=') + { + if (vsprites.Size() == 0) + { + sc.ScriptMessage("No sprites specified for voxel.\n"); + } + return true; + } + if (sc.StringLen != 4 && sc.StringLen != 5) + { + sc.ScriptMessage("Sprite name \"%s\" is wrong size.\n", sc.String); + } + else if (sc.StringLen == 5 && (sc.String[4] = toupper(sc.String[4]), sc.String[4] < 'A' || sc.String[4] >= 'A' + MAX_SPRITE_FRAMES)) + { + sc.ScriptMessage("Sprite frame %s is invalid.\n", sc.String[4]); + } + else + { + int frame = (sc.StringLen == 4) ? 255 : sc.String[4] - 'A'; + + i = GetSpriteIndex(sc.String, false); + if (i != -1) + { + vsprites.Push((frame << 24) | i); + } + } + } + if (vsprites.Size() != 0) + { + sc.ScriptMessage("Unexpected end of file\n"); + } + return false; +} + +//========================================================================== +// +// VOX_ReadOptions +// +// Reads a list of options from a VOXELDEF lump, terminated with a '}' +// character. The leading '{' must already be consumed +// +//========================================================================== + +static void VOX_ReadOptions(FScanner &sc, VoxelOptions &opts) +{ + while (sc.GetToken()) + { + if (sc.TokenType == '}') + { + return; + } + sc.TokenMustBe(TK_Identifier); + if (sc.Compare("scale")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_FloatConst); + opts.Scale = FLOAT2FIXED(sc.Float); + } + else if (sc.Compare("spin")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_IntConst); + opts.DroppedSpin = opts.PlacedSpin = sc.Number; + } + else if (sc.Compare("placedspin")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_IntConst); + opts.PlacedSpin = sc.Number; + } + else if (sc.Compare("droppedspin")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_IntConst); + opts.DroppedSpin = sc.Number; + } + else if (sc.Compare("angleoffset")) + { + sc.MustGetToken('='); + sc.MustGetAnyToken(); + if (sc.TokenType == TK_IntConst) + { + sc.Float = sc.Number; + } + else + { + sc.TokenMustBe(TK_FloatConst); + } + opts.AngleOffset = angle_t(sc.Float * ANGLE_180 / 180.0); + } + else + { + sc.ScriptMessage("Unknown voxel option '%s'\n", sc.String); + if (sc.CheckToken('=')) + { + sc.MustGetAnyToken(); + } + } + } + sc.ScriptMessage("Unterminated voxel option block\n"); +} + +//========================================================================== +// +// 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. +// +//========================================================================== + +static 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; +} + +//========================================================================== +// +// R_InitVoxels +// +// Process VOXELDEF lumps for defining voxel options that cannot be +// condensed neatly into a sprite name format. +// +//========================================================================== + +void R_InitVoxels() +{ + int lump, lastlump = 0; + + while ((lump = Wads.FindLump("VOXELDEF", &lastlump)) != -1) + { + FScanner sc(lump); + TArray vsprites; + + while (VOX_ReadSpriteNames(sc, vsprites)) + { + FVoxel *voxeldata = NULL; + int voxelfile; + VoxelOptions opts; + + sc.SetCMode(true); + sc.MustGetToken(TK_StringConst); + voxelfile = Wads.CheckNumForFullName(sc.String, true, ns_voxels); + if (voxelfile < 0) + { + sc.ScriptMessage("Voxel \"%s\" not found.\n", sc.String); + } + else + { + voxeldata = VOX_GetVoxel(voxelfile); + if (voxeldata == NULL) + { + sc.ScriptMessage("\"%s\" is not a valid voxel file.\n", sc.String); + } + } + if (sc.CheckToken('{')) + { + VOX_ReadOptions(sc, opts); + } + sc.SetCMode(false); + if (voxeldata != NULL && vsprites.Size() != 0) + { + FVoxelDef *def = new FVoxelDef; + + def->Voxel = voxeldata; + def->Scale = opts.Scale; + def->DroppedSpin = opts.DroppedSpin; + def->PlacedSpin = opts.PlacedSpin; + def->AngleOffset = opts.AngleOffset; + VoxelDefs.Push(def); + + for (unsigned i = 0; i < vsprites.Size(); ++i) + { + int sprnum = int(vsprites[i] & 0xFFFFFF); + int frame = int(vsprites[i] >> 24); + if (frame == 255) + { // Apply voxel to all frames. + for (int j = MAX_SPRITE_FRAMES - 1; j >= 0; --j) + { + VOX_AddVoxel(sprnum, j, def); + } + } + else + { // Apply voxel to only one frame. + VOX_AddVoxel(sprnum, frame, def); + } + } + } + } + } +} + diff --git a/src/resources/voxels.h b/src/resources/voxels.h new file mode 100644 index 0000000000..76f405726b --- /dev/null +++ b/src/resources/voxels.h @@ -0,0 +1,62 @@ +#ifndef __RES_VOXEL_H +#define __RES_VOXEL_H + +#include "doomdef.h" + +// [RH] Voxels from Build + +#define MAXVOXMIPS 5 + +struct kvxslab_t +{ + BYTE ztop; // starting z coordinate of top of slab + BYTE zleng; // # of bytes in the color array - slab height + BYTE backfacecull; // low 6 bits tell which of 6 faces are exposed + BYTE col[1/*zleng*/];// color data from top to bottom +}; + +struct FVoxelMipLevel +{ + FVoxelMipLevel(); + ~FVoxelMipLevel(); + + int SizeX; + int SizeY; + int SizeZ; + fixed_t PivotX; // 24.8 fixed point + fixed_t PivotY; // "" + fixed_t PivotZ; // "" + int *OffsetX; + short *OffsetXY; + BYTE *SlabData; +}; + +struct FVoxel +{ + int LumpNum; + int NumMips; + BYTE *Palette; + FVoxelMipLevel Mips[MAXVOXMIPS]; + + FVoxel(); + ~FVoxel(); + void Remap(); +}; + +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 + fixed_t Scale; + angle_t 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 *R_LoadKVX(int lumpnum); +FVoxelDef *R_LoadVoxelDef(int lumpnum, int spin); +void R_InitVoxels(); + +#endif diff --git a/src/v_video.cpp b/src/v_video.cpp index deb53c6de4..b6289077cd 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -63,6 +63,7 @@ #include "v_palette.h" #include "r_sky.h" #include "menu/menu.h" +#include "resources/voxels.h" IMPLEMENT_ABSTRACT_CLASS (DCanvas) @@ -1318,7 +1319,6 @@ void DFrameBuffer::RenderView(player_t *player) // // //========================================================================== -extern TDeletingArray Voxels; void DFrameBuffer::RemapVoxels() { diff --git a/zdoom.vcproj b/zdoom.vcproj index be3edbd756..0885a56044 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -6644,6 +6644,10 @@ RelativePath=".\src\resources\colormaps.h" > + + + +