minerender 2.1.0-alpha.27 → 2.1.0-alpha.29

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/bundle.js CHANGED
@@ -45411,6 +45411,7 @@ return ${compiler2.callType("value", tagType)}
45411
45411
  SchematicParser: () => SchematicParser,
45412
45412
  SkinObject: () => SkinObject,
45413
45413
  SkinPart: () => SkinPart,
45414
+ SkinTextures: () => SkinTextures,
45414
45415
  Skins: () => Skins,
45415
45416
  StructureParser: () => StructureParser,
45416
45417
  TextureAtlas: () => TextureAtlas,
@@ -63612,6 +63613,16 @@ return ${compiler2.callType("value", tagType)}
63612
63613
  }
63613
63614
  }
63614
63615
  };
63616
+ var DataTexture = class extends Texture {
63617
+ constructor(data = null, width = 1, height = 1, format2, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace) {
63618
+ super(null, mapping, wrapS, wrapT, magFilter, minFilter, format2, type, anisotropy, colorSpace);
63619
+ this.isDataTexture = true;
63620
+ this.image = { data, width, height };
63621
+ this.generateMipmaps = false;
63622
+ this.flipY = false;
63623
+ this.unpackAlignment = 1;
63624
+ }
63625
+ };
63615
63626
  var InstancedBufferAttribute = class extends BufferAttribute {
63616
63627
  constructor(array, itemSize, normalized, meshPerAttribute = 1) {
63617
63628
  super(array, itemSize, normalized);
@@ -67353,7 +67364,7 @@ return ${compiler2.callType("value", tagType)}
67353
67364
  }
67354
67365
  static async getItemModel(key) {
67355
67366
  const itemKey = new AssetKey(key.namespace, key.path, "items", void 0, key.rootType, ".json", key.root);
67356
- const model = await this.PERSISTENT_CACHE.getOrLoad(itemKey.serialize(), async () => {
67367
+ const model = await this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(itemKey.serialize()), async () => {
67357
67368
  const result = await AssetLoader.getFirst([itemKey, key], "json" /* JSON */);
67358
67369
  if (!result) return void 0;
67359
67370
  if (result.key.assetType !== "items") return { ...result.asset, key };
@@ -67399,7 +67410,7 @@ return ${compiler2.callType("value", tagType)}
67399
67410
  }
67400
67411
  const keyStr = key.serialize();
67401
67412
  return Caching.rawModelCache.get(keyStr, (k4) => {
67402
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
67413
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
67403
67414
  return AssetLoader.get(key, "model" /* MODEL */);
67404
67415
  });
67405
67416
  }).then((asset) => {
@@ -67474,7 +67485,7 @@ return ${compiler2.callType("value", tagType)}
67474
67485
  }
67475
67486
  const keyStr = key.serialize();
67476
67487
  return Caching.blockStateCache.get(keyStr, (k4) => {
67477
- return this.PERSISTENT_CACHE.getOrLoad(keyStr, (k1) => {
67488
+ return this.PERSISTENT_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
67478
67489
  return AssetLoader.get(key, "blockstate" /* BLOCKSTATE */);
67479
67490
  });
67480
67491
  }).then((asset) => {
@@ -67528,6 +67539,14 @@ return ${compiler2.callType("value", tagType)}
67528
67539
  var AssetSource = class {
67529
67540
  constructor() {
67530
67541
  }
67542
+ /**
67543
+ * Identifies this source's content across sessions for persistent caching. Sources that
67544
+ * cannot tell whether their content changed (an unnamed archive, for example) return
67545
+ * undefined, which keeps their results out of long-lived caches.
67546
+ */
67547
+ get cacheId() {
67548
+ return void 0;
67549
+ }
67531
67550
  };
67532
67551
  var AssetLoadError = class extends Error {
67533
67552
  constructor(source, key, path, cause) {
@@ -67789,6 +67808,9 @@ return ${compiler2.callType("value", tagType)}
67789
67808
  get root() {
67790
67809
  return this._root;
67791
67810
  }
67811
+ get cacheId() {
67812
+ return `hosted:${this._root}`;
67813
+ }
67792
67814
  get options() {
67793
67815
  return this._options;
67794
67816
  }
@@ -77313,9 +77335,10 @@ return ${compiler2.callType("value", tagType)}
77313
77335
 
77314
77336
  // src/assets/source/archive/BrowserArchiveProxy.ts
77315
77337
  var BrowserArchiveProxy = class {
77316
- constructor(blob) {
77338
+ constructor(blob, id) {
77317
77339
  this._blob = blob;
77318
77340
  this._reader = new ZipReader(new BlobReader(this._blob));
77341
+ this.id = id ?? (typeof File !== "undefined" && blob instanceof File ? `${blob.name}:${blob.size}:${blob.lastModified}` : void 0);
77319
77342
  }
77320
77343
  async getEntries() {
77321
77344
  return (await this._reader.getEntries()).map((e6) => {
@@ -77340,6 +77363,10 @@ return ${compiler2.callType("value", tagType)}
77340
77363
  static blob(blob) {
77341
77364
  return new _ArchiveAssetSource(new BrowserArchiveProxy(blob));
77342
77365
  }
77366
+ get cacheId() {
77367
+ const id = this._archiveProxy.id;
77368
+ return id === void 0 ? void 0 : `archive:${id}`;
77369
+ }
77343
77370
  async getEntries() {
77344
77371
  return this._archiveProxy.getEntries();
77345
77372
  }
@@ -77392,6 +77419,7 @@ return ${compiler2.callType("value", tagType)}
77392
77419
 
77393
77420
  // src/assets/AssetLoader.ts
77394
77421
  var p7 = prefix("AssetLoader");
77422
+ var FALLBACK_ROOT = "https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master";
77395
77423
  var _AssetLoader = class _AssetLoader {
77396
77424
  static get version() {
77397
77425
  return this.ROOT.substring(this.ROOT.lastIndexOf("/") + 1);
@@ -77407,6 +77435,28 @@ return ${compiler2.callType("value", tagType)}
77407
77435
  }
77408
77436
  Caching.clear();
77409
77437
  }
77438
+ /**
77439
+ * Scope for persistent cache keys. Empty with only the default vanilla sources, so their
77440
+ * entries stay valid across sessions; otherwise it names every added source (resource packs,
77441
+ * mirrors) so their results never masquerade as vanilla assets after a reload.
77442
+ */
77443
+ static get persistentScope() {
77444
+ const parts = [];
77445
+ for (const { key, source } of this._SOURCES) {
77446
+ const id = source.cacheId;
77447
+ if (key === "mcassets" && id === `hosted:${this.ROOT}`) continue;
77448
+ if (key === "mcassets-fallback" && id === `hosted:${FALLBACK_ROOT}`) continue;
77449
+ if (id === void 0) return this.SESSION_SCOPE;
77450
+ parts.push(`${key}=${id}`);
77451
+ }
77452
+ return parts.join("|");
77453
+ }
77454
+ /** Prefixes a persistent cache key with the current source scope. */
77455
+ static persistentKey(key) {
77456
+ const scope = this.persistentScope;
77457
+ return scope ? `${scope}
77458
+ ${key}` : key;
77459
+ }
77410
77460
  static addSource(key, source, override = true) {
77411
77461
  if (override) {
77412
77462
  const existing = this.removeSource(key);
@@ -77468,7 +77518,9 @@ return ${compiler2.callType("value", tagType)}
77468
77518
  };
77469
77519
  _AssetLoader.ROOT = DEFAULT_ROOT;
77470
77520
  _AssetLoader._SOURCES = [];
77471
- _AssetLoader.addSource("mcassets-fallback", new HostedAssetSource("https://raw.githubusercontent.com/InventivetalentDev/minerender-fallback-assets/master", { retryDefaults: false }));
77521
+ /** Used when a registered source cannot identify its content, so nothing persists past this session. */
77522
+ _AssetLoader.SESSION_SCOPE = `session:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
77523
+ _AssetLoader.addSource("mcassets-fallback", new HostedAssetSource(FALLBACK_ROOT, { retryDefaults: false }));
77472
77524
  _AssetLoader.addSource("mcassets", new HostedAssetSource(_AssetLoader.ROOT, { retryDefaults: false }));
77473
77525
  var AssetLoader = _AssetLoader;
77474
77526
 
@@ -77511,7 +77563,7 @@ return ${compiler2.callType("value", tagType)}
77511
77563
  }
77512
77564
  const keyStr = key.serialize();
77513
77565
  return Caching.textureMetaCache.get(keyStr, (k4) => {
77514
- return this.PERSISTENT_META_CACHE.getOrLoad(keyStr, (k1) => {
77566
+ return this.PERSISTENT_META_CACHE.getOrLoad(AssetLoader.persistentKey(keyStr), (k1) => {
77515
77567
  return AssetLoader.get(key, "meta" /* META */).then((asset) => {
77516
77568
  if (asset)
77517
77569
  asset.key = key;
@@ -80364,7 +80416,9 @@ return ${compiler2.callType("value", tagType)}
80364
80416
  if (typeof variant.y !== "undefined") {
80365
80417
  rotation.y = toRadians(clampRotationDegrees(typeof variant.x !== "undefined" ? variant.y : 360 - variant.y));
80366
80418
  }
80367
- const model = await Models.getMerged(AssetKey.parse("models", variant.model));
80419
+ const modelKey = AssetKey.parse("models", variant.model);
80420
+ modelKey.root = this.blockState.key?.root;
80421
+ const model = await Models.getMerged(modelKey);
80368
80422
  const options = {
80369
80423
  ...this.options,
80370
80424
  tints: model ? await BlockTints.get(this.blockState.key, this.state, model, this.options.tints) : this.options.tints,
@@ -81199,56 +81253,12 @@ return ${compiler2.callType("value", tagType)}
81199
81253
  }
81200
81254
  },
81201
81255
  cape: {
81202
- //TODO
81203
- east: {
81204
- uv: [
81205
- 0,
81206
- 36,
81207
- 4,
81208
- 48
81209
- ]
81210
- },
81211
- west: {
81212
- uv: [
81213
- 8,
81214
- 36,
81215
- 12,
81216
- 48
81217
- ]
81218
- },
81219
- north: {
81220
- uv: [
81221
- 4,
81222
- 36,
81223
- 8,
81224
- 48
81225
- ]
81226
- },
81227
- south: {
81228
- uv: [
81229
- 12,
81230
- 36,
81231
- 16,
81232
- 48
81233
- ]
81234
- },
81235
- up: {
81236
- uv: [
81237
- 4,
81238
- 32,
81239
- 8,
81240
- 36
81241
- ]
81242
- },
81243
- down: {
81244
- uv: [
81245
- 8,
81246
- 32,
81247
- 12,
81248
- 36
81249
- ],
81250
- rotation: 180
81251
- }
81256
+ east: { uv: [0, 1, 1, 17] },
81257
+ west: { uv: [11, 1, 12, 17] },
81258
+ north: { uv: [1, 1, 11, 17] },
81259
+ south: { uv: [12, 1, 22, 17] },
81260
+ up: { uv: [11, 1, 1, 0] },
81261
+ down: { uv: [21, 0, 11, 1] }
81252
81262
  }
81253
81263
  };
81254
81264
  var classicSkinTextureCoordinates = {
@@ -81332,14 +81342,112 @@ return ${compiler2.callType("value", tagType)}
81332
81342
 
81333
81343
  // src/skin/scene/SkinObject.ts
81334
81344
  var import_ts_deepmerge7 = __toESM(require_dist());
81345
+
81346
+ // src/skin/SkinTextures.ts
81347
+ init_dirname();
81348
+ init_buffer2();
81349
+ init_process2();
81350
+ var SkinTextures = class {
81351
+ static async get(src, legacy) {
81352
+ const image = await ImageLoader.getData(src);
81353
+ const scale = image.width / 64;
81354
+ if (!Number.isInteger(scale) || scale < 1 || image.height !== image.width && image.height !== image.width / 2) {
81355
+ throw new MineRenderError(`Invalid skin dimensions ${image.width}x${image.height}; expected 64x64 or 64x32, or an integer-scaled equivalent`);
81356
+ }
81357
+ const isLegacy = legacy ?? image.height === image.width / 2;
81358
+ if (!isLegacy && image.height !== image.width) {
81359
+ throw new MineRenderError(`Invalid skin dimensions ${image.width}x${image.height}; non-legacy skins must be square`);
81360
+ }
81361
+ const slim = !isLegacy && this.isSlim(image, scale);
81362
+ const key = `skin:${serializeImageKey({ src })}:${isLegacy}`;
81363
+ const material = Caching.materialCache.get(key, () => {
81364
+ const pixels = isLegacy ? this.normalizeLegacy(image, scale) : new Uint8Array(image.data);
81365
+ return this.createMaterial(pixels, image.width, image.width);
81366
+ });
81367
+ return { material, slim, legacy: isLegacy };
81368
+ }
81369
+ static async getCape(src) {
81370
+ const image = await ImageLoader.getData(src);
81371
+ const scale = image.width / 64;
81372
+ if (!Number.isInteger(scale) || scale < 1 || image.height !== image.width / 2) {
81373
+ throw new MineRenderError(`Invalid cape dimensions ${image.width}x${image.height}; expected 64x32 or an integer-scaled equivalent`);
81374
+ }
81375
+ return Caching.materialCache.get(`cape:${serializeImageKey({ src })}`, () => {
81376
+ return this.createMaterial(new Uint8Array(image.data), image.width, image.height, false);
81377
+ });
81378
+ }
81379
+ static createMaterial(pixels, width, height, transparent = true) {
81380
+ const texture = Textures.initTextureProps(new DataTexture(pixels, width, height));
81381
+ texture.colorSpace = SRGBColorSpace;
81382
+ texture.flipY = true;
81383
+ texture.needsUpdate = true;
81384
+ return new MeshBasicMaterial({ map: texture, transparent, side: transparent ? DoubleSide : FrontSide, alphaTest: transparent ? 0.5 : 0 });
81385
+ }
81386
+ static isSlim(image, scale) {
81387
+ for (const [x4, y5] of [[46, 52], [54, 20]]) {
81388
+ for (let row = y5 * scale; row < (y5 + 12) * scale; row++) {
81389
+ for (let column = x4 * scale; column < (x4 + 1) * scale; column++) {
81390
+ if (image.data[(row * image.width + column) * 4 + 3] !== 0) return false;
81391
+ }
81392
+ }
81393
+ }
81394
+ return true;
81395
+ }
81396
+ static normalizeLegacy(image, scale) {
81397
+ const pixels = new Uint8Array(image.width * image.width * 4);
81398
+ pixels.set(image.data.subarray(0, image.width * image.width / 2 * 4));
81399
+ for (const [sx, sy, dx, dy, width, height] of [
81400
+ [4, 16, 20, 48, 4, 4],
81401
+ [8, 16, 24, 48, 4, 4],
81402
+ [0, 20, 24, 52, 4, 12],
81403
+ [4, 20, 20, 52, 4, 12],
81404
+ [8, 20, 16, 52, 4, 12],
81405
+ [12, 20, 28, 52, 4, 12],
81406
+ [44, 16, 36, 48, 4, 4],
81407
+ [48, 16, 40, 48, 4, 4],
81408
+ [40, 20, 40, 52, 4, 12],
81409
+ [44, 20, 36, 52, 4, 12],
81410
+ [48, 20, 32, 52, 4, 12],
81411
+ [52, 20, 44, 52, 4, 12]
81412
+ ]) {
81413
+ for (let y5 = 0; y5 < height * scale; y5++) {
81414
+ for (let x4 = 0; x4 < width * scale; x4++) {
81415
+ const source = ((sy * scale + y5) * image.width + sx * scale + x4) * 4;
81416
+ const target = ((dy * scale + y5) * image.width + (dx + width) * scale - 1 - x4) * 4;
81417
+ pixels.set(image.data.subarray(source, source + 4), target);
81418
+ }
81419
+ }
81420
+ }
81421
+ let clearHat = true;
81422
+ for (let y5 = 0; y5 < 32 * scale; y5++) {
81423
+ for (let x4 = 32 * scale; x4 < image.width; x4++) {
81424
+ if (pixels[(y5 * image.width + x4) * 4 + 3] < 128) clearHat = false;
81425
+ }
81426
+ }
81427
+ for (let y5 = 0; y5 < image.width; y5++) {
81428
+ for (let x4 = 0; x4 < image.width; x4++) {
81429
+ const alpha = (y5 * image.width + x4) * 4 + 3;
81430
+ if (x4 < 32 * scale && y5 < 16 * scale || y5 >= 16 * scale && y5 < 32 * scale || x4 >= 16 * scale && x4 < 48 * scale && y5 >= 48 * scale) {
81431
+ pixels[alpha] = 255;
81432
+ } else if (clearHat && x4 >= 32 * scale && y5 < 32 * scale) {
81433
+ pixels[alpha] = 0;
81434
+ }
81435
+ }
81436
+ }
81437
+ return pixels;
81438
+ }
81439
+ };
81440
+
81441
+ // src/skin/scene/SkinObject.ts
81335
81442
  var SkinObject = class extends SceneObject {
81336
81443
  constructor(options) {
81337
81444
  super();
81338
81445
  this.slim = false;
81339
- this.legacy = false;
81340
- this.skinTextureWidth = 64;
81341
- this.skinTextureHeight = 64;
81446
+ this.detectedSlim = false;
81447
+ this.skinLoad = 0;
81448
+ this.capeLoad = 0;
81342
81449
  this.options = (0, import_ts_deepmerge7.default)({}, SceneObject.DEFAULT_OPTIONS, options ?? {});
81450
+ this.slim = this.options.slim ?? false;
81343
81451
  }
81344
81452
  async init() {
81345
81453
  this.createMeshes();
@@ -81348,15 +81456,13 @@ return ${compiler2.callType("value", tagType)}
81348
81456
  }
81349
81457
  createMeshes() {
81350
81458
  console.log("#createMeshes");
81351
- const mat = Materials.MISSING_TEXTURE;
81459
+ const mat = this.skinMaterial ?? Materials.MISSING_TEXTURE;
81352
81460
  {
81353
81461
  const headGroup = this.createAndAddGroup("head", 0, 28, 0, "y" /* Y */, -4);
81354
81462
  const headGeo = this.getBoxGeometry("head" /* HEAD */);
81355
81463
  const head = this.createAndAddMesh("head", headGroup, headGeo, mat, "y" /* Y */, 4);
81356
- if (!this.legacy) {
81357
- const hatGeo = this.getBoxGeometry("hat" /* HAT */);
81358
- const hat = this.createAndAddMesh("hat", headGroup, hatGeo, mat, "y" /* Y */, 4);
81359
- }
81464
+ const hatGeo = this.getBoxGeometry("hat" /* HAT */);
81465
+ const hat = this.createAndAddMesh("hat", headGroup, hatGeo, mat, "y" /* Y */, 4);
81360
81466
  }
81361
81467
  {
81362
81468
  const bodyGroup = this.createAndAddGroup("body", 0, 18, 0);
@@ -81399,21 +81505,58 @@ return ${compiler2.callType("value", tagType)}
81399
81505
  }
81400
81506
  console.log("#createMeshes done");
81401
81507
  }
81402
- setSkinTexture(src) {
81508
+ async setSkinTexture(src) {
81403
81509
  if (typeof src === "undefined") return;
81404
- const mat = Materials.getImage({
81405
- texture: { src },
81406
- transparent: true
81407
- });
81408
- for (let part of SKIN_PARTS) {
81409
- let mesh = this.getMeshByName(part);
81510
+ this.skinTextureSrc = src;
81511
+ const load = ++this.skinLoad;
81512
+ const texture = await SkinTextures.get(src, this.options.legacy);
81513
+ if (load !== this.skinLoad) return;
81514
+ this.skinMaterial = texture.material;
81515
+ this.detectedSlim = texture.slim;
81516
+ this.updateSlim(this.options.slim ?? this.detectedSlim);
81517
+ for (const part of SKIN_PARTS) {
81518
+ if (part === "cape" /* CAPE */) continue;
81519
+ const mesh = this.getMeshByName(part);
81410
81520
  if (mesh) {
81411
- mesh.material = mat;
81521
+ mesh.material = texture.material;
81412
81522
  }
81413
81523
  }
81524
+ this.notifyDirty();
81525
+ }
81526
+ /** Load a vanilla cape texture, or pass undefined to remove the cape. */
81527
+ async setCapeTexture(src) {
81528
+ const load = ++this.capeLoad;
81529
+ if (src === void 0) {
81530
+ const group = this.getGroupByName("cape" /* CAPE */);
81531
+ if (group) this.remove(group);
81532
+ this.notifyDirty();
81533
+ return;
81534
+ }
81535
+ const material = await SkinTextures.getCape(src);
81536
+ if (load !== this.capeLoad) return;
81537
+ const mesh = this.getMeshByName("cape" /* CAPE */);
81538
+ if (mesh) {
81539
+ mesh.material = material;
81540
+ } else {
81541
+ const group = this.createAndAddGroup("cape" /* CAPE */, 0, 24, 2);
81542
+ group.rotation.set(toRadians(-6), Math.PI, 0);
81543
+ const geometry = this._getBoxGeometryFromDimensions(
81544
+ classicSkinGeometries.cape,
81545
+ classicSkinTextureCoordinates.cape,
81546
+ [64, 32],
81547
+ [64, 32]
81548
+ );
81549
+ const cape = this.createAndAddMesh("cape" /* CAPE */, group, geometry, material);
81550
+ cape.position.set(0, -8, -0.5);
81551
+ }
81552
+ this.notifyDirty();
81414
81553
  }
81415
- //TODO: cape
81554
+ /** Select the arm model, or pass undefined to use texture detection. */
81416
81555
  setSlim(slim) {
81556
+ this.options.slim = slim;
81557
+ this.updateSlim(slim ?? this.detectedSlim);
81558
+ }
81559
+ updateSlim(slim) {
81417
81560
  if (slim === this.slim) return;
81418
81561
  this.slim = slim;
81419
81562
  for (const part of ["leftArm" /* LEFT_ARM */, "leftSleeve" /* LEFT_SLEEVE */, "rightArm" /* RIGHT_ARM */, "rightSleeve" /* RIGHT_SLEEVE */]) {
@@ -81427,17 +81570,23 @@ return ${compiler2.callType("value", tagType)}
81427
81570
  if (rightArm) rightArm.position.x -= offset;
81428
81571
  this.notifyDirty();
81429
81572
  }
81430
- setLegacy(legacy) {
81431
- if (legacy !== this.legacy) {
81432
- }
81433
- this.legacy = legacy;
81573
+ /** Select the texture layout, or pass undefined to detect it from image dimensions. */
81574
+ async setLegacy(legacy) {
81575
+ if (legacy === this.options.legacy) return;
81576
+ this.options.legacy = legacy;
81577
+ if (this.skinTextureSrc) await this.setSkinTexture(this.skinTextureSrc);
81578
+ }
81579
+ dispose() {
81580
+ this.skinLoad++;
81581
+ this.capeLoad++;
81582
+ super.dispose();
81434
81583
  }
81435
81584
  //TODO: layer toggles
81436
81585
  getBoxGeometry(part) {
81437
81586
  console.log("slim", this.slim);
81438
81587
  const coordinates = this.slim ? slimSkinTextureCoordinates : classicSkinTextureCoordinates;
81439
81588
  const geometries = this.slim ? slimSkinGeometries : classicSkinGeometries;
81440
- return this._getBoxGeometryFromDimensions(geometries[part], coordinates[part], [64, 64], [this.skinTextureWidth, this.skinTextureHeight]);
81589
+ return this._getBoxGeometryFromDimensions(geometries[part], coordinates[part], [64, 64], [64, 64]);
81441
81590
  }
81442
81591
  };
81443
81592
 
@@ -81547,10 +81696,10 @@ return ${compiler2.callType("value", tagType)}
81547
81696
  this.dirty = true;
81548
81697
  const obj = new SkinObject(options);
81549
81698
  obj.scene = this;
81550
- await obj.init();
81551
81699
  if (skin) {
81552
- obj.setSkinTexture(skin);
81700
+ await obj.setSkinTexture(skin);
81553
81701
  }
81702
+ await obj.init();
81554
81703
  parent.add(obj);
81555
81704
  return obj;
81556
81705
  }