Skip to content

Commit c9e43be

Browse files
authored
Merge pull request #41 from adobe/v1.0.10
v1.0.10
2 parents cb0e91b + 7a8bd9d commit c9e43be

22 files changed

+423
-192
lines changed

changelog.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
v1.0.10 November 19th, 2024
2+
fbx:
3+
- small fbx spot light fixes
4+
- missing file warning
5+
- file names added to metadata now avoid dupes
6+
gltf:
7+
- ensure consistent light with usd
8+
stl:
9+
- fix up axis rotation, default is assumed to be z-up
10+
utility:
11+
- more robust handling of the no texture coordinate warning
12+
- add mesh name in generated sub mesh
13+
114
v1.0.9 October 29th, 2024
215
fbx:
316
- import dependent filenames now added to metadata

fbx/src/fbx.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ namespace adobe::usd {
3737
// lighting doesn't match
3838
constexpr float FBX_TO_USD_INTENSITY_SCALE_FACTOR = 1.0;
3939

40+
constexpr float DEFAULT_POINT_LIGHT_RADIUS = 0.01; // 1 cm
41+
constexpr float DEFAULT_SPOT_LIGHT_RADIUS = 0.1; // 10 cm
42+
4043
// Camera rotation to apply to revert to FBX coordinates, on export. Inspired by the Blender code
4144
// base, which converts from -Z to +X with a 90º rotation around the Y axis:
4245
// https://github.com/blender/blender/blob/e1a44ad129d53fbd47215845be2c42fb0850135d/scripts/addons_core/io_scene_fbx/fbx_utils.py#L74C64-L74C88

fbx/src/fbxExport.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -765,8 +765,17 @@ exportFbxLights(ExportFbxContext& ctx)
765765
type = "spot (from USD disk light)";
766766
lightType = FbxLight::EType::eSpot;
767767

768-
innerAngle = light.coneAngle;
769-
outerAngle = light.coneFalloff;
768+
// FBX inner cone angle is from the center to where falloff begins, and outer cone
769+
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
770+
// the center to the edge of the cone, and softness is a number from 0 to 1
771+
// indicating how close to the center the falloff begins.
772+
773+
// USD's cone angle is the entire shape of the spot light, corresponding to FBX's
774+
// outer angle
775+
outerAngle = light.coneAngle;
776+
777+
// Use the fraction of the cone containing the falloff to calculate the inner cone
778+
innerAngle = (1 - light.coneFalloff) * outerAngle;
770779

771780
break;
772781
case LightType::Rectangle:

fbx/src/fbxImport.cpp

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ struct ImportFbxContext
7979

8080
// Each ImportedFbxStack has a cache of all anim layers present in that animation stack
8181
std::vector<ImportedFbxStack> animationStacks;
82-
83-
// paths to files loaded on import
84-
PXR_NS::VtArray<std::string> filenames;
8582
};
8683

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

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

