🆙 Update for the wf_slc_users_neighborhood

This commit is contained in:
duckietm
2026-03-10 15:40:24 +01:00
parent 9d519a8446
commit ec0aef3c41
6 changed files with 198 additions and 7 deletions
@@ -3,6 +3,7 @@
export interface IRoomPlane
{
uniqueId: number;
type: number;
location: IVector3D;
leftSide: IVector3D;
rightSide: IVector3D;
@@ -51,6 +51,10 @@ export class FurnitureGuildIsometricBadgeVisualization extends IsometricImageFur
protected generateTransformedThumbnail(texture: Texture, asset: IGraphicAsset): Texture
{
// Render into a texture exactly matching the asset slot (e.g. 40×58 for guild_forum layer i).
// dScale is derived so the sheared content fills the slot top-to-bottom without overflow:
// shear contribution = 0.5 * renderWidth, vertical fill = dScale * texture.height
// => dScale = (renderHeight - 0.5 * renderWidth) / texture.height
const renderWidth = asset.width || 64;
const renderHeight = asset.height || renderWidth;
const difference = (renderWidth / texture.width);
@@ -85,6 +89,7 @@ export class FurnitureGuildIsometricBadgeVisualization extends IsometricImageFur
matrix.ty = 0;
}
// Pass the matrix directly as a render transform — preserves full skew/shear in Pixi.js v8.
return TextureUtils.createAndWriteRenderTexture(renderWidth, renderHeight, new Sprite(texture), matrix);
}
@@ -80,6 +80,9 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza
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);
@@ -225,8 +225,7 @@ export class RoomPlane implements IRoomPlane
switch(this._type)
{
case RoomPlane.TYPE_FLOOR: {
const heightOffset = (this._location.z + Math.min(0, this._leftSide.z, this._rightSide.z)) * geometry.scale;
relativeDepth = (relativeDepth - heightOffset);
relativeDepth = (relativeDepth - ((this._location.z + Math.min(0, this._leftSide.z, this._rightSide.z)) * 8));
break;
}
case RoomPlane.TYPE_LANDSCAPE:
@@ -701,9 +701,11 @@ export class RoomVisualization extends RoomObjectSpriteVisualization implements
{
if(plane.visible)
{
depth = ((plane.relativeDepth + this.floorRelativeDepth) + (id / 1000));
if(plane.type !== RoomPlane.TYPE_FLOOR)
if(plane.type === RoomPlane.TYPE_FLOOR)
{
depth = ((plane.relativeDepth + this.floorRelativeDepth) + (id / 1000));
}
else
{
depth = ((plane.relativeDepth + this.wallRelativeDepth) + (id / 1000));
+183 -2
View File
@@ -1,8 +1,8 @@
import { IRoomCanvasMouseListener, IRoomGeometry, IRoomObject, IRoomObjectSprite, IRoomObjectSpriteVisualization, IRoomRenderingCanvas, IRoomSpriteCanvasContainer, IRoomSpriteMouseEvent, MouseEventType, RoomObjectSpriteData, RoomObjectSpriteType } from '@nitrots/api';
import { IPlaneVisualization, IRoomCanvasMouseListener, IRoomGeometry, IRoomObject, IRoomObjectSprite, IRoomObjectSpriteVisualization, IRoomPlane, IRoomRenderingCanvas, IRoomSpriteCanvasContainer, IRoomSpriteMouseEvent, MouseEventType, RoomObjectSpriteData, RoomObjectSpriteType } from '@nitrots/api';
import { GetConfiguration } from '@nitrots/configuration';
import { RoomSpriteMouseEvent } from '@nitrots/events';
import { GetTicker, TextureUtils, Vector3d } from '@nitrots/utils';
import { Container, Matrix, Point, Rectangle, Sprite, Texture } from 'pixi.js';
import { Container, Graphics, Matrix, Point, Rectangle, Sprite, Texture } from 'pixi.js';
import { RoomEnterEffect, RoomGeometry, RoomRotatingEffect, RoomShakingEffect } from '../utils';
import { RoomObjectCache, RoomObjectCacheItem } from './cache';
import { ExtendedSprite, ObjectMouseData, SortableSprite } from './utils';
@@ -18,6 +18,11 @@ export class RoomSpriteCanvas implements IRoomRenderingCanvas
private _master: Container = null;
private _display: Container = null;
private _mask: Sprite = null;
private _boundaryMask: Graphics = null;
private _lastBoundaryOffsetX: number = NaN;
private _lastBoundaryOffsetY: number = NaN;
private _lastBoundaryGeometryId: number = -1;
private _lastBoundaryScale: number = NaN;
private _sortableSprites: SortableSprite[] = [];
private _spriteCount: number = 0;
@@ -91,6 +96,13 @@ export class RoomSpriteCanvas implements IRoomRenderingCanvas
this._display = display;
}
if(!this._boundaryMask)
{
this._boundaryMask = new Graphics();
this._master.addChild(this._boundaryMask);
}
}
public dispose(): void
@@ -106,6 +118,8 @@ export class RoomSpriteCanvas implements IRoomRenderingCanvas
if(this._mask) this._mask = null;
if(this._boundaryMask) this._boundaryMask = null;
if(this._objectCache)
{
this._objectCache.dispose();
@@ -246,6 +260,170 @@ export class RoomSpriteCanvas implements IRoomRenderingCanvas
this.screenOffsetY = (offsetPoint.y - (point.y * this._scale));
}
private updateBoundaryMask(): void
{
if(!this._boundaryMask || !this._display || !this._geometry) return;
const geometryId = this._geometry.updateId;
const offsetX = this._screenOffsetX;
const offsetY = this._screenOffsetY;
const scale = this._scale;
if(geometryId === this._lastBoundaryGeometryId && offsetX === this._lastBoundaryOffsetX && offsetY === this._lastBoundaryOffsetY && scale === this._lastBoundaryScale) return;
this._lastBoundaryGeometryId = geometryId;
this._lastBoundaryOffsetX = offsetX;
this._lastBoundaryOffsetY = offsetY;
this._lastBoundaryScale = scale;
const pts: { x: number; y: number }[] = [];
const w2 = this._width / 2;
const h2 = this._height / 2;
for(const object of this._container.objects.values())
{
if(!object) continue;
const viz = object.visualization as unknown as IPlaneVisualization;
if(!viz || !viz.planes) continue;
for(const plane of (viz.planes as IRoomPlane[]))
{
if(!plane || plane.type === 3) continue;
const loc = plane.location;
const ls = plane.leftSide;
const rs = plane.rightSide;
const corners = [
new Vector3d(loc.x, loc.y, loc.z),
new Vector3d(loc.x + rs.x, loc.y + rs.y, loc.z + rs.z),
new Vector3d(loc.x + ls.x + rs.x, loc.y + ls.y + rs.y, loc.z + ls.z + rs.z),
new Vector3d(loc.x + ls.x, loc.y + ls.y, loc.z + ls.z)
];
for(const c of corners)
{
const sp = this._geometry.getScreenPosition(c);
if(!sp) continue;
pts.push({ x: (sp.x + w2) * scale + offsetX, y: (sp.y + h2) * scale + offsetY });
}
}
break;
}
this._boundaryMask.clear();
if(pts.length < 3)
{
if(this._display.mask === this._boundaryMask) this._display.mask = this._mask ?? null;
return;
}
const hull = RoomSpriteCanvas.convexHull(pts);
const maskPolygon = RoomSpriteCanvas.createMaskPolygon(hull);
this._boundaryMask.poly(maskPolygon.flatMap(p => [p.x, p.y]));
this._boundaryMask.fill(0xFFFFFF);
if(this._display.mask !== this._boundaryMask)
{
this._display.mask = this._boundaryMask;
}
}
private static convexHull(points: { x: number; y: number }[]): { x: number; y: number }[]
{
if(points.length < 3) return points;
const sorted = [...points].sort((a, b) => (a.x !== b.x ? a.x - b.x : a.y - b.y));
const cross = (o: { x: number; y: number }, a: { x: number; y: number }, b: { x: number; y: number }) =>
(a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
const lower: { x: number; y: number }[] = [];
for(const p of sorted)
{
while(lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) lower.pop();
lower.push(p);
}
const upper: { x: number; y: number }[] = [];
for(let i = sorted.length - 1; i >= 0; i--)
{
const p = sorted[i];
while(upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) upper.pop();
upper.push(p);
}
lower.pop();
upper.pop();
return [...lower, ...upper];
}
private static createMaskPolygon(hull: { x: number; y: number }[], extension: number = 5000): { x: number; y: number }[]
{
if(hull.length < 3) return hull;
let leftIdx = 0;
let rightIdx = 0;
for(let i = 1; i < hull.length; i++)
{
if(hull[i].x < hull[leftIdx].x) leftIdx = i;
if(hull[i].x > hull[rightIdx].x) rightIdx = i;
}
const n = hull.length;
// Collect arc going CCW: leftIdx → rightIdx via increasing indices
const arcCCW: { x: number; y: number }[] = [];
let idx = leftIdx;
while(idx !== rightIdx)
{
arcCCW.push(hull[idx]);
idx = (idx + 1) % n;
}
arcCCW.push(hull[rightIdx]);
// Collect arc going CW: leftIdx → rightIdx via decreasing indices
const arcCW: { x: number; y: number }[] = [];
idx = leftIdx;
while(idx !== rightIdx)
{
arcCW.push(hull[idx]);
idx = (idx - 1 + n) % n;
}
arcCW.push(hull[rightIdx]);
// Bottom arc = the arc with larger average Y (floor/front tiles)
const avgCCW = arcCCW.reduce((s, p) => s + p.y, 0) / arcCCW.length;
const avgCW = arcCW.reduce((s, p) => s + p.y, 0) / arcCW.length;
const bottomArc = avgCCW >= avgCW ? arcCCW : arcCW;
// Build polygon: extend upward far above walls, then trace bottom boundary
return [
{ x: hull[leftIdx].x, y: -extension },
{ x: hull[rightIdx].x, y: -extension },
...bottomArc.slice().reverse()
];
}
public render(time: number, update: boolean = false): void
{
this._canvasUpdated = false;
@@ -319,6 +497,9 @@ export class RoomSpriteCanvas implements IRoomRenderingCanvas
this.cleanSprites(spriteCount);
this.updateBoundaryMask();
if(update || updateVisuals) this._canvasUpdated = true;
this._renderTimestamp = this._totalTimeRunning;