From 038259a590bc996838c618f3ef5f864084a8a9c3 Mon Sep 17 00:00:00 2001 From: RaveYard Date: Tue, 21 Jun 2022 15:20:18 +0200 Subject: [PATCH 1/2] Add support for most common slope types Certain actors used to make slopes aren't still supported --- CMakeLists.txt | 1 + src/framework/tarray.h | 684 +++++++++++++++++++++++++++++++++++++ src/level/doomdata.h | 15 + src/level/level.cpp | 97 +++++- src/level/level.h | 9 + src/level/level_light.cpp | 44 --- src/level/level_slopes.cpp | 414 ++++++++++++++++++++++ src/level/level_udmf.cpp | 4 +- src/math/mathlib.h | 1 + src/math/plane.cpp | 8 + 10 files changed, 1218 insertions(+), 59 deletions(-) create mode 100644 src/level/level_slopes.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c534c5c..598e6f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ set( SOURCES src/level/level.cpp src/level/level_udmf.cpp src/level/level_light.cpp + src/level/level_slopes.cpp src/level/doomdata.h src/level/level.h src/level/workdata.h diff --git a/src/framework/tarray.h b/src/framework/tarray.h index 37f299c..1cab040 100644 --- a/src/framework/tarray.h +++ b/src/framework/tarray.h @@ -36,6 +36,7 @@ #include #include +#include #ifndef __APPLE__ #include #endif @@ -343,3 +344,686 @@ public: } } }; + +// TMap --------------------------------------------------------------------- +// An associative array, similar in concept to the STL extension +// class hash_map. It is implemented using Lua's table algorithm: +/* +** Hash uses a mix of chained scatter table with Brent's variation. +** A main invariant of these tables is that, if an element is not +** in its main position (i.e. the `original' position that its hash gives +** to it), then the colliding element is in its own main position. +** Hence even when the load factor reaches 100%, performance remains good. +*/ +/****************************************************************************** +* Copyright (C) 1994-2006 Lua.org, PUC-Rio. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + +typedef unsigned int hash_t; + +template struct THashTraits +{ + // Returns the hash value for a key. + hash_t Hash(const KT key) { return (hash_t)(intptr_t)key; } + hash_t Hash(double key) + { + hash_t keyhash[2]; + memcpy(&keyhash, &key, sizeof(keyhash)); + return keyhash[0] ^ keyhash[1]; + } + + // Compares two keys, returning zero if they are the same. + int Compare(const KT left, const KT right) { return left != right; } +}; + +template<> struct THashTraits +{ + // Use all bits when hashing singles instead of converting them to ints. + hash_t Hash(float key) + { + hash_t keyhash; + memcpy(&keyhash, &key, sizeof(keyhash)); + return keyhash; + } + int Compare(float left, float right) { return left != right; } +}; + +template<> struct THashTraits +{ + // Use all bits when hashing doubles instead of converting them to ints. + hash_t Hash(double key) + { + hash_t keyhash[2]; + memcpy(&keyhash, &key, sizeof(keyhash)); + return keyhash[0] ^ keyhash[1]; + } + int Compare(double left, double right) { return left != right; } +}; + +template struct TValueTraits +{ + // Initializes a value for TMap. If a regular constructor isn't + // good enough, you can override it. + void Init(VT& value) + { + ::new(&value) VT; + } +}; + +// Must match layout of TMap +struct FMap +{ + void* Nodes; + void* LastFree; + hash_t Size; + hash_t NumUsed; +}; + + +template class TMapIterator; +template class TMapConstIterator; + +template, class ValueTraits = TValueTraits > +class TMap +{ + template friend class TMapIterator; + template friend class TMapConstIterator; + +public: + typedef class TMap MyType; + typedef class TMapIterator Iterator; + typedef class TMapConstIterator ConstIterator; + typedef struct { const KT Key; VT Value; } Pair; + typedef const Pair ConstPair; + + TMap() { NumUsed = 0; SetNodeVector(1); } + TMap(hash_t size) { NumUsed = 0; SetNodeVector(size); } + ~TMap() { ClearNodeVector(); } + + TMap(const TMap& o) + { + NumUsed = 0; + SetNodeVector(o.CountUsed()); + CopyNodes(o.Nodes, o.Size); + } + + TMap(TMap&& o) + { + Nodes = o.Nodes; + LastFree = o.LastFree; /* any free position is before this position */ + Size = o.Size; /* must be a power of 2 */ + NumUsed = o.NumUsed; + + o.Size = 0; + o.NumUsed = 0; + o.SetNodeVector(1); + } + + TMap& operator= (const TMap& o) + { + NumUsed = 0; + ClearNodeVector(); + SetNodeVector(o.CountUsed()); + CopyNodes(o.Nodes, o.Size); + return *this; + } + + TMap& operator= (TMap&& o) + { + TransferFrom(o); + return *this; + } + + //======================================================================= + // + // TransferFrom + // + // Moves the contents from one TMap to another, leaving the TMap moved + // from empty. + // + //======================================================================= + + void TransferFrom(TMap& o) + { + // Clear all our nodes. + NumUsed = 0; + ClearNodeVector(); + + // Copy all of o's nodes. + Nodes = o.Nodes; + LastFree = o.LastFree; + Size = o.Size; + NumUsed = o.NumUsed; + + // Tell o it doesn't have any nodes. + o.Nodes = NULL; + o.Size = 0; + o.LastFree = NULL; + o.NumUsed = 0; + + // Leave o functional with one empty node. + o.SetNodeVector(1); + } + + //======================================================================= + // + // Clear + // + // Empties out the table and resizes it with room for count entries. + // + //======================================================================= + + void Clear(hash_t count = 1) + { + ClearNodeVector(); + SetNodeVector(count); + } + + //======================================================================= + // + // CountUsed + // + // Returns the number of entries in use in the table. + // + //======================================================================= + + hash_t CountUsed() const + { +#ifdef _DEBUG + hash_t used = 0; + hash_t ct = Size; + for (Node* n = Nodes; ct-- > 0; ++n) + { + if (!n->IsNil()) + { + ++used; + } + } + assert(used == NumUsed); +#endif + return NumUsed; + } + + //======================================================================= + // + // operator[] + // + // Returns a reference to the value associated with a particular key, + // creating the pair if the key isn't already in the table. + // + //======================================================================= + + VT& operator[] (const KT key) + { + return GetNode(key)->Pair.Value; + } + + const VT& operator[] (const KT key) const + { + return GetNode(key)->Pair.Value; + } + + //======================================================================= + // + // CheckKey + // + // Returns a pointer to the value associated with a particular key, or + // NULL if the key isn't in the table. + // + //======================================================================= + + VT* CheckKey(const KT key) + { + Node* n = FindKey(key); + return n != NULL ? &n->Pair.Value : NULL; + } + + const VT* CheckKey(const KT key) const + { + const Node* n = FindKey(key); + return n != NULL ? &n->Pair.Value : NULL; + } + + //======================================================================= + // + // Insert + // + // Adds a key/value pair to the table if key isn't in the table, or + // replaces the value for the existing pair if the key is in the table. + // + // This is functionally equivalent to (*this)[key] = value; but can be + // slightly faster if the pair needs to be created because it doesn't run + // the constructor on the value part twice. + // + //======================================================================= + + VT& Insert(const KT key, const VT& value) + { + Node* n = FindKey(key); + if (n != NULL) + { + n->Pair.Value = value; + } + else + { + n = NewKey(key); + ::new(&n->Pair.Value) VT(value); + } + return n->Pair.Value; + } + + VT& Insert(const KT key, VT&& value) + { + Node* n = FindKey(key); + if (n != NULL) + { + n->Pair.Value = value; + } + else + { + n = NewKey(key); + ::new(&n->Pair.Value) VT(value); + } + return n->Pair.Value; + } + + VT& InsertNew(const KT key) + { + Node* n = FindKey(key); + if (n != NULL) + { + n->Pair.Value.~VT(); + } + else + { + n = NewKey(key); + } + ::new(&n->Pair.Value) VT; + return n->Pair.Value; + } + + //======================================================================= + // + // Remove + // + // Removes the key/value pair for a particular key if it is in the table. + // + //======================================================================= + + void Remove(const KT key) + { + DelKey(key); + } + + void Swap(MyType& other) + { + std::swap(Nodes, other.Nodes); + std::swap(LastFree, other.LastFree); + std::swap(Size, other.Size); + std::swap(NumUsed, other.NumUsed); + } + +protected: + struct IPair // This must be the same as Pair above, but with a + { // non-const Key. + KT Key; + VT Value; + }; + struct Node + { + Node* Next; + IPair Pair; + void SetNil() + { + Next = (Node*)1; + } + bool IsNil() const + { + return Next == (Node*)1; + } + }; + + /* This is used instead of memcpy, because Node is likely to be small, + * such that the time spent calling a function would eclipse the time + * spent copying. */ + struct NodeSizedStruct { unsigned char Pads[sizeof(Node)]; }; + + Node* Nodes; + Node* LastFree; /* any free position is before this position */ + hash_t Size; /* must be a power of 2 */ + hash_t NumUsed; + + const Node* MainPosition(const KT k) const + { + HashTraits Traits; + return &Nodes[Traits.Hash(k) & (Size - 1)]; + } + + Node* MainPosition(const KT k) + { + HashTraits Traits; + return &Nodes[Traits.Hash(k) & (Size - 1)]; + } + + void SetNodeVector(hash_t size) + { + // Round size up to nearest power of 2 + for (Size = 1; Size < size; Size <<= 1) + { + } + Nodes = (Node*)malloc(Size * sizeof(Node)); + LastFree = &Nodes[Size]; /* all positions are free */ + for (hash_t i = 0; i < Size; ++i) + { + Nodes[i].SetNil(); + } + } + + void ClearNodeVector() + { + for (hash_t i = 0; i < Size; ++i) + { + if (!Nodes[i].IsNil()) + { + Nodes[i].~Node(); + } + } + free(Nodes); + Nodes = NULL; + Size = 0; + LastFree = NULL; + NumUsed = 0; + } + + void Resize(hash_t nhsize) + { + hash_t i, oldhsize = Size; + Node* nold = Nodes; + /* create new hash part with appropriate size */ + SetNodeVector(nhsize); + /* re-insert elements from hash part */ + NumUsed = 0; + for (i = 0; i < oldhsize; ++i) + { + if (!nold[i].IsNil()) + { + Node* n = NewKey(nold[i].Pair.Key); + ::new(&n->Pair.Value) VT(std::move(nold[i].Pair.Value)); + nold[i].~Node(); + } + } + free(nold); + } + + void Rehash() + { + Resize(Size << 1); + } + + Node* GetFreePos() + { + while (LastFree-- > Nodes) + { + if (LastFree->IsNil()) + { + return LastFree; + } + } + return NULL; /* could not find a free place */ + } + + /* + ** Inserts a new key into a hash table; first, check whether key's main + ** position is free. If not, check whether colliding node is in its main + ** position or not: if it is not, move colliding node to an empty place and + ** put new key in its main position; otherwise (colliding node is in its main + ** position), new key goes to an empty position. + ** + ** The Value field is left unconstructed. + */ + Node* NewKey(const KT key) + { + Node* mp = MainPosition(key); + if (!mp->IsNil()) + { + Node* othern; + Node* n = GetFreePos(); /* get a free place */ + if (n == NULL) /* cannot find a free place? */ + { + Rehash(); /* grow table */ + return NewKey(key); /* re-insert key into grown table */ + } + othern = MainPosition(mp->Pair.Key); + if (othern != mp) /* is colliding node out of its main position? */ + { /* yes; move colliding node into free position */ + while (othern->Next != mp) /* find previous */ + { + othern = othern->Next; + } + othern->Next = n; /* redo the chain with 'n' in place of 'mp' */ + CopyNode(n, mp); /* copy colliding node into free pos. (mp->Next also goes) */ + mp->Next = NULL; /* now 'mp' is free */ + } + else /* colliding node is in its own main position */ + { /* new node will go into free position */ + n->Next = mp->Next; /* chain new position */ + mp->Next = n; + mp = n; + } + } + else + { + mp->Next = NULL; + } + ++NumUsed; + ::new(&mp->Pair.Key) KT(key); + return mp; + } + + void DelKey(const KT key) + { + Node* mp = MainPosition(key), ** mpp; + HashTraits Traits; + + if (mp->IsNil()) + { + /* the key is definitely not present, because there is nothing at its main position */ + } + else if (!Traits.Compare(mp->Pair.Key, key)) /* the key is in its main position */ + { + if (mp->Next != NULL) /* move next node to its main position */ + { + Node* n = mp->Next; + mp->~Node(); /* deconstruct old node */ + CopyNode(mp, n); /* copy next node */ + n->SetNil(); /* next node is now nil */ + } + else + { + mp->~Node(); + mp->SetNil(); /* there is no chain, so main position is nil */ + } + --NumUsed; + } + else /* the key is either not present or not in its main position */ + { + for (mpp = &mp->Next, mp = *mpp; mp != NULL && Traits.Compare(mp->Pair.Key, key); mpp = &mp->Next, mp = *mpp) + { + } /* look for the key */ + if (mp != NULL) /* found it */ + { + *mpp = mp->Next; /* rechain so this node is skipped */ + mp->~Node(); + mp->SetNil(); /* because this node is now nil */ + --NumUsed; + } + } + } + + Node* FindKey(const KT key) + { + HashTraits Traits; + Node* n = MainPosition(key); + while (n != NULL && !n->IsNil() && Traits.Compare(n->Pair.Key, key)) + { + n = n->Next; + } + return n == NULL || n->IsNil() ? NULL : n; + } + + const Node* FindKey(const KT key) const + { + HashTraits Traits; + const Node* n = MainPosition(key); + while (n != NULL && !n->IsNil() && Traits.Compare(n->Pair.Key, key)) + { + n = n->Next; + } + return n == NULL || n->IsNil() ? NULL : n; + } + + Node* GetNode(const KT key) + { + Node* n = FindKey(key); + if (n != NULL) + { + return n; + } + n = NewKey(key); + ValueTraits traits; + traits.Init(n->Pair.Value); + return n; + } + + /* Perform a bit-wise copy of the node. Used when relocating a node in the table. */ + void CopyNode(Node* dst, const Node* src) + { + *(NodeSizedStruct*)dst = *(const NodeSizedStruct*)src; + } + + /* Copy all nodes in the node vector to this table. */ + void CopyNodes(const Node* nodes, hash_t numnodes) + { + for (; numnodes-- > 0; ++nodes) + { + if (!nodes->IsNil()) + { + Node* n = NewKey(nodes->Pair.Key); + ::new(&n->Pair.Value) VT(nodes->Pair.Value); + } + } + } +}; + +// TMapIterator ------------------------------------------------------------- +// A class to iterate over all the pairs in a TMap. + +template > +class TMapIterator +{ +public: + TMapIterator(MapType& map) + : Map(map), Position(0) + { + } + + //======================================================================= + // + // NextPair + // + // Returns false if there are no more entries in the table. Otherwise, it + // returns true, and pair is filled with a pointer to the pair in the + // table. + // + //======================================================================= + + bool NextPair(typename MapType::Pair*& pair) + { + if (Position >= Map.Size) + { + return false; + } + do + { + if (!Map.Nodes[Position].IsNil()) + { + pair = reinterpret_cast(&Map.Nodes[Position].Pair); + Position += 1; + return true; + } + } while (++Position < Map.Size); + return false; + } + + //======================================================================= + // + // Reset + // + // Restarts the iteration so you can do it all over again. + // + //======================================================================= + + void Reset() + { + Position = 0; + } + +protected: + MapType& Map; + hash_t Position; +}; + +// TMapConstIterator -------------------------------------------------------- +// Exactly the same as TMapIterator, but it works with a const TMap. + +template > +class TMapConstIterator +{ +public: + TMapConstIterator(const MapType& map) + : Map(map), Position(0) + { + } + + bool NextPair(typename MapType::ConstPair*& pair) + { + if (Position >= Map.Size) + { + return false; + } + do + { + if (!Map.Nodes[Position].IsNil()) + { + pair = reinterpret_cast(&Map.Nodes[Position].Pair); + Position += 1; + return true; + } + } while (++Position < Map.Size); + return false; + } + +protected: + const MapType& Map; + hash_t Position; +}; + diff --git a/src/level/doomdata.h b/src/level/doomdata.h index c137fdf..8b14f7c 100644 --- a/src/level/doomdata.h +++ b/src/level/doomdata.h @@ -92,6 +92,8 @@ struct MapLineDef2 uint16_t sidenum[2]; }; +struct IntSector; + struct IntLineDef { uint32_t v1; @@ -103,6 +105,8 @@ struct IntLineDef TArray props; TArray ids; + + IntSector *frontsector, *backsector; }; struct MapSector @@ -139,6 +143,8 @@ struct IntSector bool skySector; TArray props; + + TArray lines; }; struct MapSubsector @@ -263,6 +269,9 @@ struct IntVertex { TArray props; double zfloor = 100000, zceiling = 100000; + + inline bool HasZFloor() const { return zfloor != 100000; } + inline bool HasZCeiling() const { return zceiling != 100000; } }; class BBox; @@ -416,6 +425,9 @@ struct FLevel void RemoveExtraLines (); void RemoveExtraSides (); void RemoveExtraSectors (); + + void PostLoadInitialization(); + void SetupLights(); int NumSides() const { return Sides.Size(); } @@ -433,6 +445,9 @@ struct FLevel vec3 GetLightProbePosition(int index); + int FindFirstSectorFromTag(int tag); + + inline IntSector* PointInSector(const dvec2& pos) { return GetSectorFromSubSector(PointInSubSector(int(pos.x), int(pos.y))); } private: void CheckSkySectors(); void CreateLights(); diff --git a/src/level/level.cpp b/src/level/level.cpp index d659fd3..bfccdc7 100644 --- a/src/level/level.cpp +++ b/src/level/level.cpp @@ -21,6 +21,7 @@ #include "level/level.h" #include "lightmap/cpuraytracer.h" #include "lightmap/gpuraytracer.h" +#include "math/vec.h" //#include "rejectbuilder.h" #include @@ -46,19 +47,6 @@ enum PO_SPAWN_TYPE, PO_SPAWNCRUSH_TYPE, PO_SPAWNHURT_TYPE, - - // Thing numbers used to define slopes - SMT_VavoomFloor = 1500, - SMT_VavoomCeiling = 1501, - SMT_VertexFloorZ = 1504, - SMT_VertexCeilingZ = 1505, - SMT_SlopeFloorPointLine = 9500, - SMT_SlopeCeilingPointLine = 9501, - SMT_SetFloorSlope = 9502, - SMT_SetCeilingSlope = 9503, - SMT_CopyFloorPlane = 9510, - SMT_CopyCeilingPlane = 9511, - }; FLevel::FLevel () @@ -549,6 +537,22 @@ void FLevel::RemoveExtraSectors () delete[] remap; } +int FLevel::FindFirstSectorFromTag(int tag) +{ + for (unsigned i = 0; i < Sectors.Size(); ++i) + { + for (auto& sectorTag : Sectors[i].tags) + { + if (sectorTag == tag) + { + return i; + } + } + } + + return -1; +} + void FProcessor::GetPolySpots () { if (Extended && CheckPolyobjs) @@ -602,6 +606,65 @@ void FProcessor::GetPolySpots () } } +void FLevel::PostLoadInitialization() +{ + CheckSkySectors(); + + for (unsigned int i = 0; i < Sectors.Size(); i++) + Sectors[i].controlsector = false; + + for (unsigned int i = 0; i < Sides.Size(); i++) + Sides[i].line = nullptr; + + for (unsigned int i = 0; i < Lines.Size(); i++) + { + IntLineDef* line = &Lines[i]; + + // Link sides to lines + if (line->sidenum[0] < Sides.Size()) + Sides[line->sidenum[0]].line = line; + if (line->sidenum[1] < Sides.Size()) + Sides[line->sidenum[1]].line = line; + + if (line->special == Sector_Set3DFloor) + { + int sectorTag = line->args[0]; + int type = line->args[1]; + int opacity = line->args[3]; + + if (opacity > 0) + { + IntSector* controlsector = &Sectors[Sides[Lines[i].sidenum[0]].sector]; + controlsector->controlsector = true; + + for (unsigned int j = 0; j < Sectors.Size(); j++) + { + for (unsigned t = 0; t < Sectors[j].tags.Size(); t++) + { + if (Sectors[j].tags[t] == sectorTag) + { + Sectors[j].x3dfloors.Push(controlsector); + break; + } + } + } + } + } + } + + // Set front and back sector pointer + for (auto& side : Sides) + { + auto& sector = Sectors[side.sector]; + auto line = side.line; + + sector.lines.Push(line); + + line->frontsector = (line->sidenum[0] != NO_INDEX) ? &Sectors[Sides[line->sidenum[0]].sector] : nullptr; + line->backsector = (line->sidenum[1] != NO_INDEX) ? &Sectors[Sides[line->sidenum[1]].sector] : nullptr; + } +} + void FProcessor::BuildNodes() { NodesBuilt = true; @@ -693,7 +756,15 @@ void FProcessor::BuildNodes() void FProcessor::BuildLightmaps() { + Level.PostLoadInitialization(); + + SpawnSlopeMakers(&Level.Things[0], &Level.Things[Level.Things.Size()], nullptr); + CopySlopes(); + + SetSlopes(); + Level.SetupLights(); + LightmapMesh = std::make_unique(Level, Level.DefaultSamples, LMDims); std::unique_ptr gpuraytracer; diff --git a/src/level/level.h b/src/level/level.h index 3bccfda..b9f2453 100644 --- a/src/level/level.h +++ b/src/level/level.h @@ -69,6 +69,15 @@ private: void GetPolySpots(); void SetLineID(IntLineDef *ld); + void SetSlopes(); + void CopySlopes(); + void AlignPlane(IntSector* sec, IntLineDef* line, int which); + void SetSlopesFromVertexHeights(IntThing* firstmt, IntThing* lastmt, const int* oldvertextable); + void SpawnSlopeMakers(IntThing* firstmt, IntThing* lastmt, const int* oldvertextable); + void CopyPlane(int tag, IntSector* dest, bool copyCeil); + void CopyPlane(int tag, const dvec2& pos, bool copyCeil); + + MapNodeEx *NodesToEx(const MapNode *nodes, int count); MapSubsectorEx *SubsectorsToEx(const MapSubsector *ssec, int count); MapSegGLEx *SegGLsToEx(const MapSegGL *segs, int count); diff --git a/src/level/level_light.cpp b/src/level/level_light.cpp index e1bc076..c823a5b 100644 --- a/src/level/level_light.cpp +++ b/src/level/level_light.cpp @@ -40,50 +40,6 @@ void FLevel::SetupLights() { - CheckSkySectors(); - - for (unsigned int i = 0; i < Sectors.Size(); i++) - Sectors[i].controlsector = false; - - for (unsigned int i = 0; i < Sides.Size(); i++) - Sides[i].line = nullptr; - - for (unsigned int i = 0; i < Lines.Size(); i++) - { - IntLineDef *line = &Lines[i]; - - // Link sides to lines - if (line->sidenum[0] < Sides.Size()) - Sides[line->sidenum[0]].line = line; - if (line->sidenum[1] < Sides.Size()) - Sides[line->sidenum[1]].line = line; - - if (line->special == Sector_Set3DFloor) - { - int sectorTag = line->args[0]; - int type = line->args[1]; - int opacity = line->args[3]; - - if (opacity > 0) - { - IntSector *controlsector = &Sectors[Sides[Lines[i].sidenum[0]].sector]; - controlsector->controlsector = true; - - for (unsigned int j = 0; j < Sectors.Size(); j++) - { - for (unsigned t = 0; t < Sectors[j].tags.Size(); t++) - { - if (Sectors[j].tags[t] == sectorTag) - { - Sectors[j].x3dfloors.Push(controlsector); - break; - } - } - } - } - } - } - // GG to whoever memset'ed FLevel defaultSunColor = vec3(1, 1, 1); defaultSunDirection = vec3(0.45f, 0.3f, 0.9f); diff --git a/src/level/level_slopes.cpp b/src/level/level_slopes.cpp new file mode 100644 index 0000000..e6343bc --- /dev/null +++ b/src/level/level_slopes.cpp @@ -0,0 +1,414 @@ +/* +** p_slopes.cpp +** Slope creation +** +**--------------------------------------------------------------------------- +** Copyright 1998-2008 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 "level.h" + +#include + +#define EQUAL_EPSILON (1/65536.) + +// convert from fixed point(FRACUNIT) to floating point +#define F(x) (((float)(x))/65536.0f) + +inline int P_PointOnLineSidePrecise(const vec2& p, const vec2& v1, const vec2& v2) +{ + return (p.y - v1.y) * (v2.x - v1.x) + (v1.x - p.x) * (v2.y - v1.y) > EQUAL_EPSILON; +} + +inline int P_PointOnLineSidePrecise(const vec2& p, const FloatVertex& v1, const FloatVertex& v2) +{ + return P_PointOnLineSidePrecise(p, vec2(v1.x, v1.y), vec2(v2.x, v2.y)); +} + +enum +{ + // Thing numbers used to define slopes + SMT_VavoomFloor = 1500, + SMT_VavoomCeiling = 1501, + SMT_VertexFloorZ = 1504, + SMT_VertexCeilingZ = 1505, + SMT_SlopeFloorPointLine = 9500, + SMT_SlopeCeilingPointLine = 9501, + SMT_SetFloorSlope = 9502, + SMT_SetCeilingSlope = 9503, + SMT_CopyFloorPlane = 9510, + SMT_CopyCeilingPlane = 9511, +}; + +void FProcessor::SetSlopesFromVertexHeights(IntThing* firstmt, IntThing* lastmt, const int* oldvertextable) +{ + TMap vt_heights[2]; + IntThing* mt; + bool vt_found = false; + + for (mt = firstmt; mt < lastmt; ++mt) + { + if (mt->type == SMT_VertexFloorZ || mt->type == SMT_VertexCeilingZ) + { + for (int i = 0; i < Level.NumVertices; i++) + { + auto vertex = Level.GetSegVertex(i); + + if(F(mt->x) == vertex.x && F(mt->y) == vertex.y) + { + if (mt->type == SMT_VertexFloorZ) + { + vt_heights[0][i] = F(mt->z); + } + else + { + vt_heights[1][i] = F(mt->z); + } + vt_found = true; + } + } + mt->type = 0; + } + } + + for (unsigned i = 0; i < Level.VertexProps.Size(); i++) + { + auto& vertex = Level.VertexProps[i]; + + int ii = oldvertextable == NULL ? i : oldvertextable[i]; + + if (vertex.HasZCeiling()) + { + vt_heights[1][ii] = vertex.zceiling; + vt_found = true; + } + + if (vertex.HasZFloor()) + { + vt_heights[0][ii] = vertex.zfloor; + vt_found = true; + } + } + + if (vt_found) + { + for (auto& sec : Level.Sectors) + { + if (sec.lines.Size() != 3) continue; // only works with triangular sectors + + dvec3 vt1, vt2, vt3; + dvec3 vec1, vec2; + int vi1, vi2, vi3; + + vi1 = sec.lines[0]->v1; + vi2 = sec.lines[0]->v2; + vi3 = (sec.lines[1]->v1 == sec.lines[0]->v1 || sec.lines[1]->v1 == sec.lines[0]->v2) ? + sec.lines[1]->v2 : sec.lines[1]->v1; + + const auto sv1 = Level.GetSegVertex(vi1); + const auto sv2 = Level.GetSegVertex(vi2); + const auto sv3 = Level.GetSegVertex(vi3); + + vt1 = dvec3(sv1.x, sv1.y, 0); + vt2 = dvec3(sv2.x, sv2.y, 0); + vt3 = dvec3(sv3.x, sv3.y, 0); + + for (int j = 0; j < 2; j++) + { + double* h1 = vt_heights[j].CheckKey(vi1); + double* h2 = vt_heights[j].CheckKey(vi2); + double* h3 = vt_heights[j].CheckKey(vi3); + + if (h1 == NULL && h2 == NULL && h3 == NULL) continue; + + vt1.z = h1 ? *h1 : j == 0 ? sec.data.floorheight : sec.data.ceilingheight; + vt2.z = h2 ? *h2 : j == 0 ? sec.data.floorheight : sec.data.ceilingheight; + vt3.z = h3 ? *h3 : j == 0 ? sec.data.floorheight : sec.data.ceilingheight; + + if (P_PointOnLineSidePrecise(::vec2(vt3.xy()), Level.GetSegVertex(sec.lines[0]->v1), Level.GetSegVertex(sec.lines[0]->v2)) == 0) + { + vec1 = vt2 - vt3; + vec2 = vt1 - vt3; + } + else + { + vec1 = vt1 - vt3; + vec2 = vt2 - vt3; + } + + dvec3 cross = ::cross(vec1, vec2); + + double len = length(cross); + if (len == 0) + { + // Only happens when all vertices in this sector are on the same line. + // Let's just ignore this case. + continue; + } + cross /= len; + + // Fix backward normals + if ((cross.z < 0 && j == 0) || (cross.z > 0 && j == 1)) + { + cross = -cross; + } + + Plane* plane = j == 0 ? &sec.floorplane : &sec.ceilingplane; + + double dist = -cross[0] * vt3.x - cross[1] * vt3.y - cross[2] * vt3.z; + plane->Set(float(cross[0]), float(cross[1]), float(cross[2]), float(-dist)); + } + } + } +} + +void FProcessor::SpawnSlopeMakers(IntThing* firstmt, IntThing* lastmt, const int* oldvertextable) +{ + IntThing* mt; + + for (mt = firstmt; mt < lastmt; ++mt) + { + if (mt->type >= SMT_SlopeFloorPointLine && mt->type <= SMT_VavoomCeiling) + { + dvec3 pos = dvec3(F(mt->x), F(mt->y), F(mt->z)); + Plane* refplane; + IntSector* sec; + bool ceiling; + + sec = Level.PointInSector(pos.xy()); + if (mt->type == SMT_SlopeCeilingPointLine || mt->type == SMT_VavoomCeiling || mt->type == SMT_SetCeilingSlope) + { + refplane = &sec->ceilingplane; + ceiling = true; + } + else + { + refplane = &sec->floorplane; + ceiling = false; + } + + pos.z = double(refplane->zAt(float(pos.x), float(pos.y))) + pos.z; + + /*if (mt->type <= SMT_SlopeCeilingPointLine) + { // SlopeFloorPointLine and SlopCeilingPointLine + SlopeLineToPoint(mt->args[0], pos, ceiling); + } + else if (mt->type <= SMT_SetCeilingSlope) + { // SetFloorSlope and SetCeilingSlope + SetSlope(refplane, ceiling, mt->angle, mt->args[0], pos); + } + else + { // VavoomFloor and VavoomCeiling (these do not perform any sector height adjustment - z is absolute) + VavoomSlope(sec, mt->thingid, mt->pos, ceiling); + }*/ + mt->type = 0; + } + } + SetSlopesFromVertexHeights(firstmt, lastmt, oldvertextable); + + for (mt = firstmt; mt < lastmt; ++mt) + { + if (mt->type == SMT_CopyFloorPlane || mt->type == SMT_CopyCeilingPlane) + { + CopyPlane(mt->args[0], dvec2(F(mt->x), F(mt->y)), mt->type == SMT_CopyCeilingPlane); + mt->type = 0; + } + } + +} + +void FProcessor::AlignPlane(IntSector* sec, IntLineDef* line, int which) +{ + IntSector* refsec; + double bestdist; + FloatVertex refvert = Level.GetSegVertex(sec->lines[0]->v1); + + if (line->backsector == NULL) + return; + + auto lv1 = Level.GetSegVertex(line->v1); + auto lv2 = Level.GetSegVertex(line->v2); + + // Find furthest vertex from the reference line. It, along with the two ends + // of the line, will define the plane. + bestdist = 0; + for (auto ln : sec->lines) + { + for (int i = 0; i < 2; i++) + { + double dist; + FloatVertex vert; + + vert = Level.GetSegVertex(i == 0 ? ln->v1 : ln->v2); + + dist = fabs((lv1.y - vert.y) * (lv2.x - lv1.x) - + (lv1.x - vert.x) * (lv2.y - lv1.y)); + + if (dist > bestdist) + { + bestdist = dist; + refvert = vert; + } + } + } + + refsec = line->frontsector == sec ? line->backsector : line->frontsector; + + dvec3 p, v1, v2, cross; + + Plane* srcplane; + double srcheight, destheight; + + srcplane = (which == 0) ? &sec->floorplane : &sec->ceilingplane; + srcheight = (which == 0) ? sec->data.floorheight : sec->data.ceilingheight; + destheight = (which == 0) ? refsec->data.floorheight : refsec->data.ceilingheight; + + p[0] = lv1.x; + p[1] = lv1.y; + p[2] = destheight; + v1[0] = double(lv2.x) - double(lv1.x); + v1[1] = double(lv2.y) - double(lv1.y); + v1[2] = 0; + v2[0] = double(refvert.x) - double(lv1.x); + v2[1] = double(refvert.y) - double(lv1.y); + v2[2] = srcheight - destheight; + + cross = normalize(::cross(v1, v2)); + + // Fix backward normals + if ((cross.z < 0 && which == 0) || (cross.z > 0 && which == 1)) + { + cross = -cross; + } + + double dist = -cross[0] * lv1.x - cross[1] * lv1.y - cross[2] * destheight; + srcplane->Set(float(cross[0]), float(cross[1]), float(cross[2]), float(-dist)); +} + +void FProcessor::SetSlopes() +{ + int s; + + for (auto& line : Level.Lines) + { + if (line.special == Plane_Align) + { + line.special = 0; + if (line.backsector != nullptr) + { + // args[0] is for floor, args[1] is for ceiling + // + // As a special case, if args[1] is 0, + // then args[0], bits 2-3 are for ceiling. + for (s = 0; s < 2; s++) + { + int bits = line.args[s] & 3; + + if (s == 1 && bits == 0) + bits = (line.args[0] >> 2) & 3; + + if (bits == 1) // align front side to back + AlignPlane(line.frontsector, &line, s); + else if (bits == 2) // align back side to front + AlignPlane(line.backsector, &line, s); + } + } + } + } +} + +void FProcessor::CopySlopes() +{ + for (auto& line : Level.Lines) + { + if (line.special == Plane_Copy) + { + // The args are used for the tags of sectors to copy: + // args[0]: front floor + // args[1]: front ceiling + // args[2]: back floor + // args[3]: back ceiling + // args[4]: copy slopes from one side of the line to the other. + line.special = 0; + for (int s = 0; s < (line.backsector ? 4 : 2); s++) + { + if (line.args[s]) + CopyPlane(line.args[s], (s & 2 ? line.backsector : line.frontsector), s & 1); + } + + if (line.backsector != NULL) + { + if ((line.args[4] & 3) == 1) + { + line.backsector->floorplane = line.frontsector->floorplane; + } + else if ((line.args[4] & 3) == 2) + { + line.frontsector->floorplane = line.backsector->floorplane; + } + if ((line.args[4] & 12) == 4) + { + line.backsector->ceilingplane = line.frontsector->ceilingplane; + } + else if ((line.args[4] & 12) == 8) + { + line.frontsector->ceilingplane = line.backsector->ceilingplane; + } + } + } + } +} + +void FProcessor::CopyPlane(int tag, IntSector* dest, bool copyCeil) +{ + IntSector* source; + int secnum; + + secnum = Level.FindFirstSectorFromTag(tag); + if (secnum == -1) + { + return; + } + + source = &Level.Sectors[secnum]; + + if (copyCeil) + { + dest->ceilingplane = source->ceilingplane; + } + else + { + dest->floorplane = source->floorplane; + } +} + +void FProcessor::CopyPlane(int tag, const dvec2& pos, bool copyCeil) +{ + CopyPlane(tag, Level.PointInSector(pos), copyCeil); +} \ No newline at end of file diff --git a/src/level/level_udmf.cpp b/src/level/level_udmf.cpp index f9ee79a..5ff960c 100644 --- a/src/level/level_udmf.cpp +++ b/src/level/level_udmf.cpp @@ -613,11 +613,11 @@ void FProcessor::ParseVertex(WideVertex *vt, IntVertex *vtp) } if (!stricmp(key, "zfloor")) { - vtp->zfloor = CheckFixed(key); + vtp->zfloor = CheckFloat(key); } else if (!stricmp(key, "zceiling")) { - vtp->zceiling = CheckFixed(key); + vtp->zceiling = CheckFloat(key); } // now store the key in its unprocessed form diff --git a/src/math/mathlib.h b/src/math/mathlib.h index 72070d8..6182934 100644 --- a/src/math/mathlib.h +++ b/src/math/mathlib.h @@ -94,6 +94,7 @@ public: AXIS_XY }; + void Set(float a, float b, float c, float d); const vec3 &Normal() const; vec3 &Normal(); Plane &SetNormal(const vec3 &normal); diff --git a/src/math/plane.cpp b/src/math/plane.cpp index 8f4fa14..ee4c483 100644 --- a/src/math/plane.cpp +++ b/src/math/plane.cpp @@ -66,6 +66,14 @@ Plane::Plane(const Plane &plane) this->d = plane.d; } +void Plane::Set(float a, float b, float c, float d) +{ + this->a = a; + this->b = b; + this->c = c; + this->d = d; +} + Plane &Plane::SetNormal(const vec3 &normal) { Normal() = normal; From 114b5c68d25104a249e74f6f759791d1b16a3010 Mon Sep 17 00:00:00 2001 From: RaveYard Date: Wed, 22 Jun 2022 13:21:06 +0200 Subject: [PATCH 2/2] Fix certain broken normals --- src/lightmap/levelmesh.cpp | 8 ++++---- src/math/mathlib.h | 3 ++- src/math/plane.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/lightmap/levelmesh.cpp b/src/lightmap/levelmesh.cpp index 3bb6ca1..2107317 100644 --- a/src/lightmap/levelmesh.cpp +++ b/src/lightmap/levelmesh.cpp @@ -539,7 +539,7 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->verts[1].z = xfloor->floorplane.zAt(v1.x, v1.y); surf->verts[2].z = xfloor->ceilingplane.zAt(v2.x, v2.y); surf->verts[3].z = xfloor->ceilingplane.zAt(v1.x, v1.y); - surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2]); + surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2], surf->verts[3]); surf->plane.SetDistance(surf->verts[0]); float texZ = surf->verts[0].z; @@ -589,7 +589,7 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->verts[2].z = v1BottomBack; surf->verts[3].z = v2BottomBack; - surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2]); + surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2], surf->verts[3]); surf->plane.SetDistance(surf->verts[0]); surf->type = ST_LOWERSIDE; surf->typeIndex = typeIndex; @@ -647,7 +647,7 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->verts[2].z = v1Top; surf->verts[3].z = v2Top; - surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2]); + surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2], surf->verts[3]); surf->plane.SetDistance(surf->verts[0]); surf->type = ST_UPPERSIDE; surf->typeIndex = typeIndex; @@ -695,7 +695,7 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->verts[2].z = v1Top; surf->verts[3].z = v2Top; - surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2]); + surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2], surf->verts[3]); surf->plane.SetDistance(surf->verts[0]); surf->type = ST_MIDDLESIDE; surf->typeIndex = typeIndex; diff --git a/src/math/mathlib.h b/src/math/mathlib.h index 6182934..d38b978 100644 --- a/src/math/mathlib.h +++ b/src/math/mathlib.h @@ -98,7 +98,8 @@ public: const vec3 &Normal() const; vec3 &Normal(); Plane &SetNormal(const vec3 &normal); - Plane &SetNormal(const vec3 &pt1, const vec3 &pt2, const vec3 &pt3); + Plane& SetNormal(const vec3& pt1, const vec3& pt2, const vec3& pt3); + Plane& SetNormal(const vec3& v1, const vec3& v2, const vec3& v3, const vec3& v4); float Distance(const vec3 &point); Plane &SetDistance(const vec3 &point); bool IsFacing(const float yaw); diff --git a/src/math/plane.cpp b/src/math/plane.cpp index ee4c483..528cd8e 100644 --- a/src/math/plane.cpp +++ b/src/math/plane.cpp @@ -86,6 +86,37 @@ Plane &Plane::SetNormal(const vec3 &pt1, const vec3 &pt2, const vec3 &pt3) return *this; } +Plane& Plane::SetNormal(const vec3& v1, const vec3& v2, const vec3& v3, const vec3& v4) +{ + // Handle cases where two vertices share the same position + + // TODO fix this hack + // Certain vertex positions are close enough to cause NaN, but aren't equal + auto iv1 = ivec3(v1); + auto iv2 = ivec3(v2); + auto iv3 = ivec3(v3); + auto iv4 = ivec3(v4); + + if (iv1 == iv3) + { + return SetNormal(v1, v2, v4); + } + else if (iv2 == iv4) + { + return SetNormal(v1, v2, v3); + } + else if (iv1 == iv2) + { + return SetNormal(v2, v3, v4); + } + else if (iv2 == iv3) + { + return SetNormal(v1, v2, v4); + } + + return SetNormal(v1, v2, v3); +} + vec3 const &Plane::Normal() const { return *reinterpret_cast(&a);