You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-19 15:06:20 +00:00
Merge remote-tracking branch 'origin/main' into feature/checkpoint-20260403
This commit is contained in:
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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++) {
|
||||||
|
|||||||
+12
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -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;
|
||||||
|
|||||||
+109
-72
@@ -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));
|
||||||
|
|||||||
Reference in New Issue
Block a user