Skip to content

Commit

Permalink
Merge pull request #41 from adobe/v1.0.10
Browse files Browse the repository at this point in the history
v1.0.10
  • Loading branch information
jakes-adobe authored Nov 19, 2024
2 parents cb0e91b + 7a8bd9d commit c9e43be
Show file tree
Hide file tree
Showing 22 changed files with 423 additions and 192 deletions.
13 changes: 13 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
v1.0.10 November 19th, 2024
fbx:
- small fbx spot light fixes
- missing file warning
- file names added to metadata now avoid dupes
gltf:
- ensure consistent light with usd
stl:
- fix up axis rotation, default is assumed to be z-up
utility:
- more robust handling of the no texture coordinate warning
- add mesh name in generated sub mesh

v1.0.9 October 29th, 2024
fbx:
- import dependent filenames now added to metadata
Expand Down
3 changes: 3 additions & 0 deletions fbx/src/fbx.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ namespace adobe::usd {
// lighting doesn't match
constexpr float FBX_TO_USD_INTENSITY_SCALE_FACTOR = 1.0;

constexpr float DEFAULT_POINT_LIGHT_RADIUS = 0.01; // 1 cm
constexpr float DEFAULT_SPOT_LIGHT_RADIUS = 0.1; // 10 cm

// Camera rotation to apply to revert to FBX coordinates, on export. Inspired by the Blender code
// base, which converts from -Z to +X with a 90º rotation around the Y axis:
// https://github.com/blender/blender/blob/e1a44ad129d53fbd47215845be2c42fb0850135d/scripts/addons_core/io_scene_fbx/fbx_utils.py#L74C64-L74C88
Expand Down
13 changes: 11 additions & 2 deletions fbx/src/fbxExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,17 @@ exportFbxLights(ExportFbxContext& ctx)
type = "spot (from USD disk light)";
lightType = FbxLight::EType::eSpot;

innerAngle = light.coneAngle;
outerAngle = light.coneFalloff;
// FBX inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1
// indicating how close to the center the falloff begins.

// USD's cone angle is the entire shape of the spot light, corresponding to FBX's
// outer angle
outerAngle = light.coneAngle;

// Use the fraction of the cone containing the falloff to calculate the inner cone
innerAngle = (1 - light.coneFalloff) * outerAngle;

break;
case LightType::Rectangle:
Expand Down
47 changes: 30 additions & 17 deletions fbx/src/fbxImport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,6 @@ struct ImportFbxContext

// Each ImportedFbxStack has a cache of all anim layers present in that animation stack
std::vector<ImportedFbxStack> animationStacks;

// paths to files loaded on import
PXR_NS::VtArray<std::string> filenames;
};

// Metadata on USD will be stored uniformily in the CustomLayerData dictionary.
Expand Down Expand Up @@ -1133,7 +1130,7 @@ importFbxMaterials(ImportFbxContext& ctx)
normalizePathFromAnyOS(fileTexture->GetRelativeFileName());

// Add the path to the metadata even if the file is not present on disk.
ctx.filenames.push_back(filePathNormalized.u8string());
ctx.usd->importedFileNames.insert(filePathNormalized.u8string());

std::filesystem::path absFilePath;
if (isAbsolutePathFromAnyOS(filePathNormalized)) {
Expand Down Expand Up @@ -1183,7 +1180,7 @@ importFbxMaterials(ImportFbxContext& ctx)
} else {
std::ifstream file(absFileName, std::ios::binary);
if (!file.is_open()) {
TF_RUNTIME_ERROR("Failed to open file \"%s\"", absFileName.c_str());
TF_WARN("Failed to open file \"%s\"", absFileName.c_str());
continue;
}
file.seekg(0, file.end);
Expand Down Expand Up @@ -1401,11 +1398,14 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)
LightType usdType;
float coneAngle = 0;
float coneFalloff = 0;
float radius = 0.5;
switch (fbxLight->LightType.Get()) {
case FbxLight::ePoint:
type = "sphere (from FBX point light)";
usdType = LightType::Sphere;

radius = DEFAULT_POINT_LIGHT_RADIUS;

break;
case FbxLight::eDirectional:
type = "sun (from FBX directional light)";
Expand All @@ -1416,12 +1416,21 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)
type = "disk (from FBX spot light)";
usdType = LightType::Disk;