11381135
std::filesystem::path absFilePath;
11391136
if (isAbsolutePathFromAnyOS(filePathNormalized)) {
@@ -1183,7 +1180,7 @@ importFbxMaterials(ImportFbxContext& ctx)
11831180
} else {
11841181
std::ifstream file(absFileName, std::ios::binary);
11851182
if (!file.is_open()) {
1186-
TF_RUNTIME_ERROR("Failed to open file \"%s\"", absFileName.c_str());
1183+
TF_WARN("Failed to open file \"%s\"", absFileName.c_str());
11871184
continue;
11881185
}
11891186
file.seekg(0, file.end);
@@ -1401,11 +1398,14 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)
14011398
LightType usdType;
14021399
float coneAngle = 0;
14031400
float coneFalloff = 0;
1401+
float radius = 0.5;
14041402
switch (fbxLight->LightType.Get()) {
14051403
case FbxLight::ePoint:
14061404
type = "sphere (from FBX point light)";
14071405
usdType = LightType::Sphere;
14081406

1407+
radius = DEFAULT_POINT_LIGHT_RADIUS;
1408+
14091409
break;
14101410
case FbxLight::eDirectional:
14111411
type = "sun (from FBX directional light)";
@@ -1416,12 +1416,21 @@ importFbxLight(ImportFbxContext& ctx, FbxNodeAttribute* attribute, int parent)
14161416
type = "disk (from FBX spot light)";
14171417
usdType = LightType::Disk;
14181418

1419-
// According to FBX specs, inner angle is "HotSpot". In USD, this translates to the
1420-
// USDLuxShapingAPI ConeAngleAttribute
1421-
coneAngle = fbxLight->InnerAngle.Get();
1422-
// According to FBX specs, outer angle is falloff. In USD, this translates to the
1423-
// USDLuxShapingAPI ConeSoftnessAttribute
1424-
coneFalloff = fbxLight->OuterAngle.Get();
1419+
radius = DEFAULT_SPOT_LIGHT_RADIUS;
1420+
1421+
// FBX inner cone angle is from the center to where falloff begins, and outer cone
1422+
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
1423+
// the center to the edge of the cone, and softness is a number from 0 to 1 indicating
1424+
// how close to the center the falloff begins.
1425+
1426+
// USD's cone angle is the entire shape of the spot light, corresponding to FBX's
1427+
// outer angle
1428+
coneAngle = fbxLight->OuterAngle.Get();
1429+
1430+
// Get the fraction of the cone containing the falloff
1431+
if (fbxLight->OuterAngle.Get()) {
1432+
coneFalloff = 1 - (fbxLight->InnerAngle.Get() / fbxLight->OuterAngle.Get());
1433+
}
14251434

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

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

14841495
return true;
14851496
}
@@ -2133,6 +2144,12 @@ importFbx(const ImportFbxOptions& options, Fbx& fbx, UsdData& usd)
21332144
ctx.scene = fbx.scene;
21342145
ctx.originalColorSpace = options.originalColorSpace;
21352146

2147+
// Include the FBX file name itself in the filenames we add to the metadata
2148+
{
2149+
std::string baseName = TfGetBaseName(fbx.filename);
2150+
usd.importedFileNames.emplace(std::move(baseName));
2151+
}
2152+
21362153
importMetadata(ctx);
21372154
importFbxSettings(ctx);
21382155

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

2152-
if (!ctx.filenames.empty()) {
2153-
usd.metadata.SetValueAtPath("filenames", VtValue(ctx.filenames));
2154-
}
2155-
21562169
return true;
21572170
}
21582171
}

gltf/src/gltf.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ governing permissions and limitations under the License.
1515

