@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
267 lines (199 loc) • 8.07 kB
JavaScript
import { Frustum } from "three";
import Vector3 from "../../../core/geom/Vector3.js";
import Vector4 from "../../../core/geom/Vector4.js";
import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
import { CompositingStages } from "../../graphics/composit/CompositingStages.js";
import { CompositLayer } from "../../graphics/composit/CompositLayer.js";
import { Camera } from "../../graphics/ecs/camera/Camera.js";
import {
compute_perspective_camera_focal_position
} from "../../graphics/ecs/camera/compute_perspective_camera_focal_position.js";
import { frustum_from_camera } from "../../graphics/ecs/camera/frustum_from_camera.js";
import WaterSystem from "../../graphics/ecs/water/WaterSystem.js";
import { VisibilityFilter } from "../../graphics/render/visibility/VisibilityFilter.js";
import { StandardFrameBuffers } from "../../graphics/StandardFrameBuffers.js";
import { BlendingType } from "../../graphics/texture/sampler/BlendingType.js";
import { System } from "../System.js";
import Terrain from "../terrain/ecs/Terrain.js";
import { FogOfWar } from "./FogOfWar.js";
import { FogOfWarRenderer } from "./shader/FogOfWarRenderer.js";
const frustum = new Frustum();
export class FogOfWarSystem extends System {
/**
*
* @param {GraphicsEngine} graphics
*/
constructor(graphics) {
super();
/**
*
* @type {GraphicsEngine}
*/
this.graphics = graphics;
this.dependencies = [FogOfWar, Terrain];
this.components_used = [
ResourceAccessSpecification.from(FogOfWar, ResourceAccessKind.Read | ResourceAccessKind.Write)
];
this.visibilityFilter = this.buildVisibilityFilter();
//turn off by default
this.visibilityFilter.enabled = false;
/**
*
* @type {CompositLayer}
*/
this.compositLayer = null;
this.componentCount = 0;
}
/**
*
* @returns {VisibilityFilter}
*/
buildVisibilityFilter() {
/**
*
* @type {FogOfWar|null}
*/
let fog = null;
const projectionPlane = new Vector4();
const cameraFocalPoint = new Vector3();
const result = new VisibilityFilter({
name: 'fog of war',
layerPredicate: (layer) => {
return (layer !== this.renderLayer) && (layer.name !== WaterSystem.RENDER_LAYER_NAME);
},
objectPredicateInitialize: (camera) => {
compute_perspective_camera_focal_position(camera, cameraFocalPoint);
fog = null;
const dataset = this.entityManager.dataset;
if (dataset !== null) {
dataset.traverseComponents(FogOfWar, (fow) => {
fog = fow;
// stop traversal
return false;
});
//find current projection plane
dataset.traverseComponents(Camera, c => {
if (c.active) {
frustum_from_camera(c.object, frustum);
const nearPlane = frustum.planes[4];
const ppN = nearPlane.normal;
projectionPlane.set(ppN.x, ppN.y, ppN.z, nearPlane.constant);
return false;
}
});
}
},
objectPredicateFinalize: () => {
fog = null;
},
/**
*
* @param {AABB3} aabb
* @returns {boolean}
*/
objectPredicateExecute: function (aabb) {
return fog.computeAABBVisibility(aabb, cameraFocalPoint, 1);
}
});
return result;
}
/**
*
* @param {EntityManager} entityManager
*/
async startup(entityManager) {
this.entityManager = entityManager;
const graphics = this.graphics;
const compositerLayer = graphics.layerComposer.createLayer({}, BlendingType.Normal);
compositerLayer.name = 'Fog of War';
compositerLayer.stage = CompositingStages.POST_TRANSPARENT;
// Fog of war should be drawn last typically, over everything else, so we set order to a large number
compositerLayer.order = 1000;
compositerLayer.enabled = false;
this.compositLayer = compositerLayer;
const renderTarget = compositerLayer.renderTarget;
this.renderTarget = renderTarget;
const fogOfWarRenderer = new FogOfWarRenderer();
// renderer.visibilitySet.addFilter(this.visibilityFilter);
let renderingFog = false;
graphics.on.preRender.add(function (renderer, camera, scene) {
const dataset = entityManager.dataset;
const depthFrameBuffer = graphics.frameBuffers.getById(StandardFrameBuffers.ColorAndDepth);
renderingFog = false;
/**
*
* @param {FogOfWar} fow
*/
function drawFow(fow) {
renderingFog = true;
fogOfWarRenderer.setDepthBuffer(depthFrameBuffer.renderTarget.depthTexture);
fogOfWarRenderer.setFogBuffer(fow.texture);
fogOfWarRenderer.setResolution(fow.size.x, fow.size.y);
fogOfWarRenderer.setUvTransformFromFog(fow);
fogOfWarRenderer.setFogColor(fow.color);
//stop traversal
return false;
}
if (dataset !== null) {
dataset.traverseComponents(FogOfWar, drawFow);
if (!renderingFog) {
compositerLayer.disable();
} else {
compositerLayer.enable();
depthFrameBuffer.referenceCount++;
}
}
});
graphics.on.buffersRendered.add(function (renderer, camera, scene) {
if (renderingFog) {
fogOfWarRenderer.render(renderer, camera, scene, compositerLayer.renderTarget);
}
});
graphics.on.postRender.add(function (renderer, camera, scene) {
if (renderingFog) {
const depthFrameBuffer = graphics.frameBuffers.getById(StandardFrameBuffers.ColorAndDepth);
depthFrameBuffer.referenceCount--;
}
});
}
async shutdown(entityManager) {
this.graphics.layerComposer.remove(this.compositLayer);
}
/**
*
* @param {FogOfWar} fow
* @param {Terrain} terrain
* @param {number} entity
*/
link(fow, terrain, entity) {
/**
*
* @type {Vector2}
*/
const terrainSize = terrain.size;
fow.resize(terrainSize.x, terrainSize.y, terrain.gridScale);
this.componentCount++;
if (!this.visibilityFilter.enabled) {
this.visibilityFilter.enabled = true;
}
this.compositLayer.enabled = true;
}
unlink(fow, terrain, entity) {
this.componentCount--;
if (this.componentCount <= 0) {
//disable visibility filter
this.visibilityFilter.enabled = false;
this.compositLayer.enabled = false;
}
}
update(timeDelta) {
const entityManager = this.entityManager;
const dataset = entityManager.dataset;
if (dataset !== null) {
dataset.traverseComponents(FogOfWar, function (fow) {
fow.update(timeDelta);
});
}
}
}