@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
251 lines (207 loc) • 5.68 kB
JavaScript
import { LightType } from "../LightType.js";
import { AmbientLight, DirectionalLight, PointLight, SpotLight } from "three";
import { assert } from "../../../../../core/assert.js";
import { light2type } from "./light2type.js";
/**
*
* @param {Light} light
*/
function disableLight(light) {
light.intensity = 0;
if (typeof light.castShadow !== "undefined") {
light.castShadow = false;
}
}
/**
*
* @param {LightType} type
*/
function createLight(type) {
switch (type) {
case LightType.POINT:
return new PointLight(0x000000, 0, 0, 1);
case LightType.AMBIENT:
return new AmbientLight(0x000000, 0);
case LightType.DIRECTION:
return new DirectionalLight(0x000000, 0);
case LightType.SPOT:
return new SpotLight(0x000000, 0, 0, 0, 0, 1);
default:
throw new Error('Unknown light type: ' + type);
}
}
/**
* @template T,U
* @param {Map<T,U[]>} map
* @param {T} key
* @returns U[]
*/
function ensureBucket(map, key) {
let elements = map.get(key);
if (elements === undefined) {
elements = [];
map.set(key, elements);
}
return elements;
}
export class ThreeLightCache {
constructor() {
/**
*
* @type {Map<LightType, Light[]>}
* @private
*/
this.__used = new Map();
/**
*
* @type {Map<LightType, Light[]>}
* @private
*/
this.__available = new Map();
/**
*
* @type {null}
* @private
*/
this.__scene = null;
}
/**
*
* @param {LightType} type
* @returns {Light[]}
* @private
*/
__getUsed(type) {
return ensureBucket(this.__used, type);
}
/**
*
* @param {LightType} type
* @returns {Light[]}
* @private
*/
__getAvailable(type) {
return ensureBucket(this.__available, type);
}
/**
*
* @param {LightType} type
* @returns {Light}
* @private
*/
__create(type) {
const light = createLight(type);
if (this.__scene !== null) {
this.__scene.add(light);
}
return light;
}
/**
*
* @param {Scene} scene Three.js scene
*/
attach(scene) {
if (this.__scene === scene) {
// nothing to do
return;
}
if (this.__scene !== null) {
this.detach();
}
this.__scene = scene;
// add all lights
this.__traverseAllLight(scene.add, scene);
}
detach() {
const scene = this.__scene;
if (scene === null) {
// not connected
return;
}
// disconnect all lights
this.__traverseAllLight(scene.remove, scene);
this.__scene = null;
}
/**
*
* @param {function(light:(Light|Object3D)):*} visitor
* @param {*} [thisArg]
* @private
*/
__traverseAllLight(visitor, thisArg) {
for (const [type, lights] of this.__used) {
lights.forEach(visitor, thisArg);
}
for (const [type, lights] of this.__available) {
lights.forEach(visitor, thisArg);
}
}
/**
*
* @param {LightType} type
* @returns {Light}
*/
obtain(type) {
// check for available light of the type
const available_lights = this.__getAvailable(type);
let light;
if (available_lights.length <= 0) {
// no available lights, create one
light = this.__create(type);
} else {
// found some available lights, take one
light = available_lights.pop();
}
// move to "used" bucket
const used = this.__getUsed(type);
used.push(light);
return light;
}
/**
*
* @param {Light} light
*/
release(light) {
assert.defined(light, 'light');
assert.notNull(light, 'light');
const type = light2type(light);
const used_lights = this.__getUsed(type);
const index = used_lights.indexOf(light);
if (index === -1) {
// light is not in use
return false;
}
// disable light and move it to available
disableLight(light);
// removed from "used"
used_lights.splice(index, 1);
const available_lights = this.__getAvailable(type);
// add to "available"
available_lights.push(light);
return true;
}
/**
*
* @param {LightType} type
* @returns {number}
*/
countTotalByType(type) {
const available = this.__getAvailable(type);
const used = this.__getUsed(type);
return available.length + used.length;
}
/**
* Pre-allocate up to specified number of lights of a given type. If number of allocated light already matches or exceeds that count, nothing will happen
* @param {LightType} type
* @param {number} count
*/
reserve(type, count) {
assert.isNonNegativeInteger(count, 'count');
let i = this.countTotalByType(type);
for (; i < count; i++) {
const light = this.__create(type);
const available = this.__getAvailable(type);
available.push(light);
}
}
}