1616
namespace adobe::usd {
1717

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

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

2427
// max color value of a pixel
25-
const float MAX_COLOR_VALUE = 255.0f;
28+
constexpr float MAX_COLOR_VALUE = 255.0f;
2629

2730
struct WriteGltfOptions
2831
{

gltf/src/gltfExport.cpp

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,43 +224,95 @@ exportLightExtension(ExportGltfContext& ctx, int lightIndex, ExtMap& extensions)
224224
bool
225225
exportLights(ExportGltfContext& ctx)
226226
{
227-
228227
ctx.gltf->lights.resize(ctx.usd->lights.size());
229228
for (size_t i = 0; i < ctx.usd->lights.size(); ++i) {
230229
const Light& light = ctx.usd->lights[i];
231230
tinygltf::Light& gltfLight = ctx.gltf->lights[i];
232231

232+
float radius = light.radius;
233+
GfVec2f length = light.length;
234+
235+
// Modify light values if the incoming USD values are in different units
236+
if (ctx.usd->metersPerUnit > 0) {
237+
if (radius > 0) {
238+
radius *= ctx.usd->metersPerUnit;
239+
}
240+
if (length[0] > 0) {
241+
length[0] *= ctx.usd->metersPerUnit;
242+
}
243+
if (length[1] > 0) {
244+
length[1] *= ctx.usd->metersPerUnit;
245+
}
246+
}
247+
248+
// glTF doesn't use lights that emit based on their surface area, so will multiply the
249+
// intensity below based on the light type
250+
float intensity = light.intensity;
251+
233252
switch (light.type) {
234253
case LightType::Disk: {
235254
gltfLight.type = "spot";
236255

237-
// Only spot lights have innerConeAngle and outerConeAngle. We must make a separate
238-
// "spot" attribute with this information
239-
gltfLight.spot.innerConeAngle = GfDegreesToRadians(light.coneAngle);
240-
gltfLight.spot.outerConeAngle = GfDegreesToRadians(light.coneFalloff);
256+
// glTF inner cone angle is from the center to where falloff begins, and outer cone
257+
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
258+
// the center to the edge of the cone, and softness is a number from 0 to 1
259+
// indicating how close to the center the falloff begins.
260+
261+
// glTF outer cone angle is equivalent to USD cone angle
262+
gltfLight.spot.outerConeAngle = GfDegreesToRadians(light.coneAngle);
263+
264+
// Use the fraction of the cone containing the falloff to calculate the inner cone
265+
gltfLight.spot.innerConeAngle =
266+
(1 - ctx.usd->lights[i].coneFalloff) * gltfLight.spot.outerConeAngle;
267+
268+
// inner cone angle must always be less than outer cone angle, according to the
269+
// glTF spec. If it isn't, set it to be just less than the outer cone angle
270+
const float epsilon = 1e-6;
271+
if (gltfLight.spot.innerConeAngle >= gltfLight.spot.outerConeAngle &&
272+
gltfLight.spot.outerConeAngle >= epsilon) {
273+
gltfLight.spot.innerConeAngle = gltfLight.spot.outerConeAngle - epsilon;
274+
}
275+
276+
if (radius > 0) { // Disk light, area = pi r^2
277+
intensity *= (M_PI * radius * radius);
278+
}
279+
280+
intensity *= GLTF_SPOT_LIGHT_INTENSITY_MULT;
241281

242-
} break;
282+
break;
283+
}
243284
case LightType::Sun:
244285
gltfLight.type = "directional";
245286

287+
intensity *= GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT;
288+
246289
break;
247290
default:
248291
// All other light types are encoded as point lights, since gltf supports fewer
249292
// light types
250293
gltfLight.type = "point";
251294

295+
if (radius > 0) { // Sphere light, area = 4 pi r^2
296+
intensity *= (4.0 * M_PI * radius * radius);
297+
} else if (length[0] > 0 && length[1] > 0) { // Rectangle light, area = l * w
298+
intensity *= (length[0] * length[1]);
299+
}
300+
301+
intensity *= GLTF_POINT_LIGHT_INTENSITY_MULT;
302+
303+
// TODO: Address environment lights separately
304+
252305
break;
253306
}
254307

255308
gltfLight.name = light.name;
256309

310+
gltfLight.intensity = intensity;
311+
257312
gltfLight.color.resize(3);
258313
gltfLight.color[0] = light.color[0];
259314
gltfLight.color[1] = light.color[1];
260315
gltfLight.color[2] = light.color[2];
261-
262-
// Divide by the scale factor to convert from USD to GLTF
263-
gltfLight.intensity = light.intensity / GLTF_TO_USD_INTENSITY_SCALE_FACTOR;
264316
}
265317
return true;
266318
}

gltf/src/gltfImport.cpp

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,25 +1913,56 @@ importLights(ImportGltfContext& ctx)
19131913
light.color[1] = gltfLight.color[1];
19141914
light.color[2] = gltfLight.color[2];
19151915
}
1916-
light.intensity = gltfLight.intensity * GLTF_TO_USD_INTENSITY_SCALE_FACTOR;
19171916

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

19211921
// Add type-specific light info
19221922

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