// According to FBX specs, inner angle is "HotSpot". In USD, this translates to the
// USDLuxShapingAPI ConeAngleAttribute
coneAngle = fbxLight->InnerAngle.Get();
// According to FBX specs, outer angle is falloff. In USD, this translates to the
// USDLuxShapingAPI ConeSoftnessAttribute
coneFalloff = fbxLight->OuterAngle.Get();
radius = DEFAULT_SPOT_LIGHT_RADIUS;

// FBX inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1 indicating
// how close to the center the falloff begins.

// USD's cone angle is the entire shape of the spot light, corresponding to FBX's
// outer angle
coneAngle = fbxLight->OuterAngle.Get();

// Get the fraction of the cone containing the falloff
if (fbxLight->OuterAngle.Get()) {
coneFalloff = 1 - (fbxLight->InnerAngle.Get() / fbxLight->OuterAngle.Get());
}

break;
case FbxLight::eArea:
Expand Down Expand Up @@ -1478,8 +1487,10 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)

// TODO: Extract FBX light radius and replace this temporary dummy value with it. When this is
// updated, please update corresponding unit tests as well
light.radius = 0.5;
TF_WARN("importFbxLight: ignoring FBX light radius, setting radius=0.5\n");
light.radius = radius;
TF_WARN("importFbxLight: ignoring FBX light radius for light of type %s, setting radius=%f\n",
type.c_str(),
radius);

return true;
}
Expand Down Expand Up @@ -2133,6 +2144,12 @@ importFbx(const ImportFbxOptions& options, Fbx& fbx, UsdData& usd)
ctx.scene = fbx.scene;
ctx.originalColorSpace = options.originalColorSpace;

// Include the FBX file name itself in the filenames we add to the metadata
{
std::string baseName = TfGetBaseName(fbx.filename);
usd.importedFileNames.emplace(std::move(baseName));
}

importMetadata(ctx);
importFbxSettings(ctx);

Expand All @@ -2149,10 +2166,6 @@ importFbx(const ImportFbxOptions& options, Fbx& fbx, UsdData& usd)
setSkeletonParents(ctx);
}

if (!ctx.filenames.empty()) {
usd.metadata.SetValueAtPath("filenames", VtValue(ctx.filenames));
}

return true;
}
}
13 changes: 8 additions & 5 deletions gltf/src/gltf.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ governing permissions and limitations under the License.

