Skip to content

Commit

Permalink
Add: Support for KHR_lights_punctual
Browse files Browse the repository at this point in the history
  • Loading branch information
spnda committed Jan 22, 2023
1 parent f67844e commit 7c7a81b
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 7 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ fastgltf supports glTF 2.0:
fastgltf supports a number of glTF extensions:
- [x] EXT_meshopt_compression
- [x] EXT_texture_webp
- [x] KHR_lights_punctual
- [x] KHR_texture_basisu
- [x] KHR_texture_transform
- [x] KHR_mesh_quantization
- [x] MSFT_texture_dds

fastgltf brings many utilities:
- [x] SIMD powered base64 buffer decoder
- [x] Can decompose transform matrices, so you only ever have translation, rotation, and scale.
Expand Down
150 changes: 144 additions & 6 deletions src/fastgltf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ static constexpr std::array<std::pair<std::string_view, fastgltf::Extensions>, 8
{ fg::extensions::EXT_mesh_gpu_instancing, fg::Extensions::EXT_mesh_gpu_instancing },
{ fg::extensions::EXT_meshopt_compression, fg::Extensions::EXT_meshopt_compression },
{ fg::extensions::EXT_texture_webp, fg::Extensions::EXT_texture_webp },
{ fg::extensions::KHR_lights_punctual, fg::Extensions::KHR_lights_punctual },
{ fg::extensions::KHR_mesh_quantization, fg::Extensions::KHR_mesh_quantization },
{ fg::extensions::KHR_texture_basisu, fg::Extensions::KHR_texture_basisu },
{ fg::extensions::KHR_texture_transform, fg::Extensions::KHR_texture_transform },
Expand Down Expand Up @@ -599,7 +600,16 @@ fg::Error fg::glTF::parse(Category categories) {
}
parsedAsset->defaultScene = static_cast<size_t>(defaultScene);
continue;
} else if (hashedKey == force_consteval<crc32("asset")> || hashedKey == force_consteval<crc32("extensions")> || hashedKey == force_consteval<crc32("extras")>) {
} else if (hashedKey == force_consteval<crc32("extensions")>) {
dom::object extensionsObject;
if (object.value.get_object().get(extensionsObject) != SUCCESS) {
errorCode = Error::InvalidGltf;
return errorCode;
}

parseExtensions(extensionsObject);
continue;
} else if (hashedKey == force_consteval<crc32("asset")> || hashedKey == force_consteval<crc32("extras")>) {
continue;
}

Expand Down Expand Up @@ -1155,6 +1165,37 @@ void fg::glTF::parseCameras(simdjson::dom::array& cameras) {
}
}

void fg::glTF::parseExtensions(simdjson::dom::object& extensionsObject) {
using namespace simdjson;

for (auto extensionValue : extensionsObject) {
dom::object extensionObject;
if (auto error = extensionValue.value.get_object().get(extensionObject); error != SUCCESS) {
if (error == INCORRECT_TYPE) {
continue; // We want to ignore
} else {
SET_ERROR_RETURN(Error::InvalidGltf)
}
}

auto hash = crc32(extensionValue.key);
switch (hash) {
case force_consteval<crc32(extensions::KHR_lights_punctual)>: {
if (!hasBit(extensions, Extensions::KHR_lights_punctual))
break;

dom::array lightsArray;
if (auto error = extensionObject["lights"].get_array().get(lightsArray); error == SUCCESS) {
parseLights(lightsArray);
} else if (error != NO_SUCH_FIELD) {
SET_ERROR_RETURN(Error::InvalidGltf)
}
break;
}
}
}
}

void fg::glTF::parseImages(simdjson::dom::array& images) {
using namespace simdjson;

Expand Down Expand Up @@ -1213,6 +1254,95 @@ void fg::glTF::parseImages(simdjson::dom::array& images) {
}
}

