Move to Renderer V2

This commit is contained in:
duckietm
2024-04-03 09:27:56 +02:00
parent 110c3ad393
commit b3134ce50b
4080 changed files with 115593 additions and 66375 deletions
+160
View File
@@ -0,0 +1,160 @@
import { IRoomObject, IRoomRenderer, IRoomRenderingCanvas, IRoomSpriteCanvasContainer } from '@nitrots/api';
import { RoomSpriteCanvas } from './RoomSpriteCanvas';
export class RoomRenderer implements IRoomRenderer, IRoomSpriteCanvasContainer
{
private _objects: Map<number, IRoomObject> = new Map();
private _canvases: Map<number, IRoomRenderingCanvas> = new Map();
private _disposed: boolean = false;
private _roomObjectVariableAccurateZ: string = null;
public dispose(): void
{
if(this._disposed) return;
if(this._canvases)
{
for(const [key, canvas] of this._canvases.entries())
{
this._canvases.delete(key);
if(!canvas) continue;
canvas.dispose();
}
this._canvases = null;
}
if(this._objects)
{
this._objects = null;
}
this._disposed = true;
}
public reset(): void
{
this._objects.clear();
}
public getInstanceId(object: IRoomObject): number
{
if(!object) return -1;
return object.instanceId;
}
public getRoomObject(instanceId: number): IRoomObject
{
return this._objects.get(instanceId);
}
public addObject(object: IRoomObject): void
{
if(!object) return;
this._objects.set(this.getInstanceId(object), object);
}
public removeObject(object: IRoomObject): void
{
const instanceId = this.getInstanceId(object);
this._objects.delete(instanceId);
for(const canvas of this._canvases.values())
{
if(!canvas) continue;
const spriteCanvas = canvas as RoomSpriteCanvas;
spriteCanvas.removeFromCache(instanceId.toString());
}
}
public render(time: number, update: boolean = false): void
{
if(!this._canvases || !this._canvases.size) return;
for(const canvas of this._canvases.values()) canvas && canvas.render(time, update);
}
public update(time: number, update: boolean = false): void
{
if(!this._canvases || !this._canvases.size) return;
this.render(time, update);
for(const canvas of this._canvases.values()) canvas && canvas.update();
}
public getCanvas(id: number): IRoomRenderingCanvas
{
const existing = this._canvases.get(id);
if(!existing) return null;
return existing;
}
public createCanvas(id: number, width: number, height: number, scale: number): IRoomRenderingCanvas
{
const existing = this._canvases.get(id) as IRoomRenderingCanvas;
if(existing)
{
existing.initialize(width, height);
if(existing.geometry) existing.geometry.scale = scale;
return existing;
}
const canvas = this.createSpriteCanvas(id, width, height, scale);
if(!canvas) return;
this._canvases.set(id, canvas);
return canvas;
}
private createSpriteCanvas(id: number, width: number, height: number, scale: number): IRoomRenderingCanvas
{
return new RoomSpriteCanvas(id, this, width, height, scale);
}
public removeCanvas(id: number): void
{
const existing = this._canvases.get(id);
if(!existing) return;
this._canvases.delete(id);
existing.dispose();
}
public get objects(): Map<number, IRoomObject>
{
return this._objects;
}
public get disposed(): boolean
{
return this._disposed;
}
public get roomObjectVariableAccurateZ(): string
{
return this._roomObjectVariableAccurateZ;
}
public set roomObjectVariableAccurateZ(z: string)
{
this._roomObjectVariableAccurateZ = z;
}
}
File diff suppressed because it is too large Load Diff
+142
View File
@@ -0,0 +1,142 @@
import { IRoomObjectSprite, RoomObjectSpriteData, RoomObjectSpriteType } from '@nitrots/api';
import { SortableSprite } from '../utils';
import { RoomObjectCacheItem } from './RoomObjectCacheItem';
export class RoomObjectCache
{
private static MAX_SIZE_FOR_AVG_COLOR: number = 200;
private _data: Map<string, RoomObjectCacheItem> = new Map();
private _roomObjectVariableAccurateZ: string;
constructor(accurateZ: string)
{
this._roomObjectVariableAccurateZ = accurateZ;
}
public dispose(): void
{
if(this._data)
{
for(const [key, item] of this._data.entries())
{
if(!item) continue;
this._data.delete(key);
item.dispose();
}
this._data = null;
}
}
public getObjectCache(k: string): RoomObjectCacheItem
{
let existing = this._data.get(k);
if(!existing)
{
existing = new RoomObjectCacheItem(this._roomObjectVariableAccurateZ);
this._data.set(k, existing);
}
return existing;
}
public removeObjectCache(k: string): void
{
const existing = this._data.get(k);
if(!existing) return;
this._data.delete(k);
existing.dispose();
}
public getSortableSpriteList(): RoomObjectSpriteData[]
{
const spriteData: RoomObjectSpriteData[] = [];
for(const item of this._data.values())
{
if(!item) continue;
const sprites = item.sprites && item.sprites.sprites;
if(!sprites || !sprites.length) continue;
for(const sprite of sprites)
{
if(!sprite) continue;
if((sprite.sprite.spriteType !== RoomObjectSpriteType.ROOM_PLANE) && (sprite.sprite.name !== ''))
{
const data = new RoomObjectSpriteData();
data.objectId = item.objectId;
data.x = sprite.x;
data.y = sprite.y;
data.z = sprite.z;
data.name = sprite.sprite.name || '';
data.flipH = sprite.sprite.flipH;
data.alpha = sprite.sprite.alpha;
data.color = sprite.sprite.color.toString();
data.blendMode = sprite.sprite.blendMode.toString();
data.width = sprite.sprite.width;
data.height = sprite.sprite.height;
data.type = sprite.sprite.type;
data.posture = sprite.sprite.posture;
const isSkewed = this.isSkewedSprite(sprite.sprite);
if(isSkewed) data.skew = (((sprite.sprite.direction % 4) === 0) ? -0.5 : 0.5);
if(((((isSkewed || (sprite.name.indexOf('%image.library.url%') >= 0)) || (sprite.name.indexOf('%group.badge.url%') >= 0)) && (data.width <= RoomObjectCache.MAX_SIZE_FOR_AVG_COLOR)) && (data.height <= RoomObjectCache.MAX_SIZE_FOR_AVG_COLOR)))
{
//data.color = Canvas._Str_23439(sprite.sprite.texture).toString();
if(sprite.sprite.name.indexOf('external_image_wallitem') === 0)
{
data.frame = true;
}
}
spriteData.push(data);
}
}
}
if(!spriteData || !spriteData.length) return null;
return spriteData;
}
private isSkewedSprite(k: IRoomObjectSprite): boolean
{
if(!k.type) return false;
if((k.type.indexOf('external_image_wallitem') === 0) && (k.tag === 'THUMBNAIL')) return true;
if((k.type.indexOf('guild_forum') === 0) && (k.tag === 'THUMBNAIL')) return true;
return false;
}
public getPlaneSortableSprites(): SortableSprite[]
{
const sprites: SortableSprite[] = [];
for(const item of this._data.values())
{
for(const sprite of item.sprites.sprites)
{
if(sprite.sprite.spriteType === RoomObjectSpriteType.ROOM_PLANE) sprites.push(sprite);
}
}
return sprites;
}
}
+52
View File
@@ -0,0 +1,52 @@
import { RoomObjectLocationCacheItem } from './RoomObjectLocationCacheItem';
import { RoomObjectSortableSpriteCacheItem } from './RoomObjectSortableSpriteCacheItem';
export class RoomObjectCacheItem
{
private _objectId: number;
private _location: RoomObjectLocationCacheItem;
private _sprites: RoomObjectSortableSpriteCacheItem;
constructor(accurateZ: string)
{
this._location = new RoomObjectLocationCacheItem(accurateZ);
this._sprites = new RoomObjectSortableSpriteCacheItem();
}
public dispose(): void
{
if(this._location)
{
this._location.dispose();
this._location = null;
}
if(this._sprites)
{
this._sprites.dispose();
this._sprites = null;
}
}
public get objectId(): number
{
return this._objectId;
}
public set objectId(k: number)
{
this._objectId = k;
}
public get location(): RoomObjectLocationCacheItem
{
return this._location;
}
public get sprites(): RoomObjectSortableSpriteCacheItem
{
return this._sprites;
}
}
@@ -0,0 +1,96 @@
import { IRoomGeometry, IRoomObject, IVector3D } from '@nitrots/api';
import { Vector3d } from '@nitrots/utils';
export class RoomObjectLocationCacheItem
{
private _roomObjectVariableAccurateZ: string;
private _location: Vector3d;
private _screenLocation: Vector3d;
private _locationChanged: boolean;
private _geometryUpdateId: number;
private _objectUpdateId: number;
constructor(accurateZ: string)
{
this._roomObjectVariableAccurateZ = accurateZ || '';
this._location = new Vector3d();
this._screenLocation = new Vector3d();
this._locationChanged = false;
this._geometryUpdateId = -1;
this._objectUpdateId = -1;
}
public dispose(): void
{
this._screenLocation = null;
}
public updateLocation(object: IRoomObject, geometry: IRoomGeometry): IVector3D
{
if(!object || !geometry) return null;
let locationChanged = false;
const location = object.getLocation();
if((geometry.updateId !== this._geometryUpdateId) || (object.updateCounter !== this._objectUpdateId))
{
this._objectUpdateId = object.updateCounter;
if((geometry.updateId !== this._geometryUpdateId) || (location.x !== this._location.x) || (location.y !== this._location.y) || (location.z !== this._location.z))
{
this._geometryUpdateId = geometry.updateId;
this._location.assign(location);
locationChanged = true;
}
}
this._locationChanged = locationChanged;
if(this._locationChanged)
{
const screenLocation = geometry.getScreenPosition(location);
if(!screenLocation) return null;
const accurateZ = object.model.getValue<number>(this._roomObjectVariableAccurateZ);
if(isNaN(accurateZ) || (accurateZ === 0))
{
const rounded = new Vector3d(Math.round(location.x), Math.round(location.y), location.z);
if((rounded.x !== location.x) || (rounded.y !== location.y))
{
const roundedScreen = geometry.getScreenPosition(rounded);
this._screenLocation.assign(screenLocation);
if(roundedScreen) this._screenLocation.z = roundedScreen.z;
}
else
{
this._screenLocation.assign(screenLocation);
}
}
else
{
this._screenLocation.assign(screenLocation);
}
this._screenLocation.x = Math.round(this._screenLocation.x);
this._screenLocation.y = Math.round(this._screenLocation.y);
}
return this._screenLocation;
}
public get locationChanged(): boolean
{
return this._locationChanged;
}
}
@@ -0,0 +1,68 @@
import { SortableSprite } from '../utils';
export class RoomObjectSortableSpriteCacheItem
{
private _sprites: SortableSprite[] = [];
private _updateId1: number = -1;
private _updateId2: number = -1;
private _isEmpty: boolean = false;
public dispose(): void
{
this.setSpriteCount(0);
}
public addSprite(sprite: SortableSprite): void
{
this._sprites.push(sprite);
}
public getSprite(index: number): SortableSprite
{
return this._sprites[index];
}
public needsUpdate(id1: number, id2: number): boolean
{
if((id1 === this._updateId1) && (id2 === this._updateId2)) return false;
this._updateId1 = id1;
this._updateId2 = id2;
return true;
}
public setSpriteCount(count: number): void
{
if(count < this._sprites.length)
{
let i = count;
while(i < this._sprites.length)
{
this._sprites[i]?.dispose();
i++;
}
this._sprites.splice(count, (this._sprites.length - count));
}
this._isEmpty = (this._sprites.length) ? false : true;
}
public get sprites(): SortableSprite[]
{
return this._sprites;
}
public get spriteCount(): number
{
return this._sprites.length;
}
public get isEmpty(): boolean
{
return this._isEmpty;
}
}
+4
View File
@@ -0,0 +1,4 @@
export * from './RoomObjectCache';
export * from './RoomObjectCacheItem';
export * from './RoomObjectLocationCacheItem';
export * from './RoomObjectSortableSpriteCacheItem';
+4
View File
@@ -0,0 +1,4 @@
export * from './RoomRenderer';
export * from './RoomSpriteCanvas';
export * from './cache';
export * from './utils';
@@ -0,0 +1,170 @@
import { AlphaTolerance } from '@nitrots/api';
import { GetRenderer } from '@nitrots/utils';
import { GlRenderTarget, Point, Sprite, Texture, TextureSource, WebGLRenderer } from 'pixi.js';
const BYTES_PER_PIXEL = 4;
export class ExtendedSprite extends Sprite
{
private _offsetX: number = 0;
private _offsetY: number = 0;
private _tag: string = '';
private _alphaTolerance: number = AlphaTolerance.MATCH_OPAQUE_PIXELS;
private _varyingDepth: boolean = false;
private _clickHandling: boolean = false;
private _updateId1: number = -1;
private _updateId2: number = -1;
public needsUpdate(updateId1: number, updateId2: number): boolean
{
if((this._updateId1 === updateId1) && (this._updateId2 === updateId2)) return false;
this._updateId1 = updateId1;
this._updateId2 = updateId2;
return true;
}
public setTexture(texture: Texture): void
{
if(!texture) texture = Texture.EMPTY;
if(texture === this.texture) return;
if(texture === Texture.EMPTY)
{
this._updateId1 = -1;
this._updateId2 = -1;
}
this.texture = texture;
}
public containsPoint(point: Point): boolean
{
if(!point || (this.alphaTolerance > 255) || !this.texture || (this.texture === Texture.EMPTY) || (this.blendMode !== 'normal')) return false;
point = new Point((point.x * this.scale.x), (point.y * this.scale.y));
if(!super.containsPoint(point)) return false;
const texture = this.texture;
const textureSource = this.texture.source;
//@ts-ignore
if((!textureSource || !textureSource.hitMap) && !ExtendedSprite.generateHitMapForTextureSource(textureSource)) return false;
//@ts-ignore
const hitMap = (textureSource.hitMap as U8intclampedArray);
let dx = (point.x + texture.frame.x);
let dy = (point.y + texture.frame.y);
if(this.texture.trim)
{
dx -= texture.trim.x;
dy -= texture.trim.y;
}
dx = (Math.round(dx) * textureSource.resolution);
dy = (Math.round(dy) * textureSource.resolution);
const index = (dx + dy * textureSource.width) * 4;
return (hitMap[index + 3] >= this.alphaTolerance);
}
private static generateHitMapForTextureSource(textureSource: TextureSource): boolean
{
if(!textureSource) return false;
const width = Math.max(Math.round(textureSource.width * textureSource.resolution), 1);
const height = Math.max(Math.round(textureSource.height * textureSource.resolution), 1);
const pixels = new Uint8Array(BYTES_PER_PIXEL * width * height);
const renderer = GetRenderer() as WebGLRenderer;
const renderTarget = renderer.renderTarget.getRenderTarget(textureSource);
const glRenterTarget = renderer.renderTarget.getGpuRenderTarget(renderTarget) as GlRenderTarget;
const gl = renderer.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, glRenterTarget.resolveTargetFramebuffer);
gl.readPixels(
0,
0,
width,
height,
gl.RGBA,
gl.UNSIGNED_BYTE,
pixels
);
//@ts-ignore
textureSource.hitMap = pixels;
return true;
}
public get offsetX(): number
{
return this._offsetX;
}
public set offsetX(offset: number)
{
this._offsetX = offset;
}
public get offsetY(): number
{
return this._offsetY;
}
public set offsetY(offset: number)
{
this._offsetY = offset;
}
public get tag(): string
{
return this._tag;
}
public set tag(tag: string)
{
this._tag = tag;
}
public get alphaTolerance(): number
{
return this._alphaTolerance;
}
public set alphaTolerance(tolerance: number)
{
this._alphaTolerance = tolerance;
}
public get varyingDepth(): boolean
{
return this._varyingDepth;
}
public set varyingDepth(flag: boolean)
{
this._varyingDepth = flag;
}
public get clickHandling(): boolean
{
return this._clickHandling;
}
public set clickHandling(flag: boolean)
{
this._clickHandling = flag;
}
}
@@ -0,0 +1,31 @@
export class ObjectMouseData
{
private _objectId: string;
private _spriteTag: string;
constructor()
{
this._objectId = '';
this._spriteTag = '';
}
public get objectId(): string
{
return this._objectId;
}
public set objectId(k: string)
{
this._objectId = k;
}
public get spriteTag(): string
{
return this._spriteTag;
}
public set spriteTag(k: string)
{
this._spriteTag = k;
}
}
@@ -0,0 +1,79 @@
import { IRoomObjectSprite, ISortableSprite } from '@nitrots/api';
export class SortableSprite implements ISortableSprite
{
public static Z_INFINITY: number = 100000000;
private _name: string;
private _sprite: IRoomObjectSprite;
private _x: number;
private _y: number;
private _z: number;
constructor()
{
this._name = '';
this._sprite = null;
this._x = 0;
this._y = 0;
this._z = 0;
}
public dispose(): void
{
this._z = -(SortableSprite.Z_INFINITY);
this._sprite = null;
}
public get name(): string
{
return this._name;
}
public set name(name: string)
{
this._name = name;
}
public get sprite(): IRoomObjectSprite
{
return this._sprite;
}
public set sprite(sprite: IRoomObjectSprite)
{
this._sprite = sprite;
}
public get x(): number
{
return this._x;
}
public set x(x: number)
{
this._x = x;
}
public get y(): number
{
return this._y;
}
public set y(y: number)
{
this._y = y;
}
public get z(): number
{
return this._z;
}
public set z(z: number)
{
this._z = z;
}
}
@@ -0,0 +1,3 @@
export * from './ExtendedSprite';
export * from './ObjectMouseData';
export * from './SortableSprite';