namespace adobe::usd {

// Scale between intensity of USD lights and GLTF lights
const float GLTF_TO_USD_INTENSITY_SCALE_FACTOR = 100.0;
// Experimentally found to result in similar brightnesses between glTF and USD
constexpr float GLTF_POINT_LIGHT_INTENSITY_MULT = 0.225;
constexpr float GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT = 0.0000625;
constexpr float GLTF_SPOT_LIGHT_INTENSITY_MULT = 1.0;

// lights are by default given a diameter of 1, since there is no concept of light radius in glTF
const float DEFAULT_LIGHT_RADIUS = 0.5;
// Note there is no concept of a light radius in glTF
constexpr float DEFAULT_POINT_LIGHT_RADIUS = 0.01; // 1 cm
constexpr float DEFAULT_SPOT_LIGHT_RADIUS = 0.1; // 10 cm

// max color value of a pixel
const float MAX_COLOR_VALUE = 255.0f;
constexpr float MAX_COLOR_VALUE = 255.0f;

struct WriteGltfOptions
{
Expand Down
70 changes: 61 additions & 9 deletions gltf/src/gltfExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,43 +224,95 @@ exportLightExtension(ExportGltfContext& ctx, int lightIndex, ExtMap& extensions)
bool
exportLights(ExportGltfContext& ctx)
{

ctx.gltf->lights.resize(ctx.usd->lights.size());
for (size_t i = 0; i < ctx.usd->lights.size(); ++i) {
const Light& light = ctx.usd->lights[i];
tinygltf::Light& gltfLight = ctx.gltf->lights[i];

float radius = light.radius;
GfVec2f length = light.length;

// Modify light values if the incoming USD values are in different units
if (ctx.usd->metersPerUnit > 0) {
if (radius > 0) {
radius *= ctx.usd->metersPerUnit;
}
if (length[0] > 0) {
length[0] *= ctx.usd->metersPerUnit;
}
if (length[1] > 0) {
length[1] *= ctx.usd->metersPerUnit;
}
}

// glTF doesn't use lights that emit based on their surface area, so will multiply the
// intensity below based on the light type
float intensity = light.intensity;

switch (light.type) {
case LightType::Disk: {
gltfLight.type = "spot";

// Only spot lights have innerConeAngle and outerConeAngle. We must make a separate
// "spot" attribute with this information
gltfLight.spot.innerConeAngle = GfDegreesToRadians(light.coneAngle);
gltfLight.spot.outerConeAngle = GfDegreesToRadians(light.coneFalloff);
// glTF inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1
// indicating how close to the center the falloff begins.

// glTF outer cone angle is equivalent to USD cone angle
gltfLight.spot.outerConeAngle = GfDegreesToRadians(light.coneAngle);

// Use the fraction of the cone containing the falloff to calculate the inner cone
gltfLight.spot.innerConeAngle =
(1 - ctx.usd->lights[i].coneFalloff) * gltfLight.spot.outerConeAngle;

// inner cone angle must always be less than outer cone angle, according to the
// glTF spec. If it isn't, set it to be just less than the outer cone angle
const float epsilon = 1e-6;
if (gltfLight.spot.innerConeAngle >= gltfLight.spot.outerConeAngle &&
gltfLight.spot.outerConeAngle >= epsilon) {
gltfLight.spot.innerConeAngle = gltfLight.spot.outerConeAngle - epsilon;
}

if (radius > 0) { // Disk light, area = pi r^2
intensity *= (M_PI * radius * radius);
}

intensity *= GLTF_SPOT_LIGHT_INTENSITY_MULT;

} break;
break;
}
case LightType::Sun:
gltfLight.type = "directional";

intensity *= GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT;

break;
default:
// All other light types are encoded as point lights, since gltf supports fewer
// light types
gltfLight.type = "point";

if (radius > 0) { // Sphere light, area = 4 pi r^2
intensity *= (4.0 * M_PI * radius * radius);
} else if (length[0] > 0 && length[1] > 0) { // Rectangle light, area = l * w
intensity *= (length[0] * length[1]);
}

intensity *= GLTF_POINT_LIGHT_INTENSITY_MULT;

// TODO: Address environment lights separately

break;
}

gltfLight.name = light.name;

gltfLight.intensity = intensity;

gltfLight.color.resize(3);
gltfLight.color[0] = light.color[0];
gltfLight.color[1] = light.color[1];
gltfLight.color[2] = light.color[2];

// Divide by the scale factor to convert from USD to GLTF
gltfLight.intensity = light.intensity / GLTF_TO_USD_INTENSITY_SCALE_FACTOR;
}
return true;
}
Expand Down
41 changes: 36 additions & 5 deletions gltf/src/gltfImport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1913,25 +1913,56 @@ importLights(ImportGltfContext& ctx)
light.color[1] = gltfLight.color[1];
light.color[2] = gltfLight.color[2];
}
light.intensity = gltfLight.intensity * GLTF_TO_USD_INTENSITY_SCALE_FACTOR;

// GLTF lights have no radius, so we use a default value
light.radius = DEFAULT_LIGHT_RADIUS;
// USD uses lights that emit based on their surface area, so will multiply the intensity
// below based on the light type
float intensity = gltfLight.intensity;

// Add type-specific light info