void fg::glTF::parseLights(simdjson::dom::array& lights) {
using namespace simdjson;

parsedAsset->lights.reserve(lights.size());
for (auto lightValue : lights) {
dom::object lightObject;
if (lightValue.get_object().get(lightObject) != SUCCESS) {
SET_ERROR_RETURN(Error::InvalidGltf);
}
Light light = {};

std::string_view type;
if (lightObject["type"].get_string().get(type) == SUCCESS) {
switch (crc32(type)) {
case force_consteval<crc32("directional")>: {
light.type = LightType::Directional;
break;
}
case force_consteval<crc32("spot")>: {
light.type = LightType::Spot;
break;
}
case force_consteval<crc32("point")>: {
light.type = LightType::Point;
break;
}
default: {
SET_ERROR_RETURN(Error::InvalidGltf)
}
}
} else {
SET_ERROR_RETURN(Error::InvalidGltf)
}

if (light.type == LightType::Spot) {
dom::object spotObject;
if (lightObject["spot"].get_object().get(spotObject) != SUCCESS) {
SET_ERROR_RETURN(Error::InvalidGltf)
}

double innerConeAngle;
if (lightObject["innerConeAngle"].get_double().get(innerConeAngle) != SUCCESS) {
SET_ERROR_RETURN(Error::InvalidGltf)
}
light.innerConeAngle = static_cast<float>(innerConeAngle);

double outerConeAngle;
if (lightObject["outerConeAngle"].get_double().get(outerConeAngle) != SUCCESS) {
SET_ERROR_RETURN(Error::InvalidGltf)
}
light.outerConeAngle = static_cast<float>(outerConeAngle);
}

dom::array colorArray;
if (lightObject["color"].get_array().get(colorArray) == SUCCESS) {
if (colorArray.size() != 3) {
SET_ERROR_RETURN(Error::InvalidGltf)
}
for (auto i = 0; i < colorArray.size(); ++i) {
double color;
if (colorArray.at(i).get_double().get(color) == SUCCESS) {
light.color[i] = static_cast<float>(color);
} else {
SET_ERROR_RETURN(Error::InvalidGltf)
}
}
}

double intensity;
if (lightObject["intensity"].get_double().get(intensity) == SUCCESS) {
light.intensity = static_cast<float>(intensity);
} else {
light.intensity = 0.0;
}

double range;
if (lightObject["range"].get_double().get(range) == SUCCESS) {
light.range = static_cast<float>(range);
}

std::string_view name;
if (lightObject["name"].get_string().get(name) == SUCCESS) {
light.name = std::string { name };
}

parsedAsset->lights.emplace_back(std::move(light));
}
}

