roavatar-renderer 1.5.18 → 1.5.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +148 -19
  2. package/dist/index.js +504 -299
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -653,7 +653,7 @@ export declare interface AvatarModel_Result {
653
653
  avatarConfigurations: AvatarConfigurations | null;
654
654
  }
655
655
 
656
- export declare type AvatarModelUpdateType = "UpdateName" | "UpdateBodyColors" | "UpdateAssets" | "UpdateScales" | "UpdateBackground";
656
+ export declare type AvatarModelUpdateType = "UpdateName" | "UpdateBodyColors" | "UpdateAssets" | "UpdateScales" | "UpdateBackground" | "UpdateAvatarType";
657
657
 
658
658
  /** @category Outfit */
659
659
  export declare type AvatarType = "R6" | "R15";
@@ -1493,9 +1493,15 @@ export declare const FLAGS: {
1493
1493
  ALWAYS_SHOW_ATTACHMENTS: boolean;
1494
1494
  /**Multiplier for delta time used by renderer things (does not affect datamodel side of things but particles and such instead) */
1495
1495
  RENDERER_DELTA_TIME_MULTIPLIER: number;
1496
- /**How many seconds particles will presimulate (mainly so they arent incomplete in thumbnail generations) */
1496
+ /**
1497
+ * How many seconds particles will presimulate (mainly so they arent incomplete in thumbnail generations)
1498
+ * @deprecated This can now be set per-scene
1499
+ * */
1497
1500
  PARTICLES_START_FULL: number;
1498
- /**Framerate of presimulation particles */
1501
+ /**
1502
+ * Framerate of presimulation particles
1503
+ * @deprecated This can now be set per-scene
1504
+ * */
1499
1505
  PARTICLES_START_FULL_FRAMERATE: number;
1500
1506
  /**shows ThreeJS SkeletonHelper */
1501
1507
  SHOW_SKELETON_HELPER: boolean;
@@ -1621,12 +1627,9 @@ export declare function generateOutfitModelThumbnail(auth: Authentication, outfi
1621
1627
  * **Example on generating 1000x1000 webp thumbnail for blank outfit**
1622
1628
  * ```ts
1623
1629
  * const outfit = new Outfit()
1624
- * FLAGS.THUMBNAIL_TIMEOUT = 1 //makes thumbnail generation faster
1625
- * FLAGS.PARTICLES_START_FULL = 10 //makes emitters appear as if theyve been there for some time (10 seconds)
1626
1630
  * FLAGS.RENDERTARGET_TO_CANVASTEXTURE = true //required for gltf export
1627
1631
  * const result = await generateOutfitThumbnail(new Authentication(), outfit, [1000,1000], "webp", 0.99)
1628
1632
  * FLAGS.RENDERTARGET_TO_CANVASTEXTURE = false
1629
- * FLAGS.PARTICLES_START_FULL = 0
1630
1633
  * console.log(result)
1631
1634
  * ```
1632
1635
  */
@@ -1661,21 +1664,20 @@ export declare function getCameraCFrameForHeadshotCustomized(rig: Instance, fov:
1661
1664
  export declare function getDistIndexArray(ref: FileMesh, dist: FileMesh): (number | undefined)[];
1662
1665
 
1663
1666
  /**
1664
- * @deprecated Use new Thumbnails category instead
1665
1667
  * @category ThumbnailGenerator */
1666
1668
  export declare function getExtents(cframe: CFrame, parts: Instance[]): [Vector3, Vector3];
1667
1669
 
1668
1670
  /**
1669
- * @deprecated Use new Thumbnails category instead
1670
1671
  * @category ThumbnailGenerator */
1671
1672
  export declare function getExtentsCenter(extents: [Vector3, Vector3]): Vector3;
1672
1673
 
1673
1674
  /**
1674
- * @deprecated Use new Thumbnails category instead
1675
1675
  * @category ThumbnailGenerator
1676
1676
  * */
1677
1677
  export declare function getExtentsForParts(parts: Instance[], includeTransform?: boolean): [Vector3, Vector3];
1678
1678
 
1679
+ export declare function getExtentsWorld(rig: Instance): [Vector3, Vector3];
1680
+
1679
1681
  export declare function getHeadExtents(rig: Instance): [Vector3, Vector3] | undefined;
1680
1682
 
1681
1683
  /**@category API */
@@ -1707,8 +1709,6 @@ export declare function getPartsInAssembly_Generate(part: Instance): Instance[];
1707
1709
 
1708
1710
  export declare function getRandomBetweenInclusive(min: number, max: number, randomVal?: number): number;
1709
1711
 
1710
- export declare function getRigExtentsWorld(rig: Instance): [Vector3, Vector3];
1711
-
1712
1712
  export declare function getRootAssemblyPart_Generate(part: Instance): Instance;
1713
1713
 
1714
1714
  /**@category API */
@@ -1731,7 +1731,6 @@ export declare interface GetSubscription_Result {
1731
1731
  * Calculates the CFrame the camera should be at when generating a thumbnail
1732
1732
  * @param model The model-like instance to get thumbnail camera for
1733
1733
  * @returns Thumbnail camera cframe
1734
- * @deprecated Use new Thumbnails category instead
1735
1734
  * @category ThumbnailGenerator
1736
1735
  */
1737
1736
  export declare function getThumbnailCameraCFrame(model: Instance, fov: number, forceAngle?: Vec3): CFrame | undefined;
@@ -1921,6 +1920,31 @@ export declare const HumanoidRigType: {
1921
1920
  R15: number;
1922
1921
  };
1923
1922
 
1923
+ /**
1924
+ * Renders a scene to an image and returns a data url
1925
+ * @param renderScene Scene that will be rendered
1926
+ * @param width Width in pixels
1927
+ * @param height Height in pixels
1928
+ * @param format Type of image
1929
+ * @param options
1930
+ * @returns data url
1931
+ *
1932
+ * @category ThumbnailGenerator
1933
+ */
1934
+ export declare function imageThumbnailClick(renderScene: RBXRendererScene, width: number, height: number, format: ImageThumbnailFormat, options?: Partial<ImageThumbnailOptions>): Promise<string | undefined>;
1935
+
1936
+ /**
1937
+ * @category ThumbnailGenerator
1938
+ */
1939
+ export declare type ImageThumbnailFormat = "png" | "webp" | "jpeg";
1940
+
1941
+ /**
1942
+ * @category ThumbnailGenerator
1943
+ */
1944
+ export declare interface ImageThumbnailOptions {
1945
+ quality: number;
1946
+ }
1947
+
1924
1948
  export declare function imageUrlToDataUrl(imageUrl: string): Promise<string>;
1925
1949
 
1926
1950
  /**
@@ -2476,6 +2500,31 @@ export declare const minimumDeltaEBodyColorDifference = 11.4;
2476
2500
  */
2477
2501
  export declare function minus(v0: Vec3, v1: Vec3): Vec3;
2478
2502
 
2503
+ /**
2504
+ * Generates a 3D model from the scene
2505
+ * @param renderScene Scene that will be rendered
2506
+ * @param format Format of resulting model
2507
+ * @param options
2508
+ * @returns ArrayBuffer for glb or Object for gltf
2509
+ *
2510
+ * @category ThumbnailGenerator
2511
+ */
2512
+ export declare function modelThumbnailClick(renderScene: RBXRendererScene, format: ModelThumbnailFormat, options?: Partial<ModelThumbnailOptions>): Promise<ArrayBuffer | {
2513
+ [key: string]: unknown;
2514
+ }>;
2515
+
2516
+ /**
2517
+ * @category ThumbnailGenerator
2518
+ */
2519
+ export declare type ModelThumbnailFormat = "gltf" | "glb";
2520
+
2521
+ /**
2522
+ * @category ThumbnailGenerator
2523
+ */
2524
+ export declare interface ModelThumbnailOptions {
2525
+ includeAnimations: boolean;
2526
+ }
2527
+
2479
2528
  /**
2480
2529
  * @category InstanceWrapper
2481
2530
  */
@@ -2811,6 +2860,37 @@ export declare const OutfitOrigin: {
2811
2860
 
2812
2861
  /**
2813
2862
  * @category Renderer
2863
+ * @example
2864
+ * **Example on usage**
2865
+ * ```ts
2866
+ * const auth = new Authentication()
2867
+ * const outfit = new Outfit()
2868
+ * const renderScene = RBXRenderer.addScene()
2869
+ *
2870
+ * const outfitRenderer = new OutfitRenderer(auth, outfit, renderScene)
2871
+ * outfitRenderer.startAnimating()
2872
+ * outfitRenderer.setMainAnimation("anim.1234") //animation with id 1234, check documentation for other possible values
2873
+ *
2874
+ * outfitRenderer.onRenderSuccess.Connect(() => {
2875
+ * //handle Instance tree + renderDesc being successfuly
2876
+ * })
2877
+ * outfitRenderer.onError.Connect(() => {
2878
+ * //handle Instance tree error
2879
+ * })
2880
+ * outfitRenderer.onRenderError.Connect(() => {
2881
+ * //handle renderDesc error
2882
+ * })
2883
+ *
2884
+ * //onRenderSuccess is fired once after some time
2885
+ *
2886
+ * outfitRenderer.setOutfit(new Outfit())
2887
+ *
2888
+ * //onRenderSuccess is fired again once after some time
2889
+ *
2890
+ * //when we're done rendering
2891
+ * outfitRenderer.destroy()
2892
+ * renderScene.destroy()
2893
+ * ```
2814
2894
  */
2815
2895
  export declare class OutfitRenderer {
2816
2896
  auth: Authentication;
@@ -2820,6 +2900,7 @@ export declare class OutfitRenderer {
2820
2900
  doCameraUpdateOnLoad: boolean; /**Makes camera update when new avatar has loaded */
2821
2901
  doCameraUpdate: boolean; /**Does camera update every frame */
2822
2902
  doAddInstance: boolean; /**If outfitRenderer should call RBXRenderer.addInstance() */
2903
+ forceAnimationLoop: boolean; /**If future loaded animations should be set to loop */
2823
2904
  currentlyChangingRig: boolean;
2824
2905
  currentlyUpdating: boolean;
2825
2906
  hasNewUpdate: boolean;
@@ -2829,14 +2910,34 @@ export declare class OutfitRenderer {
2829
2910
  animationFPS: number;
2830
2911
  deltaTimeMultiplier: number;
2831
2912
  renderScene: RBXRendererScene;
2832
- /**Event is fired if a new outfit failed to load
2913
+ private _renderSceneCompiledConnection?;
2914
+ private _renderSceneFailedConnection?;
2915
+ /**Event is fired if a new outfit failed to load (specifically the Instance tree, not the rendering part)
2916
+ *
2917
+ * Simple: Fired when, Instance tree = fail
2833
2918
  * @returns OutfitRendererErrorType
2834
2919
  */
2835
2920
  onError: Event_2;
2836
- /**Event is fired if a new outfit successfully loaded
2921
+ /**Event is fired if a new outfit successfully loaded (specifically the Instance tree, not the rendering part)
2922
+ *
2923
+ * Simple: Fired when, Instance tree = done
2837
2924
  * @returns void
2838
2925
  */
2839
2926
  onSuccess: Event_2;
2927
+ /**Event is fired when all the renderDescs have been compiled, if any renderDescs fail to render this wil never be fired (can only be fired after Instance tree is done)
2928
+ *
2929
+ * Simple: Fired when, Instance tree = done & Rendering = done (AKA everything)
2930
+ *
2931
+ * NOTE: If FLAGS.LAYERED_CLOTHING_COOLDOWN > 0 (non-default), this may be fired too early and later fire again
2932
+ * @returns void
2933
+ */
2934
+ onRenderSuccess: Event_2;
2935
+ /**Event is fired if a renderDesc fails to compile, it can be fired multiple times
2936
+ *
2937
+ * Simple: Fired when, Rendering = fail
2938
+ * @returns void
2939
+ */
2940
+ onRenderError: Event_2;
2840
2941
  /**
2841
2942
  * Creates a new OutfitRenderer which makes it easy to render outfits
2842
2943
  * @param auth The authentication object, you should have one you use for everything
@@ -2874,11 +2975,22 @@ export declare class OutfitRenderer {
2874
2975
  stopAnimating(): void;
2875
2976
  /**
2876
2977
  * Sets the current animation being played
2877
- * @param name The name of the animation, for example "idle", "run" or "emote.1234"
2978
+ * @param name The name of the animation, for example "idle", "run", "emote.1234" or "anim.1234"
2878
2979
  * @returns If the animation started playing, it may start playing later if it has been queued despite returning false
2879
2980
  */
2880
2981
  setMainAnimation(name: string): Promise<boolean>;
2881
- /**Calls destroy on the rig and stops animating, the OutfitRenderer should not be interacted with after this */
2982
+ fireFullyRenderedIfNeeded(): void;
2983
+ /**
2984
+ * Prepares the OutfitRenderer to be used for a thumbnail, call IMMEDIATELY after creating the OutfitRenderer
2985
+ * @returns true on success
2986
+ */
2987
+ prepareForThumbnail(): Promise<boolean>;
2988
+ private _prepareForThumbnail;
2989
+ /**
2990
+ * Updates rendered particles so they face the camera without simulating them (usually used when generating thumbnails)
2991
+ */
2992
+ updateParticleMatrix(): void;
2993
+ /**Calls destroy on the rig, stops animating and disconnects connections. The OutfitRenderer should not be interacted with after this */
2882
2994
  destroy(): void;
2883
2995
  }
2884
2996
 
@@ -3261,9 +3373,15 @@ export declare class RBXRendererScene {
3261
3373
  effectComposer: EffectComposer | undefined;
3262
3374
  scissor?: [number, number, number, number];
3263
3375
  viewport?: [number, number, number, number];
3376
+ addedInstances: Instance[];
3264
3377
  isRenderingMesh: Map<Instance, boolean>;
3265
3378
  renderDescs: Map<Instance, RenderDesc>;
3266
3379
  destroyConnections: Map<Instance, Connection>;
3380
+ compiledRenderDesc: Event_2;
3381
+ failedRenderDesc: Event_2;
3382
+ forceAccurateNeedsRegeneration: boolean;
3383
+ particlesStartFull?: number;
3384
+ particlesStartFullFramerate: number;
3267
3385
  lookAwayVector: Vec3;
3268
3386
  lookAwayDistance: number;
3269
3387
  shadowEnabled: boolean;
@@ -3282,6 +3400,16 @@ export declare class RBXRendererScene {
3282
3400
  noRect(): void;
3283
3401
  /** Destroys all renderDescs but does not call Destroy on instances */
3284
3402
  destroy(): void;
3403
+ /**
3404
+ * Checks that renderDescs for provided instances are compiled, ignores instances that shouldn't be compiled or haven't been added
3405
+ * @returns true if all provided instances's renderDescs have been compiled
3406
+ */
3407
+ areInstancesCompiled(instances: Instance[]): boolean;
3408
+ /**
3409
+ * Checks that all renderDescs are compiled
3410
+ * @return true if all have been compiled
3411
+ */
3412
+ isFullyCompiled(): boolean;
3285
3413
  /**
3286
3414
  * Exports scene as a GLTF or GLB
3287
3415
  * @param name Name of the resulting file if autoDownload is true
@@ -3341,6 +3469,8 @@ declare class RenderDesc extends DisposableDesc {
3341
3469
  renderScene: RBXRendererScene;
3342
3470
  results?: THREE.Object3D[];
3343
3471
  instance?: Instance;
3472
+ compiled: boolean;
3473
+ failed: boolean;
3344
3474
  constructor(renderScene: RBXRendererScene);
3345
3475
  isSame(_other: RenderDesc): boolean;
3346
3476
  needsRegeneration(_other: RenderDesc): boolean;
@@ -3544,7 +3674,6 @@ export declare interface Search_Result {
3544
3674
  * Gives a scene the default appearance for thumbnails
3545
3675
  * @param renderScene RBXRenderScene to setup
3546
3676
  *
3547
- * @deprecated Use new Thumbnails category instead
3548
3677
  * @category ThumbnailGenerator
3549
3678
  */
3550
3679
  export declare function setupThumbnailScene(renderScene: RBXRendererScene): void;
@@ -3925,8 +4054,6 @@ export declare const WrapLayerAutoSkin: {
3925
4054
 
3926
4055
  export declare const xmlMagic = "<roblox ";
3927
4056
 
3928
- export declare function zoomExtents(cameraCFrame: CFrame, modelCFrame: CFrame, modelSize: Vector3, targetFOV: number, distanceScale: number, sizeType?: ZoomExtentsSizeType): void;
3929
-
3930
4057
  /**
3931
4058
  * Makes model fit inside camera
3932
4059
  * @param cameraCFrame Original camera cframe
@@ -3936,6 +4063,8 @@ export declare function zoomExtents(cameraCFrame: CFrame, modelCFrame: CFrame, m
3936
4063
  * @param distanceScale Distance is multiplied by this
3937
4064
  * @category ThumbnailGenerator
3938
4065
  */
4066
+ export declare function zoomExtents(cameraCFrame: CFrame, modelCFrame: CFrame, modelSize: Vector3, targetFOV: number, distanceScale: number, sizeType?: ZoomExtentsSizeType): void;
4067
+
3939
4068
  export declare type ZoomExtentsSizeType = "largestAxis" | "calculate";
3940
4069
 
3941
4070
  /**
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ function cloneSearch_Payload(data) {
20
20
  }
21
21
  return newData;
22
22
  }
23
- const AllAvatarModelOutfitUpdateTypes = ["UpdateBodyColors", "UpdateAssets", "UpdateScales", "UpdateBackground"];
23
+ const AllAvatarModelOutfitUpdateTypes = ["UpdateBodyColors", "UpdateAssets", "UpdateAvatarType", "UpdateScales", "UpdateBackground"];
24
24
  const BrickColors = {
25
25
  1: "#F2F3F3",
26
26
  2: "#A1A5A2",
@@ -42568,6 +42568,8 @@ class RenderDesc extends DisposableDesc {
42568
42568
  renderScene;
42569
42569
  results;
42570
42570
  instance;
42571
+ compiled = false;
42572
+ failed = false;
42571
42573
  constructor(renderScene) {
42572
42574
  super();
42573
42575
  this.renderScene = renderScene;
@@ -47092,8 +47094,10 @@ class ObjectDesc extends RenderDesc {
47092
47094
  return this.cframe.isSame(other.cframe) && this.meshDesc.isSame(other.meshDesc) && this.materialDesc.isSame(other.materialDesc) && this.isBodyPart === other.isBodyPart && this.size.isSame(other.size) && this.skeletonDesc === other.skeletonDesc;
47093
47095
  }
47094
47096
  needsRegeneration(other) {
47095
- if (this.meshDesc.layerDesc && Date.now() / 1e3 - this.meshDesc.compilationTimestamp < FLAGS.LAYERED_CLOTHING_COOLDOWN) {
47096
- return false;
47097
+ if (!this.renderScene.forceAccurateNeedsRegeneration) {
47098
+ if (this.meshDesc.layerDesc && Date.now() / 1e3 - this.meshDesc.compilationTimestamp < FLAGS.LAYERED_CLOTHING_COOLDOWN) {
47099
+ return false;
47100
+ }
47097
47101
  }
47098
47102
  return !this.meshDesc.isSame(other.meshDesc) || this.materialDesc.needsRegeneration(other.materialDesc);
47099
47103
  }
@@ -48643,6 +48647,7 @@ class AnimatorWrapper extends InstanceWrapper {
48643
48647
  }
48644
48648
  }
48645
48649
  }
48650
+ await Promise.all(promises);
48646
48651
  dataModel.Destroy();
48647
48652
  }
48648
48653
  /**
@@ -51449,18 +51454,20 @@ class EmitterGroupDesc extends RenderDesc {
51449
51454
  this.disposeMeshes(scene, originalResults);
51450
51455
  this.disposeRenderLists(renderer);
51451
51456
  }
51452
- if (FLAGS.PARTICLES_START_FULL) {
51457
+ const startFull = this.renderScene.particlesStartFull || FLAGS.PARTICLES_START_FULL;
51458
+ const startFullFramerate = this.renderScene.particlesStartFullFramerate;
51459
+ if (startFull) {
51453
51460
  for (const emitterDesc of this.emitterDescs) {
51454
- for (let i = 0; i < Math.min(emitterDesc.lifetime.Max * 2 * FLAGS.PARTICLES_START_FULL_FRAMERATE, FLAGS.PARTICLES_START_FULL * FLAGS.PARTICLES_START_FULL_FRAMERATE); i++) {
51455
- emitterDesc.tick(1 / FLAGS.PARTICLES_START_FULL_FRAMERATE, this);
51461
+ for (let i = 0; i < Math.min(emitterDesc.lifetime.Max * 2 * startFullFramerate, startFull * startFullFramerate); i++) {
51462
+ emitterDesc.tick(1 / startFullFramerate, this);
51456
51463
  }
51457
51464
  emitterDesc.updateResult(this.renderScene);
51458
51465
  }
51459
51466
  }
51460
51467
  return this.results;
51461
51468
  }
51462
- updateResults() {
51463
- const dt = specialClamp(this.time - this.lastTime, 0, 1 / 10) * FLAGS.RENDERER_DELTA_TIME_MULTIPLIER;
51469
+ updateResults(forceDeltaTime) {
51470
+ const dt = forceDeltaTime !== void 0 ? forceDeltaTime : specialClamp(this.time - this.lastTime, 0, 1 / 10) * FLAGS.RENDERER_DELTA_TIME_MULTIPLIER;
51464
51471
  this.lastTime = this.time;
51465
51472
  for (const emitterDesc of this.emitterDescs) {
51466
51473
  emitterDesc.tick(dt, this);
@@ -52189,9 +52196,15 @@ class RBXRendererScene {
52189
52196
  scissor;
52190
52197
  viewport;
52191
52198
  //renderables data
52199
+ addedInstances = [];
52192
52200
  isRenderingMesh = /* @__PURE__ */ new Map();
52193
52201
  renderDescs = /* @__PURE__ */ new Map();
52194
52202
  destroyConnections = /* @__PURE__ */ new Map();
52203
+ compiledRenderDesc = new Event();
52204
+ failedRenderDesc = new Event();
52205
+ forceAccurateNeedsRegeneration = false;
52206
+ particlesStartFull = void 0;
52207
+ particlesStartFullFramerate = FLAGS.PARTICLES_START_FULL_FRAMERATE;
52195
52208
  //scene appearance config
52196
52209
  lookAwayVector = [0.406, 0.306, -0.819];
52197
52210
  lookAwayDistance = 6;
@@ -52240,6 +52253,27 @@ class RBXRendererScene {
52240
52253
  this.shadowPlane = void 0;
52241
52254
  }
52242
52255
  }
52256
+ /**
52257
+ * Checks that renderDescs for provided instances are compiled, ignores instances that shouldn't be compiled or haven't been added
52258
+ * @returns true if all provided instances's renderDescs have been compiled
52259
+ */
52260
+ areInstancesCompiled(instances) {
52261
+ for (const instance of instances) {
52262
+ if (!this.addedInstances.includes(instance)) continue;
52263
+ const renderDesc = this.renderDescs.get(instance);
52264
+ if (!renderDesc || !renderDesc.compiled || this.isRenderingMesh.get(instance)) {
52265
+ return false;
52266
+ }
52267
+ }
52268
+ return true;
52269
+ }
52270
+ /**
52271
+ * Checks that all renderDescs are compiled
52272
+ * @return true if all have been compiled
52273
+ */
52274
+ isFullyCompiled() {
52275
+ return this.areInstancesCompiled(this.addedInstances);
52276
+ }
52243
52277
  /**
52244
52278
  * Exports scene as a GLTF or GLB
52245
52279
  * @param name Name of the resulting file if autoDownload is true
@@ -52828,12 +52862,18 @@ class RBXRenderer {
52828
52862
  /**Removes an instance from the renderer */
52829
52863
  static removeInstance(instance, renderScene = RBXRenderer.firstScene) {
52830
52864
  if (!RBXRenderer.renderer) return;
52865
+ const index = renderScene.addedInstances.indexOf(instance);
52866
+ if (index >= 0) {
52867
+ renderScene.addedInstances[index] = renderScene.addedInstances[renderScene.addedInstances.length - 1];
52868
+ renderScene.addedInstances.splice(renderScene.addedInstances.length - 1, 1);
52869
+ }
52831
52870
  const desc = renderScene.renderDescs.get(instance);
52832
52871
  if (desc) {
52833
52872
  desc.dispose(RBXRenderer.renderer, renderScene.scene);
52834
52873
  }
52835
52874
  renderScene.renderDescs.delete(instance);
52836
52875
  renderScene.isRenderingMesh.delete(instance);
52876
+ renderScene.compiledRenderDesc.Fire(instance);
52837
52877
  for (const child of instance.GetChildren()) {
52838
52878
  RBXRenderer.removeInstance(child, renderScene);
52839
52879
  }
@@ -52851,7 +52891,11 @@ class RBXRenderer {
52851
52891
  const oldDesc = renderScene.renderDescs.get(instance);
52852
52892
  const newDesc = new DescClass(renderScene);
52853
52893
  newDesc.fromInstance(instance);
52854
- if (oldDesc && !oldDesc.needsRegeneration(newDesc)) {
52894
+ if (oldDesc && !oldDesc.needsRegeneration(newDesc) && !renderScene.isRenderingMesh.get(instance)) {
52895
+ if (!oldDesc.compiled && !oldDesc.failed) {
52896
+ oldDesc.compiled = true;
52897
+ renderScene.compiledRenderDesc.Fire(instance);
52898
+ }
52855
52899
  if (!oldDesc.isSame(newDesc)) {
52856
52900
  oldDesc.fromRenderDesc(newDesc);
52857
52901
  oldDesc.updateResults();
@@ -52892,6 +52936,9 @@ class RBXRenderer {
52892
52936
  newDesc.dispose(RBXRenderer.renderer, renderScene.scene);
52893
52937
  }
52894
52938
  } else {
52939
+ newDesc.failed = true;
52940
+ renderScene.isRenderingMesh.set(instance, false);
52941
+ renderScene.failedRenderDesc.Fire(instance);
52895
52942
  warn(false, "Failed to compile mesh", this, results);
52896
52943
  }
52897
52944
  });
@@ -52911,6 +52958,7 @@ class RBXRenderer {
52911
52958
  if (renderScene.destroyed) return;
52912
52959
  const RenderDescType = getRenderDescForInstance(instance);
52913
52960
  if (RenderDescType) {
52961
+ if (renderScene.addedInstances.indexOf(instance) < 0) renderScene.addedInstances.push(instance);
52914
52962
  RBXRenderer._addRenderDesc(instance, auth, RenderDescType, renderScene);
52915
52963
  }
52916
52964
  for (const child of instance.GetChildren()) {
@@ -53101,209 +53149,6 @@ class HSR {
53101
53149
  }
53102
53150
  }
53103
53151
  }
53104
- function getCorners(cframe, size) {
53105
- const halfX = size.X / 2;
53106
- const halfY = size.Y / 2;
53107
- const halfZ = size.Z / 2;
53108
- return [
53109
- cframe.multiply(new CFrame(halfX, halfY, halfZ)),
53110
- cframe.multiply(new CFrame(halfX, halfY, -halfZ)),
53111
- cframe.multiply(new CFrame(-halfX, halfY, halfZ)),
53112
- cframe.multiply(new CFrame(-halfX, halfY, -halfZ)),
53113
- cframe.multiply(new CFrame(halfX, -halfY, halfZ)),
53114
- cframe.multiply(new CFrame(halfX, -halfY, -halfZ)),
53115
- cframe.multiply(new CFrame(-halfX, -halfY, halfZ)),
53116
- cframe.multiply(new CFrame(-halfX, -halfY, -halfZ))
53117
- ];
53118
- }
53119
- function getLower(a, b) {
53120
- return new Vector32(
53121
- a.X < b.X ? a.X : b.X,
53122
- a.Y < b.Y ? a.Y : b.Y,
53123
- a.Z < b.Z ? a.Z : b.Z
53124
- );
53125
- }
53126
- function getHigher(a, b) {
53127
- return new Vector32(
53128
- a.X > b.X ? a.X : b.X,
53129
- a.Y > b.Y ? a.Y : b.Y,
53130
- a.Z > b.Z ? a.Z : b.Z
53131
- );
53132
- }
53133
- function getExtentsForParts(parts, includeTransform) {
53134
- let lowerExtents = new Vector32(0, 0, 0);
53135
- let higherExtents = new Vector32(0, 0, 0);
53136
- for (const child of parts) {
53137
- if (child.createWrapper()?.IsA("BasePart")) {
53138
- const cframe = traverseRigCFrame(child, includeTransform, true);
53139
- const size = child.Prop("Size");
53140
- const corners = getCorners(cframe, size);
53141
- for (const corner of corners) {
53142
- lowerExtents = getLower(lowerExtents, new Vector32().fromVec3(corner.Position));
53143
- higherExtents = getHigher(higherExtents, new Vector32().fromVec3(corner.Position));
53144
- }
53145
- }
53146
- }
53147
- return [lowerExtents, higherExtents];
53148
- }
53149
- function getExtents(cframe, parts) {
53150
- const inverseCF = cframe.inverse();
53151
- let lowerExtents = new Vector32(0, 0, 0);
53152
- let higherExtents = new Vector32(0, 0, 0);
53153
- for (const child of parts) {
53154
- if (child.createWrapper()?.IsA("BasePart")) {
53155
- const partCF = child.Prop("CFrame");
53156
- const partSize = child.Prop("Size");
53157
- const corners = getCorners(inverseCF.multiply(partCF), partSize);
53158
- for (const corner of corners) {
53159
- lowerExtents = getLower(lowerExtents, new Vector32().fromVec3(corner.Position));
53160
- higherExtents = getHigher(higherExtents, new Vector32().fromVec3(corner.Position));
53161
- }
53162
- }
53163
- }
53164
- return [lowerExtents, higherExtents];
53165
- }
53166
- function getExtentsCenter(extents) {
53167
- return extents[1].minus(extents[0]).divide(new Vector32(2, 2, 2)).add(extents[0]);
53168
- }
53169
- function zoomExtents(cameraCFrame, modelCFrame, modelSize, targetFOV, distanceScale, sizeType = "calculate") {
53170
- let largestSize = Math.max(modelSize.X, modelSize.Y, modelSize.Z);
53171
- if (sizeType === "calculate") {
53172
- largestSize = modelSize.magnitude() / 2 / Math.sin(rad(targetFOV / 2));
53173
- }
53174
- const fovMultiplier = sizeType === "largestAxis" ? 70 / targetFOV : 1;
53175
- const lookDir = multiply(normalize(minus(cameraCFrame.Position, modelCFrame.Position)), [distanceScale, distanceScale, distanceScale]);
53176
- cameraCFrame.Position = add(modelCFrame.Position, multiply(multiply(lookDir, [largestSize, largestSize, largestSize]), [fovMultiplier, fovMultiplier, fovMultiplier]));
53177
- }
53178
- function getCameraOffset(fov2, extentsSize) {
53179
- const halfSize = extentsSize.magnitude() / 2;
53180
- const fovDivisor = Math.tan(rad(fov2 / 2));
53181
- return halfSize / fovDivisor;
53182
- }
53183
- function zoomToExtents(cameraCFrame, modelCFrame, modelSize, fov2 = 70) {
53184
- const cameraOffset = getCameraOffset(fov2, modelSize);
53185
- const cameraRotation = new CFrame();
53186
- cameraRotation.Orientation = cameraCFrame.Orientation;
53187
- const instancePosition = modelCFrame.Position;
53188
- cameraCFrame.Position = add(instancePosition, multiply(minus([0, 0, 0], cameraRotation.lookVector()), [cameraOffset, cameraOffset, cameraOffset]));
53189
- }
53190
- function getHeadExtents(rig) {
53191
- const head = rig.FindFirstChild("Head");
53192
- if (!head) return;
53193
- const headParts = [];
53194
- for (const child of rig.GetDescendants()) {
53195
- if (child === head) {
53196
- headParts.push(head);
53197
- } else {
53198
- const weld = child.FindFirstChildOfClass("Weld");
53199
- if (weld && child.parent && child.parent.className === "Accessory") {
53200
- if (weld.Prop("Part0") === head || weld.Prop("Part1") === head) {
53201
- headParts.push(child);
53202
- }
53203
- }
53204
- }
53205
- }
53206
- const extents = getExtents(head.Prop("CFrame"), headParts);
53207
- return extents;
53208
- }
53209
- function getRigExtentsWorld(rig) {
53210
- const rigParts = [];
53211
- for (const child of rig.GetDescendants()) {
53212
- if (child.createWrapper()?.IsA("BasePart")) {
53213
- rigParts.push(child);
53214
- }
53215
- }
53216
- const extents = getExtents(new CFrame(), rigParts);
53217
- return extents;
53218
- }
53219
- function getCameraCFrameForHeadshotCustomized(rig, fov2, yRot, distance2) {
53220
- const head = rig.FindFirstChild("Head");
53221
- if (!head) return;
53222
- const headCF = head.PropOrDefault("CFrame", new CFrame());
53223
- const headLocalExtents = getHeadExtents(rig);
53224
- if (!headLocalExtents) return;
53225
- const headCenterPosLocal = headLocalExtents[0].add(headLocalExtents[1].minus(headLocalExtents[0]).divide(new Vector32(2, 2, 2)));
53226
- const headCenterPos = new Vector32().fromVec3(headCF.multiply(new CFrame(...headCenterPosLocal.toVec3())).Position);
53227
- const headCenterCF = new CFrame(...headCenterPos.toVec3());
53228
- let lookVector = headCF.lookVector();
53229
- if (Math.abs(lookVector[1]) > 0.95) {
53230
- lookVector = [0, 0, -1];
53231
- } else {
53232
- lookVector[1] = 0;
53233
- lookVector = normalize(lookVector);
53234
- }
53235
- let lookCF = CFrame.lookAt([0, 0, 0], lookVector);
53236
- lookCF = lookCF.multiply(CFrame.fromEulerAngles(0, rad(yRot), 0, "ZXY"));
53237
- lookVector = lookCF.lookVector();
53238
- const fovMultiplier = 70 / fov2;
53239
- lookCF.Position = add(headCenterCF.Position, multiply(multiply([10, 10, 10], lookVector), [fovMultiplier, fovMultiplier, fovMultiplier]));
53240
- lookCF = CFrame.lookAt(lookCF.Position, headCenterCF.Position);
53241
- const cameraCF = lookCF.clone();
53242
- zoomExtents(cameraCF, headCenterCF, headLocalExtents[1].minus(headLocalExtents[0]), fov2, distance2, "largestAxis");
53243
- return cameraCF;
53244
- }
53245
- function getCameraCFrameForAvatarNonCustomized(rig) {
53246
- const thumbnailCamera = rig.FindFirstChildOfClass("Camera");
53247
- if (thumbnailCamera) return thumbnailCamera.PropOrDefault("CFrame", new CFrame());
53248
- let rootPart = rig.PropOrDefault("PrimaryPart", void 0);
53249
- if (!rootPart) rootPart = rig.FindFirstChildOfClass("Part");
53250
- if (!rootPart) rootPart = rig.FindFirstChildOfClass("MeshPart");
53251
- if (!rootPart) return;
53252
- const rootPartCF = rootPart.PropOrDefault("CFrame", new CFrame()).clone();
53253
- const worldExtents = getRigExtentsWorld(rig);
53254
- if (!worldExtents) return;
53255
- const extentsSize = worldExtents[1].minus(worldExtents[0]);
53256
- rootPartCF.Position = getExtentsCenter(worldExtents).toVec3();
53257
- let lookVector = rootPartCF.lookVector();
53258
- if (Math.abs(lookVector[1]) > 0.95) {
53259
- lookVector = [0, 0, -1];
53260
- } else {
53261
- lookVector[1] = 0;
53262
- lookVector = normalize(lookVector);
53263
- }
53264
- let lookCF = CFrame.lookAt([0, 0, 0], lookVector);
53265
- lookCF = lookCF.multiply(CFrame.fromEulerAngles(25 * Math.PI / 180, 27.5 * Math.PI / 180, 0, "ZXY"));
53266
- lookVector = lookCF.lookVector();
53267
- lookCF.Position = add(rootPartCF.Position, multiply([10, 10, 10], lookVector));
53268
- lookCF = CFrame.lookAt(lookCF.Position, rootPartCF.Position);
53269
- const cameraCF = lookCF.clone();
53270
- zoomExtents(cameraCF, rootPartCF, extentsSize, 70, 1);
53271
- return cameraCF;
53272
- }
53273
- function getThumbnailCameraCFrame(model, fov2, forceAngle) {
53274
- const thumbnailCamera = model.FindFirstChildOfClass("Camera");
53275
- if (thumbnailCamera) return thumbnailCamera.PropOrDefault("CFrame", new CFrame());
53276
- let rootPart = model.PropOrDefault("PrimaryPart", void 0);
53277
- if (!rootPart) rootPart = model.FindFirstChildOfClass("Part");
53278
- if (!rootPart) rootPart = model.FindFirstChildOfClass("MeshPart");
53279
- if (!rootPart) return;
53280
- const rootPartCF = rootPart.PropOrDefault("CFrame", new CFrame()).clone();
53281
- const worldExtents = getRigExtentsWorld(model);
53282
- if (!worldExtents) return;
53283
- const extentsSize = worldExtents[1].minus(worldExtents[0]);
53284
- rootPartCF.Position = getExtentsCenter(worldExtents).toVec3();
53285
- let lookVector = rootPartCF.lookVector();
53286
- if (Math.abs(lookVector[1]) > 0.95) {
53287
- lookVector = [0, 0, -1];
53288
- } else {
53289
- lookVector[1] = 0;
53290
- lookVector = normalize(lookVector);
53291
- }
53292
- let lookCF = CFrame.lookAt([0, 0, 0], lookVector);
53293
- if (!forceAngle) {
53294
- lookCF = lookCF.multiply(CFrame.fromEulerAngles(0, 0, rad(45)));
53295
- lookCF = lookCF.multiply(CFrame.fromEulerAngles(rad(35), 0, 0));
53296
- lookCF = lookCF.multiply(CFrame.fromEulerAngles(0, 0, 0));
53297
- } else {
53298
- lookCF = lookCF.multiply(CFrame.fromEulerAngles(rad(forceAngle[0]), rad(forceAngle[1]), rad(forceAngle[2])));
53299
- }
53300
- lookVector = lookCF.lookVector();
53301
- lookCF.Position = add(rootPartCF.Position, multiply([10, 10, 10], lookVector));
53302
- lookCF = CFrame.lookAt(lookCF.Position, rootPartCF.Position);
53303
- const cameraCF = lookCF.clone();
53304
- zoomExtents(cameraCF, rootPartCF, extentsSize, fov2, 1);
53305
- return cameraCF;
53306
- }
53307
53152
  class OutfitRenderer {
53308
53153
  auth;
53309
53154
  outfit;
@@ -53316,6 +53161,8 @@ class OutfitRenderer {
53316
53161
  /**Does camera update every frame */
53317
53162
  doAddInstance = true;
53318
53163
  /**If outfitRenderer should call RBXRenderer.addInstance() */
53164
+ forceAnimationLoop = true;
53165
+ /**If future loaded animations should be set to loop */
53319
53166
  currentlyChangingRig = false;
53320
53167
  currentlyUpdating = false;
53321
53168
  hasNewUpdate = false;
@@ -53325,14 +53172,34 @@ class OutfitRenderer {
53325
53172
  animationFPS = 60;
53326
53173
  deltaTimeMultiplier = 1;
53327
53174
  renderScene = RBXRenderer.firstScene;
53328
- /**Event is fired if a new outfit failed to load
53175
+ _renderSceneCompiledConnection;
53176
+ _renderSceneFailedConnection;
53177
+ /**Event is fired if a new outfit failed to load (specifically the Instance tree, not the rendering part)
53178
+ *
53179
+ * Simple: Fired when, Instance tree = fail
53329
53180
  * @returns OutfitRendererErrorType
53330
53181
  */
53331
53182
  onError = new Event();
53332
- /**Event is fired if a new outfit successfully loaded
53333
- * @returns void
53334
- */
53183
+ /**Event is fired if a new outfit successfully loaded (specifically the Instance tree, not the rendering part)
53184
+ *
53185
+ * Simple: Fired when, Instance tree = done
53186
+ * @returns void
53187
+ */
53335
53188
  onSuccess = new Event();
53189
+ /**Event is fired when all the renderDescs have been compiled, if any renderDescs fail to render this wil never be fired (can only be fired after Instance tree is done)
53190
+ *
53191
+ * Simple: Fired when, Instance tree = done & Rendering = done (AKA everything)
53192
+ *
53193
+ * NOTE: If FLAGS.LAYERED_CLOTHING_COOLDOWN > 0 (non-default), this may be fired too early and later fire again
53194
+ * @returns void
53195
+ */
53196
+ onRenderSuccess = new Event();
53197
+ /**Event is fired if a renderDesc fails to compile, it can be fired multiple times
53198
+ *
53199
+ * Simple: Fired when, Rendering = fail
53200
+ * @returns void
53201
+ */
53202
+ onRenderError = new Event();
53336
53203
  /**
53337
53204
  * Creates a new OutfitRenderer which makes it easy to render outfits
53338
53205
  * @param auth The authentication object, you should have one you use for everything
@@ -53344,6 +53211,13 @@ class OutfitRenderer {
53344
53211
  this.outfit = outfit;
53345
53212
  this.currentRigType = outfit.playerAvatarType;
53346
53213
  this.renderScene = renderScene;
53214
+ this._renderSceneCompiledConnection = this.renderScene.compiledRenderDesc.Connect(() => {
53215
+ this.fireFullyRenderedIfNeeded();
53216
+ });
53217
+ this._renderSceneFailedConnection = this.renderScene.failedRenderDesc.Connect(() => {
53218
+ console.log("OutfitRenderer failed");
53219
+ this.onRenderError.Fire();
53220
+ });
53347
53221
  this._updateOutfit();
53348
53222
  }
53349
53223
  /**
@@ -53404,7 +53278,10 @@ class OutfitRenderer {
53404
53278
  }
53405
53279
  this.currentlyUpdating = false;
53406
53280
  if (this.currentRig) {
53407
- if (this.doAddInstance) RBXRenderer.addInstance(this.currentRig, this.auth, this.renderScene);
53281
+ if (this.doAddInstance) {
53282
+ RBXRenderer.addInstance(this.currentRig, this.auth, this.renderScene);
53283
+ this.fireFullyRenderedIfNeeded();
53284
+ }
53408
53285
  if (this.doCameraUpdateOnLoad) {
53409
53286
  this.centerCamera();
53410
53287
  }
@@ -53479,7 +53356,7 @@ class OutfitRenderer {
53479
53356
  if (humanoid) {
53480
53357
  const animator = humanoid.FindFirstChildOfClass("Animator");
53481
53358
  if (animator) {
53482
- const deltaTime = deltaTimeOverride || (Date.now() / 1e3 - this.lastFrameTime) * this.deltaTimeMultiplier;
53359
+ const deltaTime = deltaTimeOverride !== void 0 ? deltaTimeOverride : (Date.now() / 1e3 - this.lastFrameTime) * this.deltaTimeMultiplier;
53483
53360
  this.lastFrameTime = Date.now() / 1e3;
53484
53361
  const animatorW = new AnimatorWrapper(animator);
53485
53362
  animatorW.renderAnimation(deltaTime);
@@ -53500,7 +53377,7 @@ class OutfitRenderer {
53500
53377
  }
53501
53378
  /**
53502
53379
  * Sets the current animation being played
53503
- * @param name The name of the animation, for example "idle", "run" or "emote.1234"
53380
+ * @param name The name of the animation, for example "idle", "run", "emote.1234" or "anim.1234"
53504
53381
  * @returns If the animation started playing, it may start playing later if it has been queued despite returning false
53505
53382
  */
53506
53383
  setMainAnimation(name) {
@@ -53514,7 +53391,12 @@ class OutfitRenderer {
53514
53391
  const successfullyPlayed = animatorW.playAnimation(name);
53515
53392
  if (!successfullyPlayed && name.startsWith("emote.") && name) {
53516
53393
  const emoteId = BigInt(name.split(".")[1]);
53517
- animatorW.loadAvatarAnimation(emoteId, true, true).then(() => {
53394
+ animatorW.loadAvatarAnimation(emoteId, true, this.forceAnimationLoop).then(() => {
53395
+ resolve(animatorW.playAnimation(name));
53396
+ });
53397
+ } else if (!successfullyPlayed && name.startsWith("id.") && name) {
53398
+ const animId = BigInt(name.split(".")[1]);
53399
+ animatorW.loadAnimation(animId, this.forceAnimationLoop).then(() => {
53518
53400
  resolve(animatorW.playAnimation(name));
53519
53401
  });
53520
53402
  } else {
@@ -53532,13 +53414,93 @@ class OutfitRenderer {
53532
53414
  }
53533
53415
  });
53534
53416
  }
53535
- /**Calls destroy on the rig and stops animating, the OutfitRenderer should not be interacted with after this */
53417
+ fireFullyRenderedIfNeeded() {
53418
+ if (!this.currentlyChangingRig && !this.currentlyUpdating && !this.hasNewUpdate && this.currentRig && this.renderScene.areInstancesCompiled(this.currentRig.GetDescendants())) {
53419
+ this.onRenderSuccess.Fire();
53420
+ }
53421
+ }
53422
+ /**
53423
+ * Prepares the OutfitRenderer to be used for a thumbnail, call IMMEDIATELY after creating the OutfitRenderer
53424
+ * @returns true on success
53425
+ */
53426
+ async prepareForThumbnail() {
53427
+ const connections = [];
53428
+ const result = await Promise.race([
53429
+ this._prepareForThumbnail(),
53430
+ new Promise((resolve) => {
53431
+ connections.push(this.onError.Connect(() => {
53432
+ resolve(false);
53433
+ }));
53434
+ }),
53435
+ new Promise((resolve) => {
53436
+ connections.push(this.onRenderError.Connect(() => {
53437
+ resolve(false);
53438
+ }));
53439
+ })
53440
+ ]);
53441
+ for (const connection of connections) {
53442
+ connection.Disconnect();
53443
+ }
53444
+ return result;
53445
+ }
53446
+ async _prepareForThumbnail() {
53447
+ this.doAddInstance = false;
53448
+ if (this.outfit.playerAvatarType === AvatarType.R6) this.deltaTimeMultiplier = 0;
53449
+ await new Promise((resolve) => {
53450
+ this.onSuccess.Connect(() => {
53451
+ if (!this.outfit.containsAssetType("Gear")) {
53452
+ if (this.outfit.playerAvatarType === AvatarType.R15) {
53453
+ this.setMainAnimation("pose").then(() => {
53454
+ resolve(void 0);
53455
+ });
53456
+ } else {
53457
+ this.setMainAnimation("idle").then(() => {
53458
+ resolve(void 0);
53459
+ });
53460
+ }
53461
+ } else {
53462
+ this.setMainAnimation("toolnone").then(() => {
53463
+ resolve(void 0);
53464
+ });
53465
+ }
53466
+ });
53467
+ });
53468
+ this.animateOnce(!this.outfit.containsAssetType("Gear") && this.outfit.playerAvatarType === AvatarType.R6 ? 0 : 1);
53469
+ if (this.currentRig) RBXRenderer.addInstance(this.currentRig, this.auth, this.renderScene);
53470
+ await new Promise((resolve) => {
53471
+ const connection = this.onRenderSuccess.Connect(() => {
53472
+ resolve(void 0);
53473
+ connection.Disconnect();
53474
+ });
53475
+ });
53476
+ return true;
53477
+ }
53478
+ /**
53479
+ * Updates rendered particles so they face the camera without simulating them (usually used when generating thumbnails)
53480
+ */
53481
+ updateParticleMatrix() {
53482
+ if (!this.currentRig) return;
53483
+ for (const instance of this.currentRig.GetDescendants()) {
53484
+ const renderDesc = this.renderScene.renderDescs.get(instance);
53485
+ if (renderDesc && renderDesc instanceof EmitterGroupDesc) {
53486
+ renderDesc.updateResults(0);
53487
+ }
53488
+ }
53489
+ }
53490
+ /**Calls destroy on the rig, stops animating and disconnects connections. The OutfitRenderer should not be interacted with after this */
53536
53491
  destroy() {
53537
53492
  this.stopAnimating();
53538
53493
  this.currentRig?.Destroy();
53494
+ this.currentRig = void 0;
53495
+ this._renderSceneCompiledConnection?.Disconnect();
53496
+ this._renderSceneFailedConnection?.Disconnect();
53497
+ this.onError.Clear();
53498
+ this.onSuccess.Clear();
53499
+ this.onRenderError.Clear();
53500
+ this.onRenderSuccess.Clear();
53539
53501
  }
53540
53502
  }
53541
- function renderToRenderTarget(width, height, renderScene) {
53503
+ function renderToRenderTarget$1(width, height, renderScene) {
53542
53504
  const renderTarget = new WebGLRenderTarget(width, height, {
53543
53505
  colorSpace: SRGBColorSpace,
53544
53506
  generateMipmaps: false,
@@ -53553,7 +53515,7 @@ function renderToRenderTarget(width, height, renderScene) {
53553
53515
  rbxRenderer.render(renderScene.scene, renderScene.camera);
53554
53516
  return renderTarget;
53555
53517
  }
53556
- async function renderTargetToCanvas(renderTarget) {
53518
+ async function renderTargetToCanvas$1(renderTarget) {
53557
53519
  const rbxRenderer = RBXRenderer.getRenderer();
53558
53520
  if (!rbxRenderer) return;
53559
53521
  const width = renderTarget.width;
@@ -53562,45 +53524,210 @@ async function renderTargetToCanvas(renderTarget) {
53562
53524
  await rbxRenderer.readRenderTargetPixelsAsync(renderTarget, 0, 0, width, height, data);
53563
53525
  return imageDataToCanvas(data, width, height);
53564
53526
  }
53565
- async function generateModelThumbnail(auth, renderScene, model, size = [150, 150], type = "png", quality = 1, gltfAutoDownload = false, includeAnimations = false) {
53566
- return new Promise((resolve) => {
53567
- const cameraCFrame = getThumbnailCameraCFrame(model, renderScene.camera.fov);
53527
+ async function imageThumbnailClick(renderScene, width, height, format, options) {
53528
+ const resultOptions = {
53529
+ quality: 1
53530
+ };
53531
+ if (options) Object.assign(resultOptions, options);
53532
+ const renderTarget = renderToRenderTarget$1(width, height, renderScene);
53533
+ const canvas = await renderTargetToCanvas$1(renderTarget);
53534
+ renderTarget.dispose();
53535
+ if (canvas) {
53536
+ return canvas.toDataURL(`image/${format}`, resultOptions.quality);
53537
+ } else {
53538
+ return void 0;
53539
+ }
53540
+ }
53541
+ async function modelThumbnailClick(renderScene, format, options) {
53542
+ const resultOptions = {
53543
+ includeAnimations: false
53544
+ };
53545
+ if (options) Object.assign(resultOptions, options);
53546
+ return await renderScene.exportGLTF(`result`, false, {
53547
+ includeAnimations: resultOptions.includeAnimations,
53548
+ binary: format === "glb"
53549
+ });
53550
+ }
53551
+ function getCorners(cframe, size) {
53552
+ const halfX = size.X / 2;
53553
+ const halfY = size.Y / 2;
53554
+ const halfZ = size.Z / 2;
53555
+ return [
53556
+ cframe.multiply(new CFrame(halfX, halfY, halfZ)),
53557
+ cframe.multiply(new CFrame(halfX, halfY, -halfZ)),
53558
+ cframe.multiply(new CFrame(-halfX, halfY, halfZ)),
53559
+ cframe.multiply(new CFrame(-halfX, halfY, -halfZ)),
53560
+ cframe.multiply(new CFrame(halfX, -halfY, halfZ)),
53561
+ cframe.multiply(new CFrame(halfX, -halfY, -halfZ)),
53562
+ cframe.multiply(new CFrame(-halfX, -halfY, halfZ)),
53563
+ cframe.multiply(new CFrame(-halfX, -halfY, -halfZ))
53564
+ ];
53565
+ }
53566
+ function getLower(a, b) {
53567
+ return new Vector32(
53568
+ a.X < b.X ? a.X : b.X,
53569
+ a.Y < b.Y ? a.Y : b.Y,
53570
+ a.Z < b.Z ? a.Z : b.Z
53571
+ );
53572
+ }
53573
+ function getHigher(a, b) {
53574
+ return new Vector32(
53575
+ a.X > b.X ? a.X : b.X,
53576
+ a.Y > b.Y ? a.Y : b.Y,
53577
+ a.Z > b.Z ? a.Z : b.Z
53578
+ );
53579
+ }
53580
+ function getExtentsForParts(parts, includeTransform) {
53581
+ let lowerExtents = new Vector32(0, 0, 0);
53582
+ let higherExtents = new Vector32(0, 0, 0);
53583
+ for (const child of parts) {
53584
+ if (child.createWrapper()?.IsA("BasePart")) {
53585
+ const cframe = traverseRigCFrame(child, includeTransform, true);
53586
+ const size = child.Prop("Size");
53587
+ const corners = getCorners(cframe, size);
53588
+ for (const corner of corners) {
53589
+ lowerExtents = getLower(lowerExtents, new Vector32().fromVec3(corner.Position));
53590
+ higherExtents = getHigher(higherExtents, new Vector32().fromVec3(corner.Position));
53591
+ }
53592
+ }
53593
+ }
53594
+ return [lowerExtents, higherExtents];
53595
+ }
53596
+ function getExtents(cframe, parts) {
53597
+ const inverseCF = cframe.inverse();
53598
+ let lowerExtents = new Vector32(0, 0, 0);
53599
+ let higherExtents = new Vector32(0, 0, 0);
53600
+ for (const child of parts) {
53601
+ if (child.createWrapper()?.IsA("BasePart")) {
53602
+ const partCF = child.Prop("CFrame");
53603
+ const partSize = child.Prop("Size");
53604
+ const corners = getCorners(inverseCF.multiply(partCF), partSize);
53605
+ for (const corner of corners) {
53606
+ lowerExtents = getLower(lowerExtents, new Vector32().fromVec3(corner.Position));
53607
+ higherExtents = getHigher(higherExtents, new Vector32().fromVec3(corner.Position));
53608
+ }
53609
+ }
53610
+ }
53611
+ return [lowerExtents, higherExtents];
53612
+ }
53613
+ function getExtentsWorld(rig) {
53614
+ const rigParts = [];
53615
+ for (const child of rig.GetDescendants()) {
53616
+ if (child.createWrapper()?.IsA("BasePart")) {
53617
+ rigParts.push(child);
53618
+ }
53619
+ }
53620
+ const extents = getExtents(new CFrame(), rigParts);
53621
+ return extents;
53622
+ }
53623
+ function getExtentsCenter(extents) {
53624
+ return extents[1].minus(extents[0]).divide(new Vector32(2, 2, 2)).add(extents[0]);
53625
+ }
53626
+ function zoomExtents(cameraCFrame, modelCFrame, modelSize, targetFOV, distanceScale, sizeType = "calculate") {
53627
+ let largestSize = Math.max(modelSize.X, modelSize.Y, modelSize.Z);
53628
+ if (sizeType === "calculate") {
53629
+ largestSize = modelSize.magnitude() / 2 / Math.sin(rad(targetFOV / 2));
53630
+ }
53631
+ const fovMultiplier = sizeType === "largestAxis" ? 70 / targetFOV : 1;
53632
+ const lookDir = multiply(normalize(minus(cameraCFrame.Position, modelCFrame.Position)), [distanceScale, distanceScale, distanceScale]);
53633
+ cameraCFrame.Position = add(modelCFrame.Position, multiply(multiply(lookDir, [largestSize, largestSize, largestSize]), [fovMultiplier, fovMultiplier, fovMultiplier]));
53634
+ }
53635
+ function getCameraOffset(fov2, extentsSize) {
53636
+ const halfSize = extentsSize.magnitude() / 2;
53637
+ const fovDivisor = Math.tan(rad(fov2 / 2));
53638
+ return halfSize / fovDivisor;
53639
+ }
53640
+ function zoomToExtents(cameraCFrame, modelCFrame, modelSize, fov2 = 70) {
53641
+ const cameraOffset = getCameraOffset(fov2, modelSize);
53642
+ const cameraRotation = new CFrame();
53643
+ cameraRotation.Orientation = cameraCFrame.Orientation;
53644
+ const instancePosition = modelCFrame.Position;
53645
+ cameraCFrame.Position = add(instancePosition, multiply(minus([0, 0, 0], cameraRotation.lookVector()), [cameraOffset, cameraOffset, cameraOffset]));
53646
+ }
53647
+ function getThumbnailCameraCFrame(model, fov2, forceAngle) {
53648
+ const thumbnailCamera = model.FindFirstChildOfClass("Camera");
53649
+ if (thumbnailCamera) return thumbnailCamera.PropOrDefault("CFrame", new CFrame());
53650
+ let rootPart = model.PropOrDefault("PrimaryPart", void 0);
53651
+ if (!rootPart) rootPart = model.FindFirstChildOfClass("Part");
53652
+ if (!rootPart) rootPart = model.FindFirstChildOfClass("MeshPart");
53653
+ if (!rootPart) return;
53654
+ const rootPartCF = rootPart.PropOrDefault("CFrame", new CFrame()).clone();
53655
+ const worldExtents = getExtentsWorld(model);
53656
+ if (!worldExtents) return;
53657
+ const extentsSize = worldExtents[1].minus(worldExtents[0]);
53658
+ rootPartCF.Position = getExtentsCenter(worldExtents).toVec3();
53659
+ let lookVector = rootPartCF.lookVector();
53660
+ if (Math.abs(lookVector[1]) > 0.95) {
53661
+ lookVector = [0, 0, -1];
53662
+ } else {
53663
+ lookVector[1] = 0;
53664
+ lookVector = normalize(lookVector);
53665
+ }
53666
+ let lookCF = CFrame.lookAt([0, 0, 0], lookVector);
53667
+ if (!forceAngle) {
53668
+ lookCF = lookCF.multiply(CFrame.fromEulerAngles(0, 0, rad(45)));
53669
+ lookCF = lookCF.multiply(CFrame.fromEulerAngles(rad(35), 0, 0));
53670
+ lookCF = lookCF.multiply(CFrame.fromEulerAngles(0, 0, 0));
53671
+ } else {
53672
+ lookCF = lookCF.multiply(CFrame.fromEulerAngles(rad(forceAngle[0]), rad(forceAngle[1]), rad(forceAngle[2])));
53673
+ }
53674
+ lookVector = lookCF.lookVector();
53675
+ lookCF.Position = add(rootPartCF.Position, multiply([10, 10, 10], lookVector));
53676
+ lookCF = CFrame.lookAt(lookCF.Position, rootPartCF.Position);
53677
+ const cameraCF = lookCF.clone();
53678
+ zoomExtents(cameraCF, rootPartCF, extentsSize, fov2, 1);
53679
+ return cameraCF;
53680
+ }
53681
+ function setupThumbnailScene(renderScene) {
53682
+ renderScene.particlesStartFull = 4;
53683
+ renderScene.particlesStartFullFramerate = 20;
53684
+ renderScene.forceAccurateNeedsRegeneration = true;
53685
+ renderScene.shouldAnimate = false;
53686
+ renderScene.wellLitDirectionalLightIntensity *= 2;
53687
+ renderScene.shadowEnabled = false;
53688
+ RBXRenderer.setupScene("Thumbnail", 16777215, renderScene);
53689
+ if (renderScene.plane) renderScene.scene.remove(renderScene.plane);
53690
+ if (renderScene.shadowPlane) renderScene.scene.remove(renderScene.shadowPlane);
53691
+ renderScene.scene.background = null;
53692
+ }
53693
+ async function generateOutfitThumbnail(auth, outfit, size = [150, 150], type = "png", quality = 1, gltfAutoDownload = false, includeAnimations = false, renderSceneParam) {
53694
+ const renderScene = renderSceneParam || RBXRenderer.addScene();
53695
+ if (renderScene !== renderSceneParam) {
53696
+ setupThumbnailScene(renderScene);
53697
+ }
53698
+ const outfitRenderer = new OutfitRenderer(auth, outfit, renderScene);
53699
+ const outfitSuccess = await outfitRenderer.prepareForThumbnail();
53700
+ if (!outfitSuccess) {
53701
+ outfitRenderer.destroy();
53702
+ renderScene.destroy();
53703
+ return void 0;
53704
+ }
53705
+ if (outfitRenderer.currentRig) {
53706
+ const cameraCFrame = getThumbnailCameraCFrame(outfitRenderer.currentRig, renderScene.camera.fov);
53568
53707
  if (cameraCFrame) {
53569
53708
  RBXRenderer.setCameraCFrame(cameraCFrame, renderScene);
53570
53709
  }
53571
- RBXRenderer.addInstance(model, auth, renderScene);
53572
- let exportTimeout = !API.Misc.getCurrentlyLoading() ? setTimeout(doExport, FLAGS.THUMBNAIL_TIMEOUT) : void 0;
53573
- const onLoadingConnection = API.Events.OnLoadingAssets.Connect((currentlyLoading) => {
53574
- if (exportTimeout) {
53575
- clearTimeout(exportTimeout);
53576
- exportTimeout = void 0;
53577
- }
53578
- if (!currentlyLoading) {
53579
- exportTimeout = setTimeout(doExport, FLAGS.THUMBNAIL_TIMEOUT);
53580
- }
53710
+ outfitRenderer.updateParticleMatrix();
53711
+ const result = type === "gltf" || type === "glb" ? await modelThumbnailClick(renderScene, type, {
53712
+ includeAnimations
53713
+ }) : await imageThumbnailClick(renderScene, size[0], size[1], type, {
53714
+ quality
53581
53715
  });
53582
- async function doExport() {
53583
- onLoadingConnection.Disconnect();
53584
- if (type === "gltf" || type === "glb") {
53585
- if (!FLAGS.RENDERTARGET_TO_CANVASTEXTURE && FLAGS.USE_RENDERTARGET) {
53586
- warn(true, "FLAGS.RENDERTARGET_TO_CANVASTEXTURE is false, GLTF export cannot export render target textures, consider setting this flag to true");
53587
- }
53588
- resolve(await renderScene.exportGLTF(`result`, gltfAutoDownload, {
53589
- includeAnimations,
53590
- binary: type === "glb"
53591
- }));
53716
+ if (gltfAutoDownload && (type === "gltf" || type === "glb")) {
53717
+ if (result instanceof ArrayBuffer) {
53718
+ saveByteArray([result], `result.glb`);
53592
53719
  } else {
53593
- const renderTarget = renderToRenderTarget(...size, renderScene);
53594
- const canvasTarget = await renderTargetToCanvas(renderTarget);
53595
- if (canvasTarget) {
53596
- resolve(canvasTarget.toDataURL(`image/${type}`, quality));
53597
- } else {
53598
- resolve(void 0);
53599
- }
53720
+ download(`result.gltf`, JSON.stringify(result));
53600
53721
  }
53601
- renderScene.destroy();
53602
53722
  }
53603
- });
53723
+ outfitRenderer.destroy();
53724
+ renderScene.destroy();
53725
+ return result;
53726
+ } else {
53727
+ outfitRenderer.destroy();
53728
+ renderScene.destroy();
53729
+ return void 0;
53730
+ }
53604
53731
  }
53605
53732
  async function generateOutfitModelThumbnail(auth, outfitModel, options) {
53606
53733
  const defaultOptions = {
@@ -53643,34 +53770,37 @@ async function generateOutfitModelThumbnail(auth, outfitModel, options) {
53643
53770
  }
53644
53771
  return generateOutfitThumbnail(auth, outfitModel.outfit, defaultOptions.size, defaultOptions.type, defaultOptions.quality, defaultOptions.gltfAutoDownload, defaultOptions.includeAnimations, renderScene);
53645
53772
  }
53646
- async function generateOutfitThumbnail(auth, outfit, size = [150, 150], type = "png", quality = 1, gltfAutoDownload = false, includeAnimations = false, renderSceneParam) {
53773
+ function renderToRenderTarget(width, height, renderScene) {
53774
+ const renderTarget = new WebGLRenderTarget(width, height, {
53775
+ colorSpace: SRGBColorSpace,
53776
+ generateMipmaps: false,
53777
+ minFilter: LinearFilter,
53778
+ magFilter: LinearFilter,
53779
+ type: UnsignedByteType,
53780
+ samples: 4
53781
+ });
53782
+ const rbxRenderer = RBXRenderer.getRenderer();
53783
+ if (!rbxRenderer) return renderTarget;
53784
+ rbxRenderer.setRenderTarget(renderTarget);
53785
+ rbxRenderer.render(renderScene.scene, renderScene.camera);
53786
+ return renderTarget;
53787
+ }
53788
+ async function renderTargetToCanvas(renderTarget) {
53789
+ const rbxRenderer = RBXRenderer.getRenderer();
53790
+ if (!rbxRenderer) return;
53791
+ const width = renderTarget.width;
53792
+ const height = renderTarget.height;
53793
+ const data = new Uint8Array(width * height * 4);
53794
+ await rbxRenderer.readRenderTargetPixelsAsync(renderTarget, 0, 0, width, height, data);
53795
+ return imageDataToCanvas(data, width, height);
53796
+ }
53797
+ async function generateModelThumbnail(auth, renderScene, model, size = [150, 150], type = "png", quality = 1, gltfAutoDownload = false, includeAnimations = false) {
53647
53798
  return new Promise((resolve) => {
53648
- const startTime = performance.now();
53649
- const renderScene = renderSceneParam || RBXRenderer.addScene();
53650
- if (renderScene !== renderSceneParam) {
53651
- setupThumbnailScene(renderScene);
53652
- }
53653
- const outfitRenderer = new OutfitRenderer(auth, outfit, renderScene);
53654
- outfitRenderer.doAddInstance = false;
53655
- if (outfit.playerAvatarType === AvatarType.R6) outfitRenderer.deltaTimeMultiplier = 0;
53656
- API.Misc.startCurrentlyLoadingAssets("thumbnail-animation");
53657
- outfitRenderer.onSuccess.Connect(() => {
53658
- if (!outfit.containsAssetType("Gear")) {
53659
- if (outfit.playerAvatarType === AvatarType.R15) {
53660
- outfitRenderer.setMainAnimation("pose").then(() => {
53661
- API.Misc.stopCurrentlyLoadingAssets("thumbnail-animation");
53662
- });
53663
- } else {
53664
- outfitRenderer.setMainAnimation("idle").then(() => {
53665
- API.Misc.stopCurrentlyLoadingAssets("thumbnail-animation");
53666
- });
53667
- }
53668
- } else {
53669
- outfitRenderer.setMainAnimation("toolnone").then(() => {
53670
- API.Misc.stopCurrentlyLoadingAssets("thumbnail-animation");
53671
- });
53672
- }
53673
- });
53799
+ const cameraCFrame = getThumbnailCameraCFrame(model, renderScene.camera.fov);
53800
+ if (cameraCFrame) {
53801
+ RBXRenderer.setCameraCFrame(cameraCFrame, renderScene);
53802
+ }
53803
+ RBXRenderer.addInstance(model, auth, renderScene);
53674
53804
  let exportTimeout = !API.Misc.getCurrentlyLoading() ? setTimeout(doExport, FLAGS.THUMBNAIL_TIMEOUT) : void 0;
53675
53805
  const onLoadingConnection = API.Events.OnLoadingAssets.Connect((currentlyLoading) => {
53676
53806
  if (exportTimeout) {
@@ -53683,27 +53813,27 @@ async function generateOutfitThumbnail(auth, outfit, size = [150, 150], type = "
53683
53813
  });
53684
53814
  async function doExport() {
53685
53815
  onLoadingConnection.Disconnect();
53686
- if (outfitRenderer.currentRig) {
53687
- outfitRenderer.animateOnce(!outfit.containsAssetType("Gear") && outfit.playerAvatarType === AvatarType.R6 ? 0 : 1);
53688
- const thumbnailResult = await generateModelThumbnail(auth, renderScene, outfitRenderer.currentRig, size, type, quality, gltfAutoDownload, includeAnimations);
53689
- console.log("Generated outfit thumbnail after seconds:", (performance.now() - startTime) / 1e3);
53690
- resolve(thumbnailResult);
53691
- if (outfitRenderer.currentRig) outfitRenderer.currentRig.Destroy();
53816
+ if (type === "gltf" || type === "glb") {
53817
+ if (!FLAGS.RENDERTARGET_TO_CANVASTEXTURE && FLAGS.USE_RENDERTARGET) {
53818
+ warn(true, "FLAGS.RENDERTARGET_TO_CANVASTEXTURE is false, GLTF export cannot export render target textures, consider setting this flag to true");
53819
+ }
53820
+ resolve(await renderScene.exportGLTF(`result`, gltfAutoDownload, {
53821
+ includeAnimations,
53822
+ binary: type === "glb"
53823
+ }));
53692
53824
  } else {
53693
- resolve(void 0);
53825
+ const renderTarget = renderToRenderTarget(...size, renderScene);
53826
+ const canvasTarget = await renderTargetToCanvas(renderTarget);
53827
+ if (canvasTarget) {
53828
+ resolve(canvasTarget.toDataURL(`image/${type}`, quality));
53829
+ } else {
53830
+ resolve(void 0);
53831
+ }
53694
53832
  }
53833
+ renderScene.destroy();
53695
53834
  }
53696
53835
  });
53697
53836
  }
53698
- function setupThumbnailScene(renderScene) {
53699
- renderScene.shouldAnimate = false;
53700
- renderScene.wellLitDirectionalLightIntensity *= 2;
53701
- renderScene.shadowEnabled = false;
53702
- RBXRenderer.setupScene("Thumbnail", 16777215, renderScene);
53703
- if (renderScene.plane) renderScene.scene.remove(renderScene.plane);
53704
- if (renderScene.shadowPlane) renderScene.scene.remove(renderScene.shadowPlane);
53705
- renderScene.scene.background = null;
53706
- }
53707
53837
  function exposeAPI() {
53708
53838
  globalThis.API = API;
53709
53839
  globalThis.APICACHE = CACHE;
@@ -53721,6 +53851,79 @@ function exposeThumbnailGenerator() {
53721
53851
  globalThis.setupThumbnailScene = setupThumbnailScene;
53722
53852
  globalThis.getThumbnailCameraCFrame = getThumbnailCameraCFrame;
53723
53853
  }
53854
+ function getHeadExtents(rig) {
53855
+ const head = rig.FindFirstChild("Head");
53856
+ if (!head) return;
53857
+ const headParts = [];
53858
+ for (const child of rig.GetDescendants()) {
53859
+ if (child === head) {
53860
+ headParts.push(head);
53861
+ } else {
53862
+ const weld = child.FindFirstChildOfClass("Weld");
53863
+ if (weld && child.parent && child.parent.className === "Accessory") {
53864
+ if (weld.Prop("Part0") === head || weld.Prop("Part1") === head) {
53865
+ headParts.push(child);
53866
+ }
53867
+ }
53868
+ }
53869
+ }
53870
+ const extents = getExtents(head.Prop("CFrame"), headParts);
53871
+ return extents;
53872
+ }
53873
+ function getCameraCFrameForHeadshotCustomized(rig, fov2, yRot, distance2) {
53874
+ const head = rig.FindFirstChild("Head");
53875
+ if (!head) return;
53876
+ const headCF = head.PropOrDefault("CFrame", new CFrame());
53877
+ const headLocalExtents = getHeadExtents(rig);
53878
+ if (!headLocalExtents) return;
53879
+ const headCenterPosLocal = headLocalExtents[0].add(headLocalExtents[1].minus(headLocalExtents[0]).divide(new Vector32(2, 2, 2)));
53880
+ const headCenterPos = new Vector32().fromVec3(headCF.multiply(new CFrame(...headCenterPosLocal.toVec3())).Position);
53881
+ const headCenterCF = new CFrame(...headCenterPos.toVec3());
53882
+ let lookVector = headCF.lookVector();
53883
+ if (Math.abs(lookVector[1]) > 0.95) {
53884
+ lookVector = [0, 0, -1];
53885
+ } else {
53886
+ lookVector[1] = 0;
53887
+ lookVector = normalize(lookVector);
53888
+ }
53889
+ let lookCF = CFrame.lookAt([0, 0, 0], lookVector);
53890
+ lookCF = lookCF.multiply(CFrame.fromEulerAngles(0, rad(yRot), 0, "ZXY"));
53891
+ lookVector = lookCF.lookVector();
53892
+ const fovMultiplier = 70 / fov2;
53893
+ lookCF.Position = add(headCenterCF.Position, multiply(multiply([10, 10, 10], lookVector), [fovMultiplier, fovMultiplier, fovMultiplier]));
53894
+ lookCF = CFrame.lookAt(lookCF.Position, headCenterCF.Position);
53895
+ const cameraCF = lookCF.clone();
53896
+ zoomExtents(cameraCF, headCenterCF, headLocalExtents[1].minus(headLocalExtents[0]), fov2, distance2, "largestAxis");
53897
+ return cameraCF;
53898
+ }
53899
+ function getCameraCFrameForAvatarNonCustomized(rig) {
53900
+ const thumbnailCamera = rig.FindFirstChildOfClass("Camera");
53901
+ if (thumbnailCamera) return thumbnailCamera.PropOrDefault("CFrame", new CFrame());
53902
+ let rootPart = rig.PropOrDefault("PrimaryPart", void 0);
53903
+ if (!rootPart) rootPart = rig.FindFirstChildOfClass("Part");
53904
+ if (!rootPart) rootPart = rig.FindFirstChildOfClass("MeshPart");
53905
+ if (!rootPart) return;
53906
+ const rootPartCF = rootPart.PropOrDefault("CFrame", new CFrame()).clone();
53907
+ const worldExtents = getExtentsWorld(rig);
53908
+ if (!worldExtents) return;
53909
+ const extentsSize = worldExtents[1].minus(worldExtents[0]);
53910
+ rootPartCF.Position = getExtentsCenter(worldExtents).toVec3();
53911
+ let lookVector = rootPartCF.lookVector();
53912
+ if (Math.abs(lookVector[1]) > 0.95) {
53913
+ lookVector = [0, 0, -1];
53914
+ } else {
53915
+ lookVector[1] = 0;
53916
+ lookVector = normalize(lookVector);
53917
+ }
53918
+ let lookCF = CFrame.lookAt([0, 0, 0], lookVector);
53919
+ lookCF = lookCF.multiply(CFrame.fromEulerAngles(25 * Math.PI / 180, 27.5 * Math.PI / 180, 0, "ZXY"));
53920
+ lookVector = lookCF.lookVector();
53921
+ lookCF.Position = add(rootPartCF.Position, multiply([10, 10, 10], lookVector));
53922
+ lookCF = CFrame.lookAt(lookCF.Position, rootPartCF.Position);
53923
+ const cameraCF = lookCF.clone();
53924
+ zoomExtents(cameraCF, rootPartCF, extentsSize, 70, 1);
53925
+ return cameraCF;
53926
+ }
53724
53927
  export {
53725
53928
  API,
53726
53929
  AbbreviationToFaceControlProperty,
@@ -53931,6 +54134,7 @@ export {
53931
54134
  getExtents,
53932
54135
  getExtentsCenter,
53933
54136
  getExtentsForParts,
54137
+ getExtentsWorld,
53934
54138
  getHeadExtents,
53935
54139
  getOffsetArray,
53936
54140
  getOriginalAttachmentOrientation,
@@ -53939,7 +54143,6 @@ export {
53939
54143
  getPartAssemblyScore,
53940
54144
  getPartsInAssembly_Generate,
53941
54145
  getRandomBetweenInclusive,
53942
- getRigExtentsWorld,
53943
54146
  getRootAssemblyPart_Generate,
53944
54147
  getThumbnailCameraCFrame,
53945
54148
  getUVtoIndexMap,
@@ -53952,6 +54155,7 @@ export {
53952
54155
  hashVec3Safe,
53953
54156
  hexToColor3,
53954
54157
  hexToRgb,
54158
+ imageThumbnailClick,
53955
54159
  imageUrlToDataUrl,
53956
54160
  inheritSkeleton,
53957
54161
  inheritUV,
@@ -53972,6 +54176,7 @@ export {
53972
54176
  mergeTargetWithReference,
53973
54177
  minimumDeltaEBodyColorDifference,
53974
54178
  minus,
54179
+ modelThumbnailClick,
53975
54180
  mount,
53976
54181
  mountElement,
53977
54182
  multiply,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roavatar-renderer",
3
- "version": "1.5.18",
3
+ "version": "1.5.19",
4
4
  "description": "A renderer for Roblox avatars, used by the RoAvatar extension.",
5
5
  "author": "steinan",
6
6
  "type": "module",