if (gltfLight.type == "directional") {
light.type = LightType::Sun;

intensity /= GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT;

} else if (gltfLight.type == "point") {
light.type = LightType::Sphere;

// Divide by the surface area of a sphere, 4 pi r^2
intensity /= (4.0 * M_PI * DEFAULT_POINT_LIGHT_RADIUS * DEFAULT_POINT_LIGHT_RADIUS);
intensity /= GLTF_POINT_LIGHT_INTENSITY_MULT;

// glTF lights have no radius, so we use a default value
light.radius = DEFAULT_POINT_LIGHT_RADIUS;

} else if (gltfLight.type == "spot") {
light.type = LightType::Disk;

ctx.usd->lights[i].coneAngle = GfRadiansToDegrees(gltfLight.spot.innerConeAngle);
ctx.usd->lights[i].coneFalloff = GfRadiansToDegrees(gltfLight.spot.outerConeAngle);
// Divide by the area of a disk, pi r^2
intensity /= (M_PI * DEFAULT_SPOT_LIGHT_RADIUS * DEFAULT_SPOT_LIGHT_RADIUS);
intensity /= GLTF_SPOT_LIGHT_INTENSITY_MULT;

// glTF lights have no radius, so we use a default value
light.radius = DEFAULT_SPOT_LIGHT_RADIUS;

// glTF inner cone angle is from the center to where falloff begins, and outer cone
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
// the center to the edge of the cone, and softness is a number from 0 to 1 indicating
// how close to the center the falloff begins.

// glTF outer cone angle is equivalent to USD cone angle
ctx.usd->lights[i].coneAngle = GfRadiansToDegrees(gltfLight.spot.outerConeAngle);

if (gltfLight.spot.outerConeAngle > 0) {
// Get the fraction of the cone containing the falloff
ctx.usd->lights[i].coneFalloff =
1 - (gltfLight.spot.innerConeAngle / gltfLight.spot.outerConeAngle);
} else {
ctx.usd->lights[i].coneFalloff = 0;
}
}

ctx.usd->lights[i].intensity = intensity;
}
}

Expand Down
9 changes: 4 additions & 5 deletions obj/src/obj.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,7 @@ addImage(Obj& obj,
image.uri = basename;
image.name = TfStringGetBeforeSuffix(basename);
image.format = getFormat(extension);
obj.filenames.push_back(filename);
obj.importedFilenames.insert(filename);
if (readImages) {
std::string fullFilename = parentPath + filename;
if (!readFileContents(fullFilename,
Expand Down Expand Up @@ -1516,7 +1516,7 @@ readObj(Obj& obj, const std::string& filename, bool readImages)
TfStopwatch watch;
watch.Start();
std::string baseName = TfGetBaseName(filename);
obj.filenames.push_back(baseName);
obj.importedFilenames.insert(baseName);
std::vector<char> objBuffer;
GUARD(readFileContents(filename, objBuffer), "Failed reading obj file");
watch.Stop();
Expand All @@ -1530,7 +1530,7 @@ readObj(Obj& obj, const std::string& filename, bool readImages)
const std::string parentPath = TfGetPathName(filename);
for (size_t i = 0; i < obj.libraries.size(); i++) {
ObjMaterialLibrary& library = obj.libraries[i];
obj.filenames.push_back(library.filename);
obj.importedFilenames.insert(library.filename);
std::string materialFilename = parentPath + library.filename;
std::vector<char> materialBuffer;
if (!readFileContents(materialFilename, materialBuffer)) {
Expand Down Expand Up @@ -1631,12 +1631,11 @@ writeObjHeader(const Obj& obj, std::fstream& file)

buffer.directWrite("# Obj model");
buffer.directWrite("\n# This model was generated by the USD fileformat plugin");
for (const auto& comment: obj.comments)
for (const auto& comment : obj.comments)
buffer.directWrite(std::string("\n") + comment);
buffer.flush();
}


// Writes obj geometry to the stream `file` in a buffered way.
/// See `BufferControl`.
void
Expand Down
2 changes: 1 addition & 1 deletion obj/src/obj.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ struct ObjObject
struct Obj
{
bool hasAdobeProperties = false;
PXR_NS::VtArray<std::string> filenames;
std::set<std::string> importedFilenames;
std::vector<ObjObject> objects;
std::vector<ObjMaterial> materials;
std::vector<ImageAsset> images;
Expand Down
Loading

0 comments on commit c9e43be

Please sign in to comment.