mirror of
https://github.com/ZDoom/ZDRay.git
synced 2025-01-25 00:51:20 +00:00
Add the gzdoom model loaders
This commit is contained in:
parent
8f097378d5
commit
a647f21501
21 changed files with 7786 additions and 0 deletions
|
@ -138,6 +138,10 @@ set( SOURCES
|
||||||
src/commandline/getopt1.c
|
src/commandline/getopt1.c
|
||||||
src/framework/halffloat.cpp
|
src/framework/halffloat.cpp
|
||||||
src/framework/binfile.cpp
|
src/framework/binfile.cpp
|
||||||
|
src/framework/zstring.cpp
|
||||||
|
src/framework/zstrformat.cpp
|
||||||
|
src/framework/utf8.cpp
|
||||||
|
src/framework/utf8.h
|
||||||
src/blockmapbuilder/blockmapbuilder.cpp
|
src/blockmapbuilder/blockmapbuilder.cpp
|
||||||
src/level/level.cpp
|
src/level/level.cpp
|
||||||
src/level/level_udmf.cpp
|
src/level/level_udmf.cpp
|
||||||
|
@ -164,6 +168,18 @@ set( SOURCES
|
||||||
src/math/plane.cpp
|
src/math/plane.cpp
|
||||||
src/math/quaternion.cpp
|
src/math/quaternion.cpp
|
||||||
src/math/vector.cpp
|
src/math/vector.cpp
|
||||||
|
src/models/model.cpp
|
||||||
|
src/models/model.h
|
||||||
|
src/models/model_md2.h
|
||||||
|
src/models/model_md3.h
|
||||||
|
src/models/model_obj.h
|
||||||
|
src/models/model_ue1.h
|
||||||
|
src/models/modelrenderer.h
|
||||||
|
src/models/models_md2.cpp
|
||||||
|
src/models/models_md3.cpp
|
||||||
|
src/models/models_obj.cpp
|
||||||
|
src/models/models_ue1.cpp
|
||||||
|
src/models/tab_anorms.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set( HEADERS
|
set( HEADERS
|
||||||
|
@ -261,3 +277,4 @@ source_group("Sources\\Platform\\Windows" REGULAR_EXPRESSION "^${CMAKE_CURRENT_S
|
||||||
source_group("Sources\\Wad" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/wad/.+")
|
source_group("Sources\\Wad" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/wad/.+")
|
||||||
source_group("Sources\\Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/math/.+")
|
source_group("Sources\\Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/math/.+")
|
||||||
source_group("Sources\\Lightmap" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/lightmap/.+")
|
source_group("Sources\\Lightmap" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/lightmap/.+")
|
||||||
|
source_group("Sources\\Models" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/src/models/.+")
|
||||||
|
|
|
@ -262,6 +262,7 @@ public:
|
||||||
{
|
{
|
||||||
return Most;
|
return Most;
|
||||||
}
|
}
|
||||||
|
void Reset() { Clear(); }
|
||||||
void Clear ()
|
void Clear ()
|
||||||
{
|
{
|
||||||
if (Count > 0)
|
if (Count > 0)
|
||||||
|
@ -270,6 +271,11 @@ public:
|
||||||
Count = 0;
|
Count = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// returns address of first element
|
||||||
|
T* Data() const
|
||||||
|
{
|
||||||
|
return &Array[0];
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
T *Array;
|
T *Array;
|
||||||
unsigned int Most;
|
unsigned int Most;
|
||||||
|
|
1155
src/framework/utf8.cpp
Normal file
1155
src/framework/utf8.cpp
Normal file
File diff suppressed because it is too large
Load diff
20
src/framework/utf8.h
Normal file
20
src/framework/utf8.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
int utf8_encode(int32_t codepoint, uint8_t *buffer, int *size);
|
||||||
|
int utf8_decode(const uint8_t *src, int *size);
|
||||||
|
int GetCharFromString(const uint8_t *&string);
|
||||||
|
inline int GetCharFromString(const char32_t *&string)
|
||||||
|
{
|
||||||
|
return *string++;
|
||||||
|
}
|
||||||
|
const char *MakeUTF8(const char *outline, int *numchars = nullptr); // returns a pointer to a static buffer, assuming that its caller will immediately process the result.
|
||||||
|
const char *MakeUTF8(int codepoint, int *psize = nullptr);
|
||||||
|
|
||||||
|
bool myislower(int code);
|
||||||
|
bool myisupper(int code);
|
||||||
|
int stripaccent(int code);
|
||||||
|
int getAlternative(int code);
|
||||||
|
|
||||||
|
extern uint16_t win1252map[];
|
||||||
|
extern uint16_t lowerforupper[65536];
|
||||||
|
extern uint16_t upperforlower[65536];
|
1068
src/framework/zstrformat.cpp
Normal file
1068
src/framework/zstrformat.cpp
Normal file
File diff suppressed because it is too large
Load diff
1416
src/framework/zstring.cpp
Normal file
1416
src/framework/zstring.cpp
Normal file
File diff suppressed because it is too large
Load diff
486
src/framework/zstring.h
Normal file
486
src/framework/zstring.h
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
#pragma once
|
||||||
|
/*
|
||||||
|
** zstring.h
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2005-2007 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 <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
#include "tarray.h"
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#define PRINTFISH(x) __attribute__((format(printf, 2, x)))
|
||||||
|
#else
|
||||||
|
#define PRINTFISH(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#define IGNORE_FORMAT_PRE \
|
||||||
|
_Pragma("GCC diagnostic push") \
|
||||||
|
_Pragma("GCC diagnostic ignored \"-Wformat\"") \
|
||||||
|
_Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"")
|
||||||
|
#define IGNORE_FORMAT_POST _Pragma("GCC diagnostic pop")
|
||||||
|
#else
|
||||||
|
#define IGNORE_FORMAT_PRE
|
||||||
|
#define IGNORE_FORMAT_POST
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::wstring WideString(const char *);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct FStringData
|
||||||
|
{
|
||||||
|
unsigned int Len; // Length of string, excluding terminating null
|
||||||
|
unsigned int AllocLen; // Amount of memory allocated for string
|
||||||
|
int RefCount; // < 0 means it's locked
|
||||||
|
// char StrData[xxx];
|
||||||
|
|
||||||
|
char *Chars()
|
||||||
|
{
|
||||||
|
return (char *)(this + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Chars() const
|
||||||
|
{
|
||||||
|
return (const char *)(this + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *AddRef()
|
||||||
|
{
|
||||||
|
if (RefCount < 0)
|
||||||
|
{
|
||||||
|
return (char *)(MakeCopy() + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RefCount++;
|
||||||
|
return (char *)(this + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release()
|
||||||
|
{
|
||||||
|
assert (RefCount != 0);
|
||||||
|
|
||||||
|
if (--RefCount <= 0)
|
||||||
|
{
|
||||||
|
Dealloc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FStringData *MakeCopy();
|
||||||
|
|
||||||
|
static FStringData *Alloc (size_t strlen);
|
||||||
|
FStringData *Realloc (size_t newstrlen);
|
||||||
|
void Dealloc ();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FNullStringData
|
||||||
|
{
|
||||||
|
unsigned int Len;
|
||||||
|
unsigned int AllocLen;
|
||||||
|
int RefCount;
|
||||||
|
char Nothing[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ELumpNum
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
class FString
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FString () { ResetToNull(); }
|
||||||
|
|
||||||
|
// Copy constructors
|
||||||
|
FString (const FString &other) { AttachToOther (other); }
|
||||||
|
FString (FString &&other) : Chars(other.Chars) { other.ResetToNull(); }
|
||||||
|
FString (const char *copyStr);
|
||||||
|
FString (const char *copyStr, size_t copyLen);
|
||||||
|
FString (char oneChar);
|
||||||
|
FString(const TArray<char> & source) : FString(source.Data(), source.Size()) {}
|
||||||
|
FString(const TArray<uint8_t> & source) : FString((char*)source.Data(), source.Size()) {}
|
||||||
|
// This is intentionally #ifdef'd. The only code which needs this is parts of the Windows backend that receive Unicode text from the system.
|
||||||
|
#ifdef _WIN32
|
||||||
|
explicit FString(const wchar_t *copyStr);
|
||||||
|
FString &operator = (const wchar_t *copyStr);
|
||||||
|
std::wstring WideString() const { return ::WideString(Chars); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Concatenation constructors
|
||||||
|
FString (const FString &head, const FString &tail);
|
||||||
|
FString (const FString &head, const char *tail);
|
||||||
|
FString (const FString &head, char tail);
|
||||||
|
FString (const char *head, const FString &tail);
|
||||||
|
FString (const char *head, const char *tail);
|
||||||
|
FString (char head, const FString &tail);
|
||||||
|
|
||||||
|
// Other constructors
|
||||||
|
FString (ELumpNum); // Create from a lump
|
||||||
|
|
||||||
|
~FString ();
|
||||||
|
|
||||||
|
// Discard string's contents, create a new buffer, and lock it.
|
||||||
|
char *LockNewBuffer(size_t len);
|
||||||
|
|
||||||
|
char *LockBuffer(); // Obtain write access to the character buffer
|
||||||
|
void UnlockBuffer(); // Allow shared access to the character buffer
|
||||||
|
|
||||||
|
void Swap(FString &other)
|
||||||
|
{
|
||||||
|
std::swap(Chars, other.Chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not want any implicit conversions from FString in conditionals.
|
||||||
|
explicit operator bool() = delete; // this is needed to render the operator const char * ineffective when used in boolean constructs.
|
||||||
|
bool operator !() = delete;
|
||||||
|
|
||||||
|
operator const char *() const { return Chars; }
|
||||||
|
|
||||||
|
const char *GetChars() const { return Chars; }
|
||||||
|
|
||||||
|
const char &operator[] (int index) const { return Chars[index]; }
|
||||||
|
#if defined(_WIN32) && !defined(_WIN64) && defined(_MSC_VER)
|
||||||
|
// Compiling 32-bit Windows source with MSVC: size_t is typedefed to an
|
||||||
|
// unsigned int with the 64-bit portability warning attribute, so the
|
||||||
|
// prototype cannot substitute unsigned int for size_t, or you get
|
||||||
|
// spurious warnings.
|
||||||
|
const char &operator[] (size_t index) const { return Chars[index]; }
|
||||||
|
#else
|
||||||
|
const char &operator[] (unsigned int index) const { return Chars[index]; }
|
||||||
|
#endif
|
||||||
|
const char &operator[] (unsigned long index) const { return Chars[index]; }
|
||||||
|
const char &operator[] (unsigned long long index) const { return Chars[index]; }
|
||||||
|
|
||||||
|
FString &operator = (const FString &other);
|
||||||
|
FString &operator = (FString &&other);
|
||||||
|
FString &operator = (const char *copyStr);
|
||||||
|
|
||||||
|
FString operator + (const FString &tail) const;
|
||||||
|
FString operator + (const char *tail) const;
|
||||||
|
FString operator + (char tail) const;
|
||||||
|
friend FString operator + (const char *head, const FString &tail);
|
||||||
|
friend FString operator + (char head, const FString &tail);
|
||||||
|
|
||||||
|
FString &operator += (const FString &tail);
|
||||||
|
FString &operator += (const char *tail);
|
||||||
|
FString &operator += (char tail);
|
||||||
|
FString &AppendCStrPart (const char *tail, size_t tailLen);
|
||||||
|
FString &CopyCStrPart(const char *tail, size_t tailLen);
|
||||||
|
|
||||||
|
FString &operator << (const FString &tail) { return *this += tail; }
|
||||||
|
FString &operator << (const char *tail) { return *this += tail; }
|
||||||
|
FString &operator << (char tail) { return *this += tail; }
|
||||||
|
|
||||||
|
const char &Front() const { assert(IsNotEmpty()); return Chars[0]; }
|
||||||
|
const char &Back() const { assert(IsNotEmpty()); return Chars[Len() - 1]; }
|
||||||
|
|
||||||
|
FString Left (size_t numChars) const;
|
||||||
|
FString Right (size_t numChars) const;
|
||||||
|
FString Mid (size_t pos, size_t numChars = ~(size_t)0) const;
|
||||||
|
|
||||||
|
void AppendCharacter(int codepoint);
|
||||||
|
void DeleteLastCharacter();
|
||||||
|
|
||||||
|
ptrdiff_t IndexOf (const FString &substr, ptrdiff_t startIndex=0) const;
|
||||||
|
ptrdiff_t IndexOf (const char *substr, ptrdiff_t startIndex=0) const;
|
||||||
|
ptrdiff_t IndexOf (char subchar, ptrdiff_t startIndex=0) const;
|
||||||
|
|
||||||
|
ptrdiff_t IndexOfAny (const FString &charset, ptrdiff_t startIndex=0) const;
|
||||||
|
ptrdiff_t IndexOfAny (const char *charset, ptrdiff_t startIndex=0) const;
|
||||||
|
|
||||||
|
// This is only kept for backwards compatibility with old ZScript versions that used this function and depend on its bug.
|
||||||
|
ptrdiff_t LastIndexOf (char subchar) const;
|
||||||
|
ptrdiff_t LastIndexOfBroken (const FString &substr, ptrdiff_t endIndex) const;
|
||||||
|
ptrdiff_t LastIndexOf (char subchar, ptrdiff_t endIndex) const;
|
||||||
|
|
||||||
|
ptrdiff_t LastIndexOfAny (const FString &charset) const;
|
||||||
|
ptrdiff_t LastIndexOfAny (const char *charset) const;
|
||||||
|
ptrdiff_t LastIndexOfAny (const FString &charset, ptrdiff_t endIndex) const;
|
||||||
|
ptrdiff_t LastIndexOfAny (const char *charset, ptrdiff_t endIndex) const;
|
||||||
|
|
||||||
|
ptrdiff_t LastIndexOf (const FString &substr) const;
|
||||||
|
ptrdiff_t LastIndexOf (const FString &substr, ptrdiff_t endIndex) const;
|
||||||
|
ptrdiff_t LastIndexOf (const char *substr) const;
|
||||||
|
ptrdiff_t LastIndexOf (const char *substr, ptrdiff_t endIndex) const;
|
||||||
|
ptrdiff_t LastIndexOf (const char *substr, ptrdiff_t endIndex, size_t substrlen) const;
|
||||||
|
|
||||||
|
void ToUpper ();
|
||||||
|
void ToLower ();
|
||||||
|
FString MakeUpper() const;
|
||||||
|
FString MakeLower() const;
|
||||||
|
|
||||||
|
void StripLeft ();
|
||||||
|
void StripLeft (const FString &charset);
|
||||||
|
void StripLeft (const char *charset);
|
||||||
|
|
||||||
|
void StripRight ();
|
||||||
|
void StripRight (const FString &charset);
|
||||||
|
void StripRight (const char *charset);
|
||||||
|
|
||||||
|
void StripLeftRight ();
|
||||||
|
void StripLeftRight (const FString &charset);
|
||||||
|
void StripLeftRight (const char *charset);
|
||||||
|
|
||||||
|
void Insert (size_t index, const FString &instr);
|
||||||
|
void Insert (size_t index, const char *instr);
|
||||||
|
void Insert (size_t index, const char *instr, size_t instrlen);
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
void ReplaceChars (Func IsOldChar, char newchar)
|
||||||
|
{
|
||||||
|
size_t i, j;
|
||||||
|
|
||||||
|
LockBuffer();
|
||||||
|
for (i = 0, j = Len(); i < j; ++i)
|
||||||
|
{
|
||||||
|
if (IsOldChar(Chars[i]))
|
||||||
|
{
|
||||||
|
Chars[i] = newchar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UnlockBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReplaceChars (char oldchar, char newchar);
|
||||||
|
void ReplaceChars (const char *oldcharset, char newchar);
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
void StripChars (Func IsKillChar)
|
||||||
|
{
|
||||||
|
size_t read, write, mylen;
|
||||||
|
|
||||||
|
LockBuffer();
|
||||||
|
for (read = write = 0, mylen = Len(); read < mylen; ++read)
|
||||||
|
{
|
||||||
|
if (!IsKillChar(Chars[read]))
|
||||||
|
{
|
||||||
|
Chars[write++] = Chars[read];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Chars[write] = '\0';
|
||||||
|
ReallocBuffer (write);
|
||||||
|
UnlockBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StripChars (char killchar);
|
||||||
|
void StripChars (const char *killcharset);
|
||||||
|
|
||||||
|
void MergeChars (char merger);
|
||||||
|
void MergeChars (char merger, char newchar);
|
||||||
|
void MergeChars (const char *charset, char newchar);
|
||||||
|
|
||||||
|
void Substitute (const FString &oldstr, const FString &newstr);
|
||||||
|
void Substitute (const char *oldstr, const FString &newstr);
|
||||||
|
void Substitute (const FString &oldstr, const char *newstr);
|
||||||
|
void Substitute (const char *oldstr, const char *newstr);
|
||||||
|
void Substitute (const char *oldstr, const char *newstr, size_t oldstrlen, size_t newstrlen);
|
||||||
|
|
||||||
|
void Format (const char *fmt, ...) PRINTFISH(3);
|
||||||
|
void AppendFormat (const char *fmt, ...) PRINTFISH(3);
|
||||||
|
void VFormat (const char *fmt, va_list arglist) PRINTFISH(0);
|
||||||
|
void VAppendFormat (const char *fmt, va_list arglist) PRINTFISH(0);
|
||||||
|
|
||||||
|
bool IsInt () const;
|
||||||
|
bool IsFloat () const;
|
||||||
|
int64_t ToLong (int base=0) const;
|
||||||
|
uint64_t ToULong (int base=0) const;
|
||||||
|
double ToDouble () const;
|
||||||
|
|
||||||
|
size_t Len() const { return Data()->Len; }
|
||||||
|
size_t CharacterCount() const;
|
||||||
|
int GetNextCharacter(int &position) const;
|
||||||
|
bool IsEmpty() const { return Len() == 0; }
|
||||||
|
bool IsNotEmpty() const { return Len() != 0; }
|
||||||
|
|
||||||
|
void Truncate (size_t newlen);
|
||||||
|
void Remove(size_t index, size_t remlen);
|
||||||
|
|
||||||
|
int Compare (const FString &other) const { return strcmp (Chars, other.Chars); }
|
||||||
|
int Compare (const char *other) const { return strcmp (Chars, other); }
|
||||||
|
int Compare(const FString &other, int len) const { return strncmp(Chars, other.Chars, len); }
|
||||||
|
int Compare(const char *other, int len) const { return strncmp(Chars, other, len); }
|
||||||
|
|
||||||
|
int CompareNoCase (const FString &other) const { return stricmp (Chars, other.Chars); }
|
||||||
|
int CompareNoCase (const char *other) const { return stricmp (Chars, other); }
|
||||||
|
int CompareNoCase(const FString &other, int len) const { return strnicmp(Chars, other.Chars, len); }
|
||||||
|
int CompareNoCase(const char *other, int len) const { return strnicmp(Chars, other, len); }
|
||||||
|
|
||||||
|
enum EmptyTokenType
|
||||||
|
{
|
||||||
|
TOK_SKIPEMPTY = 0,
|
||||||
|
TOK_KEEPEMPTY = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
TArray<FString> Split(const FString &delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
|
||||||
|
TArray<FString> Split(const char *delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
|
||||||
|
void Split(TArray<FString>& tokens, const FString &delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
|
||||||
|
void Split(TArray<FString>& tokens, const char *delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const FStringData *Data() const { return (FStringData *)Chars - 1; }
|
||||||
|
FStringData *Data() { return (FStringData *)Chars - 1; }
|
||||||
|
|
||||||
|
void ResetToNull()
|
||||||
|
{
|
||||||
|
NullString.RefCount++;
|
||||||
|
Chars = &NullString.Nothing[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachToOther (const FString &other);
|
||||||
|
void AllocBuffer (size_t len);
|
||||||
|
void ReallocBuffer (size_t newlen);
|
||||||
|
|
||||||
|
static int FormatHelper (void *data, const char *str, int len);
|
||||||
|
static void StrCopy (char *to, const char *from, size_t len);
|
||||||
|
static void StrCopy (char *to, const FString &from);
|
||||||
|
|
||||||
|
char *Chars;
|
||||||
|
|
||||||
|
static FNullStringData NullString;
|
||||||
|
|
||||||
|
friend struct FStringData;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool operator == (const FString &other) const
|
||||||
|
{
|
||||||
|
return Compare(other) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator != (const FString &other) const
|
||||||
|
{
|
||||||
|
return Compare(other) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator < (const FString &other) const
|
||||||
|
{
|
||||||
|
return Compare(other) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator > (const FString &other) const
|
||||||
|
{
|
||||||
|
return Compare(other) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator <= (const FString &other) const
|
||||||
|
{
|
||||||
|
return Compare(other) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator >= (const FString &other) const
|
||||||
|
{
|
||||||
|
return Compare(other) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are needed to block the default char * conversion operator from making a mess.
|
||||||
|
bool operator == (const char *) const = delete;
|
||||||
|
bool operator != (const char *) const = delete;
|
||||||
|
bool operator < (const char *) const = delete;
|
||||||
|
bool operator > (const char *) const = delete;
|
||||||
|
bool operator <= (const char *) const = delete;
|
||||||
|
bool operator >= (const char *) const = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
// These are also needed to block the default char * conversion operator from making a mess.
|
||||||
|
bool operator == (const char *, const FString &) = delete;
|
||||||
|
bool operator != (const char *, const FString &) = delete;
|
||||||
|
bool operator < (const char *, const FString &) = delete;
|
||||||
|
bool operator > (const char *, const FString &) = delete;
|
||||||
|
bool operator <= (const char *, const FString &) = delete;
|
||||||
|
bool operator >= (const char *, const FString &) = delete;
|
||||||
|
|
||||||
|
class FStringf : public FString
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FStringf(const char *fmt, ...);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace StringFormat
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
// Format specification flags
|
||||||
|
F_MINUS = 1,
|
||||||
|
F_PLUS = 2,
|
||||||
|
F_ZERO = 4,
|
||||||
|
F_BLANK = 8,
|
||||||
|
F_HASH = 16,
|
||||||
|
|
||||||
|
F_SIGNED = 32,
|
||||||
|
F_NEGATIVE = 64,
|
||||||
|
F_ZEROVALUE = 128,
|
||||||
|
F_FPT = 256,
|
||||||
|
|
||||||
|
// Format specification size prefixes
|
||||||
|
F_HALFHALF = 0x1000, // hh
|
||||||
|
F_HALF = 0x2000, // h
|
||||||
|
F_LONG = 0x3000, // l
|
||||||
|
F_LONGLONG = 0x4000, // ll or I64
|
||||||
|
F_BIGI = 0x5000, // I
|
||||||
|
F_PTRDIFF = 0x6000, // t
|
||||||
|
F_SIZE = 0x7000, // z
|
||||||
|
};
|
||||||
|
typedef int (*OutputFunc)(void *data, const char *str, int len);
|
||||||
|
|
||||||
|
int VWorker (OutputFunc output, void *outputData, const char *fmt, va_list arglist);
|
||||||
|
int Worker (OutputFunc output, void *outputData, const char *fmt, ...);
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef PRINTFISH
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
// Hash FStrings on their contents. (used by TMap)
|
||||||
|
#include "superfasthash.h"
|
||||||
|
|
||||||
|
template<> struct THashTraits<FString>
|
||||||
|
{
|
||||||
|
hash_t Hash(const FString &key) { return (hash_t)SuperFastHash(key.GetChars(), key.Len()); }
|
||||||
|
// Compares two keys, returning zero if they are the same.
|
||||||
|
int Compare(const FString &left, const FString &right) { return left.Compare(right); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StringNoCaseHashTraits
|
||||||
|
{
|
||||||
|
hash_t Hash(const FString& key) { return (hash_t)SuperFastHashI(key.GetChars(), key.Len()); }
|
||||||
|
// Compares two keys, returning zero if they are the same.
|
||||||
|
int Compare(const FString& left, const FString& right) { return left.CompareNoCase(right); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -125,6 +125,7 @@ class Vec2
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Vec2();
|
Vec2();
|
||||||
|
Vec2(const float v);
|
||||||
Vec2(const float x, const float y);
|
Vec2(const float x, const float y);
|
||||||
|
|
||||||
void Set(const float x, const float y);
|
void Set(const float x, const float y);
|
||||||
|
@ -191,6 +192,7 @@ class Vec3
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Vec3();
|
Vec3();
|
||||||
|
Vec3(const float v);
|
||||||
Vec3(const float x, const float y, const float z);
|
Vec3(const float x, const float y, const float z);
|
||||||
|
|
||||||
void Set(const float x, const float y, const float z);
|
void Set(const float x, const float y, const float z);
|
||||||
|
@ -582,6 +584,11 @@ inline Vec2::Vec2()
|
||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Vec2::Vec2(const float v)
|
||||||
|
{
|
||||||
|
Set(v, v);
|
||||||
|
}
|
||||||
|
|
||||||
inline Vec2::Vec2(const float x, const float y)
|
inline Vec2::Vec2(const float x, const float y)
|
||||||
{
|
{
|
||||||
Set(x, y);
|
Set(x, y);
|
||||||
|
@ -886,6 +893,11 @@ inline Vec3::Vec3()
|
||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Vec3::Vec3(const float v)
|
||||||
|
{
|
||||||
|
Set(v, v, v);
|
||||||
|
}
|
||||||
|
|
||||||
inline Vec3::Vec3(const float x, const float y, const float z)
|
inline Vec3::Vec3(const float x, const float y, const float z)
|
||||||
{
|
{
|
||||||
Set(x, y, z);
|
Set(x, y, z);
|
||||||
|
|
255
src/models/m_swap.h
Normal file
255
src/models/m_swap.h
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
//
|
||||||
|
// DESCRIPTION:
|
||||||
|
// Endianess handling, swapping 16bit and 32bit.
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __M_SWAP_H__
|
||||||
|
#define __M_SWAP_H__
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// Endianess handling.
|
||||||
|
// WAD files are stored little endian.
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <libkern/OSByteOrder.h>
|
||||||
|
|
||||||
|
inline short LittleShort(short x)
|
||||||
|
{
|
||||||
|
return (short)OSSwapLittleToHostInt16((uint16_t)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short LittleShort(unsigned short x)
|
||||||
|
{
|
||||||
|
return OSSwapLittleToHostInt16(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline short LittleShort(int x)
|
||||||
|
{
|
||||||
|
return OSSwapLittleToHostInt16((uint16_t)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short LittleShort(unsigned int x)
|
||||||
|
{
|
||||||
|
return OSSwapLittleToHostInt16((uint16_t)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int LittleLong(int x)
|
||||||
|
{
|
||||||
|
return OSSwapLittleToHostInt32((uint32_t)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int LittleLong(unsigned int x)
|
||||||
|
{
|
||||||
|
return OSSwapLittleToHostInt32(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline short BigShort(short x)
|
||||||
|
{
|
||||||
|
return (short)OSSwapBigToHostInt16((uint16_t)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short BigShort(unsigned short x)
|
||||||
|
{
|
||||||
|
return OSSwapBigToHostInt16(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int BigLong(int x)
|
||||||
|
{
|
||||||
|
return OSSwapBigToHostInt32((uint32_t)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int BigLong(unsigned int x)
|
||||||
|
{
|
||||||
|
return OSSwapBigToHostInt32(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined __BIG_ENDIAN__
|
||||||
|
|
||||||
|
// Swap 16bit, that is, MSB and LSB byte.
|
||||||
|
// No masking with 0xFF should be necessary.
|
||||||
|
inline short LittleShort (short x)
|
||||||
|
{
|
||||||
|
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short LittleShort (unsigned short x)
|
||||||
|
{
|
||||||
|
return (unsigned short)((x>>8) | (x<<8));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline short LittleShort (int x)
|
||||||
|
{
|
||||||
|
return LittleShort((short)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short LittleShort (unsigned int x)
|
||||||
|
{
|
||||||
|
return LittleShort((unsigned short)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swapping 32bit.
|
||||||
|
inline unsigned int LittleLong (unsigned int x)
|
||||||
|
{
|
||||||
|
return (unsigned int)(
|
||||||
|
(x>>24)
|
||||||
|
| ((x>>8) & 0xff00)
|
||||||
|
| ((x<<8) & 0xff0000)
|
||||||
|
| (x<<24));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int LittleLong (int x)
|
||||||
|
{
|
||||||
|
return (int)(
|
||||||
|
(((unsigned int)x)>>24)
|
||||||
|
| ((((unsigned int)x)>>8) & 0xff00)
|
||||||
|
| ((((unsigned int)x)<<8) & 0xff0000)
|
||||||
|
| (((unsigned int)x)<<24));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline short BigShort(short x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short BigShort(unsigned short x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int BigLong(unsigned int x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int BigLong(int x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
inline short LittleShort(short x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short LittleShort(unsigned short x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int LittleLong(unsigned int x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int LittleLong(int x)
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
|
||||||
|
inline short BigShort(short x)
|
||||||
|
{
|
||||||
|
return (short)_byteswap_ushort((unsigned short)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short BigShort(unsigned short x)
|
||||||
|
{
|
||||||
|
return _byteswap_ushort(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int BigLong(int x)
|
||||||
|
{
|
||||||
|
return (int)_byteswap_ulong((unsigned long)x);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int BigLong(unsigned int x)
|
||||||
|
{
|
||||||
|
return (unsigned int)_byteswap_ulong((unsigned long)x);
|
||||||
|
}
|
||||||
|
#pragma warning (default: 4035)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
inline short BigShort (short x)
|
||||||
|
{
|
||||||
|
return (short)((((unsigned short)x)>>8) | (((unsigned short)x)<<8));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned short BigShort (unsigned short x)
|
||||||
|
{
|
||||||
|
return (unsigned short)((x>>8) | (x<<8));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int BigLong (unsigned int x)
|
||||||
|
{
|
||||||
|
return (unsigned int)(
|
||||||
|
(x>>24)
|
||||||
|
| ((x>>8) & 0xff00)
|
||||||
|
| ((x<<8) & 0xff0000)
|
||||||
|
| (x<<24));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int BigLong (int x)
|
||||||
|
{
|
||||||
|
return (int)(
|
||||||
|
(((unsigned int)x)>>24)
|
||||||
|
| ((((unsigned int)x)>>8) & 0xff00)
|
||||||
|
| ((((unsigned int)x)<<8) & 0xff0000)
|
||||||
|
| (((unsigned int)x)<<24));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __BIG_ENDIAN__
|
||||||
|
|
||||||
|
// These may be destructive so they should create errors
|
||||||
|
unsigned long BigLong(unsigned long) = delete;
|
||||||
|
long BigLong(long) = delete;
|
||||||
|
unsigned long LittleLong(unsigned long) = delete;
|
||||||
|
long LittleLong(long) = delete;
|
||||||
|
|
||||||
|
|
||||||
|
// Data accessors, since some data is highly likely to be unaligned.
|
||||||
|
#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__)
|
||||||
|
inline int GetShort(const unsigned char *foo)
|
||||||
|
{
|
||||||
|
return *(const short *)foo;
|
||||||
|
}
|
||||||
|
inline int GetInt(const unsigned char *foo)
|
||||||
|
{
|
||||||
|
return *(const int *)foo;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline int GetShort(const unsigned char *foo)
|
||||||
|
{
|
||||||
|
return short(foo[0] | (foo[1] << 8));
|
||||||
|
}
|
||||||
|
inline int GetInt(const unsigned char *foo)
|
||||||
|
{
|
||||||
|
return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
inline int GetBigInt(const unsigned char *foo)
|
||||||
|
{
|
||||||
|
return int((foo[0] << 24) | (foo[1] << 16) | (foo[2] << 8) | foo[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __BIG_ENDIAN__
|
||||||
|
inline int GetNativeInt(const unsigned char *foo)
|
||||||
|
{
|
||||||
|
return GetBigInt(foo);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline int GetNativeInt(const unsigned char *foo)
|
||||||
|
{
|
||||||
|
return GetInt(foo);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __M_SWAP_H__
|
200
src/models/model.cpp
Normal file
200
src/models/model.cpp
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright(C) 2005-2016 Christoph Oelckers
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
** gl_models.cpp
|
||||||
|
**
|
||||||
|
** General model handling code
|
||||||
|
**
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include "model_ue1.h"
|
||||||
|
#include "model_obj.h"
|
||||||
|
#include "model_md2.h"
|
||||||
|
#include "model_md3.h"
|
||||||
|
#include "modelrenderer.h"
|
||||||
|
|
||||||
|
FFileSystem fileSystem;
|
||||||
|
FTextureManager TexMan;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
FModel::FModel()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NumModelRendererTypes; i++)
|
||||||
|
mVBuf[i] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FModel::~FModel()
|
||||||
|
{
|
||||||
|
DestroyVertexBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FModel::DestroyVertexBuffer()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NumModelRendererTypes; i++)
|
||||||
|
{
|
||||||
|
delete mVBuf[i];
|
||||||
|
mVBuf[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FindGFXFile
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
static int FindGFXFile(FString & fn)
|
||||||
|
{
|
||||||
|
int lump = fileSystem.CheckNumForFullName(fn); // if we find something that matches the name plus the extension, return it and do not enter the substitution logic below.
|
||||||
|
if (lump != -1) return lump;
|
||||||
|
|
||||||
|
int best = -1;
|
||||||
|
auto dot = fn.LastIndexOf('.');
|
||||||
|
auto slash = fn.LastIndexOf('/');
|
||||||
|
if (dot > slash) fn.Truncate(dot);
|
||||||
|
|
||||||
|
static const char * extensions[] = { ".png", ".jpg", ".tga", ".pcx", nullptr };
|
||||||
|
|
||||||
|
for (const char ** extp=extensions; *extp; extp++)
|
||||||
|
{
|
||||||
|
int lump = fileSystem.CheckNumForFullName(fn + *extp);
|
||||||
|
if (lump >= best) best = lump;
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// LoadSkin
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
FTextureID LoadSkin(const char * path, const char * fn)
|
||||||
|
{
|
||||||
|
FString buffer;
|
||||||
|
|
||||||
|
buffer.Format("%s%s", path, fn);
|
||||||
|
|
||||||
|
int texlump = FindGFXFile(buffer);
|
||||||
|
const char * const texname = texlump < 0 ? fn : fileSystem.GetFileFullName(texlump);
|
||||||
|
return TexMan.CheckForTexture(texname, ETextureType::Any, FTextureManager::TEXMAN_TryAny);
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// ModelFrameHash
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
int ModelFrameHash(FSpriteModelFrame * smf)
|
||||||
|
{
|
||||||
|
const uint32_t *table = GetCRCTable ();
|
||||||
|
uint32_t hash = 0xffffffff;
|
||||||
|
|
||||||
|
const char * s = (const char *)(&smf->type); // this uses type, sprite and frame for hashing
|
||||||
|
const char * se= (const char *)(&smf->hashnext);
|
||||||
|
|
||||||
|
for (; s<se; s++)
|
||||||
|
{
|
||||||
|
hash = CRC1 (hash, *s, table);
|
||||||
|
}
|
||||||
|
return hash ^ 0xffffffff;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FindModel
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
std::unique_ptr<FModel> LoadModel(const char * path, const char * modelfile)
|
||||||
|
{
|
||||||
|
std::unique_ptr<FModel> model;
|
||||||
|
FString fullname;
|
||||||
|
|
||||||
|
fullname = FString(path) + modelfile;
|
||||||
|
int lump = fileSystem.CheckNumForFullName(fullname);
|
||||||
|
|
||||||
|
if (lump<0)
|
||||||
|
{
|
||||||
|
//Printf("FindModel: '%s' not found\n", fullname.GetChars());
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = fileSystem.FileLength(lump);
|
||||||
|
FileData lumpd = fileSystem.ReadFile(lump);
|
||||||
|
char * buffer = (char*)lumpd.GetMem();
|
||||||
|
|
||||||
|
if ( (size_t)fullname.LastIndexOf("_d.3d") == fullname.Len()-5 )
|
||||||
|
{
|
||||||
|
FString anivfile = fullname.GetChars();
|
||||||
|
anivfile.Substitute("_d.3d","_a.3d");
|
||||||
|
if ( fileSystem.CheckNumForFullName(anivfile) > 0 )
|
||||||
|
{
|
||||||
|
model.reset(new FUE1Model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( (size_t)fullname.LastIndexOf("_a.3d") == fullname.Len()-5 )
|
||||||
|
{
|
||||||
|
FString datafile = fullname.GetChars();
|
||||||
|
datafile.Substitute("_a.3d","_d.3d");
|
||||||
|
if ( fileSystem.CheckNumForFullName(datafile) > 0 )
|
||||||
|
{
|
||||||
|
model.reset(new FUE1Model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 4 )
|
||||||
|
{
|
||||||
|
model.reset(new FOBJModel);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (!memcmp(buffer, "DMDM", 4))
|
||||||
|
{
|
||||||
|
model.reset(new FDMDModel);
|
||||||
|
}
|
||||||
|
else if (!memcmp(buffer, "IDP2", 4))
|
||||||
|
{
|
||||||
|
model.reset(new FMD2Model);
|
||||||
|
}
|
||||||
|
else if (!memcmp(buffer, "IDP3", 4))
|
||||||
|
{
|
||||||
|
model.reset(new FMD3Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!model)
|
||||||
|
return model;
|
||||||
|
|
||||||
|
if (!model->Load(path, lump, buffer, len))
|
||||||
|
{
|
||||||
|
model.reset();
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The vertex buffer cannot be initialized here because this gets called before OpenGL is initialized
|
||||||
|
model->mFileName = fullname;
|
||||||
|
return model;
|
||||||
|
}
|
220
src/models/model.h
Normal file
220
src/models/model.h
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "framework/tarray.h"
|
||||||
|
#include "framework/templates.h"
|
||||||
|
#include "framework/zstring.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class FModelRenderer;
|
||||||
|
class FGameTexture;
|
||||||
|
class IModelVertexBuffer;
|
||||||
|
class FModel;
|
||||||
|
struct FSpriteModelFrame;
|
||||||
|
|
||||||
|
struct FileData
|
||||||
|
{
|
||||||
|
char* GetMem() { return nullptr; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class FFileSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int CheckNumForFullName(const FString& fullname) { return -1; }
|
||||||
|
int FileLength(int lump) { return 0; }
|
||||||
|
FileData ReadFile(int lump) { return {}; }
|
||||||
|
const char* GetFileFullName(int lump, bool returnshort = true) const { return ""; }
|
||||||
|
};
|
||||||
|
|
||||||
|
extern FFileSystem fileSystem;
|
||||||
|
|
||||||
|
class FTextureID
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool isValid() const { return false; }
|
||||||
|
int GetIndex() const { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class FNullTextureID : public FTextureID
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
};
|
||||||
|
|
||||||
|
class FGameTexture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ETextureType : uint8_t
|
||||||
|
{
|
||||||
|
Any,
|
||||||
|
Wall,
|
||||||
|
Flat,
|
||||||
|
Sprite,
|
||||||
|
WallPatch,
|
||||||
|
Build, // no longer used but needs to remain for ZScript
|
||||||
|
SkinSprite,
|
||||||
|
Decal,
|
||||||
|
MiscPatch,
|
||||||
|
FontChar,
|
||||||
|
Override, // For patches between TX_START/TX_END
|
||||||
|
Autopage, // Automap background - used to enable the use of FAutomapTexture
|
||||||
|
SkinGraphic,
|
||||||
|
Null,
|
||||||
|
FirstDefined,
|
||||||
|
Special,
|
||||||
|
SWCanvas,
|
||||||
|
};
|
||||||
|
|
||||||
|
class FTextureManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FGameTexture* GetGameTexture()
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameTexture* GetGameTexture(FTextureID, bool)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
TEXMAN_TryAny = 1,
|
||||||
|
TEXMAN_Overridable = 2,
|
||||||
|
TEXMAN_ReturnFirst = 4,
|
||||||
|
TEXMAN_AllowSkins = 8,
|
||||||
|
TEXMAN_ShortNameOnly = 16,
|
||||||
|
TEXMAN_DontCreate = 32,
|
||||||
|
TEXMAN_Localize = 64,
|
||||||
|
TEXMAN_ForceLookup = 128,
|
||||||
|
TEXMAN_NoAlias = 256,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
HIT_Wall = 1,
|
||||||
|
HIT_Flat = 2,
|
||||||
|
HIT_Sky = 4,
|
||||||
|
HIT_Sprite = 8,
|
||||||
|
|
||||||
|
HIT_Columnmode = HIT_Wall | HIT_Sky | HIT_Sprite
|
||||||
|
};
|
||||||
|
|
||||||
|
FTextureID CheckForTexture(const char* name, ETextureType usetype, uint32_t flags = TEXMAN_TryAny) { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
extern FTextureManager TexMan;
|
||||||
|
|
||||||
|
struct FModelVertex
|
||||||
|
{
|
||||||
|
float x, y, z; // world position
|
||||||
|
float u, v; // texture coordinates
|
||||||
|
unsigned packedNormal; // normal vector as GL_INT_2_10_10_10_REV.
|
||||||
|
float lu, lv; // lightmap texture coordinates
|
||||||
|
float lindex; // lightmap texture index
|
||||||
|
|
||||||
|
void Set(float xx, float yy, float zz, float uu, float vv)
|
||||||
|
{
|
||||||
|
x = xx;
|
||||||
|
y = yy;
|
||||||
|
z = zz;
|
||||||
|
u = uu;
|
||||||
|
v = vv;
|
||||||
|
lindex = -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNormal(float nx, float ny, float nz)
|
||||||
|
{
|
||||||
|
int inx = clamp(int(nx * 512), -512, 511);
|
||||||
|
int iny = clamp(int(ny * 512), -512, 511);
|
||||||
|
int inz = clamp(int(nz * 512), -512, 511);
|
||||||
|
int inw = 0;
|
||||||
|
packedNormal = (inw << 30) | ((inz & 1023) << 20) | ((iny & 1023) << 10) | (inx & 1023);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define VMO ((FModelVertex*)nullptr)
|
||||||
|
|
||||||
|
class IModelVertexBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IModelVertexBuffer() { }
|
||||||
|
|
||||||
|
virtual FModelVertex* LockVertexBuffer(unsigned int size) = 0;
|
||||||
|
virtual void UnlockVertexBuffer() = 0;
|
||||||
|
|
||||||
|
virtual unsigned int* LockIndexBuffer(unsigned int size) = 0;
|
||||||
|
virtual void UnlockIndexBuffer() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
FTextureID LoadSkin(const char* path, const char* fn);
|
||||||
|
|
||||||
|
#define MD3_MAX_SURFACES 32
|
||||||
|
#define MIN_MODELS 4
|
||||||
|
|
||||||
|
struct FSpriteModelFrame
|
||||||
|
{
|
||||||
|
uint8_t modelsAmount = 0;
|
||||||
|
TArray<int> modelIDs;
|
||||||
|
TArray<FTextureID> skinIDs;
|
||||||
|
TArray<FTextureID> surfaceskinIDs;
|
||||||
|
TArray<int> modelframes;
|
||||||
|
float xscale, yscale, zscale;
|
||||||
|
// [BB] Added zoffset, rotation parameters and flags.
|
||||||
|
// Added xoffset, yoffset
|
||||||
|
float xoffset, yoffset, zoffset;
|
||||||
|
float xrotate, yrotate, zrotate;
|
||||||
|
float rotationCenterX, rotationCenterY, rotationCenterZ;
|
||||||
|
float rotationSpeed;
|
||||||
|
unsigned int flags;
|
||||||
|
const void* type; // used for hashing, must point to something usable as identifier for the model's owner.
|
||||||
|
short sprite;
|
||||||
|
short frame;
|
||||||
|
int hashnext;
|
||||||
|
float angleoffset;
|
||||||
|
// added pithoffset, rolloffset.
|
||||||
|
float pitchoffset, rolloffset; // I don't want to bother with type transformations, so I made this variables float.
|
||||||
|
bool isVoxel;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ModelRendererType
|
||||||
|
{
|
||||||
|
GLModelRendererType,
|
||||||
|
SWModelRendererType,
|
||||||
|
PolyModelRendererType,
|
||||||
|
NumModelRendererTypes
|
||||||
|
};
|
||||||
|
|
||||||
|
class FModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FModel();
|
||||||
|
virtual ~FModel();
|
||||||
|
|
||||||
|
virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length) = 0;
|
||||||
|
virtual int FindFrame(const char * name) = 0;
|
||||||
|
virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0) = 0;
|
||||||
|
virtual void BuildVertexBuffer(FModelRenderer *renderer) = 0;
|
||||||
|
virtual void AddSkins(uint8_t *hitlist) = 0;
|
||||||
|
virtual float getAspectFactor(float vscale) { return 1.f; }
|
||||||
|
|
||||||
|
void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; }
|
||||||
|
IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; }
|
||||||
|
void DestroyVertexBuffer();
|
||||||
|
|
||||||
|
const FSpriteModelFrame *curSpriteMDLFrame;
|
||||||
|
int curMDLIndex;
|
||||||
|
void PushSpriteMDLFrame(const FSpriteModelFrame *smf, int index) { curSpriteMDLFrame = smf; curMDLIndex = index; };
|
||||||
|
|
||||||
|
FString mFileName;
|
||||||
|
|
||||||
|
private:
|
||||||
|
IModelVertexBuffer *mVBuf[NumModelRendererTypes];
|
||||||
|
};
|
||||||
|
|
||||||
|
//int ModelFrameHash(FSpriteModelFrame* smf);
|
||||||
|
std::unique_ptr<FModel> LoadModel(const char* path, const char* modelfile);
|
||||||
|
|
137
src/models/model_md2.h
Normal file
137
src/models/model_md2.h
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
#pragma once
|
||||||
|
#include "model.h"
|
||||||
|
|
||||||
|
#define MD2_MAGIC 0x32504449
|
||||||
|
#define DMD_MAGIC 0x4D444D44
|
||||||
|
#define MAX_LODS 4
|
||||||
|
|
||||||
|
class FDMDModel : public FModel
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
|
||||||
|
struct FTriangle
|
||||||
|
{
|
||||||
|
unsigned short vertexIndices[3];
|
||||||
|
unsigned short textureIndices[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct DMDHeader
|
||||||
|
{
|
||||||
|
int magic;
|
||||||
|
int version;
|
||||||
|
int flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DMDModelVertex
|
||||||
|
{
|
||||||
|
float xyz[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FTexCoord
|
||||||
|
{
|
||||||
|
short s, t;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FGLCommandVertex
|
||||||
|
{
|
||||||
|
float s, t;
|
||||||
|
int index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DMDInfo
|
||||||
|
{
|
||||||
|
int skinWidth;
|
||||||
|
int skinHeight;
|
||||||
|
int frameSize;
|
||||||
|
int numSkins;
|
||||||
|
int numVertices;
|
||||||
|
int numTexCoords;
|
||||||
|
int numFrames;
|
||||||
|
int numLODs;
|
||||||
|
int offsetSkins;
|
||||||
|
int offsetTexCoords;
|
||||||
|
int offsetFrames;
|
||||||
|
int offsetLODs;
|
||||||
|
int offsetEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModelFrame
|
||||||
|
{
|
||||||
|
char name[16];
|
||||||
|
unsigned int vindex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModelFrameVertexData
|
||||||
|
{
|
||||||
|
DMDModelVertex *vertices;
|
||||||
|
DMDModelVertex *normals;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DMDLoDInfo
|
||||||
|
{
|
||||||
|
int numTriangles;
|
||||||
|
int numGlCommands;
|
||||||
|
int offsetTriangles;
|
||||||
|
int offsetGlCommands;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DMDLoD
|
||||||
|
{
|
||||||
|
FTriangle * triangles;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int mLumpNum;
|
||||||
|
DMDHeader header;
|
||||||
|
DMDInfo info;
|
||||||
|
FTextureID * skins;
|
||||||
|
ModelFrame * frames;
|
||||||
|
bool allowTexComp; // Allow texture compression with this.
|
||||||
|
|
||||||
|
// Temp data only needed for buffer construction
|
||||||
|
FTexCoord * texCoords;
|
||||||
|
ModelFrameVertexData *framevtx;
|
||||||
|
DMDLoDInfo lodInfo[MAX_LODS];
|
||||||
|
DMDLoD lods[MAX_LODS];
|
||||||
|
|
||||||
|
public:
|
||||||
|
FDMDModel()
|
||||||
|
{
|
||||||
|
mLumpNum = -1;
|
||||||
|
frames = NULL;
|
||||||
|
skins = NULL;
|
||||||
|
for (int i = 0; i < MAX_LODS; i++)
|
||||||
|
{
|
||||||
|
lods[i].triangles = NULL;
|
||||||
|
}
|
||||||
|
info.numLODs = 0;
|
||||||
|
texCoords = NULL;
|
||||||
|
framevtx = NULL;
|
||||||
|
}
|
||||||
|
virtual ~FDMDModel();
|
||||||
|
|
||||||
|
virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length);
|
||||||
|
virtual int FindFrame(const char * name);
|
||||||
|
virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0);
|
||||||
|
virtual void LoadGeometry();
|
||||||
|
virtual void AddSkins(uint8_t *hitlist);
|
||||||
|
|
||||||
|
void UnloadGeometry();
|
||||||
|
void BuildVertexBuffer(FModelRenderer *renderer);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// This uses the same internal representation as DMD
|
||||||
|
class FMD2Model : public FDMDModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FMD2Model() {}
|
||||||
|
virtual ~FMD2Model();
|
||||||
|
|
||||||
|
virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length);
|
||||||
|
virtual void LoadGeometry();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
75
src/models/model_md3.h
Normal file
75
src/models/model_md3.h
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
#include "model.h"
|
||||||
|
|
||||||
|
#define MD3_MAGIC 0x33504449
|
||||||
|
|
||||||
|
class FMD3Model : public FModel
|
||||||
|
{
|
||||||
|
struct MD3Tag
|
||||||
|
{
|
||||||
|
// Currently I have no use for this
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MD3TexCoord
|
||||||
|
{
|
||||||
|
float s,t;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MD3Vertex
|
||||||
|
{
|
||||||
|
float x,y,z;
|
||||||
|
float nx,ny,nz;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MD3Triangle
|
||||||
|
{
|
||||||
|
int VertIndex[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MD3Surface
|
||||||
|
{
|
||||||
|
unsigned numVertices;
|
||||||
|
unsigned numTriangles;
|
||||||
|
unsigned numSkins;
|
||||||
|
|
||||||
|
TArray<FTextureID> Skins;
|
||||||
|
TArray<MD3Triangle> Tris;
|
||||||
|
TArray<MD3TexCoord> Texcoords;
|
||||||
|
TArray<MD3Vertex> Vertices;
|
||||||
|
|
||||||
|
unsigned int vindex = UINT_MAX; // contains numframes arrays of vertices
|
||||||
|
unsigned int iindex = UINT_MAX;
|
||||||
|
|
||||||
|
void UnloadGeometry()
|
||||||
|
{
|
||||||
|
Tris.Reset();
|
||||||
|
Vertices.Reset();
|
||||||
|
Texcoords.Reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MD3Frame
|
||||||
|
{
|
||||||
|
// The bounding box information is of no use in the Doom engine
|
||||||
|
// That will still be done with the actor's size information.
|
||||||
|
char Name[16];
|
||||||
|
float origin[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
int numTags;
|
||||||
|
int mLumpNum;
|
||||||
|
|
||||||
|
TArray<MD3Frame> Frames;
|
||||||
|
TArray<MD3Surface> Surfaces;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FMD3Model() = default;
|
||||||
|
|
||||||
|
virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length);
|
||||||
|
virtual int FindFrame(const char * name);
|
||||||
|
virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0);
|
||||||
|
void LoadGeometry();
|
||||||
|
void BuildVertexBuffer(FModelRenderer *renderer);
|
||||||
|
virtual void AddSkins(uint8_t *hitlist);
|
||||||
|
};
|
||||||
|
|
105
src/models/model_obj.h
Normal file
105
src/models/model_obj.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright(C) 2018 Kevin Caccamo
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "model.h"
|
||||||
|
#include "math/mathlib.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
class FOBJModel : public FModel
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const char *newSideSep = "$"; // OBJ side separator is /, which is parsed as a line comment by FScanner if two of them are next to each other.
|
||||||
|
bool hasMissingNormals;
|
||||||
|
bool hasSmoothGroups;
|
||||||
|
|
||||||
|
enum class FaceElement
|
||||||
|
{
|
||||||
|
VertexIndex,
|
||||||
|
UVIndex,
|
||||||
|
VNormalIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OBJTriRef
|
||||||
|
{
|
||||||
|
unsigned int surf;
|
||||||
|
unsigned int tri;
|
||||||
|
OBJTriRef(): surf(0), tri(0) {}
|
||||||
|
OBJTriRef(unsigned int surf, unsigned int tri): surf(surf), tri(tri) {}
|
||||||
|
bool operator== (OBJTriRef other) { return surf == other.surf && tri == other.tri; }
|
||||||
|
};
|
||||||
|
struct OBJFaceSide
|
||||||
|
{
|
||||||
|
int vertref;
|
||||||
|
int normref;
|
||||||
|
int uvref;
|
||||||
|
};
|
||||||
|
struct OBJFace
|
||||||
|
{
|
||||||
|
unsigned int sideCount;
|
||||||
|
unsigned int smoothGroup;
|
||||||
|
OBJFaceSide sides[4];
|
||||||
|
OBJFace(): sideCount(0), smoothGroup(0) {}
|
||||||
|
};
|
||||||
|
struct OBJSurface // 1 surface per 'usemtl'
|
||||||
|
{
|
||||||
|
unsigned int numTris; // Number of triangulated faces
|
||||||
|
unsigned int numFaces; // Number of faces
|
||||||
|
unsigned int vbStart; // First index in vertex buffer
|
||||||
|
unsigned int faceStart; // Index of first face in faces array
|
||||||
|
OBJFace* tris; // Triangles
|
||||||
|
FTextureID skin;
|
||||||
|
OBJSurface(FTextureID skin): numTris(0), numFaces(0), vbStart(0), faceStart(0), tris(nullptr), skin(skin) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
TArray<Vec3> verts;
|
||||||
|
TArray<Vec3> norms;
|
||||||
|
TArray<Vec2> uvs;
|
||||||
|
TArray<OBJFace> faces;
|
||||||
|
TArray<OBJSurface> surfaces;
|
||||||
|
FScanner sc;
|
||||||
|
TArray<OBJTriRef>* vertFaces;
|
||||||
|
|
||||||
|
int ResolveIndex(int origIndex, FaceElement el);
|
||||||
|
template<typename T, size_t L> void ParseVector(TArray<T> &array);
|
||||||
|
bool ParseFaceSide(const FString &side, OBJFace &face, int sidx);
|
||||||
|
void ConstructSurfaceTris(OBJSurface &surf);
|
||||||
|
void AddVertFaces();
|
||||||
|
void TriangulateQuad(const OBJFace &quad, OBJFace *tris);
|
||||||
|
Vec3 RealignVector(Vec3 vecToRealign);
|
||||||
|
Vec2 FixUV(Vec2 vecToRealign);
|
||||||
|
Vec3 CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx);
|
||||||
|
Vec3 CalculateNormalFlat(OBJTriRef otr);
|
||||||
|
Vec3 CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup);
|
||||||
|
public:
|
||||||
|
FOBJModel(): hasMissingNormals(false), hasSmoothGroups(false), vertFaces(nullptr) {}
|
||||||
|
~FOBJModel();
|
||||||
|
bool Load(const char* fn, int lumpnum, const char* buffer, int length) override;
|
||||||
|
int FindFrame(const char* name) override;
|
||||||
|
void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, int translation=0) override;
|
||||||
|
void BuildVertexBuffer(FModelRenderer* renderer) override;
|
||||||
|
void AddSkins(uint8_t* hitlist) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
113
src/models/model_ue1.h
Normal file
113
src/models/model_ue1.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "model.h"
|
||||||
|
#include "math/mathlib.h"
|
||||||
|
|
||||||
|
class FUE1Model : public FModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum EPolyType
|
||||||
|
{
|
||||||
|
PT_Normal = 0, // normal renderstyle
|
||||||
|
PT_TwoSided = 1, // like normal, but don't cull backfaces
|
||||||
|
PT_Translucent = 2, // additive blending
|
||||||
|
PT_Masked = 3, // draw with alpha testing
|
||||||
|
PT_Modulated = 4, // overlay-like blending (rgb values below 128 darken, 128 is unchanged, and above 128 lighten)
|
||||||
|
// types mask
|
||||||
|
PT_Type = 7,
|
||||||
|
// flags
|
||||||
|
PT_WeaponTriangle = 0x08, // this poly is used for positioning a weapon attachment and should not be drawn
|
||||||
|
PT_Unlit = 0x10, // this poly is fullbright
|
||||||
|
PT_Curvy = 0x20, // this poly uses the facet normal
|
||||||
|
PT_EnvironmentMap = 0x40, // vertex UVs are remapped to their view-space X and Z normals, fake cubemap look
|
||||||
|
PT_NoSmooth = 0x80 // this poly forcibly uses nearest filtering
|
||||||
|
};
|
||||||
|
|
||||||
|
bool Load(const char * fn, int lumpnum, const char * buffer, int length) override;
|
||||||
|
int FindFrame(const char * name) override;
|
||||||
|
void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation=0) override;
|
||||||
|
void BuildVertexBuffer(FModelRenderer *renderer) override;
|
||||||
|
void AddSkins(uint8_t *hitlist) override;
|
||||||
|
void LoadGeometry();
|
||||||
|
void UnloadGeometry();
|
||||||
|
FUE1Model()
|
||||||
|
{
|
||||||
|
mDataLump = -1;
|
||||||
|
mAnivLump = -1;
|
||||||
|
mDataLoaded = false;
|
||||||
|
dhead = NULL;
|
||||||
|
dpolys = NULL;
|
||||||
|
ahead = NULL;
|
||||||
|
averts = NULL;
|
||||||
|
numVerts = 0;
|
||||||
|
numFrames = 0;
|
||||||
|
numPolys = 0;
|
||||||
|
numGroups = 0;
|
||||||
|
}
|
||||||
|
~FUE1Model();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int mDataLump, mAnivLump;
|
||||||
|
bool mDataLoaded;
|
||||||
|
|
||||||
|
// raw data structures
|
||||||
|
struct d3dhead
|
||||||
|
{
|
||||||
|
uint16_t numpolys, numverts;
|
||||||
|
uint16_t bogusrot, bogusframe;
|
||||||
|
uint32_t bogusnorm[3];
|
||||||
|
uint32_t fixscale;
|
||||||
|
uint32_t unused[3];
|
||||||
|
uint8_t padding[12];
|
||||||
|
};
|
||||||
|
struct d3dpoly
|
||||||
|
{
|
||||||
|
uint16_t vertices[3];
|
||||||
|
uint8_t type, color;
|
||||||
|
uint8_t uv[3][2];
|
||||||
|
uint8_t texnum, flags;
|
||||||
|
};
|
||||||
|
struct a3dhead
|
||||||
|
{
|
||||||
|
uint16_t numframes, framesize;
|
||||||
|
};
|
||||||
|
d3dhead * dhead;
|
||||||
|
d3dpoly * dpolys;
|
||||||
|
a3dhead * ahead;
|
||||||
|
uint32_t * averts;
|
||||||
|
struct dxvert
|
||||||
|
{
|
||||||
|
int16_t x, y, z, pad;
|
||||||
|
};
|
||||||
|
dxvert * dxverts;
|
||||||
|
|
||||||
|
// converted data structures
|
||||||
|
struct UE1Vertex
|
||||||
|
{
|
||||||
|
Vec3 Pos, Normal;
|
||||||
|
TArray<int> P; // polys that reference this vertex, used in normal computation to save time
|
||||||
|
int nP; // count of those polys
|
||||||
|
};
|
||||||
|
struct UE1Poly
|
||||||
|
{
|
||||||
|
int V[3];
|
||||||
|
Vec2 C[3];
|
||||||
|
TArray<Vec3> Normals;
|
||||||
|
};
|
||||||
|
struct UE1Group
|
||||||
|
{
|
||||||
|
TArray<int> P;
|
||||||
|
int numPolys, texNum, type;
|
||||||
|
};
|
||||||
|
|
||||||
|
int numVerts;
|
||||||
|
int numFrames;
|
||||||
|
int numPolys;
|
||||||
|
int numGroups;
|
||||||
|
TArray<int> specialPolys; // for future model attachment support, unused for now
|
||||||
|
|
||||||
|
TArray<UE1Vertex> verts;
|
||||||
|
TArray<UE1Poly> polys;
|
||||||
|
TArray<UE1Group> groups;
|
||||||
|
};
|
36
src/models/modelrenderer.h
Normal file
36
src/models/modelrenderer.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "model.h"
|
||||||
|
|
||||||
|
struct FRenderStyle
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VSMatrix
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
class FModelRenderer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FModelRenderer() = default;
|
||||||
|
|
||||||
|
virtual ModelRendererType GetType() const = 0;
|
||||||
|
|
||||||
|
virtual void BeginDrawModel(FRenderStyle style, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) = 0;
|
||||||
|
virtual void EndDrawModel(FRenderStyle style, FSpriteModelFrame *smf) = 0;
|
||||||
|
|
||||||
|
virtual IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) = 0;
|
||||||
|
|
||||||
|
virtual VSMatrix GetViewToWorldMatrix() = 0;
|
||||||
|
|
||||||
|
virtual void BeginDrawHUDModel(FRenderStyle style, const VSMatrix &objectToWorldMatrix, bool mirrored) = 0;
|
||||||
|
virtual void EndDrawHUDModel(FRenderStyle style) = 0;
|
||||||
|
|
||||||
|
virtual void SetInterpolation(double interpolation) = 0;
|
||||||
|
virtual void SetMaterial(FGameTexture *skin, bool clampNoFilter, int translation) = 0;
|
||||||
|
virtual void DrawArrays(int start, int count) = 0;
|
||||||
|
virtual void DrawElements(int numIndices, size_t offset) = 0;
|
||||||
|
virtual void SetupFrame(FModel *model, unsigned int frame1, unsigned int frame2, unsigned int size) = 0;
|
||||||
|
};
|
||||||
|
|
559
src/models/models_md2.cpp
Normal file
559
src/models/models_md2.cpp
Normal file
|
@ -0,0 +1,559 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright(C) 2005-2016 Christoph Oelckers
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
** models.cpp
|
||||||
|
**
|
||||||
|
** MD2/DMD model format code
|
||||||
|
**
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include "model_md2.h"
|
||||||
|
#include "modelrenderer.h"
|
||||||
|
#include "m_swap.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const double M_PI = 3.14159265358979323846; // matches value in gcc v2 math.h
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(disable:4244) // warning C4244: conversion from 'double' to 'float', possible loss of data
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum { VX, VZ, VY };
|
||||||
|
#define NUMVERTEXNORMALS 162
|
||||||
|
|
||||||
|
static float avertexnormals[NUMVERTEXNORMALS][3] = {
|
||||||
|
#include "tab_anorms.h"
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// UnpackVector
|
||||||
|
// Packed: pppppppy yyyyyyyy. Yaw is on the XY plane.
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
static void UnpackVector(unsigned short packed, float vec[3])
|
||||||
|
{
|
||||||
|
float yaw = (packed & 511) / 512.0f * 2 * M_PI;
|
||||||
|
float pitch = ((packed >> 9) / 127.0f - 0.5f) * M_PI;
|
||||||
|
float cosp = (float) cos(pitch);
|
||||||
|
|
||||||
|
vec[VX] = (float) cos(yaw) * cosp;
|
||||||
|
vec[VY] = (float) sin(yaw) * cosp;
|
||||||
|
vec[VZ] = (float) sin(pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// DMD file structure
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
struct dmd_chunk_t
|
||||||
|
{
|
||||||
|
int type;
|
||||||
|
int length; // Next chunk follows...
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(1)
|
||||||
|
struct dmd_packedVertex_t
|
||||||
|
{
|
||||||
|
uint8_t vertex[3];
|
||||||
|
unsigned short normal; // Yaw and pitch.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dmd_packedFrame_t
|
||||||
|
{
|
||||||
|
float scale[3];
|
||||||
|
float translate[3];
|
||||||
|
char name[16];
|
||||||
|
dmd_packedVertex_t vertices[1];
|
||||||
|
};
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
// Chunk types.
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
DMC_END, // Must be the last chunk.
|
||||||
|
DMC_INFO // Required; will be expected to exist.
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FDMDModel::Load
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
bool FDMDModel::Load(const char * path, int lumpnum, const char * buffer, int length)
|
||||||
|
{
|
||||||
|
dmd_chunk_t * chunk = (dmd_chunk_t*)(buffer + 12);
|
||||||
|
char *temp;
|
||||||
|
ModelFrame *frame;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
int fileoffset = 12 + sizeof(dmd_chunk_t);
|
||||||
|
|
||||||
|
chunk->type = LittleLong(chunk->type);
|
||||||
|
while (chunk->type != DMC_END)
|
||||||
|
{
|
||||||
|
switch (chunk->type)
|
||||||
|
{
|
||||||
|
case DMC_INFO: // Standard DMD information chunk.
|
||||||
|
memcpy(&info, buffer + fileoffset, LittleLong(chunk->length));
|
||||||
|
info.skinWidth = LittleLong(info.skinWidth);
|
||||||
|
info.skinHeight = LittleLong(info.skinHeight);
|
||||||
|
info.frameSize = LittleLong(info.frameSize);
|
||||||
|
info.numSkins = LittleLong(info.numSkins);
|
||||||
|
info.numVertices = LittleLong(info.numVertices);
|
||||||
|
info.numTexCoords = LittleLong(info.numTexCoords);
|
||||||
|
info.numFrames = LittleLong(info.numFrames);
|
||||||
|
info.numLODs = LittleLong(info.numLODs);
|
||||||
|
info.offsetSkins = LittleLong(info.offsetSkins);
|
||||||
|
info.offsetTexCoords = LittleLong(info.offsetTexCoords);
|
||||||
|
info.offsetFrames = LittleLong(info.offsetFrames);
|
||||||
|
info.offsetLODs = LittleLong(info.offsetLODs);
|
||||||
|
info.offsetEnd = LittleLong(info.offsetEnd);
|
||||||
|
fileoffset += chunk->length;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Just skip all unknown chunks.
|
||||||
|
fileoffset += chunk->length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Read the next chunk header.
|
||||||
|
chunk = (dmd_chunk_t*)(buffer + fileoffset);
|
||||||
|
chunk->type = LittleLong(chunk->type);
|
||||||
|
fileoffset += sizeof(dmd_chunk_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and load in the data.
|
||||||
|
skins = new FTextureID[info.numSkins];
|
||||||
|
|
||||||
|
for (i = 0; i < info.numSkins; i++)
|
||||||
|
{
|
||||||
|
skins[i] = LoadSkin(path, buffer + info.offsetSkins + i * 64);
|
||||||
|
}
|
||||||
|
temp = (char*)buffer + info.offsetFrames;
|
||||||
|
frames = new ModelFrame[info.numFrames];
|
||||||
|
|
||||||
|
for (i = 0, frame = frames; i < info.numFrames; i++, frame++)
|
||||||
|
{
|
||||||
|
dmd_packedFrame_t *pfr = (dmd_packedFrame_t *)(temp + info.frameSize * i);
|
||||||
|
|
||||||
|
memcpy(frame->name, pfr->name, sizeof(pfr->name));
|
||||||
|
frame->vindex = UINT_MAX;
|
||||||
|
}
|
||||||
|
mLumpNum = lumpnum;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FDMDModel::LoadGeometry
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FDMDModel::LoadGeometry()
|
||||||
|
{
|
||||||
|
static int axis[3] = { VX, VY, VZ };
|
||||||
|
FileData lumpdata = fileSystem.ReadFile(mLumpNum);
|
||||||
|
const char *buffer = (const char *)lumpdata.GetMem();
|
||||||
|
texCoords = new FTexCoord[info.numTexCoords];
|
||||||
|
memcpy(texCoords, buffer + info.offsetTexCoords, info.numTexCoords * sizeof(FTexCoord));
|
||||||
|
|
||||||
|
const char *temp = buffer + info.offsetFrames;
|
||||||
|
framevtx= new ModelFrameVertexData[info.numFrames];
|
||||||
|
|
||||||
|
ModelFrameVertexData *framev;
|
||||||
|
int i, k, c;
|
||||||
|
for(i = 0, framev = framevtx; i < info.numFrames; i++, framev++)
|
||||||
|
{
|
||||||
|
dmd_packedFrame_t *pfr = (dmd_packedFrame_t *) (temp + info.frameSize * i);
|
||||||
|
dmd_packedVertex_t *pVtx;
|
||||||
|
|
||||||
|
framev->vertices = new DMDModelVertex[info.numVertices];
|
||||||
|
framev->normals = new DMDModelVertex[info.numVertices];
|
||||||
|
|
||||||
|
// Translate each vertex.
|
||||||
|
for(k = 0, pVtx = pfr->vertices; k < info.numVertices; k++, pVtx++)
|
||||||
|
{
|
||||||
|
UnpackVector((unsigned short)(pVtx->normal), framev->normals[k].xyz);
|
||||||
|
for(c = 0; c < 3; c++)
|
||||||
|
{
|
||||||
|
framev->vertices[k].xyz[axis[c]] =
|
||||||
|
(pVtx->vertex[c] * float(pfr->scale[c]) + float(pfr->translate[c]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(lodInfo, buffer+info.offsetLODs, info.numLODs * sizeof(DMDLoDInfo));
|
||||||
|
for(i = 0; i < info.numLODs; i++)
|
||||||
|
{
|
||||||
|
lodInfo[i].numTriangles = LittleLong(lodInfo[i].numTriangles);
|
||||||
|
lodInfo[i].offsetTriangles = LittleLong(lodInfo[i].offsetTriangles);
|
||||||
|
if (lodInfo[i].numTriangles > 0)
|
||||||
|
{
|
||||||
|
lods[i].triangles = new FTriangle[lodInfo[i].numTriangles];
|
||||||
|
memcpy(lods[i].triangles, buffer + lodInfo[i].offsetTriangles, lodInfo[i].numTriangles * sizeof(FTriangle));
|
||||||
|
for (int j = 0; j < lodInfo[i].numTriangles; j++)
|
||||||
|
{
|
||||||
|
for (int k = 0; k < 3; k++)
|
||||||
|
{
|
||||||
|
lods[i].triangles[j].textureIndices[k] = LittleShort(lods[i].triangles[j].textureIndices[k]);
|
||||||
|
lods[i].triangles[j].vertexIndices[k] = LittleShort(lods[i].triangles[j].vertexIndices[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// Deletes everything that's no longer needed after building the vertex buffer
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FDMDModel::UnloadGeometry()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (framevtx != NULL)
|
||||||
|
{
|
||||||
|
for (i=0;i<info.numFrames;i++)
|
||||||
|
{
|
||||||
|
if (framevtx[i].vertices != NULL) delete [] framevtx[i].vertices;
|
||||||
|
if (framevtx[i].normals != NULL) delete [] framevtx[i].normals;
|
||||||
|
|
||||||
|
framevtx[i].vertices = NULL;
|
||||||
|
framevtx[i].normals = NULL;
|
||||||
|
}
|
||||||
|
delete[] framevtx;
|
||||||
|
framevtx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < info.numLODs; i++)
|
||||||
|
{
|
||||||
|
if (lods[i].triangles != NULL) delete[] lods[i].triangles;
|
||||||
|
lods[i].triangles = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texCoords != NULL) delete[] texCoords;
|
||||||
|
texCoords = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
FDMDModel::~FDMDModel()
|
||||||
|
{
|
||||||
|
UnloadGeometry();
|
||||||
|
|
||||||
|
// skins are managed by the texture manager so they must not be deleted here.
|
||||||
|
if (skins != NULL) delete [] skins;
|
||||||
|
if (frames != NULL) delete [] frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FDMDModel::BuildVertexBuffer(FModelRenderer *renderer)
|
||||||
|
{
|
||||||
|
if (!GetVertexBuffer(renderer->GetType()))
|
||||||
|
{
|
||||||
|
LoadGeometry();
|
||||||
|
|
||||||
|
int VertexBufferSize = info.numFrames * lodInfo[0].numTriangles * 3;
|
||||||
|
unsigned int vindex = 0;
|
||||||
|
|
||||||
|
auto vbuf = renderer->CreateVertexBuffer(false, info.numFrames == 1);
|
||||||
|
SetVertexBuffer(renderer->GetType(), vbuf);
|
||||||
|
|
||||||
|
FModelVertex *vertptr = vbuf->LockVertexBuffer(VertexBufferSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < info.numFrames; i++)
|
||||||
|
{
|
||||||
|
DMDModelVertex *vert = framevtx[i].vertices;
|
||||||
|
DMDModelVertex *norm = framevtx[i].normals;
|
||||||
|
|
||||||
|
frames[i].vindex = vindex;
|
||||||
|
|
||||||
|
FTriangle *tri = lods[0].triangles;
|
||||||
|
|
||||||
|
for (int i = 0; i < lodInfo[0].numTriangles; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
{
|
||||||
|
|
||||||
|
int ti = tri->textureIndices[j];
|
||||||
|
int vi = tri->vertexIndices[j];
|
||||||
|
|
||||||
|
FModelVertex *bvert = &vertptr[vindex++];
|
||||||
|
bvert->Set(vert[vi].xyz[0], vert[vi].xyz[1], vert[vi].xyz[2], (float)texCoords[ti].s / info.skinWidth, (float)texCoords[ti].t / info.skinHeight);
|
||||||
|
bvert->SetNormal(norm[vi].xyz[0], norm[vi].xyz[1], norm[vi].xyz[2]);
|
||||||
|
}
|
||||||
|
tri++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vbuf->UnlockVertexBuffer();
|
||||||
|
UnloadGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// for skin precaching
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FDMDModel::AddSkins(uint8_t *hitlist)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < info.numSkins; i++)
|
||||||
|
{
|
||||||
|
if (skins[i].isValid())
|
||||||
|
{
|
||||||
|
hitlist[skins[i].GetIndex()] |= FTextureManager::HIT_Flat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FDMDModel::FindFrame
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
int FDMDModel::FindFrame(const char * name)
|
||||||
|
{
|
||||||
|
for (int i=0;i<info.numFrames;i++)
|
||||||
|
{
|
||||||
|
if (!stricmp(name, frames[i].name)) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FDMDModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation)
|
||||||
|
{
|
||||||
|
if (frameno >= info.numFrames || frameno2 >= info.numFrames) return;
|
||||||
|
|
||||||
|
if (!skin)
|
||||||
|
{
|
||||||
|
if (info.numSkins == 0 || !skins[0].isValid()) return;
|
||||||
|
skin = TexMan.GetGameTexture(skins[0], true);
|
||||||
|
if (!skin) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer->SetInterpolation(inter);
|
||||||
|
renderer->SetMaterial(skin, false, translation);
|
||||||
|
renderer->SetupFrame(this, frames[frameno].vindex, frames[frameno2].vindex, lodInfo[0].numTriangles * 3);
|
||||||
|
renderer->DrawArrays(0, lodInfo[0].numTriangles * 3);
|
||||||
|
renderer->SetInterpolation(0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// Internal data structures of MD2 files - only used during loading
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
struct md2_header_t
|
||||||
|
{
|
||||||
|
int magic;
|
||||||
|
int version;
|
||||||
|
int skinWidth;
|
||||||
|
int skinHeight;
|
||||||
|
int frameSize;
|
||||||
|
int numSkins;
|
||||||
|
int numVertices;
|
||||||
|
int numTexCoords;
|
||||||
|
int numTriangles;
|
||||||
|
int numGlCommands;
|
||||||
|
int numFrames;
|
||||||
|
int offsetSkins;
|
||||||
|
int offsetTexCoords;
|
||||||
|
int offsetTriangles;
|
||||||
|
int offsetFrames;
|
||||||
|
int offsetGlCommands;
|
||||||
|
int offsetEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md2_triangleVertex_t
|
||||||
|
{
|
||||||
|
uint8_t vertex[3];
|
||||||
|
uint8_t lightNormalIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md2_packedFrame_t
|
||||||
|
{
|
||||||
|
float scale[3];
|
||||||
|
float translate[3];
|
||||||
|
char name[16];
|
||||||
|
md2_triangleVertex_t vertices[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FMD2Model::Load
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
bool FMD2Model::Load(const char * path, int lumpnum, const char * buffer, int length)
|
||||||
|
{
|
||||||
|
md2_header_t * md2header = (md2_header_t *)buffer;
|
||||||
|
ModelFrame *frame;
|
||||||
|
uint8_t *md2_frames;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// Convert it to DMD.
|
||||||
|
header.magic = MD2_MAGIC;
|
||||||
|
header.version = 8;
|
||||||
|
header.flags = 0;
|
||||||
|
info.skinWidth = LittleLong(md2header->skinWidth);
|
||||||
|
info.skinHeight = LittleLong(md2header->skinHeight);
|
||||||
|
info.frameSize = LittleLong(md2header->frameSize);
|
||||||
|
info.numLODs = 1;
|
||||||
|
info.numSkins = LittleLong(md2header->numSkins);
|
||||||
|
info.numTexCoords = LittleLong(md2header->numTexCoords);
|
||||||
|
info.numVertices = LittleLong(md2header->numVertices);
|
||||||
|
info.numFrames = LittleLong(md2header->numFrames);
|
||||||
|
info.offsetSkins = LittleLong(md2header->offsetSkins);
|
||||||
|
info.offsetTexCoords = LittleLong(md2header->offsetTexCoords);
|
||||||
|
info.offsetFrames = LittleLong(md2header->offsetFrames);
|
||||||
|
info.offsetLODs = LittleLong(md2header->offsetEnd); // Doesn't exist.
|
||||||
|
lodInfo[0].numTriangles = LittleLong(md2header->numTriangles);
|
||||||
|
lodInfo[0].numGlCommands = LittleLong(md2header->numGlCommands);
|
||||||
|
lodInfo[0].offsetTriangles = LittleLong(md2header->offsetTriangles);
|
||||||
|
lodInfo[0].offsetGlCommands = LittleLong(md2header->offsetGlCommands);
|
||||||
|
info.offsetEnd = LittleLong(md2header->offsetEnd);
|
||||||
|
|
||||||
|
if (info.offsetFrames + info.frameSize * info.numFrames > length)
|
||||||
|
{
|
||||||
|
//Printf("LoadModel: Model '%s' file too short\n", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (lodInfo[0].numGlCommands <= 0)
|
||||||
|
{
|
||||||
|
//Printf("LoadModel: Model '%s' invalid NumGLCommands\n", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
skins = new FTextureID[info.numSkins];
|
||||||
|
|
||||||
|
for (i = 0; i < info.numSkins; i++)
|
||||||
|
{
|
||||||
|
skins[i] = LoadSkin(path, buffer + info.offsetSkins + i * 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The frames need to be unpacked.
|
||||||
|
md2_frames = (uint8_t*)buffer + info.offsetFrames;
|
||||||
|
frames = new ModelFrame[info.numFrames];
|
||||||
|
|
||||||
|
for (i = 0, frame = frames; i < info.numFrames; i++, frame++)
|
||||||
|
{
|
||||||
|
md2_packedFrame_t *pfr = (md2_packedFrame_t *)(md2_frames + info.frameSize * i);
|
||||||
|
|
||||||
|
memcpy(frame->name, pfr->name, sizeof(pfr->name));
|
||||||
|
frame->vindex = UINT_MAX;
|
||||||
|
}
|
||||||
|
mLumpNum = lumpnum;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// FMD2Model::LoadGeometry
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FMD2Model::LoadGeometry()
|
||||||
|
{
|
||||||
|
static int axis[3] = { VX, VY, VZ };
|
||||||
|
uint8_t *md2_frames;
|
||||||
|
FileData lumpdata = fileSystem.ReadFile(mLumpNum);
|
||||||
|
const char *buffer = (const char *)lumpdata.GetMem();
|
||||||
|
|
||||||
|
texCoords = new FTexCoord[info.numTexCoords];
|
||||||
|
memcpy(texCoords, (uint8_t*)buffer + info.offsetTexCoords, info.numTexCoords * sizeof(FTexCoord));
|
||||||
|
|
||||||
|
md2_frames = (uint8_t*)buffer + info.offsetFrames;
|
||||||
|
framevtx = new ModelFrameVertexData[info.numFrames];
|
||||||
|
ModelFrameVertexData *framev;
|
||||||
|
int i, k, c;
|
||||||
|
|
||||||
|
for(i = 0, framev = framevtx; i < info.numFrames; i++, framev++)
|
||||||
|
{
|
||||||
|
md2_packedFrame_t *pfr = (md2_packedFrame_t *) (md2_frames + info.frameSize * i);
|
||||||
|
md2_triangleVertex_t *pVtx;
|
||||||
|
|
||||||
|
framev->vertices = new DMDModelVertex[info.numVertices];
|
||||||
|
framev->normals = new DMDModelVertex[info.numVertices];
|
||||||
|
|
||||||
|
// Translate each vertex.
|
||||||
|
for(k = 0, pVtx = pfr->vertices; k < info.numVertices; k++, pVtx++)
|
||||||
|
{
|
||||||
|
memcpy(framev->normals[k].xyz,
|
||||||
|
avertexnormals[pVtx->lightNormalIndex], sizeof(float) * 3);
|
||||||
|
|
||||||
|
for(c = 0; c < 3; c++)
|
||||||
|
{
|
||||||
|
framev->vertices[k].xyz[axis[c]] =
|
||||||
|
(pVtx->vertex[c] * pfr->scale[c] + pfr->translate[c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lods[0].triangles = new FTriangle[lodInfo[0].numTriangles];
|
||||||
|
|
||||||
|
int cnt = lodInfo[0].numTriangles;
|
||||||
|
memcpy(lods[0].triangles, buffer + lodInfo[0].offsetTriangles, sizeof(FTriangle) * cnt);
|
||||||
|
for (int j = 0; j < cnt; j++)
|
||||||
|
{
|
||||||
|
for (int k = 0; k < 3; k++)
|
||||||
|
{
|
||||||
|
lods[0].triangles[j].textureIndices[k] = LittleShort(lods[0].triangles[j].textureIndices[k]);
|
||||||
|
lods[0].triangles[j].vertexIndices[k] = LittleShort(lods[0].triangles[j].vertexIndices[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FMD2Model::~FMD2Model()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
402
src/models/models_md3.cpp
Normal file
402
src/models/models_md3.cpp
Normal file
|
@ -0,0 +1,402 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright(C) 2006-2016 Christoph Oelckers
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "model_md3.h"
|
||||||
|
#include "modelrenderer.h"
|
||||||
|
#include "m_swap.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const double M_PI = 3.14159265358979323846; // matches value in gcc v2 math.h
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAX_QPATH 64
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(disable:4244) // warning C4244: conversion from 'double' to 'float', possible loss of data
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// decode the lat/lng normal to a 3 float normal
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
static void UnpackVector(unsigned short packed, float & nx, float & ny, float & nz)
|
||||||
|
{
|
||||||
|
double lat = ( packed >> 8 ) & 0xff;
|
||||||
|
double lng = ( packed & 0xff );
|
||||||
|
lat *= M_PI/128;
|
||||||
|
lng *= M_PI/128;
|
||||||
|
|
||||||
|
nx = std::cos(lat) * std::sin(lng);
|
||||||
|
ny = std::sin(lat) * std::sin(lng);
|
||||||
|
nz = std::cos(lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// MD3 File structure
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
#pragma pack(4)
|
||||||
|
struct md3_header_t
|
||||||
|
{
|
||||||
|
uint32_t Magic;
|
||||||
|
uint32_t Version;
|
||||||
|
char Name[MAX_QPATH];
|
||||||
|
uint32_t Flags;
|
||||||
|
uint32_t Num_Frames;
|
||||||
|
uint32_t Num_Tags;
|
||||||
|
uint32_t Num_Surfaces;
|
||||||
|
uint32_t Num_Skins;
|
||||||
|
uint32_t Ofs_Frames;
|
||||||
|
uint32_t Ofs_Tags;
|
||||||
|
uint32_t Ofs_Surfaces;
|
||||||
|
uint32_t Ofs_Eof;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md3_surface_t
|
||||||
|
{
|
||||||
|
uint32_t Magic;
|
||||||
|
char Name[MAX_QPATH];
|
||||||
|
uint32_t Flags;
|
||||||
|
uint32_t Num_Frames;
|
||||||
|
uint32_t Num_Shaders;
|
||||||
|
uint32_t Num_Verts;
|
||||||
|
uint32_t Num_Triangles;
|
||||||
|
uint32_t Ofs_Triangles;
|
||||||
|
uint32_t Ofs_Shaders;
|
||||||
|
uint32_t Ofs_Texcoord;
|
||||||
|
uint32_t Ofs_XYZNormal;
|
||||||
|
uint32_t Ofs_End;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md3_triangle_t
|
||||||
|
{
|
||||||
|
uint32_t vt_index[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md3_shader_t
|
||||||
|
{
|
||||||
|
char Name[MAX_QPATH];
|
||||||
|
uint32_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md3_texcoord_t
|
||||||
|
{
|
||||||
|
float s, t;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md3_vertex_t
|
||||||
|
{
|
||||||
|
short x, y, z, n;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct md3_frame_t
|
||||||
|
{
|
||||||
|
float min_Bounds[3];
|
||||||
|
float max_Bounds[3];
|
||||||
|
float localorigin[3];
|
||||||
|
float radius;
|
||||||
|
char Name[16];
|
||||||
|
};
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
static void FixPathSeperator(char* path)
|
||||||
|
{
|
||||||
|
while (*path)
|
||||||
|
{
|
||||||
|
if (*path == '\\')
|
||||||
|
*path = '/';
|
||||||
|
path++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FMD3Model::Load(const char * path, int lumpnum, const char * buffer, int length)
|
||||||
|
{
|
||||||
|
md3_header_t * hdr = (md3_header_t *)buffer;
|
||||||
|
|
||||||
|
auto numFrames = LittleLong(hdr->Num_Frames);
|
||||||
|
auto numSurfaces = LittleLong(hdr->Num_Surfaces);
|
||||||
|
|
||||||
|
numTags = LittleLong(hdr->Num_Tags);
|
||||||
|
|
||||||
|
md3_frame_t * frm = (md3_frame_t*)(buffer + LittleLong(hdr->Ofs_Frames));
|
||||||
|
|
||||||
|
Frames.Resize(numFrames);
|
||||||
|
for (unsigned i = 0; i < numFrames; i++)
|
||||||
|
{
|
||||||
|
strncpy(Frames[i].Name, frm[i].Name, 16);
|
||||||
|
for (int j = 0; j < 3; j++) Frames[i].origin[j] = frm[i].localorigin[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces));
|
||||||
|
|
||||||
|
Surfaces.Resize(numSurfaces);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < numSurfaces; i++)
|
||||||
|
{
|
||||||
|
MD3Surface * s = &Surfaces[i];
|
||||||
|
md3_surface_t * ss = surf;
|
||||||
|
|
||||||
|
surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End));
|
||||||
|
|
||||||
|
s->numSkins = LittleLong(ss->Num_Shaders);
|
||||||
|
s->numTriangles = LittleLong(ss->Num_Triangles);
|
||||||
|
s->numVertices = LittleLong(ss->Num_Verts);
|
||||||
|
|
||||||
|
// copy shaders (skins)
|
||||||
|
md3_shader_t * shader = (md3_shader_t*)(((char*)ss) + LittleLong(ss->Ofs_Shaders));
|
||||||
|
s->Skins.Resize(s->numSkins);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < s->numSkins; i++)
|
||||||
|
{
|
||||||
|
// [BB] According to the MD3 spec, Name is supposed to include the full path.
|
||||||
|
// ... and since some tools seem to output backslashes, these need to be replaced with forward slashes to work.
|
||||||
|
FixPathSeperator(shader[i].Name);
|
||||||
|
s->Skins[i] = LoadSkin("", shader[i].Name);
|
||||||
|
// [BB] Fall back and check if Name is relative.
|
||||||
|
if (!s->Skins[i].isValid())
|
||||||
|
s->Skins[i] = LoadSkin(path, shader[i].Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mLumpNum = lumpnum;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FMD3Model::LoadGeometry()
|
||||||
|
{
|
||||||
|
FileData lumpdata = fileSystem.ReadFile(mLumpNum);
|
||||||
|
const char *buffer = (const char *)lumpdata.GetMem();
|
||||||
|
md3_header_t * hdr = (md3_header_t *)buffer;
|
||||||
|
md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces));
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < Surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
MD3Surface * s = &Surfaces[i];
|
||||||
|
md3_surface_t * ss = surf;
|
||||||
|
|
||||||
|
surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End));
|
||||||
|
|
||||||
|
// copy triangle indices
|
||||||
|
md3_triangle_t * tris = (md3_triangle_t*)(((char*)ss) + LittleLong(ss->Ofs_Triangles));
|
||||||
|
s->Tris.Resize(s->numTriangles);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < s->numTriangles; i++) for (int j = 0; j < 3; j++)
|
||||||
|
{
|
||||||
|
s->Tris[i].VertIndex[j] = LittleLong(tris[i].vt_index[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load texture coordinates
|
||||||
|
md3_texcoord_t * tc = (md3_texcoord_t*)(((char*)ss) + LittleLong(ss->Ofs_Texcoord));
|
||||||
|
s->Texcoords.Resize(s->numVertices);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < s->numVertices; i++)
|
||||||
|
{
|
||||||
|
s->Texcoords[i].s = tc[i].s;
|
||||||
|
s->Texcoords[i].t = tc[i].t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load vertices and texture coordinates
|
||||||
|
md3_vertex_t * vt = (md3_vertex_t*)(((char*)ss) + LittleLong(ss->Ofs_XYZNormal));
|
||||||
|
s->Vertices.Resize(s->numVertices * Frames.Size());
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < s->numVertices * Frames.Size(); i++)
|
||||||
|
{
|
||||||
|
s->Vertices[i].x = LittleShort(vt[i].x) / 64.f;
|
||||||
|
s->Vertices[i].y = LittleShort(vt[i].y) / 64.f;
|
||||||
|
s->Vertices[i].z = LittleShort(vt[i].z) / 64.f;
|
||||||
|
UnpackVector(LittleShort(vt[i].n), s->Vertices[i].nx, s->Vertices[i].ny, s->Vertices[i].nz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FMD3Model::BuildVertexBuffer(FModelRenderer *renderer)
|
||||||
|
{
|
||||||
|
if (!GetVertexBuffer(renderer->GetType()))
|
||||||
|
{
|
||||||
|
LoadGeometry();
|
||||||
|
|
||||||
|
unsigned int vbufsize = 0;
|
||||||
|
unsigned int ibufsize = 0;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < Surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
MD3Surface * surf = &Surfaces[i];
|
||||||
|
vbufsize += Frames.Size() * surf->numVertices;
|
||||||
|
ibufsize += 3 * surf->numTriangles;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vbuf = renderer->CreateVertexBuffer(true, Frames.Size() == 1);
|
||||||
|
SetVertexBuffer(renderer->GetType(), vbuf);
|
||||||
|
|
||||||
|
FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize);
|
||||||
|
unsigned int *indxptr = vbuf->LockIndexBuffer(ibufsize);
|
||||||
|
|
||||||
|
assert(vertptr != nullptr && indxptr != nullptr);
|
||||||
|
|
||||||
|
unsigned int vindex = 0, iindex = 0;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < Surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
MD3Surface * surf = &Surfaces[i];
|
||||||
|
|
||||||
|
surf->vindex = vindex;
|
||||||
|
surf->iindex = iindex;
|
||||||
|
for (unsigned j = 0; j < Frames.Size() * surf->numVertices; j++)
|
||||||
|
{
|
||||||
|
MD3Vertex* vert = &surf->Vertices[j];
|
||||||
|
|
||||||
|
FModelVertex *bvert = &vertptr[vindex++];
|
||||||
|
|
||||||
|
int tc = j % surf->numVertices;
|
||||||
|
bvert->Set(vert->x, vert->z, vert->y, surf->Texcoords[tc].s, surf->Texcoords[tc].t);
|
||||||
|
bvert->SetNormal(vert->nx, vert->nz, vert->ny);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned k = 0; k < surf->numTriangles; k++)
|
||||||
|
{
|
||||||
|
for (int l = 0; l < 3; l++)
|
||||||
|
{
|
||||||
|
indxptr[iindex++] = surf->Tris[k].VertIndex[l];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
surf->UnloadGeometry();
|
||||||
|
}
|
||||||
|
vbuf->UnlockVertexBuffer();
|
||||||
|
vbuf->UnlockIndexBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
// for skin precaching
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FMD3Model::AddSkins(uint8_t *hitlist)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
int ssIndex = i + curMDLIndex * MD3_MAX_SURFACES;
|
||||||
|
if (curSpriteMDLFrame && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
||||||
|
{
|
||||||
|
hitlist[curSpriteMDLFrame->surfaceskinIDs[ssIndex].GetIndex()] |= FTextureManager::HIT_Flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
MD3Surface * surf = &Surfaces[i];
|
||||||
|
for (unsigned j = 0; j < surf->numSkins; j++)
|
||||||
|
{
|
||||||
|
if (surf->Skins[j].isValid())
|
||||||
|
{
|
||||||
|
hitlist[surf->Skins[j].GetIndex()] |= FTextureManager::HIT_Flat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
int FMD3Model::FindFrame(const char * name)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Frames.Size(); i++)
|
||||||
|
{
|
||||||
|
if (!stricmp(name, Frames[i].Name)) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void FMD3Model::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation)
|
||||||
|
{
|
||||||
|
if ((unsigned)frameno >= Frames.Size() || (unsigned)frameno2 >= Frames.Size()) return;
|
||||||
|
|
||||||
|
renderer->SetInterpolation(inter);
|
||||||
|
for (unsigned i = 0; i < Surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
MD3Surface * surf = &Surfaces[i];
|
||||||
|
|
||||||
|
// [BB] In case no skin is specified via MODELDEF, check if the MD3 has a skin for the current surface.
|
||||||
|
// Note: Each surface may have a different skin.
|
||||||
|
FGameTexture *surfaceSkin = skin;
|
||||||
|
if (!surfaceSkin)
|
||||||
|
{
|
||||||
|
if (curSpriteMDLFrame)
|
||||||
|
{
|
||||||
|
int ssIndex = i + curMDLIndex * MD3_MAX_SURFACES;
|
||||||
|
if (curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
||||||
|
{
|
||||||
|
surfaceSkin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[ssIndex], true);
|
||||||
|
}
|
||||||
|
else if (surf->numSkins > 0 && surf->Skins[0].isValid())
|
||||||
|
{
|
||||||
|
surfaceSkin = TexMan.GetGameTexture(surf->Skins[0], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!surfaceSkin)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer->SetMaterial(surfaceSkin, false, translation);
|
||||||
|
renderer->SetupFrame(this, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices);
|
||||||
|
renderer->DrawElements(surf->numTriangles * 3, surf->iindex * sizeof(unsigned int));
|
||||||
|
}
|
||||||
|
renderer->SetInterpolation(0.f);
|
||||||
|
}
|
||||||
|
|
701
src/models/models_obj.cpp
Normal file
701
src/models/models_obj.cpp
Normal file
|
@ -0,0 +1,701 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright(C) 2018 Kevin Caccamo
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#include "model_obj.h"
|
||||||
|
#include "modelrenderer.h"
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an OBJ model
|
||||||
|
*
|
||||||
|
* @param fn The path to the model file
|
||||||
|
* @param lumpnum The lump index in the wad collection
|
||||||
|
* @param buffer The contents of the model file
|
||||||
|
* @param length The size of the model file
|
||||||
|
* @return Whether or not the model was parsed successfully
|
||||||
|
*/
|
||||||
|
bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length)
|
||||||
|
{
|
||||||
|
FString objName = fileSystem.GetFileFullPath(lumpnum);
|
||||||
|
FString objBuf(buffer, length);
|
||||||
|
|
||||||
|
// Do some replacements before we parse the OBJ string
|
||||||
|
{
|
||||||
|
// Ensure usemtl statements remain intact
|
||||||
|
TArray<FString> mtlUsages;
|
||||||
|
TArray<ptrdiff_t> mtlUsageIdxs;
|
||||||
|
ptrdiff_t bpos = 0, nlpos = 0, slashpos = 0;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
bpos = objBuf.IndexOf("\nusemtl", bpos);
|
||||||
|
if (bpos == -1) break;
|
||||||
|
slashpos = objBuf.IndexOf('/', bpos);
|
||||||
|
nlpos = objBuf.IndexOf('\n', ++bpos);
|
||||||
|
if (slashpos > nlpos || slashpos == -1)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nlpos == -1)
|
||||||
|
{
|
||||||
|
nlpos = objBuf.Len();
|
||||||
|
}
|
||||||
|
FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos);
|
||||||
|
mtlUsages.Push(lineStr);
|
||||||
|
mtlUsageIdxs.Push(bpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace forward slashes with percent signs so they aren't parsed as line comments
|
||||||
|
objBuf.ReplaceChars('/', *newSideSep);
|
||||||
|
char* wObjBuf = objBuf.LockBuffer();
|
||||||
|
|
||||||
|
// Substitute broken usemtl statements with old ones
|
||||||
|
for (size_t i = 0; i < mtlUsages.Size(); i++)
|
||||||
|
{
|
||||||
|
bpos = mtlUsageIdxs[i];
|
||||||
|
nlpos = objBuf.IndexOf('\n', bpos);
|
||||||
|
if (nlpos == -1)
|
||||||
|
{
|
||||||
|
nlpos = objBuf.Len();
|
||||||
|
}
|
||||||
|
memcpy(wObjBuf + bpos, mtlUsages[i].GetChars(), nlpos - bpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
bpos = 0;
|
||||||
|
// Find each OBJ line comment, and convert each to a C-style line comment
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
bpos = objBuf.IndexOf('#', bpos);
|
||||||
|
if (bpos == -1) break;
|
||||||
|
if (objBuf[(unsigned int)bpos + 1] == '\n')
|
||||||
|
{
|
||||||
|
wObjBuf[bpos] = ' ';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wObjBuf[bpos] = '/';
|
||||||
|
wObjBuf[bpos+1] = '/';
|
||||||
|
}
|
||||||
|
bpos += 1;
|
||||||
|
}
|
||||||
|
wObjBuf = nullptr;
|
||||||
|
objBuf.UnlockBuffer();
|
||||||
|
}
|
||||||
|
sc.OpenString(objName, objBuf);
|
||||||
|
|
||||||
|
FTextureID curMtl = FNullTextureID();
|
||||||
|
OBJSurface *curSurface = nullptr;
|
||||||
|
unsigned int aggSurfFaceCount = 0;
|
||||||
|
unsigned int curSurfFaceCount = 0;
|
||||||
|
unsigned int curSmoothGroup = 0;
|
||||||
|
|
||||||
|
while(sc.GetString())
|
||||||
|
{
|
||||||
|
if (sc.Compare("v")) // Vertex
|
||||||
|
{
|
||||||
|
ParseVector<Vec3, 3>(this->verts);
|
||||||
|
}
|
||||||
|
else if (sc.Compare("vn")) // Vertex normal
|
||||||
|
{
|
||||||
|
ParseVector<Vec3, 3>(this->norms);
|
||||||
|
}
|
||||||
|
else if (sc.Compare("vt")) // UV Coordinates
|
||||||
|
{
|
||||||
|
ParseVector<Vec2, 2>(this->uvs);
|
||||||
|
}
|
||||||
|
else if (sc.Compare("usemtl"))
|
||||||
|
{
|
||||||
|
// Get material name and try to load it
|
||||||
|
sc.MustGetString();
|
||||||
|
|
||||||
|
curMtl = LoadSkin("", sc.String);
|
||||||
|
if (!curMtl.isValid())
|
||||||
|
{
|
||||||
|
// Relative to model file path?
|
||||||
|
curMtl = LoadSkin(fn, sc.String);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!curMtl.isValid())
|
||||||
|
{
|
||||||
|
sc.ScriptMessage("Material %s (#%u) not found.", sc.String, surfaces.Size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build surface...
|
||||||
|
if (curSurface == nullptr)
|
||||||
|
{
|
||||||
|
// First surface
|
||||||
|
curSurface = new OBJSurface(curMtl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (curSurfFaceCount > 0)
|
||||||
|
{
|
||||||
|
// Add previous surface
|
||||||
|
curSurface->numFaces = curSurfFaceCount;
|
||||||
|
curSurface->faceStart = aggSurfFaceCount;
|
||||||
|
surfaces.Push(*curSurface);
|
||||||
|
delete curSurface;
|
||||||
|
// Go to next surface
|
||||||
|
curSurface = new OBJSurface(curMtl);
|
||||||
|
aggSurfFaceCount += curSurfFaceCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
curSurface->skin = curMtl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curSurfFaceCount = 0;
|
||||||
|
}
|
||||||
|
else if (sc.Compare("f"))
|
||||||
|
{
|
||||||
|
FString sides[4];
|
||||||
|
OBJFace face;
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
// A face must have at least 3 sides
|
||||||
|
sc.MustGetString();
|
||||||
|
sides[i] = sc.String;
|
||||||
|
if (!ParseFaceSide(sides[i], face, i)) return false;
|
||||||
|
}
|
||||||
|
face.sideCount = 3;
|
||||||
|
if (sc.GetString())
|
||||||
|
{
|
||||||
|
if (!sc.Compare("f") && FString(sc.String).IndexOfAny("-0123456789") == 0)
|
||||||
|
{
|
||||||
|
sides[3] = sc.String;
|
||||||
|
face.sideCount += 1;
|
||||||
|
if (!ParseFaceSide(sides[3], face, 3)) return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sc.UnGet(); // No 4th side, move back
|
||||||
|
}
|
||||||
|
}
|
||||||
|
face.smoothGroup = curSmoothGroup;
|
||||||
|
faces.Push(face);
|
||||||
|
curSurfFaceCount += 1;
|
||||||
|
}
|
||||||
|
else if (sc.Compare("s"))
|
||||||
|
{
|
||||||
|
sc.MustGetString();
|
||||||
|
if (sc.Compare("off"))
|
||||||
|
{
|
||||||
|
curSmoothGroup = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sc.UnGet();
|
||||||
|
sc.MustGetNumber();
|
||||||
|
curSmoothGroup = sc.Number;
|
||||||
|
hasSmoothGroups = hasSmoothGroups || curSmoothGroup > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sc.Close();
|
||||||
|
|
||||||
|
if (curSurface == nullptr)
|
||||||
|
{ // No valid materials detected
|
||||||
|
FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom
|
||||||
|
curSurface = new OBJSurface(dummyMtl);
|
||||||
|
}
|
||||||
|
curSurface->numFaces = curSurfFaceCount;
|
||||||
|
curSurface->faceStart = aggSurfFaceCount;
|
||||||
|
surfaces.Push(*curSurface);
|
||||||
|
delete curSurface;
|
||||||
|
|
||||||
|
if (uvs.Size() == 0)
|
||||||
|
{ // Needed so that OBJs without UVs can work
|
||||||
|
uvs.Push(Vec2(0.0, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an x-Dimensional vector
|
||||||
|
*
|
||||||
|
* @tparam T A subclass of TVector2 to be used
|
||||||
|
* @tparam L The length of the vector to parse
|
||||||
|
* @param[out] array The array to append the parsed vector to
|
||||||
|
*/
|
||||||
|
template<typename T, size_t L> void FOBJModel::ParseVector(TArray<T> &array)
|
||||||
|
{
|
||||||
|
T vec;
|
||||||
|
for (unsigned axis = 0; axis < L; axis++)
|
||||||
|
{
|
||||||
|
sc.MustGetFloat();
|
||||||
|
vec[axis] = (float)sc.Float;
|
||||||
|
}
|
||||||
|
array.Push(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a side of a face
|
||||||
|
*
|
||||||
|
* @param[in] sideStr The side definition string
|
||||||
|
* @param[out] face The face to assign the parsed side data to
|
||||||
|
* @param sidx The 0-based index of the side
|
||||||
|
* @return Whether or not the face side was parsed successfully
|
||||||
|
*/
|
||||||
|
bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx)
|
||||||
|
{
|
||||||
|
OBJFaceSide side;
|
||||||
|
int origIdx;
|
||||||
|
if (sideStr.IndexOf(newSideSep) >= 0)
|
||||||
|
{
|
||||||
|
TArray<FString> sides = sideStr.Split(newSideSep, FString::TOK_KEEPEMPTY);
|
||||||
|
|
||||||
|
if (sides[0].Len() > 0)
|
||||||
|
{
|
||||||
|
origIdx = atoi(sides[0].GetChars());
|
||||||
|
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sc.ScriptError("Vertex reference is not optional!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sides[1].Len() > 0)
|
||||||
|
{
|
||||||
|
origIdx = atoi(sides[1].GetChars());
|
||||||
|
side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
side.uvref = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sides.Size() > 2)
|
||||||
|
{
|
||||||
|
if (sides[2].Len() > 0)
|
||||||
|
{
|
||||||
|
origIdx = atoi(sides[2].GetChars());
|
||||||
|
side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
side.normref = -1;
|
||||||
|
hasMissingNormals = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
side.normref = -1;
|
||||||
|
hasMissingNormals = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
origIdx = atoi(sideStr.GetChars());
|
||||||
|
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex);
|
||||||
|
side.normref = -1;
|
||||||
|
hasMissingNormals = true;
|
||||||
|
side.uvref = -1;
|
||||||
|
}
|
||||||
|
face.sides[sidx] = side;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve an OBJ index to an absolute index
|
||||||
|
*
|
||||||
|
* OBJ indices are 1-based, and can also be negative
|
||||||
|
*
|
||||||
|
* @param origIndex The original OBJ index to resolve
|
||||||
|
* @param el What type of element the index references
|
||||||
|
* @return The absolute index of the element
|
||||||
|
*/
|
||||||
|
int FOBJModel::ResolveIndex(int origIndex, FaceElement el)
|
||||||
|
{
|
||||||
|
if (origIndex > 0)
|
||||||
|
{
|
||||||
|
return origIndex - 1; // OBJ indices start at 1
|
||||||
|
}
|
||||||
|
else if (origIndex < 0)
|
||||||
|
{
|
||||||
|
if (el == FaceElement::VertexIndex)
|
||||||
|
{
|
||||||
|
return verts.Size() + origIndex; // origIndex is negative
|
||||||
|
}
|
||||||
|
else if (el == FaceElement::UVIndex)
|
||||||
|
{
|
||||||
|
return uvs.Size() + origIndex;
|
||||||
|
}
|
||||||
|
else if (el == FaceElement::VNormalIndex)
|
||||||
|
{
|
||||||
|
return norms.Size() + origIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the vertex buffer for this model
|
||||||
|
*
|
||||||
|
* @param renderer A pointer to the model renderer. Used to allocate the vertex buffer.
|
||||||
|
*/
|
||||||
|
void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer)
|
||||||
|
{
|
||||||
|
if (GetVertexBuffer(renderer->GetType()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int vbufsize = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
ConstructSurfaceTris(surfaces[i]);
|
||||||
|
surfaces[i].vbStart = vbufsize;
|
||||||
|
vbufsize += surfaces[i].numTris * 3;
|
||||||
|
}
|
||||||
|
// Initialize/populate vertFaces
|
||||||
|
if (hasMissingNormals && hasSmoothGroups)
|
||||||
|
{
|
||||||
|
AddVertFaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vbuf = renderer->CreateVertexBuffer(false,true);
|
||||||
|
SetVertexBuffer(renderer->GetType(), vbuf);
|
||||||
|
|
||||||
|
FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
for (unsigned int j = 0; j < surfaces[i].numTris; j++)
|
||||||
|
{
|
||||||
|
for (size_t side = 0; side < 3; side++)
|
||||||
|
{
|
||||||
|
FModelVertex *mdv = vertptr +
|
||||||
|
side + j * 3 + // Current surface and previous triangles
|
||||||
|
surfaces[i].vbStart; // Previous surfaces
|
||||||
|
|
||||||
|
OBJFaceSide &curSide = surfaces[i].tris[j].sides[2 - side];
|
||||||
|
|
||||||
|
int vidx = curSide.vertref;
|
||||||
|
int uvidx = (curSide.uvref >= 0 && (unsigned int)curSide.uvref < uvs.Size()) ? curSide.uvref : 0;
|
||||||
|
int nidx = curSide.normref;
|
||||||
|
|
||||||
|
Vec3 curVvec = RealignVector(verts[vidx]);
|
||||||
|
Vec2 curUvec = FixUV(uvs[uvidx]);
|
||||||
|
Vec3 nvec;
|
||||||
|
|
||||||
|
mdv->Set(curVvec.x, curVvec.y, curVvec.z, curUvec.x, curUvec.y);
|
||||||
|
|
||||||
|
if (nidx >= 0 && (unsigned int)nidx < norms.Size())
|
||||||
|
{
|
||||||
|
nvec = RealignVector(norms[nidx]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (surfaces[i].tris[j].smoothGroup == 0)
|
||||||
|
{
|
||||||
|
nvec = CalculateNormalFlat(i, j);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nvec = CalculateNormalSmooth(vidx, surfaces[i].tris[j].smoothGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mdv->SetNormal(nvec.x, nvec.y, nvec.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete[] surfaces[i].tris;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy vertFaces
|
||||||
|
if (hasMissingNormals && hasSmoothGroups)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < verts.Size(); i++)
|
||||||
|
{
|
||||||
|
vertFaces[i].Clear();
|
||||||
|
}
|
||||||
|
delete[] vertFaces;
|
||||||
|
}
|
||||||
|
vbuf->UnlockVertexBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill in the triangle data for a surface
|
||||||
|
*
|
||||||
|
* @param[in,out] surf The surface to fill in the triangle data for
|
||||||
|
*/
|
||||||
|
void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
|
||||||
|
{
|
||||||
|
unsigned int triCount = 0;
|
||||||
|
|
||||||
|
size_t start = surf.faceStart;
|
||||||
|
size_t end = start + surf.numFaces;
|
||||||
|
for (size_t i = start; i < end; i++)
|
||||||
|
{
|
||||||
|
triCount += faces[i].sideCount - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
surf.numTris = triCount;
|
||||||
|
surf.tris = new OBJFace[triCount];
|
||||||
|
|
||||||
|
for (size_t i = start, triIdx = 0; i < end; i++, triIdx++)
|
||||||
|
{
|
||||||
|
surf.tris[triIdx].sideCount = 3;
|
||||||
|
if (faces[i].sideCount == 3)
|
||||||
|
{
|
||||||
|
surf.tris[triIdx].smoothGroup = faces[i].smoothGroup;
|
||||||
|
memcpy(surf.tris[triIdx].sides, faces[i].sides, sizeof(OBJFaceSide) * 3);
|
||||||
|
}
|
||||||
|
else if (faces[i].sideCount == 4) // Triangulate face
|
||||||
|
{
|
||||||
|
OBJFace *triangulated = new OBJFace[2];
|
||||||
|
TriangulateQuad(faces[i], triangulated);
|
||||||
|
memcpy(surf.tris[triIdx].sides, triangulated[0].sides, sizeof(OBJFaceSide) * 3);
|
||||||
|
memcpy(surf.tris[triIdx+1].sides, triangulated[1].sides, sizeof(OBJFaceSide) * 3);
|
||||||
|
delete[] triangulated;
|
||||||
|
triIdx += 1; // Filling out two faces
|
||||||
|
}
|
||||||
|
//DPrintf(DMSG_SPAMMY, "Smooth group: %d\n", surf.tris[triIdx].smoothGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triangulate a 4-sided face
|
||||||
|
*
|
||||||
|
* @param[in] quad The 4-sided face to triangulate
|
||||||
|
* @param[out] tris The resultant triangle data
|
||||||
|
*/
|
||||||
|
void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris)
|
||||||
|
{
|
||||||
|
tris[0].sideCount = 3;
|
||||||
|
tris[0].smoothGroup = quad.smoothGroup;
|
||||||
|
tris[1].sideCount = 3;
|
||||||
|
tris[1].smoothGroup = quad.smoothGroup;
|
||||||
|
|
||||||
|
int tsidx[2][3] = {{0, 1, 3}, {1, 2, 3}};
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 2; j++)
|
||||||
|
{
|
||||||
|
tris[j].sides[i].vertref = quad.sides[tsidx[j][i]].vertref;
|
||||||
|
tris[j].sides[i].uvref = quad.sides[tsidx[j][i]].uvref;
|
||||||
|
tris[j].sides[i].normref = quad.sides[tsidx[j][i]].normref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the vertices of all surfaces' triangles to the array of vertex->triangle references
|
||||||
|
*/
|
||||||
|
void FOBJModel::AddVertFaces() {
|
||||||
|
// Initialize and populate vertFaces - this array stores references to triangles per vertex
|
||||||
|
vertFaces = new TArray<OBJTriRef>[verts.Size()];
|
||||||
|
for (unsigned int i = 0; i < surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
for (unsigned int j = 0; j < surfaces[i].numTris; j++)
|
||||||
|
{
|
||||||
|
OBJTriRef otr = OBJTriRef(i, j);
|
||||||
|
for (size_t k = 0; k < surfaces[i].tris[j].sideCount; k++)
|
||||||
|
{
|
||||||
|
int vidx = surfaces[i].tris[j].sides[k].vertref;
|
||||||
|
vertFaces[vidx].Push(otr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-align a vector to match MD3 alignment
|
||||||
|
*
|
||||||
|
* @param vecToRealign The vector to re-align
|
||||||
|
* @return The re-aligned vector
|
||||||
|
*/
|
||||||
|
inline Vec3 FOBJModel::RealignVector(Vec3 vecToRealign)
|
||||||
|
{
|
||||||
|
vecToRealign.z *= -1;
|
||||||
|
return vecToRealign;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix UV coordinates of a UV vector
|
||||||
|
*
|
||||||
|
* @param vecToRealign The vector to fix
|
||||||
|
* @return The fixed UV coordinate vector
|
||||||
|
*/
|
||||||
|
inline Vec2 FOBJModel::FixUV(Vec2 vecToRealign)
|
||||||
|
{
|
||||||
|
vecToRealign.y *= -1;
|
||||||
|
return vecToRealign;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the surface normal for a triangle
|
||||||
|
*
|
||||||
|
* @param surfIdx The surface index
|
||||||
|
* @param triIdx The triangle Index
|
||||||
|
* @return The surface normal vector
|
||||||
|
*/
|
||||||
|
Vec3 FOBJModel::CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx)
|
||||||
|
{
|
||||||
|
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
|
||||||
|
int curVert = surfaces[surfIdx].tris[triIdx].sides[0].vertref;
|
||||||
|
int nextVert = surfaces[surfIdx].tris[triIdx].sides[2].vertref;
|
||||||
|
int lastVert = surfaces[surfIdx].tris[triIdx].sides[1].vertref;
|
||||||
|
|
||||||
|
// Cross-multiply the U-vector and V-vector
|
||||||
|
Vec3 curVvec = RealignVector(verts[curVert]);
|
||||||
|
Vec3 uvec = RealignVector(verts[nextVert]) - curVvec;
|
||||||
|
Vec3 vvec = RealignVector(verts[lastVert]) - curVvec;
|
||||||
|
|
||||||
|
return uvec ^ vvec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the surface normal for a triangle
|
||||||
|
*
|
||||||
|
* @param otr A reference to the surface, and a triangle within that surface, as an OBJTriRef
|
||||||
|
* @return The surface normal vector
|
||||||
|
*/
|
||||||
|
Vec3 FOBJModel::CalculateNormalFlat(OBJTriRef otr)
|
||||||
|
{
|
||||||
|
return CalculateNormalFlat(otr.surf, otr.tri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the normal of a vertex in a specific smooth group
|
||||||
|
*
|
||||||
|
* @param vidx The index of the vertex in the array of vertices
|
||||||
|
* @param smoothGroup The smooth group number
|
||||||
|
*/
|
||||||
|
Vec3 FOBJModel::CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup)
|
||||||
|
{
|
||||||
|
unsigned int connectedFaces = 0;
|
||||||
|
TArray<OBJTriRef>& vTris = vertFaces[vidx];
|
||||||
|
|
||||||
|
Vec3 vNormal(0,0,0);
|
||||||
|
for (size_t face = 0; face < vTris.Size(); face++)
|
||||||
|
{
|
||||||
|
OBJFace& tri = surfaces[vTris[face].surf].tris[vTris[face].tri];
|
||||||
|
if (tri.smoothGroup == smoothGroup)
|
||||||
|
{
|
||||||
|
Vec3 fNormal = CalculateNormalFlat(vTris[face]);
|
||||||
|
connectedFaces += 1;
|
||||||
|
vNormal += fNormal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vNormal /= (float)connectedFaces;
|
||||||
|
return vNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the index of the frame with the given name
|
||||||
|
*
|
||||||
|
* OBJ models are not animated, so this always returns 0
|
||||||
|
*
|
||||||
|
* @param name The name of the frame
|
||||||
|
* @return The index of the frame
|
||||||
|
*/
|
||||||
|
int FOBJModel::FindFrame(const char* name)
|
||||||
|
{
|
||||||
|
return 0; // OBJs are not animated.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the model
|
||||||
|
*
|
||||||
|
* @param renderer The model renderer
|
||||||
|
* @param skin The loaded skin for the surface
|
||||||
|
* @param frameno Unused
|
||||||
|
* @param frameno2 Unused
|
||||||
|
* @param inter Unused
|
||||||
|
* @param translation The translation for the skin
|
||||||
|
*/
|
||||||
|
void FOBJModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
OBJSurface *surf = &surfaces[i];
|
||||||
|
|
||||||
|
FGameTexture *userSkin = skin;
|
||||||
|
if (!userSkin && curSpriteMDLFrame)
|
||||||
|
{
|
||||||
|
int ssIndex = i + curMDLIndex * MD3_MAX_SURFACES;
|
||||||
|
if (i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
||||||
|
{
|
||||||
|
userSkin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[ssIndex], true);
|
||||||
|
}
|
||||||
|
else if (surf->skin.isValid())
|
||||||
|
{
|
||||||
|
userSkin = TexMan.GetGameTexture(surf->skin, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Still no skin after checking for one?
|
||||||
|
if (!userSkin)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer->SetMaterial(userSkin, false, translation);
|
||||||
|
renderer->SetupFrame(this, surf->vbStart, surf->vbStart, surf->numTris * 3);
|
||||||
|
renderer->DrawArrays(0, surf->numTris * 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-cache skins for the model
|
||||||
|
*
|
||||||
|
* @param hitlist The list of textures
|
||||||
|
*/
|
||||||
|
void FOBJModel::AddSkins(uint8_t* hitlist)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < surfaces.Size(); i++)
|
||||||
|
{
|
||||||
|
size_t ssIndex = i + curMDLIndex * MD3_MAX_SURFACES;
|
||||||
|
if (curSpriteMDLFrame && i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
||||||
|
{
|
||||||
|
// Precache skins manually reassigned by the user.
|
||||||
|
// On OBJs with lots of skins, such as Doom map OBJs exported from GZDB,
|
||||||
|
// there may be too many skins for the user to manually change, unless
|
||||||
|
// the limit is bumped or surfaceskinIDs is changed to a TArray<FTextureID>.
|
||||||
|
hitlist[curSpriteMDLFrame->surfaceskinIDs[ssIndex].GetIndex()] |= FTextureManager::HIT_Flat;
|
||||||
|
return; // No need to precache skin that was replaced
|
||||||
|
}
|
||||||
|
|
||||||
|
OBJSurface * surf = &surfaces[i];
|
||||||
|
if (surf->skin.isValid())
|
||||||
|
{
|
||||||
|
hitlist[surf->skin.GetIndex()] |= FTextureManager::HIT_Flat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the data that was loaded
|
||||||
|
*/
|
||||||
|
FOBJModel::~FOBJModel()
|
||||||
|
{
|
||||||
|
verts.Clear();
|
||||||
|
norms.Clear();
|
||||||
|
uvs.Clear();
|
||||||
|
faces.Clear();
|
||||||
|
surfaces.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
314
src/models/models_ue1.cpp
Normal file
314
src/models/models_ue1.cpp
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright(C) 2018 Marisa Kirisame
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||||
|
//
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "model_ue1.h"
|
||||||
|
#include "modelrenderer.h"
|
||||||
|
|
||||||
|
float unpackuvert( uint32_t n, int c )
|
||||||
|
{
|
||||||
|
switch( c )
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return ((int16_t)((n&0x7ff)<<5))/128.f;
|
||||||
|
case 1:
|
||||||
|
return ((int16_t)(((n>>11)&0x7ff)<<5))/128.f;
|
||||||
|
case 2:
|
||||||
|
return ((int16_t)(((n>>22)&0x3ff)<<6))/128.f;
|
||||||
|
default:
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FUE1Model::Load( const char *filename, int lumpnum, const char *buffer, int length )
|
||||||
|
{
|
||||||
|
int lumpnum2;
|
||||||
|
FString realfilename = fileSystem.GetFileFullName(lumpnum);
|
||||||
|
if ( (size_t)realfilename.IndexOf("_d.3d") == realfilename.Len()-5 )
|
||||||
|
{
|
||||||
|
realfilename.Substitute("_d.3d","_a.3d");
|
||||||
|
lumpnum2 = fileSystem.CheckNumForFullName(realfilename);
|
||||||
|
mDataLump = lumpnum;
|
||||||
|
mAnivLump = lumpnum2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
realfilename.Substitute("_a.3d","_d.3d");
|
||||||
|
lumpnum2 = fileSystem.CheckNumForFullName(realfilename);
|
||||||
|
mAnivLump = lumpnum;
|
||||||
|
mDataLump = lumpnum2;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FUE1Model::LoadGeometry()
|
||||||
|
{
|
||||||
|
FileData lump, lump2;
|
||||||
|
const char *buffer, *buffer2;
|
||||||
|
lump = fileSystem.ReadFile(mDataLump);
|
||||||
|
buffer = (char*)lump.GetMem();
|
||||||
|
lump2 = fileSystem.ReadFile(mAnivLump);
|
||||||
|
buffer2 = (char*)lump2.GetMem();
|
||||||
|
// map structures
|
||||||
|
dhead = (d3dhead*)(buffer);
|
||||||
|
dpolys = (d3dpoly*)(buffer+sizeof(d3dhead));
|
||||||
|
ahead = (a3dhead*)(buffer2);
|
||||||
|
// detect deus ex format
|
||||||
|
if ( (ahead->framesize/dhead->numverts) == 8 )
|
||||||
|
{
|
||||||
|
averts = NULL;
|
||||||
|
dxverts = (dxvert*)(buffer2+sizeof(a3dhead));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
averts = (uint32_t*)(buffer2+sizeof(a3dhead));
|
||||||
|
dxverts = NULL;
|
||||||
|
}
|
||||||
|
// set counters
|
||||||
|
numVerts = dhead->numverts;
|
||||||
|
numFrames = ahead->numframes;
|
||||||
|
numPolys = dhead->numpolys;
|
||||||
|
numGroups = 0;
|
||||||
|
// populate vertex arrays
|
||||||
|
for ( int i=0; i<numFrames; i++ )
|
||||||
|
{
|
||||||
|
for ( int j=0; j<numVerts; j++ )
|
||||||
|
{
|
||||||
|
UE1Vertex Vert;
|
||||||
|
if ( dxverts != NULL )
|
||||||
|
{
|
||||||
|
// convert padded XYZ16
|
||||||
|
Vert.Pos = Vec3(dxverts[j+i*numVerts].x,
|
||||||
|
dxverts[j+i*numVerts].z,
|
||||||
|
(float)-dxverts[j+i*numVerts].y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// convert packed XY11Z10
|
||||||
|
Vert.Pos = Vec3(unpackuvert(averts[j+i*numVerts],0),
|
||||||
|
unpackuvert(averts[j+i*numVerts],2),
|
||||||
|
-unpackuvert(averts[j+i*numVerts],1));
|
||||||
|
}
|
||||||
|
// refs will be set later
|
||||||
|
Vert.P.Reset();
|
||||||
|
Vert.nP = 0;
|
||||||
|
// push vertex (without normals, will be calculated later)
|
||||||
|
verts.Push(Vert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// populate poly arrays
|
||||||
|
for ( int i=0; i<numPolys; i++ )
|
||||||
|
{
|
||||||
|
UE1Poly Poly;
|
||||||
|
// set indices
|
||||||
|
for ( int j=0; j<3; j++ )
|
||||||
|
Poly.V[j] = dpolys[i].vertices[j];
|
||||||
|
// unpack coords
|
||||||
|
for ( int j=0; j<3; j++ )
|
||||||
|
Poly.C[j] = Vec2(dpolys[i].uv[j][0]/255.f,dpolys[i].uv[j][1]/255.f);
|
||||||
|
// compute facet normals
|
||||||
|
for ( int j=0; j<numFrames; j++ )
|
||||||
|
{
|
||||||
|
Vec3 dir[2];
|
||||||
|
dir[0] = verts[Poly.V[1]+numVerts*j].Pos-verts[Poly.V[0]+numVerts*j].Pos;
|
||||||
|
dir[1] = verts[Poly.V[2]+numVerts*j].Pos-verts[Poly.V[0]+numVerts*j].Pos;
|
||||||
|
Poly.Normals.Push((Vec3::Cross(dir[0],dir[1])).Unit());
|
||||||
|
// since we're iterating frames, also set references for later
|
||||||
|
for ( int k=0; k<3; k++ )
|
||||||
|
{
|
||||||
|
verts[Poly.V[k]+numVerts*j].P.Push(i);
|
||||||
|
verts[Poly.V[k]+numVerts*j].nP++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// push
|
||||||
|
polys.Push(Poly);
|
||||||
|
}
|
||||||
|
// compute normals for vertex arrays (average of all referenced poly normals)
|
||||||
|
// since we have references listed from before, this saves a lot of time
|
||||||
|
// without having to loop through the entire model each vertex (especially true for very complex models)
|
||||||
|
for ( int i=0; i<numFrames; i++ )
|
||||||
|
{
|
||||||
|
for ( int j=0; j<numVerts; j++ )
|
||||||
|
{
|
||||||
|
Vec3 nsum = Vec3(0,0,0);
|
||||||
|
for ( int k=0; k<verts[j+numVerts*i].nP; k++ )
|
||||||
|
nsum += polys[verts[j+numVerts*i].P[k]].Normals[i];
|
||||||
|
verts[j+numVerts*i].Normal = Vec3(nsum.Unit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// populate poly groups (subdivided by texture number and type)
|
||||||
|
// this method minimizes searches in the group list as much as possible
|
||||||
|
// while still doing a single pass through the poly list
|
||||||
|
int curgroup = -1;
|
||||||
|
UE1Group Group;
|
||||||
|
for ( int i=0; i<numPolys; i++ )
|
||||||
|
{
|
||||||
|
// while we're at it, look for attachment triangles
|
||||||
|
// technically only one should exist, but we ain't following the specs 100% here
|
||||||
|
if ( dpolys[i].type&PT_WeaponTriangle ) specialPolys.Push(i);
|
||||||
|
if ( curgroup == -1 )
|
||||||
|
{
|
||||||
|
// no group, create it
|
||||||
|
Group.P.Reset();
|
||||||
|
Group.numPolys = 0;
|
||||||
|
Group.texNum = dpolys[i].texnum;
|
||||||
|
Group.type = dpolys[i].type;
|
||||||
|
groups.Push(Group);
|
||||||
|
curgroup = numGroups++;
|
||||||
|
}
|
||||||
|
else if ( (dpolys[i].texnum != groups[curgroup].texNum) || (dpolys[i].type != groups[curgroup].type) )
|
||||||
|
{
|
||||||
|
// different attributes than last time
|
||||||
|
// search for existing group with new attributes, create one if not found
|
||||||
|
curgroup = -1;
|
||||||
|
for ( int j=0; j<numGroups; j++ )
|
||||||
|
{
|
||||||
|
if ( (groups[j].texNum != dpolys[i].texnum) || (groups[j].type != dpolys[i].type) ) continue;
|
||||||
|
curgroup = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// counter the increment that will happen after continuing this loop
|
||||||
|
// otherwise it'll be skipped over
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
groups[curgroup].P.Push(i);
|
||||||
|
groups[curgroup].numPolys++;
|
||||||
|
}
|
||||||
|
// ... and it's finally done
|
||||||
|
mDataLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FUE1Model::UnloadGeometry()
|
||||||
|
{
|
||||||
|
mDataLoaded = false;
|
||||||
|
specialPolys.Reset();
|
||||||
|
numVerts = 0;
|
||||||
|
numFrames = 0;
|
||||||
|
numPolys = 0;
|
||||||
|
numGroups = 0;
|
||||||
|
verts.Reset();
|
||||||
|
for ( int i=0; i<numPolys; i++ )
|
||||||
|
polys[i].Normals.Reset();
|
||||||
|
polys.Reset();
|
||||||
|
for ( int i=0; i<numGroups; i++ )
|
||||||
|
groups[i].P.Reset();
|
||||||
|
groups.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
int FUE1Model::FindFrame( const char *name )
|
||||||
|
{
|
||||||
|
// unsupported, there are no named frames
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FUE1Model::RenderFrame( FModelRenderer *renderer, FGameTexture *skin, int frame, int frame2, double inter, int translation )
|
||||||
|
{
|
||||||
|
// the moment of magic
|
||||||
|
if ( (frame >= numFrames) || (frame2 >= numFrames) ) return;
|
||||||
|
renderer->SetInterpolation(inter);
|
||||||
|
int vsize, fsize = 0, vofs = 0;
|
||||||
|
for ( int i=0; i<numGroups; i++ ) fsize += groups[i].numPolys*3;
|
||||||
|
for ( int i=0; i<numGroups; i++ )
|
||||||
|
{
|
||||||
|
vsize = groups[i].numPolys*3;
|
||||||
|
if ( groups[i].type&PT_WeaponTriangle )
|
||||||
|
{
|
||||||
|
// weapon triangle should never be drawn, it only exists to calculate attachment position and orientation
|
||||||
|
vofs += vsize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FGameTexture *sskin = skin;
|
||||||
|
if ( !sskin )
|
||||||
|
{
|
||||||
|
int ssIndex = groups[i].texNum + curMDLIndex * MD3_MAX_SURFACES;
|
||||||
|
if (curSpriteMDLFrame && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
||||||
|
sskin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[ssIndex], true);
|
||||||
|
if ( !sskin )
|
||||||
|
{
|
||||||
|
vofs += vsize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Handle per-group render styles and other flags once functions for it are implemented
|
||||||
|
// Future note: poly renderstyles should always be enforced unless the actor itself has a style other than Normal
|
||||||
|
renderer->SetMaterial(sskin,false,translation);
|
||||||
|
renderer->SetupFrame(this, vofs+frame*fsize,vofs+frame2*fsize,vsize);
|
||||||
|
renderer->DrawArrays(0,vsize);
|
||||||
|
vofs += vsize;
|
||||||
|
}
|
||||||
|
renderer->SetInterpolation(0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FUE1Model::BuildVertexBuffer( FModelRenderer *renderer )
|
||||||
|
{
|
||||||
|
if (GetVertexBuffer(renderer->GetType()))
|
||||||
|
return;
|
||||||
|
if ( !mDataLoaded )
|
||||||
|
LoadGeometry();
|
||||||
|
int vsize = 0;
|
||||||
|
for ( int i=0; i<numGroups; i++ )
|
||||||
|
vsize += groups[i].numPolys*3;
|
||||||
|
vsize *= numFrames;
|
||||||
|
auto vbuf = renderer->CreateVertexBuffer(false,numFrames==1);
|
||||||
|
SetVertexBuffer(renderer->GetType(), vbuf);
|
||||||
|
FModelVertex *vptr = vbuf->LockVertexBuffer(vsize);
|
||||||
|
int vidx = 0;
|
||||||
|
for ( int i=0; i<numFrames; i++ )
|
||||||
|
{
|
||||||
|
for ( int j=0; j<numGroups; j++ )
|
||||||
|
{
|
||||||
|
for ( int k=0; k<groups[j].numPolys; k++ )
|
||||||
|
{
|
||||||
|
for ( int l=0; l<3; l++ )
|
||||||
|
{
|
||||||
|
UE1Vertex V = verts[polys[groups[j].P[k]].V[l]+i*numVerts];
|
||||||
|
Vec2 C = polys[groups[j].P[k]].C[l];
|
||||||
|
FModelVertex *vert = &vptr[vidx++];
|
||||||
|
vert->Set(V.Pos.x,V.Pos.y,V.Pos.z,C.x,C.y);
|
||||||
|
if ( groups[j].type&PT_Curvy ) // use facet normal
|
||||||
|
{
|
||||||
|
vert->SetNormal(polys[groups[j].P[k]].Normals[i].x,
|
||||||
|
polys[groups[j].P[k]].Normals[i].y,
|
||||||
|
polys[groups[j].P[k]].Normals[i].z);
|
||||||
|
}
|
||||||
|
else vert->SetNormal(V.Normal.x,V.Normal.y,V.Normal.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vbuf->UnlockVertexBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FUE1Model::AddSkins( uint8_t *hitlist )
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numGroups; i++)
|
||||||
|
{
|
||||||
|
int ssIndex = groups[i].texNum + curMDLIndex * MD3_MAX_SURFACES;
|
||||||
|
if (curSpriteMDLFrame && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
||||||
|
hitlist[curSpriteMDLFrame->surfaceskinIDs[ssIndex].GetIndex()] |= FTextureManager::HIT_Flat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FUE1Model::~FUE1Model()
|
||||||
|
{
|
||||||
|
UnloadGeometry();
|
||||||
|
}
|
489
src/models/tab_anorms.h
Normal file
489
src/models/tab_anorms.h
Normal file
|
@ -0,0 +1,489 @@
|
||||||
|
#ifdef _WIN32
|
||||||
|
#pragma warning(disable:4305)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.525731f, 0.000000f, 0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.442863f, 0.238856f, 0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.295242f, 0.000000f, 0.955423f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.309017f, 0.500000f, 0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.162460f, 0.262866f, 0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.000000f, 1.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.850651f, 0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.147621f, 0.716567f, 0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.147621f, 0.716567f, 0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.525731f, 0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.309017f, 0.500000f, 0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.525731f, 0.000000f, 0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.295242f, 0.000000f, 0.955423f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.442863f, 0.238856f, 0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.162460f, 0.262866f, 0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.681718f, 0.147621f, 0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.809017f, 0.309017f, 0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.587785f, 0.425325f, 0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.850651f, 0.525731f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.864188f, 0.442863f, 0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.716567f, 0.681718f, 0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.688191f, 0.587785f, 0.425325f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.500000f, 0.809017f, 0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.238856f, 0.864188f, 0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.425325f, 0.688191f, 0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.716567f, 0.681718f, -0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.500000f, 0.809017f, -0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.525731f, 0.850651f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.850651f, -0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.238856f, 0.864188f, -0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.955423f, -0.295242f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.262866f, 0.951056f, -0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 1.000000f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.955423f, 0.295242f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.262866f, 0.951056f, 0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.238856f, 0.864188f, 0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.262866f, 0.951056f, 0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.500000f, 0.809017f, 0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.238856f, 0.864188f, -0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.262866f, 0.951056f, -0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.500000f, 0.809017f, -0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.850651f, 0.525731f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.716567f, 0.681718f, 0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.716567f, 0.681718f, -0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.525731f, 0.850651f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.425325f, 0.688191f, 0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.864188f, 0.442863f, 0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.688191f, 0.587785f, 0.425325f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.809017f, 0.309017f, 0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.681718f, 0.147621f, 0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.587785f, 0.425325f, 0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.955423f, 0.295242f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
1.000000f, 0.000000f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.951056f, 0.162460f, 0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.850651f, -0.525731f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.955423f, -0.295242f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.864188f, -0.442863f, 0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.951056f, -0.162460f, 0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.809017f, -0.309017f, 0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.681718f, -0.147621f, 0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.850651f, 0.000000f, 0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.864188f, 0.442863f, -0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.809017f, 0.309017f, -0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.951056f, 0.162460f, -0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.525731f, 0.000000f, -0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.681718f, 0.147621f, -0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.681718f, -0.147621f, -0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.850651f, 0.000000f, -0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.809017f, -0.309017f, -0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.864188f, -0.442863f, -0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.951056f, -0.162460f, -0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.147621f, 0.716567f, -0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.309017f, 0.500000f, -0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.425325f, 0.688191f, -0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.442863f, 0.238856f, -0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.587785f, 0.425325f, -0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.688191f, 0.587785f, -0.425325f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.147621f, 0.716567f, -0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.309017f, 0.500000f, -0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.525731f, -0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.525731f, 0.000000f, -0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.442863f, 0.238856f, -0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.295242f, 0.000000f, -0.955423f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.162460f, 0.262866f, -0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, 0.000000f, -1.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.295242f, 0.000000f, -0.955423f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.162460f, 0.262866f, -0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.442863f, -0.238856f, -0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.309017f, -0.500000f, -0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.162460f, -0.262866f, -0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, -0.850651f, -0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.147621f, -0.716567f, -0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.147621f, -0.716567f, -0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, -0.525731f, -0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.309017f, -0.500000f, -0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.442863f, -0.238856f, -0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.162460f, -0.262866f, -0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.238856f, -0.864188f, -0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.500000f, -0.809017f, -0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.425325f, -0.688191f, -0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.716567f, -0.681718f, -0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.688191f, -0.587785f, -0.425325f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.587785f, -0.425325f, -0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, -0.955423f, -0.295242f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, -1.000000f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.262866f, -0.951056f, -0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, -0.850651f, 0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, -0.955423f, 0.295242f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.238856f, -0.864188f, 0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.262866f, -0.951056f, 0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.500000f, -0.809017f, 0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.716567f, -0.681718f, 0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.525731f, -0.850651f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.238856f, -0.864188f, -0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.500000f, -0.809017f, -0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.262866f, -0.951056f, -0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.850651f, -0.525731f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.716567f, -0.681718f, -0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.716567f, -0.681718f, 0.147621f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.525731f, -0.850651f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.500000f, -0.809017f, 0.309017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.238856f, -0.864188f, 0.442863f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.262866f, -0.951056f, 0.162460f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.864188f, -0.442863f, 0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.809017f, -0.309017f, 0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.688191f, -0.587785f, 0.425325f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.681718f, -0.147621f, 0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.442863f, -0.238856f, 0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.587785f, -0.425325f, 0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.309017f, -0.500000f, 0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.147621f, -0.716567f, 0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.425325f, -0.688191f, 0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.162460f, -0.262866f, 0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.442863f, -0.238856f, 0.864188f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.162460f, -0.262866f, 0.951056f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.309017f, -0.500000f, 0.809017f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.147621f, -0.716567f, 0.681718f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.000000f, -0.525731f, 0.850651f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.425325f, -0.688191f, 0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.587785f, -0.425325f, 0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
0.688191f, -0.587785f, 0.425325f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.955423f, 0.295242f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.951056f, 0.162460f, 0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-1.000000f, 0.000000f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.850651f, 0.000000f, 0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.955423f, -0.295242f, 0.000000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.951056f, -0.162460f, 0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.864188f, 0.442863f, -0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.951056f, 0.162460f, -0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.809017f, 0.309017f, -0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.864188f, -0.442863f, -0.238856f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.951056f, -0.162460f, -0.262866f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.809017f, -0.309017f, -0.500000f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.681718f, 0.147621f, -0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.681718f, -0.147621f, -0.716567f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.850651f, 0.000000f, -0.525731f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.688191f, 0.587785f, -0.425325f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.587785f, 0.425325f, -0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.425325f, 0.688191f, -0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.425325f, -0.688191f, -0.587785f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.587785f, -0.425325f, -0.688191f},
|
||||||
|
|
||||||
|
{
|
||||||
|
-0.688191f, -0.587785f, -0.425325f},
|
Loading…
Reference in a new issue