1926+
intensity /= GLTF_DIRECTIONAL_LIGHT_INTENSITY_MULT;
1927+
19261928
} else if (gltfLight.type == "point") {
19271929
light.type = LightType::Sphere;
19281930

1931+
// Divide by the surface area of a sphere, 4 pi r^2
1932+
intensity /= (4.0 * M_PI * DEFAULT_POINT_LIGHT_RADIUS * DEFAULT_POINT_LIGHT_RADIUS);
1933+
intensity /= GLTF_POINT_LIGHT_INTENSITY_MULT;
1934+
1935+
// glTF lights have no radius, so we use a default value
1936+
light.radius = DEFAULT_POINT_LIGHT_RADIUS;
1937+
19291938
} else if (gltfLight.type == "spot") {
19301939
light.type = LightType::Disk;
19311940

1932-
ctx.usd->lights[i].coneAngle = GfRadiansToDegrees(gltfLight.spot.innerConeAngle);
1933-
ctx.usd->lights[i].coneFalloff = GfRadiansToDegrees(gltfLight.spot.outerConeAngle);
1941+
// Divide by the area of a disk, pi r^2
1942+
intensity /= (M_PI * DEFAULT_SPOT_LIGHT_RADIUS * DEFAULT_SPOT_LIGHT_RADIUS);
1943+
intensity /= GLTF_SPOT_LIGHT_INTENSITY_MULT;
1944+
1945+
// glTF lights have no radius, so we use a default value
1946+
light.radius = DEFAULT_SPOT_LIGHT_RADIUS;
1947+
1948+
// glTF inner cone angle is from the center to where falloff begins, and outer cone
1949+
// angle is from the center to where falloff ends. Meanwhile, in USD, angle is from
1950+
// the center to the edge of the cone, and softness is a number from 0 to 1 indicating
1951+
// how close to the center the falloff begins.
1952+
1953+
// glTF outer cone angle is equivalent to USD cone angle
1954+
ctx.usd->lights[i].coneAngle = GfRadiansToDegrees(gltfLight.spot.outerConeAngle);
1955+
1956+
if (gltfLight.spot.outerConeAngle > 0) {
1957+
// Get the fraction of the cone containing the falloff
1958+
ctx.usd->lights[i].coneFalloff =
1959+
1 - (gltfLight.spot.innerConeAngle / gltfLight.spot.outerConeAngle);
1960+
} else {
1961+
ctx.usd->lights[i].coneFalloff = 0;
1962+
}
19341963
}
1964+
1965+
ctx.usd->lights[i].intensity = intensity;
19351966
}
19361967
}
19371968

obj/src/obj.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,7 @@ addImage(Obj& obj,
10651065
image.uri = basename;
10661066
image.name = TfStringGetBeforeSuffix(basename);
10671067
image.format = getFormat(extension);
1068-
obj.filenames.push_back(filename);
1068+
obj.importedFilenames.insert(filename);
10691069
if (readImages) {
10701070
std::string fullFilename = parentPath + filename;
10711071
if (!readFileContents(fullFilename,
@@ -1516,7 +1516,7 @@ readObj(Obj& obj, const std::string& filename, bool readImages)
15161516
TfStopwatch watch;
15171517
watch.Start();
15181518
std::string baseName = TfGetBaseName(filename);
1519-
obj.filenames.push_back(baseName);
1519+
obj.importedFilenames.insert(baseName);
15201520
std::vector<char> objBuffer;
15211521
GUARD(readFileContents(filename, objBuffer), "Failed reading obj file");
15221522
watch.Stop();
@@ -1530,7 +1530,7 @@ readObj(Obj& obj, const std::string& filename, bool readImages)
15301530
const std::string parentPath = TfGetPathName(filename);
15311531
for (size_t i = 0; i < obj.libraries.size(); i++) {
15321532
ObjMaterialLibrary& library = obj.libraries[i];
1533-
obj.filenames.push_back(library.filename);
1533+
obj.importedFilenames.insert(library.filename);
15341534
std::string materialFilename = parentPath + library.filename;
15351535
std::vector<char> materialBuffer;
15361536
if (!readFileContents(materialFilename, materialBuffer)) {
@@ -1631,12 +1631,11 @@ writeObjHeader(const Obj& obj, std::fstream& file)
16311631

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

1639-
16401639
// Writes obj geometry to the stream `file` in a buffered way.
16411640
/// See `BufferControl`.
16421641
void

obj/src/obj.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ struct ObjObject
181181
struct Obj
182182
{
183183
bool hasAdobeProperties = false;
184-
PXR_NS::VtArray<std::string> filenames;
184+
std::set<std::string> importedFilenames;
185185
std::vector<ObjObject> objects;
186186
std::vector<ObjMaterial> materials;
187187
std::vector<ImageAsset> images;

0 commit comments

Comments
 (0)