void fg::glTF::parseMaterials(simdjson::dom::array& materials) {
using namespace simdjson;

Expand Down Expand Up @@ -1570,14 +1700,22 @@ void fg::glTF::parseNodes(simdjson::dom::array& nodes) {
node.transform = trs;
}

// name is optional.
{
std::string_view name;
if (nodeObject["name"].get_string().get(name) == SUCCESS) {
node.name = std::string { name };
dom::object extensionsObject;
if (nodeObject["extensions"].get_object().get(extensionsObject) == SUCCESS) {
dom::object lightsObject;
if (extensionsObject[extensions::KHR_lights_punctual].get_object().get(lightsObject) == SUCCESS) {
uint64_t light;
if (lightsObject["light"].get_uint64().get(light) == SUCCESS) {
node.lightsIndex = static_cast<size_t>(light);
}
}
}

std::string_view name;
if (nodeObject["name"].get_string().get(name) == SUCCESS) {
node.name = std::string { name };
}

parsedAsset->nodes.emplace_back(std::move(node));
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/fastgltf_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// fwd
namespace simdjson::dom {
class array;
class object;
class parser;
}

Expand Down Expand Up @@ -67,6 +68,9 @@ namespace fastgltf {
// See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md
EXT_meshopt_compression = 1 << 5,

// See https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md
KHR_lights_punctual = 1 << 6,

// See https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md
EXT_mesh_gpu_instancing = 1 << 7,

Expand Down Expand Up @@ -186,6 +190,7 @@ namespace fastgltf {
constexpr std::string_view EXT_mesh_gpu_instancing = "EXT_mesh_gpu_instancing";
constexpr std::string_view EXT_meshopt_compression = "EXT_meshopt_compression";
constexpr std::string_view EXT_texture_webp = "EXT_texture_webp";
constexpr std::string_view KHR_lights_punctual = "KHR_lights_punctual";
constexpr std::string_view KHR_mesh_quantization = "KHR_mesh_quantization";
constexpr std::string_view KHR_texture_basisu = "KHR_texture_basisu";
constexpr std::string_view KHR_texture_transform = "KHR_texture_transform";
Expand Down Expand Up @@ -234,7 +239,9 @@ namespace fastgltf {
void parseBuffers(simdjson::dom::array& array);
void parseBufferViews(simdjson::dom::array& array);
void parseCameras(simdjson::dom::array& array);
void parseExtensions(simdjson::dom::object& extensionsObject);
void parseImages(simdjson::dom::array& array);
void parseLights(simdjson::dom::array& array);
void parseMaterials(simdjson::dom::array& array);
void parseMeshes(simdjson::dom::array& array);
void parseNodes(simdjson::dom::array& array);
Expand Down
24 changes: 24 additions & 0 deletions src/fastgltf_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ namespace fastgltf {
Quaternion,
Exponential,
};

enum class LightType : uint8_t {
Directional,
Spot,
Point,
};
// clang-format on
#pragma endregion

Expand Down Expand Up @@ -655,6 +661,7 @@ namespace fastgltf {
std::optional<size_t> meshIndex;
std::optional<size_t> skinIndex;
std::optional<size_t> cameraIndex;
std::optional<size_t> lightsIndex;
SmallVector<size_t> children;
SmallVector<float> weights;

Expand Down Expand Up @@ -844,6 +851,22 @@ namespace fastgltf {
std::string name;
};

struct Light {
LightType type;
/** RGB light color in linear space. */
std::array<float, 3> color;

/** Point and spot lights use candela (lm/sr) while directional use lux (lm/m^2) */
float intensity;
/** Range for point and spot lights. If not present, range is infinite. */
std::optional<float> range;

std::optional<float> innerConeAngle;
std::optional<float> outerConeAngle;

std::string name;
};

struct Asset {
/**
* This will only ever have no value if Options::DontRequireValidAssetMember was specified.
Expand All @@ -856,6 +879,7 @@ namespace fastgltf {
std::vector<BufferView> bufferViews;
std::vector<Camera> cameras;
std::vector<Image> images;
std::vector<Light> lights;
std::vector<Material> materials;
std::vector<Mesh> meshes;
std::vector<Node> nodes;
Expand Down
27 changes: 27 additions & 0 deletions tests/basic_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,3 +520,30 @@ TEST_CASE("Validate morph target parsing", "[gltf-loader]") {
REQUIRE(primitive.targets[1].contains("POSITION"));
REQUIRE(primitive.targets[1]["POSITION"] == 3);
}

TEST_CASE("Test KHR_lights_punctual", "[gltf-loader]") {
auto lightsLamp = sampleModels / "2.0" / "LightsPunctualLamp" / "glTF";
fastgltf::GltfDataBuffer jsonData;
jsonData.loadFromFile(lightsLamp / "LightsPunctualLamp.gltf");

fastgltf::Parser parser(fastgltf::Extensions::KHR_lights_punctual);
auto model = parser.loadGLTF(&jsonData, lightsLamp);
REQUIRE(parser.getError() == fastgltf::Error::None);
REQUIRE(model->parse(fastgltf::Category::All) == fastgltf::Error::None);

auto asset = model->getParsedAsset();
REQUIRE(asset->lights.size() == 5);
REQUIRE(asset->nodes.size() > 4);

auto& nodes = asset->nodes;
REQUIRE(nodes[3].lightsIndex.has_value());
REQUIRE(nodes[3].lightsIndex.value() == 0);

auto& lights = asset->lights;
REQUIRE(lights[0].name == "Point");
REQUIRE(lights[0].type == fastgltf::LightType::Point);
REQUIRE(lights[0].intensity == 15.0f);
REQUIRE(glm::epsilonEqual(lights[0].color[0], 1.0f, glm::epsilon<float>()));
REQUIRE(glm::epsilonEqual(lights[0].color[1], 0.63187497854232788f, glm::epsilon<float>()));
REQUIRE(glm::epsilonEqual(lights[0].color[2], 0.23909975588321689f, glm::epsilon<float>()));
}

0 comments on commit 7c7a81b

Please sign in to comment.