diff --git a/src/ObjLoading/Game/T6/BSP/BSP.h b/src/ObjLoading/Game/T6/BSP/BSP.h index ae080d6b..cad8b6da 100644 --- a/src/ObjLoading/Game/T6/BSP/BSP.h +++ b/src/ObjLoading/Game/T6/BSP/BSP.h @@ -61,13 +61,15 @@ namespace BSP struct BSPLight { BSPLightType type; - vec3_t pos; - vec3_t direction; vec3_t colour; float range; float intensity; - // only used on spot and dir lights + vec3_t pos; + vec3_t direction; + bool hasPosBeenSet; + + // angle is in radians. only used on spot/dir lights float innerConeAngle; float outerConeAngle; }; diff --git a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp index fe07b649..c77e3332 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPCreator.cpp @@ -125,6 +125,8 @@ namespace throw GltfLoadException(std::format("Element count of {} accessor does not match expected vertex count of {}", accessorType, vertexCount)); } + using Transform3f = Eigen::Transform; + Eigen::Matrix4f createNodeMatrix(const gltf::JsonNode& node) { if (node.matrix) @@ -180,18 +182,15 @@ namespace } Eigen::Vector3f translation(localTranslation[0], localTranslation[1], localTranslation[2]); - Eigen::Quaternionf rotation(localRotation[0], localRotation[1], localRotation[2], localRotation[3]); + Eigen::Quaternionf rotation(localRotation[3], localRotation[0], localRotation[1], localRotation[2]); // GLTF is XYZW, Eigen is WXYZ Eigen::Vector3f scale(localScale[0], localScale[1], localScale[2]); - Eigen::Affine3f transform = Eigen::Affine3f::Identity(); - transform.translate(translation); - transform.rotate(rotation); - transform.scale(scale); - - return transform.matrix(); + Transform3f T; + T = T.fromPositionOrientationScale(translation, rotation, scale); + return T.matrix(); } - unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, BSPSurface& surface) + unsigned CreateVertices(const AccessorsForVertex& accessorsForVertex, const gltf::JsonNode& node, Eigen::Matrix4f& nodeMatrix, BSPSurface& surface) { // clang-format off const auto* positionAccessor = GetAccessorForIndex( @@ -271,7 +270,6 @@ namespace m_bsp->gfxWorld.indices.emplace_back(static_cast(indices[2])); } - Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); const auto vertexOffset = static_cast(m_bsp->gfxWorld.vertices.size()); m_bsp->gfxWorld.vertices.reserve(vertexOffset + vertexCount); for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++) @@ -315,7 +313,7 @@ namespace return vertexOffset; } - void loadSurfaceMaterialData(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive, BSPSurface& surface) + void loadSurfaceLightData(const JsonRoot& jRoot, const JsonMeshPrimitives& primitive, BSPSurface& surface) { if (!primitive.material) { @@ -330,9 +328,53 @@ namespace bool CreateSurfacesFromNode(const JsonRoot& jRoot, const gltf::JsonNode& node) { - if (!node.mesh) + if (!node.mesh && !node.extensions) return false; + Eigen::Matrix4f nodeMatrix = createNodeMatrix(node); + + if (node.extensions && node.extensions->KHR_lights_punctual) + { + int lightIndex = node.extensions->KHR_lights_punctual->light; + + assert(lightIndex >= 0); + if (m_bsp->lights[lightIndex].hasPosBeenSet == true) + con::warn("Internal error, multiple nodes reference the same light. Light positions/rotations are likely incorrect."); + + Eigen::Vector4f position(0, 0, 0, 1.0f); + Eigen::Vector4f transformedPosition = nodeMatrix * position; + m_bsp->lights[lightIndex].pos = vec3_t{transformedPosition.x(), transformedPosition.y(), transformedPosition.z()}; + RhcToLhcCoordinates(m_bsp->lights[lightIndex].pos.v); + + // BO2 uses -Z up and the default light direction is straight down + Eigen::Vector3f defaultDirection(0.0f, 0.0f, 1.0f); + Eigen::Vector3f outputDirection; + + if (node.rotation) + { + Eigen::Quaternionf rotationQuat( + (*node.rotation)[3], (*node.rotation)[0], (*node.rotation)[1], (*node.rotation)[2]); // GLTF is XYZW, Eigen is WXYZ + outputDirection = rotationQuat * defaultDirection; + } + else if (node.matrix) + { + con::error("matrix rotation unimpelemted"); + assert(false); + // Eigen::Quaternionf rotationQuat; + // rotationQuat = nodeMatrix; + // outputDirection = rotationQuat * defaultDirection; + } + else + outputDirection = defaultDirection; + outputDirection.normalize(); + m_bsp->lights[lightIndex].direction = vec3_t{outputDirection.x(), outputDirection.y(), outputDirection.z()}; + RhcToLhcCoordinates(m_bsp->lights[lightIndex].direction.v); + + m_bsp->lights[lightIndex].hasPosBeenSet = true; + + return true; + } + con::info("Mesh {} found", node.name.has_value() ? node.name.value() : ""); const auto& mesh = jRoot.meshes.value()[node.mesh.value()]; @@ -359,8 +401,14 @@ namespace BSPSurface surface; - loadSurfaceMaterialData(jRoot, primitive, surface); - CreateVertices(accessorsForVertex, node, surface); + if (primitive.material) + surface.materialIndex = *primitive.material; + else if (primitive.attributes.COLOR_0) + surface.materialIndex = m_color_mat_idx; + else + throw GltfLoadException("Primitive requires material or colour data."); + + CreateVertices(accessorsForVertex, node, nodeMatrix, surface); m_bsp->gfxWorld.surfaces.emplace_back(surface); } @@ -433,6 +481,78 @@ namespace m_bsp->gfxWorld.materials.emplace_back(colorMaterial); } + void LoadLights(const JsonRoot& jRoot) + { + if (!jRoot.extensions) + return; + if (!jRoot.extensions->KHR_lights_punctual) + return; + if (!jRoot.extensions->KHR_lights_punctual->lights) + return; + const std::vector& jsLightArray = jRoot.extensions->KHR_lights_punctual->lights.value(); + + m_bsp->lights.reserve(jsLightArray.size()); + for (const JsonPunctualLight& jsLight : jsLightArray) + { + if (jsLight.type == JsonPunctualLightType::POINT) + con::error("Any point lights will be converted to a spotlight as point lights are unsupported right now."); + + BSPLight light{}; + + // position and direction data will be set during node traversal + light.hasPosBeenSet = false; + + if (!jsLight.color) + { + light.colour.x = 1.0f; + light.colour.y = 1.0f; + light.colour.z = 1.0f; + } + else + { + light.colour.x = (*jsLight.color)[0]; + light.colour.y = (*jsLight.color)[1]; + light.colour.z = (*jsLight.color)[2]; + } + + if (!jsLight.intensity) + light.intensity = 100000.0f; // adjusted from GLTF spec to better match BO2 + else + light.intensity = *jsLight.intensity; + + if (!jsLight.range) + light.range = 1000.0f; // adjusted from GLTF spec to better match BO2 + else + light.range = *jsLight.range; + + if (jsLight.type == JsonPunctualLightType::DIRECTIONAL) + { + light.type = LIGHT_TYPE_DIRECTIONAL; + } + else if (jsLight.type == JsonPunctualLightType::POINT) + { + light.type = LIGHT_TYPE_POINT; + } + else // JsonPunctualLightType::SPOT + { + light.type = LIGHT_TYPE_SPOT; + assert(jsLight.spot); + + if (!jsLight.spot->innerConeAngle) + light.innerConeAngle = 0.0f; + else + light.innerConeAngle = *jsLight.spot->innerConeAngle; + + if (!jsLight.spot->outerConeAngle) + light.outerConeAngle = 3.14159265359f / 4.0f; /// 45 degrees + else + light.outerConeAngle = *jsLight.spot->outerConeAngle; + } + + m_bsp->lights.emplace_back(light); + } + } + void TraverseNodes(const JsonRoot& jRoot) { // Make sure there are any nodes to traverse @@ -570,6 +690,7 @@ namespace CreateBufferViews(jRoot); CreateAccessors(jRoot); + LoadLights(jRoot); LoadMaterials(jRoot); TraverseNodes(jRoot); } diff --git a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp index 8c56f95d..795e7a56 100644 --- a/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp +++ b/src/ObjLoading/Game/T6/BSP/BSPUtil.cpp @@ -103,28 +103,23 @@ namespace BSP return sqrtf((x * x) + (y * y) + (z * z)); } - // angles are in euler degrees void BSPUtil::convertAnglesToAxis(vec3_t* angles, vec3_t* axis) { - float xRadians = angles->x * 0.017453292f; // M_PI / 180.0f - float yRadians = angles->y * 0.017453292f; // M_PI / 180.0f - float zRadians = angles->z * 0.017453292f; // M_PI / 180.0f - - float cosX = cos(xRadians); - float sinX = sin(xRadians); - float cosY = cos(yRadians); - float sinY = sin(yRadians); - float cosZ = cos(zRadians); - float sinZ = sin(zRadians); + float cosX = cos(angles->x); + float sinX = sin(angles->x); + float cosY = cos(angles->y); + float sinY = sin(angles->y); + float cosZ = cos(angles->z); + float sinZ = sin(angles->z); axis[0].x = cosX * cosY; axis[0].y = cosX * sinY; axis[0].z = -sinX; - axis[1].x = (sinZ * sinX * cosY) - (cosZ * sinY); - axis[1].y = (sinZ * sinX * sinY) + (cosZ * cosY); + axis[1].x = ((sinZ * sinX) * cosY) - (cosZ * sinY); + axis[1].y = ((sinZ * sinX) * sinY) + (cosZ * cosY); axis[1].z = sinZ * cosX; - axis[2].x = (cosZ * sinX * cosY) + (sinZ * sinY); - axis[2].y = (cosZ * sinX * sinY) - (sinZ * cosY); + axis[2].x = ((cosZ * sinX) * cosY) + (sinZ * sinY); + axis[2].y = ((cosZ * sinX) * sinY) - (sinZ * cosY); axis[2].z = cosZ * cosX; } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp index 8d6fa9aa..9bec483e 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/ComWorldLinker.cpp @@ -1,5 +1,7 @@ #include "ComWorldLinker.h" +#include "../BSPUtil.h" + #define _USE_MATH_DEFINES #include @@ -44,24 +46,31 @@ namespace BSP light->origin.y = bspLight->pos.y; light->origin.z = bspLight->pos.z; - light->color.x = bspLight->colour.x; - light->color.y = bspLight->colour.y; - light->color.z = bspLight->colour.z; - light->diffuseColor.x = bspLight->colour.x; - light->diffuseColor.y = bspLight->colour.y; - light->diffuseColor.z = bspLight->colour.z; - light->diffuseColor.w = 0.0f; + // colour also effects the brightness of the light, it's unclear what the min/max is + vec3_t BSPColor; + BSPColor.x = bspLight->colour.x; + BSPColor.y = bspLight->colour.y; + BSPColor.z = bspLight->colour.z; + light->color.x = BSPColor.x; + light->color.y = BSPColor.y; + light->color.z = BSPColor.z; + light->diffuseColor.x = BSPColor.x; + light->diffuseColor.y = BSPColor.y; + light->diffuseColor.z = BSPColor.z; + light->diffuseColor.w = 0.0f; // always 0 + // the forward dir of the light, default is (0, 0, 1) to face down (-Z up) light->dir.x = bspLight->direction.x; light->dir.y = bspLight->direction.y; light->dir.z = bspLight->direction.z; - light->dAttenuation = bspLight->intensity; // not too sure if this is correct or not + light->dAttenuation = bspLight->intensity; // not 100% sure if these values are calculated the same + // the radius of the light's lens light->radius = bspLight->range; light->falloff.x = 0.0f; - light->falloff.y = bspLight->range; + light->falloff.y = bspLight->range; // only Y is set light->falloff.z = 0.0f; light->falloff.w = 0.0f; @@ -71,16 +80,17 @@ namespace BSP light->angle.z = 0.0f; light->angle.w = 0.0f; + // 0 - light cannot move + // 1 <= x > 0 - limit that it can move each game update + light->translationLimit = 0.0f; + // 1.0f - light cannot rotate // -1.0f - infinitely rotate // between 1 and -1 - limit that it can rotate each game update - light->translationLimit = 1.0f; - - // 0 - light cannot move - // 1 <= x > 0 - limit that it can move each game update - light->rotationLimit = 0.0f; + light->rotationLimit = 1.0f; // default values from official map + light->useCookie = 0; light->cookieControl0.x = 0.0f; light->cookieControl0.y = 0.0f; light->cookieControl0.z = 1.0f; @@ -94,39 +104,32 @@ namespace BSP light->cookieControl2.z = 0.0f; light->cookieControl2.w = 0.0f; - // values taken from an mp_overflow light + // these are possibly the result of cos(), unsure how the angle is generated though + // values taken from official maps light->aAbB.x = 0.5303301215171814f; light->aAbB.y = 0.7071067690849304f; light->aAbB.z = 0.5303301215171814f; light->aAbB.w = 0.7071067690849304f; - light->canUseShadowMap = false; - light->shadowmapVolume = 0; + // light won't light the surface unless canUseShadowMap is set to 1 + // may be to do with lights casting shadows on other lights + light->canUseShadowMap = 1; + light->shadowmapVolume = 0; // ignored if this is set to 0 + light->exponent = 0; light->priority = 0; - light->cullDist = 10000; - light->useCookie = 0; + light->cullDist = 1000; light->mipDistance = 0.0f; } ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp) { - BSPLight eeeee; - eeeee.type = LIGHT_TYPE_POINT; - eeeee.pos = vec3_t{22.35f, (-493.42f), 10.96f}; - eeeee.direction = vec3_t{0.0f, 0.0f, 0.0f}; - eeeee.colour = vec3_t{1.0f, 1.0f, 1.0f}; - eeeee.range = 1000.0f; - eeeee.intensity = 1515948.33f; - eeeee.innerConeAngle = 0.0f; - eeeee.outerConeAngle = 0.0f; - bsp->lights.emplace_back(eeeee); - // all lights that aren't the sunlight or default light need their own GfxLightDef asset ComWorld* comWorld = m_memory.Alloc(); comWorld->name = m_memory.Dup(bsp->bspName.c_str()); comWorld->isInUse = 1; + // first two lights are the empty light and the sun light. size_t totalLightCount = bsp->lights.size() + BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT; comWorld->primaryLightCount = static_cast(totalLightCount); comWorld->primaryLights = m_memory.Alloc(totalLightCount); @@ -158,9 +161,6 @@ namespace BSP else { BSPLight* bspLight = &bsp->lights.at(lightIdx - BSPGameConstants::BSP_DEFAULT_LIGHT_COUNT); - - // cosHalfFovOuter, cosHalfFovInner, cosHalfFovExpanded - setLightCommonValues(light, bspLight); if (bspLight->type == LIGHT_TYPE_DIRECTIONAL) { light->type = GFX_LIGHT_TYPE_DIR; @@ -184,10 +184,11 @@ namespace BSP light->type = GFX_LIGHT_TYPE_OMNI; light->defName = "white_light_cube"; light->roundness = 0.0f; - light->cosHalfFovInner = cosf(30 * (M_PI / 180.0)); - light->cosHalfFovOuter = cosf(60 * (M_PI / 180.0)); - light->cosHalfFovExpanded = cosf(60 * (M_PI / 180.0)); + light->cosHalfFovInner = cosf(30.0f * (M_PI / 180.0f)); + light->cosHalfFovOuter = cosf(55.0f * (M_PI / 180.0f)); + light->cosHalfFovExpanded = cosf(55.0f * (M_PI / 180.0f)); } + setLightCommonValues(light, bspLight); } }