diff --git a/.travis.yml b/.travis.yml index 956ed21cf..6962cbaff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ matrix: - CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Debug -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9" - os: osx - osx_image: xcode9.4 + osx_image: xcode10 env: - CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9" diff --git a/src/posix/osx/zdoom-info.plist b/src/posix/osx/zdoom-info.plist index f0bbbf3d9..341a061d7 100644 --- a/src/posix/osx/zdoom-info.plist +++ b/src/posix/osx/zdoom-info.plist @@ -48,5 +48,7 @@ NSApplication NSSupportsAutomaticGraphicsSwitching + NSRequiresAquaSystemAppearance + diff --git a/src/r_data/models/models_obj.cpp b/src/r_data/models/models_obj.cpp index 4cc5ffa17..035efd4d4 100644 --- a/src/r_data/models/models_obj.cpp +++ b/src/r_data/models/models_obj.cpp @@ -101,8 +101,9 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length FTextureID curMtl = FNullTextureID(); OBJSurface *curSurface = nullptr; - int aggSurfFaceCount = 0; - int curSurfFaceCount = 0; + unsigned int aggSurfFaceCount = 0; + unsigned int curSurfFaceCount = 0; + unsigned int curSmoothGroup = 0; while(sc.GetString()) { @@ -186,9 +187,25 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length 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(); @@ -277,11 +294,13 @@ bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) else { side.normref = -1; + hasMissingNormals = true; } } else { side.normref = -1; + hasMissingNormals = true; } } else @@ -289,6 +308,7 @@ bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) origIdx = atoi(sideStr.GetChars()); side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); side.normref = -1; + hasMissingNormals = true; side.uvref = -1; } face.sides[sidx] = side; @@ -348,15 +368,20 @@ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) surfaces[i].vbStart = vbufsize; vbufsize += surfaces[i].numTris * 3; } + // Initialize/populate vertFaces + if (hasMissingNormals && hasSmoothGroups) + { + AddVertFaces(); + } auto vbuf = renderer->CreateVertexBuffer(false,true); SetVertexBuffer(renderer, vbuf); FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize); - for (size_t i = 0; i < surfaces.Size(); i++) + for (unsigned int i = 0; i < surfaces.Size(); i++) { - for (size_t j = 0; j < surfaces[i].numTris; j++) + for (unsigned int j = 0; j < surfaces[i].numTris; j++) { for (size_t side = 0; side < 3; side++) { @@ -372,39 +397,40 @@ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) FVector3 curVvec = RealignVector(verts[vidx]); FVector2 curUvec = FixUV(uvs[uvidx]); - FVector3 *nvec = nullptr; + FVector3 nvec; mdv->Set(curVvec.X, curVvec.Y, curVvec.Z, curUvec.X, curUvec.Y); if (nidx >= 0 && (unsigned int)nidx < norms.Size()) { - nvec = new FVector3(RealignVector(norms[nidx])); + nvec = RealignVector(norms[nidx]); } else { - // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal - // Find other sides of triangle - auto nextSidx = side + 2; - if (nextSidx >= 3) nextSidx -= 3; - - auto lastSidx = side + 1; - if (lastSidx >= 3) lastSidx -= 3; - - OBJFaceSide &nextSide = surfaces[i].tris[j].sides[nextSidx]; - OBJFaceSide &lastSide = surfaces[i].tris[j].sides[lastSidx]; - - // Cross-multiply the U-vector and V-vector - FVector3 uvec = RealignVector(verts[nextSide.vertref]) - curVvec; - FVector3 vvec = RealignVector(verts[lastSide.vertref]) - curVvec; - - nvec = new FVector3(uvec ^ vvec); + 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 nvec; + 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(); } @@ -432,6 +458,7 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) 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 @@ -443,6 +470,7 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) delete[] triangulated; triIdx += 1; // Filling out two faces } + DPrintf(DMSG_SPAMMY, "Smooth group: %d\n", surf.tris[triIdx].smoothGroup); } } @@ -455,7 +483,9 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) 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}}; @@ -470,6 +500,26 @@ void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) } } +/** + * Add the vertices of all surfaces' triangles to the array of vertex->triangle references + */ +void FOBJModel::AddVertFaces() { + // Initialize and populate vertFaces - this array stores references to triangles per vertex + vertFaces = new TArray[verts.Size()]; + for (unsigned int i = 0; i < surfaces.Size(); i++) + { + for (unsigned int j = 0; j < surfaces[i].numTris; j++) + { + OBJTriRef otr = OBJTriRef(i, j); + for (size_t k = 0; k < surfaces[i].tris[j].sideCount; k++) + { + int vidx = surfaces[i].tris[j].sides[k].vertref; + vertFaces[vidx].Push(otr); + } + } + } +} + /** * Re-align a vector to match MD3 alignment * @@ -494,6 +544,65 @@ inline FVector2 FOBJModel::FixUV(FVector2 vecToRealign) return vecToRealign; } +/** + * Calculate the surface normal for a triangle + * + * @param surfIdx The surface index + * @param triIdx The triangle Index + * @return The surface normal vector + */ +FVector3 FOBJModel::CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx) +{ + // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal + int curVert = surfaces[surfIdx].tris[triIdx].sides[0].vertref; + int nextVert = surfaces[surfIdx].tris[triIdx].sides[2].vertref; + int lastVert = surfaces[surfIdx].tris[triIdx].sides[1].vertref; + + // Cross-multiply the U-vector and V-vector + FVector3 curVvec = RealignVector(verts[curVert]); + FVector3 uvec = RealignVector(verts[nextVert]) - curVvec; + FVector3 vvec = RealignVector(verts[lastVert]) - curVvec; + + return uvec ^ vvec; +} + +/** + * Calculate the surface normal for a triangle + * + * @param otr A reference to the surface, and a triangle within that surface, as an OBJTriRef + * @return The surface normal vector + */ +FVector3 FOBJModel::CalculateNormalFlat(OBJTriRef otr) +{ + return CalculateNormalFlat(otr.surf, otr.tri); +} + +/** + * Calculate the normal of a vertex in a specific smooth group + * + * @param vidx The index of the vertex in the array of vertices + * @param smoothGroup The smooth group number + */ +FVector3 FOBJModel::CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup) +{ + unsigned int connectedFaces = 0; + TArray& vTris = vertFaces[vidx]; + + FVector3 vNormal(0,0,0); + for (size_t face = 0; face < vTris.Size(); face++) + { + OBJFace& tri = surfaces[vTris[face].surf].tris[vTris[face].tri]; + if (tri.smoothGroup == smoothGroup) + { + FVector3 fNormal = CalculateNormalFlat(vTris[face]); + connectedFaces += 1; + vNormal += fNormal; + } + } + vNormal /= (float)connectedFaces; + return vNormal; +} + /** * Find the index of the frame with the given name * diff --git a/src/r_data/models/models_obj.h b/src/r_data/models/models_obj.h index d6d68a58e..01a6e03cf 100644 --- a/src/r_data/models/models_obj.h +++ b/src/r_data/models/models_obj.h @@ -30,6 +30,8 @@ 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 { @@ -38,6 +40,14 @@ private: 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; @@ -47,7 +57,9 @@ private: struct OBJFace { unsigned int sideCount; + unsigned int smoothGroup; OBJFaceSide sides[4]; + OBJFace(): sideCount(0), smoothGroup(0) {} }; struct OBJSurface // 1 surface per 'usemtl' { @@ -66,16 +78,21 @@ private: TArray faces; TArray surfaces; FScanner sc; + TArray* vertFaces; + int ResolveIndex(int origIndex, FaceElement el); template void ParseVector(TArray &array); bool ParseFaceSide(const FString &side, OBJFace &face, int sidx); void ConstructSurfaceTris(OBJSurface &surf); - int ResolveIndex(int origIndex, FaceElement el); + void AddVertFaces(); void TriangulateQuad(const OBJFace &quad, OBJFace *tris); FVector3 RealignVector(FVector3 vecToRealign); FVector2 FixUV(FVector2 vecToRealign); + FVector3 CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx); + FVector3 CalculateNormalFlat(OBJTriRef otr); + FVector3 CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup); public: - FOBJModel() {} + 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; diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 6ce6fcf23..6a58cb0cf 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2442,7 +2442,9 @@ OPTVAL_FOO_DUMB = "foo_dumb"; OPTVAL_ALIASING = "Aliasing"; OPTVAL_LINEAR = "Linear"; OPTVAL_NEAREST = "Nearest"; -OPTVAL_PCF = "PCF"; +OPTVAL_PCF_LOW = "PCF (Low)"; +OPTVAL_PCF_MEDIUM = "PCF (Medium)"; +OPTVAL_PCF_HIGH = "PCF (High)"; OPTVAL_CUBIC = "Cubic"; OPTVAL_BLEP = "Band-limited step"; OPTVAL_LINEARSLOW = "Linear (Slower)"; diff --git a/wadsrc/static/language.fr b/wadsrc/static/language.fr index c43a3c222..22cd852d8 100644 --- a/wadsrc/static/language.fr +++ b/wadsrc/static/language.fr @@ -2419,7 +2419,9 @@ OPTVAL_DEFAULT = "Défaut"; OPTVAL_SOUNDSYSTEM = "Système Sonore"; OPTVAL_LINEAR = "Linéaire"; OPTVAL_NEAREST = "Nearest"; -OPTVAL_PCF = "PCF"; +OPTVAL_PCF_LOW = "PCF (Low)"; +OPTVAL_PCF_MEDIUM = "PCF (Medium)"; +OPTVAL_PCF_HIGH = "PCF (High)"; OPTVAL_CUBIC = "Cubique"; OPTVAL_BLEP = "Step limité sur bande"; OPTVAL_LINEARSLOW = "Linéaire (Lent)"; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index f210af477..640b6186c 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -2261,7 +2261,9 @@ OptionValue ShadowMapQuality OptionValue ShadowMapFilter { 0, "$OPTVAL_NEAREST" - 1, "$OPTVAL_PCF" + 1, "$OPTVAL_PCF_LOW" + 2, "$OPTVAL_PCF_MEDIUM" + 3, "$OPTVAL_PCF_HIGH" } diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index c8ad439cb..228d7b02d 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -178,47 +178,26 @@ float shadowDirToU(vec2 dir) } } -float sampleShadowmap(vec2 dir, float v) +vec2 shadowUToDir(float u) { - float u = shadowDirToU(dir); - float dist2 = dot(dir, dir); - return step(dist2, texture(ShadowMap, vec2(u, v)).x); + u *= 4.0; + vec2 raydir; + switch (int(u)) + { + case 0: raydir = vec2(u * 2.0 - 1.0, 1.0); break; + case 1: raydir = vec2(1.0, 1.0 - (u - 1.0) * 2.0); break; + case 2: raydir = vec2(1.0 - (u - 2.0) * 2.0, -1.0); break; + case 3: raydir = vec2(-1.0, (u - 3.0) * 2.0 - 1.0); break; + } + return raydir; } -float sampleShadowmapLinear(vec2 dir, float v) +float sampleShadowmap(vec3 planePoint, float v) { - float u = shadowDirToU(dir); - float dist2 = dot(dir, dir); + float bias = 1.0; + float negD = dot(vWorldNormal.xyz, planePoint); - vec2 isize = textureSize(ShadowMap, 0); - vec2 size = vec2(isize); - - vec2 fetchPos = vec2(u, v) * size - vec2(0.5, 0.0); - if (fetchPos.x < 0.0) - fetchPos.x += size.x; - - ivec2 ifetchPos = ivec2(fetchPos); - int y = ifetchPos.y; - - float t = fract(fetchPos.x); - int x0 = ifetchPos.x; - int x1 = ifetchPos.x + 1; - if (x1 == isize.x) - x1 = 0; - - float depth0 = texelFetch(ShadowMap, ivec2(x0, y), 0).x; - float depth1 = texelFetch(ShadowMap, ivec2(x1, y), 0).x; - return mix(step(dist2, depth0), step(dist2, depth1), t); -} - -vec2 shadowmapAdjustedRay(vec4 lightpos) -{ - vec3 planePoint = pixelpos.xyz - lightpos.xyz; - - if (dot(planePoint.xz, planePoint.xz) < 1.0) - return planePoint.xz * 0.5; - - vec3 ray = normalize(planePoint); + vec3 ray = planePoint; vec2 isize = textureSize(ShadowMap, 0); float scale = float(isize.x) * 0.25; @@ -239,51 +218,70 @@ vec2 shadowmapAdjustedRay(vec4 lightpos) ray.x = sign(ray.x); } - float bias = 1.0; - float negD = dot(vWorldNormal.xyz, planePoint); float t = negD / dot(vWorldNormal.xyz, ray) - bias; - return ray.xz * t; + vec2 dir = ray.xz * t; + + float u = shadowDirToU(dir); + float dist2 = dot(dir, dir); + return step(dist2, texture(ShadowMap, vec2(u, v)).x); } -//=========================================================================== -// -// Check if light is in shadow using Percentage Closer Filtering (PCF) -// -//=========================================================================== +float sampleShadowmapPCF(vec3 planePoint, float v) +{ + float bias = 1.0; + float negD = dot(vWorldNormal.xyz, planePoint); + + vec3 ray = planePoint; + + if (abs(ray.z) > abs(ray.x)) + ray.y = ray.y / abs(ray.z); + else + ray.y = ray.y / abs(ray.x); + + vec2 isize = textureSize(ShadowMap, 0); + float scale = float(isize.x); + float texelPos = floor(shadowDirToU(ray.xz) * scale); + + float sum = 0.0; + float step_count = uShadowmapFilter; + + texelPos -= step_count + 0.5; + for (float x = -step_count; x <= step_count; x++) + { + float u = fract(texelPos / scale); + vec2 dir = shadowUToDir(u); + + ray.x = dir.x; + ray.z = dir.y; + float t = negD / dot(vWorldNormal.xyz, ray) - bias; + dir = ray.xz * t; + + float dist2 = dot(dir, dir); + sum += step(dist2, texture(ShadowMap, vec2(u, v)).x); + texelPos++; + } + return sum / (uShadowmapFilter * 2.0 + 1.0); +} float shadowmapAttenuation(vec4 lightpos, float shadowIndex) { if (shadowIndex >= 1024.0) return 1.0; // No shadowmap available for this light - float v = (shadowIndex + 0.5) / 1024.0; + vec3 planePoint = pixelpos.xyz - lightpos.xyz; - vec2 ray = shadowmapAdjustedRay(lightpos); + if (dot(planePoint.xz, planePoint.xz) < 1.0) + return 1.0; // Light is too close + + float v = (shadowIndex + 0.5) / 1024.0; if (uShadowmapFilter <= 0) { - return sampleShadowmap(ray, v); - //return sampleShadowmapLinear(ray, v); + return sampleShadowmap(planePoint, v); } else { - float length = length(ray); - if (length < 3.0) - return 1.0; - - vec2 dir = ray / length * min(length / 50.0, 1.0); // avoid sampling behind light - - vec2 normal = vec2(-dir.y, dir.x); - vec2 bias = dir * 10.0; - - float sum = 0.0; - float step_count = ((uShadowmapFilter - 1) / 2.); - - for (float x = -step_count; x <= step_count; x++) - { - sum += sampleShadowmap(ray + normal * x /*- bias * abs(x)*/, v); - } - return sum / uShadowmapFilter; + return sampleShadowmapPCF(planePoint, v); } } diff --git a/wadsrc/static/zscript/hexen/spike.txt b/wadsrc/static/zscript/hexen/spike.txt index cb8df17e2..a411dbf3f 100644 --- a/wadsrc/static/zscript/hexen/spike.txt +++ b/wadsrc/static/zscript/hexen/spike.txt @@ -179,26 +179,29 @@ class ThrustFloor : Actor while (it.Next()) { let targ = it.thing; - double blockdist = radius + it.thing.radius; - if (abs(targ.pos.x - it.Position.X) >= blockdist || abs(targ.pos.y - it.Position.Y) >= blockdist) - continue; - - // Q: Make this z-aware for everything? It never was before. - if (targ.pos.z + targ.height < pos.z || targ.pos.z > pos.z + height) + if (targ != null) { - if (CurSector.PortalGroup != targ.CurSector.PortalGroup) + double blockdist = radius + targ.radius; + if (abs(targ.pos.x - it.Position.X) >= blockdist || abs(targ.pos.y - it.Position.Y) >= blockdist) continue; + + // Q: Make this z-aware for everything? It never was before. + if (targ.pos.z + targ.height < pos.z || targ.pos.z > pos.z + height) + { + if (CurSector.PortalGroup != targ.CurSector.PortalGroup) + continue; + } + + if (!targ.bShootable) + continue; + + if (targ == self) + continue; // don't clip against self + + int newdam = targ.DamageMobj (self, self, 10001, 'Crush'); + targ.TraceBleed (newdam > 0 ? newdam : 10001, null); + args[1] = 1; // Mark thrust thing as bloody } - - if (!targ.bShootable) - continue; - - if (targ == self) - continue; // don't clip against self - - int newdam = targ.DamageMobj (self, self, 10001, 'Crush'); - targ.TraceBleed (newdam > 0 ? newdam : 10001, null); - args[1] = 1; // Mark thrust thing as bloody } } } diff --git a/wadsrc/static/zscript/menu/optionmenu.txt b/wadsrc/static/zscript/menu/optionmenu.txt index 3e1853735..f73035ab0 100644 --- a/wadsrc/static/zscript/menu/optionmenu.txt +++ b/wadsrc/static/zscript/menu/optionmenu.txt @@ -540,14 +540,19 @@ class CompatibilityMenu : OptionMenu class GLTextureGLOptions : OptionMenu { private int mWarningIndex; + private string mWarningLabel; override void Init(Menu parent, OptionMenuDescriptor desc) { super.Init(parent, desc); + // Find index of warning item placeholder + mWarningIndex = -1; + mWarningLabel = "!HQRESIZE_WARNING!"; + for (int i=0; i < mDesc.mItems.Size(); ++i) { - if (mDesc.mItems[i].mLabel == "!HQRESIZE_WARNING!") + if (mDesc.mItems[i].mLabel == mWarningLabel) { mWarningIndex = i; break; @@ -555,11 +560,22 @@ class GLTextureGLOptions : OptionMenu } } + override void OnDestroy() + { + // Restore warning item placeholder + if (mWarningIndex >= 0) + { + mDesc.mItems[mWarningIndex].mLabel = mWarningLabel; + } + + Super.OnDestroy(); + } + override void Ticker() { Super.Ticker(); - if (mWarningIndex > 0) + if (mWarningIndex >= 0) { string message; diff --git a/wadsrc_lights/static/filter/hexen/gldefs.txt b/wadsrc_lights/static/filter/hexen/gldefs.txt index cc9513d97..fb6cb49c3 100644 --- a/wadsrc_lights/static/filter/hexen/gldefs.txt +++ b/wadsrc_lights/static/filter/hexen/gldefs.txt @@ -1545,11 +1545,6 @@ pointlight MANA1 offset 0 36 0 } -object Mana1 -{ - frame MAN1 { light MANA1 } -} - // Green mana pointlight MANA2 { @@ -1559,11 +1554,6 @@ pointlight MANA2 offset 0 36 0 } -object Mana2 -{ - frame MAN2 { light MANA2 } -} - // Combined mana pointlight MANA3 {