Merge remote-tracking branch 'origin/main' into feature/checkpoint-20260403

This commit is contained in:
Lorenzune
2026-04-03 05:25:28 +02:00
10 changed files with 342 additions and 244 deletions
@@ -23,9 +23,18 @@ export class AvatarAssetDownloadManager
this._structure = structure;
}
private static DEFAULT_MANDATORY_LIBS: string[] = ['hh_human_face'];
public async init(): Promise<void>
{
this._missingMandatoryLibs = GetConfiguration().getValue<string[]>('avatar.mandatory.libraries');
const configuredLibs = GetConfiguration().getValue<string[]>('avatar.mandatory.libraries') || [];
this._missingMandatoryLibs = [ ...configuredLibs ];
for(const lib of AvatarAssetDownloadManager.DEFAULT_MANDATORY_LIBS)
{
if(this._missingMandatoryLibs.indexOf(lib) === -1) this._missingMandatoryLibs.push(lib);
}
const url = GetConfiguration().getValue<string>('avatar.figuremap.url');
+2
View File
@@ -285,6 +285,8 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener
const container = this.buildAvatarContainer(avatarCanvas, setType);
if(!container) return null;
GetRenderer().render({
target: this._activeTexture,
container: container,
@@ -117,7 +117,12 @@ export class RoomCameraWidgetManager implements IRoomCameraWidgetManager
TextureUtils.writeToTexture(container, renderTexture);
return await TextureUtils.generateImage(renderTexture);
const image = await TextureUtils.generateImage(renderTexture);
container.destroy({ children: true });
renderTexture.destroy(true);
return image;
}
public get effects(): Map<string, IRoomCameraWidgetEffect>
@@ -62,7 +62,8 @@ export class CommunicationManager implements ICommunicationManager
t.shadowColor = 'blue';
t.fillRect(-20, 10, 234, 5);
const i = e.toDataURL();
document.body.appendChild(e);
e.width = 0;
e.height = 0;
let r = 0;
if (i.length === 0) return 'nothing!';
for (let n = 0; n < i.length; n++) {
@@ -283,6 +283,8 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali
tempCtx.putImageData(patchData, 0, 0);
accCtx.drawImage(tempCanvas, frame.dims.left, frame.dims.top);
tempCanvas.width = 0;
tempCanvas.height = 0;
// Create a new canvas for this frame and create a texture from it
const frameCanvas = document.createElement('canvas');
@@ -299,6 +301,9 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali
frameDelays.push(frame.delay || 10);
}
accCanvas.width = 0;
accCanvas.height = 0;
// Create AnimatedSprite with frame textures
if(this._frameTextures.length > 1)
{
@@ -340,6 +345,13 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali
ctx.clearRect(0, 0, badgeCanvas.width, badgeCanvas.height);
ctx.drawImage(img, 0, 0, badgeCanvas.width, badgeCanvas.height);
tex.source.update();
img.onload = null;
img.onerror = null;
};
img.onerror = () =>
{
img.onload = null;
img.onerror = null;
};
img.src = badgeUrl;
}
@@ -28,10 +28,19 @@ export class FurnitureDynamicThumbnailVisualization extends IsometricImageFurniV
if (image.complete && image.width > 0 && image.height > 0) {
const texture = Texture.from(image);
texture.source.scaleMode = 'linear';
this.setThumbnailImages(texture, thumbnailUrl); // Pass URL here
this.setThumbnailImages(texture, thumbnailUrl);
} else {
console.error("Image failed to load properly:", thumbnailUrl);
this.setThumbnailImages(null);
}
image.onload = null;
image.onerror = null;
};
image.onerror = () => {
this.setThumbnailImages(null);
image.onload = null;
image.onerror = null;
};
} else {
this.setThumbnailImages(null);
@@ -5,6 +5,13 @@ export class FurnitureYoutubeVisualization extends FurnitureDynamicThumbnailVisu
{
protected static THUMBNAIL_URL: string = 'THUMBNAIL_URL';
constructor()
{
super();
this._hasOutline = false;
}
protected getThumbnailURL(): string
{
if(!this.object) return null;
@@ -1,15 +1,16 @@
import { IGraphicAsset } from '@nitrots/api';
import { GetRenderer, TextureUtils } from '@nitrots/utils';
import { Container, Graphics, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js';
import { GetRenderer } from '@nitrots/utils';
import { Container, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js';
import { FurnitureAnimatedVisualization } from './FurnitureAnimatedVisualization';
export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualization {
protected static THUMBNAIL: string = 'THUMBNAIL';
private _thumbnailAssetNameNormal: string;
private _thumbnailImageNormal: Texture;
private _thumbnailDirection: number;
private _thumbnailChanged: boolean;
private _thumbnailLayerId: number;
private _thumbnailTexture: Texture;
private _uniqueId: string;
private _photoUrl: string;
protected _hasOutline: boolean;
@@ -17,10 +18,11 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
constructor() {
super();
this._thumbnailAssetNameNormal = null;
this._thumbnailImageNormal = null;
this._thumbnailDirection = -1;
this._thumbnailChanged = false;
this._thumbnailLayerId = -1;
this._thumbnailTexture = null;
this._uniqueId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
this._photoUrl = null;
}
@@ -56,13 +58,14 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
return;
}
const thumbnailAssetName = this.getThumbnailAssetName(64);
if (this._thumbnailImageNormal) {
this.addThumbnailAsset(this._thumbnailImageNormal, 64);
} else {
const layerId = 2;
const sprite = this.getSprite(layerId);
if (this._thumbnailTexture instanceof RenderTexture) {
this._thumbnailTexture.destroy(true);
}
this._thumbnailTexture = null;
this._thumbnailLayerId = -1;
}
this._thumbnailChanged = false;
@@ -76,21 +79,16 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
const layerTag = this.getLayerTag(scale, this.direction, layerId);
if (layerTag === IsometricImageFurniVisualization.THUMBNAIL) {
this._thumbnailLayerId = layerId;
const assetName = (this.cacheSpriteAssetName(scale, layerId, false) + this.getFrameNumber(scale, layerId));
const asset = this.getAsset(assetName, layerId);
const thumbnailAssetName = `${this.getThumbnailAssetName(scale)}-${this._uniqueId}`;
const transformedTexture = this.generateTransformedThumbnail(k, asset || { width: 64, height: 64 });
// Use the original asset's registered offsets so the thumbnail is drawn at the
// furniture-defined sprite position. Fall back to centering when no asset exists.
const offsetX = asset ? asset.offsetX : -Math.floor(transformedTexture.width / 2);
const offsetY = asset ? asset.offsetY : -Math.floor(transformedTexture.height / 2);
this.asset.addAsset(thumbnailAssetName, transformedTexture, true, offsetX, offsetY, false, false);
const placedSprite = this.getSprite(layerId);
if (placedSprite) {
placedSprite.texture = transformedTexture;
if (asset) {
if (this._thumbnailTexture instanceof RenderTexture) {
this._thumbnailTexture.destroy(true);
}
this._thumbnailTexture = this.generateTransformedThumbnail(k, asset);
}
return;
@@ -100,78 +98,117 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
}
}
protected generateTransformedThumbnail(texture: Texture, asset: IGraphicAsset): Texture {
const scaleFactor = (asset?.width || 64) / texture.width;
const verticalScale = 1.0265;
const matrix = new Matrix();
const frameThickness = 20;
const frameColor = 0x000000;
protected updateSprite(scale: number, layerId: number): void {
super.updateSprite(scale, layerId);
switch (this.direction) {
if (this._thumbnailTexture && this._thumbnailLayerId === layerId) {
const sprite = this.getSprite(layerId);
if (sprite) {
sprite.texture = this._thumbnailTexture;
}
}
}
protected generateTransformedThumbnail(texture: Texture, asset: IGraphicAsset): Texture {
const assetWidth = asset.width;
const assetHeight = asset.height;
let outlineTexture: RenderTexture = null;
if(this._hasOutline)
{
const borderSize = 20;
const bgWidth = texture.width + borderSize * 2;
const bgHeight = texture.height + borderSize * 2;
const container = new Container();
const background = new Sprite(Texture.WHITE);
background.tint = 0x000000;
background.width = bgWidth;
background.height = bgHeight;
const imageSprite = new Sprite(texture);
imageSprite.position.set(borderSize, borderSize);
container.addChild(background, imageSprite);
outlineTexture = RenderTexture.create({ width: bgWidth, height: bgHeight, resolution: 1 });
GetRenderer().render({ container, target: outlineTexture, clear: true });
texture = outlineTexture;
}
texture.source.scaleMode = 'linear';
const texW = texture.width;
const texH = texture.height;
const scaleX = assetWidth / texW;
const scaleY = assetHeight / texH;
const matrix = new Matrix();
switch(this.direction)
{
case 2:
matrix.a = scaleFactor;
matrix.b = (-0.5 * scaleFactor);
matrix.a = scaleX;
matrix.b = -(0.5 * scaleX);
matrix.c = 0;
matrix.d = (scaleFactor * verticalScale);
matrix.d = (scaleY / 1.6);
matrix.tx = 0;
matrix.ty = (0.5 * scaleFactor * texture.width);
matrix.ty = (0.5 * scaleX * texW);
break;
case 0:
case 4:
matrix.a = scaleFactor;
matrix.b = (0.5 * scaleFactor);
matrix.a = scaleX;
matrix.b = (0.5 * scaleX);
matrix.c = 0;
matrix.d = (scaleFactor * verticalScale);
matrix.d = (scaleY / 1.6);
matrix.tx = 0;
matrix.ty = 0;
break;
default:
matrix.a = scaleFactor;
matrix.a = scaleX;
matrix.b = 0;
matrix.c = 0;
matrix.d = scaleFactor;
matrix.d = scaleY;
matrix.tx = 0;
matrix.ty = 0;
}
const imgWidth = texture.width;
const imgHeight = texture.height;
const flatWidth = imgWidth + frameThickness * 2;
const flatHeight = imgHeight + frameThickness * 2;
const flatRenderTexture = TextureUtils.createAndFillRenderTexture(flatWidth, flatHeight, frameColor);
const imageSprite = new Sprite(texture);
imageSprite.position.set(frameThickness, frameThickness);
TextureUtils.writeToTexture(imageSprite, flatRenderTexture, false);
const flatTexture = flatRenderTexture;
const transformedSprite = new Sprite(flatTexture);
transformedSprite.setFromMatrix(matrix);
const width = 80;
const height = 80;
const finalContainer = new Container();
const posX = (width - transformedSprite.width) / 2;
const posY = (height - transformedSprite.height) / 2;
transformedSprite.position.set(posX, posY);
finalContainer.addChild(transformedSprite);
// Calculate transformed corners manually for accurate bounds
const corners = [
{ x: matrix.tx, y: matrix.ty },
{ x: matrix.a * texW + matrix.tx, y: matrix.b * texW + matrix.ty },
{ x: matrix.c * texH + matrix.tx, y: matrix.d * texH + matrix.ty },
{ x: matrix.a * texW + matrix.c * texH + matrix.tx, y: matrix.b * texW + matrix.d * texH + matrix.ty }
];
const renderTexture = RenderTexture.create({ width, height, resolution: 1 });
GetRenderer().render({ container: finalContainer, target: renderTexture, clear: true });
let minX = corners[0].x, minY = corners[0].y;
let maxX = corners[0].x, maxY = corners[0].y;
for (const corner of corners) {
if (corner.x < minX) minX = corner.x;
if (corner.y < minY) minY = corner.y;
if (corner.x > maxX) maxX = corner.x;
if (corner.y > maxY) maxY = corner.y;
}
const renderWidth = Math.ceil(maxX - minX);
const renderHeight = Math.ceil(maxY - minY);
matrix.tx -= minX;
matrix.ty -= minY;
const transformedSprite = new Sprite(texture);
transformedSprite.setFromMatrix(matrix);
const renderTexture = RenderTexture.create({ width: renderWidth, height: renderHeight, resolution: 1 });
GetRenderer().render({ container: transformedSprite, target: renderTexture, clear: true });
if (outlineTexture) {
outlineTexture.destroy(true);
}
return renderTexture;
}
protected getSpriteAssetName(scale: number, layerId: number): string {
if (this._thumbnailImageNormal && (this.getLayerTag(scale, this.direction, layerId) === IsometricImageFurniVisualization.THUMBNAIL)) {
return `${this.getThumbnailAssetName(scale)}-${this._uniqueId}`;
}
return super.getSpriteAssetName(scale, layerId);
}
protected getThumbnailAssetName(scale: number): string {
return this.cacheSpriteAssetName(scale, 2, false) + this.getFrameNumber(scale, 2);
}
protected getFullThumbnailAssetName(k: number, _arg_2: number): string {
return [this._type, k, 'thumb', _arg_2].join('_');
}
}
}
@@ -867,203 +867,218 @@ export class RoomPlane implements IRoomPlane
}
private renderWindowReflections(): void
{
if(!this._planeTexture || !this._leftSide || !this._rightSide || !this._normal) return;
if(this._leftSide.length <= 0 || this._rightSide.length <= 0) return;
const now = Date.now();
const fadeDurationMs = 150;
const avatars = RoomWindowReflectionState.getAvatars();
const canvasWidth = this._landscapeRenderWidth;
const canvasHeight = this._landscapeRenderHeight;
if(canvasWidth <= 0 || canvasHeight <= 0) return;
const container = new Container();
const visibleAvatarIds = new Set<number>();
const addReflectionSprite = (
texture: Texture,
oppositeTexture: Texture,
location: IVector3D,
alpha: number,
verticalOffset: number = 0,
direction: number = 0,
avatarId: number = -1
): boolean =>
{
if(!this._planeTexture || !this._leftSide || !this._rightSide || !this._normal) return;
if(!texture?.source || texture.source.destroyed || !texture.source.style || !location || alpha < 0)
return false;
if(this._leftSide.length <= 0 || this._rightSide.length <= 0) return;
const relative = Vector3d.dif(location, this._location);
const planeDistance = Math.abs(Vector3d.scalarProjection(relative, this._normal));
const now = Date.now();
const fadeDurationMs = 150;
const avatars = RoomWindowReflectionState.getAvatars();
const canvasWidth = this._landscapeRenderWidth;
const canvasHeight = this._landscapeRenderHeight;
if(planeDistance > 0.8) return false;
if(canvasWidth <= 0 || canvasHeight <= 0) return;
const leftSideLoc = Vector3d.scalarProjection(relative, this._leftSide);
const rightSideLoc = Vector3d.scalarProjection(relative, this._rightSide);
const container = new Container();
const visibleAvatarIds = new Set<number>();
const addReflectionSprite = (texture: Texture, oppositeTexture: Texture, location: IVector3D, alpha: number, verticalOffset: number = 0, direction: number = 0, avatarId: number = -1): boolean => {
if(!texture?.source || texture.source.destroyed || !texture.source.style || !location || alpha < 0) return false;
const relative = Vector3d.dif(location, this._location);
const planeDistance = Math.abs(Vector3d.scalarProjection(relative, this._normal));
if(planeDistance > 0.8) return false;
const leftSideLoc = Vector3d.scalarProjection(relative, this._leftSide);
const rightSideLoc = Vector3d.scalarProjection(relative, this._rightSide);
const closestMask = this._windowMasks.reduce((best, mask) => {
const score = Math.abs(mask.leftSideLoc - leftSideLoc) + Math.abs(mask.rightSideLoc - rightSideLoc);
if(!best || (score < best.score)) return { mask, score };
return best;
}, null as { mask: { leftSideLoc: number; rightSideLoc: number }; score: number } | null);
if(!closestMask || (closestMask.score > 3)) return false;
const x = (canvasWidth - ((canvasWidth * leftSideLoc) / this._leftSide.length));
const y = (canvasHeight - ((canvasHeight * rightSideLoc) / this._rightSide.length)) + verticalOffset;
const toPlaneX = (this._location.x - location.x);
const toPlaneY = (this._location.y - location.y);
const toPlaneLength = Math.hypot(toPlaneX, toPlaneY);
const facingRadians = ((((direction - 90) % 360) + 360) % 360) * (Math.PI / 180);
const facingX = Math.cos(facingRadians);
const facingY = Math.sin(facingRadians);
const facingWindow = (toPlaneLength > 0.001)
? (((facingX * toPlaneX) + (facingY * toPlaneY)) / toPlaneLength) > 0.5
: false;
const deltaLeft = Math.abs(closestMask.mask.leftSideLoc - leftSideLoc);
const deltaRight = Math.abs(closestMask.mask.rightSideLoc - rightSideLoc);
const isInFrontOfWindow = ((closestMask.score <= 2) && ((deltaLeft <= 0.9) || (deltaRight <= 0.9)));
const shouldMirror = isInFrontOfWindow;
const normal2DLength = Math.hypot(this._normal.x, this._normal.y);
const normalX = (normal2DLength > 0.0001) ? (this._normal.x / normal2DLength) : 0;
const normalY = (normal2DLength > 0.0001) ? (this._normal.y / normal2DLength) : 0;
const normalFacingDot = Math.abs((facingX * normalX) + (facingY * normalY));
const transitionLow = 0.6;
const transitionHigh = 0.8;
let oppositeWeight = 0;
if(shouldMirror && oppositeTexture)
{
if(normalFacingDot >= transitionHigh) oppositeWeight = 1;
else if(normalFacingDot > transitionLow) oppositeWeight = (normalFacingDot - transitionLow) / (transitionHigh - transitionLow);
}
if(oppositeWeight < 1)
{
const sprite = new Sprite(texture);
sprite.anchor.set(0.5, 1);
sprite.position.set(Math.trunc(x), Math.trunc(y));
sprite.scale.set(1, 1);
sprite.tint = 0xCFE3FF;
sprite.alpha = alpha * (1 - oppositeWeight);
container.addChild(sprite);
}
if(oppositeWeight > 0 && oppositeTexture)
{
const sprite = new Sprite(oppositeTexture);
sprite.anchor.set(0.5, 1);
sprite.position.set(Math.trunc(x), Math.trunc(y));
sprite.scale.set(1, 1);
sprite.tint = 0xCFE3FF;
sprite.alpha = alpha * oppositeWeight;
container.addChild(sprite);
}
return true;
};
for(const avatar of avatars)
const closestMask = this._windowMasks.reduce((best, mask) =>
{
if(!avatar?.texture?.source || avatar.texture.source.destroyed || !avatar.texture.source.style || !avatar.location) continue;
const score = Math.abs(mask.leftSideLoc - leftSideLoc) + Math.abs(mask.rightSideLoc - rightSideLoc);
let firstSeenAt = this._windowReflectionFirstSeenAt.get(avatar.id);
if(!best || (score < best.score)) return { mask, score };
if(firstSeenAt === undefined)
{
firstSeenAt = now;
}
return best;
}, null as { mask: { leftSideLoc: number; rightSideLoc: number }; score: number } | null);
const elapsed = Math.min(fadeDurationMs, Math.max(0, (now - firstSeenAt)));
const progress = (elapsed / fadeDurationMs);
const alpha = (0.4 * progress);
if(!closestMask || (closestMask.score > 3)) return false;
if(!addReflectionSprite(avatar.texture, avatar.oppositeTexture, avatar.location, alpha, avatar.verticalOffset || 0, avatar.direction || 0, avatar.id)) continue;
const x = (canvasWidth - ((canvasWidth * leftSideLoc) / this._leftSide.length));
const y = (canvasHeight - ((canvasHeight * rightSideLoc) / this._rightSide.length)) + verticalOffset;
if(!this._windowReflectionFirstSeenAt.has(avatar.id)) this._windowReflectionFirstSeenAt.set(avatar.id, firstSeenAt);
const toPlaneX = (this._location.x - location.x);
const toPlaneY = (this._location.y - location.y);
const toPlaneLength = Math.hypot(toPlaneX, toPlaneY);
visibleAvatarIds.add(avatar.id);
this._windowReflectionFadeOut.delete(avatar.id);
const facingRadians = ((((direction - 90) % 360) + 360) % 360) * (Math.PI / 180);
const facingX = Math.cos(facingRadians);
const facingY = Math.sin(facingRadians);
const facingWindow = (toPlaneLength > 0.001)
? (((facingX * toPlaneX) + (facingY * toPlaneY)) / toPlaneLength) > 0.5
: false;
const storedLocation = new Vector3d();
storedLocation.assign(avatar.location);
const deltaLeft = Math.abs(closestMask.mask.leftSideLoc - leftSideLoc);
const deltaRight = Math.abs(closestMask.mask.rightSideLoc - rightSideLoc);
this._windowReflectionLastVisible.set(avatar.id, {
texture: avatar.texture,
oppositeTexture: avatar.oppositeTexture,
location: storedLocation,
verticalOffset: avatar.verticalOffset || 0,
direction: avatar.direction || 0
});
const isInFrontOfWindow = ((closestMask.score <= 2) && ((deltaLeft <= 0.9) || (deltaRight <= 0.9)));
const shouldMirror = isInFrontOfWindow;
const normal2DLength = Math.hypot(this._normal.x, this._normal.y);
const normalX = (normal2DLength > 0.0001) ? (this._normal.x / normal2DLength) : 0;
const normalY = (normal2DLength > 0.0001) ? (this._normal.y / normal2DLength) : 0;
const normalFacingDot = Math.abs((facingX * normalX) + (facingY * normalY));
const transitionLow = 0.6;
const transitionHigh = 0.8;
let oppositeWeight = 0;
if(shouldMirror && oppositeTexture)
{
if(normalFacingDot >= transitionHigh) oppositeWeight = 1;
else if(normalFacingDot > transitionLow)
oppositeWeight = (normalFacingDot - transitionLow) / (transitionHigh - transitionLow);
}
for(const [id, lastVisible] of this._windowReflectionLastVisible)
if(oppositeWeight < 1)
{
if(visibleAvatarIds.has(id) || this._windowReflectionFadeOut.has(id)) continue;
if(!lastVisible.texture?.source || lastVisible.texture.source.destroyed || !lastVisible.texture.source.style)
{
this._windowReflectionLastVisible.delete(id);
this._windowReflectionFirstSeenAt.delete(id);
continue;
}
this._windowReflectionFadeOut.set(id, {
texture: lastVisible.texture,
oppositeTexture: lastVisible.oppositeTexture,
location: lastVisible.location,
verticalOffset: lastVisible.verticalOffset,
direction: lastVisible.direction,
startedAt: now
});
this._windowReflectionLastVisible.delete(id);
this._windowReflectionFirstSeenAt.delete(id);
const sprite = new Sprite(texture);
sprite.anchor.set(0.5, 1);
sprite.position.set(Math.trunc(x), Math.trunc(y));
sprite.tint = 0xCFE3FF;
sprite.alpha = alpha * (1 - oppositeWeight);
container.addChild(sprite);
}
for(const [id, fadeOut] of this._windowReflectionFadeOut)
if(oppositeWeight > 0 && oppositeTexture)
{
const elapsed = (now - fadeOut.startedAt);
if(elapsed >= fadeDurationMs)
{
this._windowReflectionFadeOut.delete(id);
continue;
}
const alpha = (0.4 * (1 - (elapsed / fadeDurationMs)));
if(!addReflectionSprite(fadeOut.texture, fadeOut.oppositeTexture, fadeOut.location, alpha, fadeOut.verticalOffset, fadeOut.direction, id)) this._windowReflectionFadeOut.delete(id);
const sprite = new Sprite(oppositeTexture);
sprite.anchor.set(0.5, 1);
sprite.position.set(Math.trunc(x), Math.trunc(y));
sprite.tint = 0xCFE3FF;
sprite.alpha = alpha * oppositeWeight;
container.addChild(sprite);
}
if(!container.children.length)
{
container.destroy({ children: true });
return true;
};
if(!avatars.length)
{
this._windowReflectionFirstSeenAt.clear();
this._windowReflectionLastVisible.clear();
}
for(const avatar of avatars)
{
if(!avatar?.texture?.source || avatar.texture.source.destroyed || !avatar.texture.source.style || !avatar.location)
continue;
return;
}
let firstSeenAt = this._windowReflectionFirstSeenAt.get(avatar.id);
if(this._maskFilter) container.filters = [this._maskFilter];
if(firstSeenAt === undefined) firstSeenAt = now;
GetRenderer().render({
target: this._planeTexture,
container,
transform: this.getMatrixForDimensions(canvasWidth, canvasHeight),
clear: false
const elapsed = Math.min(fadeDurationMs, Math.max(0, (now - firstSeenAt)));
const alpha = (0.4 * (elapsed / fadeDurationMs));
if(!addReflectionSprite(
avatar.texture,
avatar.oppositeTexture,
avatar.location,
alpha,
avatar.verticalOffset || 0,
avatar.direction || 0,
avatar.id))
continue;
if(!this._windowReflectionFirstSeenAt.has(avatar.id))
this._windowReflectionFirstSeenAt.set(avatar.id, firstSeenAt);
visibleAvatarIds.add(avatar.id);
this._windowReflectionFadeOut.delete(avatar.id);
const storedLocation = new Vector3d();
storedLocation.assign(avatar.location);
this._windowReflectionLastVisible.set(avatar.id, {
texture: avatar.texture,
oppositeTexture: avatar.oppositeTexture,
location: storedLocation,
verticalOffset: avatar.verticalOffset || 0,
direction: avatar.direction || 0
});
}
// move to fade-out (NO destruction)
for(const [id, lastVisible] of this._windowReflectionLastVisible)
{
if(visibleAvatarIds.has(id) || this._windowReflectionFadeOut.has(id)) continue;
this._windowReflectionFadeOut.set(id, {
...lastVisible,
startedAt: now
});
container.destroy({ children: true });
this._windowReflectionLastVisible.delete(id);
this._windowReflectionFirstSeenAt.delete(id);
}
// fade-out rendering (NO destruction)
for(const [id, fadeOut] of this._windowReflectionFadeOut)
{
const elapsed = (now - fadeOut.startedAt);
if(elapsed >= fadeDurationMs)
{
this._windowReflectionFadeOut.delete(id);
continue;
}
const alpha = (0.4 * (1 - (elapsed / fadeDurationMs)));
if(!addReflectionSprite(
fadeOut.texture,
fadeOut.oppositeTexture,
fadeOut.location,
alpha,
fadeOut.verticalOffset,
fadeOut.direction,
id))
{
this._windowReflectionFadeOut.delete(id);
}
}
if(!container.children.length)
{
container.destroy({ children: true });
if(!avatars.length)
{
this._windowReflectionFirstSeenAt.clear();
this._windowReflectionLastVisible.clear();
}
return;
}
if(this._maskFilter) container.filters = [this._maskFilter];
GetRenderer().render({
target: this._planeTexture,
container,
transform: this.getMatrixForDimensions(canvasWidth, canvasHeight),
clear: false
});
container.destroy({ children: true });
}
private updateCorners(geometry: IRoomGeometry): void
{
this._cornerA.assign(geometry.getScreenPosition(this._location));
@@ -248,6 +248,7 @@ export class BadgeImageManager
if(!renderedLayers) return false;
const texture = TextureUtils.generateTexture(container);
container.destroy({ children: true });
GetAssetManager().setTexture(groupBadge.code, texture);
GetEventDispatcher().dispatchEvent(new BadgeImageReadyEvent(groupBadge.code, texture));