roavatar-renderer 1.3.5 → 1.4.0

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.
package/dist/index.d.ts CHANGED
@@ -97,7 +97,7 @@ export declare class AnimationConstraintWrapper extends InstanceWrapper {
97
97
  setup(): void;
98
98
  }
99
99
 
100
- export declare type AnimationProp = "ClimbAnimation" | "FallAnimation" | "IdleAnimation" | "JumpAnimation" | "MoodAnimation" | "RunAnimation" | "SwimAnimation" | "WalkAnimation" | "dance1" | "dance2" | "dance3" | "toolnone";
100
+ export declare type AnimationProp = "ClimbAnimation" | "FallAnimation" | "IdleAnimation" | "JumpAnimation" | "MoodAnimation" | "RunAnimation" | "SwimAnimation" | "WalkAnimation" | "dance1" | "dance2" | "dance3" | "toolnone" | "pose";
101
101
 
102
102
  export declare const AnimationPropToName: {
103
103
  [K in AnimationProp]: string;
@@ -309,6 +309,9 @@ export declare const API: {
309
309
  PremiumFeatures: {
310
310
  GetSubscription: (userId: number) => Promise<Response | GetSubscription_Result>;
311
311
  };
312
+ Subscriptions: {
313
+ HasPlus: () => Promise<Response | boolean>;
314
+ };
312
315
  RBLXGet: typeof RBLXGet;
313
316
  RBLXPost: typeof RBLXPost;
314
317
  RBLXDelete: typeof RBLXDelete;
@@ -418,6 +421,13 @@ export declare const AssetTypeToMakeupType: {
418
421
  EyeMakeup: number;
419
422
  };
420
423
 
424
+ export declare class AttachmentWrapper extends InstanceWrapper {
425
+ static className: string;
426
+ static requiredProperties: string[];
427
+ setup(): void;
428
+ getWorldCFrame(): CFrame;
429
+ }
430
+
421
431
  export declare class Authentication {
422
432
  TOKEN?: string;
423
433
  SessionUUID?: string;
@@ -810,6 +820,9 @@ declare class COREMESH {
810
820
  getColors(): Uint8Array;
811
821
  getIndices(): Uint16Array;
812
822
  getTouchingVerts(index: number): number[];
823
+ getEdgeId(v0: number, v1: number): bigint;
824
+ getFaceEdges(i: number): [bigint, bigint, bigint];
825
+ getEdgeCounts(): Map<bigint, number>;
813
826
  }
814
827
 
815
828
  export declare function createContentMap(): void;
@@ -893,6 +906,8 @@ declare class DisposableDesc {
893
906
  dispose(_renderer: THREE.WebGLRenderer, _scene: THREE.Scene): void;
894
907
  }
895
908
 
909
+ export declare function disposeMesh(scene: THREE.Scene, mesh: THREE.Mesh): void;
910
+
896
911
  export declare function distance(v0: Vec3, v1: Vec3): number;
897
912
 
898
913
  export declare function divide(v0: Vec3, v1: Vec3): Vec3;
@@ -914,8 +929,12 @@ export { Event_2 as Event }
914
929
 
915
930
  export declare function exposeAPI(): void;
916
931
 
932
+ export declare function exposeFLAGS(): void;
933
+
917
934
  export declare function exposeMesh(): void;
918
935
 
936
+ export declare function exposeThumbnailGenerator(): void;
937
+
919
938
  export declare const FaceControlNames: string[];
920
939
 
921
940
  export declare class FaceControlsWrapper extends InstanceWrapper {
@@ -1012,6 +1031,8 @@ export declare class FileMeshSubset {
1012
1031
 
1013
1032
  export declare function fileMeshToTHREEGeometry(mesh: FileMesh, canIncludeSkinning?: boolean, forceVertexColor?: Vector3): THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>;
1014
1033
 
1034
+ export declare function FindFirstMatchingAttachment(attachmentName: string, rig: Instance): Instance | null;
1035
+
1015
1036
  export declare const FLAGS: {
1016
1037
  HAIR_IS_BODYPART: boolean;
1017
1038
  AVATAR_JOINT_UPGRADE: boolean;
@@ -1044,6 +1065,9 @@ export declare const FLAGS: {
1044
1065
  AUDIO_ENABLED: boolean;
1045
1066
  LEGACY_WELD_BEHAVIOR: boolean;
1046
1067
  USE_RENDERTARGET: boolean;
1068
+ AUTO_RESTORE_CONTEXT: boolean;
1069
+ RENDERTARGET_TO_CANVASTEXTURE: boolean;
1070
+ THUMBNAIL_TIMEOUT: number;
1047
1071
  SHOW_SKELETON_HELPER: boolean;
1048
1072
  UPDATE_SKELETON: boolean;
1049
1073
  ANIMATE_SKELETON: boolean;
@@ -1084,16 +1108,26 @@ export declare const FullBodyColors: string[];
1084
1108
 
1085
1109
  export declare function gaussian_rbf(v0: Vec3, v1: Vec3, sigma?: number): number;
1086
1110
 
1111
+ export declare function generateModelThumbnail(auth: Authentication, renderScene: RBXRendererScene, model: Instance, size?: Vec2, type?: ThumbnailType, quality?: number, gltfAutoDownload?: boolean): Promise<ThumbnailResult>;
1112
+
1113
+ export declare function generateOutfitThumbnail(auth: Authentication, outfit: Outfit, size?: Vec2, type?: ThumbnailType, quality?: number, gltfAutoDownload?: boolean): Promise<ThumbnailResult>;
1114
+
1087
1115
  export declare function generateUUIDv4(): string;
1088
1116
 
1089
1117
  export declare function GetAttachedPart(accessory: Instance, rig: Instance): Instance | undefined;
1090
1118
 
1119
+ export declare function getCameraCFrameForAvatarNonCustomized(rig: Instance): CFrame | undefined;
1120
+
1091
1121
  export declare function getCameraCFrameForHeadshotCustomized(rig: Instance, fov: number, yRot: number, distance: number): CFrame | undefined;
1092
1122
 
1123
+ export declare function getCameraOffset(fov: number, extentsSize: Vector3): number;
1124
+
1093
1125
  export declare function getDistIndexArray(ref: FileMesh, dist: FileMesh): (number | undefined)[];
1094
1126
 
1095
1127
  export declare function getExtents(cframe: CFrame, parts: Instance[]): [Vector3, Vector3];
1096
1128
 
1129
+ export declare function getExtentsCenter(extents: [Vector3, Vector3]): Vector3;
1130
+
1097
1131
  /**@deprecated this is SO broken */
1098
1132
  export declare function getExtentsForParts(parts: Instance[], includeTransform?: boolean): [Vector3, Vector3];
1099
1133
 
@@ -1112,10 +1146,16 @@ export declare interface GetInfoForId_Result {
1112
1146
 
1113
1147
  export declare function getOffsetArray(inner: FileMesh, outer: FileMesh): ([Vec3, THREE.Quaternion, number] | undefined)[];
1114
1148
 
1149
+ export declare function getOriginalAttachmentOrientation(attachment: Instance): Vector3;
1150
+
1151
+ export declare function getOriginalAttachmentPosition(attachment: Instance): Vector3;
1152
+
1115
1153
  export declare function getOriginalSize(part: Instance): Vector3;
1116
1154
 
1117
1155
  export declare function getRandomBetweenInclusive(min: number, max: number): number;
1118
1156
 
1157
+ export declare function getRigExtentsWorld(rig: Instance): [Vector3, Vector3];
1158
+
1119
1159
  export declare interface GetSubscription_Result {
1120
1160
  "subscriptionProductModel": {
1121
1161
  "premiumFeatureId": number;
@@ -2195,7 +2235,8 @@ export declare class RBXRenderer {
2195
2235
  /**Creates canvasContainer */
2196
2236
  static createContainer(): void;
2197
2237
  /**Sets up the THREE.js renderer */
2198
- static create(): void;
2238
+ static create(canvas?: HTMLCanvasElement): void;
2239
+ static setupLostContextHandler(): void;
2199
2240
  /**Sets up a basic scene with lighting
2200
2241
  * @param lightingType "WellLit" is the default lighting for RoAvatar, "Thumbnail" tries to match the Roblox thumbnail lighting
2201
2242
  * @param backgroundColorHex is the hex code for the background color, for example 0x2b2d33
@@ -2252,6 +2293,8 @@ export declare class RBXRendererScene {
2252
2293
  scene: THREE.Scene;
2253
2294
  camera: THREE.PerspectiveCamera;
2254
2295
  controls: OrbitControls | undefined;
2296
+ shouldAnimate: boolean;
2297
+ destroyed: boolean;
2255
2298
  effectComposer: EffectComposer | undefined;
2256
2299
  scissor?: [number, number, number, number];
2257
2300
  viewport?: [number, number, number, number];
@@ -2272,6 +2315,10 @@ export declare class RBXRendererScene {
2272
2315
  directionalLight2?: THREE.DirectionalLight;
2273
2316
  setRect(bounds: DOMRect): void;
2274
2317
  noRect(): void;
2318
+ destroy(): void;
2319
+ exportGLTF(name?: string, autoDownload?: boolean): Promise<ArrayBuffer | {
2320
+ [key: string]: unknown;
2321
+ }>;
2275
2322
  }
2276
2323
 
2277
2324
  declare class RBXSimpleView {
@@ -2501,6 +2548,8 @@ export declare interface Search_Result {
2501
2548
  }[];
2502
2549
  }
2503
2550
 
2551
+ export declare function setupThumbnailScene(renderScene: RBXRendererScene): void;
2552
+
2504
2553
  declare class SimpleView {
2505
2554
  view: DataView;
2506
2555
  viewOffset: number;
@@ -2604,6 +2653,10 @@ export declare interface ThumbnailCustomizations_Result {
2604
2653
  }[];
2605
2654
  }
2606
2655
 
2656
+ export declare type ThumbnailResult = ArrayBuffer | {
2657
+ [key: string]: unknown;
2658
+ } | string | undefined;
2659
+
2607
2660
  export declare interface ThumbnailsCustomization_Payload {
2608
2661
  thumbnailType: number;
2609
2662
  emoteAssetId: number;
@@ -2614,6 +2667,8 @@ export declare interface ThumbnailsCustomization_Payload {
2614
2667
  };
2615
2668
  }
2616
2669
 
2670
+ export declare type ThumbnailType = "png" | "webp" | "gltf";
2671
+
2617
2672
  export declare class ToolWrapper extends InstanceWrapper {
2618
2673
  static className: string;
2619
2674
  static requiredProperties: string[];
@@ -2801,4 +2856,6 @@ export declare const xmlMagic = "<roblox ";
2801
2856
 
2802
2857
  export declare function zoomExtents(cameraCFrame: CFrame, modelCFrame: CFrame, modelSize: Vector3, targetFOV: number, distanceScale: number): void;
2803
2858
 
2859
+ export declare function zoomToExtents(cameraCFrame: CFrame, modelCFrame: CFrame, modelSize: Vector3, fov?: number): void;
2860
+
2804
2861
  export { }
package/dist/index.js CHANGED
@@ -27277,7 +27277,8 @@ const AnimationPropToName = {
27277
27277
  "dance1": "dance1",
27278
27278
  "dance2": "dance2",
27279
27279
  "dance3": "dance3",
27280
- "toolnone": "toolnone"
27280
+ "toolnone": "toolnone",
27281
+ "pose": "pose"
27281
27282
  };
27282
27283
  const DefaultAnimations = {
27283
27284
  "ClimbAnimation": ["climb", [["ClimbAnim", 507765644n]]],
@@ -27292,7 +27293,8 @@ const DefaultAnimations = {
27292
27293
  "dance1": ["dance1", [["2", 507772104n]]],
27293
27294
  "dance2": ["dance2", [["2", 507776879n]]],
27294
27295
  "dance3": ["dance3", [["2", 507777623n]]],
27295
- "toolnone": ["toolnone", [["ToolNoneAnim", 507768375n]]]
27296
+ "toolnone": ["toolnone", [["ToolNoneAnim", 507768375n]]],
27297
+ "pose": ["pose", [["pose", 11600209531n]]]
27296
27298
  };
27297
27299
  const DefaultAnimationsR6 = {
27298
27300
  "ClimbAnimation": ["climb", [["ClimbAnim", 180436334n]]],
@@ -27307,7 +27309,8 @@ const DefaultAnimationsR6 = {
27307
27309
  "dance1": ["dance1", [["2", 182491065n]]],
27308
27310
  "dance2": ["dance2", [["2", 182491277n]]],
27309
27311
  "dance3": ["dance3", [["2", 182491423n]]],
27310
- "toolnone": ["toolnone", [["ToolNoneAnim", 182393478n]]]
27312
+ "toolnone": ["toolnone", [["ToolNoneAnim", 182393478n]]],
27313
+ "pose": ["pose", []]
27311
27314
  };
27312
27315
  const animNamesR6 = {
27313
27316
  idle: [
@@ -27410,6 +27413,9 @@ const animNamesR15 = {
27410
27413
  ],
27411
27414
  mood: [
27412
27415
  { id: "http://www.roblox.com/asset/?id=14366558676", weight: 10 }
27416
+ ],
27417
+ pose: [
27418
+ { id: "http://www.roblox.com/asset/?id=11600209531", weight: 10 }
27413
27419
  ]
27414
27420
  /*wave: [
27415
27421
  { id: "http://www.roblox.com/asset/?id=507770239", weight: 10 }
@@ -28490,7 +28496,7 @@ function hashVec3Safe(a, b, c) {
28490
28496
  a = BigInt(a);
28491
28497
  b = BigInt(b);
28492
28498
  c = BigInt(c);
28493
- return a * 100n + b * 10n + c * 1n;
28499
+ return a * 10000000n + b * 1000n + c * 1n;
28494
28500
  }
28495
28501
  function calculateMagnitude3D(x, y, z) {
28496
28502
  return Math.sqrt(x * x + y * y + z * z);
@@ -28663,6 +28669,7 @@ function buildVertKD(mesh) {
28663
28669
  function inheritUV(to, from) {
28664
28670
  const meshCollider = new MeshCollider(to);
28665
28671
  const faceKD = buildFaceKD(from);
28672
+ const edgeCountMap = from.coreMesh.getEdgeCounts();
28666
28673
  for (let i = 0; i < to.coreMesh.numverts; i++) {
28667
28674
  const pos = to.coreMesh.getPos(i);
28668
28675
  const closest = nearestSearch(faceKD, pos);
@@ -28671,6 +28678,7 @@ function inheritUV(to, from) {
28671
28678
  const va = from.coreMesh.getUV(face[0]);
28672
28679
  const vb = from.coreMesh.getUV(face[1]);
28673
28680
  const vc = from.coreMesh.getUV(face[2]);
28681
+ let newAlpha = 255;
28674
28682
  const triangle = from.coreMesh.getTriangle(closestI);
28675
28683
  const closestPointPos = closestPointTriangle(pos, triangle);
28676
28684
  const barycentricPos = barycentric(closestPointPos, triangle);
@@ -28678,15 +28686,27 @@ function inheritUV(to, from) {
28678
28686
  barycentricPos[0] * va[0] + barycentricPos[1] * vb[0] + barycentricPos[2] * vc[0],
28679
28687
  barycentricPos[0] * va[1] + barycentricPos[1] * vb[1] + barycentricPos[2] * vc[1]
28680
28688
  ];
28689
+ if (barycentricPos[0] <= 0.1) {
28690
+ const edgeId = from.coreMesh.getEdgeId(face[1], face[2]);
28691
+ const edgeCount = edgeCountMap.get(edgeId) || 999;
28692
+ if (edgeCount <= 1) newAlpha = 0;
28693
+ } else if (barycentricPos[1] <= 0.1) {
28694
+ const edgeId = from.coreMesh.getEdgeId(face[0], face[2]);
28695
+ const edgeCount = edgeCountMap.get(edgeId) || 999;
28696
+ if (edgeCount <= 1) newAlpha = 0;
28697
+ } else if (barycentricPos[2] <= 0.1) {
28698
+ const edgeId = from.coreMesh.getEdgeId(face[0], face[1]);
28699
+ const edgeCount = edgeCountMap.get(edgeId) || 999;
28700
+ if (edgeCount <= 1) newAlpha = 0;
28701
+ }
28681
28702
  const ray = new Ray$1(pos, closestPointPos);
28682
28703
  if (meshCollider.raycast(ray)) {
28683
- newUV[0] = -Infinity;
28684
- newUV[1] = -Infinity;
28704
+ newAlpha = 0;
28685
28705
  }
28686
28706
  if (magnitude(minus(closestPointPos, pos)) > 0.1) {
28687
- newUV[0] = -Infinity;
28688
- newUV[1] = -Infinity;
28707
+ newAlpha = 0;
28689
28708
  }
28709
+ to.coreMesh.setColor(i, [255, 255, 255, newAlpha]);
28690
28710
  to.coreMesh.setUV(i, newUV);
28691
28711
  }
28692
28712
  }
@@ -29505,7 +29525,7 @@ const FLAGS = {
29505
29525
  //only used by linear algorithms
29506
29526
  LAYERED_CLOTHING_ALGORITHM: "rbf",
29507
29527
  SHOW_CAGE: false,
29508
- LAYERED_CLOTHING_COOLDOWN: 0.6,
29528
+ LAYERED_CLOTHING_COOLDOWN: 0.25,
29509
29529
  GET_WORKER_FUNC: DefaultGetWorkerFunc,
29510
29530
  RBF_PATCH_COUNT: 300,
29511
29531
  //amount of "patches" that are used for layered clothing, multiple verts share the same patch
@@ -29523,6 +29543,9 @@ const FLAGS = {
29523
29543
  AUDIO_ENABLED: true,
29524
29544
  LEGACY_WELD_BEHAVIOR: false,
29525
29545
  USE_RENDERTARGET: true,
29546
+ AUTO_RESTORE_CONTEXT: true,
29547
+ RENDERTARGET_TO_CANVASTEXTURE: false,
29548
+ THUMBNAIL_TIMEOUT: 750,
29526
29549
  //skeleton
29527
29550
  SHOW_SKELETON_HELPER: false,
29528
29551
  UPDATE_SKELETON: true,
@@ -33961,6 +33984,36 @@ class COREMESH {
33961
33984
  }
33962
33985
  return touchingVerts;
33963
33986
  }
33987
+ getEdgeId(v0, v1) {
33988
+ const [x0, y0, z0] = this.getPos(v0);
33989
+ const [x1, y1, z1] = this.getPos(v1);
33990
+ const v0h = hashVec3Safe(Math.round(x0 * 1e3), Math.round(y0 * 1e3), Math.round(z0 * 1e3));
33991
+ const v1h = hashVec3Safe(Math.round(x1 * 1e3), Math.round(y1 * 1e3), Math.round(z1 * 1e3));
33992
+ const tv0 = v0h < v1h ? v0h : v1h;
33993
+ const tv1 = v0h > v1h ? v0h : v1h;
33994
+ return tv1 * 10000000n + tv0;
33995
+ }
33996
+ getFaceEdges(i) {
33997
+ const face = this.getFace(i);
33998
+ const edge0 = this.getEdgeId(face[0], face[1]);
33999
+ const edge1 = this.getEdgeId(face[1], face[2]);
34000
+ const edge2 = this.getEdgeId(face[2], face[0]);
34001
+ return [edge0, edge1, edge2];
34002
+ }
34003
+ getEdgeCounts() {
34004
+ const edgeCountMap = /* @__PURE__ */ new Map();
34005
+ for (let i = 0; i < this.numfaces; i++) {
34006
+ const edges = this.getFaceEdges(i);
34007
+ for (const edge of edges) {
34008
+ if (!edgeCountMap.has(edge)) {
34009
+ edgeCountMap.set(edge, 1);
34010
+ } else {
34011
+ edgeCountMap.set(edge, edgeCountMap.get(edge) + 1);
34012
+ }
34013
+ }
34014
+ }
34015
+ return edgeCountMap;
34016
+ }
33964
34017
  }
33965
34018
  class LODS {
33966
34019
  lodType = LodType.Unknown;
@@ -36095,6 +36148,15 @@ const API = {
36095
36148
  return await response.json();
36096
36149
  }
36097
36150
  },
36151
+ "Subscriptions": {
36152
+ HasPlus: async function() {
36153
+ const response = await RBLXGet("https://apis.roblox.com/subscriptions/v2/user/subscriptions?ProductType=Blackbird&ResultsPerPage=1");
36154
+ if (response.status !== 200) {
36155
+ return response;
36156
+ }
36157
+ return (await response.json()).subscriptions.length > 0;
36158
+ }
36159
+ },
36098
36160
  "RBLXGet": RBLXGet,
36099
36161
  "RBLXPost": RBLXPost,
36100
36162
  "RBLXDelete": RBLXDelete,
@@ -42316,27 +42378,7 @@ class MeshDesc {
42316
42378
  }
42317
42379
  return true;
42318
42380
  }
42319
- async compileMesh() {
42320
- if (!this.mesh) {
42321
- return void 0;
42322
- }
42323
- const meshToLoad = this.mesh;
42324
- const mesh = await API.Asset.GetMesh(meshToLoad, void 0);
42325
- if (mesh instanceof Response) {
42326
- warn(true, "Failed to get mesh for compileMesh", mesh);
42327
- return mesh;
42328
- }
42329
- if (!mesh.facs && this.headMesh && mesh.skinning.skinnings.length > 0) {
42330
- const headMesh = await API.Asset.GetMesh(this.headMesh, void 0, true);
42331
- if (headMesh instanceof Response) {
42332
- warn(true, "Failed to get headMesh for compileMesh", headMesh);
42333
- return headMesh;
42334
- }
42335
- if (headMesh.facs) {
42336
- mesh.facs = headMesh.facs.clone();
42337
- }
42338
- }
42339
- let the_ref_mesh = void 0;
42381
+ async wrapDeformer(mesh) {
42340
42382
  if (this.deformerDesc) {
42341
42383
  const meshMap = /* @__PURE__ */ new Map();
42342
42384
  const meshPromises = [];
@@ -42365,6 +42407,9 @@ class MeshDesc {
42365
42407
  await targetDeformer.solveAsync();
42366
42408
  targetDeformer.deformMesh();
42367
42409
  }
42410
+ }
42411
+ async wrapLayer(mesh) {
42412
+ let the_ref_mesh = void 0;
42368
42413
  if (this.layerDesc && this.modelLayersDesc && this.modelLayersDesc.targetCages && this.modelLayersDesc.targetCages.length > 0 && this.modelLayersDesc.targetCFrames && this.modelLayersDesc.targetSizes && this.modelLayersDesc.layers) {
42369
42414
  const meshMap = /* @__PURE__ */ new Map();
42370
42415
  const meshPromises = [];
@@ -42502,8 +42547,11 @@ class MeshDesc {
42502
42547
  offsetMesh(mesh, totalOffset);
42503
42548
  }
42504
42549
  if (!FLAGS.SHOW_CAGE) the_ref_mesh = void 0;
42505
- if (FLAGS.HIDE_LAYERED_CLOTHING) return;
42550
+ if (FLAGS.HIDE_LAYERED_CLOTHING) return the_ref_mesh;
42506
42551
  }
42552
+ return the_ref_mesh;
42553
+ }
42554
+ async wrapTarget(mesh) {
42507
42555
  if (this.target && this.targetOrigin && this.hsrDesc) {
42508
42556
  const meshMap = /* @__PURE__ */ new Map();
42509
42557
  const meshPromises = [];
@@ -42536,6 +42584,8 @@ class MeshDesc {
42536
42584
  doHSR(totalUvToHits, targetCage, mesh, true, `${this.target}-${this.mesh}`);
42537
42585
  }
42538
42586
  }
42587
+ }
42588
+ async wrapTextureTransfer(mesh) {
42539
42589
  if (this.wrapTextureTarget && this.wrapTextureTargetOrigin && this.wrapTextureMinBound && this.wrapTextureMaxBound) {
42540
42590
  const meshMap = /* @__PURE__ */ new Map();
42541
42591
  const meshPromises = [];
@@ -42565,11 +42615,38 @@ class MeshDesc {
42565
42615
  }
42566
42616
  for (let i = mesh.coreMesh.numverts - 1; i >= 0; i--) {
42567
42617
  const vertUV = mesh.coreMesh.getUV(i);
42568
- if (vertUV[0] < -0.05 || vertUV[0] > 1.05 || vertUV[1] < -0.05 || vertUV[1] > 1.05) {
42569
- mesh.coreMesh.setUV(i, [-Infinity, -Infinity]);
42618
+ if (vertUV[0] < 0.05 || vertUV[0] > 0.95 || vertUV[1] < 0.05 || vertUV[1] > 0.95) {
42619
+ mesh.coreMesh.setColor(i, [255, 255, 255, 0]);
42570
42620
  }
42571
42621
  }
42572
42622
  }
42623
+ }
42624
+ async compileMesh() {
42625
+ if (!this.mesh) return;
42626
+ const meshToLoad = this.mesh;
42627
+ const mesh = await API.Asset.GetMesh(meshToLoad, void 0);
42628
+ if (mesh instanceof Response) {
42629
+ warn(true, "Failed to get mesh for compileMesh", mesh);
42630
+ return mesh;
42631
+ }
42632
+ if (!mesh.facs && this.headMesh && mesh.skinning.skinnings.length > 0) {
42633
+ const headMesh = await API.Asset.GetMesh(this.headMesh, void 0, true);
42634
+ if (headMesh instanceof Response) {
42635
+ warn(true, "Failed to get headMesh for compileMesh", headMesh);
42636
+ return headMesh;
42637
+ }
42638
+ if (headMesh.facs) {
42639
+ mesh.facs = headMesh.facs.clone();
42640
+ }
42641
+ }
42642
+ const wrapDeformerResult = await this.wrapDeformer(mesh);
42643
+ if (wrapDeformerResult instanceof Response) return wrapDeformerResult;
42644
+ const the_ref_mesh = await this.wrapLayer(mesh);
42645
+ if (the_ref_mesh instanceof Response) return the_ref_mesh;
42646
+ const wrapTargetResult = await this.wrapTarget(mesh);
42647
+ if (wrapTargetResult instanceof Response) return wrapTargetResult;
42648
+ const wrapTextureTransferResult = await this.wrapTextureTransfer(mesh);
42649
+ if (wrapTextureTransferResult instanceof Response) return wrapTextureTransferResult;
42573
42650
  this.fileMesh = mesh;
42574
42651
  const geometry = fileMeshToTHREEGeometry(the_ref_mesh || mesh, this.canHaveSkinning, this.forceVertexColor);
42575
42652
  let threeMesh = void 0;
@@ -42609,6 +42686,9 @@ class MeshDesc {
42609
42686
  break;
42610
42687
  }
42611
42688
  }
42689
+ if (child.className === "Decal") {
42690
+ this.forceVertexColor = void 0;
42691
+ }
42612
42692
  }
42613
42693
  fromPart(child) {
42614
42694
  this.canHaveSkinning = false;
@@ -43125,6 +43205,26 @@ function fastMask(mask, image) {
43125
43205
  ctx.drawImage(image, 0, 0, mask.width, mask.height);
43126
43206
  return canvas;
43127
43207
  }
43208
+ function imageDataToCanvas(data, width, height) {
43209
+ const offscreenCanvas = new OffscreenCanvas(width, height);
43210
+ const offscreenCtx = offscreenCanvas.getContext("2d");
43211
+ const canvas = document.createElement("canvas");
43212
+ const ctx = canvas.getContext("2d");
43213
+ canvas.width = width;
43214
+ canvas.height = height;
43215
+ if (!ctx || !offscreenCtx) {
43216
+ throw new Error("Failed to get CanvasContext");
43217
+ }
43218
+ const imgData = new ImageData(new Uint8ClampedArray(data.buffer), width, height);
43219
+ offscreenCtx.putImageData(imgData, 0, 0);
43220
+ ctx.translate(0, height);
43221
+ ctx.scale(1, -1);
43222
+ ctx.drawImage(offscreenCanvas, 0, 0);
43223
+ return canvas;
43224
+ }
43225
+ function imageDataToCanvasTexture(data, width, height) {
43226
+ return new CanvasTexture(imageDataToCanvas(data, width, height));
43227
+ }
43128
43228
  class ColorLayer {
43129
43229
  color;
43130
43230
  bodyPart;
@@ -43169,9 +43269,11 @@ class MaterialDesc {
43169
43269
  bodyPart;
43170
43270
  //should only be accounted for if uvType != Normal in TextureLayer
43171
43271
  avatarType;
43272
+ dirty = false;
43172
43273
  //result
43173
43274
  createdTextures = [];
43174
43275
  isSame(other) {
43276
+ if (this.dirty || other.dirty) return false;
43175
43277
  const propertiesSame = this.isDecal === other.isDecal && this.transparent === other.transparent && Math.round(this.transparency * 100) === Math.round(other.transparency * 100) && this.doubleSided === other.doubleSided && this.visible === other.visible;
43176
43278
  let layersSame = true;
43177
43279
  if (this.layers.length !== other.layers.length) {
@@ -43473,7 +43575,7 @@ class MaterialDesc {
43473
43575
  }
43474
43576
  let hasTransparency = false;
43475
43577
  const rbxRenderer = RBXRenderer.getRenderer();
43476
- if (!hasColorLayer && rbxRenderer) {
43578
+ if (!hasColorLayer && rbxRenderer || FLAGS.RENDERTARGET_TO_CANVASTEXTURE && rbxRenderer) {
43477
43579
  const data = new Uint8Array(width * height * 4);
43478
43580
  await rbxRenderer.readRenderTargetPixelsAsync(renderTarget, 0, 0, width, height, data);
43479
43581
  for (let i = 3; i < data.length; i += 4) {
@@ -43482,6 +43584,15 @@ class MaterialDesc {
43482
43584
  break;
43483
43585
  }
43484
43586
  }
43587
+ if (FLAGS.RENDERTARGET_TO_CANVASTEXTURE) {
43588
+ const ogTexture = texture;
43589
+ texture = imageDataToCanvasTexture(data, width, height);
43590
+ texture.colorSpace = textureType === "color" ? SRGBColorSpace : NoColorSpace;
43591
+ texture.wrapS = ogTexture.wrapS;
43592
+ texture.wrapT = ogTexture.wrapT;
43593
+ texture.minFilter = ogTexture.minFilter;
43594
+ texture.magFilter = ogTexture.magFilter;
43595
+ }
43485
43596
  }
43486
43597
  if (!this.transparent) {
43487
43598
  hasTransparency = false;
@@ -43534,7 +43645,7 @@ class MaterialDesc {
43534
43645
  }
43535
43646
  }
43536
43647
  let hasTransparency = false;
43537
- if (this.transparent) {
43648
+ if (this.transparent || FLAGS.RENDERTARGET_TO_CANVASTEXTURE) {
43538
43649
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
43539
43650
  const data = imageData.data;
43540
43651
  for (let i = 3; i < data.length; i += 4) {
@@ -43549,7 +43660,32 @@ class MaterialDesc {
43549
43660
  texture.needsUpdate = true;
43550
43661
  return [texture, hasTransparency];
43551
43662
  }
43552
- async compileTexture(textureType, meshDesc) {
43663
+ async compileTexture_DirectCompose(textureType) {
43664
+ let textureUrl = void 0;
43665
+ for (const layer of this.layers) {
43666
+ if (layer instanceof TextureLayer) {
43667
+ if (layer[textureType]) {
43668
+ textureUrl = layer[textureType];
43669
+ }
43670
+ }
43671
+ }
43672
+ if (textureUrl) {
43673
+ const image = await API.Generic.LoadImage(textureUrl);
43674
+ if (image) {
43675
+ let hasTransparency = true;
43676
+ if (!this.transparent) {
43677
+ hasTransparency = false;
43678
+ }
43679
+ const texture = new Texture(image);
43680
+ texture.wrapS = RepeatWrapping;
43681
+ texture.wrapT = RepeatWrapping;
43682
+ texture.colorSpace = textureType === "color" ? SRGBColorSpace : NoColorSpace;
43683
+ texture.needsUpdate = true;
43684
+ return [texture, hasTransparency];
43685
+ }
43686
+ }
43687
+ }
43688
+ getComposeType(textureType) {
43553
43689
  let hasSpecialUVType = false;
43554
43690
  let hasColorLayer = false;
43555
43691
  let hasLayerOfType = false;
@@ -43571,33 +43707,23 @@ class MaterialDesc {
43571
43707
  }
43572
43708
  if (!hasLayerOfType) return;
43573
43709
  if ((hasSpecialUVType || this.bodyPart !== void 0) && FLAGS.USE_RENDERTARGET) {
43574
- return this.compileTexture_FullCompose(textureType, meshDesc);
43575
- } else if (this.layers.length > 1 || hasColorLayer && FLAGS.USE_RENDERTARGET) {
43576
- return this.compileTexture_SimpleCompose(textureType);
43710
+ return "full";
43711
+ } else if ((this.layers.length > 1 || hasColorLayer) && FLAGS.USE_RENDERTARGET) {
43712
+ return "simple";
43577
43713
  } else {
43578
- let textureUrl = void 0;
43579
- for (const layer of this.layers) {
43580
- if (layer instanceof TextureLayer) {
43581
- if (layer[textureType]) {
43582
- textureUrl = layer[textureType];
43583
- }
43584
- }
43585
- }
43586
- if (textureUrl) {
43587
- const image = await API.Generic.LoadImage(textureUrl);
43588
- if (image) {
43589
- let hasTransparency = true;
43590
- if (!this.transparent) {
43591
- hasTransparency = false;
43592
- }
43593
- const texture = new Texture(image);
43594
- texture.wrapS = RepeatWrapping;
43595
- texture.wrapT = RepeatWrapping;
43596
- texture.colorSpace = textureType === "color" ? SRGBColorSpace : NoColorSpace;
43597
- texture.needsUpdate = true;
43598
- return [texture, hasTransparency];
43599
- }
43600
- }
43714
+ return "direct";
43715
+ }
43716
+ }
43717
+ async compileTexture(textureType, meshDesc) {
43718
+ const composeType = this.getComposeType(textureType);
43719
+ if (!composeType) return;
43720
+ switch (composeType) {
43721
+ case "full":
43722
+ return this.compileTexture_FullCompose(textureType, meshDesc);
43723
+ case "simple":
43724
+ return this.compileTexture_SimpleCompose(textureType);
43725
+ case "direct":
43726
+ return this.compileTexture_DirectCompose(textureType);
43601
43727
  }
43602
43728
  }
43603
43729
  async compileMaterial(meshDesc) {
@@ -43639,13 +43765,15 @@ class MaterialDesc {
43639
43765
  hasTransparency = true;
43640
43766
  }
43641
43767
  let material = void 0;
43768
+ const textureTemplate = {};
43769
+ if (colorTexture) textureTemplate.map = colorTexture;
43770
+ if (normalTexture) textureTemplate.normalMap = normalTexture;
43771
+ if (roughnessTexture) textureTemplate.roughnessMap = roughnessTexture;
43772
+ if (metalnessTexture) textureTemplate.metalnessMap = metalnessTexture;
43773
+ if (emissiveTexture) textureTemplate.emissiveMap = emissiveTexture;
43642
43774
  if (normalTexture || roughnessTexture || metalnessTexture || emissiveTexture) {
43643
43775
  material = new MeshStandardMaterial({
43644
- map: colorTexture,
43645
- normalMap: normalTexture,
43646
- roughnessMap: roughnessTexture,
43647
- metalnessMap: metalnessTexture,
43648
- emissiveMap: emissiveTexture,
43776
+ ...textureTemplate,
43649
43777
  emissiveIntensity: hasEmissive ? this.emissiveStrength : 0,
43650
43778
  emissive: hasEmissive ? new Color(this.emissiveTint.R, this.emissiveTint.G, this.emissiveTint.B) : new Color(0, 0, 0),
43651
43779
  transparent: hasTransparency,
@@ -43661,7 +43789,7 @@ class MaterialDesc {
43661
43789
  });
43662
43790
  } else {
43663
43791
  material = new MeshPhongMaterial({
43664
- map: colorTexture,
43792
+ ...textureTemplate,
43665
43793
  specular: new Color(1 / 102, 1 / 102, 1 / 102),
43666
43794
  shininess: 9,
43667
43795
  transparent: hasTransparency,
@@ -44761,31 +44889,7 @@ class SkeletonDesc2 {
44761
44889
  }
44762
44890
  class DisposableDesc {
44763
44891
  disposeMesh(scene, mesh) {
44764
- if (mesh.material) {
44765
- const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
44766
- for (const material of materials) {
44767
- for (const key of Object.keys(material)) {
44768
- const value = material[key];
44769
- if (value instanceof Texture) {
44770
- value.dispose();
44771
- }
44772
- }
44773
- if (material instanceof ShaderMaterial) {
44774
- const uniforms = material.uniforms;
44775
- for (const key of Object.keys(uniforms)) {
44776
- const value = uniforms[key].value;
44777
- if (value instanceof Texture) {
44778
- value.dispose();
44779
- }
44780
- }
44781
- }
44782
- material.dispose();
44783
- }
44784
- }
44785
- if (mesh.geometry) {
44786
- mesh.geometry.dispose();
44787
- }
44788
- scene.remove(mesh);
44892
+ disposeMesh(scene, mesh);
44789
44893
  }
44790
44894
  disposeMeshes(scene, meshes) {
44791
44895
  for (const mesh of meshes) {
@@ -47043,6 +47147,26 @@ class AnimatorWrapper extends InstanceWrapper {
47043
47147
  return false;
47044
47148
  }
47045
47149
  }
47150
+ class AttachmentWrapper extends InstanceWrapper {
47151
+ static className = "Attachment";
47152
+ static requiredProperties = [
47153
+ "Name",
47154
+ "CFrame"
47155
+ ];
47156
+ setup() {
47157
+ if (!this.instance.HasProperty("Name")) this.instance.addProperty(new Property("Name", DataType.String), this.instance.className);
47158
+ if (!this.instance.HasProperty("CFrame")) this.instance.addProperty(new Property("CFrame", DataType.CFrame), new CFrame());
47159
+ }
47160
+ getWorldCFrame() {
47161
+ if (this.instance.parent) {
47162
+ if (this.instance.parent.className.includes("Part")) {
47163
+ const parentCF = this.instance.parent.PropOrDefault("CFrame", new CFrame());
47164
+ return parentCF.multiply(this.instance.Prop("CFrame"));
47165
+ }
47166
+ }
47167
+ return this.instance.Prop("CFrame");
47168
+ }
47169
+ }
47046
47170
  class BodyColorsWrapper extends InstanceWrapper {
47047
47171
  static className = "BodyColors";
47048
47172
  static requiredProperties = [
@@ -48592,6 +48716,7 @@ class HumanoidDescriptionWrapper extends InstanceWrapper {
48592
48716
  toChange.push("dance2");
48593
48717
  toChange.push("dance3");
48594
48718
  toChange.push("toolnone");
48719
+ if (this.instance.Prop("IdleAnimation") <= 0) toChange.push("pose");
48595
48720
  }
48596
48721
  miniPromises.push(this._applyAnimations(humanoid, toChange));
48597
48722
  }
@@ -48961,6 +49086,7 @@ function RegisterWrappers() {
48961
49086
  WeldWrapper.register();
48962
49087
  Motor6DWrapper.register();
48963
49088
  ManualWeldWrapper.register();
49089
+ AttachmentWrapper.register();
48964
49090
  AnimationConstraintWrapper.register();
48965
49091
  AnimatorWrapper.register();
48966
49092
  FaceControlsWrapper.register();
@@ -48971,11 +49097,40 @@ function RegisterWrappers() {
48971
49097
  BodyColorsWrapper.register();
48972
49098
  AccessoryWrapper.register();
48973
49099
  }
49100
+ function disposeMesh(scene, mesh) {
49101
+ if (mesh.material) {
49102
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
49103
+ for (const material of materials) {
49104
+ for (const key of Object.keys(material)) {
49105
+ const value = material[key];
49106
+ if (value instanceof Texture) {
49107
+ value.dispose();
49108
+ }
49109
+ }
49110
+ if (material instanceof ShaderMaterial) {
49111
+ const uniforms = material.uniforms;
49112
+ for (const key of Object.keys(uniforms)) {
49113
+ const value = uniforms[key].value;
49114
+ if (value instanceof Texture) {
49115
+ value.dispose();
49116
+ }
49117
+ }
49118
+ }
49119
+ material.dispose();
49120
+ }
49121
+ }
49122
+ if (mesh.geometry) {
49123
+ mesh.geometry.dispose();
49124
+ }
49125
+ scene.remove(mesh);
49126
+ }
48974
49127
  class RBXRendererScene {
48975
49128
  //important scene components
48976
49129
  scene = new Scene();
48977
49130
  camera = new PerspectiveCamera(70, 1 / 1, 0.1, 100);
48978
49131
  controls;
49132
+ shouldAnimate = true;
49133
+ destroyed = false;
48979
49134
  //renderer
48980
49135
  effectComposer;
48981
49136
  //viewport
@@ -49014,6 +49169,39 @@ class RBXRendererScene {
49014
49169
  this.viewport = [0, 0, 0, 0];
49015
49170
  this.scissor = [0, 0, 0, 0];
49016
49171
  }
49172
+ destroy() {
49173
+ if (this.destroyed) return;
49174
+ this.destroyed = true;
49175
+ for (const instance of this.renderDescs.keys()) {
49176
+ RBXRenderer.removeInstance(instance, this);
49177
+ }
49178
+ RBXRenderer.scenes.splice(RBXRenderer.scenes.indexOf(this), 1);
49179
+ if (this.plane) {
49180
+ disposeMesh(this.scene, this.plane);
49181
+ this.plane = void 0;
49182
+ }
49183
+ if (this.shadowPlane) {
49184
+ disposeMesh(this.scene, this.shadowPlane);
49185
+ this.shadowPlane = void 0;
49186
+ }
49187
+ }
49188
+ async exportGLTF(name = "scene", autoDownload = true) {
49189
+ return new Promise((resolve, reject) => {
49190
+ const exporter = new GLTFExporter();
49191
+ exporter.parse(this.scene, (gltf) => {
49192
+ if (autoDownload) {
49193
+ if (gltf instanceof ArrayBuffer) {
49194
+ saveByteArray([gltf], `${name}.glb`);
49195
+ } else {
49196
+ download(`${name}.gltf`, JSON.stringify(gltf));
49197
+ }
49198
+ }
49199
+ resolve(gltf);
49200
+ }, (error2) => {
49201
+ reject(error2);
49202
+ });
49203
+ });
49204
+ }
49017
49205
  }
49018
49206
  class RBXRenderer {
49019
49207
  static orbitControlsTarget = [0, 3, 0];
@@ -49200,8 +49388,8 @@ class RBXRenderer {
49200
49388
  RBXRenderer.canvasContainer.style.height = `${RBXRenderer.resolution[1]}px`;
49201
49389
  }
49202
49390
  /**Sets up the THREE.js renderer */
49203
- static create() {
49204
- RBXRenderer.renderer = new WebGLRenderer({ antialias: true, alpha: true });
49391
+ static create(canvas) {
49392
+ RBXRenderer.renderer = new WebGLRenderer({ antialias: true, alpha: true, canvas });
49205
49393
  RBXRenderer.renderer.setClearColor(new Color(1, 0, 1), 0);
49206
49394
  RBXRenderer.renderer.outputColorSpace = SRGBColorSpace;
49207
49395
  RBXRenderer.renderer.shadowMap.enabled = true;
@@ -49213,12 +49401,45 @@ class RBXRenderer {
49213
49401
  }
49214
49402
  RBXRenderer.renderer.domElement.setAttribute("id", "OutfitInfo-outfit-image-3d");
49215
49403
  RBXRenderer.canvasContainer.appendChild(RBXRenderer.renderer.domElement);
49216
- if (RBXRenderer.createLoadingIcon) {
49404
+ if (RBXRenderer.createLoadingIcon && !RBXRenderer.loadingIcon) {
49217
49405
  RBXRenderer.createLoadingIconHTML();
49218
49406
  }
49219
49407
  if (FLAGS.USE_POST_PROCESSING) {
49220
49408
  RBXRenderer._createEffectComposer();
49221
49409
  }
49410
+ RBXRenderer.setupLostContextHandler();
49411
+ }
49412
+ static setupLostContextHandler() {
49413
+ if (!RBXRenderer.renderer) return;
49414
+ RBXRenderer.renderer.domElement.addEventListener("webglcontextlost", (e) => {
49415
+ e.preventDefault();
49416
+ error(true, "RBXRenderer's WebGL2 context was lost, consider optimizing your WebGL usage");
49417
+ if (FLAGS.AUTO_RESTORE_CONTEXT) {
49418
+ const newCanvas = document.createElement("canvas");
49419
+ if (RBXRenderer.renderer?.domElement) {
49420
+ RBXRenderer.canvasContainer.replaceChild(newCanvas, RBXRenderer.renderer.domElement);
49421
+ }
49422
+ RBXRenderer.renderer?.dispose();
49423
+ RBXRenderer.create(newCanvas);
49424
+ for (const renderScene of RBXRenderer.scenes) {
49425
+ const controls = renderScene.controls;
49426
+ if (controls) {
49427
+ controls.dispose();
49428
+ controls.domElement = newCanvas;
49429
+ controls.connect(newCanvas);
49430
+ }
49431
+ for (const renderDesc of renderScene.renderDescs.values()) {
49432
+ if (renderDesc instanceof ObjectDesc) {
49433
+ const materialDesc = renderDesc.materialDesc;
49434
+ const composeType = materialDesc.getComposeType("color");
49435
+ if (composeType === "full" || composeType === "simple") {
49436
+ materialDesc.dirty = true;
49437
+ }
49438
+ }
49439
+ }
49440
+ }
49441
+ }
49442
+ });
49222
49443
  }
49223
49444
  /**Sets up a basic scene with lighting
49224
49445
  * @param lightingType "WellLit" is the default lighting for RoAvatar, "Thumbnail" tries to match the Roblox thumbnail lighting
@@ -49374,6 +49595,7 @@ class RBXRenderer {
49374
49595
  }
49375
49596
  static renderScene(renderScene, autoClear = true) {
49376
49597
  if (!RBXRenderer.renderer) return;
49598
+ if (!renderScene.shouldAnimate) return;
49377
49599
  RBXRenderer.renderer.autoClear = autoClear;
49378
49600
  if (!autoClear) {
49379
49601
  RBXRenderer.renderer.clearDepth();
@@ -49494,6 +49716,7 @@ class RBXRenderer {
49494
49716
  }
49495
49717
  /**Adds an instance to the renderer or updates it */
49496
49718
  static addInstance(instance, auth, renderScene = RBXRenderer.firstScene) {
49719
+ if (renderScene.destroyed) return;
49497
49720
  const isDecal = instance.className === "Decal";
49498
49721
  const isBakedDecal = isDecal && !instance.FindFirstChildOfClass("WrapTextureTransfer");
49499
49722
  let isFirstDecal = true;
@@ -49694,14 +49917,6 @@ class HSR {
49694
49917
  }
49695
49918
  }
49696
49919
  }
49697
- function exposeAPI() {
49698
- globalThis.API = API;
49699
- globalThis.APICACHE = CACHE;
49700
- globalThis.Authentication = Authentication;
49701
- }
49702
- function exposeMesh() {
49703
- globalThis.fileMeshToTHREEGeometry = fileMeshToTHREEGeometry;
49704
- }
49705
49920
  function getCorners(cframe, size) {
49706
49921
  const halfX = size.X / 2;
49707
49922
  const halfY = size.Y / 2;
@@ -49764,12 +49979,27 @@ function getExtents(cframe, parts) {
49764
49979
  }
49765
49980
  return [lowerExtents, higherExtents];
49766
49981
  }
49982
+ function getExtentsCenter(extents) {
49983
+ return extents[1].minus(extents[0]).divide(new Vector32(2, 2, 2)).add(extents[0]);
49984
+ }
49767
49985
  function zoomExtents(cameraCFrame, modelCFrame, modelSize, targetFOV, distanceScale) {
49768
49986
  const largestSize = Math.max(modelSize.X, modelSize.Y, modelSize.Z);
49769
49987
  const fovMultiplier = 70 / targetFOV;
49770
49988
  const lookDir = multiply(normalize(minus(cameraCFrame.Position, modelCFrame.Position)), [distanceScale, distanceScale, distanceScale]);
49771
49989
  cameraCFrame.Position = add(modelCFrame.Position, multiply(multiply(lookDir, [largestSize, largestSize, largestSize]), [fovMultiplier, fovMultiplier, fovMultiplier]));
49772
49990
  }
49991
+ function getCameraOffset(fov2, extentsSize) {
49992
+ const halfSize = extentsSize.magnitude() / 2;
49993
+ const fovDivisor = Math.tan(rad(fov2 / 2));
49994
+ return halfSize / fovDivisor;
49995
+ }
49996
+ function zoomToExtents(cameraCFrame, modelCFrame, modelSize, fov2 = 70) {
49997
+ const cameraOffset = getCameraOffset(fov2, modelSize);
49998
+ const cameraRotation = new CFrame();
49999
+ cameraRotation.Orientation = cameraCFrame.Orientation;
50000
+ const instancePosition = modelCFrame.Position;
50001
+ cameraCFrame.Position = add(instancePosition, multiply(minus([0, 0, 0], cameraRotation.lookVector()), [cameraOffset, cameraOffset, cameraOffset]));
50002
+ }
49773
50003
  function getHeadExtents(rig) {
49774
50004
  const head = rig.FindFirstChild("Head");
49775
50005
  if (!head) return;
@@ -49789,6 +50019,16 @@ function getHeadExtents(rig) {
49789
50019
  const extents = getExtents(head.Prop("CFrame"), headParts);
49790
50020
  return extents;
49791
50021
  }
50022
+ function getRigExtentsWorld(rig) {
50023
+ const rigParts = [];
50024
+ for (const child of rig.GetDescendants()) {
50025
+ if (child.className === "Part" || child.className === "MeshPart") {
50026
+ rigParts.push(child);
50027
+ }
50028
+ }
50029
+ const extents = getExtents(new CFrame(), rigParts);
50030
+ return extents;
50031
+ }
49792
50032
  function getCameraCFrameForHeadshotCustomized(rig, fov2, yRot, distance2) {
49793
50033
  const head = rig.FindFirstChild("Head");
49794
50034
  if (!head) return;
@@ -49815,6 +50055,34 @@ function getCameraCFrameForHeadshotCustomized(rig, fov2, yRot, distance2) {
49815
50055
  zoomExtents(cameraCF, headCenterCF, headLocalExtents[1].minus(headLocalExtents[0]), fov2, distance2);
49816
50056
  return cameraCF;
49817
50057
  }
50058
+ function getCameraCFrameForAvatarNonCustomized(rig) {
50059
+ const thumbnailCamera = rig.FindFirstChildOfClass("Camera");
50060
+ if (thumbnailCamera) return thumbnailCamera.PropOrDefault("CFrame", new CFrame());
50061
+ let rootPart = rig.PropOrDefault("PrimaryPart", void 0);
50062
+ if (!rootPart) rootPart = rig.FindFirstChildOfClass("Part");
50063
+ if (!rootPart) rootPart = rig.FindFirstChildOfClass("MeshPart");
50064
+ if (!rootPart) return;
50065
+ const rootPartCF = rootPart.PropOrDefault("CFrame", new CFrame()).clone();
50066
+ const worldExtents = getRigExtentsWorld(rig);
50067
+ if (!worldExtents) return;
50068
+ const extentsSize = worldExtents[1].minus(worldExtents[0]);
50069
+ rootPartCF.Position = getExtentsCenter(worldExtents).toVec3();
50070
+ let lookVector = rootPartCF.lookVector();
50071
+ if (Math.abs(lookVector[1]) > 0.95) {
50072
+ lookVector = [0, 0, -1];
50073
+ } else {
50074
+ lookVector[1] = 0;
50075
+ lookVector = normalize(lookVector);
50076
+ }
50077
+ let lookCF = CFrame.lookAt([0, 0, 0], lookVector);
50078
+ lookCF = lookCF.multiply(CFrame.fromEulerAngles(25 * Math.PI / 180, 27.5 * Math.PI / 180, 0, "ZXY"));
50079
+ lookVector = lookCF.lookVector();
50080
+ lookCF.Position = add(rootPartCF.Position, multiply([10, 10, 10], lookVector));
50081
+ lookCF = CFrame.lookAt(lookCF.Position, rootPartCF.Position);
50082
+ const cameraCF = lookCF.clone();
50083
+ zoomExtents(cameraCF, rootPartCF, extentsSize, 70, 1);
50084
+ return cameraCF;
50085
+ }
49818
50086
  class OutfitRenderer {
49819
50087
  auth;
49820
50088
  outfit;
@@ -50003,6 +50271,128 @@ class OutfitRenderer {
50003
50271
  }
50004
50272
  }
50005
50273
  }
50274
+ function renderToRenderTarget(width, height, renderScene) {
50275
+ const renderTarget = new WebGLRenderTarget(width, height, {
50276
+ colorSpace: SRGBColorSpace,
50277
+ generateMipmaps: false,
50278
+ minFilter: LinearFilter,
50279
+ magFilter: LinearFilter,
50280
+ type: UnsignedByteType
50281
+ });
50282
+ const rbxRenderer = RBXRenderer.getRenderer();
50283
+ if (!rbxRenderer) return renderTarget;
50284
+ rbxRenderer.setRenderTarget(renderTarget);
50285
+ rbxRenderer.render(renderScene.scene, renderScene.camera);
50286
+ return renderTarget;
50287
+ }
50288
+ async function renderTargetToCanvas(renderTarget) {
50289
+ const rbxRenderer = RBXRenderer.getRenderer();
50290
+ if (!rbxRenderer) return;
50291
+ const width = renderTarget.width;
50292
+ const height = renderTarget.height;
50293
+ const data = new Uint8Array(width * height * 4);
50294
+ await rbxRenderer.readRenderTargetPixelsAsync(renderTarget, 0, 0, width, height, data);
50295
+ return imageDataToCanvas(data, width, height);
50296
+ }
50297
+ async function generateModelThumbnail(auth, renderScene, model, size = [150, 150], type = "png", quality = 1, gltfAutoDownload = false) {
50298
+ return new Promise((resolve) => {
50299
+ const cameraCFrame = getCameraCFrameForAvatarNonCustomized(model);
50300
+ if (cameraCFrame) {
50301
+ RBXRenderer.setCameraCFrame(cameraCFrame, renderScene);
50302
+ }
50303
+ RBXRenderer.addInstance(model, auth, renderScene);
50304
+ let exportTimeout = setTimeout(doExport, FLAGS.THUMBNAIL_TIMEOUT);
50305
+ const onLoadingConnection = API.Events.OnLoadingAssets.Connect((currentlyLoading) => {
50306
+ if (exportTimeout) {
50307
+ clearTimeout(exportTimeout);
50308
+ exportTimeout = void 0;
50309
+ }
50310
+ if (!currentlyLoading) {
50311
+ exportTimeout = setTimeout(doExport, FLAGS.THUMBNAIL_TIMEOUT);
50312
+ }
50313
+ });
50314
+ async function doExport() {
50315
+ onLoadingConnection.Disconnect();
50316
+ if (type === "gltf") {
50317
+ if (!FLAGS.RENDERTARGET_TO_CANVASTEXTURE && FLAGS.USE_RENDERTARGET) {
50318
+ warn(true, "FLAGS.RENDERTARGET_TO_CANVASTEXTURE is false, GLTF export cannot export render target textures, consider setting this flag to true");
50319
+ }
50320
+ resolve(await renderScene.exportGLTF(`result`, gltfAutoDownload));
50321
+ } else {
50322
+ const renderTarget = renderToRenderTarget(...size, renderScene);
50323
+ const canvasTarget = await renderTargetToCanvas(renderTarget);
50324
+ if (canvasTarget) {
50325
+ resolve(canvasTarget.toDataURL(`image/${type}`, quality));
50326
+ } else {
50327
+ resolve(void 0);
50328
+ }
50329
+ }
50330
+ renderScene.destroy();
50331
+ }
50332
+ });
50333
+ }
50334
+ async function generateOutfitThumbnail(auth, outfit, size = [150, 150], type = "png", quality = 1, gltfAutoDownload = false) {
50335
+ return new Promise((resolve) => {
50336
+ const renderScene = RBXRenderer.addScene();
50337
+ setupThumbnailScene(renderScene);
50338
+ const outfitRenderer = new OutfitRenderer(auth, outfit, renderScene);
50339
+ if (outfit.playerAvatarType === AvatarType.R6) outfitRenderer.deltaTimeMultiplier = 0;
50340
+ outfitRenderer.startAnimating();
50341
+ let exportTimeout = setTimeout(doExport, FLAGS.THUMBNAIL_TIMEOUT);
50342
+ const onLoadingConnection = API.Events.OnLoadingAssets.Connect((currentlyLoading) => {
50343
+ if (exportTimeout) {
50344
+ clearTimeout(exportTimeout);
50345
+ exportTimeout = void 0;
50346
+ }
50347
+ if (!currentlyLoading) {
50348
+ exportTimeout = setTimeout(doExport, FLAGS.THUMBNAIL_TIMEOUT);
50349
+ }
50350
+ });
50351
+ async function doExport() {
50352
+ onLoadingConnection.Disconnect();
50353
+ if (!outfit.containsAssetType("Gear")) {
50354
+ if (outfit.playerAvatarType === AvatarType.R15) {
50355
+ outfitRenderer.setMainAnimation("pose");
50356
+ }
50357
+ } else {
50358
+ outfitRenderer.setMainAnimation("toolnone");
50359
+ }
50360
+ if (outfitRenderer.currentRig) {
50361
+ const thumbnailResult = await generateModelThumbnail(auth, renderScene, outfitRenderer.currentRig, size, type, quality, gltfAutoDownload);
50362
+ resolve(thumbnailResult);
50363
+ outfitRenderer.stopAnimating();
50364
+ if (outfitRenderer.currentRig) outfitRenderer.currentRig.Destroy();
50365
+ } else {
50366
+ resolve(void 0);
50367
+ }
50368
+ }
50369
+ });
50370
+ }
50371
+ function setupThumbnailScene(renderScene) {
50372
+ renderScene.shouldAnimate = false;
50373
+ renderScene.wellLitDirectionalLightIntensity *= 2;
50374
+ renderScene.shadowEnabled = false;
50375
+ RBXRenderer.setupScene("WellLit", 16777215, renderScene);
50376
+ if (renderScene.plane) renderScene.scene.remove(renderScene.plane);
50377
+ if (renderScene.shadowPlane) renderScene.scene.remove(renderScene.shadowPlane);
50378
+ renderScene.scene.background = null;
50379
+ }
50380
+ function exposeAPI() {
50381
+ globalThis.API = API;
50382
+ globalThis.APICACHE = CACHE;
50383
+ globalThis.Authentication = Authentication;
50384
+ }
50385
+ function exposeMesh() {
50386
+ globalThis.fileMeshToTHREEGeometry = fileMeshToTHREEGeometry;
50387
+ }
50388
+ function exposeFLAGS() {
50389
+ globalThis.FLAGS = FLAGS;
50390
+ }
50391
+ function exposeThumbnailGenerator() {
50392
+ globalThis.generateOutfitThumbnail = generateOutfitThumbnail;
50393
+ globalThis.generateModelThumbnail = generateModelThumbnail;
50394
+ globalThis.setupThumbnailScene = setupThumbnailScene;
50395
+ }
50006
50396
  export {
50007
50397
  API,
50008
50398
  AbbreviationToFaceControlProperty,
@@ -50032,6 +50422,7 @@ export {
50032
50422
  AssetTypeToAccessoryType,
50033
50423
  AssetTypeToMakeupType,
50034
50424
  AssetTypes,
50425
+ AttachmentWrapper,
50035
50426
  Authentication,
50036
50427
  AvatarType,
50037
50428
  BodyColor3s,
@@ -50069,6 +50460,7 @@ export {
50069
50460
  FileMesh,
50070
50461
  FileMeshSkinning,
50071
50462
  FileMeshSubset,
50463
+ FindFirstMatchingAttachment,
50072
50464
  FullBodyColors,
50073
50465
  GetAttachedPart,
50074
50466
  HSR,
@@ -50166,24 +50558,35 @@ export {
50166
50558
  defaultShirtTemplateAssetIds,
50167
50559
  deformReferenceToBaseBodyParts,
50168
50560
  deg,
50561
+ disposeMesh,
50169
50562
  distance,
50170
50563
  divide,
50171
50564
  dot,
50172
50565
  download,
50173
50566
  exposeAPI,
50567
+ exposeFLAGS,
50174
50568
  exposeMesh,
50569
+ exposeThumbnailGenerator,
50175
50570
  fileMeshToTHREEGeometry,
50176
50571
  floor,
50177
50572
  gaussian_rbf,
50573
+ generateModelThumbnail,
50574
+ generateOutfitThumbnail,
50178
50575
  generateUUIDv4,
50576
+ getCameraCFrameForAvatarNonCustomized,
50179
50577
  getCameraCFrameForHeadshotCustomized,
50578
+ getCameraOffset,
50180
50579
  getDistIndexArray,
50181
50580
  getExtents,
50581
+ getExtentsCenter,
50182
50582
  getExtentsForParts,
50183
50583
  getHeadExtents,
50184
50584
  getOffsetArray,
50585
+ getOriginalAttachmentOrientation,
50586
+ getOriginalAttachmentPosition,
50185
50587
  getOriginalSize,
50186
50588
  getRandomBetweenInclusive,
50589
+ getRigExtentsWorld,
50187
50590
  getUVtoIndexMap,
50188
50591
  getUVtoIndicesMap,
50189
50592
  getWorkerOnMessage,
@@ -50227,6 +50630,7 @@ export {
50227
50630
  rotationMatrixToEulerAngles,
50228
50631
  saveByteArray,
50229
50632
  scaleMesh,
50633
+ setupThumbnailScene,
50230
50634
  snapToNumber,
50231
50635
  specialClamp,
50232
50636
  transferSkeleton,
@@ -50236,5 +50640,6 @@ export {
50236
50640
  versionToNumber,
50237
50641
  vertPosToChunkPos,
50238
50642
  xmlMagic,
50239
- zoomExtents
50643
+ zoomExtents,
50644
+ zoomToExtents
50240
50645
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roavatar-renderer",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "description": "A renderer for Roblox avatars, used by the RoAvatar extension.",
5
5
  "author": "steinan",
6
6
  "type": "module",