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; this._structure = structure;
} }
private static DEFAULT_MANDATORY_LIBS: string[] = ['hh_human_face'];
public async init(): Promise<void> 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'); 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); const container = this.buildAvatarContainer(avatarCanvas, setType);
if(!container) return null;
GetRenderer().render({ GetRenderer().render({
target: this._activeTexture, target: this._activeTexture,
container: container, container: container,
@@ -117,7 +117,12 @@ export class RoomCameraWidgetManager implements IRoomCameraWidgetManager
TextureUtils.writeToTexture(container, renderTexture); 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> public get effects(): Map<string, IRoomCameraWidgetEffect>
@@ -62,7 +62,8 @@ export class CommunicationManager implements ICommunicationManager
t.shadowColor = 'blue'; t.shadowColor = 'blue';
t.fillRect(-20, 10, 234, 5); t.fillRect(-20, 10, 234, 5);
const i = e.toDataURL(); const i = e.toDataURL();
document.body.appendChild(e); e.width = 0;
e.height = 0;
let r = 0; let r = 0;
if (i.length === 0) return 'nothing!'; if (i.length === 0) return 'nothing!';
for (let n = 0; n < i.length; n++) { for (let n = 0; n < i.length; n++) {
@@ -283,6 +283,8 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali
tempCtx.putImageData(patchData, 0, 0); tempCtx.putImageData(patchData, 0, 0);
accCtx.drawImage(tempCanvas, frame.dims.left, frame.dims.top); 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 // Create a new canvas for this frame and create a texture from it
const frameCanvas = document.createElement('canvas'); const frameCanvas = document.createElement('canvas');
@@ -299,6 +301,9 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali
frameDelays.push(frame.delay || 10); frameDelays.push(frame.delay || 10);
} }
accCanvas.width = 0;
accCanvas.height = 0;
// Create AnimatedSprite with frame textures // Create AnimatedSprite with frame textures
if(this._frameTextures.length > 1) if(this._frameTextures.length > 1)
{ {
@@ -340,6 +345,13 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali
ctx.clearRect(0, 0, badgeCanvas.width, badgeCanvas.height); ctx.clearRect(0, 0, badgeCanvas.width, badgeCanvas.height);
ctx.drawImage(img, 0, 0, badgeCanvas.width, badgeCanvas.height); ctx.drawImage(img, 0, 0, badgeCanvas.width, badgeCanvas.height);
tex.source.update(); tex.source.update();
img.onload = null;
img.onerror = null;
};
img.onerror = () =>
{
img.onload = null;
img.onerror = null;
}; };
img.src = badgeUrl; img.src = badgeUrl;
} }
@@ -28,10 +28,19 @@ export class FurnitureDynamicThumbnailVisualization extends IsometricImageFurniV
if (image.complete && image.width > 0 && image.height > 0) { if (image.complete && image.width > 0 && image.height > 0) {
const texture = Texture.from(image); const texture = Texture.from(image);
texture.source.scaleMode = 'linear'; texture.source.scaleMode = 'linear';
this.setThumbnailImages(texture, thumbnailUrl); // Pass URL here
this.setThumbnailImages(texture, thumbnailUrl);
} else { } 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 { } else {
this.setThumbnailImages(null); this.setThumbnailImages(null);
@@ -5,6 +5,13 @@ export class FurnitureYoutubeVisualization extends FurnitureDynamicThumbnailVisu
{ {
protected static THUMBNAIL_URL: string = 'THUMBNAIL_URL'; protected static THUMBNAIL_URL: string = 'THUMBNAIL_URL';
constructor()
{
super();
this._hasOutline = false;
}
protected getThumbnailURL(): string protected getThumbnailURL(): string
{ {
if(!this.object) return null; if(!this.object) return null;
@@ -1,15 +1,16 @@
import { IGraphicAsset } from '@nitrots/api'; import { IGraphicAsset } from '@nitrots/api';
import { GetRenderer, TextureUtils } from '@nitrots/utils'; import { GetRenderer } from '@nitrots/utils';
import { Container, Graphics, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js'; import { Container, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js';
import { FurnitureAnimatedVisualization } from './FurnitureAnimatedVisualization'; import { FurnitureAnimatedVisualization } from './FurnitureAnimatedVisualization';
export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualization { export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualization {
protected static THUMBNAIL: string = 'THUMBNAIL'; protected static THUMBNAIL: string = 'THUMBNAIL';
private _thumbnailAssetNameNormal: string;
private _thumbnailImageNormal: Texture; private _thumbnailImageNormal: Texture;
private _thumbnailDirection: number; private _thumbnailDirection: number;
private _thumbnailChanged: boolean; private _thumbnailChanged: boolean;
private _thumbnailLayerId: number;
private _thumbnailTexture: Texture;
private _uniqueId: string; private _uniqueId: string;
private _photoUrl: string; private _photoUrl: string;
protected _hasOutline: boolean; protected _hasOutline: boolean;
@@ -17,10 +18,11 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
constructor() { constructor() {
super(); super();
this._thumbnailAssetNameNormal = null;
this._thumbnailImageNormal = null; this._thumbnailImageNormal = null;
this._thumbnailDirection = -1; this._thumbnailDirection = -1;
this._thumbnailChanged = false; this._thumbnailChanged = false;
this._thumbnailLayerId = -1;
this._thumbnailTexture = null;
this._uniqueId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; this._uniqueId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
this._photoUrl = null; this._photoUrl = null;
} }
@@ -56,13 +58,14 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
return; return;
} }
const thumbnailAssetName = this.getThumbnailAssetName(64);
if (this._thumbnailImageNormal) { if (this._thumbnailImageNormal) {
this.addThumbnailAsset(this._thumbnailImageNormal, 64); this.addThumbnailAsset(this._thumbnailImageNormal, 64);
} else { } else {
const layerId = 2; if (this._thumbnailTexture instanceof RenderTexture) {
const sprite = this.getSprite(layerId); this._thumbnailTexture.destroy(true);
}
this._thumbnailTexture = null;
this._thumbnailLayerId = -1;
} }
this._thumbnailChanged = false; this._thumbnailChanged = false;
@@ -76,21 +79,16 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
const layerTag = this.getLayerTag(scale, this.direction, layerId); const layerTag = this.getLayerTag(scale, this.direction, layerId);
if (layerTag === IsometricImageFurniVisualization.THUMBNAIL) { if (layerTag === IsometricImageFurniVisualization.THUMBNAIL) {
this._thumbnailLayerId = layerId;
const assetName = (this.cacheSpriteAssetName(scale, layerId, false) + this.getFrameNumber(scale, layerId)); const assetName = (this.cacheSpriteAssetName(scale, layerId, false) + this.getFrameNumber(scale, layerId));
const asset = this.getAsset(assetName, 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 if (asset) {
// furniture-defined sprite position. Fall back to centering when no asset exists. if (this._thumbnailTexture instanceof RenderTexture) {
const offsetX = asset ? asset.offsetX : -Math.floor(transformedTexture.width / 2); this._thumbnailTexture.destroy(true);
const offsetY = asset ? asset.offsetY : -Math.floor(transformedTexture.height / 2); }
this._thumbnailTexture = this.generateTransformedThumbnail(k, asset);
this.asset.addAsset(thumbnailAssetName, transformedTexture, true, offsetX, offsetY, false, false);
const placedSprite = this.getSprite(layerId);
if (placedSprite) {
placedSprite.texture = transformedTexture;
} }
return; return;
@@ -100,78 +98,117 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
} }
} }
protected generateTransformedThumbnail(texture: Texture, asset: IGraphicAsset): Texture { protected updateSprite(scale: number, layerId: number): void {
const scaleFactor = (asset?.width || 64) / texture.width; super.updateSprite(scale, layerId);
const verticalScale = 1.0265;
const matrix = new Matrix();
const frameThickness = 20;
const frameColor = 0x000000;
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: case 2:
matrix.a = scaleFactor; matrix.a = scaleX;
matrix.b = (-0.5 * scaleFactor); matrix.b = -(0.5 * scaleX);
matrix.c = 0; matrix.c = 0;
matrix.d = (scaleFactor * verticalScale); matrix.d = (scaleY / 1.6);
matrix.tx = 0; matrix.tx = 0;
matrix.ty = (0.5 * scaleFactor * texture.width); matrix.ty = (0.5 * scaleX * texW);
break; break;
case 0: case 0:
case 4: case 4:
matrix.a = scaleFactor; matrix.a = scaleX;
matrix.b = (0.5 * scaleFactor); matrix.b = (0.5 * scaleX);
matrix.c = 0; matrix.c = 0;
matrix.d = (scaleFactor * verticalScale); matrix.d = (scaleY / 1.6);
matrix.tx = 0; matrix.tx = 0;
matrix.ty = 0; matrix.ty = 0;
break; break;
default: default:
matrix.a = scaleFactor; matrix.a = scaleX;
matrix.b = 0; matrix.b = 0;
matrix.c = 0; matrix.c = 0;
matrix.d = scaleFactor; matrix.d = scaleY;
matrix.tx = 0; matrix.tx = 0;
matrix.ty = 0; matrix.ty = 0;
} }
const imgWidth = texture.width; // Calculate transformed corners manually for accurate bounds
const imgHeight = texture.height; const corners = [
const flatWidth = imgWidth + frameThickness * 2; { x: matrix.tx, y: matrix.ty },
const flatHeight = imgHeight + frameThickness * 2; { x: matrix.a * texW + matrix.tx, y: matrix.b * texW + matrix.ty },
const flatRenderTexture = TextureUtils.createAndFillRenderTexture(flatWidth, flatHeight, frameColor); { x: matrix.c * texH + matrix.tx, y: matrix.d * texH + matrix.ty },
const imageSprite = new Sprite(texture); { x: matrix.a * texW + matrix.c * texH + matrix.tx, y: matrix.b * texW + matrix.d * texH + matrix.ty }
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);
const renderTexture = RenderTexture.create({ width, height, resolution: 1 }); let minX = corners[0].x, minY = corners[0].y;
GetRenderer().render({ container: finalContainer, target: renderTexture, clear: true }); 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; 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 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(); if(planeDistance > 0.8) return false;
const fadeDurationMs = 150;
const avatars = RoomWindowReflectionState.getAvatars();
const canvasWidth = this._landscapeRenderWidth;
const canvasHeight = this._landscapeRenderHeight;
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 closestMask = this._windowMasks.reduce((best, mask) =>
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)
{ {
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) return best;
{ }, null as { mask: { leftSideLoc: number; rightSideLoc: number }; score: number } | null);
firstSeenAt = now;
}
const elapsed = Math.min(fadeDurationMs, Math.max(0, (now - firstSeenAt))); if(!closestMask || (closestMask.score > 3)) return false;
const progress = (elapsed / fadeDurationMs);
const alpha = (0.4 * progress);
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); const facingRadians = ((((direction - 90) % 360) + 360) % 360) * (Math.PI / 180);
this._windowReflectionFadeOut.delete(avatar.id); 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(); const deltaLeft = Math.abs(closestMask.mask.leftSideLoc - leftSideLoc);
storedLocation.assign(avatar.location); const deltaRight = Math.abs(closestMask.mask.rightSideLoc - rightSideLoc);
this._windowReflectionLastVisible.set(avatar.id, { const isInFrontOfWindow = ((closestMask.score <= 2) && ((deltaLeft <= 0.9) || (deltaRight <= 0.9)));
texture: avatar.texture, const shouldMirror = isInFrontOfWindow;
oppositeTexture: avatar.oppositeTexture,
location: storedLocation, const normal2DLength = Math.hypot(this._normal.x, this._normal.y);
verticalOffset: avatar.verticalOffset || 0, const normalX = (normal2DLength > 0.0001) ? (this._normal.x / normal2DLength) : 0;
direction: avatar.direction || 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; const sprite = new Sprite(texture);
sprite.anchor.set(0.5, 1);
if(!lastVisible.texture?.source || lastVisible.texture.source.destroyed || !lastVisible.texture.source.style) sprite.position.set(Math.trunc(x), Math.trunc(y));
{ sprite.tint = 0xCFE3FF;
this._windowReflectionLastVisible.delete(id); sprite.alpha = alpha * (1 - oppositeWeight);
this._windowReflectionFirstSeenAt.delete(id); container.addChild(sprite);
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);
} }
for(const [id, fadeOut] of this._windowReflectionFadeOut) if(oppositeWeight > 0 && oppositeTexture)
{ {
const elapsed = (now - fadeOut.startedAt); const sprite = new Sprite(oppositeTexture);
sprite.anchor.set(0.5, 1);
if(elapsed >= fadeDurationMs) sprite.position.set(Math.trunc(x), Math.trunc(y));
{ sprite.tint = 0xCFE3FF;
this._windowReflectionFadeOut.delete(id); sprite.alpha = alpha * oppositeWeight;
container.addChild(sprite);
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) return true;
{ };
container.destroy({ children: true });
if(!avatars.length) for(const avatar of avatars)
{ {
this._windowReflectionFirstSeenAt.clear(); if(!avatar?.texture?.source || avatar.texture.source.destroyed || !avatar.texture.source.style || !avatar.location)
this._windowReflectionLastVisible.clear(); continue;
}
return; let firstSeenAt = this._windowReflectionFirstSeenAt.get(avatar.id);
}
if(this._maskFilter) container.filters = [this._maskFilter]; if(firstSeenAt === undefined) firstSeenAt = now;
GetRenderer().render({ const elapsed = Math.min(fadeDurationMs, Math.max(0, (now - firstSeenAt)));
target: this._planeTexture, const alpha = (0.4 * (elapsed / fadeDurationMs));
container,
transform: this.getMatrixForDimensions(canvasWidth, canvasHeight), if(!addReflectionSprite(
clear: false 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 private updateCorners(geometry: IRoomGeometry): void
{ {
this._cornerA.assign(geometry.getScreenPosition(this._location)); this._cornerA.assign(geometry.getScreenPosition(this._location));
@@ -248,6 +248,7 @@ export class BadgeImageManager
if(!renderedLayers) return false; if(!renderedLayers) return false;
const texture = TextureUtils.generateTexture(container); const texture = TextureUtils.generateTexture(container);
container.destroy({ children: true });
GetAssetManager().setTexture(groupBadge.code, texture); GetAssetManager().setTexture(groupBadge.code, texture);
GetEventDispatcher().dispatchEvent(new BadgeImageReadyEvent(groupBadge.code, texture)); GetEventDispatcher().dispatchEvent(new BadgeImageReadyEvent(groupBadge.code, texture));