diff --git a/packages/avatar/src/AvatarAssetDownloadManager.ts b/packages/avatar/src/AvatarAssetDownloadManager.ts index 1bac623..ada5077 100644 --- a/packages/avatar/src/AvatarAssetDownloadManager.ts +++ b/packages/avatar/src/AvatarAssetDownloadManager.ts @@ -23,9 +23,18 @@ export class AvatarAssetDownloadManager this._structure = structure; } + private static DEFAULT_MANDATORY_LIBS: string[] = ['hh_human_face']; + public async init(): Promise { - this._missingMandatoryLibs = GetConfiguration().getValue('avatar.mandatory.libraries'); + const configuredLibs = GetConfiguration().getValue('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('avatar.figuremap.url'); diff --git a/packages/avatar/src/AvatarImage.ts b/packages/avatar/src/AvatarImage.ts index b98e2e3..4cc52cf 100644 --- a/packages/avatar/src/AvatarImage.ts +++ b/packages/avatar/src/AvatarImage.ts @@ -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, diff --git a/packages/camera/src/RoomCameraWidgetManager.ts b/packages/camera/src/RoomCameraWidgetManager.ts index 1d28a4c..85b7067 100644 --- a/packages/camera/src/RoomCameraWidgetManager.ts +++ b/packages/camera/src/RoomCameraWidgetManager.ts @@ -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 diff --git a/packages/communication/src/CommunicationManager.ts b/packages/communication/src/CommunicationManager.ts index 0fa6a90..3718463 100644 --- a/packages/communication/src/CommunicationManager.ts +++ b/packages/communication/src/CommunicationManager.ts @@ -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++) { diff --git a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts index 7caa949..7e344e8 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts @@ -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; } diff --git a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts index 1fdb4d2..4c0697a 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts @@ -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); diff --git a/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts index 1b1a544..73cb319 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts @@ -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; diff --git a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts index 3e5ad89..a5ee557 100644 --- a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts +++ b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts @@ -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('_'); - } -} \ No newline at end of file +} diff --git a/packages/room/src/object/visualization/room/RoomPlane.ts b/packages/room/src/object/visualization/room/RoomPlane.ts index a1391bd..3556447 100644 --- a/packages/room/src/object/visualization/room/RoomPlane.ts +++ b/packages/room/src/object/visualization/room/RoomPlane.ts @@ -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(); + + 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(); - - 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)); diff --git a/packages/session/src/badge/BadgeImageManager.ts b/packages/session/src/badge/BadgeImageManager.ts index 6a51f7e..9bb5647 100644 --- a/packages/session/src/badge/BadgeImageManager.ts +++ b/packages/session/src/badge/BadgeImageManager.ts @@ -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));