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
🆙 Windows 100% done!
- Background color added - Fixed one missing window - Better cloud animation - (NEW) real miror view in windows
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { IVector3D } from '@nitrots/api';
|
||||
import { Vector3d } from '@nitrots/utils';
|
||||
import { Texture } from 'pixi.js';
|
||||
|
||||
interface IWindowReflectionAvatarState
|
||||
{
|
||||
id: number;
|
||||
texture: Texture;
|
||||
location: IVector3D;
|
||||
}
|
||||
|
||||
export class RoomWindowReflectionState
|
||||
{
|
||||
private static _avatars: Map<number, IWindowReflectionAvatarState> = new Map();
|
||||
private static _updateId: number = 0;
|
||||
|
||||
public static setAvatar(id: number, texture: Texture, location: IVector3D): void
|
||||
{
|
||||
if(!texture || !location) return;
|
||||
|
||||
const storedLocation = new Vector3d();
|
||||
|
||||
storedLocation.assign(location);
|
||||
|
||||
this._avatars.set(id, {
|
||||
id,
|
||||
texture,
|
||||
location: storedLocation
|
||||
});
|
||||
|
||||
this._updateId++;
|
||||
}
|
||||
|
||||
public static removeAvatar(id: number): void
|
||||
{
|
||||
if(this._avatars.delete(id)) this._updateId++;
|
||||
}
|
||||
|
||||
public static getAvatars(): IWindowReflectionAvatarState[]
|
||||
{
|
||||
return Array.from(this._avatars.values());
|
||||
}
|
||||
|
||||
public static get updateId(): number
|
||||
{
|
||||
return this._updateId;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { GetAssetManager } from '@nitrots/assets';
|
||||
import { AdvancedMap } from '@nitrots/utils';
|
||||
import { Texture } from 'pixi.js';
|
||||
import { RoomObjectSpriteVisualization } from '../RoomObjectSpriteVisualization';
|
||||
import { RoomWindowReflectionState } from '../RoomWindowReflectionState';
|
||||
import { AvatarVisualizationData } from './AvatarVisualizationData';
|
||||
import { ExpressionAdditionFactory, FloatingIdleZAddition, GameClickTargetAddition, GuideStatusBubbleAddition, IAvatarAddition, MutedBubbleAddition, NumberBubbleAddition, TypingBubbleAddition } from './additions';
|
||||
|
||||
@@ -119,7 +120,7 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
this._isLaying = false;
|
||||
this._layInside = false;
|
||||
this._isAnimating = false;
|
||||
this._extraSpritesStartIndex = 2;
|
||||
this._extraSpritesStartIndex = AvatarVisualization.INITIAL_RESERVED_SPRITES;
|
||||
this._forcedAnimFrames = 0;
|
||||
this._updatesUntilFrameUpdate = 0;
|
||||
|
||||
@@ -151,6 +152,8 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
|
||||
if(this._avatarImage) this._avatarImage.dispose();
|
||||
|
||||
if(this.object) RoomWindowReflectionState.removeAvatar(this.object.id);
|
||||
|
||||
this._shadow = null;
|
||||
this._disposed = true;
|
||||
}
|
||||
@@ -265,6 +268,8 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
}
|
||||
else
|
||||
{
|
||||
this.updateWindowReflectionSource();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,7 +278,6 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
if(!_local_20 || (_local_20.length < 3)) _local_20 = AvatarVisualization.DEFAULT_CANVAS_OFFSETS;
|
||||
|
||||
const sprite = this.getSprite(AvatarVisualization.SPRITE_INDEX_AVATAR);
|
||||
|
||||
if(sprite)
|
||||
{
|
||||
const highlightEnabled = ((this.object.model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT_ENABLE) === 1) && (this.object.model.getValue<number>(RoomObjectVariable.FIGURE_HIGHLIGHT) === 1));
|
||||
@@ -325,6 +329,8 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
}
|
||||
}
|
||||
|
||||
this.updateWindowReflectionSource();
|
||||
|
||||
const typingBubble = this.getAddition(AvatarVisualization.TYPING_BUBBLE_ID) as TypingBubbleAddition;
|
||||
|
||||
if(typingBubble)
|
||||
@@ -993,6 +999,22 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
this.clearAvatar();
|
||||
}
|
||||
|
||||
private updateWindowReflectionSource(): void
|
||||
{
|
||||
if(!this.object) return;
|
||||
|
||||
const sprite = this.getSprite(AvatarVisualization.SPRITE_INDEX_AVATAR);
|
||||
|
||||
if(sprite?.texture)
|
||||
{
|
||||
RoomWindowReflectionState.setAvatar(this.object.id, sprite.texture, this.object.getLocation());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RoomWindowReflectionState.removeAvatar(this.object.id);
|
||||
}
|
||||
|
||||
private clearAvatar(): void
|
||||
{
|
||||
const sprite = this.getSprite(AvatarVisualization.AVATAR_LAYER_ID);
|
||||
@@ -1003,6 +1025,7 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
sprite.alpha = 255;
|
||||
}
|
||||
|
||||
|
||||
for(const avatar of this._cachedAvatars.getValues()) avatar && avatar.dispose();
|
||||
|
||||
for(const avatar of this._cachedAvatarEffects.getValues()) avatar && avatar.dispose();
|
||||
@@ -1011,6 +1034,8 @@ export class AvatarVisualization extends RoomObjectSpriteVisualization implement
|
||||
this._cachedAvatarEffects.reset();
|
||||
|
||||
this._avatarImage = null;
|
||||
|
||||
if(this.object) RoomWindowReflectionState.removeAvatar(this.object.id);
|
||||
}
|
||||
|
||||
private getAddition(id: number): IAvatarAddition
|
||||
|
||||
@@ -3,6 +3,7 @@ import { GetAssetManager } from '@nitrots/assets';
|
||||
import { GetRenderer, GetTexturePool, PlaneMaskFilter, Vector3d } from '@nitrots/utils';
|
||||
import { Container, Filter, Graphics, Matrix, Point, RenderTexture, Sprite, Texture, TilingSprite } from 'pixi.js';
|
||||
import { RoomGeometry } from '../../../utils';
|
||||
import { RoomWindowReflectionState } from '../RoomWindowReflectionState';
|
||||
import { PlaneVisualizationAnimationLayer } from './animated';
|
||||
import { RoomPlaneBitmapMask } from './RoomPlaneBitmapMask';
|
||||
import { RoomPlaneRectangleMask } from './RoomPlaneRectangleMask';
|
||||
@@ -88,6 +89,10 @@ export class RoomPlane implements IRoomPlane
|
||||
private _lastLandscapeDebugSignature: string = null;
|
||||
private _hasWindowMask: boolean = false;
|
||||
private _windowMasks: { leftSideLoc: number; rightSideLoc: number }[] = [];
|
||||
private _lastWindowReflectionUpdateId: number = -1;
|
||||
private _windowReflectionFirstSeenAt: Map<number, number> = new Map();
|
||||
private _windowReflectionLastVisible: Map<number, { texture: Texture; location: IVector3D }> = new Map();
|
||||
private _windowReflectionFadeOut: Map<number, { texture: Texture; location: IVector3D; startedAt: number }> = new Map();
|
||||
|
||||
constructor(origin: IVector3D, location: IVector3D, leftSide: IVector3D, rightSide: IVector3D, type: number, usesMask: boolean, secondaryNormals: IVector3D[], randomSeed: number, textureOffsetX: number = 0, textureOffsetY: number = 0, textureMaxX: number = 0, textureMaxY: number = 0)
|
||||
{
|
||||
@@ -259,7 +264,6 @@ export class RoomPlane implements IRoomPlane
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to "default" plane if the requested landscape plane wasn't found
|
||||
if(!plane && planeType === RoomPlane.TYPE_LANDSCAPE)
|
||||
{
|
||||
const roomCollection2 = GetAssetManager().getCollection('room');
|
||||
@@ -334,7 +338,6 @@ export class RoomPlane implements IRoomPlane
|
||||
let backgroundColor: number = null;
|
||||
if(backgroundColorStr)
|
||||
{
|
||||
// Convert hex string like "#FEFEFE" to number
|
||||
backgroundColor = parseInt(backgroundColorStr.replace('#', ''), 16);
|
||||
}
|
||||
|
||||
@@ -514,10 +517,8 @@ export class RoomPlane implements IRoomPlane
|
||||
|
||||
this._landscapeRenderWidth = width;
|
||||
this._landscapeRenderHeight = height;
|
||||
// Use total landscape width for animation canvas, but always use actual height
|
||||
// The renderMaxY is often too small (e.g., 160) while actual height is much larger (e.g., 755)
|
||||
this._animationCanvasWidth = renderMaxX || width;
|
||||
this._animationCanvasHeight = height; // Always use actual landscape height for animations
|
||||
this._animationCanvasHeight = height;
|
||||
this._landscapeOffsetX = renderOffsetX;
|
||||
this._landscapeOffsetY = renderOffsetY;
|
||||
|
||||
@@ -601,18 +602,31 @@ export class RoomPlane implements IRoomPlane
|
||||
|
||||
this._planeTexture.source.label = `room_plane_${ this._uniqueId.toString() }`;
|
||||
|
||||
let reflectionUpdate = false;
|
||||
|
||||
if(this._type === RoomPlane.TYPE_LANDSCAPE && this._windowMasks.length)
|
||||
{
|
||||
const reflectionUpdateId = RoomWindowReflectionState.updateId;
|
||||
|
||||
if(reflectionUpdateId !== this._lastWindowReflectionUpdateId)
|
||||
{
|
||||
this._lastWindowReflectionUpdateId = reflectionUpdateId;
|
||||
reflectionUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
let animationUpdate = false;
|
||||
if(this._isAnimated && this._type === RoomPlane.TYPE_LANDSCAPE)
|
||||
{
|
||||
const timeSinceLastUpdate = timeSinceStartMs - this._lastAnimationUpdate;
|
||||
if(timeSinceLastUpdate >= RoomPlane.ANIMATION_UPDATE_INTERVAL || needsUpdate)
|
||||
if(timeSinceLastUpdate >= RoomPlane.ANIMATION_UPDATE_INTERVAL || needsUpdate || reflectionUpdate)
|
||||
{
|
||||
animationUpdate = true;
|
||||
this._lastAnimationUpdate = timeSinceStartMs;
|
||||
}
|
||||
}
|
||||
|
||||
if(needsUpdate || animationUpdate)
|
||||
if(needsUpdate || animationUpdate || reflectionUpdate)
|
||||
{
|
||||
const isLandscape = (this._type === RoomPlane.TYPE_LANDSCAPE);
|
||||
const hasLandscapeLayeredRendering = (isLandscape && (this._landscapeBackgroundTexture !== null || this._landscapeForegroundTexture !== null || this._animationLayers.length > 0 || this._landscapeBackgroundColor !== null));
|
||||
@@ -649,17 +663,20 @@ export class RoomPlane implements IRoomPlane
|
||||
this.renderLandscapeLayer(this._landscapeBackgroundTexture, this._landscapeBackgroundTint, this._landscapeBaseAlignBottom);
|
||||
}
|
||||
|
||||
// Render animation layers (clouds) - between background and foreground
|
||||
if(this._isAnimated && this._type === RoomPlane.TYPE_LANDSCAPE && this._animationLayers.length > 0)
|
||||
{
|
||||
this.renderAnimationLayers(timeSinceStartMs, geometry);
|
||||
}
|
||||
|
||||
// Render foreground layer for landscapes on top of background and clouds
|
||||
if(this._type === RoomPlane.TYPE_LANDSCAPE && this._landscapeForegroundTexture)
|
||||
{
|
||||
this.renderLandscapeLayer(this._landscapeForegroundTexture, this._landscapeForegroundTint, this._landscapeForegroundAlignBottom);
|
||||
}
|
||||
|
||||
if(this._type === RoomPlane.TYPE_LANDSCAPE && this._windowMasks.length)
|
||||
{
|
||||
this.renderWindowReflections();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -794,7 +811,6 @@ export class RoomPlane implements IRoomPlane
|
||||
|
||||
if(canvasWidth <= 0 || canvasHeight <= 0) return;
|
||||
|
||||
// Create a solid color rectangle to fill the background
|
||||
const colorGraphics = new Graphics();
|
||||
colorGraphics.rect(0, 0, canvasWidth, canvasHeight);
|
||||
colorGraphics.fill(this._landscapeBackgroundColor);
|
||||
@@ -851,6 +867,145 @@ export class RoomPlane implements IRoomPlane
|
||||
colorGraphics.destroy();
|
||||
}
|
||||
|
||||
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, location: IVector3D, alpha: number): boolean => {
|
||||
if(!texture || !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));
|
||||
|
||||
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;
|
||||
|
||||
container.addChild(sprite);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
for(const avatar of avatars)
|
||||
{
|
||||
if(!avatar?.texture || !avatar.location) continue;
|
||||
|
||||
let firstSeenAt = this._windowReflectionFirstSeenAt.get(avatar.id);
|
||||
|
||||
if(firstSeenAt === undefined)
|
||||
{
|
||||
firstSeenAt = now;
|
||||
}
|
||||
|
||||
const elapsed = Math.min(fadeDurationMs, Math.max(0, (now - firstSeenAt)));
|
||||
const progress = (elapsed / fadeDurationMs);
|
||||
const alpha = (0.4 * progress);
|
||||
|
||||
if(!addReflectionSprite(avatar.texture, avatar.location, alpha)) 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,
|
||||
location: storedLocation
|
||||
});
|
||||
}
|
||||
|
||||
for(const [id, lastVisible] of this._windowReflectionLastVisible)
|
||||
{
|
||||
if(visibleAvatarIds.has(id) || this._windowReflectionFadeOut.has(id)) continue;
|
||||
|
||||
this._windowReflectionFadeOut.set(id, {
|
||||
texture: lastVisible.texture,
|
||||
location: lastVisible.location,
|
||||
startedAt: now
|
||||
});
|
||||
|
||||
this._windowReflectionLastVisible.delete(id);
|
||||
this._windowReflectionFirstSeenAt.delete(id);
|
||||
}
|
||||
|
||||
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.location, alpha)) 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));
|
||||
|
||||
Reference in New Issue
Block a user