|
|
@@ -11,6 +11,8 @@ import {
|
|
|
MirroredRepeatWrapping,
|
|
|
NoColorSpace,
|
|
|
Object3D,
|
|
|
+ OrthographicCamera,
|
|
|
+ PerspectiveCamera,
|
|
|
Quaternion,
|
|
|
QuaternionKeyframeTrack,
|
|
|
RepeatWrapping,
|
|
|
@@ -44,6 +46,19 @@ const SpecType = {
|
|
|
VariantSet: 11
|
|
|
};
|
|
|
|
|
|
+// UsdGeomCamera fallback values (OpenUSD schema)
|
|
|
+const USD_CAMERA_DEFAULTS = {
|
|
|
+ projection: 'perspective',
|
|
|
+ clippingRange: [ 1, 1000000 ],
|
|
|
+ horizontalAperture: 20.955,
|
|
|
+ verticalAperture: 15.2908,
|
|
|
+ horizontalApertureOffset: 0,
|
|
|
+ verticalApertureOffset: 0,
|
|
|
+ focalLength: 50,
|
|
|
+ focusDistance: 0,
|
|
|
+ fStop: 0
|
|
|
+};
|
|
|
+
|
|
|
/**
|
|
|
* USDComposer handles scene composition from parsed USD data.
|
|
|
* This includes reference resolution, variant selection, transform handling,
|
|
|
@@ -657,6 +672,15 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ } else if ( typeName === 'Camera' ) {
|
|
|
+
|
|
|
+ const obj = this._buildCamera( path );
|
|
|
+ obj.name = name;
|
|
|
+ const attrs = this._getAttributes( path );
|
|
|
+ this.applyTransform( obj, spec.fields, attrs );
|
|
|
+ parent.add( obj );
|
|
|
+ this._buildHierarchy( obj, path );
|
|
|
+
|
|
|
} else if ( typeName === 'Material' || typeName === 'Shader' || typeName === 'GeomSubset' ) {
|
|
|
|
|
|
// Skip materials/shaders/subsets, they're referenced by meshes
|
|
|
@@ -1191,6 +1215,99 @@ class USDComposer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Build a camera from a Camera spec.
|
|
|
+ */
|
|
|
+ _buildCamera( path ) {
|
|
|
+
|
|
|
+ const attrs = this._getAttributes( path );
|
|
|
+ const projectionToken = attrs[ 'projection' ];
|
|
|
+ const projection = typeof projectionToken === 'string'
|
|
|
+ ? projectionToken.toLowerCase()
|
|
|
+ : USD_CAMERA_DEFAULTS.projection;
|
|
|
+ const clippingRange = attrs[ 'clippingRange' ] || USD_CAMERA_DEFAULTS.clippingRange;
|
|
|
+ const near = Math.max(
|
|
|
+ Number.EPSILON,
|
|
|
+ this._parseNumber( clippingRange[ 0 ], USD_CAMERA_DEFAULTS.clippingRange[ 0 ] )
|
|
|
+ );
|
|
|
+ const far = Math.max(
|
|
|
+ near + Number.EPSILON,
|
|
|
+ this._parseNumber( clippingRange[ 1 ], USD_CAMERA_DEFAULTS.clippingRange[ 1 ] )
|
|
|
+ );
|
|
|
+ const horizontalAperture = this._parseNumber(
|
|
|
+ attrs[ 'horizontalAperture' ],
|
|
|
+ USD_CAMERA_DEFAULTS.horizontalAperture
|
|
|
+ );
|
|
|
+ const verticalAperture = this._parseNumber(
|
|
|
+ attrs[ 'verticalAperture' ],
|
|
|
+ USD_CAMERA_DEFAULTS.verticalAperture
|
|
|
+ );
|
|
|
+ const horizontalApertureOffset = this._parseNumber(
|
|
|
+ attrs[ 'horizontalApertureOffset' ],
|
|
|
+ USD_CAMERA_DEFAULTS.horizontalApertureOffset
|
|
|
+ );
|
|
|
+ const verticalApertureOffset = this._parseNumber(
|
|
|
+ attrs[ 'verticalApertureOffset' ],
|
|
|
+ USD_CAMERA_DEFAULTS.verticalApertureOffset
|
|
|
+ );
|
|
|
+ const focalLength = this._parseNumber( attrs[ 'focalLength' ], USD_CAMERA_DEFAULTS.focalLength );
|
|
|
+ const focusDistance = this._parseNumber( attrs[ 'focusDistance' ], USD_CAMERA_DEFAULTS.focusDistance );
|
|
|
+ const fStop = this._parseNumber( attrs[ 'fStop' ], USD_CAMERA_DEFAULTS.fStop );
|
|
|
+
|
|
|
+ let camera;
|
|
|
+
|
|
|
+ if ( projection === 'orthographic' ) {
|
|
|
+
|
|
|
+ // USD orthographic apertures are in tenths of a world unit.
|
|
|
+ const width = horizontalAperture / 10;
|
|
|
+ const height = verticalAperture / 10;
|
|
|
+ const offsetX = horizontalApertureOffset / 10;
|
|
|
+ const offsetY = verticalApertureOffset / 10;
|
|
|
+
|
|
|
+ camera = new OrthographicCamera(
|
|
|
+ offsetX - width * 0.5,
|
|
|
+ offsetX + width * 0.5,
|
|
|
+ offsetY + height * 0.5,
|
|
|
+ offsetY - height * 0.5,
|
|
|
+ near,
|
|
|
+ far
|
|
|
+ );
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ const safeVerticalAperture = Math.max( Number.EPSILON, verticalAperture );
|
|
|
+ const safeFocalLength = Math.max( Number.EPSILON, focalLength );
|
|
|
+ const aspect = horizontalAperture / safeVerticalAperture;
|
|
|
+ const fov = 2 * Math.atan( safeVerticalAperture / ( 2 * safeFocalLength ) ) * 180 / Math.PI;
|
|
|
+
|
|
|
+ camera = new PerspectiveCamera( fov, aspect, near, far );
|
|
|
+ camera.filmGauge = Math.max( horizontalAperture, verticalAperture );
|
|
|
+ camera.filmOffset = horizontalApertureOffset;
|
|
|
+ camera.focus = focusDistance;
|
|
|
+ camera.setFocalLength( safeFocalLength );
|
|
|
+
|
|
|
+ if ( verticalApertureOffset !== 0 ) {
|
|
|
+
|
|
|
+ // Three.js supports only horizontal film offset directly.
|
|
|
+ camera.userData.verticalApertureOffset = verticalApertureOffset;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ camera.userData.fStop = fStop;
|
|
|
+ camera.userData.usdProjection = projection;
|
|
|
+ return camera;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ _parseNumber( value, fallback ) {
|
|
|
+
|
|
|
+ const n = Number( value );
|
|
|
+ return Number.isFinite( n ) ? n : fallback;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
_getGeomSubsets( meshPath ) {
|
|
|
|
|
|
const subsets = [];
|