2025-12-08 21:26:35 +08:00
|
|
|
|
import { EntitySystem, Matcher, ECSSystem } from '@esengine/ecs-framework';
|
|
|
|
|
|
import type { Entity } from '@esengine/ecs-framework';
|
2025-12-06 13:56:01 +08:00
|
|
|
|
import { ChunkComponent } from '../components/ChunkComponent';
|
|
|
|
|
|
import { EChunkState } from '../types';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 区块裁剪系统
|
|
|
|
|
|
*
|
|
|
|
|
|
* Handles visibility culling for chunk entities.
|
|
|
|
|
|
*
|
|
|
|
|
|
* 处理区块实体的可见性裁剪。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@ECSSystem('ChunkCulling', { updateOrder: -40 })
|
|
|
|
|
|
export class ChunkCullingSystem extends EntitySystem {
|
|
|
|
|
|
private _viewMinX: number = 0;
|
|
|
|
|
|
private _viewMinY: number = 0;
|
|
|
|
|
|
private _viewMaxX: number = 1920;
|
|
|
|
|
|
private _viewMaxY: number = 1080;
|
|
|
|
|
|
private _padding: number = 100;
|
|
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
super(Matcher.all(ChunkComponent));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置视口边界
|
|
|
|
|
|
*
|
|
|
|
|
|
* Set viewport bounds for culling.
|
|
|
|
|
|
*/
|
|
|
|
|
|
setViewBounds(minX: number, minY: number, maxX: number, maxY: number): void {
|
|
|
|
|
|
this._viewMinX = minX;
|
|
|
|
|
|
this._viewMinY = minY;
|
|
|
|
|
|
this._viewMaxX = maxX;
|
|
|
|
|
|
this._viewMaxY = maxY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置裁剪边距
|
|
|
|
|
|
*
|
|
|
|
|
|
* Set padding for culling bounds.
|
|
|
|
|
|
*/
|
|
|
|
|
|
setPadding(padding: number): void {
|
|
|
|
|
|
this._padding = padding;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected process(entities: readonly Entity[]): void {
|
|
|
|
|
|
const cullMinX = this._viewMinX - this._padding;
|
|
|
|
|
|
const cullMinY = this._viewMinY - this._padding;
|
|
|
|
|
|
const cullMaxX = this._viewMaxX + this._padding;
|
|
|
|
|
|
const cullMaxY = this._viewMaxY + this._padding;
|
|
|
|
|
|
|
|
|
|
|
|
for (const entity of entities) {
|
|
|
|
|
|
const chunk = entity.getComponent(ChunkComponent);
|
|
|
|
|
|
if (!chunk) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (chunk.state !== EChunkState.Loaded) continue;
|
|
|
|
|
|
|
|
|
|
|
|
const bounds = chunk.bounds;
|
|
|
|
|
|
const isVisible = this.boundsIntersect(
|
|
|
|
|
|
bounds.minX,
|
|
|
|
|
|
bounds.minY,
|
|
|
|
|
|
bounds.maxX,
|
|
|
|
|
|
bounds.maxY,
|
|
|
|
|
|
cullMinX,
|
|
|
|
|
|
cullMinY,
|
|
|
|
|
|
cullMaxX,
|
|
|
|
|
|
cullMaxY
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
entity.enabled = isVisible;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查边界是否相交
|
|
|
|
|
|
*
|
|
|
|
|
|
* Check if two axis-aligned bounds intersect.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private boundsIntersect(
|
|
|
|
|
|
aMinX: number,
|
|
|
|
|
|
aMinY: number,
|
|
|
|
|
|
aMaxX: number,
|
|
|
|
|
|
aMaxY: number,
|
|
|
|
|
|
bMinX: number,
|
|
|
|
|
|
bMinY: number,
|
|
|
|
|
|
bMaxX: number,
|
|
|
|
|
|
bMaxY: number
|
|
|
|
|
|
): boolean {
|
|
|
|
|
|
return aMinX < bMaxX && aMaxX > bMinX && aMinY < bMaxY && aMaxY > bMinY;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新视口(从相机)
|
|
|
|
|
|
*
|
|
|
|
|
|
* Update viewport from camera position and size.
|
|
|
|
|
|
*/
|
|
|
|
|
|
updateFromCamera(cameraX: number, cameraY: number, viewWidth: number, viewHeight: number): void {
|
|
|
|
|
|
const halfWidth = viewWidth * 0.5;
|
|
|
|
|
|
const halfHeight = viewHeight * 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
this.setViewBounds(
|
|
|
|
|
|
cameraX - halfWidth,
|
|
|
|
|
|
cameraY - halfHeight,
|
|
|
|
|
|
cameraX + halfWidth,
|
|
|
|
|
|
cameraY + halfHeight
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|