Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions client/src/core/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,18 +296,21 @@ export default class Renderer {
this._renderer.info.reset();
const pp = this._game.settingsManager.qualityPerfTradeoff.postProcessing;
if (pp?.outline || pp?.bloom || pp?.smaa) {
const hasOutlineTargets = !!pp.outline && this._game.entityManager.hasOutlines;
this._renderPass.camera = this._game.camera.activeCamera;
this._viewModelRenderPass.camera = this._game.camera.activeCamera;
this._viewModelRenderPass.enabled = this._firstPersonViewModelEntity !== undefined;
this._outlinePass.enabled = !!pp.outline;
this._bloomPass.enabled = !!pp.bloom;
this._smaaPass.enabled = !!pp.smaa;
if (pp.outline) {
if (hasOutlineTargets) {
this._outlinePass.camera = this._game.camera.activeCamera;
this._outlinePass.setOutlineTargets(this._game.entityManager.getOutlineTargets());
} else {
this._outlinePass.clearOutlineTargets();
}
this._effectComposer.render();
if (pp.outline) {
if (hasOutlineTargets) {
this._game.entityManager.clearOutlineTargets();
this._outlinePass.clearOutlineTargets();
}
Expand Down
7 changes: 4 additions & 3 deletions client/src/entities/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import {
Quaternion,
type QuaternionLike,
Texture,
Vector2,
Vector3,
type Vector3Like,
WebGLProgramParametersWithUniforms,
} from 'three';
import type { Vector2 } from 'three';
import type { GLTF } from 'three/addons/loaders/GLTFLoader.js'
import { type EntityId } from './EntityConstants';
import EntityStats from './EntityStats';
Expand Down Expand Up @@ -53,7 +53,6 @@ const MAX_UPDATE_SKIP_FRAMES = 4;
const NEAR_DISTANCE_SQUARED = 16 * 16; // 1 Chunk = 16 Blocks

// Working variables
const vec2 = new Vector2();
const corners: Vector3[] = new Array(8).fill(undefined).map(() => new Vector3());
const color = new Color();
const quaternion = new Quaternion();
Expand Down Expand Up @@ -2162,7 +2161,9 @@ export default class Entity {
// sync their visibility with the chunk. Otherwise, there could be cases where a chunk is invisible
// but the entity remains visible, which could make the entity appear to be floating in mid-air.
const coord = this.position;
this._distanceToCameraSquared = fromVec2.distanceToSquared(vec2.set(coord.x, coord.z));
const dx = fromVec2.x - coord.x;
const dz = fromVec2.y - coord.z;
this._distanceToCameraSquared = dx * dx + dz * dz;
this.visible = this._distanceToCameraSquared <= viewDistanceSquared;
if (this.visible) {
EntityStats.inViewDistanceCount++;
Expand Down
58 changes: 40 additions & 18 deletions client/src/entities/EntityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ const DEFAULT_OUTLINE_OPTIONS: OutlineOptions = {
export default class EntityManager {
private _game: Game;
private _entities: Map<EntityId, Entity | StaticEntity> = new Map();
private _dynamicEntities: Set<EntityId> = new Set();
private _dynamicEntities: Set<Entity> = new Set();
private _dynamicEntityList: Entity[] = [];
private _dynamicEntityListDirty: boolean = false;
private _outlines: Map<EntityId, OutlineOptions> = new Map();
private _outlineTargets: OutlineTarget[] = new Array(MAX_OUTLINES).fill(undefined).map(() => { return { object3d: null, options: null }; });
private _staticEnvironmentEntityManager: StaticEntityManager;
Expand All @@ -62,6 +64,7 @@ export default class EntityManager {

public get game(): Game { return this._game; }
public get count(): number { return this._entities.size; }
public get hasOutlines(): boolean { return this._outlines.size > 0; }
public get hasLightLevelVolumeUpdatedOnce(): boolean { return this._hasLightLevelVolumeUpdatedOnce; }

public getEntity(id: number): Entity | StaticEntity | undefined {
Expand Down Expand Up @@ -157,28 +160,44 @@ export default class EntityManager {
);
}

private _getDynamicEntityList(): Entity[] {
if (!this._dynamicEntityListDirty) {
return this._dynamicEntityList;
}

this._dynamicEntityList.length = 0;
for (const entity of this._dynamicEntities) {
this._dynamicEntityList.push(entity);
}
this._dynamicEntityListDirty = false;

return this._dynamicEntityList;
}

private _onAnimate = (payload: RendererEventPayload.IAnimate): void => {
EntityStats.reset();
EntityStats.count = this._entities.size;
const dynamicEntities = this._getDynamicEntityList();

// Entities are updated using a multi-pass approach.

// First pass: Update local position and rotation
for (const entityId of this._dynamicEntities) {
this._entities.get(entityId)!.update(payload.frameDeltaS);
for (let i = 0; i < dynamicEntities.length; i++) {
dynamicEntities[i].update(payload.frameDeltaS);
}

// Second pass: Apply view distance.
// To avoid subsequent updates for invisible entities, perform an early check
// using the updated local position.
if (this._game.settingsManager.qualityPerfTradeoff.viewDistance.enabled) {
// View Distance handling. Also refer to the comment in ChunkManager
const viewDistanceSquared = Math.pow(this._game.renderer.viewDistance, 2);
const viewDistance = this._game.renderer.viewDistance;
const viewDistanceSquared = viewDistance * viewDistance;
const cameraPos = this._game.camera.activeCamera.position;

fromVec2.set(cameraPos.x, cameraPos.z);
for (const entityId of this._dynamicEntities) {
this._entities.get(entityId)!.applyViewDistance(viewDistanceSquared, fromVec2);
for (let i = 0; i < dynamicEntities.length; i++) {
dynamicEntities[i].applyViewDistance(viewDistanceSquared, fromVec2);
}
} else {
// If ViewDistance can be toggled dynamically in the future, we need to
Expand All @@ -190,30 +209,30 @@ export default class EntityManager {
const camera = this._game.camera.activeCamera;
frustum.setFromProjectionMatrix(projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));

for (const entityId of this._dynamicEntities) {
this._entities.get(entityId)!.applyFrustumCulling(frustum);
for (let i = 0; i < dynamicEntities.length; i++) {
dynamicEntities[i].applyFrustumCulling(frustum);
}

// Forth pass: Update Animation and Local matrix
const frameCount = this._game.performanceMetricsManager.frameCount;
for (const entityId of this._dynamicEntities) {
this._entities.get(entityId)!.updateAnimationAndLocalMatrix(payload.frameDeltaS, frameCount);
for (let i = 0; i < dynamicEntities.length; i++) {
dynamicEntities[i].updateAnimationAndLocalMatrix(payload.frameDeltaS, frameCount);
}

// Fifth pass: World matrices update.
// Considering parent-child relationships, the WorldMatrix must be updated only
// after the LocalMatrix of all entities has been updated.
for (const entityId of this._dynamicEntities) {
this._entities.get(entityId)!.updateWorldMatrices(this._hasLightLevelVolumeUpdatedOnce);
for (let i = 0; i < dynamicEntities.length; i++) {
dynamicEntities[i].updateWorldMatrices(this._hasLightLevelVolumeUpdatedOnce);
}

// Sixth pass: Light level update
// LightLevel is only needed when a Light Emission Block is placed. However, in most maps, Light Emission
// Blocks are probably not placed at all. Therefore, detects whether a Light Level Volume has ever been
// generated by a Light Emission Block is placed, and only then perform Light Level update processing.
if (this._hasLightLevelVolumeUpdatedOnce) {
for (const entityId of this._dynamicEntities) {
this._entities.get(entityId)!.updateLightLevel(this._needsLightLevelRefresh);
for (let i = 0; i < dynamicEntities.length; i++) {
dynamicEntities[i].updateLightLevel(this._needsLightLevelRefresh);
}
if (this._needsLightLevelRefresh) {
this._staticEnvironmentEntityManager.updateLightLevel();
Expand All @@ -223,8 +242,8 @@ export default class EntityManager {

// Seventh pass: Sky light update
// Sky light is always available and doesn't depend on light emission blocks
for (const entityId of this._dynamicEntities) {
this._entities.get(entityId)!.updateSkyLight(this._needsSkyLightRefresh, payload.frameDeltaS);
for (let i = 0; i < dynamicEntities.length; i++) {
dynamicEntities[i].updateSkyLight(this._needsSkyLightRefresh, payload.frameDeltaS);
}
if (this._needsSkyLightRefresh) {
this._staticEnvironmentEntityManager.updateSkyLight();
Expand Down Expand Up @@ -296,7 +315,8 @@ export default class EntityManager {
} else {
const shouldSuppressAnimations = deserializedEntity.isEnvironmental ? this._shouldSuppressEnvironmentAnimations : false;
entity = new Entity(this._game, entityData, shouldSuppressAnimations);
this._dynamicEntities.add(entity.id);
this._dynamicEntities.add(entity);
this._dynamicEntityListDirty = true;
}

this._entities.set(entity.id, entity);
Expand All @@ -320,7 +340,9 @@ export default class EntityManager {

entity.release();
this._entities.delete(entity.id);
this._dynamicEntities.delete(entity.id);
if (entity instanceof Entity && this._dynamicEntities.delete(entity)) {
this._dynamicEntityListDirty = true;
}
this._outlines.delete(entity.id);
}

Expand Down
Loading