Merge pull request #38 from MrRaveYard/pr_fix_slopes

Initial support for most common slope types
This commit is contained in:
Magnus Norddahl 2022-06-23 00:53:22 +02:00 committed by GitHub
commit 0e3340b807
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1255 additions and 64 deletions

View file

@ -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

View file

@ -36,6 +36,7 @@
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#ifndef __APPLE__
#include <malloc.h>
#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<class KT> 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<float>
{
// 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<double>
{
// 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<class VT> 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 KT, class VT, class MapType> class TMapIterator;
template<class KT, class VT, class MapType> class TMapConstIterator;
template<class KT, class VT, class HashTraits = THashTraits<KT>, class ValueTraits = TValueTraits<VT> >
class TMap
{
template<class KTa, class VTa, class MTa> friend class TMapIterator;
template<class KTb, class VTb, class MTb> friend class TMapConstIterator;
public:
typedef class TMap<KT, VT, HashTraits, ValueTraits> MyType;
typedef class TMapIterator<KT, VT, MyType> Iterator;
typedef class TMapConstIterator<KT, VT, MyType> 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 KT, class VT, class MapType = TMap<KT, VT> >
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<typename MapType::Pair*>(&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 KT, class VT, class MapType = TMap<KT, VT> >
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<typename MapType::Pair*>(&Map.Nodes[Position].Pair);
Position += 1;
return true;
}
} while (++Position < Map.Size);
return false;
}
protected:
const MapType& Map;
hash_t Position;
};

View file

@ -92,6 +92,8 @@ struct MapLineDef2
uint16_t sidenum[2];
};
struct IntSector;
struct IntLineDef
{
uint32_t v1;
@ -103,6 +105,8 @@ struct IntLineDef
TArray<UDMFKey> props;
TArray<int> ids;
IntSector *frontsector, *backsector;
};
struct MapSector
@ -139,6 +143,8 @@ struct IntSector
bool skySector;
TArray<UDMFKey> props;
TArray<IntLineDef*> lines;
};
struct MapSubsector
@ -263,6 +269,9 @@ struct IntVertex
{
TArray<UDMFKey> 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();

View file

@ -21,6 +21,7 @@
#include "level/level.h"
#include "lightmap/cpuraytracer.h"
#include "lightmap/gpuraytracer.h"
#include "math/vec.h"
//#include "rejectbuilder.h"
#include <memory>
@ -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<LevelMesh>(Level, Level.DefaultSamples, LMDims);
std::unique_ptr<GPURaytracer> gpuraytracer;

View file

@ -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);

View file

@ -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);

414
src/level/level_slopes.cpp Normal file
View file

@ -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 <map>
#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<int, double> 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);
}

View file

@ -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

View file

@ -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;

View file

@ -94,10 +94,12 @@ public:
AXIS_XY
};
void Set(float a, float b, float c, float d);
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);

View file

@ -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;
@ -78,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<const vec3*>(&a);